mirror of
https://github.com/JHUAPL/kaipy.git
synced 2026-01-08 23:38:11 -05:00
Merged in dashboard (pull request #16)
Dashboard Approved-by: Nikhil Rao
This commit is contained in:
@@ -25,18 +25,18 @@ mpiCol = "deepskyblue"
|
||||
|
||||
jMax = 10.0 #Max current for contours
|
||||
|
||||
eMax = 5.0 #Max current for contours
|
||||
eMax = 5.0 #Max current for contours
|
||||
|
||||
#Default pressure colorbar
|
||||
vP = kv.genNorm(vMin=1.0e-2,vMax=10.0,doLog=True)
|
||||
szStrs = ['small','std','big','bigger','fullD','dm']
|
||||
szBds = {}
|
||||
szBds["std"] = [-40.0 ,20.0,2.0]
|
||||
szBds["big"] = [-100.0,20.0,2.0]
|
||||
szBds["std"] = [-40.0 ,20.0,2.0]
|
||||
szBds["big"] = [-100.0,20.0,2.0]
|
||||
szBds["bigger"] = [-200.0,25.0,2.0]
|
||||
szBds["fullD"] = [-300.0,30.0,3.0] # full domain for double res
|
||||
szBds["small"] = [-10.0 , 5.0,2.0]
|
||||
szBds["dm"] = [-30.0 ,10.0,40.0/15.0]
|
||||
szBds["fullD"] = [-300.0,30.0,3.0] # full domain for double res
|
||||
szBds["small"] = [-10.0 , 5.0,2.0]
|
||||
szBds["dm"] = [-30.0 ,10.0,40.0/15.0]
|
||||
|
||||
#Add different size options to argument
|
||||
def AddSizeArgs(parser):
|
||||
@@ -74,10 +74,10 @@ def GetSizeBds(args):
|
||||
|
||||
return xyBds
|
||||
|
||||
#Plot absolute error in the requested, or given, equatorial field
|
||||
def PlotEqErrAbs(gsphP, gsphO, nStp, xyBds, Ax, fieldNames, AxCB=None, doClear=True, doDeco=True, vMin=1e-9, vMax=1e-4, doLog=True, doVerb=True):
|
||||
#Plot absolute error in the requested, or given, equatorial or meridional field
|
||||
def PlotErrAbs(gsphP, gsphO, nStp, xyBds, Ax, fieldNames, AxCB=None, doClear=True, doDeco=True, vMin=1e-9, vMax=1e-4, doLog=True, doVerb=True, doEq=True):
|
||||
"""
|
||||
PlotEqErrAbs function plots the absolute error between two gsph objects.
|
||||
PlotErrAbs function plots the absolute error between two gsph objects.
|
||||
|
||||
Args:
|
||||
gsphP (gsph): The gsph object representing the predicted values.
|
||||
@@ -93,6 +93,7 @@ def PlotEqErrAbs(gsphP, gsphO, nStp, xyBds, Ax, fieldNames, AxCB=None, doClear=T
|
||||
vMax (float, optional): The maximum value for the colorbar. (default: 1e-4)
|
||||
doLog (bool, optional): Whether to use logarithmic scale for the colorbar. (default: True)
|
||||
doVerb (bool, optional): Whether to print verbose output. (default: True)
|
||||
doEq (bool, optional): Whether to plot equatorial or meridional (default: True)
|
||||
|
||||
Returns:
|
||||
dataAbs (ndarray): The absolute error data.
|
||||
@@ -118,8 +119,8 @@ def PlotEqErrAbs(gsphP, gsphO, nStp, xyBds, Ax, fieldNames, AxCB=None, doClear=T
|
||||
Ax.clear()
|
||||
dataAbs = None
|
||||
for fn in fieldNames:
|
||||
dataP = gsphP.EggSlice(fn, nStp, doEq=True, doVerb=doVerb)
|
||||
dataO = gsphO.EggSlice(fn, nStp, doEq=True, doVerb=doVerb)
|
||||
dataP = gsphP.EggSlice(fn, nStp, doEq=doEq, doVerb=doVerb)
|
||||
dataO = gsphO.EggSlice(fn, nStp, doEq=doEq, doVerb=doVerb)
|
||||
if dataAbs is None:
|
||||
dataAbs = np.square(dataO - dataP)
|
||||
else:
|
||||
@@ -132,13 +133,16 @@ def PlotEqErrAbs(gsphP, gsphO, nStp, xyBds, Ax, fieldNames, AxCB=None, doClear=T
|
||||
if doDeco:
|
||||
kv.addEarth2D(ax=Ax)
|
||||
Ax.set_xlabel('SM-X [Re]')
|
||||
Ax.set_ylabel('SM-Y [Re]')
|
||||
if doEq:
|
||||
Ax.set_ylabel('SM-Y [Re]')
|
||||
else:
|
||||
Ax.set_ylabel('SM-Z [Re]')
|
||||
return dataAbs
|
||||
|
||||
#Plot relative error in the requested, or given, equatorial field
|
||||
def PlotEqErrRel(gsphP, gsphO, nStp, xyBds, Ax, fieldNames, AxCB=None, doClear=True, doDeco=True, vMin=1e-16, vMax=1, doLog=True, doVerb=True):
|
||||
#Plot relative error in the requested, or given, equatorial or meridional field
|
||||
def PlotErrRel(gsphP, gsphO, nStp, xyBds, Ax, fieldNames, AxCB=None, doClear=True, doDeco=True, vMin=1e-16, vMax=1, doLog=True, doVerb=True, doEq=True):
|
||||
"""
|
||||
PlotEqErrRel function plots the relative error between two gsph objects.
|
||||
PlotErrRel function plots the relative error between two gsph objects.
|
||||
|
||||
Args:
|
||||
gsphP (gsph): The gsph object representing the predicted values.
|
||||
@@ -154,6 +158,7 @@ def PlotEqErrRel(gsphP, gsphO, nStp, xyBds, Ax, fieldNames, AxCB=None, doClear=T
|
||||
vMax (float, optional): The maximum value for the colorbar. (default: 1)
|
||||
doLog (bool, optional): Whether to use logarithmic scale for the colorbar. (default: True)
|
||||
doVerb (bool, optional): Whether to print verbose output. (default: True)
|
||||
doEq (bool, optional): Whether to plot equatorial or meridional (default: True)
|
||||
|
||||
Returns:
|
||||
dataRel (ndarray): The relative error data.
|
||||
@@ -180,8 +185,8 @@ def PlotEqErrRel(gsphP, gsphO, nStp, xyBds, Ax, fieldNames, AxCB=None, doClear=T
|
||||
dataAbs = None
|
||||
dataBase = None
|
||||
for fn in fieldNames:
|
||||
dataP = gsphP.EggSlice(fn, nStp, doEq=True, doVerb=doVerb)
|
||||
dataO = gsphO.EggSlice(fn, nStp, doEq=True, doVerb=doVerb)
|
||||
dataP = gsphP.EggSlice(fn, nStp, doEq=doEq, doVerb=doVerb)
|
||||
dataO = gsphO.EggSlice(fn, nStp, doEq=doEq, doVerb=doVerb)
|
||||
if dataAbs is None:
|
||||
dataBase = np.square(dataP)
|
||||
dataAbs = np.square(dataO - dataP)
|
||||
@@ -200,7 +205,10 @@ def PlotEqErrRel(gsphP, gsphO, nStp, xyBds, Ax, fieldNames, AxCB=None, doClear=T
|
||||
if doDeco:
|
||||
kv.addEarth2D(ax=Ax)
|
||||
Ax.set_xlabel('SM-X [Re]')
|
||||
Ax.set_ylabel('SM-Y [Re]')
|
||||
if doEq:
|
||||
Ax.set_ylabel('SM-Y [Re]')
|
||||
else:
|
||||
Ax.set_ylabel('SM-Z [Re]')
|
||||
return dataRel
|
||||
|
||||
#Plot absolute error along the requested logical axis
|
||||
|
||||
31
kaipy/kaixml.py
Normal file
31
kaipy/kaixml.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Standard modules
|
||||
import os
|
||||
import json
|
||||
import datetime
|
||||
|
||||
# Third-party modules
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
def getXmlElement(E, name, caseInsensitive=True):
|
||||
if E is None:
|
||||
return None
|
||||
for element in E.iter():
|
||||
if caseInsensitive:
|
||||
if element.tag.casefold() == name.casefold():
|
||||
return element
|
||||
else:
|
||||
if element.tag == name:
|
||||
return element
|
||||
return None
|
||||
|
||||
def getXmlAttribute(E, name, caseInsensitive=True):
|
||||
if E is None:
|
||||
return None
|
||||
for attr_name,attr_value in E.attrib.items():
|
||||
if caseInsensitive:
|
||||
if attr_name.casefold() == name.casefold():
|
||||
return attr_value
|
||||
else:
|
||||
if attr_name == name:
|
||||
return attr_value
|
||||
return None
|
||||
@@ -43,14 +43,14 @@ def makeMovie(frame_dir,movie_name):
|
||||
return
|
||||
|
||||
cmd = [
|
||||
ffmpegExe, "-nostdin", "-i", frame_pattern,
|
||||
"-vcodec", "libx264", "-crf", "14", "-profile:v", "high", "-pix_fmt", "yuv420p",
|
||||
movie_file,"-y"
|
||||
ffmpegExe, "-nostdin", "-i", frame_pattern,
|
||||
"-vcodec", "libx264", "-crf", "14", "-profile:v", "high", "-pix_fmt", "yuv420p",
|
||||
movie_file,"-y"
|
||||
]
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
# python allows changes by reference to the errTimes,errListRel, errListAbs lists
|
||||
def makeImage(i,gsph1,gsph2,tOut,doVerb,xyBds,fnList,oDir,errTimes,errListRel,errListAbs,cv,dataCounter, vO, figSz, noMPI, noLog, fieldNames):
|
||||
def makeImage(i,gsph1,gsph2,tOut,doVerb,doEq,logAx,xyBds,fnList,oDir,errTimes,errListRel,errListAbs,cv,dataCounter, vO, figSz, noMPI, noLog, fieldNames):
|
||||
if doVerb:
|
||||
print("Making image %d"%(i))
|
||||
#Convert time (in seconds) to Step #
|
||||
@@ -77,23 +77,45 @@ def makeImage(i,gsph1,gsph2,tOut,doVerb,xyBds,fnList,oDir,errTimes,errListRel,er
|
||||
AxB2.clear()
|
||||
|
||||
#plot upper left msph error
|
||||
mviz.PlotEqErrRel(gsph1,gsph2,nStp,xyBds,AxTL,fnList,AxCB=AxCT,doVerb=doVerb)
|
||||
AxTL.set_title("Equatorial Slice of Relative Error")
|
||||
mviz.PlotErrRel(gsph1,gsph2,nStp,xyBds,AxTL,fnList,AxCB=AxCT,doVerb=doVerb,doEq=doEq)
|
||||
if doEq:
|
||||
AxTL.set_title("Equatorial Slice of Relative Error")
|
||||
else:
|
||||
AxTL.set_title("Meridional Slice of Relative Error")
|
||||
|
||||
#plot upper right k-axis error
|
||||
mviz.PlotLogicalErrRel(gsph1,gsph2,nStp,AxTR,fnList,2,doVerb=doVerb)
|
||||
AxTR.set_title("Per-Cell Relative Error along K-Axis")
|
||||
#plot upper right cumulative logical error
|
||||
mviz.PlotLogicalErrRel(gsph1,gsph2,nStp,AxTR,fnList,logAx,doVerb=doVerb)
|
||||
if logAx == 0:
|
||||
AxTR.set_title("Per-Cell Relative Error along I-Axis")
|
||||
elif logAx == 1:
|
||||
AxTR.set_title("Per-Cell Relative Error along J-Axis")
|
||||
else:
|
||||
AxTR.set_title("Per-Cell Relative Error along K-Axis")
|
||||
if (not noMPI):
|
||||
#plot I-MPI decomp on logical plot
|
||||
if(gsph2.Ri > 1):
|
||||
if(gsph2.Ri > 1 and logAx != 0):
|
||||
for im in range(gsph2.Ri):
|
||||
i0 = im*gsph2.dNi
|
||||
AxTR.plot([i0, i0],[0, gsph2.Nj],"deepskyblue",linewidth=0.25,alpha=0.5)
|
||||
if logAx == 1:
|
||||
AxTR.plot([i0, i0],[0, gsph2.Nk],"deepskyblue",linewidth=0.25,alpha=0.5)
|
||||
else:
|
||||
AxTR.plot([i0, i0],[0, gsph2.Nj],"deepskyblue",linewidth=0.25,alpha=0.5)
|
||||
#plot J-MPI decomp on logical plot
|
||||
if (gsph2.Rj>1):
|
||||
if (gsph2.Rj>1 and logAx != 1):
|
||||
for jm in range(1,gsph2.Rj):
|
||||
j0 = jm*gsph2.dNj
|
||||
AxTR.plot([0, gsph2.Ni],[j0, j0],"deepskyblue",linewidth=0.25,alpha=0.5)
|
||||
if logAx == 0:
|
||||
AxTR.plot([j0, j0],[0, gsph2.Nk],"deepskyblue",linewidth=0.25,alpha=0.5)
|
||||
else:
|
||||
AxTR.plot([0, gsph2.Ni],[j0, j0],"deepskyblue",linewidth=0.25,alpha=0.5)
|
||||
#plot K-MPI decomp on logical plot
|
||||
if (gsph2.Rk>1 and logAx != 2):
|
||||
for km in range(1,gsph2.Rk):
|
||||
k0 = km*gsph2.dNk
|
||||
if logAx == 0:
|
||||
AxTR.plot([0, gsph2.Nj],[k0, k0],"deepskyblue",linewidth=0.25,alpha=0.5)
|
||||
else:
|
||||
AxTR.plot([0, gsph2.Ni],[k0, k0],"deepskyblue",linewidth=0.25,alpha=0.5)
|
||||
|
||||
#plot bottom line plot
|
||||
etval = tOut[i]/60.0
|
||||
@@ -168,15 +190,17 @@ def create_command_line_parser():
|
||||
fdir2 = os.getcwd()
|
||||
ftag2 = "msphere"
|
||||
oDir = "vid2D"
|
||||
ts = 0 #[min]
|
||||
te = 200 #[min]
|
||||
ts = 0.0 #[min]
|
||||
te = 200.0 #[min]
|
||||
dt = 0.0 #[sec] 0 default means every timestep
|
||||
logAx = 2 # axis to accumulate logical error along
|
||||
Nth = 1 #Number of threads
|
||||
noMPI = False # Don't add MPI tiling
|
||||
noLog = False
|
||||
fieldNames = "Bx, By, Bz"
|
||||
doVerb = False
|
||||
skipMovie = False
|
||||
merid = False
|
||||
|
||||
MainS = """Creates simple multi-panel figure for Gamera magnetosphere run
|
||||
Left Panel - Residual vertical magnetic field
|
||||
@@ -189,14 +213,16 @@ def create_command_line_parser():
|
||||
parser.add_argument('-d2',type=str,metavar="directory",default=fdir2,help="Directory to read second dataset from (default: %(default)s)")
|
||||
parser.add_argument('-id2',type=str,metavar="runid",default=ftag2,help="RunID of second dataset (default: %(default)s)")
|
||||
parser.add_argument('-o',type=str,metavar="directory",default=oDir,help="Subdirectory to write to (default: %(default)s)")
|
||||
parser.add_argument('-ts' ,type=int,metavar="tStart",default=ts,help="Starting time [min] (default: %(default)s)")
|
||||
parser.add_argument('-te' ,type=int,metavar="tEnd" ,default=te,help="Ending time [min] (default: %(default)s)")
|
||||
parser.add_argument('-dt' ,type=int,metavar="dt" ,default=dt,help="Cadence [sec] (default: %(default)s)")
|
||||
parser.add_argument('-ts' ,type=float,metavar="tStart",default=ts,help="Starting time [min] (default: %(default)s)")
|
||||
parser.add_argument('-te' ,type=float,metavar="tEnd" ,default=te,help="Ending time [min] (default: %(default)s)")
|
||||
parser.add_argument('-dt' ,type=float,metavar="dt" ,default=dt,help="Cadence [sec] (default: %(default)s)")
|
||||
parser.add_argument('-logAx',type=int,metavar="logAx",default=logAx,help="Index of the axis to accumulate along in the upper-right plot (default: %(default)s)")
|
||||
parser.add_argument('-Nth' ,type=int,metavar="Nth",default=Nth,help="Number of threads to use (default: %(default)s)")
|
||||
parser.add_argument('-f',type=str,metavar="fieldnames",default=fieldNames,help="Comma-separated fields to plot (default: %(default)s)")
|
||||
parser.add_argument('-linear',action='store_true', default=noLog,help="Plot linear line plot instead of logarithmic (default: %(default)s)")
|
||||
parser.add_argument('-v',action='store_true', default=doVerb,help="Do verbose output (default: %(default)s)")
|
||||
parser.add_argument('-skipMovie',action='store_true', default=skipMovie,help="Skip automatic movie generation afterwards (default: %(default)s)")
|
||||
parser.add_argument('-merid',action='store_true', default=merid,help="Plot meridional instead of equatorial slice (default: %(default)s)")
|
||||
#parser.add_argument('-nompi', action='store_true', default=noMPI,help="Don't show MPI boundaries (default: %(default)s)")
|
||||
|
||||
|
||||
@@ -209,15 +235,17 @@ def main():
|
||||
fdir2 = os.getcwd()
|
||||
ftag2 = "msphere"
|
||||
oDir = "vid2D"
|
||||
ts = 0 #[min]
|
||||
te = 200 #[min]
|
||||
ts = 0.0 #[min]
|
||||
te = 200.0 #[min]
|
||||
dt = 0.0 #[sec] 0 default means every timestep
|
||||
logAx = 2
|
||||
Nth = 1 #Number of threads
|
||||
noMPI = False # Don't add MPI tiling
|
||||
noLog = False
|
||||
fieldNames = "Bx, By, Bz"
|
||||
doVerb = False
|
||||
skipMovie = False
|
||||
doEq = True
|
||||
|
||||
parser = create_command_line_parser()
|
||||
mviz.AddSizeArgs(parser)
|
||||
@@ -228,14 +256,16 @@ def main():
|
||||
ftag1 = args.id1
|
||||
fdir2 = args.d2
|
||||
ftag2 = args.id2
|
||||
ts = args.ts
|
||||
te = args.te
|
||||
dt = args.dt
|
||||
ts = args.ts
|
||||
te = args.te
|
||||
dt = args.dt
|
||||
logAx = args.logAx
|
||||
oSub = args.o
|
||||
Nth = args.Nth
|
||||
fieldNames = args.f
|
||||
noLog = args.linear
|
||||
doVerb = args.v
|
||||
doEq = not args.merid
|
||||
#noMPI = args.noMPI
|
||||
|
||||
fnList = [item.strip() for item in fieldNames.split(',')]
|
||||
@@ -275,8 +305,8 @@ def main():
|
||||
Nt = len(tOut)
|
||||
vO = np.arange(0,Nt)
|
||||
|
||||
print("Writing %d outputs between minutes %d and %d"%(Nt,ts,te))
|
||||
print("Using %d threads"%(Nth))
|
||||
print(f"Writing {Nt} outputs between minutes {ts} and {te}")
|
||||
print(f"Using {Nth} threads")
|
||||
|
||||
errTimes = []
|
||||
errListRel = []
|
||||
@@ -293,8 +323,8 @@ def main():
|
||||
met = m.list(errTimes)
|
||||
melr = m.list(errListRel)
|
||||
mela = m.list(errListAbs)
|
||||
#imageFutures = {executor.submit(makeImage,i,gsph1,gsph2,tOut,doVerb,xyBds,fnList,oDir,errTimes,errListRel,errListAbs,cv): i for i in range(0,Nt)}
|
||||
imageFutures = {executor.submit(makeImage,i,gsph1,gsph2,tOut,doVerb,xyBds,fnList,oDir,met,melr,mela,cv,dataCounter,vO,figSz,noMPI,noLog,fieldNames): i for i in range(0,Nt)}
|
||||
#imageFutures = {executor.submit(makeImage,i,gsph1,gsph2,tOut,doVerb,doEq,logAx,xyBds,fnList,oDir,errTimes,errListRel,errListAbs,cv): i for i in range(0,Nt)}
|
||||
imageFutures = {executor.submit(makeImage,i,gsph1,gsph2,tOut,doVerb,doEq,logAx,xyBds,fnList,oDir,met,melr,mela,cv,dataCounter,vO,figSz,noMPI,noLog,fieldNames): i for i in range(0,Nt)}
|
||||
for future in concurrent.futures.as_completed(imageFutures):
|
||||
try:
|
||||
retVal = future.result()
|
||||
@@ -304,9 +334,9 @@ def main():
|
||||
traceback.print_exc()
|
||||
exit()
|
||||
bar()
|
||||
|
||||
|
||||
makeMovie(oDir,oSub)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -102,8 +102,8 @@ def main():
|
||||
AxL.clear()
|
||||
AxR.clear()
|
||||
|
||||
mviz.PlotEqErrRel(gsph1,gsph2,nStp,xyBds,AxL,fnList,AxCB=AxCL)
|
||||
mviz.PlotEqErrAbs(gsph1,gsph2,nStp,xyBds,AxR,fnList,AxCB=AxCR)
|
||||
mviz.PlotErrRel(gsph1,gsph2,nStp,xyBds,AxL,fnList,AxCB=AxCL)
|
||||
mviz.PlotErrAbs(gsph1,gsph2,nStp,xyBds,AxR,fnList,AxCB=AxCR)
|
||||
|
||||
gsph1.AddTime(nStp,AxL,xy=[0.025,0.89],fs="x-large")
|
||||
|
||||
@@ -116,4 +116,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
1058
kaipy/scripts/quicklook/mageDash.py
Executable file
1058
kaipy/scripts/quicklook/mageDash.py
Executable file
File diff suppressed because it is too large
Load Diff
@@ -4,11 +4,16 @@ cartopy
|
||||
cdasws
|
||||
cmasher
|
||||
configparser
|
||||
dash
|
||||
dash_bootstrap_components
|
||||
flask_caching
|
||||
h5py
|
||||
jupyterlab
|
||||
matplotlib
|
||||
pandas
|
||||
progressbar
|
||||
pyqt5
|
||||
pyqtwebengine
|
||||
pyspedas
|
||||
pytest
|
||||
slack_sdk
|
||||
@@ -17,4 +22,4 @@ sphinx-rtd-theme
|
||||
sphinxcontrib-autoprogram
|
||||
sunpy
|
||||
gfz-api-client
|
||||
supermag-api
|
||||
supermag-api
|
||||
|
||||
10
setup.py
10
setup.py
@@ -25,11 +25,16 @@ setup(
|
||||
'cdasws',
|
||||
'cmasher',
|
||||
'configparser',
|
||||
'dash',
|
||||
'dash_bootstrap_components',
|
||||
'flask_caching',
|
||||
'h5py',
|
||||
'jupyterlab',
|
||||
'matplotlib',
|
||||
'pandas',
|
||||
'progressbar',
|
||||
'pyqt5',
|
||||
'pyqtwebengine',
|
||||
'pyspedas',
|
||||
'pytest',
|
||||
'slack_sdk',
|
||||
@@ -91,6 +96,7 @@ setup(
|
||||
'gamsphVid=kaipy.scripts.quicklook.gamsphVid:main',
|
||||
'heliomovie=kaipy.scripts.quicklook.heliomovie:main',
|
||||
'heliopic=kaipy.scripts.quicklook.heliopic:main',
|
||||
'mageDash=kaipy.scripts.quicklook.mageDash:main',
|
||||
'mixpic=kaipy.scripts.quicklook.mixpic:main',
|
||||
'msphpic=kaipy.scripts.quicklook.msphpic:main',
|
||||
'raijupic=kaipy.scripts.quicklook.raijupic:main',
|
||||
@@ -99,8 +105,8 @@ setup(
|
||||
'remixTimeSeries=kaipy.scripts.quicklook.remixTimeSeries:main',
|
||||
'swpic=kaipy.scripts.quicklook.swpic:main',
|
||||
'vizTrj=kaipy.scripts.quicklook.vizTrj:main',
|
||||
'raijudst=kaipy.raiju.dst:main',
|
||||
'raijum2m=kaipy.raiju.m2m:main'
|
||||
'raijudst=kaipy.raiju.dst:main',
|
||||
'raijum2m=kaipy.raiju.m2m:main'
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user