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)
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)
## 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)
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']
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)
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
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
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
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
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']