1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! The same equations across gravity, electromagnetism and plasma, each stated as a test that
//! calls the crates on both sides.
//!
//! **One spherical-harmonic engine.** Earth's gravity and its main magnetic field are both
//! potential fields expanded in spherical harmonics, so one engine evaluates both through
//! [`geopotential::Field`]: gravity is `∇V`, the magnetic field `−∇V` with `GM = a²` and
//! `C̄ₙₘ = gₙᵐ/√(2n+1)`.
//!
//! ```
//! use geopotential::{Coefficients, Field};
//! use magnetics::main_field::wmm2025;
//!
//! let mut gravity = Coefficients::zeros(3.986_004_415e14, 6_378_136.3, 2);
//! gravity.set(0, 0, 1.0, 0.0);
//! gravity.set(2, 0, -4.841_694_95e-4, 0.0);
//! let magnetism = wmm2025().at(2025.0);
//! let fields: [&dyn Field; 2] = [&gravity, &magnetism];
//! let r = 6_371_200.0 + 450e3; // a Swarm-like orbit
//! let [g, b] = fields.map(|f| f.magnitude(r, 0.5, 1.0));
//! assert!((8.0..8.8).contains(&g)); // m/s²
//! assert!((20_000.0..60_000.0).contains(&b)); // nT
//! ```
//!
//! **Spinning dipoles.** A spinning charge and a spinning mass both make a dipole field, twice as
//! strong at the pole as at the equator: the geomagnetic axial dipole and the gravitomagnetic
//! field of Earth's spin.
//!
//! ```
//! use geopotential::{Coefficients, Field};
//! use gravitics::gem::GravitomagneticDipole;
//! use magnetics::main_field::{MainField, REFERENCE_RADIUS};
//!
//! let a = REFERENCE_RADIUS;
//! let mut axial = Coefficients::zeros(a * a, a, 1);
//! axial.set(1, 0, -29_351.8 / 3f64.sqrt(), 0.0); // WMM2025's g₁⁰, nT
//! let magnetic = MainField(axial);
//! let gravitomagnetic = GravitomagneticDipole { j: 5.86e33 };
//! let pole = core::f64::consts::FRAC_PI_2 - 1e-9;
//! for field in [&magnetic as &dyn Field, &gravitomagnetic] {
//! assert!((field.magnitude(a, pole, 0.0) / field.magnitude(a, 0.0, 0.0) - 2.0).abs() < 1e-9);
//! }
//! ```
//!
//! **The spin-2 factor inside a spinning shell.** Inside a spinning charged shell the magnetic
//! field is `(2/3)(μ₀/4π)Qω/R`; inside a spinning massive shell, frames are dragged at
//! `4GMω/(3c²R)`. Replace `μ₀/4π` by `G/c²` and `Q` by `M`, and gravity comes out exactly twice as
//! strong: the spin-2 graviton couples to mass-currents with twice the strength.
//!
//! ```
//! use metrology::{C, G, MU_0};
//! use plasmoid::fusion::spinning_shell_field;
//!
//! let (m, r, omega) = (1e4, 1.0, 300.0);
//! let analogue = spinning_shell_field(m, r, omega) * (G / (C * C)) / (MU_0 / (4.0 * core::f64::consts::PI));
//! let frame_dragging = 4.0 * G * m * omega / (3.0 * C * C * r);
//! assert!((frame_dragging / analogue - 2.0).abs() < 1e-12);
//! assert!((frame_dragging / 2.970e-21 - 1.0).abs() < 1e-3); // rad/s
//! ```
//!
//! **One charge-to-mass constant.** `√(4πε₀G)` is where electric repulsion matches gravity
//! (the extremal black hole and the weak-gravity scale). The drift speed at which two protons'
//! magnetic attraction matches their gravity, times the proton's ratio to that constant, is the
//! speed of light exactly.
//!
//! ```
//! use gravitics::gem::wgc_ratio;
//! use metrology::{C, E_CHARGE, M_P};
//! use plasmoid::pinch::equal_attraction_drift;
//!
//! assert!((equal_attraction_drift() * wgc_ratio(E_CHARGE, M_P) / C - 1.0).abs() < 1e-9);
//! ```
//!
//! **Pinch and filament.** The Bennett relation that holds a Z-pinch together, `μ₀I²/4π`, is the
//! Ostriker relation that holds a self-gravitating gas filament together, `Gλ²`, with magnetism in
//! place of gravity. In a 1 MA pinch, magnetism wins by 10²⁷.
//!
//! ```
//! use plasmoid::pinch::{bennett_line_density, magnetic_over_gravitational};
//!
//! let line_mass = bennett_line_density(1e6, 2000.0) * 3.3436e-27; // deuterium at 2 keV
//! assert!(magnetic_over_gravitational(1e6, line_mass) > 1e27);
//! ```
//!
//! **Schwinger and Unruh.** The exponential suppression of pair creation in a field `E`,
//! `e^(−πE_S/E)`, is exactly a Boltzmann factor `e^(−m_ec²/(2k_BT_U))` at the Unruh temperature
//! of an electron accelerated by that field.
//!
//! ```
//! use gravitics::spacetime::unruh_temperature;
//! use gravitics::strong_field::{E_SCHWINGER, schwinger_exponent};
//! use metrology::{C, E_CHARGE, K_B, M_E};
//!
//! let field = 0.01 * E_SCHWINGER;
//! let t_u = unruh_temperature(E_CHARGE * field / M_E);
//! assert!((schwinger_exponent(field) / (M_E * C * C / (2.0 * K_B * t_u)) - 1.0).abs() < 1e-12);
//! ```
//!
//! **Gravity's vacuum constants.** The gravitational analogues of ε₀ and μ₀, `1/(4πG)` and
//! `4πG/c²`, give `1/√(εμ) = c`: gravitational waves and light travel at one speed.
//!
//! ```
//! use gravitics::gem::{GM_PERMEABILITY, GM_PERMITTIVITY};
//!
//! assert!((1.0 / (GM_PERMITTIVITY * GM_PERMEABILITY).sqrt() / metrology::C - 1.0).abs() < 1e-12);
//! ```
//!
//! **One plasma, three crates.** The plasma frequency and the Alfvén speed appear in
//! `gravitational` (waves meeting a plasma), `magnetics` (MHD) and `plasmoid` (reconnection), which
//! share no dependency but `metrology`, so each keeps its one-line formula. These tests hold the
//! copies together. The electron inertial length is `c` over the plasma's angular frequency,
//! exactly. The reconnection calculator's Alfvén speed counts only the ions' mass, `A·m_p`, so it
//! sits `√(1 + m_e/m_p) − 1` = 2.7×10⁻⁴ above MHD's, which counts the electrons too.
//!
//! ```
//! use gravitational::hf::plasma_frequency;
//! use magnetics::mhd::{alfven_speed, inertial_length, mass_density};
//! use metrology::{C, M_E, M_P};
//! use plasmoid::relaxation::lundquist;
//!
//! let n = 1e19;
//! let skin = inertial_length(n, M_E, 1.0);
//! assert!((skin * 2.0 * core::f64::consts::PI * plasma_frequency(n) / C - 1.0).abs() < 1e-14);
//!
//! let (_, ions_only) = lundquist(0.1, n, 10.0, 0.1, 10.0, 1.0);
//! let with_electrons = alfven_speed(0.1, mass_density(n, M_P, 1.0));
//! let gap = ions_only / with_electrons - 1.0;
//! assert!((gap - ((1.0 + M_E / M_P).sqrt() - 1.0)).abs() < 1e-14 && (gap - 2.72e-4).abs() < 1e-6);
//! ```
//!
//! **Poisson's relation.** A uniformly magnetised body's anomaly is its own gravity gradient. With
//! magnetisation `M` along `M̂` and density contrast `Δρ`, `B = (μ₀/4π)(M/(GΔρ))(Γ − I tr Γ)·M̂`,
//! so at the magnetic pole a total-field survey reads `(μ₀/4π)(M/(GΔρ))Γ_uu`. The sphere in
//! `gravimetry` and the sphere in `magnetics` are written independently and agree on the profile.
//!
//! ```
//! use core::f64::consts::PI;
//! use gravimetry::local::sphere as gravity;
//! use magnetics::anomaly::{sphere as magnetic, total_field};
//! use metrology::{G, MU_0};
//!
//! let (a, depth, rho, m) = (5.0, 20.0, 2700.0, 1.0);
//! for x in [0.0, 7.0, 25.0] {
//! let (_, gamma_uu) = gravity(a, depth, rho, x);
//! let b = magnetic([x, 0.0, 0.0], [0.0, 0.0, depth], a, [0.0, 0.0, m]); // z down, at the pole
//! let poisson = MU_0 / (4.0 * PI) * m / (G * rho) * gamma_uu;
//! assert!((b[2] / poisson - 1.0).abs() < 1e-14);
//! // A magnetometer in a 60 000 nT field reads it to within B²/2F.
//! assert!((total_field([0.0, 0.0, 6e-5], b) - poisson).abs() < 1e-12);
//! }
//! ```
//!
//! In the wavenumber domain the same relation is Baranov's pseudo-gravity: a magnetic survey,
//! reduced to the pole and integrated once downward, is the gravity survey of the same body.
//!
//! ```
//! use geopotential::planar::Grid;
//! use gravimetry::local::sphere as gravity;
//! use magnetics::anomaly::{sphere as magnetic, total_field};
//!
//! let grid = Grid { nx: 128, ny: 128, dx: 10.0, dy: 10.0 };
//! let (a, depth, rho) = (10.0, 50.0, 2700.0);
//! let tilted = [45f64.to_radians().cos(), 0.0, 45f64.to_radians().sin()]; // I = 45°, z down
//! let earth = tilted.map(|u| 5e-5 * u);
//! let survey: Vec<f64> = grid
//! .points()
//! .map(|(x, y)| total_field(earth, magnetic([x, y, 0.0], [640.0, 640.0, depth], a, tilted)))
//! .collect();
//! let pseudo = grid.pseudo_gravity(&survey, tilted, tilted, rho);
//! let (centre, corner) = (64 * 128 + 64, 0);
//! let peak = gravity(a, depth, rho, 0.0).0 - gravity(a, depth, rho, 640f64.hypot(640.0)).0;
//! assert!(((pseudo[centre] - pseudo[corner]) / peak - 1.0).abs() < 3e-3); // −0.17%, from the periodic grid
//! ```