def mms_load_feeps(trange=['2015-10-16', '2015-10-17'], probe='1', data_rate='srvy', level='l2', datatype='electron', varformat=None, varnames=[], get_support_data=True, suffix='', time_clip=False, no_update=False, available=False, notplot=False, no_flatfield_corrections=False, data_units=['count_rate', 'intensity'], latest_version=False, major_version=False, min_version=None, cdf_version=None, spdf=False, always_prompt=False): """ This function loads FEEPS data into tplot variables Parameters ---------- trange : list of str time range of interest [starttime, endtime] with the format 'YYYY-MM-DD','YYYY-MM-DD'] or to specify more or less than a day ['YYYY-MM-DD/hh:mm:ss','YYYY-MM-DD/hh:mm:ss'] probe : str or list of str list of probes, valid values for MMS probes are ['1','2','3','4']. data_rate : str or list of str instrument data rates for FEEPS include ['brst', 'srvy']. The default is 'srvy'. level : str indicates level of data processing. the default if no level is specified is 'l2' datatype : str or list of str Valid datatypes for FEEPS are: L2, L1b: ['electron', 'ion'] L1a: ['electron-bottom', 'electron-top', 'ion-bottom', 'ion-top'] get_support_data: bool Data with an attribute "VAR_TYPE" with a value of "support_data" will be loaded into tplot. By default, only loads in data with a "VAR_TYPE" attribute of "data". time_clip: bool Data will be clipped to the exact trange specified by the trange keyword. varformat: str The file variable formats to load into tplot. Wildcard character "*" is accepted. By default, all variables are loaded in. varnames: list of str List of variable names to load (if not specified, all data variables are loaded) suffix: str The tplot variable names will be given this suffix. By default, no suffix is added. notplot: bool If True, then data are returned in a hash table instead of being stored in tplot variables (useful for debugging, and access to multi-dimensional data products) available: bool If True, simply return the available data files (without downloading) for the requested paramters no_update: bool Set this flag to preserve the original data. if not set and newer data is found the existing data will be overwritten cdf_version: str Specify a specific CDF version # to load (e.g., cdf_version='4.3.0') min_version: str Specify a minimum CDF version # to load latest_version: bool Only grab the latest CDF version in the requested time interval major_version: bool Only open the latest major CDF version (e.g., X in vX.Y.Z) in the requested time interval always_prompt: bool Set this keyword to always prompt for the user's username and password; useful if you accidently save an incorrect password, or if your SDC password has changed spdf: bool If True, download the data from the SPDF instead of the SDC Returns: List of tplot variables created. """ tvars = mms_load_data(trange=trange, notplot=notplot, probe=probe, data_rate=data_rate, level=level, instrument='feeps', datatype=datatype, varformat=varformat, varnames=varnames, get_support_data=get_support_data, suffix=suffix, time_clip=time_clip, no_update=no_update, available=available, latest_version=latest_version, major_version=major_version, min_version=min_version, cdf_version=cdf_version, spdf=spdf, always_prompt=always_prompt) if tvars == [] or available or notplot or CONFIG['download_only']: return tvars probes = probe if isinstance(probe, list) else [probe] data_rates = data_rate if isinstance(data_rate, list) else [data_rate] levels = level if isinstance(level, list) else [level] datatypes = datatype if isinstance(datatype, list) else [datatype] data_units = data_units if isinstance(data_units, list) else [data_units] probes = [str(p) for p in probes] mms_feeps_correct_energies(probes, data_rate, level=level, suffix=suffix) if not no_flatfield_corrections: mms_feeps_flat_field_corrections(probes=probes, data_rate=data_rate, suffix=suffix) for probe in probes: for lvl in levels: for drate in data_rates: for datatype in datatypes: mms_feeps_remove_bad_data(probe=probe, data_rate=drate, datatype=datatype, level=lvl, suffix=suffix) for data_unit in data_units: eyes = mms_feeps_active_eyes(trange, probe, drate, datatype, lvl) split_vars = mms_feeps_split_integral_ch( data_unit, datatype, probe, suffix=suffix, data_rate=drate, level=lvl, sensor_eyes=eyes) sun_removed_vars = mms_feeps_remove_sun( eyes, trange, probe=probe, datatype=datatype, data_units=data_unit, data_rate=drate, level=lvl, suffix=suffix) omni_vars = mms_feeps_omni(eyes, probe=probe, datatype=datatype, data_units=data_unit, data_rate=drate, level=lvl, suffix=suffix) tvars = tvars + split_vars + sun_removed_vars + omni_vars tvars.append( mms_feeps_spin_avg(probe=probe, data_units=data_unit, datatype=datatype, data_rate=drate, level=lvl, suffix=suffix)) return tvars
def mms_feeps_pad(bin_size=16.3636, probe='1', energy=[70, 600], level='l2', suffix='', datatype='electron', data_units='intensity', data_rate='srvy', angles_from_bfield=False): # account for angular response (finite field of view) of instruments # electrons can use +/- 21.4 deg on each pitch angle as average response angle; ions can start with +/-10 deg, but both need to be further refined if datatype == 'electron': dangresp = 21.4 # deg elif datatype == 'ion': dangresp = 10.0 # deg if energy[0] < 32.0: print('Please select a starting energy of 32 keV or above') return units_label = '' if data_units == 'intensity': units_label = '1/(cm^2-sr-s-keV)' elif data_units == 'counts': units_label = '[counts/s]' prefix = 'mms' + probe n_pabins = 180/bin_size pa_bins = [180.*pa_bin/n_pabins for pa_bin in range(0, int(n_pabins)+1)] pa_label = [180.*pa_bin/n_pabins+bin_size/2. for pa_bin in range(0, int(n_pabins))] if data_rate == 'brst' and angles_from_bfield == False: # v5.5+ = mms1_epd_feeps_srvy_l2_electron_pitch_angle pa_times, pa_data = get_data(prefix+'_epd_feeps_'+data_rate+'_'+level+'_'+datatype+'_pitch_angle'+suffix) else: pa_var, idx_maps = mms_feeps_pitch_angles(probe=probe, level=level, data_rate=data_rate, suffix=suffix) pa_times, pa_data = get_data(pa_var) if pa_data is None: print("Error, couldn't find the PA variable") return eyes = mms_feeps_active_eyes([pa_times.min(), pa_times.max()], probe, data_rate, datatype, level) pa_data_map = {} if data_rate == 'srvy': if datatype == 'electron': pa_data_map['top-electron'] = idx_maps['electron-top'] pa_data_map['bottom-electron'] = idx_maps['electron-bottom'] if datatype == 'ion': pa_data_map['top-ion'] = idx_maps['electron-top'] pa_data_map['bottom-ion'] = idx_maps['ion-bottom'] elif data_rate == 'brst': # note: the following are indices of the top/bottom sensors in pa_data # they should be consistent with pa_dlimits.labels pa_data_map['top-electron'] = [0, 1, 2, 3, 4, 5, 6, 7, 8] pa_data_map['bottom-electron'] = [9, 10, 11, 12, 13, 14, 15, 16, 17] # and ions: pa_data_map['top-ion'] = [0, 1, 2] pa_data_map['bottom-ion'] = [3, 4, 5] sensor_types = ['top', 'bottom'] if datatype == 'electron': dflux = np.zeros([len(pa_times), len(pa_data_map['top-electron'])+len(pa_data_map['bottom-electron'])]) dpa = np.zeros([len(pa_times), len(pa_data_map['top-electron'])+len(pa_data_map['bottom-electron'])]) elif datatype == 'ion': dflux = np.zeros([len(pa_times), len(pa_data_map['top-ion'])+len(pa_data_map['bottom-ion'])]) dpa = np.zeros([len(pa_times), len(pa_data_map['top-ion'])+len(pa_data_map['bottom-ion'])]) for s_type in sensor_types: pa_map = pa_data_map[s_type+'-'+datatype] particle_idxs = [eye-1 for eye in eyes[s_type]] for isen, sensor_num in enumerate(particle_idxs): var_name = 'mms'+str(probe)+'_epd_feeps_'+data_rate+'_'+level+'_'+datatype+'_'+s_type+'_'+data_units+'_sensorid_'+str(sensor_num+1)+'_clean_sun_removed'+suffix times, data, energies = get_data(var_name) data[data == 0] = 'nan' # remove any 0s before averaging # energy indices to use: indx = np.where((energies >= energy[0]) & (energies <= energy[1])) with warnings.catch_warnings(): warnings.simplefilter("ignore", category=RuntimeWarning) dflux[:, pa_map[isen]] = np.nanmean(data[:, indx[0]], axis=1) dpa[:, pa_map[isen]] = pa_data[:, pa_map[isen]] # we need to replace the 0.0s left in after populating dpa with NaNs; these # 0.0s are left in there because these points aren't covered by sensors loaded # for this datatype/data_rate dpa[dpa == 0] = 'nan' pa_flux = np.zeros([len(pa_times), int(n_pabins)]) delta_pa = (pa_bins[1]-pa_bins[0])/2.0 # Now loop through PA bins and time, find the telescopes where there is data in those bins and average it up! for pa_idx, pa_time in enumerate(pa_times): for ipa in range(0, int(n_pabins)): if not np.isnan(dpa[pa_idx, :][0]): ind = np.where((dpa[pa_idx, :] + dangresp >= pa_label[ipa]-delta_pa) & (dpa[pa_idx, :]-dangresp < pa_label[ipa]+delta_pa)) if ind[0].size != 0: with warnings.catch_warnings(): warnings.simplefilter("ignore", category=RuntimeWarning) if len(ind[0]) > 1: pa_flux[pa_idx, ipa] = np.nanmean(dflux[pa_idx, ind[0]], axis=0) else: pa_flux[pa_idx, ipa] = dflux[pa_idx, ind[0]] pa_flux[pa_flux == 0] = 'nan' # fill any missed bins with NAN en_range_string = str(int(energy[0])) + '-' + str(int(energy[1])) + 'keV' new_name = 'mms'+probe+'_epd_feeps_'+data_rate+'_'+level+'_'+datatype+'_'+data_units+'_'+ en_range_string +'_pad'+suffix store_data(new_name, data={'x': times, 'y': pa_flux, 'v': pa_label}) options(new_name, 'ylog', False) options(new_name, 'zlog', True) options(new_name, 'spec', True) options(new_name, 'Colormap', 'jet') options(new_name, 'ztitle', units_label) options(new_name, 'ytitle', 'MMS' + str(probe) + ' ' + datatype + ' PA (deg)') # create the spin-averaged PAD spin_avg_var = mms_feeps_pad_spinavg(probe=probe, data_units=data_units, datatype=datatype, data_rate=data_rate, level=level, suffix=suffix, energy=energy) return [new_name, spin_avg_var]
def mms_feeps_pitch_angles(trange=None, probe='1', level='l2', data_rate='srvy', datatype='electron', suffix=''): """ Generates a tplot variable containing the FEEPS pitch angles for each telescope from magnetic field data. Parameters: trange : list of str time range of interest [starttime, endtime] with the format 'YYYY-MM-DD','YYYY-MM-DD'] or to specify more or less than a day ['YYYY-MM-DD/hh:mm:ss','YYYY-MM-DD/hh:mm:ss'] probe: str probe #, e.g., '4' for MMS4 level: str data level, e.g., 'l2' data_rate: str instrument data rate, e.g., 'srvy' or 'brst' datatype: str 'electron' or 'ion' suffix: str suffix of the loaded data Returns: Tuple: (tplot variable created, hash table used by PAD routine) """ # get the times from the currently loaded FEEPS data times, pa_data = get_data('mms' + probe + '_epd_feeps_' + data_rate + '_' + level + '_' + datatype + '_pitch_angle' + suffix) if times is not None: if trange is None: trange = [float(times.min()), float(times.max())] eyes = mms_feeps_active_eyes(trange, probe, data_rate, datatype, level) # need the B-field data mms_load_fgm(trange=trange, probe=probe, data_rate=data_rate, varformat='*_b_bcs_*') btimes, Bbcs = get_data('mms' + probe + '_fgm_b_bcs_' + data_rate + '_l2') idx_maps = None # Rotation matrices for FEEPS coord system (FCS) into body coordinate system (BCS): Ttop = [[1. / np.sqrt(2.), -1. / np.sqrt(2.), 0], [1. / np.sqrt(2.), 1. / np.sqrt(2.), 0], [0, 0, 1]] Tbot = [[-1. / np.sqrt(2.), -1. / np.sqrt(2.), 0], [-1. / np.sqrt(2.), 1. / np.sqrt(2.), 0], [0, 0, -1]] # Telescope vectors in FCS: V1fcs = [0.347, -0.837, 0.423] V2fcs = [0.347, -0.837, -0.423] V3fcs = [0.837, -0.347, 0.423] V4fcs = [0.837, -0.347, -0.423] V5fcs = [-0.087, 0.000, 0.996] V6fcs = [0.104, 0.180, 0.978] V7fcs = [0.654, -0.377, 0.656] V8fcs = [0.654, -0.377, -0.656] V9fcs = [0.837, 0.347, 0.423] V10fcs = [0.837, 0.347, -0.423] V11fcs = [0.347, 0.837, 0.423] V12fcs = [0.347, 0.837, -0.423] if datatype == 'electron': pas = np.empty([len(btimes), 18]) # pitch angles for each eye at eaceh time # Telescope vectors in Body Coordinate System: # Factors of -1 account for 180 deg shift between particle velocity and telescope normal direction: # Top: Vt1bcs = [ -1. * (Ttop[0][0] * V1fcs[0] + Ttop[0][1] * V1fcs[1] + Ttop[0][2] * V1fcs[2]), -1. * (Ttop[1][0] * V1fcs[0] + Ttop[1][1] * V1fcs[1] + Ttop[1][2] * V1fcs[2]), -1. * (Ttop[2][0] * V1fcs[0] + Ttop[2][1] * V1fcs[1] + Ttop[2][2] * V1fcs[2]) ] Vt2bcs = [ -1. * (Ttop[0][0] * V2fcs[0] + Ttop[0][1] * V2fcs[1] + Ttop[0][2] * V2fcs[2]), -1. * (Ttop[1][0] * V2fcs[0] + Ttop[1][1] * V2fcs[1] + Ttop[1][2] * V2fcs[2]), -1. * (Ttop[2][0] * V2fcs[0] + Ttop[2][1] * V2fcs[1] + Ttop[2][2] * V2fcs[2]) ] Vt3bcs = [ -1. * (Ttop[0][0] * V3fcs[0] + Ttop[0][1] * V3fcs[1] + Ttop[0][2] * V3fcs[2]), -1. * (Ttop[1][0] * V3fcs[0] + Ttop[1][1] * V3fcs[1] + Ttop[1][2] * V3fcs[2]), -1. * (Ttop[2][0] * V3fcs[0] + Ttop[2][1] * V3fcs[1] + Ttop[2][2] * V3fcs[2]) ] Vt4bcs = [ -1. * (Ttop[0][0] * V4fcs[0] + Ttop[0][1] * V4fcs[1] + Ttop[0][2] * V4fcs[2]), -1. * (Ttop[1][0] * V4fcs[0] + Ttop[1][1] * V4fcs[1] + Ttop[1][2] * V4fcs[2]), -1. * (Ttop[2][0] * V4fcs[0] + Ttop[2][1] * V4fcs[1] + Ttop[2][2] * V4fcs[2]) ] Vt5bcs = [ -1. * (Ttop[0][0] * V5fcs[0] + Ttop[0][1] * V5fcs[1] + Ttop[0][2] * V5fcs[2]), -1. * (Ttop[1][0] * V5fcs[0] + Ttop[1][1] * V5fcs[1] + Ttop[1][2] * V5fcs[2]), -1. * (Ttop[2][0] * V5fcs[0] + Ttop[2][1] * V5fcs[1] + Ttop[2][2] * V5fcs[2]) ] Vt9bcs = [ -1. * (Ttop[0][0] * V9fcs[0] + Ttop[0][1] * V9fcs[1] + Ttop[0][2] * V9fcs[2]), -1. * (Ttop[1][0] * V9fcs[0] + Ttop[1][1] * V9fcs[1] + Ttop[1][2] * V9fcs[2]), -1. * (Ttop[2][0] * V9fcs[0] + Ttop[2][1] * V9fcs[1] + Ttop[2][2] * V9fcs[2]) ] Vt10bcs = [ -1. * (Ttop[0][0] * V10fcs[0] + Ttop[0][1] * V10fcs[1] + Ttop[0][2] * V10fcs[2]), -1. * (Ttop[1][0] * V10fcs[0] + Ttop[1][1] * V10fcs[1] + Ttop[1][2] * V10fcs[2]), -1. * (Ttop[2][0] * V10fcs[0] + Ttop[2][1] * V10fcs[1] + Ttop[2][2] * V10fcs[2]) ] Vt11bcs = [ -1. * (Ttop[0][0] * V11fcs[0] + Ttop[0][1] * V11fcs[1] + Ttop[0][2] * V11fcs[2]), -1. * (Ttop[1][0] * V11fcs[0] + Ttop[1][1] * V11fcs[1] + Ttop[1][2] * V11fcs[2]), -1. * (Ttop[2][0] * V11fcs[0] + Ttop[2][1] * V11fcs[1] + Ttop[2][2] * V11fcs[2]) ] Vt12bcs = [ -1. * (Ttop[0][0] * V12fcs[0] + Ttop[0][1] * V12fcs[1] + Ttop[0][2] * V12fcs[2]), -1. * (Ttop[1][0] * V12fcs[0] + Ttop[1][1] * V12fcs[1] + Ttop[1][2] * V12fcs[2]), -1. * (Ttop[2][0] * V12fcs[0] + Ttop[2][1] * V12fcs[1] + Ttop[2][2] * V12fcs[2]) ] # Bottom: Vb1bcs = [ -1. * (Tbot[0][0] * V1fcs[0] + Tbot[0][1] * V1fcs[1] + Tbot[0][2] * V1fcs[2]), -1. * (Tbot[1][0] * V1fcs[0] + Tbot[1][1] * V1fcs[1] + Tbot[1][2] * V1fcs[2]), -1. * (Tbot[2][0] * V1fcs[0] + Tbot[2][1] * V1fcs[1] + Tbot[2][2] * V1fcs[2]) ] Vb2bcs = [ -1. * (Tbot[0][0] * V2fcs[0] + Tbot[0][1] * V2fcs[1] + Tbot[0][2] * V2fcs[2]), -1. * (Tbot[1][0] * V2fcs[0] + Tbot[1][1] * V2fcs[1] + Tbot[1][2] * V2fcs[2]), -1. * (Tbot[2][0] * V2fcs[0] + Tbot[2][1] * V2fcs[1] + Tbot[2][2] * V2fcs[2]) ] Vb3bcs = [ -1. * (Tbot[0][0] * V3fcs[0] + Tbot[0][1] * V3fcs[1] + Tbot[0][2] * V3fcs[2]), -1. * (Tbot[1][0] * V3fcs[0] + Tbot[1][1] * V3fcs[1] + Tbot[1][2] * V3fcs[2]), -1. * (Tbot[2][0] * V3fcs[0] + Tbot[2][1] * V3fcs[1] + Tbot[2][2] * V3fcs[2]) ] Vb4bcs = [ -1. * (Tbot[0][0] * V4fcs[0] + Tbot[0][1] * V4fcs[1] + Tbot[0][2] * V4fcs[2]), -1. * (Tbot[1][0] * V4fcs[0] + Tbot[1][1] * V4fcs[1] + Tbot[1][2] * V4fcs[2]), -1. * (Tbot[2][0] * V4fcs[0] + Tbot[2][1] * V4fcs[1] + Tbot[2][2] * V4fcs[2]) ] Vb5bcs = [ -1. * (Tbot[0][0] * V5fcs[0] + Tbot[0][1] * V5fcs[1] + Tbot[0][2] * V5fcs[2]), -1. * (Tbot[1][0] * V5fcs[0] + Tbot[1][1] * V5fcs[1] + Tbot[1][2] * V5fcs[2]), -1. * (Tbot[2][0] * V5fcs[0] + Tbot[2][1] * V5fcs[1] + Tbot[2][2] * V5fcs[2]) ] Vb9bcs = [ -1. * (Tbot[0][0] * V9fcs[0] + Tbot[0][1] * V9fcs[1] + Tbot[0][2] * V9fcs[2]), -1. * (Tbot[1][0] * V9fcs[0] + Tbot[1][1] * V9fcs[1] + Tbot[1][2] * V9fcs[2]), -1. * (Tbot[2][0] * V9fcs[0] + Tbot[2][1] * V9fcs[1] + Tbot[2][2] * V9fcs[2]) ] Vb10bcs = [ -1. * (Tbot[0][0] * V10fcs[0] + Tbot[0][1] * V10fcs[1] + Tbot[0][2] * V10fcs[2]), -1. * (Tbot[1][0] * V10fcs[0] + Tbot[1][1] * V10fcs[1] + Tbot[1][2] * V10fcs[2]), -1. * (Tbot[2][0] * V10fcs[0] + Tbot[2][1] * V10fcs[1] + Tbot[2][2] * V10fcs[2]) ] Vb11bcs = [ -1. * (Tbot[0][0] * V11fcs[0] + Tbot[0][1] * V11fcs[1] + Tbot[0][2] * V11fcs[2]), -1. * (Tbot[1][0] * V11fcs[0] + Tbot[1][1] * V11fcs[1] + Tbot[1][2] * V11fcs[2]), -1. * (Tbot[2][0] * V11fcs[0] + Tbot[2][1] * V11fcs[1] + Tbot[2][2] * V11fcs[2]) ] Vb12bcs = [ -1. * (Tbot[0][0] * V12fcs[0] + Tbot[0][1] * V12fcs[1] + Tbot[0][2] * V12fcs[2]), -1. * (Tbot[1][0] * V12fcs[0] + Tbot[1][1] * V12fcs[1] + Tbot[1][2] * V12fcs[2]), -1. * (Tbot[2][0] * V12fcs[0] + Tbot[2][1] * V12fcs[1] + Tbot[2][2] * V12fcs[2]) ] for i in range(0, 18): if i == 0: Vbcs = Vt1bcs if i == 1: Vbcs = Vt2bcs if i == 2: Vbcs = Vt3bcs if i == 3: Vbcs = Vt4bcs if i == 4: Vbcs = Vt5bcs if i == 5: Vbcs = Vt9bcs if i == 6: Vbcs = Vt10bcs if i == 7: Vbcs = Vt11bcs if i == 8: Vbcs = Vt12bcs if i == 9: Vbcs = Vb1bcs if i == 10: Vbcs = Vb2bcs if i == 11: Vbcs = Vb3bcs if i == 12: Vbcs = Vb4bcs if i == 13: Vbcs = Vb5bcs if i == 14: Vbcs = Vb9bcs if i == 15: Vbcs = Vb10bcs if i == 16: Vbcs = Vb11bcs if i == 17: Vbcs = Vb12bcs pas[:, i] = 180. / math.pi * np.arccos( (Vbcs[0] * Bbcs[:, 0] + Vbcs[1] * Bbcs[:, 1] + Vbcs[2] * Bbcs[:, 2]) / (np.sqrt(Vbcs[0]**2 + Vbcs[1]**2 + Vbcs[2]**2) * np.sqrt(Bbcs[:, 0]**2 + Bbcs[:, 1]**2 + Bbcs[:, 2]**2))) if data_rate == 'srvy': # the following 2 hash tables map TOP/BOTTOM telescope #s to index of the PA array created above top_tele_idx_map = {} bot_tele_idx_map = {} top_tele_idx_map[1] = 0 top_tele_idx_map[2] = 1 top_tele_idx_map[3] = 2 top_tele_idx_map[4] = 3 top_tele_idx_map[5] = 4 top_tele_idx_map[9] = 5 top_tele_idx_map[10] = 6 top_tele_idx_map[11] = 7 top_tele_idx_map[12] = 8 bot_tele_idx_map[1] = 9 bot_tele_idx_map[2] = 10 bot_tele_idx_map[3] = 11 bot_tele_idx_map[4] = 12 bot_tele_idx_map[5] = 13 bot_tele_idx_map[9] = 14 bot_tele_idx_map[10] = 15 bot_tele_idx_map[11] = 16 bot_tele_idx_map[12] = 17 top_idxs = [] bot_idxs = [] # PAs for only active eyes new_pas = np.empty([ len(btimes), len(eyes['top']) + len(eyes['bottom']) ]) # pitch angles for each eye at eaceh time for top_idx, top_eye in enumerate(eyes['top']): new_pas[:, top_idx] = pas[:, top_tele_idx_map[top_eye]] top_idxs.append(top_idx) for bot_idx, bot_eye in enumerate(eyes['bottom']): new_pas[:, bot_idx + len(eyes['top'])] = pas[:, bot_tele_idx_map[bot_eye]] bot_idxs.append(bot_idx + len(eyes['top'])) idx_maps = { 'electron-top': top_idxs, 'electron-bottom': bot_idxs } else: new_pas = pas elif datatype == 'ion': pas = np.empty([len(btimes), 6]) # pitch angles for each eye at each time # Telescope vectors in Body Coordinate System: # Factors of -1 account for 180 deg shift between particle velocity and telescope normal direction: # Top: Vt6bcs = [ -1. * (Ttop[0][0] * V6fcs[0] + Ttop[0][1] * V6fcs[1] + Ttop[0][2] * V6fcs[2]), -1. * (Ttop[1][0] * V6fcs[0] + Ttop[1][1] * V6fcs[1] + Ttop[1][2] * V6fcs[2]), -1. * (Ttop[2][0] * V6fcs[0] + Ttop[2][1] * V6fcs[1] + Ttop[2][2] * V6fcs[2]) ] Vt7bcs = [ -1. * (Ttop[0][0] * V7fcs[0] + Ttop[0][1] * V7fcs[1] + Ttop[0][2] * V7fcs[2]), -1. * (Ttop[1][0] * V7fcs[0] + Ttop[1][1] * V7fcs[1] + Ttop[1][2] * V7fcs[2]), -1. * (Ttop[2][0] * V7fcs[0] + Ttop[2][1] * V7fcs[1] + Ttop[2][2] * V7fcs[2]) ] Vt8bcs = [ -1. * (Ttop[0][0] * V8fcs[0] + Ttop[0][1] * V8fcs[1] + Ttop[0][2] * V8fcs[2]), -1. * (Ttop[1][0] * V8fcs[0] + Ttop[1][1] * V8fcs[1] + Ttop[1][2] * V8fcs[2]), -1. * (Ttop[2][0] * V8fcs[0] + Ttop[2][1] * V8fcs[1] + Ttop[2][2] * V8fcs[2]) ] # Bottom: Vb6bcs = [ -1. * (Tbot[0][0] * V6fcs[0] + Tbot[0][1] * V6fcs[1] + Tbot[0][2] * V6fcs[2]), -1. * (Tbot[1][0] * V6fcs[0] + Tbot[1][1] * V6fcs[1] + Tbot[1][2] * V6fcs[2]), -1. * (Tbot[2][0] * V6fcs[0] + Tbot[2][1] * V6fcs[1] + Tbot[2][2] * V6fcs[2]) ] Vb7bcs = [ -1. * (Tbot[0][0] * V7fcs[0] + Tbot[0][1] * V7fcs[1] + Tbot[0][2] * V7fcs[2]), -1. * (Tbot[1][0] * V7fcs[0] + Tbot[1][1] * V7fcs[1] + Tbot[1][2] * V7fcs[2]), -1. * (Tbot[2][0] * V7fcs[0] + Tbot[2][1] * V7fcs[1] + Tbot[2][2] * V7fcs[2]) ] Vb8bcs = [ -1. * (Tbot[0][0] * V8fcs[0] + Tbot[0][1] * V8fcs[1] + Tbot[0][2] * V8fcs[2]), -1. * (Tbot[1][0] * V8fcs[0] + Tbot[1][1] * V8fcs[1] + Tbot[1][2] * V8fcs[2]), -1. * (Tbot[2][0] * V8fcs[0] + Tbot[2][1] * V8fcs[1] + Tbot[2][2] * V8fcs[2]) ] for i in range(0, 6): if i == 0: Vbcs = Vt6bcs if i == 1: Vbcs = Vt7bcs if i == 2: Vbcs = Vt8bcs if i == 3: Vbcs = Vb6bcs if i == 4: Vbcs = Vb7bcs if i == 5: Vbcs = Vb8bcs pas[:, i] = 180. / math.pi * np.arccos( (Vbcs[0] * Bbcs[:, 0] + Vbcs[1] * Bbcs[:, 1] + Vbcs[2] * Bbcs[:, 2]) / (np.sqrt(Vbcs[0]**2 + Vbcs[1]**2 + Vbcs[2]**2) * np.sqrt(Bbcs[:, 0]**2 + Bbcs[:, 1]**2 + Bbcs[:, 2]**2))) # the following 2 hash tables map TOP/BOTTOM telescope #s to index of the PA array created above top_tele_idx_map = {} bot_tele_idx_map = {} top_tele_idx_map[6] = 0 top_tele_idx_map[7] = 1 top_tele_idx_map[8] = 2 bot_tele_idx_map[6] = 3 bot_tele_idx_map[7] = 4 bot_tele_idx_map[8] = 5 top_idxs = [] bot_idxs = [] # PAs for only active eyes new_pas = np.empty([ len(btimes), len(eyes['top']) + len(eyes['bottom']) ]) # pitch angles for each eye at eaceh time for top_idx, top_eye in enumerate(eyes['top']): new_pas[:, top_idx] = pas[:, top_tele_idx_map[top_eye]] top_idxs.append(top_idx) for bot_idx, bot_eye in enumerate(eyes['bottom']): new_pas[:, bot_idx + len(eyes['top'])] = pas[:, bot_tele_idx_map[bot_eye]] bot_idxs.append(bot_idx + len(eyes['top'])) idx_maps = {'ion-top': top_idxs, 'ion-bottom': bot_idxs} outvar = 'mms' + probe + '_epd_feeps_' + data_rate + '_' + level + '_' + datatype + '_pa' + suffix if data_exists( outvar): # kludge for bug when the PAs were previously calculated return (outvar, idx_maps) store_data(outvar, data={'x': btimes, 'y': new_pas}) # interpolate to the PA time stamps outdata = get_data(outvar, xarray=True) outdata_interpolated = outdata.interp({'time': times}) store_data(outvar, data={'x': times, 'y': outdata_interpolated.values}) return (outvar, idx_maps)
def mms_feeps_getgyrophase(trange=['2017-07-11/22:30', '2017-07-11/22:35'], probe='2', data_rate='brst', level='l2', datatype='electron'): """ Calculates FEEPS gyrophase angles for electron burst data Notes: This is intended for use with burst mode data, but the output gyrophase product, mms*_feeps_gyrophase, can be interpolated onto FEEPS burst or survey cadence to produce spectra Based on the IDL code writen by Drew Turner (10 Oct 2017): mms_feeps_getgyrophase.pro """ mec_vars = mms.mec(trange=trange, probe=probe, data_rate=data_rate) if mec_vars is None: print( 'Problem loading MEC data for calculating FEEPS gyrophase angles') qeci2sm = get_data('mms' + probe + '_mec_quat_eci_to_sm') qeci2bcs = get_data('mms' + probe + '_mec_quat_eci_to_bcs') rsun = get_data('mms' + probe + '_mec_r_sun_de421_eci') rsunbcs = np.zeros((len(rsun.times), 3)) rduskbcs = np.zeros((len(rsun.times), 3)) rdusksm = [0, 1, 0] for i in range(len(rsun.times)): q = qeci2bcs.y[i, :] # Quaternion rotation matrix: s = 1 # these quaternions are unit-qs R = np.array([ [ 1 - 2 * s * (q[2]**2 + q[3]**2), 2 * s * (q[1] * q[2] - q[3] * q[0]), 2 * s * (q[1] * q[3] + q[2] * q[0]) ], # ECI to BCS [ 2 * s * (q[1] * q[2] + q[3] * q[0]), 1 - 2 * s * (q[1]**2 + q[3]**2), 2 * s * (q[2] * q[3] - q[1] * q[0]) ], [ 2 * s * (q[1] * q[3] - q[2] * q[0]), 2 * s * (q[2] * q[3] + q[1] * q[0]), 1 - 2 * s * (q[1]**2 + q[2]**2) ] ]) R = R.T rsunbcs[i, :] = np.array([ R[0, 0] * rsun.y[i, 0] + R[1, 0] * rsun.y[i, 1] + R[2, 0] * rsun.y[i, 2], R[0, 1] * rsun.y[i, 0] + R[1, 1] * rsun.y[i, 1] + R[2, 1] * rsun.y[i, 2], R[0, 2] * rsun.y[i, 0] + R[1, 2] * rsun.y[i, 1] + R[2, 2] * rsun.y[i, 2] ]) # now make second vector for gyroplane reference, dusk direction (+Y in SM) q = qeci2sm.y[i, :] # Quaternion rotation matrix: s = 1 # these quaternions are unit-qs R2 = np.array([ [ 1 - 2 * s * (q[2]**2 + q[3]**2), 2 * s * (q[1] * q[2] - q[3] * q[0]), 2 * s * (q[1] * q[3] + q[2] * q[0]) ], # ECI to SM [ 2 * s * (q[1] * q[2] + q[3] * q[0]), 1 - 2 * s * (q[1]**2 + q[3]**2), 2 * s * (q[2] * q[3] - q[1] * q[0]) ], [ 2 * s * (q[1] * q[3] - q[2] * q[0]), 2 * s * (q[2] * q[3] + q[1] * q[0]), 1 - 2 * s * (q[1]**2 + q[2]**2) ] ]) # going from SM to ECI, so invert R: R2 = np.linalg.inv(R2) # SM to ECI R2 = R2.T rduskeci = [ R2[0, 0] * rdusksm[0] + R2[1, 0] * rdusksm[1] + R2[2, 0] * rdusksm[2], R2[0, 1] * rdusksm[0] + R2[1, 1] * rdusksm[1] + R2[2, 1] * rdusksm[2], R2[0, 2] * rdusksm[0] + R2[1, 2] * rdusksm[1] + R2[2, 2] * rdusksm[2] ] # Now convert to BCS: rduskbcs[i, :] = np.array([ R[0, 0] * rduskeci[0] + R[1, 0] * rduskeci[1] + R[2, 0] * rduskeci[2], R[0, 1] * rduskeci[0] + R[1, 1] * rduskeci[1] + R[2, 1] * rduskeci[2], R[0, 2] * rduskeci[0] + R[1, 2] * rduskeci[1] + R[2, 2] * rduskeci[2] ]) saved = store_data('mms' + probe + '_mec_r_sun_bcs', data={ 'x': rsun.times, 'y': rsunbcs }) if not saved: print('Problem saving r_sun_bcs') saved = store_data('mms' + probe + '_mec_r_dusk_bcs', data={ 'x': rsun.times, 'y': rduskbcs }) if not saved: print('Problem saving r_dusk_bcs') # Rotation matrices for FEEPS coord system (FCS) into body coordinate system (BCS): Ttop = np.array([[1. / np.sqrt(2.), -1. / np.sqrt(2.), 0], [1. / np.sqrt(2.), 1. / np.sqrt(2.), 0], [0, 0, 1]]).T Tbot = np.array([[-1. / np.sqrt(2.), -1. / np.sqrt(2.), 0], [-1. / np.sqrt(2.), 1. / np.sqrt(2.), 0], [0, 0, -1]]).T # Telescope vectors in FCS: # Electrons V1fcs = [0.347, -0.837, 0.423] V2fcs = [0.347, -0.837, -0.423] V3fcs = [0.837, -0.347, 0.423] V4fcs = [0.837, -0.347, -0.423] V5fcs = [-0.087, 0.000, 0.996] V9fcs = [0.837, 0.347, 0.423] V10fcs = [0.837, 0.347, -0.423] V11fcs = [0.347, 0.837, 0.423] V12fcs = [0.347, 0.837, -0.423] # Ions V6fcs = [0.104, 0.180, 0.978] V7fcs = [0.654, -0.377, 0.656] V8fcs = [0.654, -0.377, -0.656] # Now telescope vectors in Body Coordinate System: # Factors of -1 account for 180 deg shift between particle velocity and telescope normal direction: # Top: Vt1bcs = [ -1. * (Ttop[0, 0] * V1fcs[0] + Ttop[1, 0] * V1fcs[1] + Ttop[2, 0] * V1fcs[2]), -1. * (Ttop[0, 1] * V1fcs[0] + Ttop[1, 1] * V1fcs[1] + Ttop[2, 1] * V1fcs[2]), -1. * (Ttop[0, 2] * V1fcs[0] + Ttop[1, 2] * V1fcs[1] + Ttop[2, 2] * V1fcs[2]) ] Vt2bcs = [ -1. * (Ttop[0, 0] * V2fcs[0] + Ttop[1, 0] * V2fcs[1] + Ttop[2, 0] * V2fcs[2]), -1. * (Ttop[0, 1] * V2fcs[0] + Ttop[1, 1] * V2fcs[1] + Ttop[2, 1] * V2fcs[2]), -1. * (Ttop[0, 2] * V2fcs[0] + Ttop[1, 2] * V2fcs[1] + Ttop[2, 2] * V2fcs[2]) ] Vt3bcs = [ -1. * (Ttop[0, 0] * V3fcs[0] + Ttop[1, 0] * V3fcs[1] + Ttop[2, 0] * V3fcs[2]), -1. * (Ttop[0, 1] * V3fcs[0] + Ttop[1, 1] * V3fcs[1] + Ttop[2, 1] * V3fcs[2]), -1. * (Ttop[0, 2] * V3fcs[0] + Ttop[1, 2] * V3fcs[1] + Ttop[2, 2] * V3fcs[2]) ] Vt4bcs = [ -1. * (Ttop[0, 0] * V4fcs[0] + Ttop[1, 0] * V4fcs[1] + Ttop[2, 0] * V4fcs[2]), -1. * (Ttop[0, 1] * V4fcs[0] + Ttop[1, 1] * V4fcs[1] + Ttop[2, 1] * V4fcs[2]), -1. * (Ttop[0, 2] * V4fcs[0] + Ttop[1, 2] * V4fcs[1] + Ttop[2, 2] * V4fcs[2]) ] Vt5bcs = [ -1. * (Ttop[0, 0] * V5fcs[0] + Ttop[1, 0] * V5fcs[1] + Ttop[2, 0] * V5fcs[2]), -1. * (Ttop[0, 1] * V5fcs[0] + Ttop[1, 1] * V5fcs[1] + Ttop[2, 1] * V5fcs[2]), -1. * (Ttop[0, 2] * V5fcs[0] + Ttop[1, 2] * V5fcs[1] + Ttop[2, 2] * V5fcs[2]) ] Vt6bcs = [ -1. * (Ttop[0, 0] * V6fcs[0] + Ttop[1, 0] * V6fcs[1] + Ttop[2, 0] * V6fcs[2]), -1. * (Ttop[0, 1] * V6fcs[0] + Ttop[1, 1] * V6fcs[1] + Ttop[2, 1] * V6fcs[2]), -1. * (Ttop[0, 2] * V6fcs[0] + Ttop[1, 2] * V6fcs[1] + Ttop[2, 2] * V6fcs[2]) ] Vt7bcs = [ -1. * (Ttop[0, 0] * V7fcs[0] + Ttop[1, 0] * V7fcs[1] + Ttop[2, 0] * V7fcs[2]), -1. * (Ttop[0, 1] * V7fcs[0] + Ttop[1, 1] * V7fcs[1] + Ttop[2, 1] * V7fcs[2]), -1. * (Ttop[0, 2] * V7fcs[0] + Ttop[1, 2] * V7fcs[1] + Ttop[2, 2] * V7fcs[2]) ] Vt8bcs = [ -1. * (Ttop[0, 0] * V8fcs[0] + Ttop[1, 0] * V8fcs[1] + Ttop[2, 0] * V8fcs[2]), -1. * (Ttop[0, 1] * V8fcs[0] + Ttop[1, 1] * V8fcs[1] + Ttop[2, 1] * V8fcs[2]), -1. * (Ttop[0, 2] * V8fcs[0] + Ttop[1, 2] * V8fcs[1] + Ttop[2, 2] * V8fcs[2]) ] Vt9bcs = [ -1. * (Ttop[0, 0] * V9fcs[0] + Ttop[1, 0] * V9fcs[1] + Ttop[2, 0] * V9fcs[2]), -1. * (Ttop[0, 1] * V9fcs[0] + Ttop[1, 1] * V9fcs[1] + Ttop[2, 1] * V9fcs[2]), -1. * (Ttop[0, 2] * V9fcs[0] + Ttop[1, 2] * V9fcs[1] + Ttop[2, 2] * V9fcs[2]) ] Vt10bcs = [ -1. * (Ttop[0, 0] * V10fcs[0] + Ttop[1, 0] * V10fcs[1] + Ttop[2, 0] * V10fcs[2]), -1. * (Ttop[0, 1] * V10fcs[0] + Ttop[1, 1] * V10fcs[1] + Ttop[2, 1] * V10fcs[2]), -1. * (Ttop[0, 2] * V10fcs[0] + Ttop[1, 2] * V10fcs[1] + Ttop[2, 2] * V10fcs[2]) ] Vt11bcs = [ -1. * (Ttop[0, 0] * V11fcs[0] + Ttop[1, 0] * V11fcs[1] + Ttop[2, 0] * V11fcs[2]), -1. * (Ttop[0, 1] * V11fcs[0] + Ttop[1, 1] * V11fcs[1] + Ttop[2, 1] * V11fcs[2]), -1. * (Ttop[0, 2] * V11fcs[0] + Ttop[1, 2] * V11fcs[1] + Ttop[2, 2] * V11fcs[2]) ] Vt12bcs = [ -1. * (Ttop[0, 0] * V12fcs[0] + Ttop[1, 0] * V12fcs[1] + Ttop[2, 0] * V12fcs[2]), -1. * (Ttop[0, 1] * V12fcs[0] + Ttop[1, 1] * V12fcs[1] + Ttop[2, 1] * V12fcs[2]), -1. * (Ttop[0, 2] * V12fcs[0] + Ttop[1, 2] * V12fcs[1] + Ttop[2, 2] * V12fcs[2]) ] # Bottom: Vb1bcs = [ -1. * (Tbot[0, 0] * V1fcs[0] + Tbot[1, 0] * V1fcs[1] + Tbot[2, 0] * V1fcs[2]), -1. * (Tbot[0, 1] * V1fcs[0] + Tbot[1, 1] * V1fcs[1] + Tbot[2, 1] * V1fcs[2]), -1. * (Tbot[0, 2] * V1fcs[0] + Tbot[1, 2] * V1fcs[1] + Tbot[2, 2] * V1fcs[2]) ] Vb2bcs = [ -1. * (Tbot[0, 0] * V2fcs[0] + Tbot[1, 0] * V2fcs[1] + Tbot[2, 0] * V2fcs[2]), -1. * (Tbot[0, 1] * V2fcs[0] + Tbot[1, 1] * V2fcs[1] + Tbot[2, 1] * V2fcs[2]), -1. * (Tbot[0, 2] * V2fcs[0] + Tbot[1, 2] * V2fcs[1] + Tbot[2, 2] * V2fcs[2]) ] Vb3bcs = [ -1. * (Tbot[0, 0] * V3fcs[0] + Tbot[1, 0] * V3fcs[1] + Tbot[2, 0] * V3fcs[2]), -1. * (Tbot[0, 1] * V3fcs[0] + Tbot[1, 1] * V3fcs[1] + Tbot[2, 1] * V3fcs[2]), -1. * (Tbot[0, 2] * V3fcs[0] + Tbot[1, 2] * V3fcs[1] + Tbot[2, 2] * V3fcs[2]) ] Vb4bcs = [ -1. * (Tbot[0, 0] * V4fcs[0] + Tbot[1, 0] * V4fcs[1] + Tbot[2, 0] * V4fcs[2]), -1. * (Tbot[0, 1] * V4fcs[0] + Tbot[1, 1] * V4fcs[1] + Tbot[2, 1] * V4fcs[2]), -1. * (Tbot[0, 2] * V4fcs[0] + Tbot[1, 2] * V4fcs[1] + Tbot[2, 2] * V4fcs[2]) ] Vb5bcs = [ -1. * (Tbot[0, 0] * V5fcs[0] + Tbot[1, 0] * V5fcs[1] + Tbot[2, 0] * V5fcs[2]), -1. * (Tbot[0, 1] * V5fcs[0] + Tbot[1, 1] * V5fcs[1] + Tbot[2, 1] * V5fcs[2]), -1. * (Tbot[0, 2] * V5fcs[0] + Tbot[1, 2] * V5fcs[1] + Tbot[2, 2] * V5fcs[2]) ] Vb6bcs = [ -1. * (Tbot[0, 0] * V6fcs[0] + Tbot[1, 0] * V6fcs[1] + Tbot[2, 0] * V6fcs[2]), -1. * (Tbot[0, 1] * V6fcs[0] + Tbot[1, 1] * V6fcs[1] + Tbot[2, 1] * V6fcs[2]), -1. * (Tbot[0, 2] * V6fcs[0] + Tbot[1, 2] * V6fcs[1] + Tbot[2, 2] * V6fcs[2]) ] Vb7bcs = [ -1. * (Tbot[0, 0] * V7fcs[0] + Tbot[1, 0] * V7fcs[1] + Tbot[2, 0] * V7fcs[2]), -1. * (Tbot[0, 1] * V7fcs[0] + Tbot[1, 1] * V7fcs[1] + Tbot[2, 1] * V7fcs[2]), -1. * (Tbot[0, 2] * V7fcs[0] + Tbot[1, 2] * V7fcs[1] + Tbot[2, 2] * V7fcs[2]) ] Vb8bcs = [ -1. * (Tbot[0, 0] * V8fcs[0] + Tbot[1, 0] * V8fcs[1] + Tbot[2, 0] * V8fcs[2]), -1. * (Tbot[0, 1] * V8fcs[0] + Tbot[1, 1] * V8fcs[1] + Tbot[2, 1] * V8fcs[2]), -1. * (Tbot[0, 2] * V8fcs[0] + Tbot[1, 2] * V8fcs[1] + Tbot[2, 2] * V8fcs[2]) ] Vb9bcs = [ -1. * (Tbot[0, 0] * V9fcs[0] + Tbot[1, 0] * V9fcs[1] + Tbot[2, 0] * V9fcs[2]), -1. * (Tbot[0, 1] * V9fcs[0] + Tbot[1, 1] * V9fcs[1] + Tbot[2, 1] * V9fcs[2]), -1. * (Tbot[0, 2] * V9fcs[0] + Tbot[1, 2] * V9fcs[1] + Tbot[2, 2] * V9fcs[2]) ] Vb10bcs = [ -1. * (Tbot[0, 0] * V10fcs[0] + Tbot[1, 0] * V10fcs[1] + Tbot[2, 0] * V10fcs[2]), -1. * (Tbot[0, 1] * V10fcs[0] + Tbot[1, 1] * V10fcs[1] + Tbot[2, 1] * V10fcs[2]), -1. * (Tbot[0, 2] * V10fcs[0] + Tbot[1, 2] * V10fcs[1] + Tbot[2, 2] * V10fcs[2]) ] Vb11bcs = [ -1. * (Tbot[0, 0] * V11fcs[0] + Tbot[1, 0] * V11fcs[1] + Tbot[2, 0] * V11fcs[2]), -1. * (Tbot[0, 1] * V11fcs[0] + Tbot[1, 1] * V11fcs[1] + Tbot[2, 1] * V11fcs[2]), -1. * (Tbot[0, 2] * V11fcs[0] + Tbot[1, 2] * V11fcs[1] + Tbot[2, 2] * V11fcs[2]) ] Vb12bcs = [ -1. * (Tbot[0, 0] * V12fcs[0] + Tbot[1, 0] * V12fcs[1] + Tbot[2, 0] * V12fcs[2]), -1. * (Tbot[0, 1] * V12fcs[0] + Tbot[1, 1] * V12fcs[1] + Tbot[2, 1] * V12fcs[2]), -1. * (Tbot[0, 2] * V12fcs[0] + Tbot[1, 2] * V12fcs[1] + Tbot[2, 2] * V12fcs[2]) ] fgm_vars = mms.fgm( trange=[time_double(trange[0]) - 600, time_double(trange[1]) + 600], probe=probe, data_rate='srvy') if fgm_vars is None: print( 'Problem loading FGM vars for calculating FEEPS gyrophase angles') # interpolate the FGM var to the MEC var timestamps tinterpol('mms' + probe + '_fgm_b_bcs_srvy_l2_bvec', 'mms' + probe + '_mec_r_sun_bcs', newname='mms' + probe + '_fgm_b_bcs_srvy_l2_bvec_int') B = get_data('mms' + probe + '_fgm_b_bcs_srvy_l2_bvec_int') # Now calculate gyrophase # Telescope vectors perp to B: Tperp = np.zeros((len(rsunbcs[:, 0]), 3, 24)) # Gyrophase: phi = np.zeros((len(rsunbcs[:, 0]), 24)) for i in range(len(rsunbcs[:, 0])): uB = B.y[i, :] / np.sqrt(B.y[i, 0]**2 + B.y[i, 1]**2 + B.y[i, 2]**2) # Sun vector perp to B: Sperp = np.cross( np.cross(uB, rsunbcs[i, :] / np.sqrt(np.nansum(rsunbcs[i, :]**2))), uB) # Dusk vector perp to B: Dperp = np.cross( np.cross(uB, rduskbcs[i, :] / np.sqrt(np.nansum(rduskbcs[i, :]**2))), uB) Tperp[i, :, 0] = np.cross(np.cross(uB, Vt1bcs), uB) Tperp[i, :, 1] = np.cross(np.cross(uB, Vt2bcs), uB) Tperp[i, :, 2] = np.cross(np.cross(uB, Vt3bcs), uB) Tperp[i, :, 3] = np.cross(np.cross(uB, Vt4bcs), uB) Tperp[i, :, 4] = np.cross(np.cross(uB, Vt5bcs), uB) Tperp[i, :, 5] = np.cross(np.cross(uB, Vt6bcs), uB) Tperp[i, :, 6] = np.cross(np.cross(uB, Vt7bcs), uB) Tperp[i, :, 7] = np.cross(np.cross(uB, Vt8bcs), uB) Tperp[i, :, 8] = np.cross(np.cross(uB, Vt9bcs), uB) Tperp[i, :, 9] = np.cross(np.cross(uB, Vt10bcs), uB) Tperp[i, :, 10] = np.cross(np.cross(uB, Vt11bcs), uB) Tperp[i, :, 11] = np.cross(np.cross(uB, Vt12bcs), uB) Tperp[i, :, 12] = np.cross(np.cross(uB, Vb1bcs), uB) Tperp[i, :, 13] = np.cross(np.cross(uB, Vb2bcs), uB) Tperp[i, :, 14] = np.cross(np.cross(uB, Vb3bcs), uB) Tperp[i, :, 15] = np.cross(np.cross(uB, Vb4bcs), uB) Tperp[i, :, 16] = np.cross(np.cross(uB, Vb5bcs), uB) Tperp[i, :, 17] = np.cross(np.cross(uB, Vb6bcs), uB) Tperp[i, :, 18] = np.cross(np.cross(uB, Vb7bcs), uB) Tperp[i, :, 19] = np.cross(np.cross(uB, Vb8bcs), uB) Tperp[i, :, 20] = np.cross(np.cross(uB, Vb9bcs), uB) Tperp[i, :, 21] = np.cross(np.cross(uB, Vb10bcs), uB) Tperp[i, :, 22] = np.cross(np.cross(uB, Vb11bcs), uB) Tperp[i, :, 23] = np.cross(np.cross(uB, Vb12bcs), uB) for j in range(24): th1 = np.arccos( np.nansum(Tperp[i, :, j] * Sperp) / (np.sqrt(np.nansum(Tperp[i, :, j]**2)) * np.sqrt(np.nansum(Sperp**2)))) th2 = np.arccos( np.nansum(Tperp[i, :, j] * Dperp) / (np.sqrt(np.nansum(Tperp[i, :, j]**2)) * np.sqrt(np.nansum(Dperp**2)))) if th1 <= np.pi / 2.0 and th2 < np.pi / 2: phi[i, j] = 2 * np.pi - th1 if th1 < np.pi / 2.0 and th2 >= np.pi / 2.0: phi[i, j] = th1 if th1 > np.pi / 2.0 and th2 <= np.pi / 2.0: phi[i, j] = 270.0 * np.pi / 180.0 - th2 if th1 >= np.pi / 2.0 and th2 > np.pi / 2.0: phi[i, j] = th1 saved = store_data('mms' + probe + '_epd_feeps_' + data_rate + '_gyrophase', data={ 'x': rsun.times, 'y': phi * 180. / np.pi }) if not saved: print('Problem saving gyrophase angles') return options('mms' + probe + '_epd_feeps_' + data_rate + '_gyrophase', 'yrange', [0, 360.0]) # Gyrophase always returns on time stamps from MEC data, get those closest to FEEPS time stamps: eyes = mms_feeps_active_eyes(trange, probe, data_rate, datatype, level) sensor_types = ['top', 'bottom'] feepst = get_data('mms' + probe + '_epd_feeps_' + data_rate + '_' + level + '_' + datatype + '_spinsectnum') indt = np.zeros(len(feepst.times), dtype='int32') gpd = get_data('mms' + probe + '_epd_feeps_' + data_rate + '_gyrophase') for i in range(len(feepst.times)): indt[i] = np.argwhere( np.abs(gpd.times - feepst.times[i]) == np.min( np.abs(gpd.times - feepst.times[i]))).flatten()[0] # Gyrophase always returns all 24 FEEPS telescopes, downselect based on species: iT = np.array([ np.array(eyes[sensor_types[0]]) - 1, np.array(eyes[sensor_types[0]]) + 11 ]).flatten().tolist() gp_data = np.zeros((len(gpd.times[indt]), len(iT))) #return (iT, gp_data, gpd) for i in range(len(iT)): gp_data[:, i] = gpd.y[indt, iT[i]] saved = store_data('mms' + probe + '_epd_feeps_' + data_rate + '_' + level + '_' + datatype + '_gyrophase', data={ 'x': gpd.times[indt], 'y': gp_data }) if saved: options( 'mms' + probe + '_epd_feeps_' + data_rate + '_' + level + '_' + datatype + '_gyrophase', 'yrange', [0.0, 360.0]) return 'mms' + probe + '_epd_feeps_' + data_rate + '_' + level + '_' + datatype + '_gyrophase'
def mms_feeps_gpd( trange=['2017-07-11/22:30', '2017-07-11/22:35'], probe='2', data_rate='brst', level='l2', datatype='electron', data_units='intensity', bin_size=15, # deg energy=[50, 500]): """ Calculate gyrophase distributions using data from the MMS Fly's Eye Energetic Particle Sensor (FEEPS) Parameters ---------- probe: str probe #, e.g., '4' for MMS4 data_units: str 'intensity' datatype: str 'electron' or 'ion' data_rate: str instrument data rate, e.g., 'srvy' or 'brst' level: str data level suffix: str suffix of the loaded data energy: list of float energy range to include in the calculation bin_size: float size of the pitch angle bins Returns -------- Tplot variable containing the gyrophase distribution Notes ------ Based on IDL code by Drew Turner (10 Oct 2017): mms_feeps_gpd.pro """ if isinstance(probe, int): probe = str(probe) feeps_data = pyspedas.mms.feeps(trange=trange, data_rate=data_rate, probe=probe, level=level) if len(feeps_data) == 0: print('Problem loading FEEPS data for this time range.') return # Account for angular response (finite field of view) of instruments # elec can use +/-21.4 deg on each pitch angle as average response angle; ions can start with +/-10 deg, but both need to be further refined if datatype == 'electron': dAngResp = 21.4 # [deg] if datatype == 'ion': dAngResp = 10.0 # [deg] bin_size = float(bin_size) n_bins = 360.0 / bin_size gyro_bins = 360. * np.arange(n_bins + 1) / n_bins gyro_centers = 360. * np.arange(n_bins) / n_bins + (gyro_bins[1] - gyro_bins[0]) / 2. # get the gyrophase angles # calculate the gyro phase angles from the magnetic field data gyro_vars = mms_feeps_getgyrophase(trange=trange, probe=probe, data_rate=data_rate, level=level, datatype=datatype) gyro_data = get_data('mms' + str(probe) + '_epd_feeps_' + data_rate + '_' + level + '_' + datatype + '_gyrophase') if gyro_data is None or gyro_vars is None: print('Problem calculating gyrophase angles.') return eyes = mms_feeps_active_eyes(trange, probe, data_rate, datatype, level) data_map = {} if data_rate == 'srvy': # From Allison Jaynes @ LASP: The 6,7,8 sensors (out of 12) are ions, # so in the pitch angle array, the 5,6,7 columns (counting from zero) will be the ion pitch angles. # for electrons: if datatype == 'electron': data_map['top-electron'] = eyes['top'] - 1 data_map['bottom-electron'] = eyes['bottom'] - 1 elif datatype == 'ion': data_map['top-ion'] = eyes['top'] - 1 data_map['bottom-ion'] = eyes['bottom'] - 1 elif data_rate == 'brst': # note: the following are indices of the top/bottom sensors in pa_data # they should be consistent with pa_dlimits.labels data_map['top-electron'] = [0, 1, 2, 3, 4, 5, 6, 7, 8] data_map['bottom-electron'] = [9, 10, 11, 12, 13, 14, 15, 16, 17] # and ions: data_map['top-ion'] = [0, 1, 2] data_map['bottom-ion'] = [3, 4, 5] sensor_types = ['top', 'bottom'] # First, initialize arrays for flux (dflux) and pitch angles (dpa) compiled from all sensors: if datatype == 'electron': dflux = np.zeros( (len(gyro_data.times), len(data_map['top-electron']) + len(data_map['bottom-electron']))) elif datatype == 'ion': dflux = np.zeros( (len(gyro_data.times), len(data_map['top-ion']) + len(data_map['bottom-ion']))) dpa = np.zeros(dflux.shape) for sensor_type in sensor_types: pa_map = data_map[sensor_type + '-' + datatype] particle_idxs = np.array(eyes[sensor_type]) - 1 for isen in range(len(particle_idxs)): # loop through sensors # get the data var_name = 'mms' + str( probe ) + '_epd_feeps_' + data_rate + '_' + level + '_' + datatype + '_' + sensor_type + '_' + data_units + '_sensorid_' + str( particle_idxs[isen] + 1) + '_clean_sun_removed' data = get_data(var_name) if data is None: print('Data not found: ' + var_name) continue data.y[data.y == 0.0] = np.nan # remove any 0s before averaging # Energy indices to use: indx = np.argwhere((data.v <= energy[1]) & (data.v >= energy[0])) if len(indx) == 0: print( 'Energy range selected is not covered by the detector for FEEPS ' + datatype + ' data') continue dflux[:, pa_map[isen]] = np.nanmean(data.y[:, indx], axis=1).flatten() dpa[:, pa_map[isen]] = gyro_data.y[:, pa_map[isen]].flatten() # we need to replace the 0.0s left in after populating dpa with NaNs; these # 0.0s are left in there because these points aren't covered by sensors loaded # for this datatype/data_rate dpa[dpa == 0.0] = np.nan gyro_flux = np.zeros((len(gyro_data.times), int(n_bins))) delta_gyro = (gyro_bins[1] - gyro_bins[0]) / 2.0 # Now loop through PA bins and time, find the telescopes where there is data in those bins and average it up! for it in range(len(dpa[:, 0])): for ipa in range(int(n_bins)): ind = np.argwhere( (dpa[it, :] + dAngResp >= gyro_centers[ipa] - delta_gyro) & (dpa[it, :] - dAngResp < gyro_centers[ipa] + delta_gyro)) if len(ind) > 0: gyro_flux[it, ipa] = np.nanmean(dflux[it, ind], axis=0).flatten() # fill any missed bins with NAN gyro_flux[gyro_flux == 0.0] = np.nan en_range_string = str(int(energy[0])) + '-' + str(int(energy[1])) + 'keV' new_name = 'mms' + str( probe ) + '_epd_feeps_' + data_rate + '_' + level + '_' + datatype + '_' + data_units + '_' + en_range_string + '_gpd' saved = store_data(new_name, data={ 'x': gyro_data.times, 'y': gyro_flux, 'v': gyro_centers }) if saved: options(new_name, 'spec', True) options(new_name, 'zlog', False) return new_name