Example #1
0
    def addIon(self,
               name,
               Z,
               iontype=IONS_PRESCRIBED,
               Z0=None,
               isotope=0,
               SPIMolarFraction=-1,
               opacity_mode=ION_OPACITY_MODE_TRANSPARENT,
               T=None,
               n=None,
               r=None,
               t=None,
               tritium=False):
        """
        Adds a new ion species to the plasma.

        :param str name:        Name by which the ion species will be referred to.
        :param int Z:           Ion charge number.
        :param int isotope:            Ion mass number.
        :param int iontype:     Method to use for evolving ions in time.
        :param int Z0:          Charge state to populate (used for populating exactly one charge state for the ion).
        :param n:               Ion density (can be either a scalar, 1D array or 2D array, depending on the other input parameters)
        :param float SPIMolarFraction: Molar fraction of the SPI injection (if any). A negative value means that this species is not part of the SPI injection 
        :param numpy.ndarray r: Radial grid on which the input density is defined.
        :param T:               Ion initial temperature (can be scalar for uniform temperature, otherwise 1D array matching `r` in size)
        :param numpy.ndarray r: Radial grid on which the input density and temperature is defined.
        :param numpy.ndarray t: Time grid on which the input density is defined.
        :param bool tritium:    If ``True``, the ion species is treated as Tritium.
        """
        if (self.r is not None) and (r is not None) and (np.any(self.r != r)):
            raise EquationException(
                "The radial grid must be the same for all ion species.")
        if (self.t is not None) and (t is not None) and (np.any(self.t != t)):
            raise EquationException(
                "The time grid must be the same for all ion species.")

        if T is not None:
            self.typeTi = IONS_T_I_INCLUDE

        ion = IonSpecies(settings=self.settings,
                         name=name,
                         Z=Z,
                         ttype=iontype,
                         Z0=Z0,
                         isotope=isotope,
                         SPIMolarFraction=SPIMolarFraction,
                         opacity_mode=opacity_mode,
                         T=T,
                         n=n,
                         r=r,
                         t=t,
                         interpr=self.r,
                         interpt=None,
                         tritium=tritium)

        self.ions.append(ion)

        self.r = ion.getR()
        if ion.getTime() is not None:
            self.t = ion.getTime()
Example #2
0
    def setCustomGridPoints(self, xi_f):
        """
        Set an arbitrary custom grid point distribution
        on the pitch flux grid (i.e. the locations of
        the cell edges). This overrides the grid resolution
        'nxi', which will be taken as the number of cells
        described by the prescribed grid points.

        :param float xi_f: List of pitch flux grid points
        """
        self.type = TYPE_CUSTOM
        if type(xi_f) == list:
            xi_f = np.array(xi_f)
        if np.size(xi_f) < 2:
            raise EquationException(
                "XiGrid: Custom grid point vector 'xi_f' must have size 2 or greater."
            )
        for i in range(np.size(xi_f) - 1):
            if not xi_f[i + 1] > xi_f[i]:
                raise EquationException(
                    "XiGrid: Custom grid points 'xi_f' must be an array of increasing numbers."
                )
        if np.min(xi_f) != -1 or np.max(xi_f) != 1:
            raise EquationException(
                "XiGrid: Custom pitch grid must span [-1,1].")
        self.xi_f = xi_f
        if self.nxi != 0:
            print(
                "*WARNING* XiGrid: Prescibing custom pitch grid overrides 'nxi'."
            )
        self.nxi = np.size(self.xi_f) - 1
Example #3
0
    def verifyInitialDistribution(self):
        """
        Verifies that the initial distribution function has
        been set correctly and consistently.
        """
        if self.init is None:
            raise EquationException("{}: No initial distribution function specified.".format(self.name))

        nr = self.init['r'].size
        p1, p2 = None, None
        p1name, p2name = None, None
        np1, np2 = 0, 0

        if self.init['p'].size > 0 and self.init['xi'].size > 0:
            p1name = 'p'
            p2name = 'xi'
        elif self.init['ppar'].size > 0 and self.init['pperp'].size > 0:
            p1name = 'ppar'
            p2name = 'pperp'
        else:
            raise EquationException("{}: No momentum grid given for initial value.".format(self.name))

        p1 = self.init[p1name]
        p2 = self.init[p2name]

        if len(p1.shape) != 1:
            raise EquationException("{}: Invalid dimensions of momentum grid '{}'. Must be 1D array.".format(self.name, p1name))
        elif len(p2.shape) != 1:
            raise EquationException("{}: Invalid dimensions of momentum grid '{}'. Must be 1D array.".format(self.name, p2name))

        np1 = p1.size
        np2 = p2.size

        if self.init['x'].shape != (nr, np2, np1):
            raise EquationException("{}: Invalid size of initial distribution function: {}. Expected: {}.".format(self.name, self.init['x'].shape, (nr, np2, np1)))
Example #4
0
 def verifySettingsPrescribedData(self):
     """
     Verify that the prescribed has a valid format.
     """
     if len(self.density.shape) != 2:
         raise EquationException("n_cold: Invalid number of dimensions in prescribed data. Expected 2 dimensions (time x radius).")
     elif len(self.times.shape) != 1:
         raise EquationException("n_cold: Invalid number of dimensions in time grid of prescribed data. Expected one dimension.")
     elif len(self.radius.shape) != 1:
         raise EquationException("n_cold: Invalid number of dimensions in radial grid of prescribed data. Expected one dimension.")
     elif self.density.shape[0] != self.times.size or self.density.shape[1] != self.radius.size:
         raise EquationException("n_cold: Invalid dimensions of prescribed data: {}x{}. Expected {}x{} (time x radius)."
             .format(self.density.shape[0], self.density.shape[1], self.times.size, self.radius.size))
Example #5
0
    def initialize_dynamic_charge_state(self,
                                        Z0,
                                        n=None,
                                        r=None,
                                        interpr=None):
        """
        Evolve the ions dynamically, initializing them all to reside in the specified charge state Z0.
        """
        if Z0 > self.Z or Z0 < 0:
            raise EquationException(
                "ion_species: '{}': Invalid charge state specified: {}. Ion has charge Z = {}."
                .format(self.name, Z0, self.Z))

        if n is None:
            raise EquationException(
                "ion_species: '{}': Input density must not be 'None'.".format(
                    self.name))

        # Convert lists to NumPy arrays
        if type(n) == list:
            n = np.array(n)

        # Scalar (assume density constant in spacetime)
        if type(n) == float or np.isscalar(n) or (type(n) == np.ndarray
                                                  and n.size == 1):
            r = interpr if interpr is not None else np.array([0])
            N = np.zeros((self.Z + 1, r.size))
            N[Z0, :] = n

            self.initialize_dynamic(n=N, r=r)
            return

        if r is None:
            raise EquationException(
                "ion_species: '{}': Non-scalar density prescribed, but no radial coordinates given."
                .format(self.name))

        # Radial profile
        if len(n.shape) == 1:
            if r.size != n.size:
                raise EquationException(
                    "ion_species: '{}': Invalid dimensions of prescribed density: {}. Expected {}."
                    .format(self.name, n.shape[0], r.size))

            N = np.zeros((self.Z + 1, r.size))
            N[Z0, :] = n
            self.initialize_dynamic(n=N, r=r)
        else:
            raise EquationException(
                "ion_species: '{}': Unrecognized shape of prescribed density: {}."
                .format(self.name, n.shape))
Example #6
0
    def getIon(self, i=None):
        """
        Returns the ion species with the specified index or name.

        :param i: Index or name of ion species to retrieve.
        """
        if type(i) == int: return self.ions[i]
        elif type(i) == str:
            for j in range(0, len(self.ions)):
                if self.ions[j].getName() == i:
                    return self.ions[j]

            raise EquationException(
                "No ion with name '{}' has been defined.".format(i))
        else:
            raise EquationException("Invalid call to 'getIon()'.")
Example #7
0
    def verifyInitialProfiles(self):
        """
        Verifies that the initial density and temperature profiles
        are set correctly.
        """
        if (self.n0 is None) or (self.T0 is None):
            raise EquationException("{}: No initial density and/or temperature profiles specified.".format(self.name))
        if (self.rn0 is None) or (self.rT0 is None):
            raise EquationException("{}: No radial grids specified for the density and/or temperature profiles.".format(self.name))

        if (self.n0.ndim != 1) or (self.rn0.ndim != 1) or (self.n0.size != self.rn0.size):
            raise EquationException("{}: Invalid number of elements of density profile: {}. Corresponding radial grid has {} elements."
                .format(self.name, self.n0.size, self.rn0.size))
        if (self.T0.ndim != 1) or (self.rT0.ndim != 1) or (self.T0.size != self.rT0.size):
            raise EquationException("{}: Invalid number of elements of temperature profile: {}. Corresponding radial grid has {} elements."
                .format(self.name, self.T0.size, self.rT0.size))
Example #8
0
    def initialize_equilibrium(self, n=None, r=None, interpr=None):
        """
        Evolve ions according to the equilibrium equation in DREAM.
        """
        self.ttype = IONS_EQUILIBRIUM

        if n is None:
            raise EquationException(
                "ion_species: '{}': Input density must not be 'None'.".format(
                    self.name))

        # Convert lists to NumPy arrays
        if type(n) == list:
            n = np.array(n)

        # Scalar (assume density constant in radius)
        if type(n) == float or (type(n) == np.ndarray and n.size == 1):
            r = interpr if interpr is not None else np.array([0])
            N = np.zeros((self.Z + 1, r.size))

            # For the equilibrium, it doesn't matter which charge state we
            # put the particles in. They will be placed in the correct state
            # after the first time step (=> make all particles fully ionized
            # so that nfree > 0)
            N[self.Z, :] = n
            n = N
        elif r is None:
            raise EquationException(
                "ion_species: '{}': Non-scalar initial ion density prescribed, but no radial coordinates given."
                .format(self.name))

        # Radial profiles for all charge states
        if len(n.shape) == 2:
            if self.Z + 1 != n.shape[0] or r.size != n.shape[1]:
                raise EquationException(
                    "ion_species: '{}': Invalid dimensions of initial ion density: {}x{}. Expected {}x{}."
                    .format(self.name, n.shape[0], n.shape[1], self.Z + 1,
                            r.size))

            self.t = None
            self.r = r
            self.n = n
        else:
            raise EquationException(
                "ion_species: '{}': Unrecognized shape of initial density: {}."
                .format(self.name, n.shape))
Example #9
0
    def verifySettings(self):
        """
        Verify that the settings of this unknown are correctly set.
        """
        if self.type == TYPE_PRESCRIBED:
            if type(self.density) != np.ndarray:
                raise EquationException("n_cold: Density prescribed, but no density data provided.")
            elif type(self.times) != np.ndarray:
                raise EquationException("n_cold: Density prescribed, but no time data provided, or provided in an invalid format.")
            elif type(self.radius) != np.ndarray:
                raise EquationException("n_cold: Density prescribed, but no radial data provided, or provided in an invalid format.")

            self.verifySettingsPrescribedData()
        elif self.type == TYPE_SELFCONSISTENT:
            # Nothing todo
            pass
        else:
            raise EquationException("n_cold: Unrecognized equation type specified: {}.".format(self.type))
Example #10
0
    def initialize_prescribed(self, n=None, r=None, t=None):
        """
        Prescribes the evolution for this ion species.
        """
        self.ttype = IONS_PRESCRIBED
        if n is None:
            raise EquationException(
                "ion_species: '{}': Input density must not be 'None'.".format(
                    self.name))

        # Convert lists to NumPy arrays
        if type(n) == list:
            n = np.array(n)

        # Scalar (assume density constant in spacetime)
        #if type(n) == float or (type(n) == np.ndarray and n.size == 1):
        if np.isscalar(n):
            self.t = np.array([0])
            self.r = np.array([0, 1])
            self.n = np.ones((self.Z + 1, 1, 2)) * n
            return
        if r is None:
            raise EquationException(
                "ion_species: '{}': Non-scalar density prescribed, but no radial coordinates given."
                .format(self.name))

        # Radial profile (assume fully ionized)
        if len(n.shape) == 1:
            raise EquationException(
                "ion_species: '{}': Prescribed density data has only one dimension."
                .format(self.name))
        # Radial profiles of charge states
        elif len(n.shape) == 2:
            raise EquationException(
                "ion_species: '{}': Prescribed density data has only two dimensions."
                .format(self.name))
        # Full time evolution of radial profiles of charge states
        elif len(n.shape) == 3:
            if t is None:
                raise EquationException(
                    "ion_species: '{}': 3D ion density prescribed, but no time coordinates given."
                    .format(self.name))

            if self.Z + 1 != n.shape[0] or t.size != n.shape[
                    1] or r.size != n.shape[2]:
                raise EquationException(
                    "ion_species: '{}': Invalid dimensions of prescribed density: {}x{}x{}. Expected {}x{}x{}"
                    .format(self.name, n.shape[0], n.shape[1], n.shape[2],
                            self.Z + 1, t.size, r.size))
            self.t = t
            self.r = r
            self.n = n
        else:
            raise EquationException(
                "ion_species: '{}': Unrecognized shape of prescribed density: {}."
                .format(self.name, n.shape))
Example #11
0
    def initialize_dynamic(self, n=None, r=None):
        """
        Evolve ions according to the ion rate equation in DREAM.
        """
        self.ttype = IONS_DYNAMIC

        if n is None:
            raise EquationException(
                "ion_species: '{}': Input density must not be 'None'.".format(
                    self.name))

        # Convert lists to NumPy arrays
        if type(n) == list:
            n = np.array(n)

        # Scalar (assume density constant in spacetime)
        if type(n) == float or (type(n) == np.ndarray and n.size == 1):
            raise EquationException(
                "ion_species: '{}': Initial density must be two dimensional (charge states x radius)."
                .format(self.name))

        if r is None:
            raise EquationException(
                "ion_species: '{}': Non-scalar initial ion density prescribed, but no radial coordinates given."
                .format(self.name))

        # Radial profiles for all charge states
        if len(n.shape) == 2:
            if self.Z + 1 != n.shape[0] or r.size != n.shape[1]:
                raise EquationException(
                    "ion_species: '{}': Invalid dimensions of initial ion density: {}x{}. Expected {}x{}."
                    .format(self.name, n.shape[0], n.shape[1], self.Z + 1,
                            r.size))

            self.t = None
            self.r = r
            self.n = n
        else:
            raise EquationException(
                "ion_species: '{}': Unrecognized shape of initial density: {}."
                .format(n.shape).format(self.name))
Example #12
0
    def verifySettings(self):
        """
        Verify that all settings are consistent.
        """
        # Make sure there are no double names
        for i in range(0, len(self.ions)):
            for j in range(0, len(self.ions)):
                if i == j: continue

                if self.ions[i].getName() == self.ions[j].getName():
                    raise EquationException(
                        "ions: More than one ion species is named '{}'.".
                        format(self.ions[i].getName()))

            self.ions[i].verifySettings()

        if (self.ionization != IONIZATION_MODE_FLUID) and (
                self.ionization != IONIZATION_MODE_KINETIC) and (
                    self.ionization != IONIZATION_MODE_KINETIC_APPROX_JAC):
            raise EquationException(
                "ions: Invalid ionization mode: {}.".format(self.ionization))
Example #13
0
    def setType(self, ttype):
        """
        Sets the type of equation to use for evolving the cold electron density.

        :param int ttype: Flag indicating how to evolve the cold electron density.
        """
        if ttype == TYPE_PRESCRIBED:
            self.type = ttype
        elif ttype == TYPE_SELFCONSISTENT:
            self.type = ttype
        else:
            raise EquationException("n_cold: Unrecognized cold electron density type: {}".format(self.type))
Example #14
0
    def verifySettings(self):
        """
        Verify that the settings of this ion species are correctly set.
        """
        if self.Z < 1:
            raise EquationException(
                "ion_species: '{}': Invalid atomic charge: {}.".format(self.Z))

        if self.ttype == IONS_PRESCRIBED:
            if self.t.ndim != 1:
                raise EquationException(
                    "ion_species: '{}': The time vector must be 1D.".format(
                        self.name))
            elif self.r.ndim != 1:
                raise EquationException(
                    "ion_species: '{}': The time vector must be 1D.".format(
                        self.name))
            elif self.n is None or (self.n.shape !=
                                    (self.Z + 1, self.t.size, self.r.size)):
                raise EquationException(
                    "ion_species: '{}': Invalid dimensions for input density: {}x{}x{}. Expected {}x{}x{}."
                    .format(self.name, self.n.shape[0], self.n.shape[1],
                            self.n.shape[2], self.Z + 1, self.t.size,
                            self.r.size))
        elif self.ttype == IONS_EQUILIBRIUM or self.ttype == IONS_DYNAMIC:
            if (self.r is None) or (self.r.ndim != 1):
                raise EquationException(
                    "ion_species: '{}': The time vector must be 1D.".format(
                        self.name))
            elif (self.n is None) or (self.n.shape !=
                                      (self.Z + 1, self.r.size)):
                raise EquationException(
                    "ion_species: '{}': Invalid dimensions for input density: {}x{}. Expected {}x{}."
                    .format(self.name, self.n.shape[0], self.n.shape[1],
                            self.Z + 1, self.r.size))
Example #15
0
    def setInitialValue(self, f, r, p=None, xi=None, ppar=None, pperp=None):
        """
        Set the initial value of this electron distribution function. Only one
        of the pairs (p, xi) and (ppar, pperp) of momentum grids need to be
        given.

        :param f:     Array representing the distribution function value on the grid (must have size (nr, nxi, np) or (nr, npperp, nppar))
        :param r:     Radial grid on which the initial distribution is given.
        :param p:     Momentum grid.
        :param xi:    Pitch grid.
        :param ppar:  Parallel momentum grid.
        :param pperp: Perpendicular momentum grid.
        """
        self.init = {}

        def conv(v):
            if type(v) == list:
                return np.array(v)
            elif type(v) == float or type(v) == int:
                return np.array([float(v)])
            else:
                return v

        ff = conv(f)
        self.init['r'] = conv(r)

        if p is not None and xi is not None:
            self.init['p'] = conv(p)
            self.init['xi'] = conv(xi)
            self.init['ppar'] = np.array([])
            self.init['pperp'] = np.array([])

            if ff.size == 1:
                ff = ff * np.ones((self.init['r'].size, self.init['xi'].size, self.init['p'].size))
        elif ppar is not None and pperp is not None:
            self.init['ppar'] = conv(ppar)
            self.init['pperp'] = conv(pperp)
            self.init['p'] = np.array([])
            self.init['xi'] = np.array([])

            if ff.size == 1:
                ff = ff * np.ones((self.init['r'].size, self.init['pperp'].size, self.init['ppar'].size))
        else:
            raise EquationException("{}: No momentum grid given for initial value.".format(self.name))

        self.init['x'] = ff

        # Reset initial profiles (if any)
        self.rn0 = self.rT0 = None
        self.n0 = self.T0 = None

        self.verifyInitialDistribution()
Example #16
0
    def setCustomGridPoints(self, p_f):
        """
        Set an arbitrary custom grid point distribution
        on the momentum flux grid (i.e. the locations of
        the cell edges). This overrides the grid resolution
        'np' which will be taken as the number of cells
        described by the prescribed grid points, and 'pmax'
        which will be taken as the largest element in p_f

        :param float p_f: List of momentum flux grid points
        """
        self.type = TYPE_CUSTOM

        if type(p_f) == list:
            p_f = np.array(p_f)
        if np.size(p_f) < 2:
            raise EquationException(
                "PGrid: Custom grid point vector 'p_f' must have size 2 or greater."
            )

        for i in range(np.size(p_f) - 1):
            if not p_f[i + 1] > p_f[i]:
                raise EquationException(
                    "PGrid: Custom grid points 'p_f' must be an array of increasing numbers."
                )

        self.p_f = p_f
        if self.np != 0:
            print(
                "*WARNING* PGrid: Prescibing custom momentum grid overrides 'np'."
            )
        self.np = np.size(self.p_f) - 1

        if self.pmax is not None:
            print(
                "*WARNING* PGrid: Prescibing custom momentum grid overrides 'pmax'."
            )
        self.pmax = float(p_f[-1])
Example #17
0
    def setInitialProfiles(self, n0, T0, rn0=None, rT0=None):
        """
        Sets the initial density and temperature profiles of the electron
        population.

        :param rn0: Radial grid on which the density is given.
        :param n0:  Electron density profile.
        :param rT0: Radial grid on which the temperature is given.
        :param T0:  Electron temperature profile.
        """
        if rn0 is not None:
            self.rn0 = np.asarray(rn0)
        else:
            if not np.isscalar(n0):
                raise EquationException("{}: Non-scalar initial density profile given, but no radial grid specified.".format(self.name))
            self.rn0 = np.array([0])

        if rT0 is not None:
            self.rT0 = np.asarray(rT0)
        else:
            if not np.isscalar(T0):
                raise EquationException("{}: Non-scalar initial temperature profile given, but no radial grid specified.".format(self.name))
            self.rT0 = np.array([0])

        self.n0  = np.asarray(n0)
        self.T0  = np.asarray(T0)

        if self.rn0.ndim == 0: self.rn0 = np.asarray([self.rn0])
        if self.n0.ndim == 0:  self.n0 = np.asarray([self.n0])
        if self.rT0.ndim == 0: self.rT0 = np.asarray([self.rT0])
        if self.T0.ndim == 0:  self.T0 = np.asarray([self.T0])

        # Reset numerically provided distribution (if any)
        self.init = None

        self.verifyInitialProfiles()
Example #18
0
    def fromdict(self, data):
        """
        Load data for this object from the given dictionary.

        :param dict data: Dictionary to load distribution function from.
        """
        def scal(v):
            if type(v) == np.ndarray: return v[0]
            else: return v

        if 'mode' in data:
            self.mode = data['mode']
        if 'boundarycondition' in data:
            self.boundarycondition = data['boundarycondition']
        if 'adv_interp' in data:
            self.adv_interp_r = data['adv_interp']['r']
            self.adv_interp_p1 = data['adv_interp']['p1']
            self.adv_interp_p2 = data['adv_interp']['p2']
            self.fluxlimiterdamping = data['adv_interp']['fluxlimiterdamping']
        if 'adv_jac_mode' in data:
            self.adv_jac_r = data['adv_jac_mode']['r']
            self.adv_jac_p1 = data['adv_jac_mode']['p1']
            self.adv_jac_p2 = data['adv_jac_mode']['p2']
        if 'init' in data:
            self.init = data['init']
        elif ('n0' in data) and ('T0' in data):
            self.rn0 = data['n0']['r']
            self.n0  = data['n0']['x']
            self.rT0 = data['T0']['r']
            self.T0  = data['T0']['x']
        elif self.grid.enabled:
            raise EquationException("{}: Unrecognized specification of initial distribution function.".format(self.name))

        if 'ripplemode' in data:
            self.ripplemode = int(scal(data['ripplemode']))

        if 'synchrotronmode' in data:
            self.synchrotronmode = data['synchrotronmode']
            if type(self.synchrotronmode) != int:
                self.synchrotronmode = int(self.synchrotronmode[0])

        if 'transport' in data:
            self.transport.fromdict(data['transport'])

        if 'fullIonJacobian' in data:
            self.fullIonJacobian = bool(data['fullIonJacobian'])

        self.verifySettings()
Example #19
0
    def fromdict(self, data):
        """
        Set all options from a dictionary.

        :param dict data: List of settings to load.
        """
        self.type = data['type']

        if self.type == TYPE_PRESCRIBED:
            self.density = data['x']
            self.radius  = data['r']
            self.times   = data['t']
        elif self.type == TYPE_SELFCONSISTENT:
            pass
        else:
            raise EquationException("n_cold: Unrecognized cold electron density type: {}".format(self.type))

        self.verifySettings()
Example #20
0
    def todict(self):
        """
        Returns a Python dictionary containing all settings of
        this ColdElectrons object.
        """
        data = { 'type': self.type }

        if self.type == TYPE_PRESCRIBED:
            data['data'] = {
                'x': self.density,
                'r': self.radius,
                't': self.times
            }
        elif self.type == TYPE_SELFCONSISTENT:
            pass
        else:
            raise EquationException("n_cold: Unrecognized cold electron density type: {}".format(self.type))

        return data
Example #21
0
 def setTemperature(self, T):
     """
     Sets the ion temperature from an input value `T`. 
     For scalar T, sets a uniform radial profile,
     otherwise requires the T profile to be given on the 
     `r` grid which is provided to the IonSpecies constructor.
     """
     if type(T) == list:
         T = np.array(T)
     if T is None:
         T = np.zeros((1, np.size(self.r)))
     elif np.isscalar(T):
         T = np.ones((1, np.size(self.r))) * T
     elif np.ndim(T) == 1:
         T = T[None, :]
     elif T.shape[1] != np.size(self.r):
         raise EquationException(
             "ion_species: '{}': Invalid dimensions of initial ion temperature T: {}x{}. Expected {}x{}."
             .format(self.name, T.shape[0], T.shape[1], 1, np.size(self.r)))
     return T
Example #22
0
    def __init__(self,
                 settings,
                 name,
                 Z,
                 ttype=0,
                 Z0=None,
                 isotope=0,
                 SPIMolarFraction=-1.0,
                 opacity_mode=ION_OPACITY_MODE_TRANSPARENT,
                 T=None,
                 n=None,
                 r=None,
                 t=None,
                 interpr=None,
                 interpt=None,
                 tritium=False):
        """
        Constructor.

        :param DREAMSettings settings: Parent DREAMSettings object.
        :param str name:               Name by which the ion species will be referred to.
        :param int Z:                  Ion charge number.
        :param int isotope:            Ion mass number.
        :param int ttype:              Method to use for evolving ions in time.
        :param int Z0:                 Charge state to populate with given density.
        :param float n:                Ion density (can be either a scalar, 1D array or 2D array, depending on the other input parameters)
        :param float SPIMolarFraction: Molar fraction of the SPI injection (if any). A negative value means that this species is not part of the SPI injection 
        :param T:                      Ion initial temperature (can be scalar for uniform temperature, otherwise 1D array matching `r` in size)
        :param numpy.ndarray r:        Radial grid on which the input density is defined.
        :param numpy.ndarray t:        Time grid on which the input density is defined.
        :param numpy.ndarray interpr:  Radial grid onto which ion densities should be interpolated.
        :param numpy.ndarray interpt:  Time grid onto which ion densities should be interpolated.
        :param bool tritium:           If ``True``, this ion species is treated as Tritium.
        """
        if ';' in name:
            raise EquationException(
                "ion_species: '{}': Invalid character found in ion name: '{}'."
                .format(name, ';'))

        self.settings = settings
        self.name = name
        self.Z = int(Z)
        self.isotope = int(isotope)
        self.ttype = None
        self.tritium = tritium
        self.opacity_mode = opacity_mode

        self.setSPIMolarFraction(SPIMolarFraction)

        # Emit warning if 'T' is used as name but 'tritium = False',
        # as this may indicate a user error
        if name == 'T' and tritium == False:
            print(
                "WARNING: Ion species with name 'T' added, but 'tritium = False'."
            )
        self.n = None
        self.r = None
        self.t = None
        if ttype == IONS_PRESCRIBED:
            if Z0 is not None:
                self.initialize_prescribed_charge_state(Z0=Z0,
                                                        n=n,
                                                        r=r,
                                                        t=t,
                                                        interpr=interpr,
                                                        interpt=interpt)
            else:
                self.initialize_prescribed(n=n, r=r, t=t)
        elif ttype == IONS_DYNAMIC:
            if Z0 is not None:
                self.initialize_dynamic_charge_state(Z0=Z0,
                                                     n=n,
                                                     r=r,
                                                     interpr=interpr)
            else:
                self.initialize_dynamic(n=n, r=r)
        elif ttype == IONS_EQUILIBRIUM:
            self.initialize_equilibrium(n=n, r=r, Z0=Z0)
        elif Z0 is not None:
            print(
                "WARNING: Charge state Z0 given, but ion type is not simply 'prescribed', 'dynamic' or 'equilibrium'. Hence, Z0 is ignored."
            )

        # TYPES AVAILABLE ONLY IN THIS INTERFACE
        elif ttype == IONS_DYNAMIC_NEUTRAL:
            self.initialize_dynamic_neutral(n=n, r=r, interpr=interpr)
        elif ttype == IONS_DYNAMIC_FULLY_IONIZED:
            self.initialize_dynamic_fully_ionized(n=n, r=r, interpr=interpr)
        elif ttype == IONS_PRESCRIBED_NEUTRAL:
            self.initialize_prescribed_neutral(n=n,
                                               r=r,
                                               t=t,
                                               interpr=interpr,
                                               interpt=interpt)
        elif ttype == IONS_PRESCRIBED_FULLY_IONIZED:
            self.initialize_prescribed_fully_ionized(n=n,
                                                     r=r,
                                                     t=t,
                                                     interpr=interpr,
                                                     interpt=interpt)
        else:
            raise EquationException(
                "ion_species: '{}': Unrecognized ion type: {}.".format(
                    self.name, ttype))

        self.T = self.setTemperature(T)
Example #23
0
    def initialize_prescribed_charge_state(self,
                                           Z0,
                                           n=None,
                                           r=None,
                                           t=None,
                                           interpr=None,
                                           interpt=None):
        """
        Prescribe the ions to all be situated in the specified charge state Z0.
        """
        if Z0 > self.Z or Z0 < 0:
            raise EquationException(
                "ion_species: '{}': Invalid charge state specified: {}. Ion has charge Z = {}."
                .format(self.name, Z0, self.Z))

        if n is None:
            raise EquationException(
                "ion_species: '{}': Input density must not be 'None'.".format(
                    self.name))

        # Convert lists to NumPy arrays
        if type(n) == list:
            n = np.array(n)

        # Scalar (assume density constant in spacetime)
        #if type(n) == float or (type(n) == np.ndarray and n.size == 1):
        if np.isscalar(n):
            t = interpt if interpt is not None else np.array([0])
            r = interpr if interpr is not None else np.array([0])
            N = np.zeros((self.Z + 1, t.size, r.size))
            N[Z0, 0, :] = n

            self.initialize_prescribed(n=N, t=t, r=r)
            return

        if r is None:
            raise EquationException(
                "ion_species: '{}': Non-scalar density prescribed, but no radial coordinates given."
                .format(self.name))

        # Radial profile
        if len(n.shape) == 1:
            if r.size != n.size:
                raise EquationException(
                    "ion_species: '{}': Invalid dimensions of prescribed density: {}. Expected {}."
                    .format(self.name, n.shape[0], r.size))

            t = interpt if interpt is not None else np.array([0])
            n = np.reshape(n, (t.size, r.size))

        # Radial + temporal profile
        if len(n.shape) == 2:
            if t is None:
                raise EquationException(
                    "ion_species: '{}': 2D ion density prescribed, but no time coordinates given."
                    .format(self.name))

            if t.size != n.shape[0] or r.size != n.shape[1]:
                raise EquationException(
                    "ion_species: '{}': Invalid dimensions of prescribed density: {}x{}. Expected {}x{}."
                    .format(self.name, n.shape[0], n.shape[1], t.size, r.size))

            N = np.zeros((self.Z + 1, t.size, r.size))
            N[Z0, :, :] = n

            self.initialize_prescribed(n=N, t=t, r=r)
        else:
            raise EquationException(
                "ion_species: '{}': Unrecognized shape of prescribed density: {}."
                .format(self.name, n.shape))
Example #24
0
    def verifySettings(self):
        """
        Verify that the settings of this unknown are correctly set.
        """
        if self.grid.enabled:
            if self.mode != DISTRIBUTION_MODE_NUMERICAL:
                raise EquationException("{}: Invalid mode set. Must be 'NUMERICAL' when the grid is 'enabled'.".format(self.name))
            bc = self.boundarycondition
            if (bc != BC_F_0) and (bc != BC_PHI_CONST) and (bc != BC_DPHI_CONST):
                raise EquationException("{}: Invalid external boundary condition set: {}.".format(self.name, bc))
            ad_int_opts = [
                AD_INTERP_CENTRED, AD_INTERP_DOWNWIND, AD_INTERP_UPWIND, AD_INTERP_UPWIND_2ND_ORDER, 
                AD_INTERP_QUICK, AD_INTERP_SMART, AD_INTERP_MUSCL, AD_INTERP_OSPRE, AD_INTERP_TCDF
            ]
            if self.adv_interp_r not in ad_int_opts:
                raise EquationException("{}: Invalid radial interpolation coefficient set: {}.".format(self.name, self.adv_interp_r))
            if self.adv_interp_p1 not in ad_int_opts:
                raise EquationException("{}: Invalid p1 interpolation coefficient set: {}.".format(self.name, self.adv_interp_p1))
            if self.adv_interp_p2 not in ad_int_opts:
                raise EquationException("{}: Invalid p2 interpolation coefficient set: {}.".format(self.name, self.adv_interp_p2))
            if (self.fluxlimiterdamping<0.0) or (self.fluxlimiterdamping>1.0):
                raise EquationException("{}: Invalid flux limiter damping coefficient: {}. Choose between 0 and 1.".format(self.name, self.fluxlimiterdamping))
            if self.init is not None:
                self.verifyInitialDistribution()
            elif (self.n0 is not None) or (self.T0 is not None):
                self.verifyInitialProfiles()
            else:
                raise EquationException("{}: Invalid/no initial condition set for the distribution function.".format(self.name))

            if type(self.ripplemode) == bool:
                self.setRippleMode(self.ripplemode)
            elif type(self.ripplemode) != int:
                raise EquationException("{}: Invalid type of ripple mode option: {}".format(self.name, type(self.ripplemode)))
            else:
                opt = [RIPPLE_MODE_NEGLECT, RIPPLE_MODE_BOX, RIPPLE_MODE_GAUSSIAN]
                if self.ripplemode not in opt:
                    raise EquationException("{}: Invalid option for ripple mode.".format(self.name, self.ripplemode))
 
            if type(self.synchrotronmode) == bool:
                self.setSynchrotronMode(self.synchrotronmode)
            elif type(self.synchrotronmode) != int:
                raise EquationException("{}: Invalid type of synchrotron mode option: {}".format(self.name, type(self.synchrotronmode)))
            else:
                opt = [SYNCHROTRON_MODE_NEGLECT, SYNCHROTRON_MODE_INCLUDE]
                if self.synchrotronmode not in opt:
                    raise EquationException("{}: Invalid option for synchrotron mode.".format(self.name, self.synchrotronmode))

            self.transport.verifySettings()
        elif self.mode != DISTRIBUTION_MODE_NUMERICAL:
            # if fluid mode and analytical distribution,
            # initial profiles must be provided:
            self.verifyInitialProfiles()