def test_weighted_averages_of_particles( particle_multiplicities: Dict[ParticleLike, int], use_rms_charge, use_rms_mass, ): """ Compare the mass and charge of the average particle for two |ParticleList| instances. The first |ParticleList| contains repeated particles. The second |ParticleList| contains only one of each kind of particle present in the first list, with the number of each particle recorded in a separate array. The unweighted averages of the first |ParticleList| should equal the weighted averages of the second |ParticleList|, with the number of each particle provided as the abundances. """ all_particles = ParticleList([]) for particle, multiplicity in particle_multiplicities.items(): all_particles.extend(ParticleList(multiplicity * [particle])) unique_particles = ParticleList(particle_multiplicities.keys()) number_of_each_particle = list(particle_multiplicities.values()) unweighted_mean_of_all_particles = all_particles.average_particle( use_rms_charge=use_rms_charge, use_rms_mass=use_rms_mass, ) weighted_mean_of_unique_particles = unique_particles.average_particle( use_rms_charge=use_rms_charge, use_rms_mass=use_rms_mass, abundances=number_of_each_particle, ) assert u.isclose( unweighted_mean_of_all_particles.mass, weighted_mean_of_unique_particles.mass, rtol=1e-14, equal_nan=True, ) assert u.isclose( unweighted_mean_of_all_particles.charge, weighted_mean_of_unique_particles.charge, rtol=1e-14, equal_nan=True, ) if len(unique_particles) == 1 and isinstance(unique_particles[0], Particle): assert isinstance(unweighted_mean_of_all_particles, Particle) assert isinstance(weighted_mean_of_unique_particles, Particle)
def average_ion( self, *, include_neutrals: bool = True, use_rms_charge: bool = False, use_rms_mass: bool = False, ) -> CustomParticle: """ Return a |CustomParticle| representing the mean particle included across all ionization states. By default, this method will use the weighted mean to calculate the properties of the |CustomParticle|, where the weights for each ionic level is given by its ionic fraction multiplied by the abundance of the base element or isotope. If ``use_rms_charge`` or ``use_rms_mass`` is `True`, then this method will return the root mean square of the charge or mass, respectively. Parameters ---------- include_neutrals : `bool`, optional, keyword-only If `True`, include neutrals when calculating the mean values of the different particles. If `False`, exclude neutrals. Defaults to `True`. use_rms_charge : `bool`, optional, keyword-only If `True`, use the root mean square charge instead of the mean charge. Defaults to `False`. use_rms_mass : `bool`, optional, keyword-only If `True`, use the root mean square mass instead of the mean mass. Defaults to `False`. Raises ------ `~plasmapy.particles.exceptions.ParticleError` If the abundance of any of the elements or isotopes is not defined and the |IonizationStateCollection| instance includes more than one element or isotope. Returns ------- ~plasmapy.particles.particle_class.CustomParticle Examples -------- >>> states = IonizationStateCollection( ... {"H": [0.1, 0.9], "He": [0, 0.1, 0.9]}, ... abundances={"H": 1, "He": 0.1} ... ) >>> states.average_ion() CustomParticle(mass=2.12498...e-27 kg, charge=1.5876...e-19 C) >>> states.average_ion(include_neutrals=False, use_rms_charge=True, use_rms_mass=True) CustomParticle(mass=2.633...e-27 kg, charge=1.805...e-19 C) """ min_charge = 0 if include_neutrals else 1 all_particles = ParticleList() all_abundances = [] for base_particle in self.base_particles: ionization_state = self[base_particle] ionic_levels = ionization_state.to_list()[min_charge:] all_particles.extend(ionic_levels) base_particle_abundance = self.abundances[base_particle] if np.isnan(base_particle_abundance): if len(self) == 1: base_particle_abundance = 1 else: raise ParticleError( "Unable to provide an average particle without abundances." ) ionic_fractions = ionization_state.ionic_fractions[min_charge:] ionic_abundances = base_particle_abundance * ionic_fractions all_abundances.extend(ionic_abundances) return all_particles.average_particle( use_rms_charge=use_rms_charge, use_rms_mass=use_rms_mass, abundances=all_abundances, )