use astrodyn_quantities::dims::GravParam;
use astrodyn_quantities::frame::SelfPlanet;
use uom::si::f64::Length;
#[derive(Debug, Clone)]
pub struct SphericalHarmonicsData {
pub degree: usize,
pub order: usize,
pub radius: f64,
pub mu: f64,
pub cnm: Vec<Vec<f64>>,
pub snm: Vec<Vec<f64>>,
pub tide_free: bool,
pub tide_free_delta: f64,
pub(crate) alpha: Vec<f64>,
pub(crate) beta: Vec<f64>,
pub(crate) xi: Vec<Vec<f64>>,
pub(crate) eta: Vec<Vec<f64>>,
pub(crate) zeta: Vec<Vec<f64>>,
pub(crate) upsilon: Vec<Vec<f64>>,
pub(crate) nrdiag: Vec<f64>,
pub(crate) int_to_double: Vec<f64>,
}
impl SphericalHarmonicsData {
#[allow(clippy::too_many_arguments)]
pub fn new(
degree: usize,
order: usize,
radius: f64,
mu: f64,
cnm: Vec<Vec<f64>>,
snm: Vec<Vec<f64>>,
tide_free: bool,
tide_free_delta: f64,
) -> Self {
assert!(degree > 0, "degree must be > 0");
assert!(order <= degree, "order must be <= degree");
assert_eq!(cnm.len(), degree + 1);
assert_eq!(snm.len(), degree + 1);
for (n, row) in cnm.iter().enumerate() {
assert_eq!(
row.len(),
n + 1,
"cnm[{n}] must have {} elements, got {}",
n + 1,
row.len()
);
}
for (n, row) in snm.iter().enumerate() {
assert_eq!(
row.len(),
n + 1,
"snm[{n}] must have {} elements, got {}",
n + 1,
row.len()
);
}
let mut data = Self {
degree,
order,
radius,
mu,
cnm,
snm,
tide_free,
tide_free_delta,
alpha: vec![0.0; degree + 1],
beta: vec![0.0; degree + 1],
xi: Vec::with_capacity(degree + 1),
eta: Vec::with_capacity(degree + 1),
zeta: Vec::with_capacity(degree + 1),
upsilon: Vec::with_capacity(degree + 1),
nrdiag: vec![0.0; degree + 1],
int_to_double: vec![0.0; degree + 2],
};
for ii in 0..=degree {
data.xi.push(vec![0.0; ii + 1]);
data.eta.push(vec![0.0; ii + 1]);
data.zeta.push(vec![0.0; ii + 1]);
data.upsilon.push(vec![0.0; ii + 1]);
}
data.initialize_body();
data
}
#[allow(clippy::needless_range_loop)]
fn initialize_body(&mut self) {
let degree = self.degree;
for ii in 0..=degree + 1 {
self.int_to_double[ii] = ii as f64;
}
let mut pnm: Vec<Vec<f64>> = Vec::with_capacity(degree + 1);
for ii in 0..=degree {
pnm.push(vec![0.0; ii + 3]);
}
pnm[0][0] = 1.0;
pnm[0][1] = 0.0;
pnm[0][2] = 0.0;
pnm[1][1] = 3.0_f64.sqrt();
pnm[1][2] = 0.0;
pnm[1][3] = 0.0;
let i2d = &self.int_to_double;
for ii in 2..=degree {
let ii_f = i2d[ii];
for jj in 0..=(ii - 1) {
let jj_f = i2d[jj];
let num1 = (2.0 * ii_f - 1.0) * (2.0 * ii_f + 1.0);
let den1 = (ii_f + jj_f) * (ii_f - jj_f);
self.xi[ii][jj] = (num1 / den1).sqrt();
let num2 = (2.0 * ii_f + 1.0) * (ii_f + jj_f - 1.0) * (ii_f - jj_f - 1.0);
let den2 = (ii_f + jj_f) * (ii_f - jj_f) * (2.0 * ii_f - 3.0);
if num2 == 0.0 {
self.eta[ii][jj] = 0.0;
} else {
self.eta[ii][jj] = (num2 / den2).sqrt();
}
}
for jj in 0..=ii {
let jj_f = i2d[jj];
if ii == jj {
self.zeta[ii][jj] = 0.0;
self.upsilon[ii][jj] = 0.0;
} else if jj == 0 {
self.zeta[ii][0] = (ii_f * (ii_f + 1.0) / 2.0).sqrt();
self.upsilon[ii][0] =
(ii_f * (ii_f - 1.0) * (ii_f + 1.0) * (ii_f + 2.0) / 2.0).sqrt();
} else {
self.zeta[ii][jj] = ((ii_f - jj_f) * (ii_f + jj_f + 1.0)).sqrt();
self.upsilon[ii][jj] = ((ii_f - jj_f)
* (ii_f + jj_f + 1.0)
* (ii_f - jj_f - 1.0)
* (ii_f + jj_f + 2.0))
.sqrt();
}
}
pnm[ii][ii] = ((2.0 * ii_f + 1.0) / (2.0 * ii_f)).sqrt() * pnm[ii - 1][ii - 1];
pnm[ii][ii + 1] = 0.0;
pnm[ii][ii + 2] = 0.0;
self.nrdiag[ii] = (2.0 * ii_f + 1.0).sqrt() * pnm[ii - 1][ii - 1];
self.alpha[ii] = ((2.0 * ii_f + 1.0) * (2.0 * ii_f - 1.0)).sqrt() / ii_f;
self.beta[ii] = ((2.0 * ii_f + 1.0) / (2.0 * ii_f - 3.0)).sqrt() * (ii_f - 1.0) / ii_f;
}
}
#[inline]
pub fn mu_typed(&self) -> GravParam<SelfPlanet> {
GravParam::<SelfPlanet>::from_si(self.mu)
}
#[inline]
pub fn radius_typed(&self) -> Length {
Length::new::<uom::si::length::meter>(self.radius)
}
}