Skip to main content

deep_time/physics/
trajectory.rs

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