def test_lower_hybrid_frequency(): r"""Test the lower_hybrid_frequency function in parameters.py.""" ion = "He-4 1+" omega_ci = gyrofrequency(B, particle=ion) omega_pi = plasma_frequency(n=n_i, particle=ion) omega_ce = gyrofrequency(B) omega_lh = lower_hybrid_frequency(B, n_i=n_i, ion=ion) omega_lh_hz = lower_hybrid_frequency(B, n_i=n_i, ion=ion, to_hz=True) assert omega_ci.unit.is_equivalent(u.rad / u.s) assert omega_pi.unit.is_equivalent(u.rad / u.s) assert omega_ce.unit.is_equivalent(u.rad / u.s) assert omega_lh.unit.is_equivalent(u.rad / u.s) left_hand_side = omega_lh ** -2 right_hand_side = ( 1 / (omega_ci ** 2 + omega_pi ** 2) + omega_ci ** -1 * omega_ce ** -1 ) assert np.isclose(left_hand_side.value, right_hand_side.value) assert np.isclose(omega_lh_hz.value, 299878691.3223296) with pytest.raises(ValueError): lower_hybrid_frequency(0.2 * u.T, n_i=5e19 * u.m ** -3, ion="asdfasd") with pytest.raises(ValueError): lower_hybrid_frequency(0.2 * u.T, n_i=-5e19 * u.m ** -3, ion="asdfasd") with pytest.raises(ValueError): lower_hybrid_frequency(np.nan * u.T, n_i=-5e19 * u.m ** -3, ion="asdfasd") with pytest.warns(u.UnitsWarning): assert lower_hybrid_frequency(1.3, 1e19) == lower_hybrid_frequency( 1.3 * u.T, 1e19 * u.m ** -3 ) assert_can_handle_nparray(lower_hybrid_frequency)
def test_upper_hybrid_frequency(): r"""Test the upper_hybrid_frequency function in parameters.py.""" omega_uh = upper_hybrid_frequency(B, n_e=n_e) omega_uh_hz = upper_hybrid_frequency(B, n_e=n_e, to_hz=True) omega_ce = gyrofrequency(B, "e-") omega_pe = plasma_frequency(n=n_e, particle="e-") assert omega_ce.unit.is_equivalent(u.rad / u.s) assert omega_pe.unit.is_equivalent(u.rad / u.s) assert omega_uh.unit.is_equivalent(u.rad / u.s) assert omega_uh_hz.unit.is_equivalent(u.Hz) left_hand_side = omega_uh**2 right_hand_side = omega_ce**2 + omega_pe**2 assert np.isclose(left_hand_side.value, right_hand_side.value) assert np.isclose(omega_uh_hz.value, 69385868857.90918) with pytest.raises(ValueError): upper_hybrid_frequency(5 * u.T, n_e=-1 * u.m**-3) with pytest.warns(u.UnitsWarning): assert upper_hybrid_frequency(1.2, 1.3) == upper_hybrid_frequency( 1.2 * u.T, 1.3 * u.m**-3) with pytest.warns(u.UnitsWarning): assert upper_hybrid_frequency(1.4 * u.T, 1.3) == upper_hybrid_frequency( 1.4, 1.3 * u.m**-3) assert_can_handle_nparray(upper_hybrid_frequency)
def calculate_gyrofrequency(form): mag_fld = form['mf_mag'] mag_unit = form['unitsB'] particle = form['particle'] z = form['z'] signed = form['signed'] to_hz = form['to_hz'] if mag_fld == None or mag_unit == 'select': return render_template('gyrofrequency.html', sum="Enter all required fields") # Gyrofrequency with only Magnetic Field and Particle b = u.Quantity(mag_fld, u.Unit(mag_unit)) p = plasmapy.particles.Particle(particle) sum = pfp.gyrofrequency(b, p) # Gyrofrequency with B, particle, z # Gyrofrequency with B, particle, z, signed # Output if to_hz is true return sum
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_gyrofrequency(): r"""Test the gyrofrequency function in parameters.py.""" assert gyrofrequency(B, "e-").unit.is_equivalent(u.rad / u.s) assert gyrofrequency(B, "e-", to_hz=True).unit.is_equivalent(u.Hz) assert np.isclose(gyrofrequency(1 * u.T, "e-").value, 175882008784.72018) assert np.isclose(gyrofrequency(2.4 * u.T, "e-").value, 422116821083.3284) assert np.isclose( gyrofrequency(1 * u.T, "e-", to_hz=True).value, 27992490076.528206) assert np.isclose( gyrofrequency(2.4 * u.T, "e-", signed=True).value, -422116821083.3284) assert np.isclose(gyrofrequency(1 * u.G, "e-").cgs.value, 1.76e7, rtol=1e-3) with pytest.raises(TypeError): gyrofrequency(u.m, "e-") with pytest.raises(u.UnitTypeError): gyrofrequency(u.m * 1, "e-") assert np.isnan(gyrofrequency(B_nanarr, "e-")[-1]) # The following is a test to check that equivalencies from astropy # are working. omega_ce = gyrofrequency(2.2 * u.T, "e-") f_ce = (omega_ce / (2 * np.pi)) / u.rad f_ce_use_equiv = omega_ce.to(u.Hz, equivalencies=[(u.cy / u.s, u.Hz)]) assert np.isclose(f_ce.value, f_ce_use_equiv.value) with pytest.warns(u.UnitsWarning): assert gyrofrequency(5.0, "e-") == gyrofrequency(5.0 * u.T, "e-") assert gyrofrequency(B, particle=ion).unit.is_equivalent(u.rad / u.s) assert np.isclose( gyrofrequency(1 * u.T, particle="p").value, 95788335.834874) assert np.isclose( gyrofrequency(2.4 * u.T, particle="p").value, 229892006.00369796) assert np.isclose(gyrofrequency(1 * u.G, particle="p").cgs.value, 9.58e3, rtol=2e-3) assert gyrofrequency(-5 * u.T, "p") == gyrofrequency(5 * u.T, "p") # Case when Z=1 is assumed # assert gyrofrequency(B, particle='p+') == gyrofrequency(B, particle='H-1') assert gyrofrequency(B, particle="e+") == gyrofrequency(B, "e-") with pytest.warns(u.UnitsWarning): gyrofrequency(8, "p") with pytest.raises(u.UnitTypeError): gyrofrequency(5 * u.m, "p") with pytest.raises(InvalidParticleError): gyrofrequency(8 * u.T, particle="asdfasd") with pytest.warns(u.UnitsWarning): # TODO this should be WARNS, not RAISES. and it's probably still raised assert gyrofrequency(5.0, "p") == gyrofrequency(5.0 * u.T, "p") gyrofrequency(1 * u.T, particle="p") # testing for user input Z testMeth1 = gyrofrequency(1 * u.T, particle="p", Z=0.8).si.value testTrue1 = 76630665.79318453 errStr = f"gyrofrequency() gave {testMeth1}, should be {testTrue1}." assert np.isclose(testMeth1, testTrue1, atol=0.0, rtol=1e-5), errStr assert_can_handle_nparray(gyrofrequency, kwargs={"signed": True}) assert_can_handle_nparray(gyrofrequency, kwargs={"signed": False})
def cold_plasma_permittivity_SDP(B: u.T, species, n, omega: u.rad / u.s): r""" Magnetized Cold Plasma Dielectric Permittivity Tensor Elements. Elements (S, D, P) are given in the "Stix" frame, ie. with B // z. The :math:`\exp(-i \omega t)` time-harmonic convention is assumed. Parameters ---------- B : ~astropy.units.Quantity Magnetic field magnitude in units convertible to tesla. species : list of str List of the plasma particle species e.g.: ['e', 'D+'] or ['e', 'D+', 'He+']. n : list of ~astropy.units.Quantity `list` of species density in units convertible to per cubic meter The order of the species densities should follow species. omega : ~astropy.units.Quantity Electromagnetic wave frequency in rad/s. Returns ------- sum : ~astropy.units.Quantity S ("Sum") dielectric tensor element. difference : ~astropy.units.Quantity D ("Difference") dielectric tensor element. plasma : ~astropy.units.Quantity P ("Plasma") dielectric tensor element. Notes ----- The dielectric permittivity tensor is expressed in the Stix frame with the :math:`\exp(-i \omega t)` time-harmonic convention as :math:`\varepsilon = \varepsilon_0 A`, with :math:`A` being .. math:: \varepsilon = \varepsilon_0 \left(\begin{matrix} S & -i D & 0 \\ +i D & S & 0 \\ 0 & 0 & P \end{matrix}\right) where: .. math:: S = 1 - \sum_s \frac{\omega_{p,s}^2}{\omega^2 - \Omega_{c,s}^2} D = \sum_s \frac{\Omega_{c,s}}{\omega} \frac{\omega_{p,s}^2}{\omega^2 - \Omega_{c,s}^2} P = 1 - \sum_s \frac{\omega_{p,s}^2}{\omega^2} where :math:`\omega_{p,s}` is the plasma frequency and :math:`\Omega_{c,s}` is the signed version of the cyclotron frequency for the species :math:`s`. References ---------- - T.H. Stix, Waves in Plasma, 1992. Examples -------- >>> from astropy import units as u >>> from numpy import pi >>> 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) >>> permittivity = S, D, P = cold_plasma_permittivity_SDP(B, species, n, omega) >>> S <Quantity 1.02422...> >>> permittivity.sum # namedtuple-style access <Quantity 1.02422...> >>> D <Quantity 0.39089...> >>> P <Quantity -4.8903...> """ S, D, P = 1, 0, 1 for s, n_s in zip(species, n): omega_c = parameters.gyrofrequency(B=B, particle=s, signed=True) omega_p = parameters.plasma_frequency(n=n_s, particle=s) S += -(omega_p ** 2) / (omega ** 2 - omega_c ** 2) D += omega_c / omega * omega_p ** 2 / (omega ** 2 - omega_c ** 2) P += -(omega_p ** 2) / omega ** 2 return StixTensorElements(S, D, P)
def cold_plasma_permittivity_LRP(B: u.T, species, n, omega: u.rad / u.s): r""" Magnetized Cold Plasma Dielectric Permittivity Tensor Elements. Elements (L, R, P) are given in the "rotating" basis, ie. in the basis :math:`(\mathbf{u}_{+}, \mathbf{u}_{-}, \mathbf{u}_z)`, where the tensor is diagonal and with B // z. The :math:`\exp(-i \omega t)` time-harmonic convention is assumed. Parameters ---------- B : ~astropy.units.Quantity Magnetic field magnitude in units convertible to tesla. species : list of str The plasma particle species (e.g.: `['e', 'D+']` or `['e', 'D+', 'He+']`. n : list of ~astropy.units.Quantity `list` of species density in units convertible to per cubic meter. The order of the species densities should follow species. omega : ~astropy.units.Quantity Electromagnetic wave frequency in rad/s. Returns ------- left : ~astropy.units.Quantity L ("Left") Left-handed circularly polarization tensor element. right : ~astropy.units.Quantity R ("Right") Right-handed circularly polarization tensor element. plasma : ~astropy.units.Quantity P ("Plasma") dielectric tensor element. Notes ----- In the rotating frame defined by :math:`(\mathbf{u}_{+}, \mathbf{u}_{-}, \mathbf{u}_z)` with :math:`\mathbf{u}_{\pm}=(\mathbf{u}_x \pm \mathbf{u}_y)/\sqrt{2}`, the dielectric tensor takes a diagonal form with elements L, R, P with: .. math:: L = 1 - \sum_s \frac{\omega_{p,s}^2}{\omega\left(\omega - \Omega_{c,s}\right)} R = 1 - \sum_s \frac{\omega_{p,s}^2}{\omega\left(\omega + \Omega_{c,s}\right)} P = 1 - \sum_s \frac{\omega_{p,s}^2}{\omega^2} where :math:`\omega_{p,s}` is the plasma frequency and :math:`\Omega_{c,s}` is the signed version of the cyclotron frequency for the species :math:`s`. References ---------- - T.H. Stix, Waves in Plasma, 1992. Examples -------- >>> from astropy import units as u >>> from numpy import pi >>> 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) >>> L, R, P = permittivity = cold_plasma_permittivity_LRP(B, species, n, omega) >>> L <Quantity 0.63333...> >>> permittivity.left # namedtuple-style access <Quantity 0.63333...> >>> R <Quantity 1.41512...> >>> P <Quantity -4.8903...> """ L, R, P = 1, 1, 1 for s, n_s in zip(species, n): omega_c = parameters.gyrofrequency(B=B, particle=s, signed=True) omega_p = parameters.plasma_frequency(n=n_s, particle=s) L += -(omega_p ** 2) / (omega * (omega - omega_c)) R += -(omega_p ** 2) / (omega * (omega + omega_c)) P += -(omega_p ** 2) / omega ** 2 return RotatingTensorElements(L, R, P)
def alfven_dispersion_solution( *, B: u.T, ion: Union[str, Particle], k: u.rad / u.m, n_i: u.m**-3, T_e: u.K, T_i: u.K, theta: u.deg, gamma_e: Union[float, int] = 1, gamma_i: Union[float, int] = 3, z_mean: Union[float, int] = None, ): # validate argument ion if not isinstance(ion, Particle): try: ion = Particle(ion) except TypeError: raise TypeError( f"For argument 'ion' expected type {Particle} but got {type(ion)}." ) if not (ion.is_ion or ion.is_category("element")): raise ValueError( "The particle passed for 'ion' must be an ion or element.") # validate z_mean if z_mean is None: try: z_mean = abs(ion.integer_charge) except ChargeError: z_mean = 1 else: if not isinstance(z_mean, (int, np.integer, float, np.floating)): raise TypeError( f"Expected int or float for argument 'z_mean', but got {type(z_mean)}." ) z_mean = abs(z_mean) # validate arguments for arg_name in ("B", "n_i", "T_e", "T_i"): val = locals()[arg_name].squeeze() if val.shape != (): raise ValueError( f"Argument '{arg_name}' must a single value and not an array of " f"shape {val.shape}.") locals()[arg_name] = val # validate arguments for arg_name in ("gamma_e", "gamma_i"): if not isinstance(locals()[arg_name], (int, np.integer, float, np.floating)): raise TypeError( f"Expected int or float for argument '{arg_name}', but got " f"{type(locals()[arg_name])}.") # validate argument k k = k.squeeze() if not (k.ndim == 0 or k.ndim == 1): raise ValueError( f"Argument 'k' needs to be a single valued or 1D array astropy Quantity," f" got array of shape {k.shape}.") if np.any(k <= 0): raise ValueError("Argument 'k' can not be a or have negative values.") # validate argument theta theta = theta.squeeze() theta = theta.to(u.radian) if not (theta.ndim == 0 or theta.ndim == 1): raise ValueError( f"Argument 'theta' needs to be a single valued or 1D array astropy " f"Quantity, got array of shape {k.shape}.") n_e = z_mean * n_i c_s = pfp.ion_sound_speed( T_e=T_e, T_i=T_i, ion=ion, n_e=n_e, gamma_e=gamma_e, gamma_i=gamma_i, z_mean=z_mean, ) v_A = pfp.Alfven_speed(B, n_i, ion=ion, z_mean=z_mean) omega_ci = pfp.gyrofrequency(B=B, particle=ion, signed=False, Z=z_mean) #Grid/vector creation for k? #Parameters kz kz = np.cos(theta.value) * k kx = np.sqrt(k**2 - kz**2) #Parameters sigma, D, and F to simplify equation 3 A = (kz * v_A)**2 F = ((kx * c_s) / omega_ci)**2 omega = np.sqrt(A * (1 + F)) print(omega_ci) return omega
def time_gyrofrequency(self): gyrofrequency(0.01 * u.T, particle='T+', to_hz=True)
def hollweg_dispersion_solution( *, B: u.T, ion: Union[str, Particle], k: u.rad / u.m, n_i: u.m**-3, T_e: u.K, T_i: u.K, theta: u.deg, gamma_e: Union[float, int] = 1, gamma_i: Union[float, int] = 3, z_mean: Union[float, int] = None, ): # validate argument ion if not isinstance(ion, Particle): try: ion = Particle(ion) except TypeError: raise TypeError( f"For argument 'ion' expected type {Particle} but got {type(ion)}." ) if not (ion.is_ion or ion.is_category("element")): raise ValueError( "The particle passed for 'ion' must be an ion or element.") # validate z_mean if z_mean is None: try: z_mean = abs(ion.integer_charge) except ChargeError: z_mean = 1 else: if not isinstance(z_mean, (int, np.integer, float, np.floating)): raise TypeError( f"Expected int or float for argument 'z_mean', but got {type(z_mean)}." ) z_mean = abs(z_mean) # validate arguments for arg_name in ("B", "n_i", "T_e", "T_i"): val = locals()[arg_name].squeeze() if val.shape != (): raise ValueError( f"Argument '{arg_name}' must a single value and not an array of " f"shape {val.shape}.") locals()[arg_name] = val # validate arguments for arg_name in ("gamma_e", "gamma_i"): if not isinstance(locals()[arg_name], (int, np.integer, float, np.floating)): raise TypeError( f"Expected int or float for argument '{arg_name}', but got " f"{type(locals()[arg_name])}.") # validate argument k k = k.squeeze() if not (k.ndim == 0 or k.ndim == 1): raise ValueError( f"Argument 'k' needs to be a single valued or 1D array astropy Quantity," f" got array of shape {k.shape}.") if np.any(k <= 0): raise ValueError("Argument 'k' can not be a or have negative values.") # validate argument theta theta = theta.squeeze() theta = theta.to(u.radian) if not (theta.ndim == 0 or theta.ndim == 1): raise ValueError( f"Argument 'theta' needs to be a single valued or 1D array astropy " f"Quantity, got array of shape {k.shape}.") # Calc needed plasma parameters n_e = z_mean * n_i c_s = pfp.ion_sound_speed( T_e=T_e, T_i=T_i, ion=ion, n_e=n_e, gamma_e=gamma_e, gamma_i=gamma_i, z_mean=z_mean, ) v_A = pfp.Alfven_speed(B, n_i, ion=ion, z_mean=z_mean) omega_ci = pfp.gyrofrequency(B=B, particle=ion, signed=False, Z=z_mean) omega_pe = pfp.plasma_frequency(n=n_e, particle="e-") # Parameters kx and kz kz = np.cos(theta.value) * k kx = np.sqrt(k**2 - kz**2) # Bellan2012JGR beta param equation 3 beta = (c_s / v_A)**2 # Parameters D, F, sigma, and alpha to simplify equation 3 D = (c_s / omega_ci)**2 F = (c / omega_pe)**2 sigma = (kz * v_A)**2 alpha = (k * v_A)**2 # Polynomial coefficients: c3*x^3 + c2*x^2 + c1*x + c0 = 0 c3 = (F * kx**2 + 1) / sigma c2 = -((alpha / sigma) * (1 + beta + F * kx**2) + D * kx**2 + 1) c1 = alpha * (1 + 2 * beta + D * kx**2) c0 = -beta * alpha * sigma omega = {} fast_mode = [] alfven_mode = [] acoustic_mode = [] # If a single k value is given if np.isscalar(k.value) == True: w = np.emath.sqrt(np.roots([c3.value, c2.value, c1.value, c0.value])) fast_mode = np.max(w) alfven_mode = np.median(w) acoustic_mode = np.min(w) # If mutliple k values are given else: # a0*x^3 + a1*x^2 + a2*x^3 + a3 = 0 for (a0, a1, a2, a3) in zip(c3, c2, c1, c0): w = np.emath.sqrt( np.roots([a0.value, a1.value, a2.value, a3.value])) fast_mode.append(np.max(w)) alfven_mode.append(np.median(w)) acoustic_mode.append(np.min(w)) omega['fast_mode'] = fast_mode * u.rad / u.s omega['alfven_mode'] = alfven_mode * u.rad / u.s omega['acoustic_mode'] = acoustic_mode * u.rad / u.s return omega
def two_fluid_dispersion_solution( *, B: u.T, ion: Union[str, Particle], k: u.rad / u.m, n_i: u.m**-3, T_e: u.K, T_i: u.K, theta: u.deg, gamma_e: Union[float, int] = 1, gamma_i: Union[float, int] = 3, z_mean: Union[float, int] = None, ): r""" Using the solution provided by Bellan 2012, calculate the analytical solution to the two fluid, low-frequency (:math:`\omega/kc \ll 1`) dispersion relation presented by Stringer 1963. This dispersion relation also assummes a uniform magnetic field :math:`\mathbf{B_o}`, no D.C. electric field :math:`\mathbf{E_o}=0`, and quasi-neutrality. For more information see the **Notes** section below. Parameters ---------- B : `~astropy.units.Quantity` The magnetic field magnitude in units convertible to :math:`T`. ion : `str` or `~plasmapy.particles.particle_class.Particle` Representation of the ion species (e.g., ``'p'`` for protons, ``'D+'`` for deuterium, ``'He-4 +1'`` for singly ionized helium-4, etc.). If no charge state information is provided, then the ions are assumed to be singly ionized. k : `~astropy.units.Quantity`, single valued or 1-D array Wavenumber in units convertible to :math:`rad / m`. Either single valued or 1-D array of length :math:`N`. n_i : `~astropy.units.Quantity` Ion number density in units convertible to :math:`m^{-3}`. T_e : `~astropy.units.Quantity` The electron temperature in units of :math:`K` or :math:`eV`. T_i : `~astropy.units.Quantity` The ion temperature in units of :math:`K` or :math:`eV`. theta : `~astropy.units.Quantity`, single valued or 1-D array The angle of propagation of the wave with respect to the magnetic field, :math:`\cos^{-1}(k_z / k)`, in units must be convertible to :math:`deg`. Either single valued or 1-D array of size :math:`M`. gamma_e : `float` or `int`, optional The adiabatic index for electrons, which defaults to 1. This value assumes that the electrons are able to equalize their temperature rapidly enough that the electrons are effectively isothermal. gamma_i : `float` or `int`, optional The adiabatic index for ions, which defaults to 3. This value assumes that ion motion has only one degree of freedom, namely along magnetic field lines. z_mean : `float` or int, optional The average ionization state (arithmetic mean) of the ``ion`` composing the plasma. Will override any charge state defined by argument ``ion``. Returns ------- omega : Dict[str, `~astropy.units.Quantity`] A dictionary of computed wave frequencies in units :math:`rad/s`. The dictionary contains three keys: ``'fast_mode'`` for the fast mode, ``'alfven_mode'`` for the Alfvén mode, and ``'acoustic_mode'`` for the ion-acoustic mode. The value for each key will be a :math:`N x M` array. Raises ------ TypeError If applicable arguments are not instances of `~astropy.units.Quantity` or cannot be converted into one. TypeError If ``ion`` is not of type or convertible to `~plasmapy.particles.Particle`. TypeError If ``gamma_e``, ``gamma_i``, or``z_mean`` are not of type `int` or `float`. ~astropy.units.UnitTypeError If applicable arguments do not have units convertible to the expected units. ValueError If any of ``B``, ``k``, ``n_i``, ``T_e``, or ``T_i`` is negative. ValueError If ``k`` is negative or zero. ValueError If ``ion`` is not of category ion or element. ValueError If ``B``, ``n_i``, ``T_e``, or ``T_I`` are not single valued `astropy.units.Quantity` (i.e. an array). ValueError If ``k`` or ``theta`` are not single valued or a 1-D array. Warns ----- : `~plasmapy.utils.exceptions.PhysicsWarning` When the computed wave frequencies violate the low-frequency (:math:`\omega/kc \ll 1`) assumption of the dispersion relation. Notes ----- The complete dispersion equation presented by Springer 1963 [2]_ (equation 1 of Bellan 2012 [1]_) is: .. math:: \left( \cos^2 \theta - Q \frac{\omega^2}{k^2 {v_A}^2} \right) & \left[ \left( \cos^2 \theta - \frac{\omega^2}{k^2 {c_s}^2} \right) - Q \frac{\omega^2}{k^2 {v_A}^2} \left( 1 - \frac{\omega^2}{k^2 {c_s}^2} \right) \right] \\ &= \left(1 - \frac{\omega^2}{k^2 {c_s}^2} \right) \frac{\omega^2}{{\omega_{ci}}^2} \cos^2 \theta where .. math:: Q &= 1 + k^2 c^2/{\omega_{pe}}^2 \\ \cos \theta &= \frac{k_z}{k} \\ \mathbf{B_o} &= B_{o} \mathbf{\hat{z}} :math:`\omega` is the wave frequency, :math:`k` is the wavenumber, :math:`v_A` is the Alfvén velocity, :math:`c_s` is the sound speed, :math:`\omega_{ci}` is the ion gyrofrequency, and :math:`\omega_{pe}` is the electron plasma frequency. This relation does additionally assume low-frequency waves :math:`\omega/kc \ll 1`, no D.C. electric field :math:`\mathbf{E_o}=0` and quasi-neutrality. Following section 5 of Bellan 2012 [1]_ the exact roots of the above dispersion equation can be derived and expressed as one analytical solution (equation 38 of Bellan 2012 [1]_): .. math:: \frac{\omega}{\omega_{ci}} = \sqrt{ 2 \Lambda \sqrt{-\frac{P}{3}} \cos\left( \frac{1}{3} \cos^{-1}\left( \frac{3q}{2p} \sqrt{-\frac{3}{p}} \right) - \frac{2 \pi}{3}j \right) + \frac{\Lambda A}{3} } where :math:`j = 0` represents the fast mode, :math:`j = 1` represents the Alfvén mode, and :math:`j = 2` represents the acoustic mode. Additionally, .. math:: p &= \frac{3B-A^2}{3} \; , \; q = \frac{9AB-2A^3-27C}{27} \\ A &= \frac{Q + Q^2 \beta + Q \alpha + \alpha \Lambda}{Q^2} \; , \; B = \alpha \frac{1 + 2 Q \beta + \Lambda \beta}{Q^2} \; , \; C = \frac{\alpha^2 \beta}{Q^2} \\ \alpha &= \cos^2 \theta \; , \; \beta = \left( \frac{c_s}{v_A}\right)^2 \; , \; \Lambda = \left( \frac{k v_{A}}{\omega_{ci}}\right)^2 References ---------- .. [1] PM Bellan, Improved basis set for low frequency plasma waves, 2012, JGR, 117, A12219, doi: `10.1029/2012JA017856 <https://agupubs.onlinelibrary.wiley.com/doi/10.1029/2012JA017856>`_. .. [2] TE Stringer, Low-frequency waves in an unbounded plasma, 1963, JNE, Part C, doi: `10.1088/0368-3281/5/2/304 <https://doi.org/10.1088/0368-3281/5/2/304>`_ Examples -------- >>> from astropy import units as u >>> from plasmapy.dispersion import two_fluid_dispersion >>> inputs = { ... "k": 0.01 * u.rad / u.m, ... "theta": 30 * u.deg, ... "B": 8.3e-9 * u.T, ... "n_i": 5e6 * u.m ** -3, ... "T_e": 1.6e6 * u.K, ... "T_i": 4.0e5 * u.K, ... "ion": "p+", ... } >>> omegas = two_fluid_dispersion_solution(**inputs) >>> omegas {'fast_mode': <Quantity 1520.57... rad / s>, 'alfven_mode': <Quantity 1261.75... rad / s>, 'acoustic_mode': <Quantity 0.688152... rad / s>} >>> inputs = { ... "k": [1e-7, 2e-7] * u.rad / u.m, ... "theta": [10, 20] * u.deg, ... "B": 8.3e-9 * u.T, ... "n_i": 5e6 * u.m ** -3, ... "T_e": 1.6e6 * u.K, ... "T_i": 4.0e5 * u.K, ... "ion": "He+", ... } >>> omegas = two_fluid_dispersion_solution(**inputs) >>> omegas['fast_mode'] <Quantity [[0.00767..., 0.00779... ], [0.01534..., 0.01558...]] rad / s> """ # validate argument ion if not isinstance(ion, Particle): try: ion = Particle(ion) except TypeError: raise TypeError( f"For argument 'ion' expected type {Particle} but got {type(ion)}." ) if not (ion.is_ion or ion.is_category("element")): raise ValueError( f"The particle passed for 'ion' must be an ion or element.") # validate z_mean if z_mean is None: try: z_mean = abs(ion.integer_charge) except ChargeError: z_mean = 1 else: if not isinstance(z_mean, (int, np.integer, float, np.floating)): raise TypeError( f"Expected int or float for argument 'z_mean', but got {type(z_mean)}." ) z_mean = abs(z_mean) # validate arguments for arg_name in ("B", "n_i", "T_e", "T_i"): val = locals()[arg_name].squeeze() if val.shape != (): raise ValueError( f"Argument '{arg_name}' must a single value and not an array of " f"shape {val.shape}.") locals()[arg_name] = val # validate arguments for arg_name in ("gamma_e", "gamma_i"): if not isinstance(locals()[arg_name], (int, np.integer, float, np.floating)): raise TypeError( f"Expected int or float for argument '{arg_name}', but got " f"{type(locals()[arg_name])}.") # validate argument k k = k.squeeze() if not (k.ndim == 0 or k.ndim == 1): raise ValueError( f"Argument 'k' needs to be a single valued or 1D array astropy Quantity," f" got array of shape {k.shape}.") if np.any(k <= 0): raise ValueError(f"Argument 'k' can not be a or have negative values.") # validate argument theta theta = theta.squeeze() theta = theta.to(u.radian) if not (theta.ndim == 0 or theta.ndim == 1): raise ValueError( f"Argument 'theta' needs to be a single valued or 1D array astropy " f"Quantity, got array of shape {k.shape}.") # Calc needed plasma parameters n_e = z_mean * n_i with warnings.catch_warnings(): warnings.simplefilter("ignore", category=PhysicsWarning) c_s = pfp.ion_sound_speed( T_e=T_e, T_i=T_i, ion=ion, n_e=n_e, gamma_e=gamma_e, gamma_i=gamma_i, z_mean=z_mean, ) v_A = pfp.Alfven_speed(B, n_i, ion=ion, z_mean=z_mean) omega_ci = pfp.gyrofrequency(B=B, particle=ion, signed=False, Z=z_mean) omega_pe = pfp.plasma_frequency(n=n_e, particle="e-") # Bellan2012JGR params equation 32 alpha = np.cos(theta.value)**2 beta = (c_s / v_A).to(u.dimensionless_unscaled).value**2 alphav, kv = np.meshgrid(alpha, k.value) # create grid Lambda = (kv * v_A.value / omega_ci.value)**2 # Bellan2012JGR params equation 2 Q = 1 + (kv * c.value / omega_pe.value)**2 # Bellan2012JGR params equation 35 A = ((1 + alphav) / Q) + beta + (alphav * Lambda / Q**2) B = alphav * (1 + 2 * Q * beta + Lambda * beta) / Q**2 C = beta * (alphav / Q)**2 # Bellan2012JGR params equation 36 p = (3 * B - A**2) / 3 q = (9 * A * B - 2 * A**3 - 27 * C) / 27 # Bellan2012JGR params equation 38 R = 2 * Lambda * np.emath.sqrt(-p / 3) S = 3 * q / (2 * p) * np.emath.sqrt(-3 / p) T = Lambda * A / 3 omega = {} for ind, key in enumerate(("fast_mode", "alfven_mode", "acoustic_mode")): # The solution corresponding to equation 38 w = omega_ci * np.emath.sqrt( R * np.cos(1 / 3 * np.emath.arccos(S) - 2 * np.pi / 3 * ind) + T) omega[key] = w.squeeze() # check for violation of dispersion relation assumptions # (i.e. low-frequency, w/kc << 0.1) wkc_max = np.max(w.value / (kv * c.value)) if wkc_max > 0.1: warnings.warn( f"The {key} calculation produced a high-frequency wave (w/kc == " f"{wkc_max:.3f}), which violates the low-frequency (w/kc << 1) " f"assumption of the dispersion relation.", PhysicsWarning, ) return omega
def calculate_gyrofrequency(form): r''' Returns -------- Quantity Gyrofrequency in units of radians per second Parameters ---------- `form`: The calculator form from the HTML page where the user enters data for calculation. ''' mag_fld = form['mf_mag'] # Magnetic field magnitude mag_unit = form['unitsB'] # Unit of magnetic field particle = form['particle'] # Use MultiDict.get() to get Z as an int or None z = form.get('z', type=int) # Average ionization signed = form['signed'] # Boolean to convert output from angular frequency to Hz to_hz = form['to_hz'] # Prompt user for required inputs if mag_fld == "" or mag_unit == 'select': return -1 b = u.Quantity(mag_fld, u.Unit(mag_unit)) p = plasmapy.particles.Particle(particle) # Form returns 'True' and 'False' as strings and not booleans # Gyrofrequency with only Magnetic Field and Particle if signed == 'False' and z == None and to_hz == 'False': sum = pfp.gyrofrequency(b, p) return sum # Gyrofrequency with B, particle, z elif signed == 'False' and z != None and to_hz == 'False': sum = pfp.gyrofrequency(b, p, signed=False, Z=z, to_hz=False) return sum # Output if to_hz is true, signed is false and Z is given elif signed == 'False' and z != None and to_hz == 'True': sum = pfp.gyrofrequency(b, p, signed=False, Z=z, to_hz=True) return sum # Output if to_hz is true, signed is false and Z is not given elif signed == 'False' and z == None and to_hz == 'True': sum = pfp.gyrofrequency(b, p, signed=False, Z=None, to_hz=True) return sum # Gyrofrequency with B, particle, signed elif signed == 'True' and z == None and to_hz == 'False': sum = pfp.gyrofrequency(b, p, signed=True, Z=None, to_hz=False) return sum # Output if to_hz is true, signed is true and Z is not given elif signed == 'True' and z == None and to_hz == 'True': sum = pfp.gyrofrequency(b, p, signed=True, Z=None, to_hz=True) return sum # Gyrofrequency with B, particle, z, signed elif signed == 'True' and z != None and to_hz == 'False': sum = pfp.gyrofrequency(b, p, signed=True, Z=z, to_hz=False) return sum # Output if to_hz is true, signed is true and Z is given elif signed == 'True' and z != None and to_hz == 'True': sum = pfp.gyrofrequency(b, p, signed=True, Z=z, to_hz=True) return sum return sum