use crate::PhysicsError;
use deep_causality_algebra::RealField;
use deep_causality_num::FromPrimitive;
#[derive(Clone, Copy, Debug)]
pub struct KsPropagator<R> {
gravitational_parameter: R,
omega: R,
a: [R; 4],
b: [R; 4],
aa: R,
bb: R,
ab: R,
semi_major_axis: R,
}
impl<R> KsPropagator<R>
where
R: RealField + FromPrimitive,
{
pub fn from_state(position: [R; 3], velocity: [R; 3], gm: R) -> Result<Self, PhysicsError> {
if gm <= R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"Gravitational parameter GM must be positive".into(),
));
}
let two = Self::lit(2.0)?;
let four = Self::lit(4.0)?;
let radius =
(position[0] * position[0] + position[1] * position[1] + position[2] * position[2])
.sqrt();
if radius <= R::zero() {
return Err(PhysicsError::Singularity("Radius must be positive".into()));
}
let v2 = velocity[0] * velocity[0] + velocity[1] * velocity[1] + velocity[2] * velocity[2];
let energy = v2 / two - gm / radius;
if energy >= R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"State is not a bound orbit (energy >= 0); only ellipses are supported".into(),
));
}
let omega = (-energy / two).sqrt();
let semi_major_axis = gm / (four * omega * omega);
let a = Self::ks_lift(position, radius);
let uprime = Self::l_transpose_times_v(&a, velocity);
let half = R::one() / two;
let b = [
uprime[0] * half / omega,
uprime[1] * half / omega,
uprime[2] * half / omega,
uprime[3] * half / omega,
];
let aa = Self::dot4(&a, &a);
let bb = Self::dot4(&b, &b);
let ab = Self::dot4(&a, &b);
Ok(Self {
gravitational_parameter: gm,
omega,
a,
b,
aa,
bb,
ab,
semi_major_axis,
})
}
pub fn propagate(&self, dt: R) -> Result<([R; 3], [R; 3]), PhysicsError> {
let s = self.solve_fictitious_time(dt)?;
let phi = self.omega * s;
let (cp, sp) = (phi.cos(), phi.sin());
let mut u = [R::zero(); 4];
let mut up = [R::zero(); 4];
for i in 0..4 {
u[i] = self.a[i] * cp + self.b[i] * sp;
up[i] = self.omega * (-self.a[i] * sp + self.b[i] * cp);
}
let radius = Self::dot4(&u, &u);
let r3 = Self::l_times(&u, &u);
let drds = Self::l_times(&u, &up);
let two = Self::lit(2.0)?;
let velocity = [
two * drds[0] / radius,
two * drds[1] / radius,
two * drds[2] / radius,
];
Ok((r3, velocity))
}
pub fn semi_major_axis(&self) -> R {
self.semi_major_axis
}
pub fn gravitational_parameter(&self) -> R {
self.gravitational_parameter
}
pub fn mean_motion(&self) -> R {
(self.gravitational_parameter
/ (self.semi_major_axis * self.semi_major_axis * self.semi_major_axis))
.sqrt()
}
pub fn period(&self) -> Result<R, PhysicsError> {
let two = Self::lit(2.0)?;
Ok(two * R::pi() / self.mean_motion())
}
fn t_and_radius(&self, s: R) -> Result<(R, R), PhysicsError> {
let two = Self::lit(2.0)?;
let four = Self::lit(4.0)?;
let phi = self.omega * s;
let (cp, sp) = (phi.cos(), phi.sin());
let sin2 = two * sp * cp; let cos2 = cp * cp - sp * sp; let c_lin = (self.aa + self.bb) / two;
let t = c_lin * s
+ (self.aa - self.bb) * sin2 / (four * self.omega)
+ self.ab * (R::one() - cos2) / (two * self.omega);
let radius = self.aa * cp * cp + two * self.ab * cp * sp + self.bb * sp * sp;
Ok((t, radius))
}
fn solve_fictitious_time(&self, dt: R) -> Result<R, PhysicsError> {
let two = Self::lit(2.0)?;
let c_lin = (self.aa + self.bb) / two; let mut s = dt / c_lin;
let tol = Self::lit(1e-15)?;
for _ in 0..100 {
let (t, radius) = self.t_and_radius(s)?;
let d = (t - dt) / radius;
s -= d;
if d.abs() < tol * (s.abs() + R::one()) {
break;
}
}
Ok(s)
}
fn ks_lift(r: [R; 3], radius: R) -> [R; 4] {
let two = Self::lit(2.0).expect("2.0 lifts into every real field");
let half = R::one() / two;
if r[0] >= R::zero() {
let u1 = (half * (radius + r[0])).sqrt();
[u1, r[1] / (two * u1), r[2] / (two * u1), R::zero()]
} else {
let u2 = (half * (radius - r[0])).sqrt();
[r[1] / (two * u2), u2, R::zero(), r[2] / (two * u2)]
}
}
fn l_times(u: &[R; 4], w: &[R; 4]) -> [R; 3] {
[
u[0] * w[0] - u[1] * w[1] - u[2] * w[2] + u[3] * w[3],
u[1] * w[0] + u[0] * w[1] - u[3] * w[2] - u[2] * w[3],
u[2] * w[0] + u[3] * w[1] + u[0] * w[2] + u[1] * w[3],
]
}
fn l_transpose_times_v(u: &[R; 4], v: [R; 3]) -> [R; 4] {
[
u[0] * v[0] + u[1] * v[1] + u[2] * v[2],
-u[1] * v[0] + u[0] * v[1] + u[3] * v[2],
-u[2] * v[0] - u[3] * v[1] + u[0] * v[2],
u[3] * v[0] - u[2] * v[1] + u[1] * v[2],
]
}
fn dot4(x: &[R; 4], y: &[R; 4]) -> R {
x[0] * y[0] + x[1] * y[1] + x[2] * y[2] + x[3] * y[3]
}
fn lit(x: f64) -> Result<R, PhysicsError> {
R::from_f64(x)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64 failed".into()))
}
}
pub fn ks_strang_step<R, A>(
position: [R; 3],
velocity: [R; 3],
gm: R,
dt: R,
accel: A,
) -> Result<([R; 3], [R; 3]), PhysicsError>
where
R: RealField + FromPrimitive,
A: Fn([R; 3], [R; 3]) -> [R; 3],
{
let two = R::from_f64(2.0)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64 failed".into()))?;
let half_dt = dt / two;
let a0 = accel(position, velocity);
let v_half = [
velocity[0] + a0[0] * half_dt,
velocity[1] + a0[1] * half_dt,
velocity[2] + a0[2] * half_dt,
];
let (r1, v1) = KsPropagator::from_state(position, v_half, gm)?.propagate(dt)?;
let a1 = accel(r1, v1);
let v_out = [
v1[0] + a1[0] * half_dt,
v1[1] + a1[1] * half_dt,
v1[2] + a1[2] * half_dt,
];
Ok((r1, v_out))
}