Esempio n. 1
0
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
Esempio n. 2
0
 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
Esempio n. 3
0
 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
Esempio n. 4
0
 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
Esempio n. 5
0
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)
Esempio n. 6
0
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)
Esempio n. 7
0
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
Esempio n. 8
0
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