def vspace_entropy(sc, mode, start_date, end_date): # Read the data b = fgm.load_data(sc, mode, start_date, end_date) dis_moms = fpi.load_moms(sc, mode, optdesc='dis-moms', start_date=start_date, end_date=end_date) des_moms = fpi.load_moms(sc, mode, optdesc='des-moms', start_date=start_date, end_date=end_date) dis_dist = fpi.load_dist(sc, mode, optdesc='dis-dist', start_date=start_date, end_date=end_date) des_dist = fpi.load_dist(sc, mode, optdesc='des-dist', start_date=start_date, end_date=end_date) # Precondition the distributions dis_kwargs = fpi.precond_params(sc, mode, 'l2', 'dis-dist', start_date, end_date, time=dis_dist['time']) des_kwargs = fpi.precond_params(sc, mode, 'l2', 'des-dist', start_date, end_date, time=des_dist['time']) f_dis = fpi.precondition(dis_dist['dist'], **dis_kwargs) f_des = fpi.precondition(des_dist['dist'], **des_kwargs) # Calculate moments # - Use calculated moments for the Maxwellian distribution Ni = fpi.density(f_dis) Vi = fpi.velocity(f_dis, N=Ni) Ti = fpi.temperature(f_dis, N=Ni, V=Vi) Pi = fpi.pressure(f_dis, N=Ni, T=Ti) ti = ((Ti[:,0,0] + Ti[:,1,1] + Ti[:,2,2]) / 3.0).drop(['t_index_dim1', 't_index_dim2']) pi = ((Pi[:,0,0] + Pi[:,1,1] + Pi[:,2,2]) / 3.0).drop(['t_index_dim1', 't_index_dim2']) Ne = fpi.density(f_des) Ve = fpi.velocity(f_des, N=Ne) Te = fpi.temperature(f_des, N=Ne, V=Ve) Pe = fpi.pressure(f_des, N=Ne, T=Te) te = ((Te[:,0,0] + Te[:,1,1] + Te[:,2,2]) / 3.0).drop(['t_index_dim1', 't_index_dim2']) pe = ((Pe[:,0,0] + Pe[:,1,1] + Pe[:,2,2]) / 3.0).drop(['t_index_dim1', 't_index_dim2']) # Equivalent (preconditioned) Maxwellian distributions # fi_max = fpi.maxwellian_distribution(f_dis, N=Ni, bulkv=Vi, T=ti) # fe_max = fpi.maxwellian_distribution(f_des, N=Ne, bulkv=Ve, T=te) fi_max = fpi.maxwellian_distribution(f_dis, N=Ni, bulkv=Vi, T=ti) fe_max = fpi.maxwellian_distribution(f_des, N=Ne, bulkv=Ve, T=te) # Analytically derived Maxwellian entropy density # si_max = fpi.maxwellian_entropy(Ni, pi) # se_max = fpi.maxwellian_entropy(Ne, pe) si_max = fpi.entropy(fi_max) se_max = fpi.entropy(fe_max) # Velocity space entropy density siv_dist = fpi.vspace_entropy(f_dis) sev_dist = fpi.vspace_entropy(f_des) # The Maxwellian is already preconditioned # - There are three options for calculating the v-space entropy of # the Maxwellian distribution: using # 1) FPI integrated moments, # 2) Custom moments of the measured distribution # 3) Custom moments of the equivalent Maxwellian distribution # Because the Maxwellian is built with discrete v-space bins, its # density, velocity, and temperature do not match that of the # measured distribution on which it is based. If NiM is used, the # M-bar term will be negative, which is unphysical, so here we use # the density of the measured distribution and the entropy of the # equivalent Maxwellian. siv_max = fpi.vspace_entropy(fi_max, N=Ni, s=si_max) sev_max = fpi.vspace_entropy(fe_max, N=Ne, s=se_max) # M-bar miv_bar = (siv_max - siv_dist) / siv_max mev_bar = (sev_max - sev_dist) / sev_max # Anisotropy Ai = dis_moms['temppara'] / dis_moms['tempperp'] - 1 Ae = des_moms['temppara'] / des_moms['tempperp'] - 1 nrows = 8 ncols = 1 figsize = (5.5, 7.0) fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=figsize, squeeze=False) # B ax = axes[0,0] ax = util.plot([b['B_GSE'][:,3], b['B_GSE'][:,0], b['B_GSE'][:,1], b['B_GSE'][:,2]], ax=ax, labels=['|B|', 'Bx', 'By', 'Bz'], xaxis='off', ylabel='B\n(nT)' ) # N ax = axes[1,0] ax = util.plot([dis_moms['density'], des_moms['density']], ax=ax, labels=['Ni', 'Ne'], xaxis='off', ylabel='N\n($cm^{-3}$)' ) # Velocity space ion entropy density ax = axes[2,0] ax = util.plot([siv_max, siv_dist], ax=ax, labels=['$s_{i,V,max}$', '$s_{i,V}$'], xaxis='off', ylabel='$s_{V}$\n$J/K/m^{3}$' ) # Velocity space electron entropy density ax = axes[3,0] ax = util.plot([sev_max, sev_dist], ax=ax, labels=['$s_{e,V,max}$', '$s_{e,V}$'], xaxis='off', ylabel='$s_{V}$' ) # Velocity space M-bar ax = axes[4,0] ax = util.plot([miv_bar, mev_bar], ax=ax, labels=['$\overline{M}_{i,V}$', '$\overline{M}_{e,V}$'], xaxis='off', ylabel='$\overline{M}_{V}$' ) # Ion temperature ax = axes[5,0] ax = util.plot([dis_moms['temppara'], dis_moms['tempperp'], dis_moms['t']], ax=ax, labels=['$T_{\parallel}$', '$T_{\perp}$', 'T'], xaxis='off', ylabel='$T_{i}$\n(eV)' ) # Electron temperature ax = axes[6,0] ax = util.plot([des_moms['temppara'], des_moms['tempperp'], des_moms['t']], ax=ax, labels=['$T_{\parallel}$', '$T_{\perp}$', 'T'], xaxis='off', ylabel='$T_{e}$\n(eV)' ) # Anisotropy ax = axes[7,0] ax = util.plot([Ai, Ae], ax=ax, labels=['$A_{i}$', '$A_{e}$'], ylabel='A' ) fig.suptitle('Velocity Space Entropy') plt.subplots_adjust(left=0.2, right=0.85, top=0.95, hspace=0.4) return fig, axes
def moments_comparison(sc, mode, species, start_date, end_date, scpot_correction=False, ephoto_correction=False, elimits=False): ''' Compare moments derived from the 3D velocity distribution functions from three sources: official FPI moments, derived herein, and those of an equivalent Maxwellian distribution derived herein. Parameters ---------- sc : str Spacecraft identifier. Choices are ('mms1', 'mms2', 'mms3', 'mms4') mode : str Data rate mode. Choices are ('fast', 'brst') species : str Particle species. Choices are ('i', 'e') for ions and electrons, respectively start_date, end_date : `datetime.datetime` Start and end dates and times of the time interval scpot_correction : bool Apply spacecraft potential correction to the distribution functions. ephoto : bool Subtract photo-electrons. Applicable to DES data only. elimits : bool Set upper and lower energy limits of integration Returns ------- fig : `matplotlib.figure` Figure in which graphics are displayed ax : list List of `matplotlib.pyplot.axes` objects ''' # Read the data moms_xr = fpi.load_moms(sc=sc, mode=mode, optdesc='d' + species + 's-moms', start_date=start_date, end_date=end_date) dist_xr = fpi.load_dist(sc=sc, mode=mode, optdesc='d' + species + 's-dist', start_date=start_date, end_date=end_date, ephoto=ephoto_correction) # Precondition the distributions kwargs = fpi.precond_params(sc, mode, 'l2', 'dis-dist', start_date, end_date, time=dist_xr['time']) if scpot_correction is False: kwargs['scpot'] == None if elimits is False: kwargs['E_low'] = None kwargs['E_high'] = None f = fpi.precondition(dist_xr['dist'], **kwargs) # Moments distribution n_xr = fpi.density(f) s_xr = fpi.entropy(f) v_xr = fpi.velocity(f, N=n_xr) T_xr = fpi.temperature(f, N=n_xr, V=v_xr) P_xr = fpi.pressure(f, N=n_xr, T=T_xr) t_scalar_xr = (T_xr[:, 0, 0] + T_xr[:, 1, 1] + T_xr[:, 2, 2]) / 3.0 p_scalar_xr = (P_xr[:, 0, 0] + P_xr[:, 1, 1] + P_xr[:, 2, 2]) / 3.0 t_scalar_xr = t_scalar_xr.drop(['t_index_dim1', 't_index_dim2']) p_scalar_xr = p_scalar_xr.drop(['t_index_dim1', 't_index_dim2']) # Create an equivalent Maxwellian distribution f_max_xr = fpi.maxwellian_distribution(f, n_xr, v_xr, t_scalar_xr) n_max_dist = fpi.density(f_max_xr) s_max_dist = fpi.entropy(f_max_xr) s_max = fpi.maxwellian_entropy(n_xr, p_scalar_xr) v_max_dist = fpi.velocity(f_max_xr, N=n_max_dist) T_max_dist = fpi.temperature(f_max_xr, N=n_max_dist, V=v_max_dist) P_max_dist = fpi.pressure(f_max_xr, N=n_max_dist, T=T_max_dist) p_scalar_max_dist = (P_max_dist[:, 0, 0] + P_max_dist[:, 1, 1] + P_max_dist[:, 2, 2]) / 3.0 p_scalar_max_dist = p_scalar_max_dist.drop( ['t_index_dim1', 't_index_dim2']) # Epsilon e_xr = fpi.epsilon(f, dist_max=f_max_xr, N=n_xr) nrows = 6 ncols = 3 figsize = (10.0, 5.5) fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=figsize, squeeze=False) # Density ax = axes[0, 0] moms_xr['density'].attrs['label'] = 'moms' n_xr.attrs['label'] = 'dist' n_max_dist.attrs['label'] = 'max' ax = util.plot([moms_xr['density'], n_xr, n_max_dist], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='N\n($cm^{-3}$)') # Entropy ax = axes[1, 0] ax = util.plot([s_max, s_xr, s_max_dist], ax=ax, labels=['moms', 'dist', 'max dist'], xaxis='off', ylabel='S\n[J/K/$m^{3}$ ln($s^{3}/m^{6}$)]') # Epsilon ax = axes[1, 0].twinx() e_xr.plot(ax=ax, color='r') ax.spines['right'].set_color('red') ax.yaxis.label.set_color('red') ax.tick_params(axis='y', colors='red') ax.set_title('') ax.set_xticks([]) ax.set_xlabel('') ax.set_ylabel('$\epsilon$\n$(s/m)^{3/2}$') # Vx ax = axes[2, 0] ax = util.plot([moms_xr['velocity'][:, 0], v_xr[:, 0], v_max_dist[:, 0]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Vx\n(km/s)') # Vy ax = axes[3, 0] ax = util.plot([moms_xr['velocity'][:, 1], v_xr[:, 1], v_max_dist[:, 1]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Vy\n(km/s)') # Vz ax = axes[4, 0] ax = util.plot([moms_xr['velocity'][:, 2], v_xr[:, 2], v_max_dist[:, 2]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Vz\n(km/s)') # Scalar Pressure ax = axes[5, 0] ax = util.plot([moms_xr['p'], p_scalar_xr, p_scalar_max_dist], ax=ax, labels=['moms', 'dist', 'max'], xlabel='', ylabel='p\n(nPa)') # T_xx ax = axes[0, 1] ax = util.plot( [moms_xr['temptensor'][:, 0, 0], T_xr[:, 0, 0], T_max_dist[:, 0, 0]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Txx\n(eV)') # T_yy ax = axes[1, 1] ax = util.plot( [moms_xr['temptensor'][:, 1, 1], T_xr[:, 1, 1], T_max_dist[:, 1, 1]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Tyy\n(eV)') # T_zz ax = axes[2, 1] ax = util.plot( [moms_xr['temptensor'][:, 2, 2], T_xr[:, 2, 2], T_max_dist[:, 2, 2]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Tzz\n(eV)') # T_xy ax = axes[3, 1] ax = util.plot( [moms_xr['temptensor'][:, 0, 1], T_xr[:, 0, 1], T_max_dist[:, 0, 1]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Txy\n(eV)') # T_xz ax = axes[4, 1] ax = util.plot( [moms_xr['temptensor'][:, 0, 2], T_xr[:, 0, 2], T_max_dist[:, 0, 2]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Txz\n(eV)') # T_yz ax = axes[5, 1] ax = util.plot( [moms_xr['temptensor'][:, 1, 2], T_xr[:, 1, 2], T_max_dist[:, 1, 2]], ax=ax, labels=['moms', 'dist', 'max'], xlabel='', ylabel='Txz\n(eV)') # P_xx ax = axes[0, 2] ax = util.plot( [moms_xr['prestensor'][:, 0, 0], P_xr[:, 0, 0], P_max_dist[:, 0, 0]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Pxx\n(nPa)') # P_yy ax = axes[1, 2] ax = util.plot( [moms_xr['prestensor'][:, 1, 1], P_xr[:, 1, 1], P_max_dist[:, 1, 1]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Pyy\n(nPa)') # P_zz ax = axes[2, 2] ax = util.plot( [moms_xr['prestensor'][:, 2, 2], P_xr[:, 2, 2], P_max_dist[:, 2, 2]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Pzz\n(nPa)') # P_xy ax = axes[3, 2] ax = util.plot( [moms_xr['prestensor'][:, 0, 1], P_xr[:, 0, 1], P_max_dist[:, 0, 1]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Pxy\n(nPa)') # P_xz ax = axes[4, 2] ax = util.plot( [moms_xr['prestensor'][:, 0, 2], P_xr[:, 0, 2], P_max_dist[:, 0, 2]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Pxz\n(nPa)') # P_yz ax = axes[5, 2] ax = util.plot( [moms_xr['prestensor'][:, 1, 2], P_xr[:, 1, 2], P_max_dist[:, 1, 2]], ax=ax, labels=['moms', 'dist', 'max'], xlabel='', ylabel='Pyz\n(nPa)') fig.suptitle( 'Comparing FPI Moments, Integrated Distribution, Equivalent Maxwellian' ) plt.subplots_adjust(left=0.1, right=0.90, top=0.95, bottom=0.12, hspace=0.3, wspace=0.8) return fig, axes
def maxwellian_lookup_table(sc, mode, species, start_date, end_date, lut_file=None, minimize='both'): instr = 'fpi' level = 'l2' optdesc = 'd' + species + 's-dist' # Name of the look-up table if lut_file is None: lut_file = data_root / '_'.join( (sc, instr, mode, level, optdesc + 'lookup-table', start_date.strftime('%Y%m%d_%H%M%S'), end_date.strftime('%Y%m%d_%H%M%S'))) lut_file = lut_file.with_suffix('.ncdf') # Ensure it is Path-like else: lut_file = Path(lut_file).expanduser().absolute() # Read the data fpi_dist = fpi.load_dist(sc=sc, mode=mode, optdesc=optdesc, start_date=start_date, end_date=end_date) # Precondition the distributions fpi_kwargs = fpi.precond_params(sc, mode, level, optdesc, start_date, end_date, time=fpi_dist['time']) f = fpi.precondition(fpi_dist['dist'], **fpi_kwargs) # Calculate Moments N = fpi.density(f) V = fpi.velocity(f, N=N) T = fpi.temperature(f, N=N, V=V) P = fpi.pressure(f, N=N, T=T) s = fpi.entropy(f) sv = fpi.vspace_entropy(f, N=N, s=s) t = ((T[:, 0, 0] + T[:, 1, 1] + T[:, 2, 2]) / 3.0).drop( ['t_index_dim1', 't_index_dim2']) p = ((P[:, 0, 0] + P[:, 1, 1] + P[:, 2, 2]) / 3.0).drop( ['t_index_dim1', 't_index_dim2']) s_max_moms = fpi.maxwellian_entropy(N, p) # Create equivalent Maxwellian distributions and calculate moments f_max = fpi.maxwellian_distribution(f, N=N, bulkv=V, T=t) N_max = fpi.density(f_max) V_max = fpi.velocity(f_max, N=N_max) T_max = fpi.temperature(f_max, N=N_max, V=V_max) P_max = fpi.pressure(f_max, N=N_max, T=T_max) s_max = fpi.entropy(f_max) sv_max = fpi.vspace_entropy(f, N=N_max, s=s_max) t_max = ((T_max[:, 0, 0] + T_max[:, 1, 1] + T_max[:, 2, 2]) / 3.0).drop( ['t_index_dim1', 't_index_dim2']) p_max = ((P_max[:, 0, 0] + P_max[:, 1, 1] + P_max[:, 2, 2]) / 3.0).drop( ['t_index_dim1', 't_index_dim2']) # Create the lookup table of Maxwellian distributions if it does not exist if not lut_file.exists(): N_range = (0.9 * N.min().values, 1.1 * N.max().values) t_range = (0.9 * t.min().values, 1.1 * t.max().values) dims = (int(10**max( np.floor(np.abs(np.log10(N_range[1] - N_range[0]))), 1)), int(10**max( np.floor(np.abs(np.log10(t_range[1] - t_range[0]))), 1))) fpi.maxwellian_lookup(f[[0], ...], N_range, t_range, dims=dims, fname=lut_file) # Read the dataset lut = xr.load_dataset(lut_file) dims = lut['N'].shape # Find the error in N and T between the Maxwellian and Measured # distribution N_grid, t_grid = np.meshgrid(lut['N_data'], lut['t_data'], indexing='ij') dN = (N_grid - lut['N']) / N_grid * 100.0 dt = (t_grid - lut['t']) / t_grid * 100.0 N_lut = xr.zeros_like(N) t_lut = xr.zeros_like(t) s_lut = xr.zeros_like(s) sv_lut = xr.zeros_like(sv) if minimize == 'N': for idx, dens in enumerate(N): imin = np.argmin(np.abs(lut['N'].data - dens.item())) irow = imin // dims[1] icol = imin % dims[1] N_lut[idx] = lut['N'][irow, icol] t_lut[idx] = lut['t'][irow, icol] s_lut[idx] = lut['s'][irow, icol] sv_lut[idx] = lut['sv'][irow, icol] elif minimize == 't': for idx, temp in enumerate(t): imin = np.argmin(np.abs(lut['t'].data - temp.item())) irow = imin // dims[1] icol = imin % dims[1] N_lut[idx] = lut['N'][irow, icol] t_lut[idx] = lut['t'][irow, icol] s_lut[idx] = lut['s'][irow, icol] sv_lut[idx] = lut['sv'][irow, icol] elif minimize == 'both': for idx, (dens, temp) in enumerate(zip(N, t)): imin = np.argmin( np.sqrt((lut['t'].data - temp.item())**2 + (lut['N'].data - dens.item())**2)) irow = imin // dims[1] icol = imin % dims[1] N_lut[idx] = lut['N'][irow, icol] t_lut[idx] = lut['t'][irow, icol] s_lut[idx] = lut['s'][irow, icol] sv_lut[idx] = lut['sv'][irow, icol] # Create a time-series dataset of Maxwellian data by interpolating the # lookup-table onto the timestamps of the data elif minimize == 'data': lut_interp = lut.interp({'N_data': N, 't_data': t}, method='linear') N_lut = lut_interp['N'] t_lut = lut_interp['t'] s_lut = lut_interp['s'] sv_lut = lut_interp['sv'] # Create the figure fig = plt.figure(figsize=(6, 7.5)) # Figure positions # - Start with the position of a plot in the first row of a one-column # set of plots. # - Define the width and height spacing between each row # - The first row will actually be broken into two columns with a gap # between the first and second rows to allow for axis labels # - Subsequent plots will be offset from the first by multiples of # height and hspace left, bottom, width, height = 0.15, 0.8, 0.7, 0.14 wspace, hspace, gap = 0.2, 0.02, 0.07 row1_width = 0.26 # Error in the look-up table density # - Error is independent of density, so plot as 1D line plot ax = fig.add_subplot(521) dN[0, :].plot(ax=ax) ax.set_title('') ax.set_xlabel('$T_{' + species + '}$ (eV)') ax.set_ylabel('$\Delta n_{' + species + '}/n_{' + species + '}$ (%)') ax.set_position((left, bottom, row1_width, height)) util.format_axes(ax, time=False) ''' img = dN.T.plot(ax=ax, cmap=cm.get_cmap('rainbow', 10), add_colorbar=False) ax.set_title('') ax.set_xlabel('$n_{'+species+'}$ ($cm^{-3}$)') ax.set_ylabel('$T_{'+species+'}$ (eV)') # Create a colorbar that is aware of the image's new position divider = make_axes_locatable(ax) cax = divider.new_horizontal(size="5%", pad=0.1) fig.add_axes(cax) cb = fig.colorbar(img, cax=cax, orientation="vertical") cb.set_label('$\Delta n_{'+species+'}/n_{'+species+'}$ (%)') cb.ax.minorticks_on() ''' # Error in the look-up table temperature # - Error is independent of density, so plot as 1D line plot ax = fig.add_subplot(522) dt[0, :].plot(ax=ax) ax.set_title('') ax.set_xlabel('$T_{' + species + '}$ (eV)') ax.set_ylabel('$\Delta T_{' + species + '}/T_{' + species + '}$ (%)') ax.set_position((left + row1_width + wspace, bottom, row1_width, height)) util.format_axes(ax, time=False) ''' img = dt.T.plot(ax=ax, cmap=cm.get_cmap('rainbow', 10), add_colorbar=False) ax.set_title('') ax.set_xlabel('$n_{'+species+'}$ ($cm^{-3}$)') ax.set_ylabel('$T_{'+species+'}$ (eV)') ax.set_position((left+row1_width+wspace, bottom, row1_width, height)) util.format_axes(ax, time=False) # Create a colorbar that is aware of the image's new position divider = make_axes_locatable(ax) cax = divider.new_horizontal(size="5%", pad=0.1) fig.add_axes(cax) cb = fig.colorbar(img, cax=cax, orientation="vertical") cb.set_label('$\Delta T_{'+species+'}/T_{'+species+'}$ (%)') cb.ax.minorticks_on() ''' # Error in the adjusted look-up table dN_max = (N - N_max) / N * 100.0 dN_adj = (N - N_lut) / N * 100.0 ax = fig.add_subplot(512) l1 = dN_max.plot(ax=ax, label='$\Delta n_{' + species + ',Max}/n_{' + species + ',Max}$') l2 = dN_adj.plot(ax=ax, label='$\Delta n_{' + species + ',adj}/n_{' + species + ',adj}$') ax.set_title('') ax.set_xticklabels([]) ax.set_xlabel('') ax.set_ylabel('$\Delta n_{' + species + '}/n_{' + species + '}$ (%)') ax.set_position((left, bottom - gap - height, width, height)) util.format_axes(ax, xaxis='off') util.add_legend(ax, [l1[0], l2[0]], corner='SE', horizontal=True) # Deviation in temperature dt_max = (t - t_max) / t * 100.0 dt_adj = (t - t_lut) / t * 100.0 ax = fig.add_subplot(513) l1 = dt_max.plot(ax=ax, label='$\Delta T_{' + species + ',Max}/T_{' + species + ',Max}$') l2 = dt_adj.plot(ax=ax, label='$\Delta T_{' + species + ',adj}/T_{' + species + ',adj}$') ax.set_title('') ax.set_xticklabels([]) ax.set_xlabel('') ax.set_ylabel('$\Delta T_{' + species + '}/T_{' + species + '}$ (%)') ax.set_ylim(-1, 2.5) ax.set_position((left, bottom - gap - 2 * height - hspace, width, height)) util.format_axes(ax, xaxis='off') util.add_legend(ax, [l1[0], l2[0]], corner='NE', horizontal=True) # Deviation in entropy ds_moms = (s - s_max_moms) / s * 100.0 ds_m = (s - s_max) / s * 100.0 ds_adj = (s - s_lut) / s * 100.0 ax = fig.add_subplot(514) l1 = ds_m.plot(ax=ax, label='$\Delta s_{' + species + ',Max}/s_{' + species + ',Max}$') l2 = ds_adj.plot(ax=ax, label='$\Delta s_{' + species + ',adj}/s_{' + species + ',adj}$') l3 = ds_moms.plot(ax=ax, label='$\Delta s_{' + species + ',moms}/s_{' + species + ',moms}$') ax.set_title('') ax.set_xticklabels([]) ax.set_xlabel('') ax.set_ylabel('$\Delta s_{' + species + '}/s_{' + species + '}$ (%)') ax.set_ylim(-9, 2.5) ax.set_position( (left, bottom - gap - 3 * height - 2 * hspace, width, height)) util.format_axes(ax, xaxis='off') util.add_legend(ax, [l1[0], l2[0], l3[0]], corner='SE', horizontal=True) # Deviation in velocity-space entropy dsv_max = (sv - sv_max) / sv * 100.0 dsv_adj = (sv - sv_lut) / sv * 100.0 ax = fig.add_subplot(515) l1 = dsv_max.plot(ax=ax, label='$\Delta s_{V,' + species + ',Max}/s_{V,' + species + ',Max}$') l2 = dsv_adj.plot(ax=ax, label='$\Delta s_{V,' + species + ',adj}/s_{V,' + species + ',adj}$') ax.set_title('') ax.set_xlabel('') ax.set_ylabel('$\Delta s_{V,' + species + '}/s_{V,' + species + '}$ (%)') ax.set_position( (left, bottom - gap - 4 * height - 3 * hspace, width, height)) util.format_axes(ax) util.add_legend(ax, [l1[0], l2[0]], corner='SE', horizontal=True) fig.suptitle('Maxwellian Look-up Table') plt.setp(fig.axes[2:], xlim=mdates.date2num([start_date, end_date])) return fig, fig.axes
def kinetic_entropy(sc, mode, start_date, end_date, **kwargs): # Read the data b = fgm.load_data(sc=sc, mode=mode, start_date=start_date, end_date=end_date) dis_dist = fpi.load_dist(sc=sc, mode=mode, optdesc='dis-dist', start_date=start_date, end_date=end_date) des_dist = fpi.load_dist(sc=sc, mode=mode, optdesc='des-dist', start_date=start_date, end_date=end_date) # Precondition the distributions dis_kwargs = fpi.precond_params(sc, mode, 'l2', 'dis-dist', start_date, end_date, time=dis_dist['time']) des_kwargs = fpi.precond_params(sc, mode, 'l2', 'des-dist', start_date, end_date, time=des_dist['time']) f_dis = fpi.precondition(dis_dist['dist'], **dis_kwargs) f_des = fpi.precondition(des_dist['dist'], **des_kwargs) # Calculate Moments Ni = fpi.density(f_dis) Vi = fpi.velocity(f_dis, N=Ni) Ti = fpi.temperature(f_dis, N=Ni, V=Vi) Pi = fpi.pressure(f_dis, N=Ni, T=Ti) ti = ((Ti[:,0,0] + Ti[:,1,1] + Ti[:,2,2]) / 3.0).drop(['t_index_dim1', 't_index_dim2']) pi = ((Pi[:,0,0] + Pi[:,1,1] + Pi[:,2,2]) / 3.0).drop(['t_index_dim1', 't_index_dim2']) Ne = fpi.density(f_des) Ve = fpi.velocity(f_des, N=Ne) Te = fpi.temperature(f_des, N=Ne, V=Ve) Pe = fpi.pressure(f_des, N=Ne, T=Te) te = ((Te[:,0,0] + Te[:,1,1] + Te[:,2,2]) / 3.0).drop(['t_index_dim1', 't_index_dim2']) pe = ((Pe[:,0,0] + Pe[:,1,1] + Pe[:,2,2]) / 3.0).drop(['t_index_dim1', 't_index_dim2']) # Equivalent (preconditioned) Maxwellian distributions fi_max = fpi.maxwellian_distribution(f_dis, N=Ni, bulkv=Vi, T=ti) fe_max = fpi.maxwellian_distribution(f_des, N=Ne, bulkv=Ve, T=te) # Entropy density si_dist = fpi.entropy(f_dis) se_dist = fpi.entropy(f_des) si_max = fpi.entropy(fi_max) se_max = fpi.entropy(fe_max) # Velcoity space entropy density siv_dist = fpi.vspace_entropy(f_dis, N=Ni, s=si_dist) sev_dist = fpi.vspace_entropy(f_des, N=Ne, s=se_dist) # The Maxwellian is already preconditioned # - There are three options for calculating the v-space entropy of # the Maxwellian distribution: using # 1) FPI integrated moments, # 2) Custom moments of the measured distribution # 3) Custom moments of the equivalent Maxwellian distribution # Because the Maxwellian is built with discrete v-space bins, its # density, velocity, and temperature do not match that of the # measured distribution on which it is based. If NiM is used, the # M-bar term will be negative, which is unphysical, so here we use # the density of the measured distribution and the entropy of the # equivalent Maxwellian. siv_max = fpi.vspace_entropy(fi_max, N=Ni, s=si_max) sev_max = fpi.vspace_entropy(fe_max, N=Ne, s=se_max) # M-bar mi_bar = np.abs(si_max - si_dist) / si_max me_bar = np.abs(se_max - se_dist) / se_max miv_bar = np.abs(siv_max - siv_dist) / siv_max mev_bar = np.abs(sev_max - sev_dist) / sev_max # Epsilon ei = fpi.epsilon(f_dis, N=Ni, V=Vi, T=ti) ee = fpi.epsilon(f_des, N=Ne, V=Ve, T=te) # Setup the plot nrows = 9 ncols = 1 figsize = (5.5, 7.0) fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=figsize, squeeze=False) # B ax = axes[0,0] ax = util.plot([b['B_GSE'][:,3], b['B_GSE'][:,0], b['B_GSE'][:,1], b['B_GSE'][:,2]], ax=ax, labels=['|B|', 'Bx', 'By', 'Bz'], xaxis='off', ylabel='B\n(nT)' ) # Ion entropy density ax = axes[1,0] ax = util.plot([si_max, si_dist], ax=ax, labels=['$s_{i,max}$', '$s_{i}$'], xaxis='off', ylabel='s\n$J/K/m^{3}$ $ln(s^{3}/m^{6})$' ) # Electron entropy density ax = axes[2,0] ax = util.plot([se_max, se_dist], ax=ax, labels=['$s_{e,max}$', '$s_{e}$'], xaxis='off', ylabel='s' ) # Ion M-bar ax = axes[3,0] ax = util.plot(mi_bar, ax=ax, legend=False, xaxis='off', ylabel='$\overline{M}_{i}$' ) # Ion Epsilon ax = ax.twinx() ei.plot(ax=ax, color='g') ax.set_ylabel('$\epsilon_{i}$\n$(s/m)^{3/2}$') ax.yaxis.label.set_color('g') ax.tick_params(axis='y', colors='g') ax.set_xticklabels([]) ax.set_title('') # Electron M-bar ax = axes[4,0] ax = util.plot(me_bar, ax=ax, legend=False, xaxis='off', ylabel='$\overline{M}_{e}$' ) # Electron Epsilon ax = ax.twinx() ee.plot(ax=ax, color='r') ax.set_ylabel('$\epsilon_{e}$\n$(s/m)^{3/2}$') ax.yaxis.label.set_color('r') ax.tick_params(axis='y', colors='r') ax.set_xticklabels([]) ax.set_title('') # Velocity space ion entropy density ax = axes[5,0] ax = util.plot([siv_max, siv_dist], ax=ax, labels=['$s_{i,V,max}$', '$s_{i,V}$'], xaxis='off', ylabel='$s_{V}$\n$J/K/m^{3}$ $ln()$' ) # Velocity space electron entropy density ax = axes[6,0] ax = util.plot([sev_max, sev_dist], ax=ax, labels=['$s_{e,V,max}$', '$s_{e,V}$'], xaxis='off', ylabel='$s_{V}$' ) # Velocity space ion M-bar ax = axes[7,0] ax = util.plot(miv_bar, ax=ax, legend=False, xaxis='off', ylabel='$\overline{M}_{i,V}$' ) # Velocity space electron M-bar ax = axes[8,0] ax = util.plot(mev_bar, ax=ax, legend=False, ylabel='$\overline{M}_{e,V}$' ) fig.suptitle('Total and Velocity Space Entropy Density') plt.subplots_adjust(left=0.2, right=0.85, top=0.95, hspace=0.4) # plt.setp(axes, xlim=xlim) return fig, axes
def compare_moments(sc, mode, species, start_date, end_date, scpot_correction=False, ephoto_correction=False): ''' Compare moments derived from the 3D velocity distribution functions from three sources: official FPI moments, derived herein, and those of an equivalent Maxwellian distribution derived herein. Parameters ---------- sc : str Spacecraft identifier. Choices are ('mms1', 'mms2', 'mms3', 'mms4') mode : str Data rate mode. Choices are ('fast', 'brst') species : str Particle species. Choices are ('i', 'e') for ions and electrons, respectively start_date, end_date : `datetime.datetime` Start and end dates and times of the time interval scpot_correction : bool Apply spacecraft potential correction to the distribution functions. ephoto : bool Subtract photo-electrons. Applicable to DES data only. Returns ------- fig : `matplotlib.figure` Figure in which graphics are displayed ax : list List of `matplotlib.pyplot.axes` objects ''' # Read the data moms_xr = fpi.load_moms(sc, mode, species, start_date, end_date) dist_xr = fpi.load_dist(sc, mode, species, start_date, end_date, ephoto=ephoto_correction) # Spacecraft potential correction scpot = None if scpot_correction: edp_mode = mode if mode == 'brst' else 'fast' scpot = edp.load_scpot(sc, edp_mode, start_date, end_date) scpot = scpot.interp_like(moms_xr, method='nearest') # Create an equivalent Maxwellian distribution max_xr = fpi.maxwellian_distribution(dist_xr, moms_xr['density'], moms_xr['velocity'], moms_xr['t']) # Density ni_xr = fpi.density(dist_xr, scpot=scpot) ni_max_dist = fpi.density(max_xr, scpot=scpot) # Entropy s_xr = fpi.entropy(dist_xr, scpot=scpot) s_max_dist = fpi.entropy(max_xr, scpot=scpot) s_max = fpi.maxwellian_entropy(moms_xr['density'], moms_xr['p']) # Velocity v_xr = fpi.velocity(dist_xr, N=ni_xr, scpot=scpot) v_max_dist = fpi.velocity(max_xr, N=ni_max_dist, scpot=scpot) # Temperature T_xr = fpi.temperature(dist_xr, N=ni_xr, V=v_xr, scpot=scpot) T_max_dist = fpi.temperature(max_xr, N=ni_max_dist, V=v_max_dist, scpot=scpot) # Pressure P_xr = fpi.pressure(dist_xr, N=ni_xr, T=T_xr) P_max_dist = fpi.pressure(max_xr, N=ni_max_dist, T=T_max_dist) # Scalar pressure p_scalar_xr = (P_xr[:, 0, 0] + P_xr[:, 1, 1] + P_xr[:, 2, 2]) / 3.0 p_scalar_max_dist = (P_max_dist[:, 0, 0] + P_max_dist[:, 1, 1] + P_max_dist[:, 2, 2]) / 3.0 p_scalar_xr = p_scalar_xr.drop(['t_index_dim1', 't_index_dim2']) p_scalar_max_dist = p_scalar_max_dist.drop( ['t_index_dim1', 't_index_dim2']) # Epsilon e_xr = fpi.epsilon(dist_xr, dist_max=max_xr, N=ni_xr) nrows = 6 ncols = 3 figsize = (10.0, 5.5) fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=figsize, squeeze=False) ''' locator = mdates.AutoDateLocator() formatter = mdates.ConciseDateFormatter(locator) ax.xaxis.set_major_locator(locator) ax.xaxis.set_major_formatter(formatter) for tick in ax.get_xticklabels(): tick.set_rotation(45) # Denisty ax = axes[0,0] lines = [] moms_xr['density'].plot(ax=ax, label='moms') ni_xr.plot(ax=ax, label='dist') ni_max_dist.plot(ax=ax, label='max') ax.set_xlabel('') ax.set_xticklabels([]) ax.set_ylabel='N\n($cm^{-3}$)' # Create the legend outside the right-most axes leg = ax.legend(bbox_to_anchor=(1.05, 1), borderaxespad=0.0, frameon=False, handlelength=0, handletextpad=0, loc='upper left') # Color the text the same as the lines for line, text in zip(lines, leg.get_texts()): text.set_color(line.get_color()) ''' # Density ax = axes[0, 0] moms_xr['density'].attrs['label'] = 'moms' ni_xr.attrs['label'] = 'dist' ni_max_dist.attrs['label'] = 'max' ax = util.plot([moms_xr['density'], ni_xr, ni_max_dist], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='N\n($cm^{-3}$)') # Entropy ax = axes[1, 0] ax = util.plot([s_max, s_xr, s_max_dist], ax=ax, labels=['moms', 'dist', 'max dist'], xaxis='off', ylabel='S\n[J/K/$m^{3}$ ln($s^{3}/m^{6}$)]') # Epsilon ax = axes[1, 0].twinx() e_xr.plot(ax=ax, color='r') ax.spines['right'].set_color('red') ax.yaxis.label.set_color('red') ax.tick_params(axis='y', colors='red') ax.set_title('') ax.set_xticks([]) ax.set_xlabel('') ax.set_ylabel('$\epsilon$\n$(s/m)^{3/2}$') # Vx ax = axes[2, 0] ax = util.plot([moms_xr['velocity'][:, 0], v_xr[:, 0], v_max_dist[:, 0]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Vx\n(km/s)') # Vy ax = axes[3, 0] ax = util.plot([moms_xr['velocity'][:, 1], v_xr[:, 1], v_max_dist[:, 1]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Vy\n(km/s)') # Vz ax = axes[4, 0] ax = util.plot([moms_xr['velocity'][:, 2], v_xr[:, 2], v_max_dist[:, 2]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Vz\n(km/s)') # Scalar Pressure ax = axes[5, 0] ax = util.plot([moms_xr['p'], p_scalar_xr, p_scalar_max_dist], ax=ax, labels=['moms', 'dist', 'max'], xlabel='', ylabel='p\n(nPa)') # T_xx ax = axes[0, 1] ax = util.plot( [moms_xr['temptensor'][:, 0, 0], T_xr[:, 0, 0], T_max_dist[:, 0, 0]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Txx\n(eV)') # T_yy ax = axes[1, 1] ax = util.plot( [moms_xr['temptensor'][:, 1, 1], T_xr[:, 1, 1], T_max_dist[:, 1, 1]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Tyy\n(eV)') # T_zz ax = axes[2, 1] ax = util.plot( [moms_xr['temptensor'][:, 2, 2], T_xr[:, 2, 2], T_max_dist[:, 2, 2]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Tzz\n(eV)') # T_xy ax = axes[3, 1] ax = util.plot( [moms_xr['temptensor'][:, 0, 1], T_xr[:, 0, 1], T_max_dist[:, 0, 1]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Txy\n(eV)') # T_xz ax = axes[4, 1] ax = util.plot( [moms_xr['temptensor'][:, 0, 2], T_xr[:, 0, 2], T_max_dist[:, 0, 2]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Txz\n(eV)') # T_yz ax = axes[5, 1] ax = util.plot( [moms_xr['temptensor'][:, 1, 2], T_xr[:, 1, 2], T_max_dist[:, 1, 2]], ax=ax, labels=['moms', 'dist', 'max'], xlabel='', ylabel='Txz\n(eV)') # P_xx ax = axes[0, 2] ax = util.plot( [moms_xr['prestensor'][:, 0, 0], P_xr[:, 0, 0], P_max_dist[:, 0, 0]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Pxx\n(nPa)') # P_yy ax = axes[1, 2] ax = util.plot( [moms_xr['prestensor'][:, 1, 1], P_xr[:, 1, 1], P_max_dist[:, 1, 1]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Pyy\n(nPa)') # P_zz ax = axes[2, 2] ax = util.plot( [moms_xr['prestensor'][:, 2, 2], P_xr[:, 2, 2], P_max_dist[:, 2, 2]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Pzz\n(nPa)') # P_xy ax = axes[3, 2] ax = util.plot( [moms_xr['prestensor'][:, 0, 1], P_xr[:, 0, 1], P_max_dist[:, 0, 1]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Pxy\n(nPa)') # P_xz ax = axes[4, 2] ax = util.plot( [moms_xr['prestensor'][:, 0, 2], P_xr[:, 0, 2], P_max_dist[:, 0, 2]], ax=ax, labels=['moms', 'dist', 'max'], xaxis='off', ylabel='Pxz\n(nPa)') # P_yz ax = axes[5, 2] ax = util.plot( [moms_xr['prestensor'][:, 1, 2], P_xr[:, 1, 2], P_max_dist[:, 1, 2]], ax=ax, labels=['moms', 'dist', 'max'], xlabel='', ylabel='Pyz\n(nPa)') fig.suptitle( 'Comparing FPI Moments, Integrated Distribution, Equivalent Maxwellian' ) plt.subplots_adjust(left=0.1, right=0.90, top=0.95, bottom=0.12, hspace=0.3, wspace=0.8) return fig, axes