use core::f64::consts::PI;
use geopotential::linalg::gauss;
use geopotential::special::carlson_rd;
use metrology::MU_0;
const C_M: f64 = MU_0 / (4.0 * PI);
pub fn body(hessian: [[f64; 3]; 3], m: [f64; 3]) -> [f64; 3] {
let trace = hessian[0][0] + hessian[1][1] + hessian[2][2];
core::array::from_fn(|i| C_M * ((0..3).map(|j| hessian[i][j] * m[j]).sum::<f64>() - trace * m[i]))
}
pub fn sphere(p: [f64; 3], centre: [f64; 3], a: f64, m: [f64; 3]) -> [f64; 3] {
let d: [f64; 3] = core::array::from_fn(|i| p[i] - centre[i]);
let r2 = d[0] * d[0] + d[1] * d[1] + d[2] * d[2];
let t = if r2 < a * a {
core::array::from_fn(|i| core::array::from_fn(|j| if i == j { -4.0 * PI / 3.0 } else { 0.0 }))
} else {
let k = 4.0 * PI * a.powi(3) / (3.0 * r2 * r2 * r2.sqrt());
core::array::from_fn(|i| core::array::from_fn(|j| k * (3.0 * d[i] * d[j] - if i == j { r2 } else { 0.0 })))
};
body(t, m)
}
pub fn cylinder(p: [f64; 3], point: [f64; 3], axis: [f64; 3], a: f64, m: [f64; 3]) -> [f64; 3] {
let n = (axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]).sqrt();
let u = axis.map(|x| x / n);
let d: [f64; 3] = core::array::from_fn(|i| p[i] - point[i]);
let along = d[0] * u[0] + d[1] * u[1] + d[2] * u[2];
let rho: [f64; 3] = core::array::from_fn(|i| d[i] - along * u[i]);
let r2 = rho[0] * rho[0] + rho[1] * rho[1] + rho[2] * rho[2];
let across = |i: usize, j: usize| if i == j { 1.0 } else { 0.0 } - u[i] * u[j];
let t = if r2 < a * a {
core::array::from_fn(|i| core::array::from_fn(|j| -2.0 * PI * across(i, j)))
} else {
let k = 2.0 * PI * a * a / (r2 * r2);
core::array::from_fn(|i| core::array::from_fn(|j| k * (2.0 * rho[i] * rho[j] - r2 * across(i, j))))
};
body(t, m)
}
pub fn ellipsoid_factors(a: f64, b: f64, c: f64) -> [f64; 3] {
let factor = |x: f64, y: f64, z: f64| x * y * z / 3.0 * carlson_rd(x * x, y * y, z * z);
[factor(b, c, a), factor(c, a, b), factor(a, b, c)]
}
pub fn magnetisation(chi: f64, h0: [f64; 3], remanent: [f64; 3], n: [[f64; 3]; 3]) -> [f64; 3] {
let rows: Vec<Vec<f64>> = (0..3)
.map(|i| (0..3).map(|j| if i == j { 1.0 } else { 0.0 } + chi * n[i][j]).chain([chi * h0[i] + remanent[i]]).collect())
.collect();
let m = gauss(rows);
[m[0], m[1], m[2]]
}
pub fn koenigsberger(remanent: [f64; 3], chi: f64, h0: [f64; 3]) -> f64 {
let norm = |v: [f64; 3]| (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
norm(remanent) / (chi * norm(h0))
}
pub fn total_field(f: [f64; 3], b: [f64; 3]) -> f64 {
let dot = |x: [f64; 3], y: [f64; 3]| x[0] * y[0] + x[1] * y[1] + x[2] * y[2];
let sum = [f[0] + b[0], f[1] + b[1], f[2] + b[2]];
(2.0 * dot(f, b) + dot(b, b)) / (dot(sum, sum).sqrt() + dot(f, f).sqrt())
}
#[cfg(test)]
mod tests {
use super::*;
use geopotential::bodies::prism;
const NT: f64 = 1e-9;
#[test]
fn a_sphere_at_the_pole_and_the_equator() {
let m = 4.0 / 3.0 * PI * 125.0;
let b = sphere([0.0; 3], [0.0, 0.0, 20.0], 5.0, [0.0, 0.0, 1.0]);
assert!((b[2] / (2.0 * C_M * m / 8000.0) - 1.0).abs() < 1e-14 && (b[2] / NT - 13.09).abs() < 1e-3);
let pole = total_field([0.0, 0.0, 50_000.0 * NT], b);
assert!((pole - b[2]).abs() < 2e-3 * NT); let b = sphere([0.0; 3], [0.0, 0.0, 20.0], 5.0, [1.0, 0.0, 0.0]);
let equator = total_field([30_000.0 * NT, 0.0, 0.0], b);
assert!((equator / NT + 6.545).abs() < 1e-3 && (equator / b[0] - 1.0).abs() < 1e-14);
let inside = sphere([1.0, 2.0, 19.0], [0.0, 0.0, 20.0], 5.0, [0.0, 0.0, 1.0]);
assert!((inside[2] - 2.0 / 3.0 * MU_0).abs() < 1e-20 && inside[0] == 0.0);
}
#[test]
fn demagnetising_factors_of_ellipsoids() {
for (gamma, want, digits) in [(0.01, 0.9845, 4), (0.1, 0.8608, 4), (0.5, 0.5272, 4), (1.0, 0.3333, 4), (2.0, 0.1736, 4), (5.0, 0.05582, 5), (10.0, 0.02029, 5), (100.0, 0.0004299, 7)] {
let n = ellipsoid_factors(1.0, 1.0, gamma);
assert!((n[2] - want).abs() < 0.5 * 10f64.powi(-digits) + 1e-12, "γ = {gamma}: {}", n[2]);
assert!((n[0] + n[1] + n[2] - 1.0).abs() < 1e-14 && (n[0] - n[1]).abs() < 1e-15);
}
assert!((ellipsoid_factors(2.0, 2.0, 2.0)[1] - 1.0 / 3.0).abs() < 1e-15);
let n = ellipsoid_factors(490.7, 69.7, 30.0);
assert!((n[2] - 0.689_521).abs() < 1e-6 && (0.08 / n[2] - 0.116).abs() < 5e-4, "{n:?}");
assert!((ellipsoid_factors(30.0, 490.7, 69.7)[0] - n[2]).abs() < 1e-15);
}
#[test]
fn induced_and_remanent_magnetisation() {
let n3 = [[1.0 / 3.0, 0.0, 0.0], [0.0, 1.0 / 3.0, 0.0], [0.0, 0.0, 1.0 / 3.0]];
let h0 = [0.0, 0.0, 50e-6 / MU_0]; assert!((h0[2] - 39.79).abs() < 0.01);
for (chi, factor) in [(0.1, 0.967_742), (1.0, 0.75), (10.0, 0.230_769)] {
let m = magnetisation(chi, h0, [0.0; 3], n3);
assert!((m[2] / (chi * h0[2]) - factor).abs() < 1e-6 && m[0] == 0.0, "χ = {chi}: {m:?}");
}
let m = magnetisation(1.0, h0, [3.0, 0.0, 0.0], n3);
assert!((m[0] - 2.25).abs() < 1e-12 && (m[2] - 0.75 * h0[2]).abs() < 1e-12);
assert!((koenigsberger([3.0, 0.0, 0.0], 1.0, h0) - 3.0 / h0[2]).abs() < 1e-15);
let (a, b, c, chi, u) = (900.0f64, 500.0f64, 100.0f64, 1.2, 2e6);
let (a2, b2, c2) = ((a * a + u).sqrt(), (b * b + u).sqrt(), (c * c + u).sqrt());
assert!((a2 - 1676.31).abs() < 0.01 && (c2 - 1417.74).abs() < 0.01);
let diag = |n: [f64; 3]| [[n[0], 0.0, 0.0], [0.0, n[1], 0.0], [0.0, 0.0, n[2]]];
let h = [1.0, 0.0, 0.0];
let moment = a * b * c * magnetisation(chi, h, [0.0; 3], diag(ellipsoid_factors(a, b, c)))[0];
let chi2 = 0.014_15;
let moment2 = a2 * b2 * c2 * magnetisation(chi2, h, [0.0; 3], diag(ellipsoid_factors(a2, b2, c2)))[0];
assert!((moment2 / moment - 1.0).abs() < 1e-3 && (a2 * b2 * c2 / (a * b * c) - 79.2).abs() < 0.1 && (chi / chi2 - 84.8).abs() < 0.1);
let weak = magnetisation(1e-4, h0, [0.1, 0.0, 0.0], n3);
assert!((weak[2] / (1e-4 * h0[2]) - 1.0).abs() < 4e-5 && (weak[0] - 0.1).abs() < 1e-5);
let t = prism([0.0; 3], [-0.5; 3], [0.5; 3]).2;
let cube: [[f64; 3]; 3] = core::array::from_fn(|i| core::array::from_fn(|j| -t[i][j] / (4.0 * PI)));
assert!((magnetisation(1.0, h0, [0.0; 3], cube)[2] / (0.75 * h0[2]) - 1.0).abs() < 1e-13);
}
#[test]
fn a_cube_through_its_gravity() {
let m = [0.3, -0.5, 0.8];
let b = body(prism([0.0; 3], [-0.5; 3], [0.5; 3]).2, m);
assert!((0..3).all(|i| (b[i] - 2.0 / 3.0 * MU_0 * m[i]).abs() < 1e-20));
let p = [12.0, -16.0, 30.0];
let (bc, bs) = (body(prism(p, [-0.5; 3], [0.5; 3]).2, m), sphere(p, [0.0; 3], (3.0 / (4.0 * PI)).cbrt(), m));
assert!((0..3).all(|i| (bc[i] / bs[i] - 1.0).abs() < 3e-7), "{bc:?} {bs:?}");
}
#[test]
fn boundary_conditions_on_a_face() {
let (min, max) = ([-1.0, -2.0, 0.0], [3.0, 1.0, 2.0]);
let m = [0.4, 0.7, -0.6];
let (p, h) = ([0.5, -0.3, 2.0], 1e-7);
let out = body(prism([p[0], p[1], p[2] + h], min, max).2, m);
let inn = body(prism([p[0], p[1], p[2] - h], min, max).2, m);
assert!((out[2] - inn[2]).abs() < 1e-12, "B_z {} vs {}", out[2], inn[2]);
for i in 0..2 {
assert!((out[i] / MU_0 - (inn[i] / MU_0 - m[i])).abs() < 1e-6, "H_{i}");
}
assert!((inn[0] - out[0] - MU_0 * m[0]).abs() < 1e-12);
}
#[test]
fn a_cylinder() {
let (a, m) = (1.0, [0.6, 0.0, 0.8]);
let s = (PI * a * a).sqrt() / 2.0;
let p = [24.0, 0.0, -32.0];
let bc = cylinder(p, [0.0; 3], [0.0, 1.0, 0.0], a, m);
let bp = body(prism(p, [-s, -1e6, -s], [s, 1e6, s]).2, m);
assert!((0..3).all(|i| (bc[i] - bp[i]).abs() < 1e-6 * bc[0].abs().max(bc[2].abs())), "{bc:?} {bp:?}");
assert!(bc[1].abs() < 1e-30);
let inside = cylinder([0.2, 5.0, 0.1], [0.0; 3], [0.0, 2.0, 0.0], a, [1.0, 1.0, 0.0]);
assert!((inside[0] - MU_0 / 2.0).abs() < 1e-20 && (inside[1] - MU_0).abs() < 1e-20);
let b = cylinder([0.0; 3], [0.0, 0.0, 15.0], [0.0, 1.0, 0.0], 3.0, [0.0, 0.0, 1.0]);
assert!((b[2] / (2.0 * C_M * PI * 9.0 / 225.0) - 1.0).abs() < 1e-14);
}
}