Skip to main content

deep_time/dt/
trajectory.rs

1use crate::{C_SQUARED, Drift, Dt, Real, Spacetime, Velocity};
2
3impl Dt {
4    /// Computes the accumulated proper time \(\Delta\tau\) experienced by a clock
5    /// traveling along a trajectory given by a sequence of physical states.
6    ///
7    /// Each input triple \((t, v, \Phi)\) is automatically converted into the
8    /// internal [`Spacetime`] representation required by the library’s
9    /// unified master Lagrangian, then passed to [`proper_time_from_path`].
10    ///
11    /// Proper time \(\Delta\tau\) is the time actually measured by a real
12    /// physical clock (e.g. an onboard spacecraft clock). It includes the
13    /// relativistic effects of the clock's velocity and local gravitic conditions,
14    /// plus the library’s built-in Planck-scale saturation term.
15    ///
16    /// # Parameters
17    ///
18    /// - `samples` — An iterator yielding tuples of the form
19    ///   `(coordinate_time, velocity, Newtonian gravitational potential)`.
20    ///   The coordinate time must be monotonically non-decreasing for physically
21    ///   meaningful results.  
22    ///   - `coordinate_time`: a [`Dt`] value (any [`Scale`] is accepted).  
23    ///   - `velocity`: a [`Velocity`] in m/s.  
24    ///   - `grav_potential`: \(\Phi\) in m² s⁻² (Newtonian potential; negative
25    ///     for bound orbits). The library converts this to the lapse factor
26    ///     \(\alpha = \sqrt{1 + 2\Phi/c^2}\).
27    ///
28    /// - `characteristic_length_scale` — A length in meters that controls whether
29    ///   the weak-field or strong-field formulation is used.
30    ///   - Pass `0.0` (the recommended default for solar-system, GNSS, and
31    ///     cislunar work) to recover the classic general-relativistic clock rate
32    ///     exactly as used by JPL, ESA, and SPICE pipelines.
33    ///   - Supply a realistic non-zero value (e.g. the local scale height of
34    ///     the gravitational field) only when operating near neutron stars,
35    ///     black-hole horizons, or other regions where spacetime curvature
36    ///     becomes extreme. This activates the Planck-scale saturation term
37    ///     encoded in \(K_{\rm eff}\).
38    ///
39    /// # Returns
40    ///
41    /// The total accumulated proper-time interval \(\Delta\tau\) as a [`Dt`]
42    /// value, computed with the library’s exact 36-digit arithmetic.
43    ///
44    /// # Examples
45    ///
46    /// **Basic solar-system usage (weak-field)**  
47    /// ```rust
48    /// use deep_time::{Dt, Velocity, Scale};
49    ///
50    /// let trajectory = vec![
51    ///     (Dt::from_sec(0, Scale::TAI),   Velocity::from_speed(7800.0), -6.0e7),
52    ///     (Dt::from_sec(3600, Scale::TAI), Velocity::from_speed(11000.0), -1.0e6),
53    /// ];
54    ///
55    /// let delta_tau = Dt::proper_time_from_states(trajectory, 0.0);
56    /// ```
57    ///
58    /// **Strong-field example (neutron-star vicinity)**  
59    /// ```rust
60    /// use deep_time::{Dt, Velocity, Scale};
61    ///
62    /// let trajectory = vec![
63    ///     (Dt::from_sec(0, Scale::TAI),   Velocity::from_speed(7800.0), -6.0e7),
64    ///     (Dt::from_sec(3600, Scale::TAI), Velocity::from_speed(11000.0), -1.0e6),
65    /// ];
66    ///
67    /// let strong_scale = 1e4; // 10 km characteristic scale near neutron star
68    /// let delta_tau = Dt::proper_time_from_states(trajectory, strong_scale);
69    /// ```
70    pub fn proper_time_from_states<I>(samples: I, characteristic_length_scale: Real) -> Self
71    where
72        I: IntoIterator<Item = (Self, Velocity, Real)>, // (t, vel_m_s, Φ m²/s²)
73    {
74        let path_iter = samples.into_iter().map(|(t, vel, phi)| {
75            let phi_over_c2 = phi / C_SQUARED;
76            let ls = Spacetime::from_potential_velocity_and_scale(
77                phi_over_c2,
78                vel,
79                characteristic_length_scale,
80            );
81            (t, ls)
82        });
83
84        Self::proper_time_from_path(path_iter)
85    }
86
87    /// Computes the relativistic clock drift \(\Delta\tau - \Delta t\) for an
88    /// onboard clock traveling along a trajectory given by a sequence of
89    /// physical states.
90    ///
91    /// This function returns the net amount by which a real physical clock
92    /// has gained or lost time relative to the coordinate time interval
93    /// between `start` and `end`.
94    ///
95    /// # Parameters
96    ///
97    /// - `start` — The starting coordinate time of the interval (any [`Scale`]).
98    /// - `end` — The ending coordinate time of the interval.
99    /// - `states` — An iterator yielding tuples of the form
100    ///   `(coordinate_time, velocity, Newtonian gravitational potential)`.
101    ///   See [`proper_time_from_states`] for a detailed description of each
102    ///   component.
103    /// - `characteristic_length_scale` — A length in meters that controls
104    ///   whether the weak-field or strong-field formulation is used.
105    ///
106    /// # Returns
107    ///
108    /// The total clock drift \(\Delta\tau - \Delta t\) as a [`Dt`] value,
109    /// computed with the library’s exact 36-digit arithmetic.
110    ///
111    /// - A positive value means the onboard clock ran **fast** relative to
112    ///   coordinate time.
113    /// - A negative value means the onboard clock ran **slow** relative to
114    ///   coordinate time.
115    ///
116    /// # Examples
117    ///
118    /// **Basic usage (weak-field solar-system trajectory)**
119    /// ```rust
120    /// use deep_time::{Dt, Velocity, Scale};
121    ///
122    /// let start = Dt::from_sec(0, Scale::TAI);
123    /// let end   = Dt::from_sec(3600, Scale::TAI);
124    ///
125    /// let trajectory = vec![
126    ///     (start, Velocity::from_speed(7800.0), -6.0e7),
127    ///     (end,   Velocity::from_speed(11000.0), -1.0e6),
128    /// ];
129    ///
130    /// let drift = Dt::proper_time_drift_from_states(
131    ///     start,
132    ///     end,
133    ///     trajectory,
134    ///     0.0,
135    /// );
136    /// ```
137    pub fn proper_time_drift_from_states<I>(
138        start: Dt,
139        end: Dt,
140        states: I,
141        characteristic_length_scale: Real,
142    ) -> Dt
143    where
144        I: IntoIterator<Item = (Self, Velocity, Real)>,
145    {
146        let dtau = Self::proper_time_from_states(states, characteristic_length_scale);
147        let dt = end.to_diff_raw(start);
148        dtau.sub(dt)
149    }
150
151    /// Computes the accumulated proper time \(\Delta\tau\) experienced by a clock
152    /// traveling along an arbitrarily spaced trajectory in coordinate time.
153    ///
154    /// This is the core integration primitive of the library. It walks the supplied
155    /// path segment-by-segment and integrates the instantaneous proper-time rate
156    /// \(d\tau/dt = \sqrt{K_{\rm eff}}\) (derived from the library’s unified master
157    /// Lagrangian). The result is the total signed proper-time interval from the
158    /// first to the last point on the path.
159    ///
160    /// It accounts exactly for special-relativistic velocity time dilation,
161    /// general-relativistic gravitational time dilation, and the intrinsic Planck-scale
162    /// saturation term encoded in \(K_{\rm eff}\).
163    ///
164    /// The integration uses the same high-order quadrature already implemented in
165    /// [`proper_time_between`]: composite Simpson’s rule for \(n \ge 2\) intervals
166    /// or the trapezoidal rule for the two-point case.
167    ///
168    /// # Parameters
169    ///
170    /// - `path` — An iterator yielding at least two tuples of the form
171    ///   `(coordinate_time, Spacetime)`. Each pair contains the coordinate
172    ///   time at that instant and the corresponding [`Spacetime`] snapshot
173    ///   (gravitational lapse factor \(\alpha\), local three-velocity magnitude
174    ///   \(\beta\), and Kretschmann scalar \(\mathcal{K}\)).
175    ///   The coordinate times must be monotonically non-decreasing for physically
176    ///   consistent results.
177    ///
178    /// # Returns
179    ///
180    /// The total accumulated proper-time interval \(\Delta\tau\) as a [`Dt`] value.
181    /// All final arithmetic is performed with the library’s exact 36-digit
182    /// representation.
183    ///
184    /// # Example
185    ///
186    /// ```rust
187    /// use deep_time::{Dt, Spacetime, Scale};
188    ///
189    /// // Sample non-uniform trajectory (e.g., denser sampling near periapsis
190    /// // or during a gravity-assist flyby)
191    /// let path: &[(Dt, Spacetime)] = &[
192    ///     (Dt::from_sec(0, Scale::TAI),   Spacetime::new(0.999_000, 0.0, 0.0)),
193    ///     (Dt::from_sec(120, Scale::TAI), Spacetime::new(0.998_500, 1.2e-4, 0.0)),
194    ///     (Dt::from_sec(250, Scale::TAI), Spacetime::new(0.997_200, 3.5e-4, 0.0)), // higher density
195    ///     (Dt::from_sec(400, Scale::TAI), Spacetime::new(0.999_200, 0.0, 0.0)),
196    /// ];
197    ///
198    /// let delta_tau = Dt::proper_time_from_path(path.iter().copied());
199    ///
200    /// // Advance the onboard proper-time clock
201    /// let onboard_tau = path[0].0.add(delta_tau);
202    /// ```
203    pub fn proper_time_from_path<I>(path: I) -> Self
204    where
205        I: IntoIterator<Item = (Self, Spacetime)>,
206    {
207        let mut iter = path.into_iter();
208
209        let Some((mut prev_t, mut prev_ls)) = iter.next() else {
210            return Self::ZERO;
211        };
212
213        let mut accumulated = Self::ZERO;
214
215        for (t, ls) in iter {
216            let segment = [prev_ls, ls];
217            let dtau = prev_t.proper_time_between(t, &segment);
218
219            accumulated = accumulated.add(dtau);
220
221            prev_t = t;
222            prev_ls = ls;
223        }
224
225        accumulated
226    }
227
228    /// Computes the accumulated proper time \(\Delta\tau\) experienced by a clock
229    /// moving along a coordinate-time path from `self` to `end`.
230    ///
231    /// Proper time is the actual time measured by a real physical clock
232    /// (onboard spacecraft clock, probe, etc.). This function evaluates the
233    /// exact relativistic rate \(d\tau/dt = \sqrt{K_{\rm eff}}\) from the
234    /// library’s unified master Lagrangian at each sample point and integrates
235    /// the result using composite Simpson’s rule (or the trapezoidal rule for
236    /// the two-point case).
237    ///
238    /// Use this when you have a fixed number of uniformly spaced
239    /// [`Spacetime`] snapshots and need the integrated proper time over a
240    /// single interval.
241    ///
242    /// # Parameters
243    ///
244    /// - `end` — The ending coordinate time of the interval (any [`Scale`]).
245    /// - `samples` — A slice of [`Spacetime`] snapshots evaluated at
246    ///   **uniformly spaced** points along the path. The slice must contain
247    ///   at least two entries. These samples can be freely reused elsewhere
248    ///   (e.g. for light-time calculations in [`ObserverState`]).
249    ///
250    /// # Returns
251    ///
252    /// The accumulated proper-time interval \(\Delta\tau\) as a [`Dt`] value,
253    /// computed with the library’s exact 36-digit arithmetic.
254    ///
255    /// # Example
256    ///
257    /// ```rust
258    /// use deep_time::{Scale, Spacetime, Dt};
259    ///
260    /// let start = Dt::from_sec(0, Scale::TAI);
261    /// let end   = Dt::from_sec(1000, Scale::TAI);
262    ///
263    /// // Constant metric example (α = 0.9 → dτ/dt = 0.9)
264    /// let slow = Spacetime::new(0.9, 0.0, 0.0);
265    /// let samples = [slow; 2];
266    ///
267    /// let delta_tau = start.proper_time_between(end, &samples);
268    /// assert_eq!(delta_tau, Dt::from_sec(900, Scale::TAI));
269    ///
270    /// // Update onboard proper time clock
271    /// let onboard_tau = start.add(delta_tau);
272    /// ```
273    pub const fn proper_time_between(self, end: Dt, samples: &[Spacetime]) -> Dt {
274        if samples.len() < 2 || self.eq(&end) {
275            return Dt::ZERO;
276        }
277
278        let mut dt = end.to_diff_raw(self);
279        let sign = if dt.sec < 0 { f!(-1.0) } else { f!(1.0) };
280        if sign < f!(0.0) {
281            dt = dt.neg();
282        }
283
284        let dt_sec = dt.to_sec_f();
285        let num_intervals = samples.len() - 1;
286
287        if num_intervals <= 1 {
288            // Fast trapezoidal rule for constant-rate cases
289            let rate0 = Self::rate_from_local(&samples[0]);
290            let rate1 = Self::rate_from_local(&samples[samples.len() - 1]);
291            let integral = f!(0.5) * (rate0 + rate1 - f!(2.0)) * dt_sec;
292            return Dt::from_sec_f(sign * (dt_sec + integral));
293        }
294
295        // Simpson’s rule quadrature (high-order accuracy)
296        let n = f!(num_intervals);
297        let h = dt_sec / n;
298        let mut s = f!(0.0);
299
300        let mut i = 0;
301        while i <= num_intervals {
302            let local = &samples[i];
303            let rate = Self::rate_from_local(local);
304
305            let coeff = if i == 0 || i == num_intervals {
306                f!(1.0)
307            } else if i % 2 == 0 {
308                f!(2.0)
309            } else {
310                f!(4.0)
311            };
312            s += coeff * (rate - f!(1.0));
313
314            i += 1;
315        }
316
317        let integral = (h / f!(3.0)) * s;
318        Dt::from_sec_f(sign * (dt_sec + integral))
319    }
320
321    /// Computes the accumulated proper time \(\Delta\tau\) when the instantaneous
322    /// rate \(d\tau/dt\) is known to be constant.
323    ///
324    /// This is the fastest and clearest way to accumulate proper time for
325    /// segments where the metric is unchanging, such as a ground station,
326    /// circular orbit, or deep-space cruise phase.
327    ///
328    /// It is mathematically equivalent to calling [`proper_time_between`] with
329    /// a two-element slice containing the same rate at both endpoints, but
330    /// requires no allocation and expresses the intent more directly.
331    ///
332    /// # Parameters
333    ///
334    /// - `end` — The ending coordinate time of the interval (any [`Scale`]).
335    /// - `dtau_dt` — The constant proper-time rate \(d\tau/dt\) (dimensionless).
336    ///   Typical sources are:
337    ///   - [`Spacetime::proper_time_rate`]
338    ///   - [`Drift::proper_time_rate`]
339    ///   - Any precomputed value of \(\sqrt{K_{\rm eff}}\) from the master Lagrangian.
340    ///
341    /// # Returns
342    ///
343    /// The accumulated proper-time interval \(\Delta\tau\) as a [`Dt`] value,
344    /// computed with the library’s exact 36-digit arithmetic.
345    #[inline]
346    pub const fn proper_time_between_constant_rate(
347        self,
348        end: Dt,
349        // can come from Drift::proper_time_rate() or Spacetime::proper_time_rate()
350        dtau_dt: Real,
351    ) -> Dt {
352        let dt_sec = end.to_diff_raw(self).to_sec_f();
353        Dt::from_sec_f(dtau_dt * dt_sec)
354    }
355
356    /// Computes the relativistic clock drift \(\Delta\tau - \Delta t\) using
357    /// pre-computed, uniformly spaced [`Spacetime`] samples.
358    ///
359    /// This function returns the difference between the proper time \(\Delta\tau\)
360    /// accumulated by a real physical clock and the elapsed coordinate time
361    /// interval from `self` to `end`. Coordinate time is the reference time
362    /// used in the ephemeris or simulation data to label each set of positions,
363    /// velocities, and gravitational potentials.
364    ///
365    /// # Parameters
366    ///
367    /// - `end` — The ending coordinate time of the interval (any [`Scale`]).
368    /// - `samples` — A slice of [`Spacetime`] snapshots evaluated at
369    ///   **uniformly spaced** points along the path. The slice must contain
370    ///   at least two entries. See [`proper_time_between`] for details on
371    ///   the sampling requirements and usage examples.
372    ///
373    /// # Returns
374    ///
375    /// The total clock drift \(\Delta\tau - \Delta t\) as a [`Dt`] value,
376    /// computed with the library’s exact 36-digit arithmetic.
377    ///
378    /// - A positive value means the onboard clock ran **fast** relative to
379    ///   coordinate time.
380    /// - A negative value means the onboard clock ran **slow** relative to
381    ///   coordinate time.
382    pub const fn relativistic_correction_between(self, end: Dt, samples: &[Spacetime]) -> Dt {
383        let dtau = self.proper_time_between(end, samples);
384        let dt = end.to_diff_raw(self);
385        dtau.sub(dt)
386    }
387
388    /// Private helper: instantaneous proper-time rate dτ/dt from a `Spacetime` snapshot.
389    #[inline]
390    const fn rate_from_local(spacetime: &Spacetime) -> Real {
391        let drift = Drift::from_spacetime(spacetime);
392        f!(1.0) + drift.rate().to_sec_f()
393    }
394}