use std::f64::consts::PI;
pub const RE_MAGNETIC: f64 = 6371.2;
pub(crate) fn legendre_schmidt(theta: f64, n_max: usize) -> (Vec<Vec<f64>>, Vec<Vec<f64>>) {
let cos_theta = theta.cos();
let sin_theta = theta.sin();
let size = n_max + 1;
let mut p = vec![vec![0.0; size]; size];
let mut dp = vec![vec![0.0; size]; size];
let mut s = vec![vec![0.0; size]; size];
s[0][0] = 1.0;
p[0][0] = 1.0;
for n in 1..=n_max {
for m in 0..=n {
if n == m {
p[n][n] = sin_theta * p[n - 1][m - 1];
dp[n][n] = sin_theta * dp[n - 1][m - 1] + cos_theta * p[n - 1][n - 1];
} else if n == 1 {
p[n][m] = cos_theta * p[n - 1][m];
dp[n][m] = cos_theta * dp[n - 1][m] - sin_theta * p[n - 1][m];
} else {
let knm = ((n - 1) * (n - 1) - m * m) as f64 / ((2 * n - 1) * (2 * n - 3)) as f64;
p[n][m] = cos_theta * p[n - 1][m] - knm * p[n - 2][m];
dp[n][m] = cos_theta * dp[n - 1][m] - sin_theta * p[n - 1][m] - knm * dp[n - 2][m];
}
if m == 0 {
s[n][0] = s[n - 1][0] * (2 * n - 1) as f64 / n as f64;
} else {
let delta_m1 = if m == 1 { 1.0 } else { 0.0 };
s[n][m] =
s[n][m - 1] * ((n - m + 1) as f64 * (1.0 + delta_m1) / (n + m) as f64).sqrt();
}
}
}
for n in 1..=n_max {
for m in 0..=n {
p[n][m] *= s[n][m];
dp[n][m] *= s[n][m];
}
}
(p, dp)
}
pub(crate) fn synth_field_geocentric(
r: f64,
theta: f64,
phi: f64,
g: &[f64],
h: &[f64],
n_max: usize,
re: f64,
) -> (f64, f64, f64) {
let theta = theta.clamp(1e-10, PI - 1e-10);
let (p, dp) = legendre_schmidt(theta, n_max);
let sin_theta = theta.sin();
let mut cos_m_phi = vec![0.0; n_max + 1];
let mut sin_m_phi = vec![0.0; n_max + 1];
for m in 0..=n_max {
cos_m_phi[m] = (m as f64 * phi).cos();
sin_m_phi[m] = (m as f64 * phi).sin();
}
let mut b_r = 0.0;
let mut b_theta = 0.0;
let mut b_phi = 0.0;
for n in 1..=n_max {
let ratio = re / r;
let ratio_n2 = ratio.powi(n as i32 + 2);
for m in 0..=n {
let idx = n * (n + 1) / 2 + m - 1;
let g_nm = g[idx];
let h_nm = h[idx];
let p_nm = p[n][m];
let dp_nm = dp[n][m];
let cos_mp = cos_m_phi[m];
let sin_mp = sin_m_phi[m];
let gh_cos = g_nm * cos_mp + h_nm * sin_mp;
let gh_sin = -g_nm * sin_mp + h_nm * cos_mp;
b_r += ratio_n2 * (n + 1) as f64 * gh_cos * p_nm;
b_theta -= ratio_n2 * gh_cos * dp_nm;
b_phi -= ratio_n2 * m as f64 * gh_sin * p_nm / sin_theta;
}
}
(b_r, b_theta, b_phi)
}
#[allow(dead_code)]
pub(crate) fn idx_to_nm(idx: usize) -> (usize, usize) {
let n = ((((2 * (idx + 1)) as f64).sqrt() + 0.5) as usize).max(1);
let base = n * (n + 1) / 2 - 1;
if idx >= base && idx < base + n + 1 {
(n, idx - base)
} else {
let n = n - 1;
let base = n * (n + 1) / 2 - 1;
(n, idx - base)
}
}
pub(crate) fn nm_to_idx(n: usize, m: usize) -> usize {
n * (n + 1) / 2 + m - 1
}
pub(crate) fn num_coefficients(n_max: usize) -> usize {
n_max * (n_max + 3) / 2
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_legendre_p00_is_one() {
for &theta_deg in &[0.1_f64, 30.0, 45.0, 60.0, 89.9, 179.9] {
let theta = theta_deg.to_radians();
let (p, _) = legendre_schmidt(theta, 3);
assert_abs_diff_eq!(p[0][0], 1.0, epsilon = 1e-14);
}
}
#[test]
fn test_legendre_p10_is_cos_theta() {
for &theta_deg in &[10.0_f64, 30.0, 45.0, 60.0, 80.0, 150.0] {
let theta = theta_deg.to_radians();
let (p, _) = legendre_schmidt(theta, 3);
assert_abs_diff_eq!(p[1][0], theta.cos(), epsilon = 1e-12);
}
}
#[test]
fn test_legendre_p11_is_sin_theta() {
for &theta_deg in &[10.0_f64, 30.0, 45.0, 60.0, 80.0, 150.0] {
let theta = theta_deg.to_radians();
let (p, _) = legendre_schmidt(theta, 3);
assert_abs_diff_eq!(p[1][1], theta.sin(), epsilon = 1e-12);
}
}
#[test]
fn test_legendre_p20() {
let theta = 60.0_f64.to_radians();
let (p, _) = legendre_schmidt(theta, 2);
assert_abs_diff_eq!(p[2][0], -0.125, epsilon = 1e-12);
let theta0 = 0.001_f64.to_radians();
let (p0, _) = legendre_schmidt(theta0, 2);
assert_abs_diff_eq!(p0[2][0], 1.0, epsilon = 1e-4);
}
#[test]
fn test_nm_to_idx_roundtrip() {
assert_eq!(nm_to_idx(1, 0), 0);
assert_eq!(nm_to_idx(1, 1), 1);
assert_eq!(nm_to_idx(2, 0), 2);
assert_eq!(nm_to_idx(2, 1), 3);
assert_eq!(nm_to_idx(2, 2), 4);
assert_eq!(nm_to_idx(3, 0), 5);
}
#[test]
fn test_num_coefficients() {
assert_eq!(num_coefficients(1), 2);
assert_eq!(num_coefficients(2), 5);
assert_eq!(num_coefficients(13), 104);
assert_eq!(num_coefficients(133), 9044);
}
}