Exemplo n.º 1
0
    def test_getitem_element(self, test):
        """Test that __get_item__ returns an IonizationState instance"""
        instance = self.instances[test]

        for key in instance.elements:

            try:
                expected = instance.ionic_fractions[key]
            except Exception as exc:
                raise AtomicError(
                    f"Unable to get ionic_fractions for '{key}' in "
                    f"test='{test}'.") from exc

            try:
                actual = instance[key].ionic_fractions
            except Exception as exc:
                raise AtomicError(f"Unable to get item {key} in test={test}.")

            try:
                if all(np.isnan(expected)):
                    test_passed = True
                else:
                    test_passed = np.allclose(expected, actual)
            except Exception:
                raise TypeError(
                    f"For test='{test}' and key='{key}', cannot "
                    f"compare expected ionic fractions of {expected} "
                    f"with the resulting ionic fractions of {actual}."
                ) from None

            if not test_passed:
                raise AtomicError(
                    f"For test='{test}' and key='{key}', the expected "
                    f"ionic fractions of {expected} are not all equal "
                    f"to the resulting ionic fractions of {actual}.")
Exemplo n.º 2
0
    def __eq__(self, other):
        """
        Return `True` if the ionic fractions for two `IonizationState`
        instances are approximately equal to within the minimum `tol`
        specified by either, and `False` otherwise.

        Raises
        ------
        AtomicError
            If `other` is not an `~plasmapy.atomic.IonizationState`
            instance, or if `other` corresponds to a different element.

        Examples
        --------
        >>> IonizationState('H', [1, 0], tol=1e-6) == IonizationState('H', [1, 1e-6], tol=1e-6)
        True
        >>> IonizationState('H', [1, 0], tol=1e-8) == IonizationState('H', [1, 1e-6], tol=1e-5)
        False

        """
        if not isinstance(other, IonizationState):
            raise AtomicError(
                "Instances of the IonizationState class may only be "
                "compared with other IonizationState instances.")

        if self.element != other.element:
            raise AtomicError(
                "Only ionization states of the same element may be compared.")

        # Use the tightest of the two absolute tolerances
        min_tol = np.min([self.tol, other.tol])

        return np.allclose(self.ionic_fractions,
                           other.ionic_fractions,
                           atol=min_tol)
Exemplo n.º 3
0
    def __init__(self,
                 particle: Particle,
                 ionic_fractions=None,
                 *,
                 T_e: u.K = np.nan * u.K,
                 kappa: Real = np.inf,
                 n_elem: u.m**-3 = np.nan * u.m**-3,
                 tol: Union[float, int] = 1e-15):
        """Initialize an `~plasmapy.atomic.IonizationState` instance."""

        self._particle_instance = particle

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

            if not np.isnan(n_elem) and isinstance(ionic_fractions, u.Quantity) and \
                    ionic_fractions.si.unit == u.m ** -3:
                raise AtomicError(
                    "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 AtomicError(f"Unable to create IonizationState instance for "
                              f"{particle.particle}.") from exc
Exemplo n.º 4
0
 def __setitem__(self, key, value):
     if isinstance(value, dict):
         raise NotImplementedError("Dictionary assignment not implemented.")
     else:
         try:
             particle = particle_symbol(key)
             if particle not in self.elements:
                 raise AtomicError(
                     f"{key} is not one of the particles kept track of "
                     f"by this IonizationStates instance.")
             new_fractions = np.array(value, dtype=np.float64)
             if new_fractions.min() < 0 or new_fractions.max() > 1:
                 raise ValueError(
                     "Ionic fractions must be between 0 and 1.")
             if not np.isclose(np.sum(new_fractions), 1):
                 raise ValueError("Ionic fractions are not normalized.")
             if len(new_fractions) != atomic_number(particle) + 1:
                 raise ValueError(
                     f"Incorrect size of ionic fraction array for {key}.")
             self._ionic_fractions[particle][:] = new_fractions[:]
         except Exception as exc:
             raise AtomicError(
                 f"Cannot set item for this IonizationStates "
                 f"instance for key = {repr(key)} and value = "
                 f"{repr(value)}")
Exemplo n.º 5
0
    def test_iteration(self, test_name: str):
        """Test that IonizationState instances iterate impeccably."""
        try:
            states = [state for state in self.instances[test_name]]
        except Exception:
            raise AtomicError(f"Unable to perform iteration for {test_name}.")

        try:
            integer_charges = [state.integer_charge for state in states]
            ionic_fractions = np.array(
                [state.ionic_fraction for state in states])
            ionic_symbols = [state.ionic_symbol for state in states]
        except Exception:
            raise AtomicError("An attribute may be misnamed or missing.")

        try:
            base_symbol = isotope_symbol(ionic_symbols[0])
        except InvalidIsotopeError:
            base_symbol = atomic_symbol(ionic_symbols[0])
        finally:
            atomic_numb = atomic_number(ionic_symbols[1])

        errors = []

        expected_charges = np.arange(atomic_numb + 1)
        if not np.all(integer_charges == expected_charges):
            errors.append(
                f"The resulting integer charges are {integer_charges}, "
                f"which are not equal to the expected integer charges, "
                f"which are {expected_charges}.")

        expected_fracs = test_cases[test_name]['ionic_fractions']
        if isinstance(expected_fracs, u.Quantity):
            expected_fracs = (expected_fracs / expected_fracs.sum()).value

        if not np.allclose(ionic_fractions, expected_fracs):
            errors.append(
                f"The resulting ionic fractions are {ionic_fractions}, "
                f"which are not equal to the expected ionic fractions "
                f"of {expected_fracs}.")

        expected_particles = [
            Particle(base_symbol, Z=charge) for charge in integer_charges
        ]
        expected_symbols = [
            particle.ionic_symbol for particle in expected_particles
        ]
        if not ionic_symbols == expected_symbols:
            errors.append(
                f"The resulting ionic symbols are {ionic_symbols}, "
                f"which are not equal to the expected ionic symbols of "
                f"{expected_symbols}.")

        if errors:
            errors.insert(
                0, (f"The test of IonizationState named '{test_name}' has "
                    f"resulted in the following errors when attempting to "
                    f"iterate."))
            errmsg = " ".join(errors)
            raise AtomicError(errmsg)
Exemplo n.º 6
0
 def T_e(self, value: u.K):
     """Set the electron temperature."""
     try:
         value = value.to(u.K, equivalencies=u.temperature_energy())
     except (AttributeError, u.UnitsError, u.UnitConversionError):
         raise AtomicError("Invalid temperature.") from None
     else:
         if value < 0 * u.K:
             raise AtomicError("T_e cannot be negative.")
     self._T_e = value
Exemplo n.º 7
0
    def abundances(self, abundances_dict: Optional[Dict]):
        """
        Set the elemental (or isotopic) abundances.  The elements and
        isotopes must be the same as or a superset of the elements whose
        ionization states are being tracked.
        """
        if abundances_dict is None:
            self._pars['abundances'] = {
                elem: np.nan
                for elem in self.base_particles
            }
        elif not isinstance(abundances_dict, dict):
            raise TypeError(f"The abundances attribute must be a dict with "
                            f"elements or isotopes as keys and real numbers "
                            f"representing relative abundances as values.")
        else:
            old_keys = abundances_dict.keys()
            try:
                new_keys_dict = {
                    particle_symbol(old_key): old_key
                    for old_key in old_keys
                }
            except Exception:
                raise AtomicError(
                    f"The key {repr(old_key)} in the abundances "
                    f"dictionary is not a valid element or isotope.")

            new_elements = new_keys_dict.keys()

            old_elements_set = set(self.base_particles)
            new_elements_set = set(new_elements)

            if old_elements_set - new_elements_set:
                raise AtomicError(
                    f"The abundances of the following particles are "
                    f"missing: {old_elements_set - new_elements_set}")

            new_abundances_dict = {}

            for element in new_elements:
                inputted_abundance = abundances_dict[new_keys_dict[element]]
                try:
                    inputted_abundance = float(inputted_abundance)
                except Exception:
                    raise TypeError(
                        f"The abundance for {element} was provided as"
                        f"{inputted_abundance}, which cannot be "
                        f"converted to a real number.") from None

                if inputted_abundance < 0:
                    raise AtomicError(
                        f"The abundance of {element} is negative.")
                new_abundances_dict[element] = inputted_abundance

            self._pars['abundances'] = new_abundances_dict
Exemplo n.º 8
0
 def n_H(self, n):
     if n is None:
         self._pars['n_H'] = n
     else:
         try:
             self._pars['n_H'] = n.to(u.m**-3)
         except u.UnitConversionError:
             raise AtomicError("Units cannot be converted to u.m**-3.")
         except Exception:
             raise AtomicError(
                 f"{n} is not a valid number density.") from None
Exemplo n.º 9
0
    def number_densities(self, value: u.m**-3):
        """Set the number densities for each state."""
        if np.any(value.value < 0):
            raise AtomicError("Number densities cannot be negative.")
        if len(value) != self.atomic_number + 1:
            raise AtomicError(f"Incorrect number of charge states for "
                              f"{self.base_particle}")
        value = value.to(u.m**-3)

        self._n_elem = value.sum()
        self._ionic_fractions = value / self._n_elem
Exemplo n.º 10
0
 def T_e(self, electron_temperature: u.K):
     """Set the electron temperature."""
     try:
         temperature = electron_temperature.to(
             u.K, equivalencies=u.temperature_energy())
     except (AttributeError, u.UnitsError):
         raise AtomicError(
             f"{electron_temperature} is not a valid temperature."
         ) from None
     if temperature < 0 * u.K:
         raise AtomicError("The electron temperature cannot be negative.")
     self._pars['T_e'] = temperature
Exemplo n.º 11
0
 def n(self, n: u.m**-3):
     """Set the number density scaling factor."""
     try:
         n = n.to(u.m**-3)
     except u.UnitConversionError as exc:
         raise AtomicError(
             "Units cannot be converted to u.m ** -3.") from exc
     except Exception as exc:
         raise AtomicError(f"{n} is not a valid number density.") from exc
     if n < 0 * u.m**-3:
         raise AtomicError("Number density cannot be negative.")
     self._pars['n'] = n.to(u.m**-3)
Exemplo n.º 12
0
 def n_elem(self, value):
     """The number density of atoms plus ions of this species."""
     if value is None:
         self._n_elem = None
     else:
         if '_n_e' in dir(self) and self._n_e is not None:
             raise AtomicError(
                 "Only one of n_e and n_elem may be set for a "
                 "single element, quasineutral plasma.")
         try:
             self._n_elem = value.to(u.m**-3)
         except (AttributeError, u.UnitConversionError):
             raise AtomicError(_number_density_errmsg) from None
Exemplo n.º 13
0
 def T_e(self, electron_temperature):
     if electron_temperature is None:
         self._pars['T_e'] = None
     else:
         try:
             temp = electron_temperature.to(
                 u.K, equivalencies=u.temperature_energy())
         except (AttributeError, u.UnitsError):
             raise AtomicError("Invalid electron temperature.")
         else:
             if temp < 0 * u.K:
                 raise AtomicError(
                     "The electron temperature cannot be negative.")
             self._pars['T_e'] = temp
Exemplo n.º 14
0
    def abundances(self, abundances_dict: Optional[Dict]):
        """
        Set the elemental (or isotopic) abundances.  The elements and
        isotopes must be the same as or a superset of the elements whose
        ionization states are being tracked.
        """
        if abundances_dict is None:
            self._pars['abundances'] = None
        elif not isinstance(abundances_dict, dict):
            raise TypeError(
                f"The abundances argument {abundances_dict} must be a dict with elements "
                "or isotopes as keys and ")
        else:
            old_keys = abundances_dict.keys()
            try:
                new_keys_dict = {
                    particle_symbol(old_key): old_key
                    for old_key in old_keys
                }
            except Exception:
                raise AtomicError(
                    "The key {repr(old_key)} in the abundances "
                    "dictionary is not a valid element or isotope.")

            new_elements = new_keys_dict.keys()

            old_elements_set = set(self.elements)
            new_elements_set = set(new_elements)

            if old_elements_set > new_elements_set:
                raise AtomicError(
                    f"The abundances of the following particles are "
                    f"missing: {old_elements_set - new_elements_set}")

            new_abundances_dict = {}

            for element in new_elements:
                inputted_abundance = abundances_dict[new_keys_dict[element]]
                try:
                    inputted_abundance = float(inputted_abundance)
                except Exception:
                    raise TypeError

                if inputted_abundance < 0:
                    raise AtomicError(
                        f"The abundance of {element} is negative.")
                new_abundances_dict[element] = inputted_abundance

            self._pars['abundances'] = new_abundances_dict
Exemplo n.º 15
0
 def number_densities(self):
     """Return the number densities for each state."""
     if self._n_e is not None or self._n_elem is not None:
         return (self.n_elem * self.ionic_fractions).to(u.m**-3)
     else:
         raise AtomicError(
             "Insufficient information to return number densities.")
Exemplo n.º 16
0
    def antiparticle(self):
        """
        Return the corresponding antiparticle, or raise an
        `~plasmapy.utils.AtomicError` if the particle is not an
        elementary particle.

        This attribute may be accessed by using the unary operator ``~``
        acting on a `~plasma.atomic.Particle` instance.

        Examples
        --------
        >>> electron = Particle('e-')
        >>> electron.antiparticle
        Particle("e+")

        >>> antineutron = Particle('antineutron')
        >>> ~antineutron
        Particle("n")

        """
        if self.particle in _antiparticles.keys():
            return Particle(_antiparticles[self.particle])
        else:
            raise AtomicError(
                "The unary operator can only be used for elementary "
                "particles and antiparticles.")
Exemplo n.º 17
0
 def n_e(self, value):
     """
     Return the electron density assuming a single-species
     plasma.
     """
     if value is None:
         self._n_e = None
         return
     elif self._n_elem is not None:
         raise AtomicError("Only one of n_e and n_elem may be set for a "
                           "single element, quasineutral plasma.")
     try:
         self._n_e = value.to(u.m**-3)
         self._n_elem = None
     except (AttributeError, u.UnitConversionError):
         raise AtomicError(_number_density_errmsg)
Exemplo n.º 18
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
Exemplo n.º 19
0
    def __init__(self,
                 particle: Particle,
                 ionic_fractions=None,
                 *,
                 T_e=None,
                 n_e=None,
                 n_elem=None,
                 tol: Union[float, int] = 1e-15):
        """Initialize a `~plasmapy.atomic.IonizationState` instance."""

        self._particle = particle

        try:
            self.tol = tol
            self.T_e = T_e
            self.n_elem = n_elem
            self.n_e = n_e
            self.ionic_fractions = ionic_fractions

            # This functionality has not yet been implemented:

            # if self._ionic_fractions is None and self.T_e is not None:
            #     self.equilibrate()

        except Exception as exc:
            raise AtomicError(f"Unable to create IonizationState instance for "
                              f"{particle.particle}.") from exc
Exemplo n.º 20
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
Exemplo n.º 21
0
    def ionic_fractions(self, fractions):
        """
        Set the ionic fractions, while checking that the new values are
        valid and normalized to one.
        """

        if fractions is None:
            self._ionic_fractions = np.full(self.atomic_number + 1,
                                            np.nan,
                                            dtype=np.float64)
        else:
            try:
                if not isinstance(fractions, np.ndarray) or 'float' not in str(
                        fractions.dtype):
                    fractions = np.array(fractions, dtype=np.float)
            except Exception as exc:
                raise AtomicError(
                    f"Unable to set ionic fractions of {self.element} "
                    f"to {fractions}.") from exc

            if np.min(fractions) < 0:
                raise AtomicError("Ionic fractions cannot be negative.")

            if isinstance(fractions, u.Quantity):
                if self._n_e is not None or self._n_elem is not None:
                    raise AtomicError(
                        "The ionization state may be set using number "
                        "densities for each ion only if neither of the "
                        "electron density and element density has "
                        "already been set.")
                self.number_densities = fractions
            else:

                if not np.any(np.isnan(fractions)):

                    total = np.sum(fractions)
                    if not np.isclose(total, 1, atol=self.tol, rtol=0):
                        raise AtomicError(
                            f"The sum of the ionic fractions of {self.element} "
                            f"equals {total}, which is not approximately one.")
                    if not len(fractions) == self.atomic_number + 1:
                        raise AtomicError(
                            f"len(fractions) equals {len(fractions)}, but "
                            f"should equal {self.atomic_number + 1} which "
                            f"is the atomic number of {self.element} + 1.")

                self._ionic_fractions = fractions
Exemplo n.º 22
0
    def test_ionic_fractions(self, test):

        errmsg = ""

        elements_actual = self.instances[test].elements
        inputs = tests[test]["inputs"]

        if isinstance(inputs, dict):

            input_keys = tests[test]["inputs"].keys()
            for element, input_key in zip(elements_actual, input_keys):

                expected = np.array(tests[test]["inputs"][input_key])

                if isinstance(expected, u.Quantity):
                    expected = np.array(expected.value /
                                        np.sum(expected.value))

                #if not isinstance(expected, np.ndarray)

                actual = self.instances[test].ionic_fractions[element]

                if not np.allclose(actual, expected):
                    errmsg += (
                        f"\n\nThere is a discrepancy in ionic fractions for "
                        f"({test}, {element}, {input_key})\n"
                        f"  expected = {expected}\n"
                        f"    actual = {actual}")
                if not isinstance(actual, np.ndarray) or isinstance(
                        actual, u.Quantity):
                    raise AtomicError(
                        f"\n\nNot a numpy.ndarray: ({test}, {element})")

        else:
            elements_expected = {
                particle_symbol(element)
                for element in inputs
            }

            assert set(self.instances[test].elements) == elements_expected

            for element in elements_expected:
                assert all(
                    np.isnan(self.instances[test].ionic_fractions[element]))

        if errmsg:
            raise AtomicError(errmsg)
Exemplo n.º 23
0
 def n_H(self):
     """
     The number density of hydrogen neutrals and atoms of all
     isotopes, if defined.
     """
     if 'H' not in self.elements or self._pars['n_H'] is None:
         raise AtomicError("The number density of hydrogen is not ")
     return self._pars['n_H']
Exemplo n.º 24
0
 def log_abundances(self):
     if self._pars['abundances'] is not None:
         log_abundances_dict = {}
         for key in self.abundances.keys():
             log_abundances_dict[key] = np.log10(self.abundances[key])
         return log_abundances_dict
     else:
         raise AtomicError("No abundances are available.")
Exemplo n.º 25
0
    def __init__(self,
                 inputs: Union[Dict[str, np.ndarray], List, Tuple],
                 *,
                 T_e: u.K = np.nan * u.K,
                 equilibrate: Optional[bool] = None,
                 abundances: Optional[Dict[str, Real]] = None,
                 log_abundances: Optional[Dict[str, Real]] = None,
                 n: u.m**-3 = np.nan * u.m**-3,
                 tol: Real = 1e-15,
                 kappa: Real = np.inf):

        abundances_provided = abundances is not None or log_abundances is not None

        set_abundances = True
        if isinstance(inputs, dict):
            all_quantities = np.all(
                [isinstance(fracs, u.Quantity) for fracs in inputs.values()])
            if all_quantities:
                right_units = np.all(
                    [fracs[0].si.unit == u.m**-3 for fracs in inputs.values()])
                if not right_units:
                    raise AtomicError(
                        "Units must be inverse volume for number densities.")
                if abundances_provided:
                    raise AtomicError(
                        "Abundances cannot be provided if inputs "
                        "provides number density information.")
                set_abundances = False

        try:
            self._pars = collections.defaultdict(lambda: None)
            self.T_e = T_e
            self.n = n
            self.tol = tol
            self.ionic_fractions = inputs
            if set_abundances:
                self.abundances = abundances
                self.log_abundances = log_abundances
            self.kappa = kappa
        except Exception as exc:
            raise AtomicError(
                "Unable to create IonizationStates instance.") from exc

        if equilibrate:
            self.equilibrate()  # for now, this raises a NotImplementedError
Exemplo n.º 26
0
    def __eq__(self, other):

        if not isinstance(other, IonizationStates):
            raise TypeError(
                "IonizationStates instance can only be compared with "
                "other IonizationStates instances.")

        if self.base_particles != other.base_particles:
            raise AtomicError(
                "Two IonizationStates instances can be compared only "
                "if the base particles are the same.")

        min_tol = np.min([self.tol, other.tol])

        # Check any of a whole bunch of equality measures, recalling
        # that np.nan == np.nan is False.

        for attribute in ['T_e', 'n_e', 'kappa']:
            this = eval(f"self.{attribute}")
            that = eval(f"other.{attribute}")

            # TODO: Maybe create a function in utils called same_enough
            # TODO: that would take care of all of these disparate
            # TODO: equality measures.

            this_equals_that = np.any([
                this == that,
                this is that,
                np.isnan(this) and np.isnan(that),
                np.isinf(this) and np.isinf(that),
                u.quantity.allclose(this, that, rtol=min_tol),
            ])

            if not this_equals_that:
                return False

        for attribute in ['ionic_fractions', 'number_densities']:

            this_dict = eval(f"self.{attribute}")
            that_dict = eval(f"other.{attribute}")

            for particle in self.base_particles:

                this = this_dict[particle]
                that = that_dict[particle]

                this_equals_that = np.any([
                    this is that,
                    np.all(np.isnan(this)) and np.all(np.isnan(that)),
                    u.quantity.allclose(this, that, rtol=min_tol),
                ])

                if not this_equals_that:
                    return False

        return True
Exemplo n.º 27
0
    def log_abundances(self, value):

        if value is not None:
            try:
                new_abundances_input = {}
                for key in value.keys():
                    new_abundances_input[key] = 10**value[key]
                self.abundances = new_abundances_input
            except Exception as exc:
                raise AtomicError("Invalid log_abundances.")
Exemplo n.º 28
0
    def ionic_fractions(self, fractions):
        """
        Set the ionic fractions, while checking that the new values are
        valid and normalized to one.
        """
        if fractions is None or np.all(np.isnan(fractions)):
            self._ionic_fractions = np.full(self.atomic_number + 1,
                                            np.nan,
                                            dtype=np.float64)
            return

        try:
            if np.min(fractions) < 0:
                raise AtomicError("Cannot have negative ionic fractions.")

            if len(fractions) != self.atomic_number + 1:
                raise AtomicError("The length of ionic_fractions must be "
                                  f"{self.atomic_number + 1}.")

            if isinstance(fractions, u.Quantity):
                fractions = fractions.to(u.m**-3)
                self.n_elem = np.sum(fractions)
                self._ionic_fractions = np.array(fractions / self.n_elem)
            else:
                fractions = np.array(fractions, dtype=np.float64)
                sum_of_fractions = np.sum(fractions)
                all_nans = np.all(np.isnan(fractions))

                if not all_nans:
                    if np.any(fractions < 0) or np.any(fractions > 1):
                        raise AtomicError(
                            "Ionic fractions must be between 0 and 1.")

                    if not np.isclose(
                            sum_of_fractions, 1, rtol=0, atol=self.tol):
                        raise AtomicError("Ionic fractions must sum to one.")

                self._ionic_fractions = fractions

        except Exception as exc:
            raise AtomicError(
                f"Unable to set ionic fractions of {self.element} "
                f"to {fractions}.") from exc
Exemplo n.º 29
0
    def T_e(self, value):

        if value is None:
            self._T_e = None
        else:
            try:
                value = value.to(u.K, equivalencies=u.temperature_energy())
            except (AttributeError, u.UnitsError):
                raise AtomicError("Invalid temperature.") from None
            self._T_e = value
Exemplo n.º 30
0
 def log_abundances(self, value: Optional[Dict[str, Real]]):
     """
     Set the base 10 logarithm of the relative abundances.
     """
     if value is not None:
         try:
             new_abundances_input = {}
             for key in value.keys():
                 new_abundances_input[key] = 10**value[key]
             self.abundances = new_abundances_input
         except Exception:
             raise AtomicError("Invalid log_abundances.") from None