use arrayvec::ArrayVec;
use core::f64::consts::PI;
#[allow(unused_imports)]
use num_traits::Float as _;
use crate::{BoundedRevs, MAX_MULTI_REV_PAIRS};
use crate::constants::{
HALLEY_MAX_ITERS, HALLEY_TOL, HOUSEHOLDER_MAX_ITERS, HOUSEHOLDER_TOL_MULTI,
HOUSEHOLDER_TOL_SINGLE,
};
use crate::error::LambertError;
use crate::geometry::Geometry;
use crate::tof::{compute_y, tof_derivatives_with_y, x_to_tof_with_y};
#[derive(Debug, Clone, Copy)]
pub(crate) struct Root {
pub x: f64,
pub y: f64,
pub iters: u32,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct RootPair {
pub n_revs: BoundedRevs,
pub long_period: Root,
pub short_period: Root,
}
#[derive(Debug, Clone)]
pub(crate) struct Roots {
pub single: Root,
pub multi: ArrayVec<RootPair, MAX_MULTI_REV_PAIRS>,
}
#[allow(clippy::similar_names)] pub(crate) fn find_xy(
geom: &Geometry,
revolutions: crate::RevolutionBudget,
) -> Result<Roots, LambertError> {
let lambda = geom.lambda;
let big_t = geom.big_t;
debug_assert!(lambda.abs() < 1.0);
debug_assert!(big_t > 0.0);
let t00 = lambda.acos() + lambda * (1.0 - lambda * lambda).sqrt();
let t1 = (2.0 / 3.0) * (1.0 - lambda * lambda * lambda);
let x0 = initial_guess_single_rev(big_t, t00, t1, lambda);
let (x, iters) = householder(x0, big_t, lambda, 0)?;
let single = Root {
x,
y: compute_y(x, lambda),
iters,
};
let mut multi: ArrayVec<RootPair, MAX_MULTI_REV_PAIRS> = ArrayVec::new();
for m in revolutions.iter_revs() {
let m_u32 = m.get();
let m_pi = f64::from(m_u32) * PI;
if big_t < m_pi {
break;
}
if big_t < t00 + m_pi {
let (_x_min, t_min) = halley_t_min(lambda, m_u32);
if t_min > big_t {
break;
}
}
let (x0l, x0r) = initial_guess_multi_rev(big_t, m_u32);
let (xl, il) = householder(x0l, big_t, lambda, m_u32)?;
let (xr, ir) = householder(x0r, big_t, lambda, m_u32)?;
if multi
.try_push(RootPair {
n_revs: m,
long_period: Root {
x: xl,
y: compute_y(xl, lambda),
iters: il,
},
short_period: Root {
x: xr,
y: compute_y(xr, lambda),
iters: ir,
},
})
.is_err()
{
break;
}
}
Ok(Roots { single, multi })
}
fn initial_guess_single_rev(big_t: f64, t0: f64, t1: f64, lambda: f64) -> f64 {
if big_t >= t0 {
(t0 / big_t).powf(2.0 / 3.0) - 1.0
} else if big_t <= t1 {
2.5 * t1 * (t1 - big_t) / (big_t * (1.0 - lambda.powi(5))) + 1.0
} else {
(t0 / big_t).powf(2_f64.ln() / (t0 / t1).ln()) - 1.0
}
}
#[allow(clippy::similar_names)] fn initial_guess_multi_rev(big_t: f64, m: u32) -> (f64, f64) {
let m_pi = f64::from(m) * PI;
let kl = ((m_pi + PI) / (8.0 * big_t)).powf(2.0 / 3.0);
let x0l = (kl - 1.0) / (kl + 1.0);
let kr = ((8.0 * big_t) / m_pi).powf(2.0 / 3.0);
let x0r = (kr - 1.0) / (kr + 1.0);
(x0l, x0r)
}
#[allow(clippy::similar_names)] fn householder(mut x: f64, big_t: f64, lambda: f64, m: u32) -> Result<(f64, u32), LambertError> {
let tol = if m == 0 {
HOUSEHOLDER_TOL_SINGLE
} else {
HOUSEHOLDER_TOL_MULTI
};
let mut last_step = f64::INFINITY;
for it in 1..=HOUSEHOLDER_MAX_ITERS {
let y = compute_y(x, lambda);
let tof = x_to_tof_with_y(x, y, lambda, m);
let (dt, ddt, dddt) = tof_derivatives_with_y(x, y, lambda, tof);
let f = tof - big_t;
let denom = dt * (dt * dt - f * ddt) + dddt * f * f / 6.0;
if denom == 0.0 {
return Err(LambertError::SingularDenominator { n_revs: m });
}
let delta = f * (dt * dt - 0.5 * f * ddt) / denom;
x -= delta;
last_step = delta.abs();
if last_step < tol {
return Ok((x, it));
}
}
Err(LambertError::NoConvergence {
iterations: HOUSEHOLDER_MAX_ITERS,
last_step,
n_revs: m,
})
}
#[allow(clippy::similar_names)] fn halley_t_min(lambda: f64, m: u32) -> (f64, f64) {
let mut x = 0.0;
let mut last_step = f64::INFINITY;
for _ in 0..HALLEY_MAX_ITERS {
let y = compute_y(x, lambda);
let tof = x_to_tof_with_y(x, y, lambda, m);
let (dt, ddt, dddt) = tof_derivatives_with_y(x, y, lambda, tof);
let denom = 2.0 * ddt * ddt - dt * dddt;
if denom == 0.0 {
break;
}
let delta = 2.0 * dt * ddt / denom;
x -= delta;
last_step = delta.abs();
if last_step < HALLEY_TOL {
break;
}
}
debug_assert!(
last_step < HALLEY_TOL,
"halley_t_min did not converge within {HALLEY_MAX_ITERS} iters: \
last |Δx| = {last_step:.3e}, λ = {lambda}, M = {m}"
);
let t_min = x_to_tof_with_y(x, compute_y(x, lambda), lambda, m);
(x, t_min)
}