use crate::coordinates::cartesian;
use crate::coordinates::centers::Geocentric;
use crate::coordinates::frames::GCRS;
use crate::qtty::force::Newton;
use crate::qtty::unit::Kilometer;
use crate::qtty::{
AreaToMass, DragCoefficient, Kilograms, KmPerSecond, KmPerSecondSquared, SquareMeters,
SrpCoefficient,
};
use crate::time::TT;
use principia::DynamicsState;
pub type Position<F = GCRS, U = Kilometer> = cartesian::Position<Geocentric, F, U>;
pub type Velocity<F = GCRS, U = KmPerSecond> = cartesian::Velocity<F, U>;
pub type Acceleration<F = GCRS, U = KmPerSecondSquared> = affn::cartesian::Acceleration<F, U>;
pub type Force<F = GCRS, U = Newton> = affn::cartesian::Force<F, U>;
pub type VelocityUnit = KmPerSecond;
pub type AccelerationUnit = KmPerSecondSquared;
pub type OrbitState<C = Geocentric, F = GCRS> = DynamicsState<TT, C, F>;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpacecraftProperties {
pub mass: Kilograms,
pub drag_area: SquareMeters,
pub cd: DragCoefficient,
pub srp_area: SquareMeters,
pub cr: SrpCoefficient,
pub area_to_mass_drag: AreaToMass,
pub area_to_mass_srp: AreaToMass,
}
impl SpacecraftProperties {
#[inline]
pub fn new(
mass: Kilograms,
drag_area: SquareMeters,
cd: DragCoefficient,
srp_area: SquareMeters,
cr: SrpCoefficient,
) -> Self {
Self::try_new(mass, drag_area, cd, srp_area, cr)
.expect("SpacecraftProperties::new: invalid physical parameters")
}
pub fn try_new(
mass: Kilograms,
drag_area: SquareMeters,
cd: DragCoefficient,
srp_area: SquareMeters,
cr: SrpCoefficient,
) -> Result<Self, String> {
let m = mass.value();
if !m.is_finite() || m <= 0.0 {
return Err(format!("mass must be finite and > 0 (got {m})"));
}
let da = drag_area.value();
if !da.is_finite() || da < 0.0 {
return Err(format!("drag_area must be finite and ≥ 0 (got {da})"));
}
let sa = srp_area.value();
if !sa.is_finite() || sa < 0.0 {
return Err(format!("srp_area must be finite and ≥ 0 (got {sa})"));
}
let cd_v = cd.value();
if !cd_v.is_finite() || cd_v < 0.0 {
return Err(format!("cd must be finite and ≥ 0 (got {cd_v})"));
}
let cr_v = cr.value();
if !cr_v.is_finite() || cr_v < 0.0 {
return Err(format!("cr must be finite and ≥ 0 (got {cr_v})"));
}
let area_to_mass_drag = AreaToMass::new(da / m);
let area_to_mass_srp = AreaToMass::new(sa / m);
Ok(Self {
mass,
drag_area,
cd,
srp_area,
cr,
area_to_mass_drag,
area_to_mass_srp,
})
}
#[inline]
pub fn drag_area_to_mass(&self) -> AreaToMass {
self.area_to_mass_drag
}
#[inline]
pub fn srp_area_to_mass(&self) -> AreaToMass {
self.area_to_mass_srp
}
pub fn demo_leo() -> Self {
Self::new(
Kilograms::new(500.0),
SquareMeters::new(2.0),
DragCoefficient::new(2.2),
SquareMeters::new(2.0),
SrpCoefficient::new(1.3),
)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpacecraftState {
pub orbit: OrbitState,
pub properties: SpacecraftProperties,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::coordinates::frames::GCRS;
use crate::qtty::{AreaToMass, Kilograms, SquareMeters};
use crate::time::{JulianDate, JD};
#[test]
fn orbit_state_alias_carries_tt_epoch() {
let state = OrbitState::new(
JulianDate::new(2_451_545.0).to_j2000s(),
Position::<GCRS>::new(7000.0, 0.0, 0.0),
Velocity::<GCRS>::new(0.0, 7.5, 0.0),
);
assert!((state.epoch.to::<JD>().raw().value() - 2_451_545.0).abs() < 1e-9);
}
#[test]
fn spacecraft_properties_precompute_area_to_mass() {
let props = SpacecraftProperties::new(
Kilograms::new(500.0),
SquareMeters::new(2.0),
DragCoefficient::new(2.2),
SquareMeters::new(3.0),
SrpCoefficient::new(1.3),
);
assert_eq!(props.drag_area_to_mass(), AreaToMass::new(0.004));
assert_eq!(props.srp_area_to_mass(), AreaToMass::new(0.006));
}
#[test]
fn try_new_rejects_zero_mass() {
assert!(SpacecraftProperties::try_new(
Kilograms::new(0.0),
SquareMeters::new(2.0),
DragCoefficient::new(2.2),
SquareMeters::new(2.0),
SrpCoefficient::new(1.3),
)
.is_err());
}
#[test]
fn try_new_rejects_negative_mass() {
assert!(SpacecraftProperties::try_new(
Kilograms::new(-1.0),
SquareMeters::new(2.0),
DragCoefficient::new(2.2),
SquareMeters::new(2.0),
SrpCoefficient::new(1.3),
)
.is_err());
}
#[test]
fn try_new_rejects_nan_mass() {
assert!(SpacecraftProperties::try_new(
Kilograms::new(f64::NAN),
SquareMeters::new(2.0),
DragCoefficient::new(2.2),
SquareMeters::new(2.0),
SrpCoefficient::new(1.3),
)
.is_err());
}
#[test]
fn try_new_rejects_negative_drag_area() {
assert!(SpacecraftProperties::try_new(
Kilograms::new(500.0),
SquareMeters::new(-1.0),
DragCoefficient::new(2.2),
SquareMeters::new(2.0),
SrpCoefficient::new(1.3),
)
.is_err());
}
#[test]
fn try_new_accepts_zero_drag_area() {
assert!(SpacecraftProperties::try_new(
Kilograms::new(500.0),
SquareMeters::new(0.0),
DragCoefficient::new(2.2),
SquareMeters::new(2.0),
SrpCoefficient::new(1.3),
)
.is_ok());
}
}