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 test_mean_particle(): """ Test that ``ParticleList.average_particle()`` returns a particle with the mean mass and mean charge of a |ParticleList|. """ massless_uncharged_particle = CustomParticle(mass=0 * u.kg, charge=0 * u.C) particle_list = ParticleList([proton, electron, alpha, massless_uncharged_particle]) expected_mass = (proton.mass + electron.mass + alpha.mass) / 4 expected_charge = (proton.charge + electron.charge + alpha.charge) / 4 average_particle = particle_list.average_particle() assert u.isclose(average_particle.mass, expected_mass, rtol=1e-14) assert u.isclose(average_particle.charge, expected_charge, rtol=1e-14)
def test_weighted_mean_particle(): """ Test that ``ParticleList.average_particle()`` returns a particle with the weighted mean. """ custom_proton = CustomParticle(mass=proton.mass, charge=proton.charge) particle_list = ParticleList([proton, electron, alpha, custom_proton]) abundances = [1, 2, 0, 1] expected_mass = (proton.mass + electron.mass) / 2 expected_charge = 0 * u.C average_particle = particle_list.average_particle(abundances=abundances) assert u.isclose(average_particle.mass, expected_mass, rtol=1e-14) assert u.isclose(average_particle.charge, expected_charge, rtol=1e-14)
def test_comparison_to_equivalent_particle_list(physical_property, use_rms): """ Test that `IonizationState.average_ion` gives consistent results with `ParticleList.average_particle` when the ratios of different particles is the same between the `IonizationState` and the `ParticleList`. """ particles = ParticleList(2 * ["He-4 0+"] + 3 * ["He-4 1+"] + 5 * ["He-4 2+"]) ionization_state = IonizationState("He-4", [0.2, 0.3, 0.5]) kwargs = {f"use_rms_{physical_property}": True} expected_average_particle = particles.average_particle(**kwargs) expected_average_quantity = getattr(expected_average_particle, physical_property) actual_average_particle = ionization_state.average_ion(**kwargs) actual_average_quantity = getattr(actual_average_particle, physical_property) assert_quantity_allclose(actual_average_quantity, expected_average_quantity)
def test_root_mean_square_particle(use_rms_charge, use_rms_mass): """ Test that ``ParticleList.average_particle`` returns the mean or root mean square of the charge and mass, as appropriate. """ particle_list = ParticleList(["p+", "e-"]) average_particle = particle_list.average_particle( use_rms_charge=use_rms_charge, use_rms_mass=use_rms_mass ) expected_average_charge = (1 if use_rms_charge else 0) * proton.charge assert u.isclose(average_particle.charge, expected_average_charge, rtol=1e-14) if use_rms_mass: expected_average_mass = np.sqrt((proton.mass**2 + electron.mass**2) / 2) else: expected_average_mass = (proton.mass + electron.mass) / 2 assert u.isclose(average_particle.mass, expected_average_mass, atol=1e-35 * u.kg)
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, )