Skip to content

Commit

Permalink
Refine time representations (#44)
Browse files Browse the repository at this point in the history
* Update naming conventions to reflect standard use
* Clarify the distinction between UTC and the continuous timescales
* Improve type safety where specific timescales are required (as opposed to any Time variant)
  • Loading branch information
AngusGMorrison authored Jan 28, 2024
1 parent 739a847 commit 12c7dfe
Show file tree
Hide file tree
Showing 21 changed files with 2,056 additions and 562 deletions.
1 change: 1 addition & 0 deletions .idea/lox-space.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions crates/lox-space/examples/iss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*/

use lox_core::time::utc::UTC;
use lox_space::prelude::*;

fn main() {
let date = Date::new(2016, 5, 30).unwrap();
let time = Time::new(12, 0, 0).unwrap();
let epoch = Epoch::from_date_and_time(TimeScale::TDB, date, time);
let utc = UTC::new(12, 0, 0).unwrap();
let time = Time::from_date_and_utc_timestamp(TimeScale::TDB, date, utc);
let position = DVec3::new(6068279.27, -1692843.94, -2516619.18) * 1e-3;
let velocity = DVec3::new(-660.415582, 5495.938726, -5303.093233) * 1e-3;
let iss_cartesian = Cartesian::new(epoch, Earth, Icrf, position, velocity);
let iss_cartesian = Cartesian::new(time, Earth, Icrf, position, velocity);
let iss = Keplerian::from(iss_cartesian);

println!("ISS Orbit for Julian Day {}", iss.time().days_since_j2000(),);
Expand Down
3 changes: 2 additions & 1 deletion crates/lox-space/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
*/

pub use lox_core::bodies::*;

pub use lox_core::coords::two_body::{Cartesian, Keplerian};
pub use lox_core::coords::DVec3;
pub use lox_core::frames::*;
pub use lox_core::time::continuous::*;
pub use lox_core::time::dates::*;
pub use lox_core::time::epochs::*;
30 changes: 15 additions & 15 deletions crates/lox_core/src/coords/states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ use float_eq::float_eq;
use glam::{DMat3, DVec3};

use crate::math::{mod_two_pi, normalize_two_pi};
use crate::time::epochs::Epoch;
use crate::time::continuous::Time;

pub trait TwoBodyState {
fn time(&self) -> Epoch;
fn time(&self) -> Time;
fn to_cartesian_state(&self, grav_param: f64) -> CartesianState;
fn to_keplerian_state(&self, grav_param: f64) -> KeplerianState;
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct CartesianState {
time: Epoch,
time: Time,
position: DVec3,
velocity: DVec3,
}

impl CartesianState {
pub fn new(time: Epoch, position: DVec3, velocity: DVec3) -> Self {
pub fn new(time: Time, position: DVec3, velocity: DVec3) -> Self {
Self {
time,
position,
Expand All @@ -44,7 +44,7 @@ impl CartesianState {
}

impl TwoBodyState for CartesianState {
fn time(&self) -> Epoch {
fn time(&self) -> Time {
self.time
}

Expand Down Expand Up @@ -120,7 +120,7 @@ impl TwoBodyState for CartesianState {

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct KeplerianState {
time: Epoch,
time: Time,
semi_major: f64,
eccentricity: f64,
inclination: f64,
Expand All @@ -131,7 +131,7 @@ pub struct KeplerianState {

impl KeplerianState {
pub fn new(
time: Epoch,
time: Time,
semi_major: f64,
eccentricity: f64,
inclination: f64,
Expand Down Expand Up @@ -196,7 +196,7 @@ impl KeplerianState {
}

impl TwoBodyState for KeplerianState {
fn time(&self) -> Epoch {
fn time(&self) -> Time {
self.time
}

Expand Down Expand Up @@ -231,15 +231,15 @@ fn is_circular(eccentricity: f64) -> bool {

#[cfg(test)]
mod tests {
use crate::time::epochs::TimeScale;
use crate::time::continuous::TimeScale;
use float_eq::assert_float_eq;
use glam::DVec3;

use super::*;

#[test]
fn test_elliptic() {
let time = Epoch::j2000(TimeScale::TDB);
let time = Time::j2000(TimeScale::TDB);
let grav_param = 3.9860047e14;
let semi_major = 24464560.0;
let eccentricity = 0.7311;
Expand Down Expand Up @@ -295,7 +295,7 @@ mod tests {

#[test]
fn test_circular() {
let time = Epoch::j2000(TimeScale::TDB);
let time = Time::j2000(TimeScale::TDB);
let grav_param = 3.986004418e14;
let semi_major = 6778136.6;
let eccentricity = 0.0;
Expand Down Expand Up @@ -336,7 +336,7 @@ mod tests {

#[test]
fn test_circular_orekit() {
let time = Epoch::j2000(TimeScale::TDB);
let time = Time::j2000(TimeScale::TDB);
let grav_param = 3.9860047e14;
let semi_major = 24464560.0;
let eccentricity = 0.0;
Expand Down Expand Up @@ -368,7 +368,7 @@ mod tests {

#[test]
fn test_hyperbolic_orekit() {
let time = Epoch::j2000(TimeScale::TDB);
let time = Time::j2000(TimeScale::TDB);
let grav_param = 3.9860047e14;
let semi_major = -24464560.0;
let eccentricity = 1.7311;
Expand Down Expand Up @@ -400,7 +400,7 @@ mod tests {

#[test]
fn test_equatorial() {
let time = Epoch::j2000(TimeScale::TDB);
let time = Time::j2000(TimeScale::TDB);
let grav_param = 3.9860047e14;
let semi_major = 24464560.0;
let eccentricity = 0.7311;
Expand Down Expand Up @@ -432,7 +432,7 @@ mod tests {

#[test]
fn test_circular_equatorial() {
let time = Epoch::j2000(TimeScale::TDB);
let time = Time::j2000(TimeScale::TDB);
let grav_param = 3.9860047e14;
let semi_major = 24464560.0;
let eccentricity = 0.0;
Expand Down
34 changes: 17 additions & 17 deletions crates/lox_core/src/coords/two_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::bodies::PointMass;
use crate::coords::states::{CartesianState, KeplerianState, TwoBodyState};
use crate::coords::CoordinateSystem;
use crate::frames::{InertialFrame, ReferenceFrame};
use crate::time::epochs::Epoch;
use crate::time::continuous::Time;

pub trait TwoBody<T, S>
where
Expand Down Expand Up @@ -40,7 +40,7 @@ where
T: PointMass + Copy,
S: ReferenceFrame + Copy,
{
pub fn new(time: Epoch, origin: T, frame: S, position: DVec3, velocity: DVec3) -> Self {
pub fn new(time: Time, origin: T, frame: S, position: DVec3, velocity: DVec3) -> Self {
let state = CartesianState::new(time, position, velocity);
Self {
state,
Expand All @@ -49,7 +49,7 @@ where
}
}

pub fn time(&self) -> Epoch {
pub fn time(&self) -> Time {
self.state.time()
}

Expand Down Expand Up @@ -127,7 +127,7 @@ where
{
#[allow(clippy::too_many_arguments)]
pub fn new(
time: Epoch,
time: Time,
origin: T,
frame: S,
semi_major: f64,
Expand All @@ -153,7 +153,7 @@ where
}
}

pub fn time(&self) -> Epoch {
pub fn time(&self) -> Time {
self.state.time()
}

Expand Down Expand Up @@ -233,18 +233,18 @@ where
mod tests {
use float_eq::assert_float_eq;

use super::*;
use crate::bodies::Earth;
use crate::frames::Icrf;
use crate::time::dates::{Date, Time};
use crate::time::epochs::TimeScale;

use super::*;
use crate::time::continuous::{Time, TimeScale};
use crate::time::dates::Date;
use crate::time::utc::UTC;

#[test]
fn test_cartesian() {
let date = Date::new(2023, 3, 25).expect("Date should be valid");
let time = Time::new(21, 8, 0).expect("Time should be valid");
let epoch = Epoch::from_date_and_time(TimeScale::TDB, date, time);
let utc = UTC::new(21, 8, 0).expect("Time should be valid");
let time = Time::from_date_and_utc_timestamp(TimeScale::TDB, date, utc);
let pos = DVec3::new(
-0.107622532467967e7,
-0.676589636432773e7,
Expand All @@ -256,12 +256,12 @@ mod tests {
-0.118801577532701e4,
) * 1e-3;

let cartesian = Cartesian::new(epoch, Earth, Icrf, pos, vel);
let cartesian = Cartesian::new(time, Earth, Icrf, pos, vel);
assert_eq!(cartesian.to_cartesian(), cartesian);

let cartesian1 = cartesian.to_keplerian().to_cartesian();

assert_eq!(cartesian1.time(), epoch);
assert_eq!(cartesian1.time(), time);
assert_eq!(cartesian1.origin(), Earth);
assert_eq!(cartesian1.reference_frame(), Icrf);

Expand All @@ -276,8 +276,8 @@ mod tests {
#[test]
fn test_keplerian() {
let date = Date::new(2023, 3, 25).expect("Date should be valid");
let time = Time::new(21, 8, 0).expect("Time should be valid");
let epoch = Epoch::from_date_and_time(TimeScale::TDB, date, time);
let utc = UTC::new(21, 8, 0).expect("Time should be valid");
let time = Time::from_date_and_utc_timestamp(TimeScale::TDB, date, utc);
let semi_major = 24464560.0e-3;
let eccentricity = 0.7311;
let inclination = 0.122138;
Expand All @@ -286,7 +286,7 @@ mod tests {
let true_anomaly = 0.44369564302687126;

let keplerian = Keplerian::new(
epoch,
time,
Earth,
Icrf,
semi_major,
Expand All @@ -300,7 +300,7 @@ mod tests {

let keplerian1 = keplerian.to_cartesian().to_keplerian();

assert_eq!(keplerian1.time(), epoch);
assert_eq!(keplerian1.time(), time);
assert_eq!(keplerian1.origin(), Earth);
assert_eq!(keplerian1.reference_frame(), Icrf);

Expand Down
27 changes: 13 additions & 14 deletions crates/lox_core/src/earth/nutation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::earth::nutation::iau2000::nutation_iau2000a;
use crate::earth::nutation::iau2000::nutation_iau2000b;
use crate::earth::nutation::iau2006::nutation_iau2006a;
use crate::math::RADIANS_IN_ARCSECOND;
use crate::time::epochs::Epoch;
use crate::time::continuous::Time;
use crate::time::intervals::tdb_julian_centuries_since_j2000;
use crate::types::Radians;

Expand Down Expand Up @@ -63,9 +63,9 @@ impl Add<&Self> for Nutation {
}
}

/// Calculate nutation coefficients at `epoch` using the given [Model].
pub fn nutation(model: Model, epoch: Epoch) -> Nutation {
let t = tdb_julian_centuries_since_j2000(epoch);
/// Calculate nutation coefficients at `time` using the given [Model].
pub fn nutation(model: Model, time: Time) -> Nutation {
let t = tdb_julian_centuries_since_j2000(time);
match model {
Model::IAU1980 => nutation_iau1980(t),
Model::IAU2000A => nutation_iau2000a(t),
Expand Down Expand Up @@ -98,57 +98,56 @@ fn point1_microarcsec_to_rad(p1_uas: Point1Microarcsec) -> Radians {

#[cfg(test)]
mod tests {
use crate::time::continuous::TimeScale;
use float_eq::assert_float_eq;

use crate::time::epochs::{Epoch, TimeScale};

use super::*;

const TOLERANCE: f64 = 1e-12;

#[test]
fn test_nutation_iau1980() {
let epoch = Epoch::j2000(TimeScale::TT);
let time = Time::j2000(TimeScale::TT);
let expected = Nutation {
longitude: -0.00006750247617532478,
obliquity: -0.00002799221238377013,
};
let actual = nutation(Model::IAU1980, epoch);
let actual = nutation(Model::IAU1980, time);
assert_float_eq!(expected.longitude, actual.longitude, rel <= TOLERANCE);
assert_float_eq!(expected.obliquity, actual.obliquity, rel <= TOLERANCE);
}
#[test]
fn test_nutation_iau2000a() {
let epoch = Epoch::j2000(TimeScale::TT);
let time = Time::j2000(TimeScale::TT);
let expected = Nutation {
longitude: -0.00006754422426417299,
obliquity: -0.00002797083119237414,
};
let actual = nutation(Model::IAU2000A, epoch);
let actual = nutation(Model::IAU2000A, time);
assert_float_eq!(expected.longitude, actual.longitude, rel <= TOLERANCE);
assert_float_eq!(expected.obliquity, actual.obliquity, rel <= TOLERANCE);
}

#[test]
fn test_nutation_iau2000b() {
let epoch = Epoch::j2000(TimeScale::TT);
let time = Time::j2000(TimeScale::TT);
let expected = Nutation {
longitude: -0.00006754261253992235,
obliquity: -0.00002797092331098565,
};
let actual = nutation(Model::IAU2000B, epoch);
let actual = nutation(Model::IAU2000B, time);
assert_float_eq!(expected.longitude, actual.longitude, rel <= TOLERANCE);
assert_float_eq!(expected.obliquity, actual.obliquity, rel <= TOLERANCE);
}

#[test]
fn test_nutation_iau2006a() {
let epoch = Epoch::j2000(TimeScale::TT);
let time = Time::j2000(TimeScale::TT);
let expected = Nutation {
longitude: -0.00006754425598969513,
obliquity: -0.00002797083119237414,
};
let actual = nutation(Model::IAU2006A, epoch);
let actual = nutation(Model::IAU2006A, time);
assert_float_eq!(expected.longitude, actual.longitude, rel <= TOLERANCE);
assert_float_eq!(expected.obliquity, actual.obliquity, rel <= TOLERANCE);
}
Expand Down
10 changes: 6 additions & 4 deletions crates/lox_core/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@

use thiserror::Error;

#[derive(Error, Debug)]
#[derive(Error, Debug, PartialEq)]
pub enum LoxError {
#[error("invalid date `{0}-{1}-{2}`")]
InvalidDate(i64, i64, i64),
#[error("invalid time `{0}:{1}:{2}`")]
InvalidTime(i64, i64, i64),
#[error("invalid time `{0}:{1}:{2}`")]
InvalidSeconds(i64, i64, f64),
InvalidTime(u8, u8, u8),
#[error("seconds must be in the range [0.0, 60.0], but was `{0}`")]
InvalidSeconds(f64),
#[error("PerMille value must be in the range [0, 999], but was `{0}`")]
InvalidPerMille(u16),
#[error("day of year cannot be 366 for a non-leap year")]
NonLeapYear,
#[error("unknown body `{0}`")]
Expand Down
Loading

0 comments on commit 12c7dfe

Please sign in to comment.