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}