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