Merged development into ewinter-doc_fixes

This commit is contained in:
Eric Winter
2024-07-10 16:46:18 +00:00
8 changed files with 256 additions and 10 deletions

View File

@@ -91,7 +91,7 @@ Mode: CLI
---------
The Kaipy package also comes with a command line interface that allows you to run scripts to analyze MAGE model data. The CLI is a great way to automate the analysis of large datasets. The CLI is run from the terminal and has a variety of options to customize the analysis.
A complete list of the available scripts can be found at the `Scripts documentation <https://kaipy.readthedocs.io/en/latest/scripts.html>`_.
A complete list of the available scripts can be found at the `Scripts documentation <https://kaipy-docs.readthedocs.io/en/latest/scripts.html>`_.
The quicklook directory has numerous scripts that can be used to generate plots and movies of the MAGE model output. For example the `msphpic.py` command makes a summary movie of the magnetosphere while the `mixpic.py` command makes a summary movie of the ionosphere.

View File

@@ -181,8 +181,55 @@ def genFatEgg(Ni=Ni0,Nj=Nj0,Rin=3.0,Rout=30.0,xtail=250,NumSph=5,TINY=1.0e-8,A=0
tuple: A tuple containing the x-coordinates and y-coordinates of the grid points.
"""
# Rest of the code...
#Get ellipse parameters
x0,a,b = Egglipses(Ni,Nj,Rin,Rout,xtail,NumSph)
xSun = np.max(x0+a) #Forward boundary
xBack = np.abs(np.min(x0-a)) #Back boundary
#Eta is index (0,1) for theta mapping
eta = np.linspace(0+TINY,1.0-TINY,Nj+1)
L = np.pi
xC = 0.5*np.pi #Any warping is symmetric
#theta = np.linspace(0+TINY,np.pi-TINY,Nj+1)
xx = np.zeros((Ni+1,Nj+1))
yy = np.zeros((Ni+1,Nj+1))
tau0 = 0.35
di = Ni/8
i1 = Ni-di
AScl = 3*A
for i in range(Ni+1):
#Calculate theta profile for this shell
xTi = np.abs(x0[i]-a[i])
theta = L*eta + A*(xC-L*eta)*(1-eta)*eta #Lines of constant phi
if (xTi>xSun):
#Outer region, keep tailward near-axis region packed tightly
Ai = A - (A-AScl)*RampUp(xTi,xSun,xBack)
thTail = L*eta + Ai*(xC-L*eta)*(1-eta)*eta
#Only use back half (past half-way)
theta[Nj//2:] = thTail[Nj//2:]
for j in range(Nj+1):
th = theta[j]
Ae = (np.cos(th)/a[i])**2.0 + (np.sin(th)/b[i])**2.0
Be = -2*np.cos(th)*x0[i]/a[i]**2.0
Ce = x0[i]**2.0/a[i]**2.0 - 1.0
r = (-Be+np.sqrt(Be*Be-4*Ae*Ce))/(2*Ae)
xx[i,j] = r*np.cos(th)
taui = min(tau0*RampUp(1.0*i,i1,di),1.0)
tauscl = taui*(xx[i,j])/a[i]
tau = min(1.0+tauscl,1)
yy[i,j] = r*np.sin(th)/np.sqrt(tau)
return xx,yy
def RampUp(r, rC, lC):
"""

View File

@@ -2,11 +2,97 @@ import h5py
import numpy as np
import os, sys, subprocess
from alive_progress import alive_bar, alive_it
import datetime
from astropy.time import Time
#Import kaipy modules
import kaipy.kdefs as kdefs
import kaipy.kaiTools as ktools
#------
# Data Containers
#------
class H5Info(object):
"""
Class to hold model data information from h5 output files
Args:
h5fname (str): .h5 filename to scrape info from
noSubsec (bool): Whether or not UTs should include subseconds (optional, default: True)
Attributes:
fname (str): .h5 filename the rest of the info came from
steps (List[int]): list of step numbers
stepStrs (List[str]): list of step strings in Step#X h5.Group format
Nt (int): Number of time steps
times (List[float]): List of times corresponding to each step
Units: Unchanged from file, likely seconds
MJDs (List[float]): List of MJDs corresponding to each step
UTs (List[float]): List of UTs corresponding to each step
May or may not include subseconds based on constructor args
"""
def __init__(self, h5fname: str, noSubsec:bool=True):
# h5fname = h5fname.split('/')[-1]
self.fname = h5fname
self.Nt, self.steps = cntSteps(self.fname)
self.stepStrs = ['Step#'+str(s) for s in self.steps]
self.times = getTs(self.fname, self.steps, "time")
self.MJDs = getTs(self.fname, self.steps, "MJD" )
f5 = h5py.File(h5fname)
if noSubsec:
ut_arr = np.zeros(self.Nt, dtype=datetime.datetime)
for i,mjd in enumerate(self.MJDs):
UTStr = Time(mjd,format='mjd').isot
utTrim = UTStr.split('.')[0]
UT = datetime.datetime.strptime(utTrim,'%Y-%m-%dT%H:%M:%S')
ut_arr[i] = UT
else:
ut_arr = ktools.MJD2UT(self.MJDs)
self.UTs = ut_arr
f5.close()
def printStepInfo(self) -> None:
"""
Prints a summary of step info for the contained data
Args: None
Returns: None
"""
print("Step Info for {}:".format(self.fname))
print(" Step start/end/stride : {} / {} / {}".format(self.steps[0], self.steps[-1], self.steps[1]-self.steps[0]))
print(" Time start/end/stride : {:1.2f} / {:1.2f} / {:1.2f}".format(self.times[0], self.times[-1], self.times[1]-self.times[0]))
print(" MJD start/end : {:1.8f} / {:1.8f}".format(self.MJDs[0], self.MJDs[-1]))
print(" UT start/end : {} / {}".format(self.UTs[0], self.UTs[-1]))
class TPInfo(H5Info):
"""
Extension of H5Info for test particle files
Attributes:
Ntp (int): Number of test particles
id2idxMap (Dict{int:}): Dict to help map TP id to its index
"""
def __init__(self, fname:str, noSubsec:bool=True):
super().__init__(fname, noSubsec)
with h5py.File(self.fname) as tp5:
ids = tp5[self.stepStrs[0]]['id']
self.Ntp = ids.shape[0]
self.id2idxMap = {}
for i in range(self.Ntp):
self.id2idxMap[int(ids[i])] = int(i)
#------
# General functions
#------
#Generate MPI-style name
def genName(bStr, i, j, k, Ri, Rj, Rk, nRes=None):
'''

View File

@@ -62,6 +62,37 @@ def MJD2UT(mjd):
else:
return [datetime.datetime.strptime(UT[n], isotfmt) for n in range(len(UT))]
def utIdx(utList,ut):
"""
Finds the index of the closest datetime within an array of datetimes
Parameters:
utList (List[datetime.datetime]): List of datetimes to find index within
ut (datetime.datetime): datetime to find closest index to
Returns:
Single integer value of the closest index within 'utList' to 'ut'
"""
return np.array([np.abs((utList[n]-ut).total_seconds()) for n in range(len(utList))]).argmin()
def pntIdx_2D(X2D, Y2D, pnt):
"""
Finds i, j, index within 'X2D' and 'Y2D' closest to 'pnt'
Assumes cartesian coordinate system
Parameters:
X2D (ndarray[float]): 2D array of X values
Y2D (ndarray[float]): 2D array of Y values
pnt (List[float]) : [x, y] point to find closest index to
Returns:
Tuple of closest i, j index to point 'pnt'
"""
distSq = (X2D-pnt[0])**2 + (Y2D-pnt[1])**2 # Each source point's euclidian distance from 'pnt'
aMinFlattened = distSq.argmin() # Index of min distance, if it was a flattened array
i, j = np.unravel_index(aMinFlattened, X2D.shape) # Convert back into i,j location
return i, j
def getRunInfo(fdir, ftag):
"""
Get information about the run.

View File

@@ -696,6 +696,32 @@ def get_aspect(ax):
return disp_ratio #/ data_ratio
def setBndsByAspect(ax, bnds, axis='x', aspect=1):
"""
Given bounds of one axis, set the other bounds of Axes object such that aspect ratio and Axes box size is perserved
Parameters:
ax: Axes object to set bounds for
bnds (List[float]): [axMin, axMax] min/max bounds for fixed axis
axis (str): ('x' or 'y') axis that fixed bounds are applied to
aspect: x:y aspect ratio to preserve
"""
boxRatio = get_aspect(ax)
da = bnds[1] - bnds[0]
if axis=='x':
db = aspect*boxRatio*da
bMin = -db/2
bMax = db/2
ax.set_xlim(bnds)
ax.set_ylim([bMin, bMax])
else:
db = 1/aspect*boxRatio*da
bMin = -db/2
bMax = db/2
ax.set_xlim([bMin, bMax])
ax.set_ylim(bnds)
# Methods specific for comparing gamhelio output to data from heliospheric
# spacecraft.

View File

@@ -114,7 +114,7 @@ def getRootVars(fname, gDims):
doV = True
if ("Step" in vID or kdefs.grpTimeCache in vID):
doV = False
if ((vID == "X") or (vID == "Y") or (vID == "Z")):
if ((vID == "X") or (vID == "Y") or (vID == "Z") or (type(hf[k]) != h5py._hl.group.Group)): # Ignore root geom and root Groups
doV = False
if (doV):
Nv = hf[k].shape

View File

@@ -90,8 +90,8 @@ class AlamParams:
# These can help to determine some things for higher-level lambda generators
#tiote: Optional[float] = None # Ratio of ion temperature to electron temperature
#ktMax: Optional[float] = None # Energy in eV
emine: Optional[float]
eminp: Optional[float]
emaxe: Optional[float]
emaxp: Optional[float]
L_kt: Optional[float]
emine: Optional[float] = None
eminp: Optional[float] = None
emaxe: Optional[float] = None
emaxp: Optional[float] = None
L_kt : Optional[float] = None

View File

@@ -562,8 +562,64 @@ class remix:
if not self.Initialized:
sys.exit("Variables should be initialized for the specific hemisphere (call init_var) prior to efield calculation.")
# Rest of the code...
Psi = self.variables['potential']['data'] # note, these are numbers of cells. self.ion['X'].shape = Nr+1,Nt+1
Nt,Np = Psi.shape
# Aliases to keep things short
x = self.ion['X']
y = self.ion['Y']
# note the change in naming convention from above
# i.e., theta is now the polar angle
# and phi is the azimuthal (what was theta)
# TODO: make consistent throughout
theta = np.arcsin(self.ion['R'])
phi = self.ion['THETA']
# interpolate Psi to corners
Psi_c = np.zeros(x.shape)
Psi_c[1:-1,1:-1] = 0.25*(Psi[1:,1:]+Psi[:-1,1:]+Psi[1:,:-1]+Psi[:-1,:-1])
# fix up periodic
Psi_c[1:-1,0] = 0.25*(Psi[1:,0]+Psi[:-1,0]+Psi[1:,-1]+Psi[:-1,-1])
Psi_c[1:-1,-1] = Psi_c[1:-1,0]
# fix up pole
Psi_pole = Psi[0,:].mean()
Psi_c[0,1:-1] = 0.25*(2.*Psi_pole + Psi[0,:-1]+Psi[0,1:])
Psi_c[0,0] = 0.25*(2.*Psi_pole + Psi[0,-1]+Psi[0,0])
Psi_c[0,-1] = 0.25*(2.*Psi_pole + Psi[0,-1]+Psi[0,0])
# fix up low lat boundary
# extrapolate linearly just like we did for the coordinates
# (see genOutGrid in src/remix/mixio.F90)
# note, neglecting the possibly non-uniform spacing (don't care)
Psi_c[-1,:] = 2*Psi_c[-2,:]-Psi_c[-3,:]
# now, do the differencing
# for each cell corner on the original grid, I have the coordinates and Psi_c
# need to find the gradient at cell center
# the result is the same size as Psi
# first etheta
tmp = 0.5*(Psi_c[:,1:]+Psi_c[:,:-1]) # move to edge center
dPsi = tmp[1:,:]-tmp[:-1,:]
tmp = 0.5*(theta[:,1:]+theta[:,:-1])
dtheta = tmp[1:,:]-tmp[:-1,:]
etheta = dPsi/dtheta/ri # this is in V/m
# now ephi
tmp = 0.5*(Psi_c[1:,:]+Psi_c[:-1,:]) # move to edge center
dPsi = tmp[:,1:]-tmp[:,:-1]
tmp = 0.5*(phi[1:,:]+phi[:-1,:])
dphi = tmp[:,1:]-tmp[:,:-1]
tc = 0.25*(theta[:-1,:-1]+theta[1:,:-1]+theta[:-1,1:]+theta[1:,1:]) # need this additionally
ephi = dPsi/dphi/np.sin(tc)/ri # this is in V/m
if returnDeltas:
return (-etheta,-ephi,dtheta,dphi) # E = -grad Psi
else:
return (-etheta,-ephi) # E = -grad Psi
def joule(self):
"""