def quantum_theta(T: u.K, n_e: u.m**-3): """ Compares Fermi energy to thermal kinetic energy to check if quantum effects are important. Parameters ---------- T : ~astropy.units.Quantity The temperature of the plasma. n_e : ~astropy.units.Quantity The electron number density of the plasma. Examples -------- >>> import astropy.units as u >>> quantum_theta(1*u.eV, 1e20*u.m**-3) <Quantity 127290.61956522> >>> quantum_theta(1*u.eV, 1e16*u.m**-3) <Quantity 59083071.83975738> >>> quantum_theta(1*u.eV, 1e26*u.m**-3) <Quantity 12.72906196> >>> quantum_theta(1*u.K, 1e26*u.m**-3) <Quantity 0.00109691> Returns ------- theta: ~astropy.units.Quantity """ fermi_energy = quantum.Fermi_energy(n_e) thermal_energy = constants.k_B * T.to(u.K, equivalencies=u.temperature_energy()) theta = thermal_energy / fermi_energy return theta
def temperature_bin_edges(self, temperature_bin_edges: u.K): delta_log_temperature = np.diff(np.log10(temperature_bin_edges.to(u.K).value)) # NOTE: Very small numerical differences not important # NOTE: Can be removed if we use gWCS to create the resulting DEM cube if not np.allclose(delta_log_temperature, delta_log_temperature[0], atol=1e-10, rtol=0): raise ValueError('Temperature must be evenly spaced in log10') self._temperature_bin_edges = temperature_bin_edges
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
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
def Debye_length(T_e: u.K, n_e: u.m**-3): r"""Calculate the characteristic decay length for electric fields, due to charge screening. Parameters ---------- T_e: ~astropy.units.Quantity Electron temperature n_e: ~astropy.units.Quantity Electron number density Returns ------- lambda_D : ~astropy.units.Quantity The Debye length in meters Raises ------ TypeError If either argument is not a `~astropy.units.Quantity` ~astropy.units.UnitConversionError If either argument is in incorrect units ValueError If either argument contains invalid values Warns ----- ~astropy.units.UnitsWarning If units are not provided, SI units are assumed Notes ----- The Debye length is the exponential scale length for charge screening and is given by .. math:: \lambda_D = \sqrt{\frac{\epsilon_0 k_b T_e}{n_e e^2}} for an electron plasma with nearly stationary ions. The electrical potential will drop by a factor of 1/e every Debye length. Plasmas will generally be quasineutral on length scales significantly larger than the Debye length. See Also -------- Debye_number Example ------- >>> from astropy import units as u >>> Debye_length(5e6*u.K, 5e15*u.m**-3) <Quantity 0.00218226 m> """ T_e = T_e.to(u.K, equivalencies=u.temperature_energy()) lambda_D = np.sqrt(eps0 * k_B * T_e / (n_e * e**2)) return lambda_D.to(u.m)
def Maxwellian_1D(v: units.m / units.s, T: units.K, particle="e", V_drift=0 * units.m / units.s): r"""Return the probability 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: Quantity The velocity in units convertible to m/s T: Quantity The temperature in Kelvin particle: string, optional Representation of the particle species(e.g., 'p' for protons, 'D+' for deuterium, or 'He-4 +1' for $He_4^{+1}$ : singly ionized helium-4), which defaults to electrons. Returns ------- f : Quantity probability in Velocity^-1, normized so that: $\int_{- \infty}^{\infty} f(v) dv = 1} Raises ------ TypeError The parameter arguments are not Quantities and cannot be converted into Quantities. 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 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}} \exp(-\frac{m}{2 k_B T} (v-V)^2) Example ------- >>> from plasmapy.physics import Maxwellian_1D >>> 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.916329687405701e-07 s / m> """ # pass to Kelvin if T.unit.is_equivalent(units.K): T = T.to(units.K, equivalencies=units.temperature_energy()) # Get mass if is_electron(particle): m_s = m_e else: try: m_s = ion_mass(particle) except Exception: raise ValueError("Unable to find ion mass in Maxwellian_1D") T = k_B * T f = np.sqrt( (m_s / (2 * pi * T))) * np.exp(-m_s / (2 * T) * (v - V_drift)**2) return f.to(units.s / units.m)
def spectral_density( wavelengths: u.nm, probe_wavelength: u.nm, n: u.m**-3, *, T_e: u.K, T_i: u.K, efract: np.ndarray = None, ifract: np.ndarray = None, ions: Union[str, List[str], Particle, List[Particle]] = "p", electron_vel: u.m / u.s = None, ion_vel: u.m / u.s = None, probe_vec=None, scatter_vec=None, instr_func=None, ) -> 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. See **Notes** section below for additional details. 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` Total combined number density of all electron populations. (convertible to cm\ :sup:`-3`) T_e : `~astropy.units.Quantity`, keyword-only, shape (Ne, ) Temperature of each electron component. Shape (Ne, ) must be equal to the number of electron populations Ne. (in K or convertible to eV) T_i : `~astropy.units.Quantity`, keyword-only, shape (Ni, ) Temperature of each ion component. Shape (Ni, ) must be equal to the number of ion populations Ni. (in K or convertible to eV) efract : array_like, shape (Ne, ), optional An array-like object representing :math:`F_e` (defined above). Must sum to 1.0. Default is [1.0], representing a single electron component. ifract : array_like, shape (Ni, ), optional An array-like object representing :math:`F_i` (defined above). Must sum to 1.0. Default is [1.0], representing a single ion component. ions : `str` or `~plasmapy.particles.particle_class.Particle` or `~plasmapy.particles.particle_collections.ParticleList`, shape (Ni, ), optional A list or single instance of `~plasmapy.particles.particle_class.Particle`, or strings convertible to `~plasmapy.particles.particle_class.Particle`, or a `~plasmapy.particles.particle_collections.ParticleList`. All ions must be positively charged. Default is ``'H+'`` corresponding to a single species of hydrogen ions. electron_vel : `~astropy.units.Quantity`, shape (Ne, 3), optional Velocity of each electron population in the rest frame. (convertible to m/s) If set, overrides ``electron_vdir`` and ``electron_speed``. 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). If set, overrides ``ion_vdir`` and ``ion_speed``. Defaults to 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° scattering angle geometry. instr_func : function A function representing the instrument function that takes a `~astropy.units.Quantity` of wavelengths (centered on zero) and returns the instrument point spread function. The resulting array will be convolved with the spectral density function before it is returned. 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 ----- 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 one or more thermal electron populations (the entire plasma is assumed to be quasi-neutral) .. math:: S(k,ω) = \sum_e \frac{2π}{k} \bigg |1 - \frac{χ_e}{ε} \bigg |^2 f_{e0,e} \bigg (\frac{ω}{k} \bigg ) + \sum_i \frac{2π Z_i}{k} \bigg |\frac{χ_e}{ε} \bigg |^2 f_{i0,i} \bigg ( \frac{ω}{k} \bigg ) where :math:`χ_e` is the electron component susceptibility of the plasma and :math:`ε = 1 + \sum_e χ_e + \sum_i χ_i` is the total plasma dielectric function (with :math:`χ_i` being the ion component of the susceptibility), :math:`Z_i` is the charge of each ion, :math:`k` is the scattering wavenumber, :math:`ω` 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 :cite:t:`sheffield:2011`\ . The number density of the e\ :sup:`th` electron populations is defined as .. math:: n_e = F_e n where :math:`n` is total density of all electron population combined and :math:`F_e` is the fractional density of each electron population such that .. math:: \sum_e n_e = n .. math:: \sum_e F_e = 1 The plasma is assumed to be charge neutral, and therefore the number density of the i\ :sup:`th` ion population is .. math:: n_i = \frac{F_i n}{\sum_i F_i Z_i} with :math:`F_i` defined in the same way as :math:`F_e`. For details, see "Plasma Scattering of Electromagnetic Radiation" by :cite:t:`sheffield:2011`. This code is a modified version of the program described therein. For a concise summary of the relevant physics, see Chapter 5 of the :cite:t:`schaeffer:2014` thesis. """ # Validate efract if efract is None: efract = np.ones(1) else: efract = np.asarray(efract, dtype=np.float64) if np.sum(efract) != 1: raise ValueError( f"The provided efract does not sum to 1: {efract}") # Validate ifract if ifract is None: ifract = np.ones(1) else: ifract = np.asarray(ifract, dtype=np.float64) if np.sum(ifract) != 1: raise ValueError( f"The provided ifract does not sum to 1: {ifract}") if probe_vec is None: probe_vec = np.array([1, 0, 0]) if scatter_vec is None: scatter_vec = np.array([0, 1, 0]) # If electron 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 # Condition the electron velocity keywords if ion_vel is None: ion_vel = np.zeros([ifract.size, 3]) * u.m / u.s # Condition ions # If a single value is provided, turn into a particle list if isinstance(ions, ParticleList): pass elif isinstance(ions, str): ions = ParticleList([Particle(ions)]) # If a list is provided, ensure all values are Particles, then convert # to a ParticleList elif isinstance(ions, list): for ii, ion in enumerate(ions): if isinstance(ion, Particle): continue ions[ii] = Particle(ion) ions = ParticleList(ions) else: raise ValueError("The type of object provided to the ``ions`` keyword " f"is not supported: {type(ions)}") # Validate ions if len(ions) == 0: raise ValueError("At least one ion species needs to be defined.") try: if sum(ion.charge_number <= 0 for ion in ions): raise ValueError("All ions must be positively charged.") # Catch error if charge information is missing except ChargeError: raise ValueError("All ions must be positively charged.") # Condition T_i if T_i.size == 1: # If a single quantity is given, put it in an array so it's iterable # If T_i.size != len(ions), assume same temp. for all species T_i = np.array([T_i.value]) * T_i.unit # Make sure the sizes of ions, ifract, ion_vel, and T_i all match if ((len(ions) != ifract.size) or (ion_vel.shape[0] != ifract.size) or (T_i.size != ifract.size)): raise ValueError( f"Inconsistent number of ion species in ifract ({ifract}), " f"ions ({len(ions)}), T_i ({T_i.size}), " f"and/or ion_vel ({ion_vel.shape[0]}).") # Condition T_e if T_e.size == 1: # If a single quantity is given, put it in an array so it's iterable # If T_e.size != len(efract), assume same temp. for all species T_e = np.array([T_e.value]) * T_e.unit # Make sure the sizes of efract, electron_vel, and T_e all match if (electron_vel.shape[0] != efract.size) or (T_e.size != efract.size): raise ValueError( f"Inconsistent number of electron populations in efract ({efract.size}), " f"T_e ({T_e.size}), or electron velocity ({electron_vel.shape[0]})." ) # Create arrays of ion Z and mass from particles given ion_z = ions.charge_number ion_mass = ions.mass probe_vec = probe_vec / np.linalg.norm(probe_vec) scatter_vec = scatter_vec / np.linalg.norm(scatter_vec) # Apply the instrument function if instr_func is not None and callable(instr_func): # Create an array of wavelengths of the same size as wavelengths # but centered on zero wspan = (np.max(wavelengths) - np.min(wavelengths)) / 2 eval_w = np.linspace(-wspan, wspan, num=wavelengths.size) instr_func_arr = instr_func(eval_w) if type(instr_func_arr) != np.ndarray: raise ValueError("instr_func must be a function that returns a " "np.ndarray, but the provided function returns " f" a {type(instr_func_arr)}") if wavelengths.shape != instr_func_arr.shape: raise ValueError("The shape of the array returned from the " f"instr_func ({instr_func_arr.shape}) " "does not match the shape of the wavelengths " f"array ({wavelengths.shape}).") instr_func_arr /= np.sum(instr_func_arr) else: instr_func_arr = None alpha, Skw = spectral_density_lite( wavelengths.to(u.m).value, probe_wavelength.to(u.m).value, n.to(u.m**-3).value, T_e.to(u.K).value, T_i.to(u.K).value, efract=efract, ifract=ifract, ion_z=ion_z, ion_mass=ion_mass.to(u.kg).value, ion_vel=ion_vel.to(u.m / u.s).value, electron_vel=electron_vel.to(u.m / u.s).value, probe_vec=probe_vec, scatter_vec=scatter_vec, instr_func_arr=instr_func_arr, ) return alpha, Skw * u.s / u.rad
def power_law_rad_loss(T: u.K, kind='klimchuk') -> u.erg * u.cm**3 / u.s: """ Raymond-Klimchuk power-law radiative loss function. See Eq. 3 of Klimchuk et al. (2008) Arguments: T {u.K} -- [description] Returns: [type] -- [description] """ log_temperature = np.log10(T.to(u.K).value) if kind == 'klimchuk': chi = np.ones(T.shape) * 1.96e-27 alpha = np.ones(T.shape) * 1.0 / 2.0 chi = np.where(log_temperature <= 7.63, 5.49e-16, chi) alpha = np.where(log_temperature <= 7.63, -1.0, alpha) chi = np.where(log_temperature <= 6.90, 3.46e-25, chi) alpha = np.where(log_temperature <= 6.90, 1.0 / 3.0, alpha) chi = np.where(log_temperature <= 6.55, 3.53e-13, chi) alpha = np.where(log_temperature <= 6.55, -3.0 / 2.0, alpha) chi = np.where(log_temperature <= 6.18, 1.90e-22, chi) alpha = np.where(log_temperature <= 6.18, 0.0, alpha) chi = np.where(log_temperature <= 5.67, 8.87e-17, chi) alpha = np.where(log_temperature <= 5.67, -1.0, alpha) chi = np.where(log_temperature <= 4.97, 1.09e-31, chi) alpha = np.where(log_temperature <= 4.97, 2.0, alpha) elif kind == 'rtv': # Not valid above 1e7 chi = np.ones(T.shape) * np.nan alpha = np.ones(T.shape) * np.nan chi = np.where(log_temperature <= 7, 10**(-17.73), chi) alpha = np.where(log_temperature <= 7, -2 / 3, alpha) chi = np.where(log_temperature <= 6.3, 10**(-21.94), chi) alpha = np.where(log_temperature <= 6.3, 0., alpha) chi = np.where(log_temperature <= 5.75, 10**(-10.4), chi) alpha = np.where(log_temperature <= 5.75, -2.0, alpha) chi = np.where(log_temperature <= 5.4, 10**(-21.2), chi) alpha = np.where(log_temperature <= 5.4, 0.0, alpha) chi = np.where(log_temperature <= 4.9, 10**(-31), chi) alpha = np.where(log_temperature <= 4.9, 2, alpha) chi = np.where(log_temperature <= 4.6, 10.**(-21.85), chi) alpha = np.where(log_temperature <= 4.6, 0.0, alpha) # Not valid below 10^4.3 chi = np.where(log_temperature <= 4.3, np.nan, chi) else: raise ValueError('Unrecognized power-law fit type.') return chi * T.to(u.K).value**alpha * u.erg * u.cm**3 / u.s