示例#1
0
文件: test_FSA.py 项目: lda16/Aurora
import omfit_eqdsk, omfit_gapy
import scipy, sys, os
import time
from scipy.interpolate import interp1d

# Make sure that package home is added to sys.path
sys.path.append('../')
import aurora

# read in default Aurora namelist
namelist = aurora.default_nml.load_default_namelist()
kp = namelist['kin_profs']

# Use gfile and statefile in local directory:
examples_dir = os.path.dirname(os.path.abspath(__file__))
geqdsk = omfit_eqdsk.OMFITgeqdsk(examples_dir + '/example.gfile')
inputgacode = omfit_gapy.OMFITgacode(examples_dir + '/example.input.gacode')

# transform rho_phi (=sqrt toroidal flux) into rho_psi (=sqrt poloidal flux) and save kinetic profiles
rhop = kp['Te']['rhop'] = kp['ne']['rhop'] = np.sqrt(
    inputgacode['polflux'] / inputgacode['polflux'][-1])
kp['ne']['vals'] = inputgacode['ne'][None, :] * 1e13  # 1e19 m^-3 --> cm^-3
kp['Te']['vals'] = inputgacode['Te'][None, :] * 1e3  # keV --> eV

# set impurity species and sources rate
imp = namelist['imp'] = 'Ar'
namelist['source_type'] = 'const'
namelist['Phi0'] = 1e24

# Setup aurora sim to efficiently setup atomic rates and profiles over radius
asim = aurora.core.aurora_sim(namelist, geqdsk=geqdsk)
示例#2
0
import omfit_eqdsk, omfit_gapy
import pickle as pkl
import scipy, sys, os
import time

# Make sure that package home is added to sys.path
import sys
sys.path.append('../')
import aurora

# read in default Aurora namelist
namelist = aurora.default_nml.load_default_namelist()
kp = namelist['kin_profs']

# Use gfile and statefile in local directory:
geqdsk = omfit_eqdsk.OMFITgeqdsk('example.gfile')
inputgacode = omfit_gapy.OMFITgacode('example.input.gacode')

# transform rho_phi (=sqrt toroidal flux) into rho_psi (=sqrt poloidal flux) and save kinetic profiles
kp['Te']['rhop'] = kp['ne']['rhop'] = np.sqrt(inputgacode['polflux'] /
                                              inputgacode['polflux'][-1])
kp['ne']['vals'] = inputgacode['ne'][None, :] * 1e13  # 1e19 m^-3 --> cm^-3
kp['Te']['vals'] = inputgacode['Te'][None, :] * 1e3  # keV --> eV

# set impurity species and sources rate
imp = namelist['imp'] = 'Ar'
namelist['source_type'] = 'const'
namelist['Phi0'] = 1e24

# Now get aurora setup
asim = aurora.core.aurora_sim(namelist, geqdsk=geqdsk)
示例#3
0
## D#2 species ?
fig, ax = plt.subplots(figsize=(4, 7))
IM = ax.tripcolor(TP, fort46['pdena'][fort46['pdena'].shape[0] // 2:])
ax.set_aspect('equal')
ax.grid()
ax.set_xlabel('Radial Position R (m)')
ax.set_ylabel('Vertical Position Z (m)')
plt.colorbar(IM, ax=ax)

##### Try loading methods similar to those of J. Lore
# number of species:
ns = b2fstate['ns']  # 2 for pure plasma

# load geqdsk
geqdsk = omfit_eqdsk.OMFITgeqdsk(baserun + 'V1E_geqdsk_LSN2')

# magnetic field flux functions
#fpsi = b2fstate['fpsi']
#ffbz = b2fstate['ffbz']

# toroidal and cylindrical symmetry:
# bx=(dpsi/dy)/(2*pi*R)
# by=-(dpsi/dx)/(2*pi*R)
# bz=ffbz/(2*pi*R)

###
nx, ny = geom['nx,ny']
crx = geom['crx'].reshape(4, ny + 2, nx + 2)
cry = geom['cry'].reshape(4, ny + 2, nx + 2)
示例#4
0
文件: core.py 项目: lda16/Aurora
    def __init__(self, namelist, geqdsk=None, nbi_cxr=None):
        '''Setup aurora simulation input dictionary from the given namelist.

        Args:
            namelist : dict
                Dictionary containing aurora inputs. See default_nml.py for some defaults, 
                which users should modify for their runs.
            geqdsk : dict, optional
                EFIT gfile as returned after postprocessing by the :py:mod:`omfit_eqdsk` 
                package (OMFITgeqdsk class). If left to None (default), the geqdsk dictionary 
                is constructed starting from the gfile in the MDS+ tree.
            nbi_cxr : array, optional
                If namelist['nbi_cxr']=True, this array represents the charge exchange rates 
                with NBI neutrals, fast and/or thermal, across the entire radius and on the 
                time base of interest. 
                Creating this input is not trivial and must be done externally to aurora. 
                General steps:
                - get density of fast NBI neutrals (both fast and thermal/halo) ---> n0_nbi, n0_halo
                - get total rates (n-unresolved) for CX with NBI neutrals --> _alpha_CX_NBI_rates
                - thermal rates for the halo may be from ADAS CCD files or from the same methods used 
                for fast neutrals
                - sum n0_nbi *  alpha_CX_NBI_rates + n0_halo * alpha_CX_rates
                This method still needs more testing within this class. Please contact author for details. 

        '''
        self.namelist = namelist
        self.kin_profs = namelist['kin_profs']
        self.nbi_cxr = nbi_cxr
        self.imp = namelist['imp']

        # import omfit_eqdsk here to avoid issues with docs and packaging
        import omfit_eqdsk
        if geqdsk is None:
            # Fetch geqdsk from MDS+ (using EFIT01) and post-process it using the OMFIT geqdsk format.
            self.geqdsk = omfit_eqdsk.OMFITgeqdsk('').from_mdsplus(
                device=namelist['device'],
                shot=namelist['shot'],
                time=namelist['time'],
                SNAPfile='EFIT01',
                fail_if_out_of_range=False,
                time_diff_warning_threshold=20)
        else:
            self.geqdsk = geqdsk

        self.Raxis_cm = self.geqdsk['RMAXIS'] * 100.  # cm

        # set up radial and temporal grids
        self.setup_grids()

        # set up kinetic profiles and atomic rates
        self.setup_kin_profs_depts()

        # create array of 0's of length equal to self.time_grid, with 1's where sawteeth must be triggered
        self.saw_on = np.zeros_like(self.time_grid)
        input_saw_times = self.namelist['saw_model']['times']
        self.saw_times = np.array(input_saw_times)[
            input_saw_times < self.time_grid[-1]]
        if self.namelist['saw_model']['saw_flag'] and len(self.saw_times) > 0:
            self.saw_on[self.time_grid.searchsorted(self.saw_times)] = 1

        # import here to avoid issues when building docs or package
        from omfit_commonclasses.utils_math import atomic_element

        # get nuclear charge Z and atomic mass number A
        out = atomic_element(symbol=self.imp)
        spec = list(out.keys())[0]
        self.Z_imp = int(out[spec]['Z'])
        self.A_imp = int(out[spec]['A'])

        # Extract other inputs from namelist:
        self.mixing_radius = self.namelist['saw_model']['rmix']
        self.decay_length_boundary = self.namelist['SOL_decay']
        self.wall_recycling = self.namelist['wall_recycling']
        self.source_div_fraction = self.namelist[
            'divbls']  # change of nomenclature
        self.tau_div_SOL_ms = self.namelist['tau_div_SOL_ms']
        self.tau_pump_ms = self.namelist['tau_pump_ms']
        self.tau_rcl_ret_ms = self.namelist['tau_rcl_ret_ms']

        # if recycling flag is set to False, avoid any divertor return flows
        # To include divertor return flows but no recycling, set wall_recycling=0
        if not self.namelist['recycling_flag']:
            self.wall_recycling = -1.0  # no divertor return flows

        self.bound_sep = self.namelist['bound_sep']
        self.lim_sep = self.namelist['lim_sep']
        self.sawtooth_erfc_width = self.namelist['saw_model'][
            'sawtooth_erfc_width']
        self.cxr_flag = self.namelist['cxr_flag']
        self.nbi_cxr_flag = self.namelist['nbi_cxr_flag']
示例#5
0
time = 2750  #ms

# get ZIPFIT kinetic profiles
zipfit = get_zipfit.load_zipfit(shot)

# get ne,Te profiles at chosen time
tidx = np.argmin(np.abs(zipfit['ne']['tvec'].data - time / 1e3))
kp['ne']['vals'] = ne = zipfit['ne']['data'].data[tidx, :] * 1e-6  # cm^-3
kp['Te']['vals'] = Te = zipfit['Te']['data'].data[tidx, :]  # eV
rho_tor = zipfit['ne']['rho'].data

# get coreSPRED line integration path
pathR, pathZ, pathL = coreSPRED_helper.get_spred_geom()

geqdsk = omfit_eqdsk.OMFITgeqdsk('').from_mdsplus(device='DIII-D',
                                                  shot=shot,
                                                  time=time,
                                                  SNAPfile='EFIT01')

psiRZ = geqdsk['AuxQuantities']['PSIRZ_NORM'].T
eq_R = geqdsk['AuxQuantities']['R']
eq_Z = geqdsk['AuxQuantities']['Z']

pathPsi = interpn((eq_R, eq_Z),
                  psiRZ,
                  np.c_[pathR, pathZ],
                  bounds_error=False,
                  fill_value=2)

# get line integration weights
kp['ne']['rhop'] = kp['Te']['rhop'] = rhop = aurora.coords.rad_coord_transform(
    rho_tor, 'rhon', 'rhop', geqdsk)
示例#6
0
sys.path.append('../')
import aurora

namelist = aurora.default_nml.load_default_namelist()

# test for C-Mod:
namelist['device'] = 'CMOD'
namelist['shot'] = 1101014030
namelist['time'] = 1250  # ms

gfile_name = f'g{namelist["shot"]}.{str(namelist["time"]).zfill(5)}'

if os.path.exists(gfile_name):
    # fetch local g-file if available
    geqdsk = omfit_eqdsk.OMFITgeqdsk(gfile_name)
    print('Fetched local g-file')
else:
    # attempt to construct it via omfit_eqdsk if not available locally
    geqdsk = omfit_eqdsk.OMFITgeqdsk('').from_mdsplus(
        device=namelist['device'],
        shot=namelist['shot'],
        time=namelist['time'],
        SNAPfile='EFIT01',
        fail_if_out_of_range=False,
        time_diff_warning_threshold=20)
    # save g-file locally:
    geqdsk.save(raw=True)
    print('Saved g-file locally')

# example kinetic profiles
示例#7
0
def Helike_emiss_metrics(imp='Ca', cs_den=None, rhop=None,
                         plot_individual_contributions=False, axs = None):
    ''' Obtain R(Te) and G(ne) from ratios of w,z,x,y He-like lines for an ion 
    '''
    
    # Use gfile and statefile in local directory:
    geqdsk = omfit_eqdsk.OMFITgeqdsk('/home/sciortino/Aurora/examples/example.gfile')
    inputgacode = omfit_gapy.OMFITgacode('/home/sciortino/Aurora/examples/example.input.gacode')
    
    # save kinetic profiles on a rhop (sqrt of norm. pol. flux) grid

    rhop_kp = np.sqrt(inputgacode['polflux']/inputgacode['polflux'][-1])
    ne = inputgacode['ne']*1e13 # 1e19 m^-3 --> cm^-3
    Te = inputgacode['Te']*1e3  # keV --> eV

    # get charge state distributions from ionization equilibrium
    atom_data = aurora.atomic.get_atom_data(imp,['scd','acd'])
    logTe, fz, rates = aurora.get_frac_abundances(atom_data, ne, Te, rho=rhop_kp)

    if cs_den is None:
        # use ionization equilibrium fractional abundances as densities
        cs_den = fz
        rhop = rhop_kp
    else:
        # use provided charge state densities, given on rhop grid
        if rhop is None:
            raise ValueError('Which rhop grid were cs_dens arrays given on??')
        ne = interp1d(rhop_kp, ne, bounds_error=False, fill_value='extrapolate')(rhop)
        Te = interp1d(rhop_kp, Te, bounds_error=False, fill_value='extrapolate')(rhop)

        # normalize cs_den to match He-like density on axis
        cs_den /= cs_den[0,-3]/fz[0,-1]
        
    imp_Z = cs_den.shape[1] -1 

    # limit to core/pedestal
    ridx = np.argmin(np.abs(rhop - 0.99))
    ne = ne[:ridx]
    Te = Te[:ridx]
    rhop = rhop[:ridx]
    cs_den = cs_den[:ridx]

    # get w,z,x,y rate components
    out = compute_Helike_rates(imp_Z, ne, Te/1e3)   # Te input must be keV
    w_comps, z_comps, x_comps, y_comps = out

    # wavelengths for each line:
    w_lam = 3.1773e-10
    z_lam = 3.2111e-10 
    x_lam = 3.1892e-10
    y_lam = 3.1928e-10

    # conversion to frequency
    f_E_lam = lambda lam: h*c/(lam)

    n_H = cs_den[:,-2]
    n_He = cs_den[:,-3]
    n_Li = cs_den[:,-4]
    
    # compute line rates in phot/s/cm^3
    nw =  n_Li*w_comps[0] + n_He*w_comps[1] + n_H*w_comps[2]
    nz= n_Li*z_comps[0] + n_He*z_comps[1] + n_H*z_comps[2]
    nx = n_He*x_comps[0] + n_H*x_comps[1] + nz*x_comps[2]
    ny = n_He*y_comps[0] + n_H*y_comps[1] + nz*y_comps[2]

    # convert rates to J/s/cm^3
    w = f_E_lam(w_lam) * nw
    z = f_E_lam(z_lam) * nz
    x = f_E_lam(x_lam) * nx
    y = f_E_lam(y_lam) * ny

    # compute atomic plasma diagnostics only in the plasma core/pedestal
    R_ne = z / (x+y)
    G_Te = (z + x + y)/w

    # plot each line emissivity

    if axs is not None:
        ax0,ax1 = axs[0]
        ax2 = axs[1]
        ax3 = axs[2]
        ls='--'
    else:

        if plot_individual_contributions:
            # make use of extra side space for labels
            fig = plt.figure(figsize=(11,7))    
            ax0 = plt.subplot2grid((5,5),(0,0), rowspan = 5, colspan=4)
            ax1 = plt.subplot2grid((5,5),(0,4), rowspan = 5, colspan=1, sharex=ax0)
        else:
            fig, ax0 = plt.subplots()
            ax1=None #dummy

        fig,ax2 = plt.subplots()
        fig,ax3 = plt.subplots()
        ls='-'

    lw=None

    # emissivity profiles
    ax0.plot(rhop, w, c='b', label='w', ls=ls, lw=lw)
    ax0.plot(rhop, z, c='r', label='z', ls=ls, lw=lw)
    ax0.plot(rhop, x, c='g', label='x', ls=ls, lw=lw)
    ax0.plot(rhop, y, c='m', label='y', ls=ls, lw=lw)
    ax0.set_xlabel(r'$\rho_p$')
    ax0.set_ylabel('IE emissivity [A.U.]')

    if plot_individual_contributions:
        ax0.plot(rhop, f_E_lam(w_lam) *n_Li*w_comps[0], ls='--', c='b', label='ionization')
        ax0.plot(rhop, f_E_lam(w_lam) *n_He*w_comps[1], ls=':', c='b', label='excitation', lw=lw)
        ax0.plot(rhop, f_E_lam(w_lam) *n_H*w_comps[2], ls='-.', c='b', label='recombination')
        
        ax0.plot(rhop, f_E_lam(z_lam) *n_Li*z_comps[0], ls='--', c='r', label='ionization')
        ax0.plot(rhop, f_E_lam(z_lam) *n_He*z_comps[1], ls=':', c='r', label='excitation', lw=lw)
        ax0.plot(rhop, f_E_lam(z_lam) *n_H*z_comps[2], ls='-.', c='r', label='recombination')
        
        ax0.plot(rhop, f_E_lam(x_lam) *n_He*x_comps[0], ls=':', c='g', label='excitation', lw=lw)
        ax0.plot(rhop, f_E_lam(x_lam) *n_H*x_comps[1], ls='-.', c='g', label='recombination')
        ax0.plot(rhop, f_E_lam(x_lam) *nz*x_comps[2], marker='*', c='g', label='z-prop')
        
        ax0.plot(rhop, f_E_lam(y_lam) *n_He*y_comps[0], ls=':', c='m', label='excitation', lw=lw)
        ax0.plot(rhop, f_E_lam(y_lam) *n_H*y_comps[1], ls='-.', c='m', label='recombination')
        ax0.plot(rhop, f_E_lam(y_lam) *nz * y_comps[2], marker='*', c='m', label='z-prop')
        

    if axs is None: # if axes were given, no need to re-plot labels
        if plot_individual_contributions:    
            
            # basic labels for each line
            ax1.plot([],[], c='b', label='w')
            ax1.plot([],[], c='r', label='z')
            ax1.plot([],[], c='g', label='x')
            ax1.plot([],[], c='m', label='y')

            # show labels for each contribution
            ax1.plot([],[], c='w', label=' ') #empty to separate colors and line styles
            ax1.plot([],[], ls='--', c='k', label='ionization', lw=lw)
            ax1.plot([],[], ls=':', c='k', label='excitation', lw=lw)
            ax1.plot([],[], ls='-.', c='k', label='recombination', lw=lw)
            ax1.plot([],[], marker='*', c='k', label='z cascade', lw=lw)
            
            leg = ax1.legend(loc='center left').set_draggable(True)
            ax1.axis('off')
        else:
            leg = ax0.legend().set_draggable(True)

    plt.tight_layout()

    # plot line ratios to w
    ax2.plot(rhop, w/w, label='w', ls=ls, c='b')
    ax2.plot(rhop, z/w, label='z', ls=ls, c='r')
    ax2.plot(rhop, x/w, label='x', ls=ls, c='g')
    ax2.plot(rhop, y/w, label='y', ls=ls, c='m')
    ax2.set_yscale('log')
    if axs is None: # if axes were given, no need to re-plot labels
        ax2.legend().set_draggable(True)
        ax2.set_xlabel(r'$\rho_p$')
        ax2.set_ylabel('Line ratios to w')
        
        # set good tick frequency on log-scale for comparison (a bit ad-hoc..)
        ax2.set_yticks([0.1,0.3, 1.0, 3.0, 6.0])
        ax2.get_yaxis().set_major_formatter(mpl.ticker.ScalarFormatter())
    plt.tight_layout()

    # plot atomic plasma diagnostics
    ax3.plot(rhop, R_ne, label=r'R($n_e$)', ls=ls, c='b')
    ax3.plot(rhop, G_Te, label=r'G($T_e$)', ls=ls, c='r')
    if axs is None:
        leg = ax3.legend().set_draggable(True)
        ax3.set_xlabel(r'$\rho_p$')

        # set good tick frequency on log-scale for comparison (a bit ad-hoc..)
        ax3.set_yticks([0.1,0.3, 1.0, 3.0, 6.0])
        ax3.get_yaxis().set_major_formatter(mpl.ticker.ScalarFormatter())

    plt.tight_layout()

    return [ax0,ax1], ax2, ax3
示例#8
0
文件: coords.py 项目: lda16/Aurora
def vol_average(quant,
                rhop,
                method='omfit',
                geqdsk=None,
                device=None,
                shot=None,
                time=None,
                return_geqdsk=False):
    '''Calculate the volume average of the given radially-dependent quantity on a rhop grid. 

    Args:
        quant : array, (space, ...)
            quantity that one wishes to volume-average. The first dimension must correspond to space,
            but other dimensions may be exist afterwards.
        rhop : array, (space,)
            Radial rhop coordinate in cm units. 
        method : {'omfit','fs'}
            Method to evaluate the volume average. The two options correspond to the way to compute
            volume averages via the OMFIT fluxSurfaces classes and via a simpler cumulative sum in r_V 
            coordinates. The methods only slightly differ in their results. Note that 'omfit' will fail if 
            rhop extends beyond the LCFS, while method 'fs' can estimate volume averages also into the SOL.
            Default is method='omfit'. 
        geqdsk : output of the omfit_eqdsk.OMFITgeqdsk class, postprocessing the EFIT geqdsk file
            containing the magnetic geometry. If this is left to None, the function internally tries to fetch
            it using MDS+ and omfit_eqdsk. In this case, device, shot and time to fetch the equilibrium 
            are required. 
        device : str
            Device name. Note that routines for this device must be implemented in omfit_eqdsk for this 
            to work. 
        shot : int
             Shot number of the above device, e.g. 1101014019 for C-Mod.
        time : float
             Time at which equilibrium should be fetched in units of ms. 
        return_geqdsk : bool
             If True, omfit_eqdsk dictionary is also returned

    Returns:
        quant_vol_avg : array, (space, ...)
            Volume average of the quantity given as an input, in the same units as in the input.
            If extrapolation beyond the range available from EFIT volume averages over a shorter section
            of the radial grid will be attempted. This does not affect volume averages within the LCFS. 
        geqdsk : dict
            Only returned if return_geqdsk=True. 
    '''
    if time is not None and np.mean(time) < 1e2:
        print(
            'Input time was likely in the wrong units! It should be in [ms]!')
    if np.max(rhop) > 1.0:
        print(
            "Input rhop goes beyond the LCFS! Results may not be meaningful (and can only be obtained via method=='fs')."
        )

    if geqdsk is None:
        # Fetch device geqdsk from MDS+ and post-process it using the OMFIT geqdsk format.
        try:
            import omfit_eqdsk
        except:
            raise ValueError(
                'Could not import omfit_eqdsk! Install with pip install omfit_eqdsk'
            )
        geqdsk = omfit_eqdsk.OMFITgeqdsk('').from_mdsplus(
            device=device,
            shot=shot,
            time=time,
            SNAPfile='EFIT01',
            fail_if_out_of_range=False,
            time_diff_warning_threshold=20)

    if method == 'fs':
        # obtain mapping between rhop and r_V coordinates
        rho_pol, r_V_ = grids_utils.get_rhopol_rV_mapping(geqdsk)

        # find r_V corresponding to input rhop (NB: extrapolation in the far SOL should be used carefully)
        r_V = interp1d(rho_pol, r_V_, bounds_error=False)(rhop)

        # use convenient volume averaging in r_V coordinates
        if np.any(np.isnan(r_V)):
            print(
                'Ignoring all nan points! These may derive from an attempted extrapolation or from nan inputs'
            )

        vol_avg = rV_vol_average(quant[~np.isnan(r_V)], r_V[~np.isnan(r_V)])

    elif method == 'omfit':
        # use fluxSurfaces classes from OMFIT
        rhopp = np.sqrt(geqdsk['fluxSurfaces']['geo']['psin'])
        quantp = interp1d(rhop,
                          quant,
                          bounds_error=False,
                          fill_value='extrapolate')(rhopp)
        vol_avg = geqdsk['fluxSurfaces'].volume_integral(quantp)

    else:
        raise ValueError(
            'Input method for volume average could not be recognized')

    if return_geqdsk:
        return vol_avg, geqdsk
    else:
        return vol_avg
示例#9
0
It is recommended to run this in IPython.
'''

import numpy as np
import matplotlib.pyplot as plt
plt.ion()
import omfit_eqdsk, omfit_gapy
import sys
from scipy.interpolate import interp1d
import aurora

# read in default Aurora namelist
namelist = aurora.default_nml.load_default_namelist()

# Use gfile and statefile in local directory:
geqdsk = omfit_eqdsk.OMFITgeqdsk(
    '/home/sciortino/Aurora/examples/example.gfile')
inputgacode = omfit_gapy.OMFITgacode(
    '/home/sciortino/Aurora/examples/example.input.gacode')

# save kinetic profiles on a rhop (sqrt of norm. pol. flux) grid
kp = namelist['kin_profs']
kp['Te']['rhop'] = kp['ne']['rhop'] = np.sqrt(inputgacode['polflux'] /
                                              inputgacode['polflux'][-1])
kp['ne']['vals'] = inputgacode['ne'] * 1e13  # 1e19 m^-3 --> cm^-3
kp['Te']['vals'] = inputgacode['Te'] * 1e3  # keV --> eV

# set impurity species and sources rate
imp = namelist['imp'] = 'Ar'
namelist['source_type'] = 'const'
namelist['source_rate'] = 2e20  # particles/s
示例#10
0
    def __init__(self, namelist, geqdsk=None, nbi_cxr=None):
        '''Setup aurora simulation input dictionary from the given namelist.

        Args:
            namelist : dict
                Dictionary containing aurora inputs. See default_nml.py for some defaults, 
                which users should modify for their runs.
            geqdsk : dict, optional
                EFIT gfile as returned after postprocessing by the :py:mod:`omfit_eqdsk` 
                package (OMFITgeqdsk class). If left to None (default), the geqdsk dictionary 
                is constructed starting from the gfile in the MDS+ tree.
            nbi_cxr : array, optional
                If namelist['nbi_cxr']=True, this array represents the charge exchange rates 
                with NBI neutrals, fast and/or thermal, across the entire radius and on the 
                time base of interest. 
                Creating this input is not trivial and must be done externally to aurora. 
                General steps:
                - get density of fast NBI neutrals (both fast and thermal/halo) ---> n0_nbi, n0_halo
                - get total rates (n-unresolved) for CX with NBI neutrals --> _alpha_CX_NBI_rates
                - thermal rates for the halo may be from ADAS CCD files or from the same methods used 
                for fast neutrals
                - sum n0_nbi *  alpha_CX_NBI_rates + n0_halo * alpha_CX_rates
                This method still needs more testing within this class. Please contact author for details. 

        '''
        self.namelist = namelist
        self.kin_profs = namelist['kin_profs']
        self.nbi_cxr = nbi_cxr
        self.imp = namelist['imp']

        if geqdsk is None:
            # Fetch geqdsk from MDS+ (using EFIT01) and post-process it using the OMFIT geqdsk format.
            self.geqdsk = omfit_eqdsk.OMFITgeqdsk('').from_mdsplus(
                device=namelist['device'],
                shot=namelist['shot'],
                time=namelist['time'],
                SNAPfile='EFIT01',
                fail_if_out_of_range=False,
                time_diff_warning_threshold=20)
        else:
            self.geqdsk = geqdsk

        # Get r_V to rho_pol mapping
        rho_pol, _rvol = grids_utils.get_rhopol_rvol_mapping(self.geqdsk)
        rvol_lcfs = interp1d(rho_pol, _rvol)(1.0)
        self.rvol_lcfs = self.namelist['rvol_lcfs'] = np.round(
            rvol_lcfs, 3)  # set limit on accuracy

        # create radial grid
        grid_params = grids_utils.create_radial_grid(self.namelist, plot=False)
        self.rvol_grid, self.pro_grid, self.qpr_grid, self.prox_param = grid_params

        # get rho_poloidal grid corresponding to aurora internal (rvol) grid
        self.rhop_grid = interp1d(_rvol, rho_pol)(self.rvol_grid)
        self.rhop_grid[0] = 0.0  # enforce on axis

        # Save R on LFS and HFS
        self.Rhfs, self.Rlfs = grids_utils.get_HFS_LFS(self.geqdsk,
                                                       rho_pol=self.rhop_grid)

        # define time grid ('timing' must be in namelist)
        self.time_grid, self.save_time = grids_utils.create_time_grid(
            timing=self.namelist['timing'], plot=False)
        self.time_out = self.time_grid[self.save_time]

        # get kinetic profiles on the radial and (internal) temporal grids
        self._ne, self._Te, self._Ti, self._n0 = self.get_aurora_kin_profs()

        # store also kinetic profiles on output time grid
        self.ne = self._ne[self.save_time, :]
        self.Te = self._Te[self.save_time, :]
        self.Ti = self._Ti[self.save_time, :]
        self.n0 = self._n0  # at present, n0 is assumed to be time-indpt

        # Get time-dependent parallel loss rate
        self.par_loss_rate = self.get_par_loss_rate()

        # Obtain atomic rates on the computational time and radial grids
        self.S_rates, self.R_rates = self.get_time_dept_atomic_rates()

        # create array of 0's of length equal to self.time_grid, with 1's where sawteeth must be triggered
        self.saw_on = np.zeros_like(self.time_grid)
        input_saw_times = self.namelist['saw_model']['times']
        self.saw_times = np.array(input_saw_times)[
            input_saw_times < self.time_grid[-1]]
        if self.namelist['saw_model']['saw_flag'] and len(self.saw_times) > 0:
            self.saw_on[self.time_grid.searchsorted(self.saw_times)] = 1

        # source function
        self.Raxis_cm = self.geqdsk['RMAXIS'] * 100.  # cm
        self.source_time_history = source_utils.get_source_time_history(
            self.namelist, self.Raxis_cm, self.time_grid)

        # get radial profile of source function for each time step
        self.source_rad_prof = source_utils.get_radial_source(
            self.namelist,
            self.rvol_grid,
            self.pro_grid,
            self.S_rates[:, 0, :],  # 0th charge state (neutral)
            self._Ti)

        # get maximum Z of impurity ion
        out = atomic_element(symbol=self.imp)
        spec = list(out.keys())[0]
        self.Z_imp = int(out[spec]['Z'])
        self.A_imp = int(out[spec]['A'])

        # Extract other inputs from namelist:
        self.mixing_radius = self.namelist['saw_model']['rmix']
        self.decay_length_boundary = self.namelist['SOL_decay']
        self.wall_recycling = self.namelist['wall_recycling']
        self.source_div_fraction = self.namelist[
            'divbls']  # change of nomenclature
        self.tau_div_SOL_ms = self.namelist['tau_div_SOL_ms']
        self.tau_pump_ms = self.namelist['tau_pump_ms']
        self.tau_rcl_ret_ms = self.namelist['tau_rcl_ret_ms']

        # if recycling flag is set to False, then prevent any divertor return flows
        # To include divertor return flows but no recycling, user should use wall_recycling=0
        if not self.namelist['recycling_flag']:
            self.wall_recycling = -1.0  # no divertor return flows

        self.bound_sep = self.namelist['bound_sep']
        self.lim_sep = self.namelist['lim_sep']
        self.sawtooth_erfc_width = self.namelist['saw_model'][
            'sawtooth_erfc_width']
        self.cxr_flag = self.namelist['cxr_flag']
        self.nbi_cxr_flag = self.namelist['nbi_cxr_flag']