Skip to main content

deep_time/dt/
trajectory.rs

1use crate::{C_SQUARED, Drift, Dt, DtErr, DtErrKind, Real, Spacetime, Velocity, an_err};
2
3impl Dt {
4    /// Computes the relativistic clock drift (proper time minus coordinate time)
5    /// over an interval.
6    ///
7    /// This returns how much a physical clock has gained or lost time compared
8    /// with coordinate time between `start` and `end`.
9    ///
10    /// - A positive result means the onboard clock ran fast.
11    /// - A negative result means the onboard clock ran slow.
12    ///
13    /// # Parameters
14    ///
15    /// - `start`: Starting coordinate time of the interval.
16    /// - `end`: Ending coordinate time of the interval.
17    /// - `states`: Iterator of physical states. Coordinate times must be
18    ///   monotonically non-decreasing. **It is the caller’s responsibility**
19    ///   to ensure the provided states cover the time range from `start` to `end`.
20    ///   The function integrates proper time over whatever states are supplied
21    ///   and subtracts the requested coordinate interval (`end - start`).
22    ///   Exact matching of the first and last state times to `start` and `end`
23    ///   is **not** validated.
24    /// - `characteristic_length_scale`: See [`proper_time_from_states`].
25    ///
26    /// # Returns
27    ///
28    /// `Ok(drift)` — the accumulated drift (Δτ − Δt) as a [`Dt`].
29    ///
30    /// `Err(DtErr)` — if the states are not monotonically increasing in time.
31    pub fn proper_time_from_states<I>(
32        samples: I,
33        characteristic_length_scale: Real,
34    ) -> Result<Self, DtErr>
35    where
36        I: IntoIterator<Item = (Self, Velocity, Real)>,
37    {
38        let path_iter = samples.into_iter().map(|(t, vel, phi)| {
39            let phi_over_c2 = phi / C_SQUARED;
40            let ls = Spacetime::from_potential_velocity_and_scale(
41                phi_over_c2,
42                vel,
43                characteristic_length_scale,
44            );
45            (t, ls)
46        });
47
48        Self::proper_time_from_path(path_iter)
49    }
50
51    /// Computes the relativistic clock drift (proper time minus coordinate time)
52    /// over an interval.
53    ///
54    /// This returns how much a physical clock has gained or lost time compared
55    /// with coordinate time between `start` and `end`.
56    ///
57    /// - A positive result means the onboard clock ran fast.
58    /// - A negative result means the onboard clock ran slow.
59    ///
60    /// # Parameters
61    ///
62    /// - `start`: Starting coordinate time.
63    /// - `end`: Ending coordinate time.
64    /// - `states`: Iterator of physical states covering the interval.
65    ///   Coordinate times must be monotonically non-decreasing.
66    ///   It is the caller’s responsibility to ensure the states span
67    ///   the requested interval (exact first/last time matching is not checked).
68    /// - `characteristic_length_scale`: See [`proper_time_from_states`].
69    ///
70    /// # Returns
71    ///
72    /// `Ok(drift)` — the accumulated drift (Δτ − Δt) as a [`Dt`].
73    ///
74    /// `Err(DtErr)` — if the states are not monotonically increasing in time.
75    pub fn proper_time_drift_from_states<I>(
76        start: Dt,
77        end: Dt,
78        states: I,
79        characteristic_length_scale: Real,
80    ) -> Result<Dt, DtErr>
81    where
82        I: IntoIterator<Item = (Self, Velocity, Real)>,
83    {
84        if start.eq(&end) {
85            return Ok(Dt::ZERO);
86        }
87        let dtau = Self::proper_time_from_states(states, characteristic_length_scale)?;
88        Ok(dtau.sub(end.to_diff_raw(start)))
89    }
90
91    /// Computes accumulated proper time along an arbitrary trajectory.
92    ///
93    /// This is the core integration function of the library. It walks the
94    /// supplied path segment by segment and applies the trapezoidal rule
95    /// to the instantaneous proper-time rate at each step.
96    ///
97    /// This approach is commonly used when integrating clock rates along
98    /// sampled trajectories in astrodynamics and high-precision timing work.
99    ///
100    /// The function enforces that coordinate times are monotonically
101    /// non-decreasing. It performs a single pass with no heap allocation.
102    ///
103    /// # Parameters
104    ///
105    /// - `path`: An iterator of `(coordinate_time, Spacetime)` pairs.
106    ///   Coordinate times must be monotonically non-decreasing.
107    ///
108    /// # Returns
109    ///
110    /// `Ok(total_proper_time)` — the accumulated proper time as a [`Dt`].
111    ///
112    /// `Err(DtErr)` — if the path is empty or contains any decrease in
113    /// coordinate time.
114    pub fn proper_time_from_path<I>(path: I) -> Result<Self, DtErr>
115    where
116        I: IntoIterator<Item = (Self, Spacetime)>,
117    {
118        let mut iter = path.into_iter();
119
120        let Some((mut prev_t, mut prev_ls)) = iter.next() else {
121            return Ok(Self::ZERO);
122        };
123
124        let mut accumulated = Self::ZERO;
125
126        for (t, ls) in iter {
127            if t.lt(&prev_t) {
128                return Err(an_err!(
129                    DtErrKind::InvalidInput,
130                    "proper_time_from_path requires monotonically non-decreasing coordinate times"
131                ));
132            }
133
134            let dt = t.to_diff_raw(prev_t);
135            if !dt.is_zero() {
136                let sign = if dt.sec < 0 { f!(-1.0) } else { f!(1.0) };
137                let dt_pos = if sign < f!(0.0) { dt.neg() } else { dt };
138                let dt_sec = dt_pos.to_sec_f();
139
140                let rate0 = Self::rate_from_local(&prev_ls);
141                let rate1 = Self::rate_from_local(&ls);
142
143                let integral = f!(0.5) * (rate0 + rate1 - f!(2.0)) * dt_sec;
144                let dtau_segment = Dt::from_sec_f(sign * (dt_sec + integral));
145
146                accumulated = accumulated.add(dtau_segment);
147            }
148
149            prev_t = t;
150            prev_ls = ls;
151        }
152
153        Ok(accumulated)
154    }
155
156    /// Computes proper time advance over an interval when the rate is constant.
157    ///
158    /// Use this for segments where conditions do not change, such as
159    /// a ground station, a circular orbit, or a deep-space cruise phase
160    /// with constant velocity and gravitational potential.
161    ///
162    /// This is mathematically equivalent to integrating a constant rate
163    /// but is more efficient and expresses intent clearly.
164    ///
165    /// # Parameters
166    ///
167    /// - `end`: Ending coordinate time.
168    /// - `dtau_dt`: Constant proper-time rate (dimensionless, usually between 0 and 1).
169    ///
170    /// # Returns
171    ///
172    /// The accumulated proper time advance as a [`Dt`].
173    #[inline]
174    pub const fn proper_time_between_constant_rate(self, end: Dt, dtau_dt: Real) -> Dt {
175        let dt_sec = end.to_diff_raw(self).to_sec_f();
176        Dt::from_sec_f(dtau_dt * dt_sec)
177    }
178
179    /// Returns the instantaneous proper-time rate (dτ/dt) from a local
180    /// spacetime state.
181    ///
182    /// This is a private helper used by the integration routines.
183    #[inline]
184    const fn rate_from_local(spacetime: &Spacetime) -> Real {
185        let drift = Drift::from_spacetime(spacetime);
186        f!(1.0) + drift.rate.to_sec_f()
187    }
188}