def satDXToTimeSeries(filename):
    file = open(filename)
    data = ft.read(file)
    (rows, cols) = data.shape
    dict = pyLTR.TimeSeries()
    mon = []
    mday = []
    dayFraction = []
    doy = []
    atime = []
    for i in range(rows):
        d = datetime.date(int(data[i, 0]), 1,
                          1) + datetime.timedelta(int(data[i, 1]) - 1)
        mon.append(d.month)
        mday.append(d.day)
        dayFraction = (data[i, 2] + data[i, 3] / 60.0 +
                       data[i, 4] / 3600.0) / 24.0
        doy.append(float(data[i, 1]) + dayFraction)
        #atime.append(datetime.datetime(d.year,d.month,d.day,data[i,2],data[i,3],data[i,4]))
    dict.append('time_doy', 'Day of Year', ' ', doy)
    dict.append('density', 'Den', r'$\mathrm{1/cm^3}$', data[:, 5])
    dict.append('vx', 'Vx', r'$\mathrm{km/s}$', data[:, 6])
    dict.append('vy', 'Vy', r'$\mathrm{km/s}$', data[:, 7])
    dict.append('vz', 'Vz', r'$\mathrm{km/s}$', data[:, 8])
    dict.append('p', 'P', r'$\mathrm{keV/cm^3}$', data[:, 9])
    dict.append('bx', 'Bx', r'$\mathrm{nT}$', data[:, 10])
    dict.append('by', 'By', r'$\mathrm{nT}$', data[:, 11])
    dict.append('bz', 'Bz', r'$\mathrm{nT}$', data[:, 12])
    dict.append('ex', 'Ex', r'$\mathrm{V/m}$', data[:, 13])
    dict.append('ey', 'Ey', r'$\mathrm{V/m}$', data[:, 14])
    dict.append('ez', 'Ez', r'$\mathrm{V/m}$', data[:, 15])

    return dict
Exemple #2
0
    def __init__(self, filename = None):        

        self.data = pyLTR.TimeSeries()
        self.bad_data = ['-1.00000E+31']
        # Match the beginning of a line with a date & time (i.e. 16-04-2008 00:00:00.000)
        self.dateRegEx = r=re.compile(r'^\d{2}\-\d{2}-\d{4} \d{2}\:\d{2}\:\d{2}\.\d{3}')        

        self.__read(filename)
        self.__appendDerivedQuantities()
Exemple #3
0
def extractQuantities(path, run, t0, t1):
    """
    Extract MIX quantities from the input path.
    
    Execute `mixTimeSeries.py --help` for details on the function parameters (path,run,t0,t1).
    
    Outputs a pyLTR.TimeSeries object containing the data.
    """
    data = pyLTR.Models.MIX(path, run)

    # hard-coded input for testing & debugging:
    #data = pyLTR.Models.LFM('/hao/aim2/schmitt/data/LTR-2_0_1b/r1432/March1995/LR/single', 'LRs')

    #Make sure variables are defined in the model.
    modelVars = data.getVarNames()
    for v in [
            'Grid X', 'Grid Y', 'Poynting flux North [???]',
            'Poynting flux South [???]', 'FAC North [A/m^2]',
            'FAC South [A/m^2]', 'Number flux North [1/cm^2 s]',
            'Number flux South [1/cm^2 s]', 'BBE average energy North [keV]',
            'BBE average energy South [keV]',
            'BBE number flux North [1/cm^2 s]',
            'BBE number flux South [1/cm^2 s]',
            'Cusp average energy North [keV]',
            'Cusp average energy South [keV]',
            'Cusp number flux North [1/cm^2 s]',
            'Cusp number flux South [1/cm^2 s]'
    ]:
        #        print v
        assert (v in modelVars)

    timeRange = data.getTimeRange()
    if len(timeRange) == 0:
        raise Exception((
            'No data files found.  Are you pointing to the correct run directory?'
        ))

    index0 = 0
    if t0:
        for i, t in enumerate(timeRange):
            if t0 >= t:
                index0 = i

    index1 = len(timeRange) - 1
    if t1:
        for i, t in enumerate(timeRange):
            if t1 >= t:
                index1 = i

    print(('Extracting MIX quantities for time series over %d time steps.' %
           (index1 - index0)))

    # Output a status bar displaying how far along the computation is.
    progress = pyLTR.StatusBar(0, index1 - index0)
    progress.start()

    t_doy = []
    isparNorth = []
    isparSouth = []
    hpNorth = []
    hpSouth = []
    asparNorth = []
    asparSouth = []
    bbe = 0
    cusp = 0

    # Pre-compute area of the grid.
    x = data.read('Grid X', timeRange[index0])
    y = data.read('Grid Y', timeRange[index0])
    # Fix singularity at the pole
    x[:, 0] = 0.0
    y[:, 0] = 0.0
    z = numpy.sqrt(1.0 - x**2 - y**2)
    ri = 6500.0e3  # Radius of ionosphere in meters
    areaMixGrid = pyLTR.math.integrate.calcFaceAreas(x, y, z) * ri * ri

    for i, time in enumerate(timeRange[index0:index1]):
        try:
            # -- Day of Year
            tt = time.timetuple()
            t_doy.append(tt.tm_yday + tt.tm_hour / 24.0 + tt.tm_min / 1440.0 +
                         tt.tm_sec / 86400.0)

            # --- Cross Polar Cap Potential
            psi = data.read('Poynting flux North [???]', time)
            psi[psi < 0] = 0.
            spar = areaMixGrid * psi[:-1, :-1]
            # is MIX grid in m^2? spar is in mW/m^2
            # mW to GW
            isparNorth.append(spar.sum() * 1.e-12)
            spar = psi[psi > 0.1].mean()
            asparNorth.append(spar.sum())

            psi = data.read('Poynting flux South [???]', time)
            psi[psi > 0] = 0.
            spar = areaMixGrid * psi[:-1, :-1]
            # is MIX grid in m^2? spar is in mW/m^2
            # mW to GW
            isparSouth.append(spar.sum() * -1.e-12)
            spar = psi[psi < -0.1].mean()
            asparSouth.append(spar.sum() * -1.)

            # --- Hemispheric Power
            energy = data.read('Average energy North [keV]', time)
            flux = data.read('Number flux North [1/cm^2 s]', time)
            mhp = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            flux = data.read('BBE number flux North [1/cm^2 s]', time)
            energy = data.read('BBE average energy North [keV]', time)
            bbe = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            flux = data.read('Cusp number flux North [1/cm^2 s]', time)
            energy = data.read('Cusp average energy North [keV]', time)
            cusp = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            hp = mhp + bbe + cusp
            # KeV/cm^2s to mW/m^2 to GW
            hpNorth.append(hp.sum() * 1.6e-21)

            energy = data.read('Average energy South [keV]', time)
            flux = data.read('Number flux South [1/cm^2 s]', time)
            mhp = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            flux = data.read('BBE number flux South [1/cm^2 s]', time)
            energy = data.read('BBE average energy South [keV]', time)
            bbe = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            flux = data.read('Cusp number flux South [1/cm^2 s]', time)
            energy = data.read('Cusp average energy South [keV]', time)
            cusp = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            hp = mhp + bbe + cusp
            # KeV/cm^2s to mW/m^2 to GW
            hpSouth.append(hp.sum() * 1.6e-21)

            progress.increment()
        except KeyboardInterrupt:
            # Exit when the user hits CTRL+C.
            progress.stop()
            progress.join()
            print('Exiting.')
            import sys
            sys.exit(0)
        except:
            # Cleanup progress bar if something bad happened.
            progress.stop()
            progress.join()
            raise
    progress.stop()
    progress.join()

    dataNorth = pyLTR.TimeSeries()
    dataSouth = pyLTR.TimeSeries()
    dataNorth.append('datetime', 'Date & Time', '', timeRange[index0:index1])
    dataSouth.append('datetime', 'Date & Time', '', timeRange[index0:index1])
    dataNorth.append('doy', 'Day of Year', '', t_doy)
    dataSouth.append('doy', 'Day of Year', '', t_doy)

    # "N" and "S" label subscripts are redundant here, potentially leading to
    # mis-labeling of plots
    #dataNorth.append('cpcp', r'$\Phi_N$', 'kV', cpcpNorth)
    #dataSouth.append('cpcp', r'$\Phi_S$', 'kV', cpcpSouth)
    #
    #dataNorth.append('hp', r'$HP_N$', 'GW', hpNorth)
    #dataSouth.append('hp', r'$HP_S$', 'GW', hpSouth)
    #
    #dataNorth.append('ipfac', r'$FAC_N$', 'MA', ipfacNorth)
    #dataSouth.append('ipfac', r'$FAC_S$', 'MA', ipfacSouth)

    dataNorth.append('ispar', r'$Integrated S||$', 'GW', isparNorth)
    dataSouth.append('ispar', r'$Integrated S||$', 'GW', isparSouth)

    dataNorth.append('hp', r'$THP$', 'GW', hpNorth)
    dataSouth.append('hp', r'$THP$', 'GW', hpSouth)

    dataNorth.append('aspar', r'$Mean S||r$', 'mW/m^2', asparNorth)
    dataSouth.append('aspar', r'$Mean S||$', 'mW/m^2', asparSouth)

    return (dataNorth, dataSouth)
Exemple #4
0
def getDst(path, runName, t0, t1):
   """
   Returns a pyLTR.TimeSeries object containing the DST index
   
   Parameters:
     path: path to run 
     runName: name of run we wish to compute DST for.
   """
   data = pyLTR.Models.LFM(path, runName)
   
   # Hard-code a subset (useful for testing & debugging):
   #data = pyLTR.Models.LFM('/Users/schmitt/paraview/testData/March1995_LM_r1432_single', 'LMs')

   # Make sure variables are defined in the model.
   modelVars = data.getVarNames()
   for v in ['X_grid', 'Y_grid', 'Z_grid',
             'bi_', 'bj_', 'bk_', 
             'bx_', 'by_', 'bz_', 
             'c_', 
             'ei_', 'ej_', 'ek_', 
             'rho_', 'vx_', 'vy_', 'vz_']:
      assert( v in modelVars )

   timeRange = data.getTimeRange()
   if len(timeRange) == 0:
       raise Exception(('No data files found.  Are you pointing to the correct run directory?'))

   index0 = 0
   if t0:
      for i,t in enumerate(timeRange):
         if t0 >= t:
            index0 = i
            
   index1 = len(timeRange)-1
   if t1:
      for i,t in enumerate(timeRange):
         if t1 >= t:
            index1 = i                

   t_doy = []
   dst = []

   # Pre-compute some quantities
   #FIXME:  Should this be a configurable parameter?
   earthRadius = 6.38e8
   #earthRadius = 6340e5
   x = data.read('X_grid', timeRange[index0]) / earthRadius
   y = data.read('Y_grid', timeRange[index0]) / earthRadius
   z = data.read('Z_grid', timeRange[index0]) / earthRadius

   dipStr = data.readAttribute('dipole_moment', timeRange[index0])
   dipStr = dipStr.split('=')[1].strip().split()[0]
   gauss_2_nT = 1.0e5
   dipoleMoment = float(dipStr) / (earthRadius**3) * gauss_2_nT

   print('Reticulating splines')
   #FIXME:  nPoints and radius should be configurable
   nPoints = 4
   radius = 3.0
   (xc, yc, zc) = getCellCenters(x,y,z)
   ijk = getDstIndices(xc,yc,zc, nPoints, radius)

   print(( 'Extracting DST from MHD files for time series over %d time steps.' % (index1-index0) ))
   
   # Output a status bar displaying how far along the computation is.   
   progress = pyLTR.StatusBar(0, index1-index0)
   progress.start()
   for i,time in enumerate(timeRange[index0:index1]):
      try:
         tt = time.timetuple()
         dayFraction = (tt.tm_hour+tt.tm_min/60.+tt.tm_sec/(60.*60.))/24.
         t_doy.append( float(tt.tm_yday) + dayFraction )

         ## Skip the file if it can't be read.
         try:
            data.readAttribute('time', time)
         except HDF4Error as msg:
            sys.stderr.write('HDF4Error: Trouble "' + data._LFM__io.filename + '"\n')
            sys.stderr.write('HDF4 Error message: "' + str(msg) + '"\n')
            sys.stderr.write('Skipping file.\n')
            sys.stderr.flush()
         
            #FIXME: What's the correct behavior here for the dst
            #       value? Set to last known good value?  Pop array values?
            dst.append(dst[len(dst)-1])
            continue      

         # Calculate DST
         dstVal = 0.0
         for idx in ijk:               
            #FIXME: when count=[1,1,1], data.read(...) returns a 3d
            #       array with just one element!            
            #FIXME: setting a particular start index doesn't work
            #       because dimensions are transposed because Fortran
            #       does the I/O... so just read all the data :-(
            #bz = data.read('bz_', time, start=idx, count=(1,1,1))
            bz = data.read('bz_', time)
                              
            # scale to nT
            bz = bz[idx[0], idx[1], idx[2]] * gauss_2_nT
            
            dip = dipole( (xc[idx[0],idx[1],idx[2]],
                           yc[idx[0],idx[1],idx[2]],
                           zc[idx[0],idx[1],idx[2]]),
                          dipoleMoment)
            
            dstVal += bz - dip[2]

         dstVal /= len(ijk)
         dst.append( dstVal )

         progress.increment()

      except KeyboardInterrupt:
         # Exit when the user hits CTRL+C.
         progress.stop()
         progress.join()            
         print('Exiting.')
         sys.exit(0)
      except:
         # Cleanup progress bar if something bad happened.
         progress.stop()
         progress.join()
         raise
   progress.stop()
   progress.join()

   if len(dst) == 0:
      raise Exception(('No data computed.  Did you specify a valid run path?'))    
   
   data = pyLTR.TimeSeries()
   data.append('datetime',     'Date & Time', '', timeRange[index0:index1])
   data.append('doy', 'Day of Year', '',      t_doy)
   data.append('dst', 'Disturbance Storm Time Index', 'nT', dst)

   return data
Exemple #5
0
def calculateIndex(path='./', run='', 
                   t0='', t1='', 
                   obsList=None, geoGrid=False, 
                   ignoreBinary=False, binaryType='pkl',
                   outDirName='figs'):
   """
   Compute deltaBs at virtual observatories, then generate an Dst-like index,
   given LFM-MIX output files in path.
   
   Computes:
     SymAsyDst - a dict of pyLTR.TimeSeries objects, corresponding to the total,
                 ionospheric current, field-aligned current, and magnetospheric
                 current constituent of dBH (mag dipole northward, scaled by mag
                 latitude), dBD (mag dipole eastward), and dBDst (horizontal, 
                 scaled by mag latitude), including both symmetric and asymmetric 
                 components.
                 NOTE: while the outputs are pyLTR.TimeSeries objects, the
                       function generates binary pkl/mat files that combine
                       each specified observatory for each time step; this
                       helps reduce re-computation time for subsequent calls
                       to this function, and provides snapshots of output to
                       be read in by alternative analysis software
   
   Requires:
     Nothing, all inputs are optional
   
   Optional:
     path      - path to data directory holding LFM and MIX model output files
                 (default is current directory)
     run       - output filename prefix identifying LFM-MIX run (i.e., the part
                 of the filename prior to [mhd|mix]_yyyy-mm-ddTHH-MM-SSZ.hdf)
                 (default is any mhd|mix files in path)
     t0        - datetime object specifying the earliest of available time step
                 to include in the extraction
                 (default is earliest available)
     t1        - datetime object specfiying the latest of available time step
                 to include in the extraction
                 (default is last available)
     obsList   - a list of lists of observatory coordinates [phi,theta,rho,ID]
                 where ID is an optional 4th element to each coordinate set
                 that, ideally, uniquely identifies the observatory...if ID is
                 not specified, it is assigned an empty string.
                 (default is coordinate system origin)
     geoGrid   - if True, assume observatory coordinates are in geographic
                 coordinates rather than solar magnetic; same for outputs
                 (default is False)
     ignoreBinary - if True, ignore any pre-computed binary files and re-
                 compute everything from scratch; NOTE: individual binary files
                 will be ignored anyway if they are incompatible with specified
                 inputs, but this option avoids reading the binary file entirely.
                 (default is False)
     binaryType   - binary type to generate, NOT to read in...routine looks for
                 PKL files first, then mat files, then proceeds to re-compute
                 if neither are available.
                 (default is 'pkl')
     outDirName - name of directory into which all output will be placed; must
                 be a relative path, to be appended to the input path; this is
                 also where binary pkl/mat files will be expected if/when the
                 function tries to read pre-computed data.
                 (default is 'figs')
   """

   # define and geo-locate virtual observatories
   if obsList==None or len(obsList) == 0:
      obsList = _defaultObs()
      # force geoGrid=True
      geoGrid=True
      
   
   # get delta B time series for Dst stations
   dBExtract = pyLTR.Tools.deltaBTimeSeries.extractQuantities
   dBObs = dBExtract(path=path, run=run, 
                     t0=t0, t1=t1,
                     obsList=obsList, geoGrid=geoGrid, 
                     ignoreBinary=ignoreBinary, binaryType=binaryType,
                     outDirName=outDirName)
   
   
   # convert lists of constituent vector components into NumPy arrays
   dBNorthIon = p.array([elem['dBIon']['North']['data'] for elem in dBObs]).T
   dBNorthFAC = p.array([elem['dBFAC']['North']['data'] for elem in dBObs]).T
   dBNorthMag = p.array([elem['dBMag']['North']['data'] for elem in dBObs]).T
   dBNorthTot = p.array([elem['dBTot']['North']['data'] for elem in dBObs]).T

   dBEastIon = p.array([elem['dBIon']['East']['data'] for elem in dBObs]).T
   dBEastFAC = p.array([elem['dBFAC']['East']['data'] for elem in dBObs]).T
   dBEastMag = p.array([elem['dBMag']['East']['data'] for elem in dBObs]).T
   dBEastTot = p.array([elem['dBTot']['East']['data'] for elem in dBObs]).T

   dBDownIon = p.array([elem['dBIon']['Down']['data'] for elem in dBObs]).T
   dBDownFAC = p.array([elem['dBFAC']['Down']['data'] for elem in dBObs]).T
   dBDownMag = p.array([elem['dBMag']['Down']['data'] for elem in dBObs]).T
   dBDownTot = p.array([elem['dBTot']['Down']['data'] for elem in dBObs]).T
   
   
   
   # convert deltaB vectors into local geomagnetic coordinates if they
   # are not there already
   if geoGrid:
      
      # extract geographic spherical coordinates
      phis = p.array([elem['dBTot']['phiGEO']['data'] for elem in dBObs]).T
      thetas = p.array([elem['dBTot']['thetaGEO']['data'] for elem in dBObs]).T
      rhos = p.array([elem['dBTot']['rhoGEO']['data'] for elem in dBObs]).T
      
      # extract datetime stamps
      datetimes = p.array([elem['dBTot']['datetime']['data'] for elem in dBObs]).T
      
      
      # convert Tot constituent to dipole North,East,Down coordinates
      # (must loop over time steps for correct time-dependent transforms)
      for i,ts in enumerate(datetimes):
         x,y,z,dx,dy,dz = pyLTR.transform.SPHtoCAR(phis[i,:], thetas[i,:], rhos[i,:], 
                                                   dBEastTot[i,:], -dBNorthTot[i,:], -dBDownTot[i,:])
         x,y,z = pyLTR.transform.GEOtoSM(x,y,z,ts[0])
         dx,dy,dz = pyLTR.transform.GEOtoSM(dx,dy,dz,ts[0])
         p_ts,t_ts,r_ts,dp_ts,dt_ts,dr_ts = pyLTR.transform.CARtoSPH(x,y,z,dx,dy,dz)
         
         dBNorthTot[i,:] = -dt_ts
         dBEastTot[i,:] =  dp_ts
         dBDownTot[i,:] = -dr_ts
      
      
      
      # convert Ion constituent to dipole North,East,Down coordinates
      # (must loop over time steps for correct time-dependent transforms)
      for i,ts in enumerate(datetimes):
         x,y,z,dx,dy,dz = pyLTR.transform.SPHtoCAR(phis[i,:], thetas[i,:], rhos[i,:], 
                                                   dBEastIon[i,:], -dBNorthIon[i,:], -dBDownIon[i,:])
         x,y,z = pyLTR.transform.GEOtoSM(x,y,z,ts[0])
         dx,dy,dz = pyLTR.transform.GEOtoSM(dx,dy,dz,ts[0])
         p_ts,t_ts,r_ts,dp_ts,dt_ts,dr_ts = pyLTR.transform.CARtoSPH(x,y,z,dx,dy,dz)
         
         dBNorthIon[i,:] = -dt_ts
         dBEastIon[i,:] =  dp_ts
         dBDownIon[i,:] = -dr_ts
      
      
      # convert FAC constituent to dipole North,East,Down coordinates
      # (must loop over time steps for correct time-dependent transforms)
      for i,ts in enumerate(datetimes):
         x,y,z,dx,dy,dz = pyLTR.transform.SPHtoCAR(phis[i,:], thetas[i,:], rhos[i,:], 
                                                   dBEastFAC[i,:], -dBNorthFAC[i,:], -dBDownFAC[i,:])
         x,y,z = pyLTR.transform.GEOtoSM(x,y,z,ts[0])
         dx,dy,dz = pyLTR.transform.GEOtoSM(dx,dy,dz,ts[0])
         p_ts,t_ts,r_ts,dp_ts,dt_ts,dr_ts = pyLTR.transform.CARtoSPH(x,y,z,dx,dy,dz)
         
         dBNorthFAC[i,:] = -dt_ts
         dBEastFAC[i,:] =  dp_ts
         dBDownFAC[i,:] = -dr_ts
      
      
      # convert Mag constituent to dipole North,East,Down coordinates
      # (must loop over time steps for correct time-dependent transforms)
      for i,ts in enumerate(datetimes):
         x,y,z,dx,dy,dz = pyLTR.transform.SPHtoCAR(phis[i,:], thetas[i,:], rhos[i,:], 
                                                   dBEastMag[i,:], -dBNorthMag[i,:], -dBDownMag[i,:])
         x,y,z = pyLTR.transform.GEOtoSM(x,y,z,ts[0])
         dx,dy,dz = pyLTR.transform.GEOtoSM(dx,dy,dz,ts[0])
         p_ts,t_ts,r_ts,dp_ts,dt_ts,dr_ts = pyLTR.transform.CARtoSPH(x,y,z,dx,dy,dz)
         
         # note that we didn't update phis,thetas,rhos until now because we needed
         # them to remain in geographic for previous calls to GEOtoSM
         phis[i,:] = p_ts
         thetas[i,:] = t_ts
         rhos[i,:] = r_ts
         
         dBNorthMag[i,:] = -dt_ts
         dBEastMag[i,:] =  dp_ts
         dBDownMag[i,:] = -dr_ts
      
   else:
      
      # extract SM spherical coordinates
      phis = p.array([elem['dBTot']['phiSM']['data'] for elem in dBObs]).T
      thetas = p.array([elem['dBTot']['thetaSM']['data'] for elem in dBObs]).T
      rhos = p.array([elem['dBTot']['rhoSM']['data'] for elem in dBObs]).T
      
      # everything else is already in the necessary coordinates
   
   
   
   # Now to replicate (more-or-less) SYM/ASY/Dst index algorithm(s) from 
   # Kyoto WDC (i.e., http://wdc.kugi.kyoto-u.ac.jp/aeasy/asy.pdf) 
   
   # symH is average of stations northward field normalized by the cosine 
   # of the stations' dipole latitude
   # (this differs slightly from Sugiura's technique, which has never been
   #  mathematically or physically sensible; also, the "paper" cited above
   #  describes some sort of additional normalization for the ASY indices,
   #  but is extremely confusing, so we're leaving that out for now) 
   symHTot = (dBNorthTot / p.cos(p.pi/2 - thetas)).mean(axis=1)
   
   # indices to max/min disturbance are all based on the total current system; 
   # this allows the constituents to sum up to give the total
   HMaxIdx = ((dBNorthTot / p.cos(p.pi/2 - thetas)) ).argmax(axis=1)
   HMinIdx = ((dBNorthTot / p.cos(p.pi/2 - thetas)) ).argmin(axis=1)
   
   # I do not understand how slices and index arrays are combined, so just create
   # an index array for all time steps (rows)
   allT = list(range(dBNorthTot.shape[0]))
   
   # indices from total current system
   #symHTot = (dBNorthTot / p.cos(p.pi/2 - thetas)).mean(axis=1) # redundant
   HMaxTot = ((dBNorthTot / p.cos(p.pi/2 - thetas)) )[allT,HMaxIdx]
   HMinTot = ((dBNorthTot / p.cos(p.pi/2 - thetas)) )[allT,HMinIdx]
   asyHTot = HMaxTot - HMinTot
  
   # indices from ionospheric current system
   symHIon = (dBNorthIon / p.cos(p.pi/2 - thetas)).mean(axis=1)
   HMaxIon = ((dBNorthIon / p.cos(p.pi/2 - thetas)) )[allT,HMaxIdx]
   HMinIon = ((dBNorthIon / p.cos(p.pi/2 - thetas)) )[allT,HMinIdx]
   asyHIon = HMaxIon - HMinIon
  
   # indices from field-aligned current system
   symHFAC = (dBNorthFAC / p.cos(p.pi/2 - thetas)).mean(axis=1)
   HMaxFAC = ((dBNorthFAC / p.cos(p.pi/2 - thetas)) )[allT,HMaxIdx]
   HMinFAC = ((dBNorthFAC / p.cos(p.pi/2 - thetas)) )[allT,HMinIdx]
   asyHFAC = HMaxFAC - HMinFAC
  
   # indices from magnetospheric current system
   symHMag = (dBNorthMag / p.cos(p.pi/2 - thetas)).mean(axis=1)
   HMaxMag = ((dBNorthMag / p.cos(p.pi/2 - thetas)) )[allT,HMaxIdx]
   HMinMag = ((dBNorthMag / p.cos(p.pi/2 - thetas)) )[allT,HMinIdx]
   asyHMag = HMaxMag - HMinMag
   
   
   
   # symD is average of stations eastward field, but NOT normalized by the 
   # cosine of dipole latitude
   # (the "paper" cited above describes some sort of normalization for the ASY
   #  indices, but is extremely confusing, so we're leaving that out for now) 
   symDTot = dBEastTot.mean(axis=1)
   
   # indices to max/min disturbance are all based on the total current system; 
   # this allows the constituents to sum up to give the total
   DMaxIdx = (dBEastTot ).argmax(axis=1)
   DMinIdx = (dBEastTot ).argmin(axis=1)
   
   # I do not understand how slices and index arrays are combined, so just create
   # an index array for all time steps (rows)
   allT = list(range(dBNorthTot.shape[0]))
   
   # indices from total current system
   #symDTot = dBEastTot.mean(axis=1) # redundant
   DMaxTot = (dBEastTot )[allT,DMaxIdx]
   DMinTot = (dBEastTot )[allT,DMinIdx]
   asyDTot = DMaxTot - DMinTot
  
   # indices from ionospheric current system
   symDIon = dBEastIon.mean(axis=1)
   DMaxIon = (dBEastIon )[allT,DMaxIdx]
   DMinIon = (dBEastIon )[allT,DMinIdx]
   asyDIon = DMaxIon - DMinIon
  
   # indices from field-aligned current system
   symDFAC = dBEastFAC.mean(axis=1)
   DMaxFAC = (dBEastFAC )[allT,DMaxIdx]
   DMinFAC = (dBEastFAC )[allT,DMinIdx]
   asyDFAC = DMaxFAC - DMinFAC
  
   # indices from magnetospheric current system
   symDMag = dBEastMag.mean(axis=1)
   DMaxMag = (dBEastMag )[allT,DMaxIdx]
   DMinMag = (dBEastMag )[allT,DMinIdx]
   asyDMag = DMaxMag - DMinMag
   
   """
   FIXME
   
   NEED TO GET DST SIGN RIGHT, JUST LIKE WITH AE...IN OTHER WORDS, ADD BASELINE
   PRIOR TO VECTOR SUMMING, THEN SUBTRACT BASELINE TO ZERO OUT
   
   FIXME
   """
   
   # Dst is average of stations' horizontal field normalized by the cosine 
   # the stations' dipole latitude
   # (this differs slightly from Sugiura's technique, which has never been
   #  mathematically or physically sensible; also, the "paper" cited above
   #  describes some sort of additional normalization for the ASY indices,
   #  but is extremely confusing, so we're leaving that out for now) 
   #dBHorizTot = ( (p.sqrt((dBNorthTot+5e4)**2+dBEastTot**2) - 
   #               (5e4*p.cos(p.arctan2(dBEastTot,(dBNorthTot+5e4)) ) )) / 
   #              p.cos(p.pi/2 - thetas))
   #dBHorizIon = ( (p.sqrt((dBNorthIon+5e4)**2+dBEastIon**2) - 
   #               (5e4*p.cos(p.arctan2(dBEastIon,(dBNorthIon+5e4)) ) )) / 
   #              p.cos(p.pi/2 - thetas))
   #dBHorizFAC = ( (p.sqrt((dBNorthFAC+5e4)**2+dBEastFAC**2) - 
   #               (5e4*p.cos(p.arctan2(dBEastFAC,(dBNorthFAC+5e4)) ) )) / 
   #              p.cos(p.pi/2 - thetas))
   #dBHorizMag = ( (p.sqrt((dBNorthMag+5e4)**2+dBEastMag**2) - 
   #               (5e4*p.cos(p.arctan2(dBEastMag,(dBNorthMag+5e4)) ) )) / 
   #              p.cos(p.pi/2 - thetas))
   
   
   
   # calculate main field to add to deltaB before calculating horizontal field,
   # then subtract quadratic sum of main field components, assuming this is the
   # equivalent to removing a quiet baseline...I know this seems like a strange
   # thing to do, but real-world indices have positive and negative values,
   # which would be impossible if vector-component baselines were removed
   # *before* adding up the disturbance vectors; so, we add plausible vector
   # component baselines to our disturbance vectors to emulate how real-world
   # observations are collected, determine the horizontal field, then remove
   # the horizontal baseline.
   dipoleMF = pyLTR.Tools.deltaBTimeSeries._dipoleMF
   mfObs = [dipoleMF(obs['dBTot'], geoGrid) for obs in dBObs]
   BNorth = p.array([obs['North']['data'] for obs in mfObs]).T
   BEast = p.array([obs['East']['data'] for obs in mfObs]).T
   BDown = p.array([obs['Down']['data'] for obs in mfObs]).T
      
   dBHorizIon = p.sqrt((dBNorthIon + BNorth)**2 + (dBEastIon + BEast)**2) # add main field
   dBHorizIon = dBHorizIon - p.sqrt(BNorth**2 + BEast**2) # remove horiz baseline
   dBHorizFAC = p.sqrt((dBNorthFAC + BNorth)**2 + (dBEastFAC + BEast)**2) # add main field
   dBHorizFAC = dBHorizFAC - p.sqrt(BNorth**2 + BEast**2) # remove horiz baseline
   dBHorizMag = p.sqrt((dBNorthMag + BNorth)**2 + (dBEastMag + BEast)**2) # add main field
   dBHorizMag = dBHorizMag - p.sqrt(BNorth**2 + BEast**2) # remove horiz baseline
   dBHorizTot = p.sqrt((dBNorthTot + BNorth)**2 + (dBEastTot + BEast)**2) # add main field
   dBHorizTot = dBHorizTot - p.sqrt(BNorth**2 + BEast**2) # remove horiz baseline
   
   
                 
   
   # indices to max/min disturbance are all based on the total current system; 
   # this allows the constituents to sum up to give the total
   DstMaxIdx = dBHorizTot.argmax(axis=1)
   DstMinIdx = dBHorizTot.argmin(axis=1)
   
   
   # I do not understand how slices and index arrays are combined, so just create
   # an index array for all time steps (rows)
   allT = list(range(dBNorthTot.shape[0]))
   
   # indices from total current system
   symDstTot = dBHorizTot.mean(axis=1)
   DstMaxTot = dBHorizTot[allT,DstMaxIdx]
   DstMinTot = dBHorizTot[allT,DstMinIdx]
   asyDstTot = DstMaxTot - DstMinTot
   
   # indices from ionospheric current system
   symDstIon = dBHorizIon.mean(axis=1)
   DstMaxIon = dBHorizIon[allT,DstMaxIdx]
   DstMinIon = dBHorizIon[allT,DstMinIdx]
   asyDstIon = DstMaxIon - DstMinIon
   
   # indices from field-aligned current system
   symDstFAC = dBHorizFAC.mean(axis=1)
   DstMaxFAC = dBHorizFAC[allT,DstMaxIdx]
   DstMinFAC = dBHorizFAC[allT,DstMinIdx]
   asyDstFAC = DstMaxFAC - DstMinFAC
   
   # indices from magnetospheric current system
   symDstMag = dBHorizMag.mean(axis=1)
   DstMaxMag = dBHorizMag[allT,DstMaxIdx]
   DstMinMag = dBHorizMag[allT,DstMinIdx]
   asyDstMag = DstMaxMag - DstMinMag
   
   
   
   
   
   
   
   # create time series object to hold sum total of constituents
   dBTot = pyLTR.TimeSeries()
   dBTot.append('datetime', 'Date & Time', '', dBObs[0]['dBTot']['datetime']['data'])
   dBTot.append('doy', 'Day of Year', 'days', dBObs[0]['dBTot']['doy']['data'])
   dBTot.append('SYMH', r'$\Delta B_{average}$', 'nT', symHTot.tolist())
   dBTot.append('ASYH', r'$\Delta B_{envelope}$', 'nT', asyHTot.tolist())
   dBTot.append('MaxH', r'$\Delta B_{upper}$', 'nT', HMaxTot.tolist())
   dBTot.append('MinH', r'$\Delta B_{lower}$', 'nT', HMinTot.tolist())
   dBTot.append('dBH', r'$\Delta B$', 'nT', (dBNorthTot / p.cos(p.pi/2 - thetas)).tolist())
   
   dBTot.append('SYMD', r'$\Delta B_{average}$', 'nT', symDTot.tolist())
   dBTot.append('ASYD', r'$\Delta B_{envelope}$', 'nT', asyDTot.tolist())
   dBTot.append('MaxD', r'$\Delta B_{upper}$', 'nT', DMaxTot.tolist())
   dBTot.append('MinD', r'$\Delta B_{lower}$', 'nT', DMinTot.tolist())
   dBTot.append('dBD', r'$\Delta B$', 'nT', dBEastTot.tolist())
   
   dBTot.append('SYMDst', r'$\Delta B_{average}$', 'nT', symDstTot.tolist())
   dBTot.append('ASYDst', r'$\Delta B_{envelope}$', 'nT', asyDstTot.tolist())
   dBTot.append('MaxDst', r'$\Delta B_{upper}$', 'nT', DstMaxTot.tolist())
   dBTot.append('MinDst', r'$\Delta B_{lower}$', 'nT', DstMinTot.tolist())
   #dBTot.append('dBDst', r'$\Delta B$', 'nT', p.sqrt(dBNorthTot**2+dBEastTot**2) / p.cos(p.pi/2 - thetas).tolist())
   dBTot.append('dBDst', r'$\Delta B$', 'nT', dBHorizTot.tolist())
   
   
   
   # create time series object to hold ionospheric current constituent
   dBIon = pyLTR.TimeSeries()
   dBIon.append('datetime', 'Date & Time', '', dBObs[0]['dBIon']['datetime']['data'])
   dBIon.append('doy', 'Day of Year', 'days', dBObs[0]['dBIon']['doy']['data'])
   dBIon.append('SYMH', r'$\Delta B_{average}$', 'nT', symHIon.tolist())
   dBIon.append('ASYH', r'$\Delta B_{envelope}$', 'nT', asyHIon.tolist())
   dBIon.append('MaxH', r'$\Delta B_{upper}$', 'nT', HMaxIon.tolist())
   dBIon.append('MinH', r'$\Delta B_{lower}$', 'nT', HMinIon.tolist())
   dBIon.append('dBH', r'$\Delta B$', 'nT', (dBNorthIon / p.cos(p.pi/2 - thetas)).tolist())
   
   dBIon.append('SYMD', r'$\Delta B_{average}$', 'nT', symDIon.tolist())
   dBIon.append('ASYD', r'$\Delta B_{envelope}$', 'nT', asyDIon.tolist())
   dBIon.append('MaxD', r'$\Delta B_{upper}$', 'nT', DMaxIon.tolist())
   dBIon.append('MinD', r'$\Delta B_{lower}$', 'nT', DMinIon.tolist())
   dBIon.append('dBD', r'$\Delta B$', 'nT', dBEastIon.tolist())
   
   dBIon.append('SYMDst', r'$\Delta B_{average}$', 'nT', symDstIon.tolist())
   dBIon.append('ASYDst', r'$\Delta B_{envelope}$', 'nT', asyDstIon.tolist())
   dBIon.append('MaxDst', r'$\Delta B_{upper}$', 'nT', DstMaxIon.tolist())
   dBIon.append('MinDst', r'$\Delta B_{lower}$', 'nT', DstMinIon.tolist())
   #dBIon.append('dBDst', r'$\Delta B$', 'nT', p.sqrt(dBNorthIon**2+dBEastIon**2) / p.cos(p.pi/2 - thetas).tolist())
   dBIon.append('dBDst', r'$\Delta B$', 'nT', dBHorizIon.tolist())
   
   
   
   # create time series object to hold field-aligned current constituent
   dBFAC = pyLTR.TimeSeries()
   dBFAC.append('datetime', 'Date & Time', '', dBObs[0]['dBFAC']['datetime']['data'])
   dBFAC.append('doy', 'Day of Year', 'days', dBObs[0]['dBFAC']['doy']['data'])
   dBFAC.append('SYMH', r'$\Delta B_{average}$', 'nT', symHFAC.tolist())
   dBFAC.append('ASYH', r'$\Delta B_{envelope}$', 'nT', asyHFAC.tolist())
   dBFAC.append('MaxH', r'$\Delta B_{upper}$', 'nT', HMaxFAC.tolist())
   dBFAC.append('MinH', r'$\Delta B_{lower}$', 'nT', HMinFAC.tolist())
   dBFAC.append('dBH', r'$\Delta B$', 'nT', (dBNorthFAC / p.cos(p.pi/2 - thetas)).tolist())
   
   dBFAC.append('SYMD', r'$\Delta B_{average}$', 'nT', symDFAC.tolist())
   dBFAC.append('ASYD', r'$\Delta B_{envelope}$', 'nT', asyDFAC.tolist())
   dBFAC.append('MaxD', r'$\Delta B_{upper}$', 'nT', DMaxFAC.tolist())
   dBFAC.append('MinD', r'$\Delta B_{lower}$', 'nT', DMinFAC.tolist())
   dBFAC.append('dBD', r'$\Delta B$', 'nT', dBEastFAC.tolist())
   
   dBFAC.append('SYMDst', r'$\Delta B_{average}$', 'nT', symDstFAC.tolist())
   dBFAC.append('ASYDst', r'$\Delta B_{envelope}$', 'nT', asyDstFAC.tolist())
   dBFAC.append('MaxDst', r'$\Delta B_{upper}$', 'nT', DstMaxFAC.tolist())
   dBFAC.append('MinDst', r'$\Delta B_{lower}$', 'nT', DstMinFAC.tolist())
   #dBFAC.append('dBDst', r'$\Delta B$', 'nT', p.sqrt(dBNorthFAC**2+dBEastFAC**2) / p.cos(p.pi/2 - thetas).tolist())
   dBFAC.append('dBDst', r'$\Delta B$', 'nT', dBHorizFAC.tolist())
   
   
   
   # create time series object to hold magnetospheric constituent
   dBMag = pyLTR.TimeSeries()
   dBMag.append('datetime', 'Date & Time', '', dBObs[0]['dBMag']['datetime']['data'])
   dBMag.append('doy', 'Day of Year', 'days', dBObs[0]['dBMag']['doy']['data'])
   dBMag.append('SYMH', r'$\Delta B_{average}$', 'nT', symHMag.tolist())
   dBMag.append('ASYH', r'$\Delta B_{envelope}$', 'nT', asyHMag.tolist())
   dBMag.append('MaxH', r'$\Delta B_{upper}$', 'nT', HMaxMag.tolist())
   dBMag.append('MinH', r'$\Delta B_{lower}$', 'nT', HMinMag.tolist())
   dBMag.append('dBH', r'$\Delta B$', 'nT', (dBNorthMag / p.cos(p.pi/2 - thetas)).tolist())
   
   dBMag.append('SYMD', r'$\Delta B_{average}$', 'nT', symDMag.tolist())
   dBMag.append('ASYD', r'$\Delta B_{envelope}$', 'nT', asyDMag.tolist())
   dBMag.append('MaxD', r'$\Delta B_{upper}$', 'nT', DMaxMag.tolist())
   dBMag.append('MinD', r'$\Delta B_{lower}$', 'nT', DMinMag.tolist())
   dBMag.append('dBD', r'$\Delta B$', 'nT', dBEastMag.tolist())
   
   dBMag.append('SYMDst', r'$\Delta B_{average}$', 'nT', symDstMag.tolist())
   dBMag.append('ASYDst', r'$\Delta B_{envelope}$', 'nT', asyDstMag.tolist())
   dBMag.append('MaxDst', r'$\Delta B_{upper}$', 'nT', DstMaxMag.tolist())
   dBMag.append('MinDst', r'$\Delta B_{lower}$', 'nT', DstMinMag.tolist())
   #dBMag.append('dBDst', r'$\Delta B$', 'nT', p.sqrt(dBNorthMag**2+dBEastMag**2) / p.cos(p.pi/2 - thetas).tolist())
   dBMag.append('dBDst', r'$\Delta B$', 'nT', dBHorizMag.tolist())
      
   
   
   SymAsyDst = {'dBTot':dBTot, 'dBIon':dBIon, 'dBFAC':dBFAC, 'dBMag':dBMag}
   
   return (SymAsyDst)
def calculateIndex(path='./',
                   run='',
                   t0='',
                   t1='',
                   obsList=None,
                   geoGrid=False,
                   ignoreBinary=False,
                   binaryType='pkl',
                   outDirName='figs'):
    """
   Compute deltaBs at virtual observatories, then generate an AE-like index,
   given LFM-MIX output files in path.
   
   Computes:
     AEdB      - a dict of pyLTR.TimeSeries objects, corresponding to the total,
                 ionospheric current, field-aligned current, and magnetospheric
                 current constituent of the AE, AU, AL, and the delta B_h's
                 used to derived these indices.
                 NOTE: while the outputs are pyLTR.TimeSeries objects, the
                       function generates binary pkl/mat files that combine
                       each specified observatory for each time step; this
                       helps reduce re-computation time for subsequent calls
                       to this function, and provides snapshots of output to
                       be read in by alternative analysis software
   
   Requires:
     Nothing, all inputs are optional
   
   Optional:
     path      - path to data directory holding LFM and MIX model output files
                 (default is current directory)
     run       - output filename prefix identifying LFM-MIX run (i.e., the part
                 of the filename prior to [mhd|mix]_yyyy-mm-ddTHH-MM-SSZ.hdf)
                 (default is any mhd|mix files in path)
     t0        - datetime object specifying the earliest of available time step
                 to include in the extraction
                 (default is earliest available)
     t1        - datetime object specfiying the latest of available time step
                 to include in the extraction
                 (default is last available)
     obsList   - a list of lists of observatory coordinates [phi,theta,rho,ID]
                 where ID is an optional 4th element to each coordinate set
                 that, ideally, uniquely identifies the observatory...if ID is
                 not specified, it is assigned an empty string.
                 (default is coordinate system origin)
     geoGrid   - if True, assume observatory coordinates are in geographic
                 coordinates rather than solar magnetic; same for outputs
                 (default is False)
     ignoreBinary - if True, ignore any pre-computed binary files and re-
                 compute everything from scratch; NOTE: individual binary files
                 will be ignored anyway if they are incompatible with specified
                 inputs, but this option avoids reading the binary file entirely.
                 (default is False)
     binaryType   - binary type to generate, NOT to read in...routine looks for
                 PKL files first, then mat files, then proceeds to re-compute
                 if neither are available.
                 (default is 'pkl')
     outDirName - name of directory into which all output will be placed; must
                 be a relative path, to be appended to the input path; this is
                 also where binary pkl/mat files will be expected if/when the
                 function tries to read pre-computed data.
                 (default is 'figs')
   """

    # define and geo-locate virtual observatories
    if obsList == None or len(obsList) == 0:
        obsList = _defaultObs()
        # force geoGrid=True
        geoGrid = True

    #print(('obsList: ',obsList))
    # get delta B time series for AE stations
    dBExtract = pyLTR.Tools.deltaBTimeSeries.extractQuantities
    dBObs = dBExtract(path=path,
                      run=run,
                      t0=t0,
                      t1=t1,
                      obsList=obsList,
                      geoGrid=geoGrid,
                      ignoreBinary=ignoreBinary,
                      binaryType=binaryType,
                      outDirName=outDirName)

    # convert lists of constituent vector components into NumPy arrays
    dBNorthIon = p.array([obs['dBIon']['North']['data'] for obs in dBObs]).T
    dBNorthFAC = p.array([obs['dBFAC']['North']['data'] for obs in dBObs]).T
    dBNorthMag = p.array([obs['dBMag']['North']['data'] for obs in dBObs]).T
    dBNorthTot = p.array([obs['dBTot']['North']['data'] for obs in dBObs]).T

    dBEastIon = p.array([obs['dBIon']['East']['data'] for obs in dBObs]).T
    dBEastFAC = p.array([obs['dBFAC']['East']['data'] for obs in dBObs]).T
    dBEastMag = p.array([obs['dBMag']['East']['data'] for obs in dBObs]).T
    dBEastTot = p.array([obs['dBTot']['East']['data'] for obs in dBObs]).T

    dBDownIon = p.array([obs['dBIon']['Down']['data'] for obs in dBObs]).T
    dBDownFAC = p.array([obs['dBFAC']['Down']['data'] for obs in dBObs]).T
    dBDownMag = p.array([obs['dBMag']['Down']['data'] for obs in dBObs]).T
    dBDownTot = p.array([obs['dBTot']['Down']['data'] for obs in dBObs]).T

    # calculate main field to add to deltaB before calculating horizontal field,
    # then subtract quadratic sum of main field components, assuming this is the
    # equivalent to removing a quiet baseline...I know this seems like a strange
    # thing to do, but real-world indices have positive and negative values,
    # which would be impossible if vector-component baselines were removed
    # *before* adding up the disturbance vectors; so, we add plausible vector
    # component baselines to our disturbance vectors to emulate how real-world
    # observations are collected, determine the horizontal field, then remove
    # the horizontal baseline.
    dipoleMF = pyLTR.Tools.deltaBTimeSeries._dipoleMF
    mfObs = [dipoleMF(obs['dBTot'], geoGrid) for obs in dBObs]
    BNorth = p.array([obs['North']['data'] for obs in mfObs]).T
    BEast = p.array([obs['East']['data'] for obs in mfObs]).T
    BDown = p.array([obs['Down']['data'] for obs in mfObs]).T

    dBHorizIon = p.sqrt(
        (dBNorthIon + BNorth)**2 + (dBEastIon + BEast)**2)  # add main field
    dBHorizIon = dBHorizIon - p.sqrt(
        BNorth**2 + BEast**2)  # remove horiz baseline
    dBHorizFAC = p.sqrt(
        (dBNorthFAC + BNorth)**2 + (dBEastFAC + BEast)**2)  # add main field
    dBHorizFAC = dBHorizFAC - p.sqrt(
        BNorth**2 + BEast**2)  # remove horiz baseline
    dBHorizMag = p.sqrt(
        (dBNorthMag + BNorth)**2 + (dBEastMag + BEast)**2)  # add main field
    dBHorizMag = dBHorizMag - p.sqrt(
        BNorth**2 + BEast**2)  # remove horiz baseline
    dBHorizTot = p.sqrt(
        (dBNorthTot + BNorth)**2 + (dBEastTot + BEast)**2)  # add main field
    dBHorizTot = dBHorizTot - p.sqrt(
        BNorth**2 + BEast**2)  # remove horiz baseline

    # compute simulated AU and AL based on the total field, then apply this to
    # mag, ion, and fac constituents to get constituent AEs that sum to the total.
    # NOTE: this is NOT the same as calculating envelopes for each constituent
    AUidx = dBHorizTot.argmax(axis=1)
    ALidx = dBHorizTot.argmin(axis=1)

    # I do not understand how slices and index arrays are combined, so just create
    # an index array for all time steps (rows)
    allT = list(range(dBHorizTot.shape[0]))

    AUTot = dBHorizTot[allT, AUidx]
    ALTot = dBHorizTot[allT, ALidx]
    AETot = AUTot - ALTot

    AUIon = dBHorizIon[allT, AUidx]
    ALIon = dBHorizIon[allT, ALidx]
    AEIon = AUIon - ALIon

    AUFAC = dBHorizFAC[allT, AUidx]
    ALFAC = dBHorizFAC[allT, ALidx]
    AEFAC = AUFAC - ALFAC

    AUMag = dBHorizMag[allT, AUidx]
    ALMag = dBHorizMag[allT, ALidx]
    AEMag = AUMag - ALMag

    # create time series object to hold sum total of constituents
    dBTot = pyLTR.TimeSeries()
    dBTot.append('datetime', 'Date & Time', '',
                 dBObs[0]['dBTot']['datetime']['data'])
    dBTot.append('doy', 'Day of Year', 'days',
                 dBObs[0]['dBTot']['doy']['data'])
    dBTot.append('AE', r'$\Delta B_{envelope}$', 'nT', AETot.tolist())
    dBTot.append('AU', r'$\Delta B_{upper}$', 'nT', AUTot.tolist())
    dBTot.append('AL', r'$\Delta B_{lower}$', 'nT', ALTot.tolist())
    #dBTot.append('dBh', r'$\Delta B$', 'nT', dBHorizTot.tolist())
    dBTot.append('dBh', [r'$\Delta B_{%s}' % o[3] for o in obsList], 'nT',
                 dBHorizTot.tolist())

    # create time series object to hold ionospheric current constituent
    dBIon = pyLTR.TimeSeries()
    dBIon.append('datetime', 'Date & Time', '',
                 dBObs[0]['dBIon']['datetime']['data'])
    dBIon.append('doy', 'Day of Year', 'days',
                 dBObs[0]['dBIon']['doy']['data'])
    dBIon.append('AE', r'$\Delta B_{envelope}$', 'nT', AEIon.tolist())
    dBIon.append('AU', r'$\Delta B_{upper}$', 'nT', AUIon.tolist())
    dBIon.append('AL', r'$\Delta B_{lower}$', 'nT', ALIon.tolist())
    #dBIon.append('dBh', r'$\Delta B$', 'nT', dBHorizIon.tolist())
    dBIon.append('dBh', [r'$\Delta B_{%s}$' % o[3] for o in obsList], 'nT',
                 dBHorizIon.tolist())

    # create time series object to hold field aligned current constituent
    dBFAC = pyLTR.TimeSeries()
    dBFAC.append('datetime', 'Date & Time', '',
                 dBObs[0]['dBFAC']['datetime']['data'])
    dBFAC.append('doy', 'Day of Year', 'days',
                 dBObs[0]['dBFAC']['doy']['data'])
    dBFAC.append('AE', r'$\Delta B_{envelope}$', 'nT', AEFAC.tolist())
    dBFAC.append('AU', r'$\Delta B_{upper}$', 'nT', AUFAC.tolist())
    dBFAC.append('AL', r'$\Delta B_{lower}$', 'nT', ALFAC.tolist())
    #dBFAC.append('dBh', r'$\Delta B$', 'nT', dBHorizFAC.tolist())
    dBFAC.append('dBh', [r'$\Delta B_{%s}$' % o[3] for o in obsList], 'nT',
                 dBHorizFAC.tolist())

    # create time series object to hold magnetospheric current constituent
    dBMag = pyLTR.TimeSeries()
    dBMag.append('datetime', 'Date & Time', '',
                 dBObs[0]['dBMag']['datetime']['data'])
    dBMag.append('doy', 'Day of Year', 'days',
                 dBObs[0]['dBMag']['doy']['data'])
    dBMag.append('AE', r'$\Delta B_{envelope}$', 'nT', AEMag.tolist())
    dBMag.append('AU', r'$\Delta B_{upper}$', 'nT', AUMag.tolist())
    dBMag.append('AL', r'$\Delta B_{lower}$', 'nT', ALMag.tolist())
    #dBMag.append('dBh', r'$\Delta B$', 'nT', dBHorizMag.tolist())
    dBMag.append('dBh', [r'$\Delta B_{%s}$' % o[3] for o in obsList], 'nT',
                 dBHorizMag.tolist())

    # output is a dictionary of time series objects
    AEdB = {'dBTot': dBTot, 'dBIon': dBIon, 'dBFAC': dBFAC, 'dBMag': dBMag}
    return (AEdB)
Exemple #7
0
def extractQuantities(path, run, t0, t1):
    """
    Extract MIX quantities from the input path.
    
    Execute `mixTimeSeries.py --help` for details on the function parameters (path,run,t0,t1).
    
    Outputs a pyLTR.TimeSeries object containing the data.
    """
    data = pyLTR.Models.MIX(path, run)

    # hard-coded input for testing & debugging:
    #data = pyLTR.Models.LFM('/hao/aim2/schmitt/data/LTR-2_0_1b/r1432/March1995/LR/single', 'LRs')

    #Make sure variables are defined in the model.
    modelVars = data.getVarNames()
    for v in [
            'Grid X', 'Grid Y', 'Potential North [V]', 'Potential South [V]',
            'FAC North [A/m^2]', 'FAC South [A/m^2]',
            'Pedersen conductance North [S]', 'Pedersen conductance South [S]',
            'Hall conductance North [S]', 'Hall conductance South [S]',
            'Average energy North [keV]', 'Average energy South [keV]',
            'Number flux North [1/cm^2 s]', 'Number flux South [1/cm^2 s]'
    ]:
        assert (v in modelVars)

    timeRange = data.getTimeRange()
    if len(timeRange) == 0:
        raise Exception((
            'No data files found.  Are you pointing to the correct run directory?'
        ))

    index0 = 0
    if t0:
        for i, t in enumerate(timeRange):
            if t0 >= t:
                index0 = i

    index1 = len(timeRange) - 1
    if t1:
        for i, t in enumerate(timeRange):
            if t1 >= t:
                index1 = i

    print(('Extracting MIX quantities for time series over %d time steps.' %
           (index1 - index0)))

    # Output a status bar displaying how far along the computation is.
    progress = pyLTR.StatusBar(0, index1 - index0)
    progress.start()

    t_doy = []
    mhpNorth = []
    mhpSouth = []
    bhpNorth = []
    bhpSouth = []
    chpNorth = []
    chpSouth = []

    # Pre-compute area of the grid.
    x = data.read('Grid X', timeRange[index0])
    y = data.read('Grid Y', timeRange[index0])
    # Fix singularity at the pole
    x[:, 0] = 0.0
    y[:, 0] = 0.0
    z = numpy.sqrt(1.0 - x**2 - y**2)
    ri = 6500.0e3  # Radius of ionosphere
    areaMixGrid = pyLTR.math.integrate.calcFaceAreas(x, y, z) * ri * ri
    doBBEaverage = 1
    doCUSPaverage = 1
    averagesize = 5

    for i, time in enumerate(timeRange[index0:index1]):
        try:
            # -- Day of Year
            tt = time.timetuple()
            t_doy.append(tt.tm_yday + tt.tm_hour / 24.0 + tt.tm_min / 1440.0 +
                         tt.tm_sec / 86400.0)

            # --- MonoDiffuse Hemispheric Power
            energy = data.read('Average energy North [keV]', time)
            flux = data.read('Number flux North [1/cm^2 s]', time)
            hp = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            # KeV/cm^2s to mW/m^2 to GW
            mhpNorth.append(hp.sum() * 1.6e-21)

            energy = data.read('Average energy South [keV]', time)
            flux = data.read('Number flux South [1/cm^2 s]', time)
            hp = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            # KeV/cm^2s to mW/m^2 to GW
            mhpSouth.append(hp.sum() * 1.6e-21)

            # --- BBE Hemispheric Power
            flux = data.read('BBE number flux North [1/cm^2 s]', time)
            energy = data.read('BBE average energy North [keV]', time)
            hp = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            # KeV/cm^2s to mW/m^2 to GW
            if doBBEaverage and i > averagesize:
                bhpNorth.append(
                    (sum(bhpNorth[-averagesize:-1]) + hp.sum() * 1.6e-21) /
                    averagesize)
            else:
                bhpNorth.append(hp.sum() * 1.6e-21)

            flux = data.read('BBE number flux South [1/cm^2 s]', time)
            energy = data.read('BBE average energy South [keV]', time)
            hp = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            # KeV/cm^2s to mW/m^2 to GW
            if doBBEaverage and i > averagesize:
                bhpSouth.append(
                    (sum(bhpSouth[-averagesize:-1]) + hp.sum() * 1.6e-21) /
                    averagesize)
            else:
                bhpSouth.append(hp.sum() * 1.6e-21)

            # --- Cusp Hemispheric Power
            flux = data.read('Cusp number flux North [1/cm^2 s]', time)
            energy = data.read('Cusp average energy North [keV]', time)
            hp = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            # KeV/cm^2s to mW/m^2 to GW
            if doCUSPaverage and i > averagesize:
                chpNorth.append(
                    (sum(chpNorth[-averagesize:-1]) + hp.sum() * 1.6e-21) /
                    averagesize)
            else:
                chpNorth.append(hp.sum() * 1.6e-21)

            flux = data.read('Cusp number flux South [1/cm^2 s]', time)
            energy = data.read('Cusp average energy South [keV]', time)
            hp = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            # KeV/cm^2s to mW/m^2 to GW
            if doCUSPaverage and i > averagesize:
                chpSouth.append(
                    (sum(chpSouth[-averagesize:-1]) + hp.sum() * 1.6e-21) /
                    averagesize)
            else:
                chpSouth.append(hp.sum() * 1.6e-21)

            progress.increment()
        except KeyboardInterrupt:
            # Exit when the user hits CTRL+C.
            progress.stop()
            progress.join()
            print('Exiting.')
            import sys
            sys.exit(0)
        except:
            # Cleanup progress bar if something bad happened.
            progress.stop()
            progress.join()
            raise
    progress.stop()
    progress.join()

    dataNorth = pyLTR.TimeSeries()
    dataSouth = pyLTR.TimeSeries()
    dataNorth.append('datetime', 'Date & Time', '', timeRange[index0:index1])
    dataSouth.append('datetime', 'Date & Time', '', timeRange[index0:index1])
    dataNorth.append('doy', 'Day of Year', '', t_doy)
    dataSouth.append('doy', 'Day of Year', '', t_doy)

    # "N" and "S" label subscripts are redundant here, potentially leading to
    # mis-labeling of plots
    #dataNorth.append('cpcp', r'$\Phi_N$', 'kV', cpcpNorth)
    #dataSouth.append('cpcp', r'$\Phi_S$', 'kV', cpcpSouth)
    #
    #dataNorth.append('hp', r'$HP_N$', 'GW', hpNorth)
    #dataSouth.append('hp', r'$HP_S$', 'GW', hpSouth)
    #
    #dataNorth.append('ipfac', r'$FAC_N$', 'MA', ipfacNorth)
    #dataSouth.append('ipfac', r'$FAC_S$', 'MA', ipfacSouth)

    dataNorth.append('diffusemono', r'$DiffuseMono$', 'GW', mhpNorth)
    dataSouth.append('diffusemono', r'$DiffuseMono$', 'GW', mhpSouth)

    dataNorth.append('bbe', r'$BBE$', 'GW', bhpNorth)
    dataSouth.append('bbe', r'$BBE$', 'GW', bhpSouth)

    dataNorth.append('cusp', r'$Cusp$', 'GW', chpNorth)
    dataSouth.append('cusp', r'$Cusp$', 'GW', chpSouth)

    return (dataNorth, dataSouth)
Exemple #8
0
def extractQuantities(path, run, runExt, t0, t1):
    """
    Extract LFM ION quantities from the input path.
    
    Execute `lfmionTimeSeries.py --help` for details on the function parameters (path,run,t0,t1).
    
    Outputs a pyLTR.TimeSeries object containing the data.
    """
    data = pyLTR.Models.LFMION(path, run, ext=runExt)

    # hard-coded input for testing & debugging:
    #data = pyLTR.Models.LFM('/hao/aim2/schmitt/data/LTR-2_0_1b/r1432/March1995/LR/single', 'LRs')

    #Make sure variables are defined in the model.
    modelVars = data.getVarNames()
    for v in [
            'x_interp', 'y_interp', 'potnorth', 'potsouth', 'curnorth',
            'cursouth', 'SigmaP_north', 'SigmaP_south', 'SigmaH_north',
            'SigmaH_south', 'avE_north', 'avE_south', 'fluxnorth', 'fluxsouth'
    ]:
        assert (v in modelVars)

    timeRange = data.getTimeRange()
    if len(timeRange) == 0:
        raise Exception((
            'No data files found.  Are you pointing to the correct run directory?'
        ))

    index0 = 0
    if t0:
        for i, t in enumerate(timeRange):
            if t0 >= t:
                index0 = i

    index1 = len(timeRange) - 1
    if t1:
        for i, t in enumerate(timeRange):
            if t1 >= t:
                index1 = i

    print(
        ('Extracting LFM ION quantities for time series over %d time steps.' %
         (index1 - index0)))

    # Output a status bar displaying how far along the computation is.
    progress = pyLTR.StatusBar(0, index1 - index0)
    progress.start()

    t_doy = []
    cpcpNorth = []
    cpcpSouth = []
    hpNorth = []
    hpSouth = []
    ipfacNorth = []
    ipfacSouth = []

    # Pre-compute area of the grid.
    x = data.read('x_interp', timeRange[index0])
    y = data.read('y_interp', timeRange[index0])
    # Fix singularity at the pole
    x[:, 0] = 0.0
    y[:, 0] = 0.0
    z = numpy.sqrt(1.0 - x**2 - y**2)
    ri = 6370.0e3  # Radius of ionosphere
    areaMixGrid = pyLTR.math.integrate.calcFaceAreas(x, y, z) * ri * ri

    for i, time in enumerate(timeRange[index0:index1]):
        try:
            # -- Day of Year
            tt = time.timetuple()
            t_doy.append(tt.tm_yday + tt.tm_hour / 24.0 + tt.tm_min / 1440.0 +
                         tt.tm_sec / 86400.0)

            # --- Cross Polar Cap Potential
            psiNorth = data.read('potnorth', time) / 1000.0
            cpcpNorth.append(psiNorth.max() - psiNorth.min())

            psiSouth = data.read('potsouth', time) / 1000.0
            cpcpSouth.append(psiSouth.max() - psiSouth.min())

            # --- Hemispheric Power
            energy = data.read('avE_north', time)
            flux = data.read('fluxnorth', time)
            hp = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            # KeV/cm^2s to mW/m^2 to GW
            hpNorth.append(hp.sum() * 1.6e-21)

            energy = data.read('avE_south', time)
            flux = data.read('fluxsouth', time)
            hp = areaMixGrid * energy[:-1, :-1] * flux[:-1, :-1]
            # KeV/cm^2s to mW/m^2 to GW
            hpSouth.append(hp.sum() * 1.6e-21)

            # --- Positive current density
            fac = data.read('curnorth', time)
            fac[fac <= 0] = 0.0
            pfac = areaMixGrid * fac[:-1, :-1]
            ipfacNorth.append(pfac.sum() / 1.0e6)

            fac = data.read('cursouth', time)
            fac[fac <= 0] = 0.0
            pfac = areaMixGrid * fac[:-1, :-1]
            ipfacSouth.append(pfac.sum() / 1.0e6)

            progress.increment()
        except KeyboardInterrupt:
            # Exit when the user hits CTRL+C.
            progress.stop()
            progress.join()
            print('Exiting.')
            import sys
            sys.exit(0)
        except:
            # Cleanup progress bar if something bad happened.
            progress.stop()
            progress.join()
            raise
    progress.stop()
    progress.join()

    dataNorth = pyLTR.TimeSeries()
    dataSouth = pyLTR.TimeSeries()
    dataNorth.append('datetime', 'Date & Time', '', timeRange[index0:index1])
    dataSouth.append('datetime', 'Date & Time', '', timeRange[index0:index1])
    dataNorth.append('doy', 'Day of Year', '', t_doy)
    dataSouth.append('doy', 'Day of Year', '', t_doy)

    dataNorth.append('cpcp', r'$\Phi_N$', 'kV', cpcpNorth)
    dataSouth.append('cpcp', r'$\Phi_S$', 'kV', cpcpSouth)

    dataNorth.append('hp', r'$HP_N$', 'GW', hpNorth)
    dataSouth.append('hp', r'$HP_S$', 'GW', hpSouth)

    dataNorth.append('ipfac', r'$FAC_N$', 'MA', ipfacNorth)
    dataSouth.append('ipfac', r'$FAC_S$', 'MA', ipfacSouth)

    return (dataNorth, dataSouth)
Exemple #9
0
# Custom imports
import pyLTR

if __name__ == '__main__':
    # Generate some arrays
    x = numpy.deg2rad(numpy.arange(0, 360, 1, float))
    y = numpy.sin(x)
    y1 = numpy.cos(x)
    y2 = y + y1
    z = 2.0 * y
    z1 = 2.0 * y1
    z2 = 2.0 * y2

    # Store arrays as pyLTR.TimeSeries objects
    Dict = pyLTR.TimeSeries()
    Dict.append('Xval', 'Time', 's', x)
    Dict.append('Yval', 'Amp', 'm', y)
    Dict.append('Y1val', 'Amp 1', 'ft', y1)
    Dict.append('Y2val', 'Amp 2', 'cm', y2)
    Dict['MetaData'] = "This is the metadata"

    Dict2 = pyLTR.TimeSeries()
    Dict2.append('Xval', 'Time', 's', x)
    Dict2.append('Yval', 'Amp', 'm', z)
    Dict2.append('Y1val', 'Amp 1', 'ft', z1)
    Dict2.append('Y2val', 'Amp 2', 'cm', z2)
    Dict2['MetaData'] = "This is the metadata"

    # Make simple Time Series plots via pyLTR.Graphics.TimeSeries module:
    pylab.figure(1)