use core::f64::consts::TAU;
use metrology::{GAMMA_P, HBAR, MU_0};
pub fn loop_bz(i: f64, r: f64, z: f64) -> f64 {
MU_0 * i * r * r / (2.0 * (r * r + z * z).powf(1.5))
}
pub fn solenoid_bz(z: f64, r: f64, l: f64, n: f64, i: f64) -> f64 {
let end = |s: f64| s / (s * s + r * r).sqrt();
MU_0 * n * i / 2.0 * (end(z + l / 2.0) - end(z - l / 2.0))
}
pub fn meg_radial_field(q: [f64; 3], r0: [f64; 3], r: [f64; 3]) -> f64 {
use crate::toroidal::{cross, dot};
let d = [r[0] - r0[0], r[1] - r0[1], r[2] - r0[2]];
MU_0 / (4.0 * core::f64::consts::PI) * dot(cross(q, d), r) / (dot(r, r).sqrt() * dot(d, d).powf(1.5))
}
pub fn wire_joule_per_length(p: f64, v: f64, d: f64, rho: f64) -> f64 {
let i = p / v;
i * i * rho / (core::f64::consts::PI * d * d / 4.0)
}
pub fn helmholtz_b0(r: f64, n: f64, i: f64) -> f64 {
0.8f64.powf(1.5) * MU_0 * n * i / r
}
pub fn lorentz_line_load(i: f64, b: f64) -> f64 {
i * b
}
pub fn stored_energy(b: f64, volume: f64) -> f64 {
crate::mhd::magnetic_pressure(b) * volume
}
pub fn virial_specific_energy(sigma: f64, rho: f64) -> f64 {
sigma / rho
}
pub fn skin_depth(rho: f64, f: f64, mu_r: f64) -> f64 {
(2.0 * rho / (TAU * f * MU_0 * mu_r)).sqrt()
}
pub fn molar_susceptibility_si(chi_cgs: f64) -> f64 {
4.0 * core::f64::consts::PI * 1e-6 * chi_cgs
}
pub fn curie_scale(chi: f64, t0: f64, t: f64) -> f64 {
chi * t0 / t
}
pub fn levitation_product(rho: f64, chi: f64, g: f64) -> f64 {
MU_0 * rho * g / chi.abs()
}
pub fn susceptibility_force(volume: f64, chi: f64, b_dbdx: f64) -> f64 {
chi * volume / MU_0 * b_dbdx
}
pub fn inductrack_lift_drag(v: f64, v_t: f64) -> f64 {
v / v_t
}
pub fn larmor_frequency(b: f64) -> f64 {
GAMMA_P * b / TAU
}
pub fn sar_partial_body(r: f64) -> f64 {
10.0 - 8.0 * r
}
pub fn energy_resolution(db: f64, volume: f64) -> f64 {
db * db * volume / (2.0 * MU_0 * HBAR)
}
pub fn flux_compression(b0: f64, r0: f64, r: f64, lambda: f64) -> f64 {
lambda * b0 * (r0 / r).powi(2)
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: f64, b: f64, rel: f64) -> bool {
(a / b - 1.0).abs() < rel
}
#[test]
fn coil_fields() {
let (n, i) = (1e4, 2.0);
assert!(close(solenoid_bz(0.0, 0.01, 100.0, n, i), MU_0 * n * i, 1e-7));
assert!(close(solenoid_bz(50.0, 0.01, 100.0, n, i), MU_0 * n * i / 2.0, 1e-6));
assert!(close(helmholtz_b0(1.0, 1.0, 1.0) / MU_0, 0.715_542, 1e-6));
assert!(close(helmholtz_b0(0.3, 1.0, 5.0), 2.0 * loop_bz(5.0, 0.3, 0.15), 1e-12));
let pair = |z: f64| loop_bz(1.0, 1.0, z - 0.5) + loop_bz(1.0, 1.0, z + 0.5);
let h = 1e-3;
assert!(((pair(h) - 2.0 * pair(0.0) + pair(-h)) / (h * h)).abs() < 1e-5 * pair(0.0));
}
#[test]
fn meg_and_thin_wires() {
let (big_r, d0) = (0.100_f64, 0.070);
let s = big_r * big_r + d0 * d0;
let c = ((s * s + 12.0 * big_r * big_r * d0 * d0).sqrt() - s) / (2.0 * big_r * d0);
let at = |th: f64| meg_radial_field([10e-9, 0.0, 0.0], [0.0, 0.0, d0], [0.0, big_r * th.sin(), big_r * th.cos()]);
let peak = at(c.acos());
let ft = peak * 1e15;
assert!((ft - 355.0).abs() < 0.05 && (0.0..2e-3).contains(&(ft - 354.958)), "{ft}");
assert!(peak > at(c.acos() + 1e-3) && peak > at(c.acos() - 1e-3) && (50e-15..500e-15).contains(&peak));
assert!(at(0.0).abs() < 1e-30 && meg_radial_field([10e-9, 0.0, 0.0], [0.0, 0.0, d0], [big_r, 0.0, 0.0]).abs() < 1e-30);
let rho = 1.7241e-8;
assert!(close(wire_joule_per_length(1.0, 1.0, 8e-6, rho), 343.0, 1e-3));
assert!(close(wire_joule_per_length(1000.0, 230.0, 8e-6, rho), 6.48e3, 1e-3));
assert!(close(wire_joule_per_length(1000.0, 10e3, 8e-6, rho), 3.43, 1e-3));
}
#[test]
fn forces_and_energy() {
assert!(close(lorentz_line_load(40.5e3, 20.1), 814_050.0, 1e-12)); assert!(close(stored_energy(16.5, 1.0), 1.083e8, 1e-3));
assert!(close(virial_specific_energy(100e6, 8000.0), 12_500.0, 1e-12)); assert!(close(flux_compression(3.0, 25.0, 1.0, 1.0), 1875.0, 1e-12));
}
#[test]
fn induction_and_levitation() {
let rho = 1.0 / 5.96e7;
assert!(close(skin_depth(rho, 60.0, 1.0), 8.42e-3, 1e-3) && close(skin_depth(rho, 50.0, 1.0), 9.22e-3, 1e-3));
assert!(close(skin_depth(rho, 1e6, 1.0), 65.2e-6, 1e-3));
assert!(close(levitation_product(1000.0, -8.8e-6, 9.81), 1400.9, 1e-4));
assert!(close(500.0 / 300.0, 50.0 / 30.0, 1e-12) && close(inductrack_lift_drag(500.0, 500.0 / 300.0), 300.0, 1e-12));
}
#[test]
fn molar_susceptibility() {
let si = molar_susceptibility_si(3449e-6);
assert!(close(si, 4.334e-8, 1e-3) && close(si, 42.92e-9, 0.011));
assert!(close(curie_scale(3449e-6, 293.15, 273.15), 3700e-6, 1e-3));
}
#[test]
fn medicine_and_sensing() {
for (b, mhz) in [(1.5, 63.866), (3.0, 127.732), (7.0, 298.042), (11.7, 498.16)] {
assert!(close(larmor_frequency(b) / 1e6, mhz, 2e-5), "{b} T");
}
assert!((sar_partial_body(0.5) - 6.0).abs() < 1e-12);
assert!(close(energy_resolution(160e-18, 0.45e-6), 43.5, 3e-3));
}
#[test]
fn magnetic_systematics_in_precision_gravity() {
let (volume, chi) = (0.2 / 19_300.0, -3.4e-5);
let signal = metrology::G * 10.0 * 0.2 / 0.1f64.powi(2);
assert!(close(signal, 1.335e-8, 1e-3));
let earth = 3.0 * 5e-5 * 5e-5 / 6.371e6;
assert!(close(susceptibility_force(volume, chi, earth).abs() / signal, 2.47e-11, 1e-2));
let lab = 1.047e-9;
assert!(close(susceptibility_force(volume, chi, lab).abs() / signal, 2.2e-5, 1e-2));
assert!(close(susceptibility_force(volume, chi, 2.0 * lab) / susceptibility_force(volume, chi, lab), 2.0, 1e-12));
assert!(susceptibility_force(volume, -chi, lab) == -susceptibility_force(volume, chi, lab));
}
}