예제 #1
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
예제 #2
0
def test_Particle_cmp():
    """Test ``__eq__`` and ``__ne__`` in the Particle class."""
    proton1 = Particle('p+')
    proton2 = Particle('proton')
    electron = Particle('e-')

    assert proton1 == proton2, "Particle('p+') == Particle('proton') is False."
    assert proton1 != electron, "Particle('p+') == Particle('e-') is True."

    with pytest.raises(TypeError):
        electron == 1

    with pytest.raises(AtomicError):
        electron == 'dfasdf'
예제 #3
0
 def particles(self) -> List[Particle]:
     """
     Return a list of the `~plasmapy.atomic.Particle` class
     instances.
     """
     return [
         Particle(self._particle.particle, Z=i)
         for i in range(self.atomic_number + 1)
     ]
예제 #4
0
def test_particle_class_mass_nuclide_mass(isotope: str, ion: str):
    """
    Test that the ``mass`` and ``nuclide_mass`` attributes return
    equivalent values when appropriate.  The inputs should generally be
    an isotope with no charge information, and a fully ionized ion of
    that isotope, in order to make sure that the nuclide mass of the
    isotope equals the mass of the fully ionized ion.  This method may
    also check neutrons and protons.
    """

    Isotope = Particle(isotope)
    Ion = Particle(ion)

    if Isotope.categories & {'isotope', 'baryon'
                             } and Ion.categories & {'ion', 'baryon'}:

        particle = Isotope.particle

        assert Isotope.nuclide_mass == Ion.mass, (
            f"Particle({repr(particle)}).nuclide_mass does not equal "
            f"Particle({repr(particle)}).mass")

    else:

        inputerrmsg = (f"isotope = {repr(isotope)} and ion = {repr(ion)} are "
                       f"not valid inputs to this test. The inputs should be "
                       f"an isotope with no charge information, and a fully "
                       f"ionized ion of that isotope, in order to make sure "
                       f"that the nuclide mass of the isotope equals the mass "
                       f"of the ion.")

        assert Isotope.isotope and not Isotope.ion, inputerrmsg
        assert Isotope.isotope == Ion.isotope, inputerrmsg
        assert Ion.integer_charge == Ion.atomic_number, inputerrmsg

        assert Isotope.nuclide_mass == Ion.mass, (
            f"The nuclide mass of {isotope} does not equal the mass of {ion} "
            f"which is the fully ionized ion of that isotope. The results of "
            f"the test are:\n\n"
            f"Particle({repr(ion)}).mass = {Ion.mass}\n"
            f"Particle({repr(isotope)}).nuclide_mass = {Isotope.nuclide_mass}"
            "\n")
예제 #5
0
파일: atomic.py 프로젝트: ritiek/PlasmaPy
def is_stable(particle: Particle,
              mass_numb: Optional[numbers.Integral] = None) -> bool:
    """
    Return `True` for stable isotopes and particles and `False` for
    unstable isotopes.

    Parameters
    ----------
    particle: `int`, `str`, or `~plasmapy.atomic.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')
예제 #6
0
def test_Particle_class(arg, kwargs, expected_dict):
    """
    Test that `~plasmapy.atomic.Particle` objects for different
    subatomic particles, elements, isotopes, and ions return the
    expected properties.  Provide a detailed error message that lists
    all of the inconsistencies with the expected results.
    """

    call = call_string(Particle, arg, kwargs)
    errmsg = ""

    try:
        particle = Particle(arg, **kwargs)
    except Exception as exc:
        raise AtomicError(f"Problem creating {call}") from exc

    for key in expected_dict.keys():
        expected = expected_dict[key]

        if inspect.isclass(expected) and issubclass(expected, Exception):

            # Exceptions are expected to be raised when accessing certain
            # attributes for some particles.  For example, accessing a
            # neutrino's mass should raise a MissingAtomicDataError since
            # only upper limits of neutrino masses are presently available.
            # If expected_dict[key] is an exception, then check to make
            # sure that this exception is raised.

            try:
                with pytest.raises(expected):
                    exec(f"particle.{key}")
            except pytest.fail.Exception:
                errmsg += f"\n{call}[{key}] does not raise {expected}."
            except Exception:
                errmsg += (f"\n{call}[{key}] does not raise {expected} but "
                           f"raises a different exception.")

        else:

            try:
                result = eval(f"particle.{key}")
                assert result == expected
            except AssertionError:
                errmsg += (f"\n{call}.{key} returns {result} instead "
                           f"of the expected value of {expected}.")
            except Exception:
                errmsg += f"\n{call}.{key} raises an unexpected exception."

    if len(errmsg) > 0:
        raise Exception(f"Problems with {call}:{errmsg}")
예제 #7
0
def test_particle_half_life_string():
    """
    Find the first isotope where the half-life is stored as a string
    (because the uncertainties are too great), and tests that requesting
    the half-life of that isotope causes a `MissingAtomicDataWarning`
    whilst returning a string.
    """

    for isotope in known_isotopes():
        half_life = _Isotopes[isotope].get('half-life', None)
        if isinstance(half_life, str):
            break

    with pytest.warns(MissingAtomicDataWarning):
        assert isinstance(Particle(isotope).half_life, str)
예제 #8
0
    def process_particles_list(
            unformatted_particles_list: List[Union[str, Particle]]) \
            -> List[Particle]:
        """
        Take an unformatted list of particles and puts each
        particle into standard form, while allowing an integer and
        asterisk immediately preceding a particle to act as a
        multiplier.  A string argument will be treated as a list
        containing that string as its sole item.
        """

        if isinstance(unformatted_particles_list, str):
            unformatted_particles_list = [unformatted_particles_list]

        if not isinstance(unformatted_particles_list, (list, tuple)):
            raise TypeError("The input to process_particles_list should be a "
                            "string, list, or tuple.")

        particles = []

        for original_item in unformatted_particles_list:

            try:
                item = original_item.strip()

                if item.count('*') == 1 and item[0].isdigit():
                    multiplier_str, item = item.split('*')
                    multiplier = int(multiplier_str)
                else:
                    multiplier = 1

                try:
                    particle = Particle(item)
                except (InvalidParticleError) as exc:
                    raise AtomicError(errmsg) from exc

                if particle.element and not particle.isotope:
                    raise AtomicError(errmsg)

                [particles.append(particle) for i in range(multiplier)]

            except Exception:
                raise AtomicError(
                    f"{original_item} is not a valid reactant or "
                    "product in a nuclear reaction.") from None

        return particles
예제 #9
0
    def get_particle_mass(particle) -> u.Quantity:
        """Return the mass of a particle.

        Take a representation of a particle and returns the mass in
        kg.  If the input is a `~astropy.units.Quantity` or
        `~astropy.constants.Constant` with units of mass already, then
        this returns that mass converted to kg.
        """
        try:
            if isinstance(particle, (u.Quantity, const.Constant)):
                return particle.to(u.kg)
            if not isinstance(particle, Particle):
                particle = Particle(particle)
            return particle.mass.to(u.kg)
        except u.UnitConversionError as exc1:
            raise u.UnitConversionError(f"Incorrect units in reduced_mass.") from exc1
        except MissingAtomicDataError:
            raise MissingAtomicDataError(
                f"Unable to find the reduced mass because the mass of "
                f"{particle} is not available.") from None
예제 #10
0
def test_particleing_a_particle(arg):
    """
    Test that Particle(arg) is equal to Particle(Particle(arg)), but is
    not the same object in memory.
    """
    particle = Particle(arg)

    assert particle == Particle(particle), (
        f"Particle({repr(arg)}) does not equal "
        f"Particle(Particle({repr(arg)}).")

    assert particle == Particle(Particle(
        Particle(particle))), (f"Particle({repr(arg)}) does not equal "
                               f"Particle(Particle(Particle({repr(arg)})).")

    assert particle is not Particle(particle), (
        f"Particle({repr(arg)}) is the same object in memory as "
        f"Particle(Particle({repr(arg)})), when it is intended to "
        f"create a new object in memory (e.g., a copy).")
예제 #11
0
def particle(request):
    return Particle(request.param)
예제 #12
0
def test_unary_operator_for_elements():
    with pytest.raises(AtomicError):
        Particle('C').antiparticle
예제 #13
0
def test_antiparticle_inversion(particle, antiparticle):
    """Test that antiparticles have the correct antiparticles."""
    assert Particle(antiparticle).antiparticle == Particle(particle), \
        (f"The antiparticle of {antiparticle} is found to be "
         f"{~Particle(antiparticle)} instead of {particle}.")
예제 #14
0
def test_particle_bool_error():
    with pytest.raises(AtomicError):
        bool(Particle('e-'))
예제 #15
0
    def ionic_fractions(self, inputs: Union[Dict, List, Tuple]):
        """Set the ionic fractions"""
        if isinstance(inputs, dict):
            original_keys = inputs.keys()

            ionfrac_types = {type(inputs[key]) for key in original_keys}
            if u.Quantity in ionfrac_types and len(ionfrac_types) != 1:
                raise TypeError(
                    "Ionic fraction information may only be inputted "
                    "as a Quantity object if all ionic fractions are "
                    "Quantity arrays with units of inverse volume.")

            # Create a dictionary of Particle instances
            particles = dict()
            for key in original_keys:
                try:
                    particles[key] = key if isinstance(
                        key, Particle) else Particle(key)
                except (InvalidParticleError, TypeError) as exc:
                    raise AtomicError(
                        f"Unable to create IonizationStates instance "
                        f"because {key} is not a valid particle") from exc

            # The particles whose ionization states are to be recorded
            # should be elements or isotopes but not ions or neutrals.
            is_element = particles[key].is_category('element')
            has_charge_info = particles[key].is_category(
                any_of=["charged", "uncharged"])
            if not is_element or has_charge_info:
                raise AtomicError(
                    f"{key} is not an element or isotope without "
                    f"charge information.")

            # We are sorting the elements/isotopes by atomic number and
            # mass number since we will often want to plot and analyze
            # things and this is the most sensible order.

            sorted_keys = sorted(original_keys,
                                 key=lambda k: (
                                     particles[k].atomic_number,
                                     particles[k].mass_number
                                     if particles[k].isotope else 0,
                                 ))

            _elements = []
            _particles = []
            new_ionic_fractions = {}
            for key in sorted_keys:
                new_key = particles[key].particle
                _particles.append(particles[key])
                if new_key in _elements:
                    raise AtomicError(
                        "Repeated particles in IonizationStates.")

                _elements.append(new_key)
                if isinstance(inputs[key], u.Quantity):
                    try:
                        number_densities = inputs[key].to(u.m**-3)
                        n_elem = np.sum(number_densities)
                        new_ionic_fractions[new_key] = np.array(
                            number_densities / n_elem)
                    except u.UnitConversionError as exc:
                        raise AtomicError(
                            "Units are not inverse volume.") from exc
                elif isinstance(inputs[key],
                                np.ndarray) and inputs[key].dtype.kind == 'f':
                    new_ionic_fractions[particles[key].particle] = inputs[key]
                else:
                    try:
                        new_ionic_fractions[particles[key].particle] = \
                            np.array(inputs[key], dtype=np.float)
                    except ValueError:
                        raise AtomicError(
                            f"Inappropriate ionic fractions for {key}.")

            for key in _elements:
                if np.min(new_ionic_fractions[key]) < 0 or np.max(
                        new_ionic_fractions[key]) > 1:
                    raise AtomicError(
                        f"Ionic fractions for {key} are not between 0 and 1.")
                if not np.isclose(
                        np.sum(new_ionic_fractions[key]), 1, atol=self.tol,
                        rtol=0):
                    raise AtomicError(
                        f"Ionic fractions for {key} are not normalized to 1.")

        elif isinstance(inputs, (list, tuple)):

            try:
                _particles = [Particle(particle) for particle in inputs]
            except (InvalidParticleError, TypeError):
                raise AtomicError("Invalid inputs to IonizationStates")

            _particles.sort(key=lambda p: (p.atomic_number, p.mass_number
                                           if p.isotope else 0))
            _elements = [particle.particle for particle in _particles]
            new_ionic_fractions = {
                particle.particle: np.full(particle.atomic_number + 1,
                                           fill_value=np.nan,
                                           dtype=np.float64)
                for particle in _particles
            }
        else:
            raise TypeError

        # Because this depends on _particles being sorted, we add in an
        # easy check that atomic numbers do not decrease.
        for i in range(1, len(_particles)):
            if _particles[i - 1].element == _particles[i].element:
                if not _particles[i - 1].isotope and _particles[i].isotope:
                    raise AtomicError(
                        "Cannot have an element and isotopes of that element.")
            elif _particles[i - 1].atomic_number > _particles[i].atomic_number:
                raise AtomicError("_particles has not been sorted.")

        self._particles = _particles
        self._elements = _elements
        self._ionic_fractions = new_ionic_fractions
예제 #16
0
     'particle': 'He-4 2+',
     'element': 'He',
     'element_name': 'helium',
     'isotope': 'He-4',
     'isotope_name': 'helium-4',
     'ionic_symbol': 'He-4 2+',
     'roman_symbol': 'He-4 III',
     'mass_energy': 5.971919969131517e-10 * u.J,
     'is_ion': True,
     'integer_charge': 2,
     'atomic_number': 2,
     'mass_number': 4,
     'baryon_number': 4,
     'lepton_number': 0,
     'half_life': np.inf * u.s,
     'recombine()': Particle('He-4 1+')
 }),
 ('He-4 0+', {}, {
     'particle': 'He-4 0+',
     'element': 'He',
     'isotope': 'He-4',
     'mass_energy': 5.971919969131517e-10 * u.J,
 }),
 ('Li', {
     'mass_numb': 7
 }, {
     'particle': 'Li-7',
     'element': 'Li',
     'element_name': 'lithium',
     'isotope': 'Li-7',
     'isotope_name': 'lithium-7',
예제 #17
0
     {'particle': 'He-4 2+',
      'element': 'He',
      'element_name': 'helium',
      'isotope': 'He-4',
      'isotope_name': 'helium-4',
      'ionic_symbol': 'He-4 2+',
      'roman_symbol': 'He-4 III',
      'mass_energy': 5.971919969131517e-10 * u.J,
      'is_ion': True,
      'integer_charge': 2,
      'atomic_number': 2,
      'mass_number': 4,
      'baryon_number': 4,
      'lepton_number': 0,
      'half_life': np.inf * u.s,
      'recombine()': Particle('He-4 1+')
      }),

    ('He-4 0+', {},
     {'particle': 'He-4 0+',
      'element': 'He',
      'isotope': 'He-4',
      'mass_energy': 5.971919969131517e-10 * u.J,
      }),

    ('Li', {'mass_numb': 7},
     {'particle': 'Li-7',
      'element': 'Li',
      'element_name': 'lithium',
      'isotope': 'Li-7',
      'isotope_name': 'lithium-7',