Пример #1
0
def test_ion_list(particle, min_charge, max_charge, expected_charge_numbers):
    """Test that inputs to ionic_levels are interpreted correctly."""
    particle = Particle(particle)
    ions = ionic_levels(particle, min_charge, max_charge)
    np.testing.assert_equal(ions.charge_number, expected_charge_numbers)
    assert ions[0].element == particle.element
    if particle.is_category("isotope"):
        assert ions[0].isotope == particle.isotope
Пример #2
0
    def __init__(
        self,
        particle: Particle,
        ionic_fractions=None,
        *,
        T_e: u.K = np.nan * u.K,
        T_i: u.K = None,
        kappa: Real = np.inf,
        n_elem: u.m**-3 = np.nan * u.m**-3,
        tol: Union[float, int] = 1e-15,
    ):
        """
        Initialize an `~plasmapy.particles.ionization_state.IonizationState`
        instance.
        """
        self._number_of_particles = particle.atomic_number + 1

        if particle.is_ion or particle.is_category(require=("uncharged",
                                                            "element")):
            if ionic_fractions is None:
                ionic_fractions = np.zeros(self._number_of_particles)
                ionic_fractions[particle.charge_number] = 1.0
                particle = Particle(
                    particle.isotope if particle.isotope else particle.element)
            else:
                raise ParticleError(
                    "The ionic fractions must not be specified when "
                    "the input particle to IonizationState is an ion.")

        self._particle = particle

        try:
            self.tol = tol
            self.T_e = T_e
            self.T_i = T_i
            self.kappa = kappa

            if (not np.isnan(n_elem)
                    and isinstance(ionic_fractions, u.Quantity)
                    and ionic_fractions.si.unit == u.m**-3):
                raise ParticleError(
                    "Cannot simultaneously provide number density "
                    "through both n_elem and ionic_fractions.")

            self.n_elem = n_elem
            self.ionic_fractions = ionic_fractions

            if ionic_fractions is None and not np.isnan(self.T_e):
                warnings.warn(
                    "Collisional ionization equilibration has not yet "
                    "been implemented in IonizationState; cannot set "
                    "ionic fractions.")

        except Exception as exc:
            raise ParticleError(
                f"Unable to create IonizationState object for {particle.symbol}."
            ) from exc
Пример #3
0
def is_stable(particle: Particle,
              mass_numb: Optional[Integral] = None) -> bool:
    """
    Return `True` for stable isotopes and particles and `False` for
    unstable isotopes.

    Parameters
    ----------
    particle: `int`, `str`, or `~plasmapy.particles.Particle`
        A string representing an isotope or particle, or an integer
        representing an atomic number.

    mass_numb: `int`, optional
        The mass number of the isotope.

    Returns
    -------
    is_stable: `bool`
        `True` if the isotope is stable, `False` if it is unstable.

    Raises
    ------
    `~plasmapy.utils.InvalidIsotopeError`
        If the arguments correspond to a valid element but not a
        valid isotope.

    `~plasmapy.utils.InvalidParticleError`
        If the arguments do not correspond to a valid particle.

    `TypeError`
        If the argument is not a `str` or `int`.

    `~plasmapy.utils.MissingAtomicDataError`
        If stability information is not available.

    Examples
    --------
    >>> is_stable("H-1")
    True
    >>> is_stable("tritium")
    False
    >>> is_stable("e-")
    True
    >>> is_stable("tau+")
    False

    """
    if particle.element and not particle.isotope:
        raise InvalidIsotopeError(
            "The input to is_stable must be either an isotope or a special particle."
        )
    return particle.is_category('stable')
Пример #4
0
    def __getitem__(self, value) -> IonicLevel:
        """Return information for a single ionization level."""
        if isinstance(value, slice):
            return [
                IonicLevel(
                    ion=Particle(self.base_particle, Z=val),
                    ionic_fraction=self.ionic_fractions[val],
                    number_density=self.number_densities[val],
                    T_i=self.T_i[val],
                ) for val in range(0, self._number_of_particles)[value]
            ]

        if isinstance(value, Integral) and 0 <= value <= self.atomic_number:
            result = IonicLevel(
                ion=Particle(self.base_particle, Z=value),
                ionic_fraction=self.ionic_fractions[value],
                number_density=self.number_densities[value],
                T_i=self.T_i[value],
            )
        else:
            if not isinstance(value, Particle):
                try:
                    value = Particle(value)
                except InvalidParticleError as exc:
                    raise InvalidParticleError(
                        f"{value} is not a valid charge number or 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.charge_number
                result = IonicLevel(
                    ion=Particle(self.base_particle, Z=Z),
                    ionic_fraction=self.ionic_fractions[Z],
                    number_density=self.number_densities[Z],
                    T_i=self.T_i[Z],
                )
            else:
                if not same_element or not same_isotope:
                    raise ParticleError("Inconsistent element or isotope.")
                elif not has_charge_info:
                    raise ChargeError("No charge number provided.")
        return result
Пример #5
0
    def __getitem__(self, value) -> State:
        """Return information for a single ionization level."""
        if isinstance(value, slice):
            raise TypeError("IonizationState instances cannot be sliced.")

        if isinstance(value, Integral) and 0 <= value <= self.atomic_number:
            result = State(
                value,
                self.ionic_fractions[value],
                self.ionic_symbols[value],
                self.number_densities[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],
                    self.number_densities[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
Пример #6
0
    def get_particle(argname, params, already_particle, funcname):
        argval, Z, mass_numb = params
        """
        Convert the argument to a
        `~plasmapy.particles.particle_class.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.

        _charge_number = particle._attributes["charge number"]

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

        uncharged = _charge_number == 0
        lacks_charge_info = _charge_number 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 ParticleError(
                _category_errmsg(particle, require, exclude, any_of, funcname))

        return particle