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 accumulated proper time along a trajectory given a sequence
5    /// of physical states.
6    ///
7    /// This function accepts samples expressed in terms of directly observable
8    /// quantities — coordinate time, velocity, and gravitational potential —
9    /// and integrates the proper time (Δτ) along the path. It is a convenience
10    /// wrapper around the core [`proper_time_from_path`] routine.
11    ///
12    /// The integration is performed using the trapezoidal rule applied to the
13    /// instantaneous proper-time rate between consecutive samples. This approach
14    /// is standard for high-precision clock modeling in astrodynamics and
15    /// relativistic timing applications.
16    ///
17    /// A single sample, or multiple samples at identical times, produces a result
18    /// of zero (no time has elapsed). An empty iterator also returns zero.
19    ///
20    /// ## Parameters
21    ///
22    /// - `samples`: Iterator yielding `(coordinate_time, velocity, gravitational_potential)`
23    ///   triples. The coordinate times must be monotonically non-decreasing.
24    ///   **It is the caller’s responsibility** to supply samples that cover the
25    ///   desired time interval. The function does not validate that the first or
26    ///   last sample exactly matches any particular start or end time.
27    /// - `characteristic_length_scale`: Controls whether the weak-field or
28    ///   strong-field formulation is used when constructing the local spacetime
29    ///   state.
30    ///
31    ///   Pass `0.0` (the normal choice) for all conventional weak-field work
32    ///   (Earth orbit, GNSS, solar-system navigation, most spacecraft). This
33    ///   produces exactly the classic relativistic clock rate used by JPL, ESA,
34    ///   and GNSS systems, with the Kretschmann scalar set to zero.
35    ///
36    ///   Supply a positive value (in meters) only when you need the library’s
37    ///   intrinsic Planck-scale saturation term. The value should represent the
38    ///   characteristic length scale over which the gravitational field varies
39    ///   significantly at the observer’s location. This is intended for strong-field
40    ///   regimes such as the vicinity of neutron stars or black-hole event horizons.
41    ///
42    /// ## Returns
43    ///
44    /// `Ok(total_proper_time)` — the total proper time (Δτ) that has accumulated
45    /// for an observer following the trajectory defined by the supplied samples,
46    /// returned as a [`Dt`].
47    ///
48    /// This value represents the actual time that would have elapsed on a physical
49    /// clock moving along the path, including all relativistic effects (velocity
50    /// and gravitational time dilation, plus the Planck-scale saturation term when
51    /// active). It is **not** a drift or difference relative to coordinate time.
52    /// If you need the difference between proper time and coordinate time
53    /// (Δτ − Δt), use [`proper_time_drift_from_states`] instead.
54    ///
55    /// `Err(DtErr)` — if the coordinate times are not monotonically non-decreasing.
56    pub fn proper_time_from_states<I>(
57        samples: I,
58        characteristic_length_scale: Real,
59    ) -> Result<Self, DtErr>
60    where
61        I: IntoIterator<Item = (Self, Velocity, Real)>,
62    {
63        let path_iter = samples.into_iter().map(|(t, vel, phi)| {
64            let phi_over_c2 = phi / C_SQUARED;
65            let ls = Spacetime::from_potential_velocity_and_scale(
66                phi_over_c2,
67                vel,
68                characteristic_length_scale,
69            );
70            (t, ls)
71        });
72
73        Self::proper_time_from_path(path_iter)
74    }
75
76    /// Computes the relativistic clock drift (proper time minus coordinate time)
77    /// over a specific interval.
78    ///
79    /// This returns how much a physical clock has gained or lost time compared
80    /// with coordinate time between `start` and `end`.
81    ///
82    /// - A positive result means the onboard clock ran **fast** (it accumulated
83    ///   more proper time than the coordinate interval).
84    /// - A negative result means the onboard clock ran **slow** (it accumulated
85    ///   less proper time than the coordinate interval).
86    ///
87    /// This is the higher-level function most callers should use when they need
88    /// the net drift over a well-defined time interval. It internally calls
89    /// [`proper_time_from_states`] to integrate proper time along the supplied
90    /// trajectory and then subtracts the requested coordinate time span.
91    ///
92    /// ## Parameters
93    ///
94    /// - `start`: Starting coordinate time of the interval.
95    /// - `end`: Ending coordinate time of the interval.
96    /// - `states`: Iterator of physical states in the form
97    ///   `(coordinate_time, velocity, gravitational_potential)`.
98    ///   Coordinate times must be monotonically **non-decreasing**.
99    ///   **It is the caller’s responsibility** to ensure the provided states
100    ///   cover the time range from `start` to `end`. The function integrates
101    ///   proper time over whatever samples are supplied and subtracts the
102    ///   requested coordinate interval (`end - start`). Exact matching of the
103    ///   first and last state times to `start` and `end` is **not** validated.
104    /// - `characteristic_length_scale`: Controls the weak-field vs strong-field
105    ///   formulation when constructing local spacetime states (see
106    ///   [`proper_time_from_states`] for full details). Pass `0.0` for all normal
107    ///   weak-field work (GNSS, Earth orbit, solar-system navigation). Supply a
108    ///   positive length (in meters) only when strong-field Planck-scale
109    ///   saturation effects are required.
110    ///
111    /// ## Returns
112    ///
113    /// `Ok(drift)` — the accumulated drift (`Δτ − Δt`) as a [`Dt`].
114    ///
115    /// `Err(DtErr)` — if the coordinate times in `states` are not monotonically
116    /// non-decreasing.
117    pub fn proper_time_drift_from_states<I>(
118        start: Dt,
119        end: Dt,
120        states: I,
121        characteristic_length_scale: Real,
122    ) -> Result<Dt, DtErr>
123    where
124        I: IntoIterator<Item = (Self, Velocity, Real)>,
125    {
126        if start.eq(&end) {
127            return Ok(Dt::ZERO);
128        }
129        let dtau = Self::proper_time_from_states(states, characteristic_length_scale)?;
130        Ok(dtau.sub(end.to_diff_raw(start)))
131    }
132
133    /// Computes accumulated proper time along an arbitrary trajectory.
134    ///
135    /// This is the core integration function of the library. It walks the
136    /// supplied path segment by segment and applies the trapezoidal rule
137    /// to the instantaneous proper-time rate at each step.
138    ///
139    /// This approach is commonly used when integrating clock rates along
140    /// sampled trajectories in astrodynamics and high-precision timing work.
141    ///
142    /// The function enforces that coordinate times are monotonically
143    /// non-decreasing (equal times are allowed). It performs a single pass
144    /// with no heap allocation.
145    ///
146    /// ## Parameters
147    ///
148    /// - `path`: An iterator of `(coordinate_time, Spacetime)` pairs.
149    ///   Coordinate times must be monotonically non-decreasing.
150    ///
151    /// ## Returns
152    ///
153    /// `Ok(total_proper_time)` — the accumulated proper time (Δτ) as a [`Dt`].
154    ///   Returns `ZERO` if the iterator is empty (no time elapsed).
155    ///
156    /// `Err(DtErr)` — if the path contains any decrease in coordinate time
157    ///   (i.e., a later sample has a strictly earlier coordinate time than a
158    ///   previous sample).
159    pub fn proper_time_from_path<I>(path: I) -> Result<Self, DtErr>
160    where
161        I: IntoIterator<Item = (Self, Spacetime)>,
162    {
163        let mut iter = path.into_iter();
164
165        let Some((mut prev_t, mut prev_ls)) = iter.next() else {
166            return Ok(Self::ZERO);
167        };
168
169        let mut accumulated = Self::ZERO;
170
171        for (t, ls) in iter {
172            if t.lt(&prev_t) {
173                return Err(an_err!(
174                    DtErrKind::InvalidInput,
175                    "proper_time_from_path requires monotonically non-decreasing coordinate times"
176                ));
177            }
178
179            let dt = t.to_diff_raw(prev_t);
180            if !dt.is_zero() {
181                let sign = if dt.to_attos() < 0 { f!(-1.0) } else { f!(1.0) };
182                let dt_pos = if sign < f!(0.0) { dt.neg() } else { dt };
183                let dt_sec = dt_pos.to_sec_f();
184
185                let rate0 = Self::rate_from_local(&prev_ls);
186                let rate1 = Self::rate_from_local(&ls);
187
188                let integral = f!(0.5) * (rate0 + rate1 - f!(2.0)) * dt_sec;
189                let dtau_segment = Dt::span_f(sign * (dt_sec + integral));
190
191                accumulated = accumulated.add(dtau_segment);
192            }
193
194            prev_t = t;
195            prev_ls = ls;
196        }
197
198        Ok(accumulated)
199    }
200
201    /// Computes proper time advance over an interval when the proper-time rate
202    /// is constant.
203    ///
204    /// This method is intended for trajectory segments where the physical
205    /// conditions remain unchanged, such as:
206    ///
207    /// - a fixed ground station,
208    /// - a circular orbit, or
209    /// - a deep-space cruise phase with constant velocity and gravitational potential.
210    ///
211    /// It is mathematically equivalent to integrating a constant rate using
212    /// the trapezoidal rule in [`proper_time_from_path`], but is more efficient
213    /// and makes the caller's intent explicit.
214    ///
215    /// The method is called on the starting coordinate time (`self`). It
216    /// calculates the coordinate time interval to `end` and multiplies it by
217    /// the supplied constant rate `dtau_dt`.
218    ///
219    /// ## Parameters
220    ///
221    /// - `end`: Ending coordinate time of the interval.
222    /// - `dtau_dt`: Constant proper-time rate (dimensionless). In relativistic
223    ///   contexts this value is typically slightly less than `1.0`. The caller
224    ///   is responsible for providing an appropriate rate (for example, from
225    ///   `Drift::proper_time_rate` or a precomputed constant).
226    ///
227    /// ## Returns
228    ///
229    /// The accumulated proper time advance (Δτ) over the interval as a [`Dt`].
230    ///
231    /// If `end` occurs before `self`, the result will be negative.
232    #[inline]
233    pub const fn proper_time_between_constant_rate(self, end: Dt, dtau_dt: Real) -> Dt {
234        let dt_sec = end.to_diff_raw(self).to_sec_f();
235        Dt::span_f(dtau_dt * dt_sec)
236    }
237
238    /// Returns the instantaneous proper-time rate (dτ/dt) from a local
239    /// spacetime state.
240    ///
241    /// This is a private helper used by the integration routines.
242    #[inline]
243    const fn rate_from_local(spacetime: &Spacetime) -> Real {
244        let drift = Drift::from_spacetime(spacetime);
245        f!(1.0) + drift.rate.to_sec_f()
246    }
247}