示例#1
0
def kinetic_entropy(sc, mode, start_date, end_date, **kwargs):

    # Read the data
    b = fgm.load_data(sc, mode, start_date, end_date)
    dis_moms = fpi.load_moms(sc, mode, 'i', start_date, end_date)
    des_moms = fpi.load_moms(sc, mode, 'e', start_date, end_date)
    dis_dist = fpi.load_dist(sc, mode, 'i', start_date, end_date)
    des_dist = fpi.load_dist(sc, mode, 'e', start_date, end_date)

    # Equivalent Maxwellian distribution
    dis_max_dist = fpi.maxwellian_distribution(dis_dist,
                                               N=dis_moms['density'],
                                               bulkv=dis_moms['velocity'],
                                               T=dis_moms['t'])
    des_max_dist = fpi.maxwellian_distribution(des_dist,
                                               N=des_moms['density'],
                                               bulkv=des_moms['velocity'],
                                               T=des_moms['t'])

    # Entropy density
    si_dist = fpi.entropy(dis_dist, **kwargs)
    se_dist = fpi.entropy(des_dist, **kwargs)

    #    si_max = fpi.maxwellian_entropy(dis_moms['density'], dis_moms['p'])
    #    se_max = fpi.maxwellian_entropy(des_moms['density'], des_moms['p'])
    si_max = fpi.entropy(dis_max_dist, **kwargs)
    se_max = fpi.entropy(des_max_dist, **kwargs)

    # Velcoity space entropy density
    siv_dist = fpi.vspace_entropy(dis_dist,
                                  N=dis_moms['density'],
                                  s=si_dist,
                                  **kwargs)
    sev_dist = fpi.vspace_entropy(des_dist,
                                  N=des_moms['density'],
                                  s=se_dist,
                                  **kwargs)

    siv_max = fpi.vspace_entropy(des_max_dist,
                                 N=dis_moms['density'],
                                 s=si_max,
                                 **kwargs)
    sev_max = fpi.vspace_entropy(des_max_dist,
                                 N=des_moms['density'],
                                 s=se_max,
                                 **kwargs)

    # 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(dis_dist,
                     N=dis_moms['density'],
                     V=dis_moms['velocity'],
                     T=dis_moms['t'],
                     **kwargs)
    ee = fpi.epsilon(des_dist,
                     N=des_moms['density'],
                     V=des_moms['velocity'],
                     T=des_moms['t'],
                     **kwargs)

    # 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'][:, 3], b['B'][:, 0], b['B'][:, 1], b['B'][:, 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([])

    # 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([])

    # 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
示例#2
0
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
示例#3
0
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
示例#4
0
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 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 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