Ejemplo n.º 1
0
    def get_par_loss_rate(self, trust_SOL_Ti=False):
        '''Calculate the parallel loss frequency on the radial and temporal grids [1/s].

        Parameters
        ----------
        trust_SOL_Ti : bool
            If True, the input Ti is trusted also in the SOL to calculate a parallel loss rate. 
            Often, Ti measurements in the SOL are unrealiable, so this parameter is set to False by default.

        Returns
        -------
        dv : array (space,time)
            Parallel loss rates in :math:`s^{-1}` units. 
            Values are zero in the core region and non-zero in the SOL. 
        
        '''
        # import here to avoid issues when building docs or package
        from omfit_classes.utils_math import atomic_element

        # background mass number (=2 for D)
        self.main_element = self.namelist['main_element']
        out = atomic_element(symbol=self.namelist['main_element'])
        spec = list(out.keys())[0]
        self.main_ion_A = int(out[spec]['A'])
        self.main_ion_Z = int(out[spec]['Z'])

        # factor for v = machnumber * sqrt((3T_i+T_e)k/m)
        vpf = self.namelist['SOL_mach'] * np.sqrt(
            q_electron / m_p / self.main_ion_A)
        # v[m/s]=vpf*sqrt(T[ev])

        # number of points inside of LCFS
        ids = self.rvol_grid.searchsorted(self.namelist['rvol_lcfs'],
                                          side='left')
        idl = self.rvol_grid.searchsorted(self.namelist['rvol_lcfs'] +
                                          self.namelist['lim_sep'],
                                          side='left')

        # Calculate parallel loss frequency using different connection lengths in the SOL and in the limiter shadow
        dv = np.zeros_like(self._Te.T)  # space x time

        # Ti may not be reliable in SOL, replace it by Te
        Ti = self._Ti if trust_SOL_Ti else self._Te

        # open SOL
        dv[ids:idl] = vpf * np.sqrt(3. * Ti.T[ids:idl] + self._Te.T[ids:idl]
                                    ) / self.namelist['clen_divertor']

        # limiter shadow
        dv[idl:] = vpf * np.sqrt(
            3. * Ti.T[idl:] + self._Te.T[idl:]) / self.namelist['clen_limiter']

        dv, _ = np.broadcast_arrays(dv, self.time_grid[None])

        return np.asfortranarray(dv)
Ejemplo n.º 2
0
def get_radial_source(namelist, rvol_grid, pro_grid, S_rates, Ti_eV=None):
    """Obtain spatial dependence of source function.

    If namelist['source_width_in']==0 and namelist['source_width_out']==0, the source
    radial profile is defined as an exponential decay due to ionization of neutrals. This requires
    S_rates, the ionization rate of neutral impurities, to be given with S_rates.shape=(len(rvol_grid),len(time_grid))

    If namelist['imp_source_energy_eV']<0, the neutrals speed is taken as the thermal speed based
    on Ti_eV, otherwise the value corresponding to namelist['imp_source_energy_eV'] is used.

    Parameters
    ----------
    namelist : dict
        Aurora namelist. Only elements referring to the spatial distribution and energy of 
        source atoms are accessed. 
    rvol_grid : array (nr,)
        Radial grid in volume-normalized coordinates [cm]
    pro_grid : array (nr,)
        Normalized first derivatives of the radial grid in volume-normalized coordinates. 
    S_rates : array (nr,nt)
        Ionization rate of neutral impurity over space and time.
    Ti_eV : array, optional (nt,nr)
        Background ion temperature, only used if source_width_in=source_width_out=0.0 and 
        imp_source_energy_eV<=0, in which case the source impurity neutrals are taken to 
        have energy equal to the local Ti [eV]. 

    Returns
    -------
    source_rad_prof : array (nr,nt)
        Radial profile of the impurity neutral source for each time step.
    """
    r_src = namelist["rvol_lcfs"] + namelist["source_cm_out_lcfs"]
    nt = S_rates.shape[1]
    try:
        # TODO: invert order of dimensions of Ti_eV...
        assert S_rates.shape == Ti_eV.T.shape
    except AssertionError as msg:
        raise AssertionError(msg)

    source_rad_prof = np.zeros_like(S_rates)

    # find index of radial grid vector that is just greater than r_src
    i_src = rvol_grid.searchsorted(r_src) - 1
    # set source to be inside of the wall
    i_src = min(i_src, len(rvol_grid) - 1)

    if namelist["source_width_in"] < 0.0 and namelist["source_width_out"] < 0.0:
        # point source
        source_rad_prof[i_src] = 1.0

    # source with FWHM=source_width_in inside and FWHM=source_width_out outside
    if namelist["source_width_in"] > 0.0 or namelist["source_width_out"] > 0.0:

        if namelist["source_width_in"] > 0.0:
            ff = np.log(2.0) / namelist["source_width_in"]**2
            source_rad_prof[:i_src] = np.exp(
                -((rvol_grid[:i_src] - rvol_grid[i_src])**2) * ff)[:, None]

        if namelist["source_width_out"] > 0.0:
            ff = np.log(2.0) / namelist["source_width_out"]**2
            source_rad_prof[i_src:] = np.exp(
                -((rvol_grid[i_src:] - rvol_grid[i_src])**2) * ff)[:, None]

    # decay of neutral density with exp(-Int( [ne*S+dv/dr]/v ))
    if namelist["source_width_in"] == 0 and namelist["source_width_out"] == 0:
        # neutrals energy
        if namelist["imp_source_energy_eV"] > 0:
            E0 = namelist["imp_source_energy_eV"] * np.ones_like(rvol_grid)
        else:
            if Ti_eV is not None:
                E0 = copy.deepcopy(Ti_eV)
            else:
                raise ValueError(
                    "Could not compute a valid energy of injected ions!")

        # import here to avoid issues with omfit_commonclasses during docs and package creation
        from omfit_classes.utils_math import atomic_element

        # velocity of neutrals [cm/s]
        out = atomic_element(symbol=namelist["imp"])
        spec = list(out.keys())[0]
        imp_ion_A = int(out[spec]["A"])
        v = -np.sqrt(2.0 * q_electron * E0 / (imp_ion_A * m_p)) * 100  # cm/s

        # integration of ne*S for atoms and calculation of ionization length for normalizing neutral density
        source_rad_prof[i_src] = (-0.0625 * S_rates[i_src] / pro_grid[i_src] /
                                  v[i_src])  # 1/16
        for i in np.arange(i_src - 1, 0, -1):
            source_rad_prof[i] = source_rad_prof[
                i + 1] + 0.25 * (S_rates[i + 1] / pro_grid[i + 1] / v[i + 1] +
                                 S_rates[i] / pro_grid[i] / v[i])

        # prevents FloatingPointError: underflow encountered
        source_rad_prof[1:i_src] = np.exp(
            np.maximum(source_rad_prof[1:i_src], -100))

        # calculate relative density of neutrals
        source_rad_prof[1:i_src] *= (rvol_grid[i_src] * v[i_src] /
                                     rvol_grid[1:i_src] / v[1:i_src])[:, None]
        source_rad_prof[i_src] = 1.0

        # remove promptly redeposited ions
        if namelist["prompt_redep_flag"]:
            omega_c = 1.602e-19 / 1.601e-27 * namelist["Baxis"] / namelist[
                "main_ion_A"]
            dt = (rvol_grid[i_src] - rvol_grid) / v
            pp = dt * omega_c
            non_redep = pp**2 / (1.0 + pp**2)
            source_rad_prof *= non_redep

    # total ion source
    pnorm = np.pi * np.sum(source_rad_prof * S_rates *
                           (rvol_grid / pro_grid)[:, None],
                           0)  # sum over radius

    # neutral density for influx/unit-length = 1/cm
    source_rad_prof /= pnorm

    # broadcast in right shape if time averaged profiles are used
    source_rad_prof = np.broadcast_to(source_rad_prof,
                                      (source_rad_prof.shape[0], nt))

    return source_rad_prof
Ejemplo n.º 3
0
    def __init__(self, namelist, geqdsk=None):

        if namelist is None:
            # option useful for calls like omfit_classes.OMFITaurora(filename)
            # A call like omfit_classes.OMFITaurora('test', namelist, geqdsk=geqdsk) is also possible
            # to initialize the class as a dictionary.
            return

        # make sure that any changes in namelist will not propagate back to the calling function
        self.namelist = deepcopy(namelist)
        self.kin_profs = self.namelist['kin_profs']
        self.imp = namelist['imp']

        # import here to avoid issues when building docs or package
        from omfit_classes.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'])

        self.reload_namelist()

        if geqdsk is None:
            # import omfit_eqdsk here to avoid issues with docs and packaging
            from omfit_classes import omfit_eqdsk
            # 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
        self.namelist['Baxis'] = self.geqdsk['BCENTR']

        # specify which atomic data files should be used -- use defaults unless user specified in namelist
        atom_files = {}
        atom_files['acd'] = self.namelist.get(
            'acd',
            adas_files.adas_files_dict()[self.imp]['acd'])
        atom_files['scd'] = self.namelist.get(
            'scd',
            adas_files.adas_files_dict()[self.imp]['scd'])
        if self.namelist['cxr_flag']:
            atom_files['ccd'] = self.namelist.get(
                'ccd',
                adas_files.adas_files_dict()[self.imp]['ccd'])

        # now load ionization and recombination rates
        self.atom_data = atomic.get_atom_data(self.imp, files=atom_files)

        # allow for ion superstaging
        self.superstages = self.namelist.get('superstages', [])

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

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