use core::f64::consts::TAU;
use metrology::{M_P, MU_0};
pub const SOLAR_ROTATION: f64 = TAU / (25.38 * 86_400.0);
pub const NERC_BENCHMARK: f64 = 8e-3;
pub const NERC_SUPPLEMENTAL: f64 = 12e-3;
pub fn dynamic_pressure(n: f64, v: f64) -> f64 {
n * M_P * v * v
}
pub fn standoff(b0: f64, p: f64, compressed: bool) -> f64 {
let k = if compressed { 2.0 } else { 0.5 };
(k * b0 * b0 / (MU_0 * p)).powf(1.0 / 6.0)
}
pub fn shue1998(bz_nt: f64, p_npa: f64) -> f64 {
(10.22 + 1.29 * (0.184 * (bz_nt + 8.14)).tanh()) * p_npa.powf(-1.0 / 6.6)
}
pub fn parker_angle(v: f64, r: f64) -> f64 {
(SOLAR_ROTATION * r / v).atan()
}
pub fn ring_current_energy(dst: f64, moment: f64) -> f64 {
moment * dst.abs() / 2.0
}
pub fn plasmapause(kp_max: f64) -> Option<f64> {
let l = 5.6 - 0.46 * kp_max;
(l > 2.25 && l < 8.0).then_some(l)
}
pub fn dipole_field(b0: f64, l: f64, mlat: f64) -> f64 {
b0 / l.powi(3) * (1.0 + 3.0 * mlat.sin().powi(2)).sqrt() / mlat.cos().powi(6)
}
pub fn invariant_latitude(l: f64) -> f64 {
l.powf(-0.5).acos()
}
pub fn dynamo_field(u: f64, b: f64) -> f64 {
u * b
}
pub fn geoelectric_halfspace(b: f64, period: f64, sigma: f64) -> (f64, f64) {
let omega = TAU / period;
((omega / (MU_0 * sigma)).sqrt() * b, (2.0 / (omega * MU_0 * sigma)).sqrt())
}
pub fn apparent_resistivity(e: f64, b: f64, period: f64) -> f64 {
MU_0 * e * e / (TAU / period * b * b)
}
pub fn field_line_radius(l: f64, mlat: f64) -> f64 {
l * mlat.cos().powi(2)
}
#[cfg(test)]
mod tests {
use super::*;
use metrology::AU;
fn close(a: f64, b: f64, rel: f64) -> bool {
(a / b - 1.0).abs() < rel
}
#[test]
fn magnetopause() {
let p = dynamic_pressure(5e6, 400e3);
assert!(close(p, 1.338_098e-9, 1e-6));
let b0 = 29_733.4e-9; assert!(close(standoff(b0, p, true), 10.084_09, 1e-6) && close(standoff(b0, p, false), 8.003_75, 1e-6));
assert!(close(standoff(b0, p, true) / standoff(b0, p, false), 4f64.powf(1.0 / 6.0), 1e-12));
assert!(close(shue1998(0.0, 1.338), 10.895_67, 1e-5) && close(shue1998(-20.0, 20.0), 5.692_45, 1e-5));
}
#[test]
fn heliosphere_and_storms() {
assert!(close(parker_angle(400e3, AU).to_degrees(), 46.979_98, 1e-6));
assert!(close(parker_angle(800e3, AU).to_degrees(), 28.182_80, 1e-6));
let m = 7.690e22;
assert!(close(ring_current_energy(-1e-9, m), 3.845e13, 1e-12));
assert!(close(ring_current_energy(-412e-9, m), 1.584e16, 1e-3) && close(ring_current_energy(-589e-9, m), 2.265e16, 1e-3));
}
#[test]
fn inner_magnetosphere() {
assert!(close(plasmapause(2.0).unwrap(), 4.68, 1e-12) && close(plasmapause(6.0).unwrap(), 2.84, 1e-12));
assert!(plasmapause(9.0).is_none());
let b0 = 29_733.4e-9;
assert!(close(dipole_field(b0, 4.0, 0.0), 464.6e-9, 1e-4));
assert!(close(dipole_field(b0, 4.0, 30f64.to_radians()), 1456.8e-9, 1e-4));
assert!(close(field_line_radius(4.0, 30f64.to_radians()), 3.0, 1e-12));
assert!(close(invariant_latitude(4.0).to_degrees(), 60.0, 1e-12));
}
#[test]
fn ground_electric_field() {
let (e, d) = geoelectric_halfspace(500e-9, 300.0, 1e-3);
assert!(close(e * 1e3, 2.04, 3e-3) && close(d / 1e3, 276.0, 2e-3));
let (e, d) = geoelectric_halfspace(1000e-9, 300.0, 1e-4);
assert!(close(e * 1e3, 12.9, 3e-3) && close(d / 1e3, 872.0, 2e-3) && e > NERC_BENCHMARK);
let (e, _) = geoelectric_halfspace(100e-9, 100.0, 1e-3);
assert!(close(apparent_resistivity(e, 100e-9, 100.0), 1e3, 1e-12));
assert!(close(apparent_resistivity(e, 100e-9, 100.0), 0.2 * 100.0 * (e * 1e6 / 100.0).powi(2), 1e-6));
}
#[test]
fn an_atmospheric_wave_drives_a_dynamo() {
assert!(close(dynamo_field(2.04, 3e-5) * 1e6, 61.2, 1e-2));
assert!(close(dynamo_field(2.04 * 1265.0, 3e-5) * 1e3, 77.4, 1e-2));
assert!(dynamo_field(0.0, 3e-5) == 0.0);
let rho = 1e12 * 16.0 * metrology::AMU;
let va = crate::mhd::alfven_speed(3e-5, rho);
assert!(close(va / 1e3, 164.0, 1e-2) && 2e6 / va < 20.0);
}
}