class dielectric: """ Benchmark that times the performance of funcions from Physics/dielectric package """ B = 2 * u.T species = ['e', 'D+'] n = [1e18 * u.m**-3, 1e18 * u.m**-3] omega = 3.7e9 * (2 * pi) * (u.rad / u.s) T = 30 * 11600 * u.K particle = 'Ne' z_mean = 8 * u.dimensionless_unscaled vTh = parameters.thermal_speed(T, particle, method="most_probable") kWave = omega / vTh n_permittivity_1D_Maxwellian = 1e18 * u.cm**-3 def setup(self): pass def time_cold_plasma_permittivity_SDP(self): permittivity = S, D, P = cold_plasma_permittivity_SDP( dielectric.B, dielectric.species, dielectric.n, dielectric.omega) def time_cold_plasma_permittivity_LRP(self): permittivity = S, D, P = cold_plasma_permittivity_LRP( dielectric.B, dielectric.species, dielectric.n, dielectric.omega) def time_permittivity_1D_Maxwellian(self): permittivity_1D_Maxwellian(dielectric.omega, dielectric.kWave, dielectric.T, dielectric.n_permittivity_1D_Maxwellian, dielectric.particle, dielectric.z_mean)
def spectral_density( wavelengths: u.nm, probe_wavelength: u.nm, n: u.m**-3, Te: u.K, Ti: u.K, efract: np.ndarray = None, ifract: np.ndarray = None, ion_species: Union[str, List[str], Particle, List[Particle]] = "H+", electron_vel: u.m / u.s = None, ion_vel: u.m / u.s = None, probe_vec=np.array([1, 0, 0]), scatter_vec=np.array([0, 1, 0]), ) -> Tuple[Union[np.floating, np.ndarray], np.ndarray]: r""" Calculate the spectral density function for Thomson scattering of a probe laser beam by a multi-species Maxwellian plasma. This function calculates the spectral density function for Thomson scattering of a probe laser beam by a plasma consisting of one or more ion species and a one or more thermal electron populations (the entire plasma is assumed to be quasi-neutral) .. math:: S(k,\omega) = \sum_e \frac{2\pi}{k} \bigg |1 - \frac{\chi_e}{\epsilon} \bigg |^2 f_{e0,e} \bigg (\frac{\omega}{k} \bigg ) + \sum_i \frac{2\pi Z_i}{k} \bigg |\frac{\chi_e}{\epsilon} \bigg |^2 f_{i0,i} \bigg ( \frac{\omega}{k} \bigg ) where :math:`\chi_e` is the electron component susceptibility of the plasma and :math:`\epsilon = 1 + \sum_e \chi_e + \sum_i \chi_i` is the total plasma dielectric function (with :math:`\chi_i` being the ion component of the susceptibility), :math:`Z_i` is the charge of each ion, :math:`k` is the scattering wavenumber, :math:`\omega` is the scattering frequency, and :math:`f_{e0,e}` and :math:`f_{i0,i}` are the electron and ion velocity distribution functions respectively. In this function the electron and ion velocity distribution functions are assumed to be Maxwellian, making this function equivalent to Eq. 3.4.6 in `Sheffield`_. Parameters ---------- wavelengths : `~astropy.units.Quantity` Array of wavelengths over which the spectral density function will be calculated. (convertible to nm) probe_wavelength : `~astropy.units.Quantity` Wavelength of the probe laser. (convertible to nm) n : `~astropy.units.Quantity` Mean (0th order) density of all plasma components combined. (convertible to cm^-3.) Te : `~astropy.units.Quantity`, shape (Ne, ) Temperature of each electron component. Shape (Ne, ) must be equal to the number of electron components Ne. (in K or convertible to eV) Ti : `~astropy.units.Quantity`, shape (Ni, ) Temperature of each ion component. Shape (Ni, ) must be equal to the number of ion components Ni. (in K or convertible to eV) efract : array_like, shape (Ne, ), optional An array-like object where each element represents the fraction (or ratio) of the electron component number density to the total electron number density. Must sum to 1.0. Default is a single electron component. ifract : array_like, shape (Ni, ), optional An array-like object where each element represents the fraction (or ratio) of the ion component number density to the total ion number density. Must sum to 1.0. Default is a single ion species. ion_species : str or `~plasmapy.particles.Particle`, shape (Ni, ), optional A list or single instance of `~plasmapy.particles.Particle`, or strings convertible to `~plasmapy.particles.Particle`. Default is `'H+'` corresponding to a single species of hydrogen ions. electron_vel : `~astropy.units.Quantity`, shape (Ne, 3), optional Velocity of each electron component in the rest frame. (convertible to m/s) Defaults to a stationary plasma [0, 0, 0] m/s. ion_vel : `~astropy.units.Quantity`, shape (Ni, 3), optional Velocity vectors for each electron population in the rest frame (convertible to m/s) Defaults zero drift for all specified ion species. probe_vec : float `~numpy.ndarray`, shape (3, ) Unit vector in the direction of the probe laser. Defaults to [1, 0, 0]. scatter_vec : float `~numpy.ndarray`, shape (3, ) Unit vector pointing from the scattering volume to the detector. Defaults to [0, 1, 0] which, along with the default `probe_vec`, corresponds to a 90 degree scattering angle geometry. Returns ------- alpha : float Mean scattering parameter, where `alpha` > 1 corresponds to collective scattering and `alpha` < 1 indicates non-collective scattering. The scattering parameter is calculated based on the total plasma density n. Skw : `~astropy.units.Quantity` Computed spectral density function over the input `wavelengths` array with units of s/rad. Notes ----- For details, see "Plasma Scattering of Electromagnetic Radiation" by Sheffield et al. `ISBN 978\\-0123748775`_. This code is a modified version of the program described therein. For a concise summary of the relevant physics, see Chapter 5 of Derek Schaeffer's thesis, DOI: `10.5281/zenodo.3766933`_. .. _`ISBN 978\\-0123748775`: https://www.sciencedirect.com/book/9780123748775/plasma-scattering-of-electromagnetic-radiation .. _`10.5281/zenodo.3766933`: https://doi.org/10.5281/zenodo.3766933 .. _`Sheffield`: https://doi.org/10.1016/B978-0-12-374877-5.00003-8 """ if efract is None: efract = np.ones(1) else: efract = np.asarray(efract, dtype=np.float64) if ifract is None: ifract = np.ones(1) else: ifract = np.asarray(ifract, dtype=np.float64) # If electon velocity is not specified, create an array corresponding # to zero drift if electron_vel is None: electron_vel = np.zeros([efract.size, 3]) * u.m / u.s # If ion drift velocity is not specified, create an array corresponding # to zero drift if ion_vel is None: ion_vel = np.zeros([ifract.size, 3]) * u.m / u.s # Condition ion_species if isinstance(ion_species, (str, Particle)): ion_species = [ion_species] if len(ion_species) == 0: raise ValueError("At least one ion species needs to be defined.") for ii, ion in enumerate(ion_species): if isinstance(ion, Particle): continue ion_species[ii] = Particle(ion) # Condition Te if Te.size == 1: # If a single quantity is given, put it in an array so it's iterable # If Te.size != len(efract), assume same temp. for all species Te = np.repeat(Te, len(efract)) elif Te.size != len(efract): raise ValueError(f"Got {Te.size} electron temperatures and expected " f"{len(efract)}.") # Condition Ti if Ti.size == 1: # If a single quantity is given, put it in an array so it's iterable # If Ti.size != len(ion_species), assume same temp. for all species Ti = [Ti.value] * len(ion_species) * Ti.unit elif Ti.size != len(ion_species): raise ValueError(f"Got {Ti.size} ion temperatures and expected " f"{len(ion_species)}.") # Make sure the sizes of ion_species, ifract, ion_vel, and Ti all match if ((len(ion_species) != ifract.size) or (ion_vel.shape[0] != ifract.size) or (Ti.size != ifract.size)): raise ValueError( f"Inconsistent number of species in ifract ({ifract}), " f"ion_species ({len(ion_species)}), Ti ({Ti.size}), " f"and/or ion_vel ({ion_vel.shape[0]}).") # Make sure the sizes of efract, electron_vel, and Te all match if (electron_vel.shape[0] != efract.size) or (Te.size != efract.size): raise ValueError( f"Inconsistent number of electron populations in efract ({efract.size}), " f"Te ({Te.size}), or electron velocity ({electron_vel.shape[0]}).") # Ensure unit vectors are normalized probe_vec = probe_vec / np.linalg.norm(probe_vec) scatter_vec = scatter_vec / np.linalg.norm(scatter_vec) # Define some constants C = const.c.si # speed of light # Calculate plasma parameters vTe = thermal_speed(Te, particle="e-") vTi, ion_z = [], [] for T, ion in zip(Ti, ion_species): vTi.append(thermal_speed(T, particle=ion).value) ion_z.append(ion.integer_charge * u.dimensionless_unscaled) vTi = vTi * vTe.unit zbar = np.sum(ifract * ion_z) ne = efract * n ni = ifract * n / zbar # ne/zbar = sum(ni) # wpe is calculated for the entire plasma (all electron populations combined) wpe = plasma_frequency(n=n, particle="e-") # Convert wavelengths to angular frequencies (electromagnetic waves, so # phase speed is c) ws = (2 * np.pi * u.rad * C / wavelengths).to(u.rad / u.s) wl = (2 * np.pi * u.rad * C / probe_wavelength).to(u.rad / u.s) # Compute the frequency shift (required by energy conservation) w = ws - wl # Compute the wavenumbers in the plasma # See Sheffield Sec. 1.8.1 and Eqs. 5.4.1 and 5.4.2 ks = np.sqrt(ws**2 - wpe**2) / C kl = np.sqrt(wl**2 - wpe**2) / C # Compute the wavenumber shift (required by momentum conservation) scattering_angle = np.arccos(np.dot(probe_vec, scatter_vec)) # Eq. 1.7.10 in Sheffield k = np.sqrt(ks**2 + kl**2 - 2 * ks * kl * np.cos(scattering_angle)) # Normal vector along k k_vec = (scatter_vec - probe_vec) * u.dimensionless_unscaled # Compute Doppler-shifted frequencies for both the ions and electrons # Matmul is simultaneously conducting dot product over all wavelengths # and ion components w_e = w - np.matmul(electron_vel, np.outer(k, k_vec).T) w_i = w - np.matmul(ion_vel, np.outer(k, k_vec).T) # Compute the scattering parameter alpha # expressed here using the fact that v_th/w_p = root(2) * Debye length alpha = np.sqrt(2) * wpe / np.outer(k, vTe) # Calculate the normalized phase velocities (Sec. 3.4.2 in Sheffield) xe = (np.outer(1 / vTe, 1 / k) * w_e).to(u.dimensionless_unscaled) xi = (np.outer(1 / vTi, 1 / k) * w_i).to(u.dimensionless_unscaled) # Calculate the susceptibilities chiE = np.zeros([efract.size, w.size], dtype=np.complex128) for i, fract in enumerate(efract): chiE[i, :] = permittivity_1D_Maxwellian(w_e[i, :], k, Te[i], ne[i], "e-") # Treatment of multiple species is an extension of the discussion in # Sheffield Sec. 5.1 chiI = np.zeros([ifract.size, w.size], dtype=np.complex128) for i, ion in enumerate(ion_species): chiI[i, :] = permittivity_1D_Maxwellian(w_i[i, :], k, Ti[i], ni[i], ion, z_mean=ion_z[i]) # Calculate the longitudinal dielectric function epsilon = 1 + np.sum(chiE, axis=0) + np.sum(chiI, axis=0) econtr = np.zeros([efract.size, w.size], dtype=np.complex128) * u.s / u.rad for m in range(efract.size): econtr[m, :] = efract[m] * (2 * np.sqrt(np.pi) / k / vTe[m] * np.power( np.abs(1 - np.sum(chiE, axis=0) / epsilon), 2) * np.exp(-xe[m, :]**2)) icontr = np.zeros([ifract.size, w.size], dtype=np.complex128) * u.s / u.rad for m in range(ifract.size): icontr[m, :] = ifract[m] * ( 2 * np.sqrt(np.pi) * ion_z[m] / k / vTi[m] * np.power(np.abs(np.sum(chiE, axis=0) / epsilon), 2) * np.exp(-xi[m, :]**2)) # Recast as real: imaginary part is already zero Skw = np.real(np.sum(econtr, axis=0) + np.sum(icontr, axis=0)) return np.mean(alpha), Skw
def test_gyroradius(): r"""Test the gyroradius function in parameters.py.""" assert gyroradius(B, "e-", T_i=T_e).unit.is_equivalent(u.m) assert gyroradius(B, "e-", Vperp=25 * u.m / u.s).unit.is_equivalent(u.m) Vperp = 1e6 * u.m / u.s Bmag = 1 * u.T omega_ce = gyrofrequency(Bmag, "e-") analytical_result = (Vperp / omega_ce).to( u.m, equivalencies=u.dimensionless_angles()) assert gyroradius(Bmag, "e-", Vperp=Vperp) == analytical_result with pytest.raises(TypeError): gyroradius(u.T, "e-") with pytest.raises(u.UnitTypeError): gyroradius(5 * u.A, "e-", Vperp=8 * u.m / u.s) with pytest.raises(u.UnitTypeError): gyroradius(5 * u.T, "e-", Vperp=8 * u.m) with pytest.raises(ValueError): gyroradius(np.array([5, 6]) * u.T, "e-", Vperp=np.array([5, 6, 7]) * u.m / u.s) assert np.isnan(gyroradius(np.nan * u.T, "e-", Vperp=1 * u.m / u.s)) with pytest.raises(ValueError): gyroradius(3.14159 * u.T, "e-", T_i=-1 * u.K) with pytest.warns(u.UnitsWarning): assert gyroradius(1.0, "e-", Vperp=1.0) == gyroradius(1.0 * u.T, "e-", Vperp=1.0 * u.m / u.s) with pytest.warns(u.UnitsWarning): assert gyroradius(1.1, "e-", T_i=1.2) == gyroradius(1.1 * u.T, "e-", T_i=1.2 * u.K) with pytest.raises(ValueError): gyroradius(1.1 * u.T, "e-", Vperp=1 * u.m / u.s, T_i=1.2 * u.K) with pytest.raises(u.UnitTypeError): gyroradius(1.1 * u.T, "e-", Vperp=1.1 * u.m, T_i=1.2 * u.K) assert gyroradius(B, particle="p", T_i=T_i).unit.is_equivalent(u.m) assert gyroradius(B, particle="p", Vperp=25 * u.m / u.s).unit.is_equivalent(u.m) # Case when Z=1 is assumed assert np.isclose( gyroradius(B, particle="p", T_i=T_i), gyroradius(B, particle="H+", T_i=T_i), atol=1e-6 * u.m, ) gyroPos = gyroradius(B, particle="p", Vperp=V) gyroNeg = gyroradius(B, particle="p", Vperp=-V) assert gyroPos == gyroNeg Vperp = 1e6 * u.m / u.s Bmag = 1 * u.T omega_ci = gyrofrequency(Bmag, particle="p") analytical_result = (Vperp / omega_ci).to( u.m, equivalencies=u.dimensionless_angles()) assert gyroradius(Bmag, particle="p", Vperp=Vperp) == analytical_result T2 = 1.2 * u.MK B2 = 123 * u.G particle2 = "alpha" Vperp2 = thermal_speed(T2, particle=particle2) gyro_by_vperp = gyroradius(B2, particle="alpha", Vperp=Vperp2) assert gyro_by_vperp == gyroradius(B2, particle="alpha", T_i=T2) explicit_positron_gyro = gyroradius(1 * u.T, particle="positron", T_i=1 * u.MK) assert explicit_positron_gyro == gyroradius(1 * u.T, "e-", T_i=1 * u.MK) with pytest.raises(TypeError): gyroradius(u.T, particle="p", Vperp=8 * u.m / u.s) with pytest.raises(ValueError): gyroradius(B, particle="p", T_i=-1 * u.K) with pytest.warns(u.UnitsWarning): gyro_without_units = gyroradius(1.0, particle="p", Vperp=1.0) gyro_with_units = gyroradius(1.0 * u.T, particle="p", Vperp=1.0 * u.m / u.s) assert gyro_without_units == gyro_with_units with pytest.warns(u.UnitsWarning): gyro_t_without_units = gyroradius(1.1, particle="p", T_i=1.2) gyro_t_with_units = gyroradius(1.1 * u.T, particle="p", T_i=1.2 * u.K) assert gyro_t_with_units == gyro_t_without_units with pytest.raises(ValueError): gyroradius(1.1 * u.T, particle="p", Vperp=1 * u.m / u.s, T_i=1.2 * u.K) with pytest.raises(u.UnitTypeError): gyroradius(1.1 * u.T, particle="p", Vperp=1.1 * u.m, T_i=1.2 * u.K) with pytest.raises(u.UnitTypeError): gyroradius(1.1 * u.T, particle="p", Vperp=1.2 * u.m, T_i=1.1 * u.K)
def test_thermal_speed(): r"""Test the thermal_speed function in parameters.py""" assert thermal_speed(T_e, "e-").unit.is_equivalent(u.m / u.s) assert thermal_speed(T_e, "e-") > thermal_speed(T_e, "p") # The NRL Plasma Formulary uses a definition of the electron # thermal speed that differs by a factor of sqrt(2). assert np.isclose(thermal_speed(1 * u.MK, "e-").value, 5505694.743141063) with pytest.raises(u.UnitTypeError): thermal_speed(5 * u.m, "e-") with pytest.raises(ValueError): thermal_speed(-T_e, "e-") with pytest.warns(RelativityWarning): thermal_speed(1e9 * u.K, "e-") with pytest.raises(RelativityError): thermal_speed(5e19 * u.K, "e-") with pytest.warns(u.UnitsWarning): assert thermal_speed(1e5, "e-") == thermal_speed(1e5 * u.K, "e-") assert thermal_speed(T_i, particle="p").unit.is_equivalent(u.m / u.s) # The NRL Plasma Formulary uses a definition of the particle thermal # speed that differs by a factor of sqrt(2). assert np.isclose( thermal_speed(1 * u.MK, particle="p").si.value, 128486.56960876315) # Explicitly check all three modes and dimensionalities # ndim = 1 assert np.isclose( thermal_speed(T_e, "e-", method="most_probable", ndim=1).si.value, 0.0) # Regression tests start here! assert np.isclose( thermal_speed(T_e, "e-", method="rms", ndim=1).si.value, 3893114.2008620175) assert np.isclose( thermal_speed(T_e, "e-", method="mean_magnitude", ndim=1).si.value, 3106255.714310189, ) # ndim = 2 assert np.isclose( thermal_speed(T_e, "e-", method="most_probable", ndim=2).si.value, 3893114.2008620175, ) assert np.isclose( thermal_speed(T_e, "e-", method="rms", ndim=2).si.value, 5505694.902726359) assert np.isclose( thermal_speed(T_e, "e-", method="mean_magnitude", ndim=2).si.value, 4879295.066124102, ) # ndim = 3 assert np.isclose( thermal_speed(T_e, "e-", method="most_probable", ndim=3).si.value, 5505694.902726359, ) assert np.isclose( thermal_speed(T_e, "e-", method="rms", ndim=3).si.value, 6743071.595560921) assert np.isclose( thermal_speed(T_e, "e-", method="mean_magnitude", ndim=3).si.value, 6212511.428620378, ) # Case when Z=1 is assumed assert thermal_speed(T_i, particle="p") == thermal_speed(T_i, particle="H-1+") assert thermal_speed(1 * u.MK, particle="e+") == thermal_speed(1 * u.MK, "e-") with pytest.raises(u.UnitTypeError): thermal_speed(5 * u.m, particle="p") with pytest.raises(ValueError): thermal_speed(-T_e, particle="p") with pytest.warns(RelativityWarning): thermal_speed(1e11 * u.K, particle="p") with pytest.raises(RelativityError): thermal_speed(1e14 * u.K, particle="p") with pytest.raises(InvalidParticleError): thermal_speed(T_i, particle="asdfasd") with pytest.warns(u.UnitsWarning): assert thermal_speed(1e6, particle="p") == thermal_speed(1e6 * u.K, particle="p") assert np.isclose( thermal_speed(1e6 * u.K, "e-", method="mean_magnitude").si.value, 6212510.3969422, ) assert np.isclose( thermal_speed(1e6 * u.K, "e-", method="rms").si.value, 6743070.475775486) # Test invalid method with pytest.raises(ValueError): thermal_speed(T_i, "e-", method="sadks") # Test invalid ndim with pytest.raises(ValueError): thermal_speed(T_i, "e-", ndim=4) assert_can_handle_nparray(thermal_speed)
def permittivity_1D_Maxwellian( omega: u.rad / u.s, kWave: u.rad / u.m, T: u.K, n: u.m ** -3, particle, z_mean: u.dimensionless_unscaled = None, ) -> u.dimensionless_unscaled: r""" The classical dielectric permittivity for a 1D Maxwellian plasma. This function can calculate both the ion and electron permittivities. No additional effects are considered (e.g. magnetic fields, relativistic effects, strongly coupled regime, etc.) Parameters ---------- omega : ~astropy.units.Quantity The frequency in rad/s of the electromagnetic wave propagating through the plasma. kWave : ~astropy.units.Quantity The corresponding wavenumber, in rad/m, of the electromagnetic wave propagating through the plasma. This is often modulated by the dispersion of the plasma or by relativistic effects. See em_wave.py for ways to calculate this. T : ~astropy.units.Quantity The plasma temperature - this can be either the electron or the ion temperature, but should be consistent with density and particle. n : ~astropy.units.Quantity The plasma density - this can be either the electron or the ion density, but should be consistent with temperature and particle. particle : str The plasma particle species. z_mean : str The average ionization of the plasma. This is only required for calculating the ion permittivity. Returns ------- chi : ~astropy.units.Quantity The ion or the electron dielectric permittivity of the plasma. This is a dimensionless quantity. Notes ----- The dielectric permittivities for a Maxwellian plasma are described by the following equations [1]_ .. math:: \chi_e(k, \omega) = - \frac{\alpha_e^2}{2} Z'(x_e) \chi_i(k, \omega) = - \frac{\alpha_i^2}{2}\frac{Z}{} Z'(x_i) \alpha = \frac{\omega_p}{k v_{Th}} x = \frac{\omega}{k v_{Th}} :math:`chi_e` and :math:`chi_i` are the electron and ion permittivities respectively. :math:`Z'` is the derivative of the plasma dispersion function. :math:`\alpha` is the scattering parameter which delineates the difference between the collective and non-collective Thomson scattering regimes. :math:`x` is the dimensionless phase velocity of the EM wave propagating through the plasma. References ---------- .. [1] J. Sheffield, D. Froula, S. H. Glenzer, and N. C. Luhmann Jr, Plasma scattering of electromagnetic radiation: theory and measurement techniques. Chapter 5 Pg 106 (Academic press, 2010). Example ------- >>> from astropy import units as u >>> from numpy import pi >>> from astropy.constants import c >>> T = 30 * 11600 * u.K >>> n = 1e18 * u.cm**-3 >>> particle = 'Ne' >>> z_mean = 8 * u.dimensionless_unscaled >>> vTh = parameters.thermal_speed(T, particle, method="most_probable") >>> omega = 5.635e14 * 2 * pi * u.rad / u.s >>> kWave = omega / vTh >>> permittivity_1D_Maxwellian(omega, kWave, T, n, particle, z_mean) <Quantity -6.72809...e-08+5.76037...e-07j> """ # thermal velocity vTh = parameters.thermal_speed(T=T, particle=particle, method="most_probable") # plasma frequency wp = parameters.plasma_frequency(n=n, particle=particle, z_mean=z_mean) # scattering parameter alpha. # explicitly removing factor of sqrt(2) to be consistent with Froula alpha = np.sqrt(2) * (wp / (kWave * vTh)).to(u.dimensionless_unscaled) # The dimensionless phase velocity of the propagating EM wave. zeta = (omega / (kWave * vTh)).to(u.dimensionless_unscaled) chi = alpha ** 2 * (-1 / 2) * plasma_dispersion_func_deriv(zeta.value) return chi
def Maxwellian_speed_3D(v, T, particle="e", v_drift=0, vTh=np.nan, units="units"): r""" Probability distribution function of speed for a Maxwellian distribution in 3D. Return the probability density function for finding a particle with speed components `vx`, `vy`, and `vz` in m/s in an equilibrium plasma of temperature `T` which follows the 3D Maxwellian distribution function. This function assumes Cartesian coordinates. Parameters ---------- v: ~astropy.units.Quantity The speed in units convertible to m/s. T: ~astropy.units.Quantity The temperature, preferably in Kelvin. particle: str, optional Representation of the particle species(e.g., `'p'` for protons, `'D+'` for deuterium, or `'He-4 +1'` for :math:`He_4^{+1}` (singly ionized helium-4)), which defaults to electrons. v_drift: ~astropy.units.Quantity The drift speed in units convertible to m/s. vTh: ~astropy.units.Quantity, optional Thermal velocity (most probable) in m/s. This is used for optimization purposes to avoid re-calculating vTh, for example when integrating over velocity-space. units: str, optional Selects whether to run function with units and unit checks (when equal to "units") or to run as unitless (when equal to "unitless"). The unitless version is substantially faster for intensive computations. Returns ------- f : ~astropy.units.Quantity Probability density in speed^-1, normalized so that: :math:`\iiint_{0}^{\infty} f(\vec{v}) d\vec{v} = 1`. Raises ------ TypeError A parameter argument is not a `~astropy.units.Quantity` and cannot be converted into a `~astropy.units.Quantity`. ~astropy.units.UnitConversionError If the parameters is not in appropriate units. ValueError If the temperature is negative, or the particle mass or charge state cannot be found. Notes ----- In 3D, the Maxwellian speed distribution function describing the distribution of particles with speed :math:`v` in a plasma with temperature :math:`T` is given by: .. math:: f = 4 \pi v^{2} (\pi v_{Th}^2)^{-3/2} \exp(-v^{2} / v_{Th}^2) where :math:`v_{Th} = \sqrt{2 k_B T / m}` is the thermal speed. See also -------- Maxwellian_speed_1D Example ------- >>> from astropy import units as u >>> v=1 * u.m / u.s >>> Maxwellian_speed_3D(v=v, T=30000*u.K, particle='e', v_drift=0 * u.m / u.s) <Quantity 2.60235...e-18 s / m> """ if v_drift != 0: raise NotImplementedError("Non-zero drift speed is work in progress.") if units == "units": # unit checks and conversions # checking velocity units v = v.to(u.m / u.s) # Catching case where drift velocity has default value, and # needs to be assigned units v_drift = _v_drift_units(v_drift) # convert temperature to Kelvins T = T.to(u.K, equivalencies=u.temperature_energy()) if np.isnan(vTh): # get thermal velocity and thermal velocity squared vTh = parameters.thermal_speed(T, particle=particle, method="most_probable") elif not np.isnan(vTh): # check units of thermal velocity vTh = vTh.to(u.m / u.s) elif np.isnan(vTh) and units == "unitless": # assuming unitless temperature is in Kelvins vTh = (parameters.thermal_speed(T * u.K, particle=particle, method="most_probable")).si.value # getting square of thermal speed vThSq = vTh**2 # get square of relative particle speed vSq = (v - v_drift)**2 # calculating distribution function coeff1 = (np.pi * vThSq)**(-3 / 2) coeff2 = 4 * np.pi * vSq expTerm = np.exp(-vSq / vThSq) distFunc = coeff1 * coeff2 * expTerm if units == "units": return distFunc.to(u.s / u.m) elif units == "unitless": return distFunc
def Maxwellian_speed_1D(v, T, particle="e", v_drift=0, vTh=np.nan, units="units"): r""" Probability distribution function of speed for a Maxwellian distribution in 1D. Return the probability density function for finding a particle with speed `v` in m/s in an equilibrium plasma of temperature `T` which follows the Maxwellian distribution function. Parameters ---------- v: ~astropy.units.Quantity The speed in units convertible to m/s. T: ~astropy.units.Quantity The temperature, preferably in Kelvin. particle: str, optional Representation of the particle species(e.g., `'p'` for protons, `'D+'` for deuterium, or `'He-4 +1'` for :math:`He_4^{+1}` (singly ionized helium-4)), which defaults to electrons. v_drift: ~astropy.units.Quantity The drift speed in units convertible to m/s. vTh: ~astropy.units.Quantity, optional Thermal velocity (most probable) in m/s. This is used for optimization purposes to avoid re-calculating vTh, for example when integrating over velocity-space. units: str, optional Selects whether to run function with units and unit checks (when equal to "units") or to run as unitless (when equal to "unitless"). The unitless version is substantially faster for intensive computations. Returns ------- f : ~astropy.units.Quantity Probability density in speed^-1, normalized so that :math:`\int_{0}^{\infty} f(v) dv = 1`. Raises ------ TypeError The parameter arguments are not Quantities and cannot be converted into Quantities. ~astropy.units.UnitConversionError If the parameters is not in appropriate units. ValueError If the temperature is negative, or the particle mass or charge state cannot be found. Notes ----- In one dimension, the Maxwellian speed distribution function describing the distribution of particles with speed v in a plasma with temperature T is given by: .. math:: f(v) = 2 \frac{1}{(\pi v_{Th}^2)^{1/2}} \exp(-(v - V_{drift})^2 / v_{Th}^2 ) where :math:`v_{Th} = \sqrt{2 k_B T / m}` is the thermal speed. Example ------- >>> from astropy import units as u >>> v=1 * u.m / u.s >>> Maxwellian_speed_1D(v=v, T=30000 * u.K, particle='e', v_drift=0 * u.m / u.s) <Quantity 1.1832...e-06 s / m> """ if units == "units": # unit checks and conversions # checking velocity units v = v.to(u.m / u.s) # Catching case where drift velocities have default values, they # need to be assigned units v_drift = _v_drift_units(v_drift) # convert temperature to Kelvins T = T.to(u.K, equivalencies=u.temperature_energy()) if np.isnan(vTh): # get thermal velocity and thermal velocity squared vTh = parameters.thermal_speed(T, particle=particle, method="most_probable") elif not np.isnan(vTh): # check units of thermal velocity vTh = vTh.to(u.m / u.s) elif np.isnan(vTh) and units == "unitless": # assuming unitless temperature is in Kelvins vTh = (parameters.thermal_speed(T * u.K, particle=particle, method="most_probable")).si.value # Get thermal velocity squared vThSq = vTh**2 # Get square of relative particle velocity vSq = (v - v_drift)**2 # calculating distribution function coeff = 2 * (vThSq * np.pi)**(-1 / 2) expTerm = np.exp(-vSq / vThSq) distFunc = coeff * expTerm if units == "units": return distFunc.to(u.s / u.m) elif units == "unitless": return distFunc
def Maxwellian_velocity_3D( vx, vy, vz, T, particle="e", vx_drift=0, vy_drift=0, vz_drift=0, vTh=np.nan, units="units", ): r""" Probability distribution function of velocity for a Maxwellian distribution in 3D. Return the probability density function for finding a particle with velocity components `vx`, `vy`, and `vz` in m/s in an equilibrium plasma of temperature `T` which follows the 3D Maxwellian distribution function. This function assumes Cartesian coordinates. Parameters ---------- vx: ~astropy.units.Quantity The velocity in x-direction units convertible to m/s. vy: ~astropy.units.Quantity The velocity in y-direction units convertible to m/s. vz: ~astropy.units.Quantity The velocity in z-direction units convertible to m/s. T: ~astropy.units.Quantity The temperature, preferably in Kelvin. particle: str, optional Representation of the particle species (e.g., ``'p'`` for protons, ``'D+'`` for deuterium, or ``'He-4 +1'`` for :math:`He_4^{+1}` (singly ionized helium-4)), which defaults to electrons. vx_drift: ~astropy.units.Quantity, optional The drift velocity in x-direction units convertible to m/s. vy_drift: ~astropy.units.Quantity, optional The drift velocity in y-direction units convertible to m/s. vz_drift: ~astropy.units.Quantity, optional The drift velocity in z-direction units convertible to m/s. vTh: ~astropy.units.Quantity, optional Thermal velocity (most probable) in m/s. This is used for optimization purposes to avoid re-calculating `vTh`, for example when integrating over velocity-space. units: str, optional Selects whether to run function with units and unit checks (when equal to "units") or to run as unitless (when equal to "unitless"). The unitless version is substantially faster for intensive computations. Returns ------- f : ~astropy.units.Quantity Probability density in Velocity^-1, normalized so that :math:`\iiint_{0}^{\infty} f(\vec{v}) d\vec{v} = 1`. Raises ------ TypeError A parameter argument is not a `~astropy.units.Quantity` and cannot be converted into a `~astropy.units.Quantity`. ~astropy.units.UnitConversionError If the parameters is not in appropriate units. ValueError If the temperature is negative, or the particle mass or charge state cannot be found. Notes ----- In 3D, the Maxwellian speed distribution function describing the distribution of particles with speed :math:`v` in a plasma with temperature :math:`T` is given by: .. math:: f = (\pi v_{Th}^2)^{-3/2} \exp \left [-(\vec{v} - \vec{V}_{drift})^2 / v_{Th}^2 \right ] where :math:`v_{Th} = \sqrt{2 k_B T / m}` is the thermal speed. See also -------- Maxwellian_1D Example ------- >>> from astropy import units as u >>> v=1 * u.m / u.s >>> Maxwellian_velocity_3D(vx=v, ... vy=v, ... vz=v, ... T=30000 * u.K, ... particle='e', ... vx_drift=0 * u.m / u.s, ... vy_drift=0 * u.m / u.s, ... vz_drift=0 * u.m / u.s) <Quantity 2.0708...e-19 s3 / m3> """ if units == "units": # unit checks and conversions # checking velocity units vx = vx.to(u.m / u.s) vy = vy.to(u.m / u.s) vz = vz.to(u.m / u.s) # catching case where drift velocities have default values, they # need to be assigned units vx_drift = _v_drift_units(vx_drift) vy_drift = _v_drift_units(vy_drift) vz_drift = _v_drift_units(vz_drift) # convert temperature to Kelvins T = T.to(u.K, equivalencies=u.temperature_energy()) if np.isnan(vTh): # get thermal velocity and thermal velocity squared vTh = parameters.thermal_speed(T, particle=particle, method="most_probable") elif not np.isnan(vTh): # check units of thermal velocity vTh = vTh.to(u.m / u.s) elif np.isnan(vTh) and units == "unitless": # assuming unitless temperature is in Kelvins vTh = parameters.thermal_speed(T * u.K, particle=particle, method="most_probable").si.value # accounting for thermal velocity in 3D vThSq = vTh**2 # Get square of relative particle velocity vSq = (vx - vx_drift)**2 + (vy - vy_drift)**2 + (vz - vz_drift)**2 # calculating distribution function coeff = (vThSq * np.pi)**(-3 / 2) expTerm = np.exp(-vSq / vThSq) distFunc = coeff * expTerm if units == "units": return distFunc.to((u.s / u.m)**3) elif units == "unitless": return distFunc
def test_thermal_speed(): r"""Test the thermal_speed function in parameters.py""" assert thermal_speed(T_e).unit.is_equivalent(u.m / u.s) assert thermal_speed(T_e) > thermal_speed(T_e, 'p') # The NRL Plasma Formulary uses a definition of the electron # thermal speed that differs by a factor of sqrt(2). assert np.isclose(thermal_speed(1 * u.MK).value, 5505694.743141063) with pytest.raises(u.UnitConversionError): thermal_speed(5 * u.m) with pytest.raises(ValueError): thermal_speed(-T_e) with pytest.warns(RelativityWarning): thermal_speed(1e9 * u.K) with pytest.raises(RelativityError): thermal_speed(5e19 * u.K) with pytest.warns(u.UnitsWarning): assert thermal_speed(1e5) == thermal_speed(1e5 * u.K) assert thermal_speed(T_i, particle='p').unit.is_equivalent(u.m / u.s) # The NRL Plasma Formulary uses a definition of the particle thermal # speed that differs by a factor of sqrt(2). assert np.isclose(thermal_speed(1 * u.MK, particle='p').si.value, 128486.56960876315) # Case when Z=1 is assumed assert thermal_speed(T_i, particle='p') == thermal_speed(T_i, particle='H-1+') assert thermal_speed(1 * u.MK, particle='e+') == thermal_speed(1 * u.MK) with pytest.raises(u.UnitConversionError): thermal_speed(5 * u.m, particle='p') with pytest.raises(ValueError): thermal_speed(-T_e, particle='p') with pytest.warns(RelativityWarning): thermal_speed(1e11 * u.K, particle='p') with pytest.raises(RelativityError): thermal_speed(1e14 * u.K, particle='p') with pytest.raises(InvalidParticleError): thermal_speed(T_i, particle='asdfasd') with pytest.warns(u.UnitsWarning): assert thermal_speed(1e6, particle='p') == thermal_speed(1e6 * u.K, particle='p') assert np.isclose(thermal_speed(1e6 * u.K, method="mean_magnitude").si.value, 6212510.3969422) assert np.isclose(thermal_speed(1e6 * u.K, method="rms").si.value, 6743070.475775486) with pytest.raises(ValueError): thermal_speed(T_i, method="sadks") assert_can_handle_nparray(thermal_speed)
def time_thermal_speed(self): thermal_speed(1 * u.MK)
def Maxwellian_1D(v, T, particle="e", v_drift=0, vTh=np.nan, units="units"): r""" Probability distribution function of velocity for a Maxwellian distribution in 1D. Returns the probability density function at the velocity ``v`` in m/s to find a particle ``particle`` in a plasma of temperature ``T`` following the Maxwellian distribution function. Parameters ---------- v : `~astropy.units.Quantity` The velocity in units convertible to m/s. T : `~astropy.units.Quantity` The temperature in kelvin. particle : `str`, optional Representation of the particle species(e.g., ``'p'`` for protons, ``'D+'`` for deuterium, or ``'He-4 +1'`` for singly ionized helium-4), which defaults to electrons. v_drift : `~astropy.units.Quantity`, optional The drift velocity in units convertible to m/s. vTh : `~astropy.units.Quantity`, optional Thermal velocity (most probable velocity) in m/s. This is used for optimization purposes to avoid re-calculating ``vTh``, for example when integrating over velocity-space. units : `str`, optional Selects whether to run function with units and unit checks (when equal to "units") or to run as unitless (when equal to "unitless"). The unitless version is substantially faster for intensive computations. Returns ------- f : `~astropy.units.Quantity` Probability density in units of velocity\ :sup:`-1`\ , normalized so that :math:`\int_{-∞}^{+∞} f(v) dv = 1`. Raises ------ `TypeError` The parameter arguments are not Quantities and cannot be converted into Quantities. `~astropy.units.UnitConversionError` If the parameters are not in appropriate units. `ValueError` If the temperature is negative, or the particle mass or charge state cannot be found. Notes ----- In one dimension, the Maxwellian distribution function for a particle of mass m, velocity v, a drift velocity V and with temperature T is: .. math:: f = \sqrt{\frac{m}{2 \pi k_B T}} e^{-\frac{m}{2 k_B T} (v-V)^2} \equiv \frac{1}{\sqrt{\pi v_{Th}^2}} e^{-(v - v_{drift})^2 / v_{Th}^2} where :math:`v_{Th} = \sqrt{2 k_B T / m}` is the thermal speed Examples -------- >>> from astropy import units as u >>> v=1*u.m/u.s >>> Maxwellian_1D(v=v, T=30000 * u.K, particle='e', v_drift=0 * u.m / u.s) <Quantity 5.9163...e-07 s / m> """ if units == "units": # unit checks and conversions # checking velocity units v = v.to(u.m / u.s) # Catching case where drift velocities have default values, they # need to be assigned units v_drift = _v_drift_units(v_drift) # convert temperature to kelvin T = T.to(u.K, equivalencies=u.temperature_energy()) if np.isnan(vTh): # get thermal velocity and thermal velocity squared vTh = parameters.thermal_speed(T, particle=particle, method="most_probable") elif not np.isnan(vTh): # check units of thermal velocity vTh = vTh.to(u.m / u.s) elif np.isnan(vTh) and units == "unitless": # assuming unitless temperature is in kelvin vTh = ( parameters.thermal_speed(T * u.K, particle=particle, method="most_probable") ).si.value # Get thermal velocity squared vThSq = vTh ** 2 # Get square of relative particle velocity vSq = (v - v_drift) ** 2 # calculating distribution function coeff = (vThSq * np.pi) ** (-1 / 2) expTerm = np.exp(-vSq / vThSq) distFunc = coeff * expTerm if units == "units": return distFunc.to(u.s / u.m) elif units == "unitless": return distFunc