use core::f64::consts::PI;
use metrology::{C, E_CHARGE, EPS_0, EV, M_E, MU_0};
pub fn mass_density(n_i: f64, m_i: f64, z: f64) -> f64 {
n_i * (m_i + z * M_E)
}
pub fn alfven_speed(b: f64, rho: f64) -> f64 {
b / (MU_0 * rho).sqrt()
}
pub fn magnetosonic(va: f64, cs: f64, theta: f64) -> (f64, f64) {
let sum = va * va + cs * cs;
let root = (sum * sum - 4.0 * va * va * cs * cs * theta.cos().powi(2)).max(0.0).sqrt();
(((sum + root) / 2.0).sqrt(), ((sum - root) / 2.0).max(0.0).sqrt())
}
pub fn magnetic_pressure(b: f64) -> f64 {
b * b / (2.0 * MU_0)
}
pub fn field_for_pressure(p: f64) -> f64 {
(2.0 * MU_0 * p).sqrt()
}
pub fn beta(n: f64, t_ev: f64, b: f64) -> f64 {
n * t_ev * EV / magnetic_pressure(b)
}
pub fn magnetic_diffusivity(sigma: f64) -> f64 {
1.0 / (MU_0 * sigma)
}
pub fn magnetic_reynolds(sigma: f64, u: f64, l: f64) -> f64 {
u * l / magnetic_diffusivity(sigma)
}
pub fn hartmann_number(b: f64, l: f64, sigma: f64, rho: f64, nu: f64) -> f64 {
b * l * (sigma / (rho * nu)).sqrt()
}
pub fn magnetic_prandtl(nu: f64, sigma: f64) -> f64 {
nu * MU_0 * sigma
}
pub fn elsasser(sigma: f64, b: f64, rho: f64, omega: f64) -> f64 {
sigma * b * b / (2.0 * rho * omega)
}
pub fn free_decay_time(r: f64, sigma: f64) -> f64 {
r * r * MU_0 * sigma / (PI * PI)
}
pub fn inertial_length(n: f64, m: f64, z: f64) -> f64 {
C / (n * (z * E_CHARGE).powi(2) / (EPS_0 * m)).sqrt()
}
pub fn gyroradius(t_ev: f64, b: f64, m: f64, z: f64) -> f64 {
(2.0 * t_ev * EV / m).sqrt() * m / (z * E_CHARGE * b)
}
pub fn ion_break_frequency(v: f64, n: f64, t_ev: f64, b: f64) -> f64 {
let scale = inertial_length(n, metrology::M_P, 1.0).max(gyroradius(t_ev, b, metrology::M_P, 1.0));
v / (2.0 * PI * scale)
}
pub fn line_current_force(i1: f64, i2: f64, r: f64) -> f64 {
MU_0 * i1 * i2 / (2.0 * PI * r)
}
pub fn line_mass_force(lambda1: f64, lambda2: f64, r: f64) -> f64 {
2.0 * metrology::G * lambda1 * lambda2 / r
}
pub fn perpendicular_shock(m_a: f64, beta: f64, gamma: f64) -> Option<f64> {
if m_a * m_a <= 1.0 + gamma * beta / 2.0 {
return None;
}
let (a, b, c) = (2.0 - gamma, gamma * (1.0 + beta) + (gamma - 1.0) * m_a * m_a, -(gamma + 1.0) * m_a * m_a);
Some(if a.abs() < 1e-12 { -c / b } else { (-b + (b * b - 4.0 * a * c).sqrt()) / (2.0 * a) })
}
#[cfg(test)]
mod tests {
use super::*;
use metrology::{AMU, M_P};
fn close(a: f64, b: f64, rel: f64) -> bool {
(a / b - 1.0).abs() < rel
}
#[test]
fn field_for_pressure_inverts_magnetic_pressure() {
assert!(close(field_for_pressure(metrology::MEV), 6.35e-10, 1e-3));
assert!(close(field_for_pressure(101_325.0), 0.5046, 1e-4));
for b in [1e-9, 1.0, 45.0] {
assert!(close(field_for_pressure(magnetic_pressure(b)), b, 1e-15));
}
}
#[test]
fn alfven_speeds() {
assert!(close(alfven_speed(0.014, mass_density(5e19, M_P, 1.0)), 43_173.870, 1e-7));
assert!(close(alfven_speed(0.014, mass_density(5e19, 6.644_657_335_7e-27, 2.0)), 21_664.18, 1e-6));
assert!(close(alfven_speed(11e-4, 1.67e-12), 759.3e3, 1e-3));
assert!(close(alfven_speed(5.3, 1.01e20 * 2.5 * AMU), 7.30156e6, 1e-5));
}
#[test]
fn waves_pressure_and_beta() {
let (f, s) = magnetosonic(3.0, 1.0, 0.0);
assert!((f - 3.0).abs() < 1e-12 && (s - 1.0).abs() < 1e-12);
let (f, s) = magnetosonic(3.0, 1.0, core::f64::consts::FRAC_PI_2);
assert!((f - 10f64.sqrt()).abs() < 1e-12 && s.abs() < 1e-7);
for (b, p) in [(1.0, 397_887.357_5), (12.2, 5.922_155e7), (45.0, 8.057_219e8)] {
assert!(close(magnetic_pressure(b), p, 1e-7));
}
assert!(close(beta(3.9e6, 1.64, 13.3e-9), 0.014_559_8, 1e-5));
}
#[test]
fn dimensionless_numbers() {
let sigma = 10.0 / MU_0;
assert!(close(magnetic_reynolds(sigma, 1.0, 10.0), 100.0, 1e-12));
let (b, l, rho, nu) = (0.5, 0.1, 930.0, 7e-7);
let s = alfven_speed(b, rho) * l / magnetic_diffusivity(sigma);
assert!(close(s, hartmann_number(b, l, sigma, rho, nu) * magnetic_prandtl(nu, sigma).sqrt(), 1e-12));
assert!(close(elsasser(1e6, 2e-3, 1.1e4, 7.292e-5), 2.493, 1e-3));
assert!(close(free_decay_time(3.48e6, 1e6) / (365.25 * 86_400.0), 48.9e3, 2e-3));
}
#[test]
fn kinetic_scales() {
assert!(close(inertial_length(5.0, 4.002_602 * AMU - M_E, 1.0), 2.029_86e8, 1e-5));
assert!(close(inertial_length(5.0, M_E, 1.0), 2.376_53e6, 1e-5));
assert!(close(inertial_length(1e6, M_P, 1.0) * 100.0, 2.277_11e7, 1e-5));
assert!(close(ion_break_frequency(460e3, 3.9e6, 1.64, 13.3e-9), 0.635, 2e-3));
assert!(close(ion_break_frequency(389e3, 17.1e6, 4.8, 1.14e-9), 0.223, 5e-3));
}
#[test]
fn perpendicular_shocks() {
assert!(close(perpendicular_shock(8.0, 1.0, 5.0 / 3.0).unwrap(), 3.615_425, 1e-6));
assert!(close(perpendicular_shock(8.0, 2.1, 5.0 / 3.0).unwrap(), 3.483_387, 1e-6));
assert!(close(perpendicular_shock(1e5, 1.0, 5.0 / 3.0).unwrap(), 4.0, 1e-6));
assert!(perpendicular_shock(1.0, 1.0, 5.0 / 3.0).is_none());
}
#[test]
fn filamentary_currents_against_gravity() {
let pc = metrology::PARSEC;
let lambda = metrology::SOLAR_MASS / pc; let magnetic = line_current_force(1e6, 1e6, pc);
let gravitational = line_mass_force(lambda, lambda, pc);
assert!(close(magnetic, 6.4815e-12, 1e-4) && close(gravitational, 17.9637, 1e-4));
assert!(close(magnetic / gravitational, 3.606e-13, 1e-3));
let far = line_current_force(1e6, 1e6, 1e3 * pc) / line_mass_force(lambda, lambda, 1e3 * pc);
assert!(close(far, magnetic / gravitational, 1e-12));
let needed = (gravitational * 2.0 * PI * pc / MU_0).sqrt();
assert!(close(needed, 1.6648e12, 1e-3), "{needed:e}");
assert!(line_current_force(1e6, 1e6, 0.01) > line_mass_force(1e-6, 1e-6, 0.01));
}
}