use std::f64::consts::PI;
use tpt_sci_astro::{EARTH_J2, EARTH_MU, EARTH_RADIUS_EQ, OrbitalElements};
const TOL: f64 = 1e-6;
fn main() {
let r1 = 7000.0; let r2 = 42164.0; let a_t = 0.5 * (r1 + r2); let e_t = (r2 - r1) / (r2 + r1);
println!("=== Hohmann transfer (LEO r1={r1} km -> GEO r2={r2} km) ===\n");
println!("Transfer ellipse: a = {a_t:.1} km, e = {e_t:.4}",);
let transfer =
OrbitalElements::new(a_t, e_t, 0.0, 0.0, 0.0, 0.0, EARTH_MU).expect("valid transfer orbit");
let (r_peri, v_peri_vec) = transfer.state_vector();
assert!(
(r_peri.norm() - r1).abs() / r1 < TOL,
"transfer periapsis must equal r1"
);
let half = transfer.propagate(transfer.period() / 2.0);
let (r_apo, v_apo_vec) = half.state_vector();
assert!(
(r_apo.norm() - r2).abs() / r2 < TOL,
"transfer apoapsis after half period must equal r2"
);
let (r0, v0) = transfer.state_vector();
let recovered =
OrbitalElements::from_state(&r0, &v0, EARTH_MU).expect("recover transfer from state");
assert!((recovered.a - a_t).abs() / a_t < TOL, "a round-trips");
assert!((recovered.e - e_t).abs() < TOL, "e round-trips");
println!("Geometry (state-vector + propagation):");
println!(" periapsis r = {:>11.3} km (expect {r1})", r_peri.norm());
println!(" apoapsis r = {:>11.3} km (expect {r2})", r_apo.norm());
println!(
" transfer period T = {:>11.1} s (expect {:.1})",
transfer.period(),
2.0 * PI * (a_t.powi(3) / EARTH_MU).sqrt()
);
let v_circ1 = (EARTH_MU / r1).sqrt();
let v_circ2 = (EARTH_MU / r2).sqrt();
let v_peri = (EARTH_MU * (2.0 / r1 - 1.0 / a_t)).sqrt();
let v_apo = (EARTH_MU * (2.0 / r2 - 1.0 / a_t)).sqrt();
let dv1 = v_peri - v_circ1; let dv2 = v_circ2 - v_apo; let dv_total = dv1 + dv2;
assert!(
(v_peri_vec.norm() - v_peri).abs() < 1e-3,
"v_peri from state vector matches vis-viva"
);
assert!(
(v_apo_vec.norm() - v_apo).abs() < 1e-3,
"v_apo from state vector matches vis-viva"
);
println!("\nImpulse budget (vis-viva, cross-checked vs state speeds):");
println!(" v_circ(LEO) = {:>10.3} km/s", v_circ1);
println!(
" v_transfer(peri)= {:>10.3} km/s -> Δv1 = {:>8.3} km/s",
v_peri, dv1
);
println!(" v_circ(GEO) = {:>10.3} km/s", v_circ2);
println!(
" v_transfer(apo) = {:>10.3} km/s -> Δv2 = {:>8.3} km/s",
v_apo, dv2
);
println!(" total Δv = {:>10.3} km/s", dv_total);
assert!(
dv_total > 3.5 && dv_total < 4.5,
"total Δv in expected band"
);
let inc = 50.0_f64.to_radians();
let low = OrbitalElements::new(r1, 0.0, inc, 0.0, 0.0, 0.0, EARTH_MU).expect("LEO circular");
let high = OrbitalElements::new(r2, 0.0, inc, 0.0, 0.0, 0.0, EARTH_MU).expect("GEO circular");
let (raan_dot_low, _) = low.j2_secular_rates(EARTH_J2, EARTH_RADIUS_EQ);
let (raan_dot_high, _) = high.j2_secular_rates(EARTH_J2, EARTH_RADIUS_EQ);
assert!(
raan_dot_low.abs() > raan_dot_high.abs(),
"lower orbit must regress faster"
);
let deg_day = |rate: f64| rate * 86_400.0 * 180.0 / PI;
println!("\nJ2 nodal drift of endpoints (i = 50 deg, via j2_secular_rates):");
println!(
" LEO (r={r1}) RAAN drift = {:>9.4} deg/day",
deg_day(raan_dot_low)
);
println!(
" GEO (r={r2}) RAAN drift = {:>9.4} deg/day",
deg_day(raan_dot_high)
);
println!(" (lower orbit regresses faster — a real transfer must account for it)");
println!("\nAll transfer checks passed.");
}