예제 #1
0
 def Z_mean(self) -> np.float64:
     """Return the mean integer charge"""
     if np.nan in self.ionic_fractions:
         raise ChargeError(
             "Z_mean cannot be found because no ionic fraction "
             f"information is available for {self.base_particle}.")
     return np.sum(self.ionic_fractions * np.arange(self.atomic_number + 1))
예제 #2
0
    def roman_symbol(self) -> Optional[str]:
        """
        Return the spectral name of the particle (i.e. the ionic symbol
        in Roman numeral notation).  If the particle is not an ion or
        neutral atom, return `None`. The roman numeral represents one
        plus the integer charge. Raise `ChargeError` if no charge has
        been specified and `~plasmapy.utils.roman.roman.OutOfRangeError`
        if the charge is negative.

        Examples
        --------
        >>> proton = Particle('proton')
        >>> proton.roman_symbol
        'H-1 II'
        >>> hydrogen_atom = Particle('H', Z=0)
        >>> hydrogen_atom.roman_symbol
        'H I'

        """
        if not self._attributes['element']:
            return None
        if self._attributes['integer charge'] is None:
            raise ChargeError(f"The charge of particle {self} has not been specified.")
        if self._attributes['integer charge'] < 0:
            raise roman.OutOfRangeError('Cannot convert negative charges to Roman.')

        symbol = self.isotope if self.isotope else self.element
        integer_charge = self._attributes['integer charge']
        roman_charge = roman.to_roman(integer_charge + 1)
        return f"{symbol} {roman_charge}"
예제 #3
0
    def __getitem__(self, value) -> State:
        """Return the ionic fraction(s)."""
        if isinstance(value, slice):
            raise TypeError("IonizationState instances cannot be sliced.")

        if isinstance(value,
                      (int, np.integer)) and 0 <= value <= self.atomic_number:
            result = State(value, self.ionic_fractions[value],
                           self.ionic_symbols[value])
        else:
            if not isinstance(value, Particle):
                try:
                    value = Particle(value)
                except InvalidParticleError as exc:
                    raise InvalidParticleError(
                        f"{value} is not a valid integer charge or "
                        f"particle.") from exc

            same_element = value.element == self.element
            same_isotope = value.isotope == self.isotope
            has_charge_info = value.is_category(
                any_of=["charged", "uncharged"])

            if same_element and same_isotope and has_charge_info:
                Z = value.integer_charge
                result = State(Z, self.ionic_fractions[Z],
                               self.ionic_symbols[Z])
            else:
                if not same_element or not same_isotope:
                    raise AtomicError("Inconsistent element or isotope.")
                elif not has_charge_info:
                    raise ChargeError("No integer charge provided.")
        return result
예제 #4
0
    def integer_charge(self) -> Integral:
        """
        Return the particle's integer charge.

        This attribute will raise a `~plasmapy.utils.ChargeError` if the
        charge has not been specified.

        Examples
        --------
        >>> muon = Particle('mu-')
        >>> muon.integer_charge
        -1

        """
        if self._attributes['integer charge'] is None:
            raise ChargeError(f"The charge of particle {self} has not been specified.")
        return self._attributes['integer charge']
예제 #5
0
    def charge(self) -> u.Quantity:
        """
        Return the particle's electron charge in coulombs.

        This attribute will raise a `~plasmapy.utils.ChargeError` if the
        charge has not been specified.

        Examples
        --------
        >>> electron = Particle('e-')
        >>> electron.charge
        <Quantity -1.60217662e-19 C>

        """
        if self._attributes['charge'] is None:
            raise ChargeError(f"The charge of particle {self} has not been specified.")
        if self._attributes['integer charge'] == 1:
            return const.e.si

        return self._attributes['charge']
예제 #6
0
    def __getitem__(self, *values) -> IonizationState:

        errmsg = f"Invalid indexing for IonizationStates instance: {values[0]}"

        one_input = not isinstance(values[0], tuple)
        two_inputs = len(values[0]) == 2

        if not one_input and not two_inputs:
            raise IndexError(errmsg)

        try:
            arg1 = values[0] if one_input else values[0][0]
            int_charge = None if one_input else values[0][1]
            particle = arg1 if arg1 in self.base_particles else particle_symbol(
                arg1)

            if int_charge is None:
                return IonizationState(
                    particle=particle,
                    ionic_fractions=self.ionic_fractions[particle],
                    T_e=self._pars["T_e"],
                    n_elem=np.sum(self.number_densities[particle]),
                    tol=self.tol,
                )
            else:
                if not isinstance(int_charge, Integral):
                    raise TypeError(
                        f"{int_charge} is not a valid charge for {base_particle}."
                    )
                elif not 0 <= int_charge <= atomic_number(particle):
                    raise ChargeError(
                        f"{int_charge} is not a valid charge for {base_particle}."
                    )
                return State(
                    integer_charge=int_charge,
                    ionic_fraction=self.ionic_fractions[particle][int_charge],
                    ionic_symbol=particle_symbol(particle, Z=int_charge),
                    number_density=self.number_densities[particle][int_charge])
        except Exception as exc:
            raise IndexError(errmsg) from exc
예제 #7
0
    def recombine(self, n: Integral = 1, inplace=False):
        """
        Create a new `~plasmapy.atomic.Particle` instance corresponding
        to the current `~plasmapy.atomic.Particle` after undergoing
        recombination ``n`` times.

        If ``inplace`` is `False` (default), then return the
        `~plasmapy.atomic.Particle` that just underwent recombination.

        If ``inplace`` is `True`, then replace the current
        `~plasmapy.atomic.Particle` with the `~plasmapy.atomic.Particle`
        that just underwent recombination.

        Parameters
        ----------
        n : positive integer
            The number of electrons to recombine into the
            `~plasmapy.atomic.Particle` object.

        inplace : bool, optional
            If `True`, then replace the current
            `~plasmapy.atomic.Particle` instance with the
            `~plasmapy.atomic.Particle` that just underwent
            recombination.

        Returns
        -------
        particle : ~plasmapy.atomic.Particle
            A new `~plasmapy.atomic.Particle` object that has undergone
            recombination ``n`` times relative to the original
            `~plasmapy.atomic.Particle`.  If ``inplace`` is `False`,
            instead return `None`.

        Raises
        ------
        ~plasmapy.atomic.InvalidElementError
            If the `~plasampy.atomic.Particle` is not an element.

        ~plasmapy.atomic.ChargeError
            If no charge information for the `~plasmapy.atomic.Particle`
            object is specified.

        ValueError
            If ``n`` is not positive.

        Examples
        --------
        >>> Particle("Fe 6+").recombine()
        Particle("Fe 5+")
        >>> helium_particle = Particle("He-4 2+")
        >>> helium_particle.recombine(n=2, inplace=True)
        >>> helium_particle
        Particle("He-4 0+")

        """

        if not self.element:
            raise InvalidElementError(
                f"{self.particle} cannot undergo recombination because "
                f"it is not a neutral atom or ion.")
        if not self.is_category(any_of={"charged", "uncharged"}):
            raise ChargeError(
                f"{self.particle} cannot undergo recombination because "
                f"its charge is not specified.")
        if not isinstance(n, Integral):
            raise TypeError("n must be a positive integer.")
        if n <= 0:
            raise ValueError("n must be a positive number.")

        base_particle = self.isotope if self.isotope else self.element
        new_integer_charge = self.integer_charge - n

        if inplace:
            self.__init__(base_particle, Z=new_integer_charge)
        else:
            return Particle(base_particle, Z=new_integer_charge)
예제 #8
0
    def ionize(self, n: Integral = 1, inplace: bool = False):
        """
        Create a new `~plasmapy.atomic.Particle` instance corresponding
        to the current `~plasmapy.atomic.Particle` after being ionized
        ``n`` times.

        If ``inplace`` is `False` (default), then return the ionized
        `~plasmapy.atomic.Particle`.

        If ``inplace`` is `True`, then replace the current
        `~plasmapy.atomic.Particle` with the newly ionized
        `~plasmapy.atomic.Particle`.

        Parameters
        ----------
        n : positive integer
            The number of bound electrons to remove from the
            `~plasmapy.atomic.Particle` object.  Defaults to ``1``.

        inplace : bool, optional
            If `True`, then replace the current
            `~plasmapy.atomic.Particle` instance with the newly ionized
            `~plasmapy.atomic.Particle`.

        Returns
        -------
        particle : ~plasmapy.atomic.Particle
            A new `~plasmapy.atomic.Particle` object that has been
            ionized ``n`` times relative to the original
            `~plasmapy.atomic.Particle`.  If ``inplace`` is `False`,
            instead return `None`.

        Raises
        ------
        ~plasmapy.atomic.InvalidElementError
            If the `~plasampy.atomic.Particle` is not an element.

        ~plasmapy.atomic.ChargeError
            If no charge information for the `~plasmapy.atomic.Particle`
            object is specified.

        ~plasmapy.atomic.InvalidIonError
            If there are less than ``n`` remaining bound electrons.

        ValueError
            If ``n`` is not positive.

        Examples
        --------
        >>> Particle("Fe 6+").ionize()
        Particle("Fe 7+")
        >>> helium_particle = Particle("He-4 0+")
        >>> helium_particle.ionize(n=2, inplace=True)
        >>> helium_particle
        Particle("He-4 2+")

        """
        if not self.element:
            raise InvalidElementError(
                f"Cannot ionize {self.particle} because it is not a "
                f"neutral atom or ion.")
        if not self.is_category(any_of={"charged", "uncharged"}):
            raise ChargeError(
                f"Cannot ionize {self.particle} because its charge "
                f"is not specified.")
        if self.integer_charge == self.atomic_number:
            raise InvalidIonError(
                f"The particle {self.particle} is already fully "
                f"ionized and cannot be ionized further.")
        if not isinstance(n, Integral):
            raise TypeError("n must be a positive integer.")
        if n <= 0:
            raise ValueError("n must be a positive number.")

        base_particle = self.isotope if self.isotope else self.element
        new_integer_charge = self.integer_charge + n

        if inplace:
            self.__init__(base_particle, Z=new_integer_charge)
        else:
            return Particle(base_particle, Z=new_integer_charge)
예제 #9
0
    def get_particle(argname, params, already_particle, funcname):
        argval, Z, mass_numb = params

        # Convert the argument to a Particle object if it is not
        # already one.

        if not already_particle:

            if not isinstance(argval, (numbers.Integral, str, tuple, list)):
                raise TypeError(
                    f"The argument {argname} to {funcname} must be "
                    f"a string, an integer or a tuple or list of them "
                    f"corresponding to an atomic number, or a "
                    f"Particle object.")

            try:
                particle = Particle(argval, Z=Z, mass_numb=mass_numb)
            except InvalidParticleError as e:
                raise InvalidParticleError(
                    _particle_errmsg(argname, argval, Z, mass_numb,
                                     funcname)) from e

        # We will need to do the same error checks whether or not the
        # argument is already an instance of the Particle class.

        if already_particle:
            particle = argval

        # If the name of the argument annotated with Particle in the
        # decorated function is element, isotope, or ion; then this
        # decorator should raise the appropriate exception when the
        # particle ends up not being an element, isotope, or ion.

        cat_table = [
            ('element', particle.element, InvalidElementError),
            ('isotope', particle.isotope, InvalidIsotopeError),
            ('ion', particle.ionic_symbol, InvalidIonError),
        ]

        for category_name, category_symbol, CategoryError in cat_table:
            if argname == category_name and not category_symbol:
                raise CategoryError(
                    f"The argument {argname} = {repr(argval)} to "
                    f"{funcname} does not correspond to a valid "
                    f"{argname}.")

        # Some functions require that particles be charged, or
        # at least that particles have charge information.

        _integer_charge = particle._attributes['integer charge']

        must_be_charged = 'charged' in require
        must_have_charge_info = set(any_of) == {'charged', 'uncharged'}

        uncharged = _integer_charge == 0
        lacks_charge_info = _integer_charge is None

        if must_be_charged and (uncharged or must_have_charge_info):
            raise ChargeError(
                f"A charged particle is required for {funcname}.")

        if must_have_charge_info and lacks_charge_info:
            raise ChargeError(
                f"Charge information is required for {funcname}.")

        # Some functions require particles that belong to more complex
        # classification schemes.  Again, be sure to provide a
        # maximally useful error message.

        if not particle.is_category(
                require=require, exclude=exclude, any_of=any_of):
            raise AtomicError(
                _category_errmsg(particle, require, exclude, any_of, funcname))

        return particle