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))
def Z_mean(self) -> np.float64: """Return the mean charge number.""" 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 * self.charge_numbers)
def ionic_levels( particle: Particle, min_charge: Integral = 0, max_charge: Optional[Integral] = None, ) -> ParticleList: """ Return a |ParticleList| that includes different ionic levels of a base atom. Parameters ---------- particle : `~plasmapy.particles.particle_class.ParticleLike` Representation of an element, ion, or isotope. min_charge : integer, optional The starting charge number. Defaults to ``0``. max_charge : integer, optional The ending charge number, which will be included in the |ParticleList|. Defaults to the atomic number. Returns ------- `~plasmapy.particles.particle_collections.ParticleList` The ionic levels of the atom provided from ``min_charge`` to ``max_charge``. Examples -------- >>> from plasmapy.particles import ionic_levels >>> ionic_levels("He") ParticleList(['He 0+', 'He 1+', 'He 2+']) >>> ionic_levels("Fe-56", min_charge=13, max_charge=15) ParticleList(['Fe-56 13+', 'Fe-56 14+', 'Fe-56 15+']) """ base_particle = Particle(particle.isotope or particle.element) if max_charge is None: max_charge = particle.atomic_number if not min_charge <= max_charge <= particle.atomic_number: raise ChargeError( f"Need min_charge ({min_charge}) " f"≤ max_charge ({max_charge}) " f"≤ atomic number ({base_particle.atomic_number})." ) return ParticleList( [Particle(base_particle, Z=Z) for Z in range(min_charge, max_charge + 1)] )
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
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
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
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