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
use crate::{C_SQUARED, Drift, Dt, DtErr, DtErrKind, Real, Spacetime, Velocity, an_err};
impl Dt {
/// Computes the accumulated proper time along a trajectory given a sequence
/// of physical states.
///
/// This function accepts samples expressed in terms of directly observable
/// quantities — coordinate time, velocity, and gravitational potential —
/// and integrates the proper time (Δτ) along the path. It is a convenience
/// wrapper around the core [`proper_time_from_path`] routine.
///
/// The integration is performed using the trapezoidal rule applied to the
/// instantaneous proper-time rate between consecutive samples. This approach
/// is standard for high-precision clock modeling in astrodynamics and
/// relativistic timing applications.
///
/// A single sample, or multiple samples at identical times, produces a result
/// of zero (no time has elapsed). An empty iterator also returns zero.
///
/// ## Parameters
///
/// - `samples`: Iterator yielding `(coordinate_time, velocity, gravitational_potential)`
/// triples. The coordinate times must be monotonically non-decreasing.
/// **It is the caller’s responsibility** to supply samples that cover the
/// desired time interval. The function does not validate that the first or
/// last sample exactly matches any particular start or end time.
/// - `characteristic_length_scale`: Controls whether the weak-field or
/// strong-field formulation is used when constructing the local spacetime
/// state.
///
/// Pass `0.0` (the normal choice) for all conventional weak-field work
/// (Earth orbit, GNSS, solar-system navigation, most spacecraft). This
/// produces exactly the classic relativistic clock rate used by JPL, ESA,
/// and GNSS systems, with the Kretschmann scalar set to zero.
///
/// Supply a positive value (in meters) only when you need the library’s
/// intrinsic Planck-scale saturation term. The value should represent the
/// characteristic length scale over which the gravitational field varies
/// significantly at the observer’s location. This is intended for strong-field
/// regimes such as the vicinity of neutron stars or black-hole event horizons.
///
/// ## Returns
///
/// `Ok(total_proper_time)` — the total proper time (Δτ) that has accumulated
/// for an observer following the trajectory defined by the supplied samples,
/// returned as a [`Dt`].
///
/// This value represents the actual time that would have elapsed on a physical
/// clock moving along the path, including all relativistic effects (velocity
/// and gravitational time dilation, plus the Planck-scale saturation term when
/// active). It is **not** a drift or difference relative to coordinate time.
/// If you need the difference between proper time and coordinate time
/// (Δτ − Δt), use [`proper_time_drift_from_states`] instead.
///
/// `Err(DtErr)` — if the coordinate times are not monotonically non-decreasing.
pub fn proper_time_from_states<I>(
samples: I,
characteristic_length_scale: Real,
) -> Result<Self, DtErr>
where
I: IntoIterator<Item = (Self, Velocity, Real)>,
{
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 (proper time minus coordinate time)
/// over a specific interval.
///
/// This returns how much a physical clock has gained or lost time compared
/// with coordinate time between `start` and `end`.
///
/// - A positive result means the onboard clock ran **fast** (it accumulated
/// more proper time than the coordinate interval).
/// - A negative result means the onboard clock ran **slow** (it accumulated
/// less proper time than the coordinate interval).
///
/// This is the higher-level function most callers should use when they need
/// the net drift over a well-defined time interval. It internally calls
/// [`proper_time_from_states`] to integrate proper time along the supplied
/// trajectory and then subtracts the requested coordinate time span.
///
/// ## Parameters
///
/// - `start`: Starting coordinate time of the interval.
/// - `end`: Ending coordinate time of the interval.
/// - `states`: Iterator of physical states in the form
/// `(coordinate_time, velocity, gravitational_potential)`.
/// Coordinate times must be monotonically **non-decreasing**.
/// **It is the caller’s responsibility** to ensure the provided states
/// cover the time range from `start` to `end`. The function integrates
/// proper time over whatever samples are supplied and subtracts the
/// requested coordinate interval (`end - start`). Exact matching of the
/// first and last state times to `start` and `end` is **not** validated.
/// - `characteristic_length_scale`: Controls the weak-field vs strong-field
/// formulation when constructing local spacetime states (see
/// [`proper_time_from_states`] for full details). Pass `0.0` for all normal
/// weak-field work (GNSS, Earth orbit, solar-system navigation). Supply a
/// positive length (in meters) only when strong-field Planck-scale
/// saturation effects are required.
///
/// ## Returns
///
/// `Ok(drift)` — the accumulated drift (`Δτ − Δt`) as a [`Dt`].
///
/// `Err(DtErr)` — if the coordinate times in `states` are not monotonically
/// non-decreasing.
pub fn proper_time_drift_from_states<I>(
start: Dt,
end: Dt,
states: I,
characteristic_length_scale: Real,
) -> Result<Dt, DtErr>
where
I: IntoIterator<Item = (Self, Velocity, Real)>,
{
if start.eq(&end) {
return Ok(Dt::ZERO);
}
let dtau = Self::proper_time_from_states(states, characteristic_length_scale)?;
Ok(dtau.sub(end.to_diff_raw(start)))
}
/// Computes accumulated proper time along an arbitrary trajectory.
///
/// This is the core integration function of the library. It walks the
/// supplied path segment by segment and applies the trapezoidal rule
/// to the instantaneous proper-time rate at each step.
///
/// This approach is commonly used when integrating clock rates along
/// sampled trajectories in astrodynamics and high-precision timing work.
///
/// The function enforces that coordinate times are monotonically
/// non-decreasing (equal times are allowed). It performs a single pass
/// with no heap allocation.
///
/// ## Parameters
///
/// - `path`: An iterator of `(coordinate_time, Spacetime)` pairs.
/// Coordinate times must be monotonically non-decreasing.
///
/// ## Returns
///
/// `Ok(total_proper_time)` — the accumulated proper time (Δτ) as a [`Dt`].
/// Returns `ZERO` if the iterator is empty (no time elapsed).
///
/// `Err(DtErr)` — if the path contains any decrease in coordinate time
/// (i.e., a later sample has a strictly earlier coordinate time than a
/// previous sample).
pub fn proper_time_from_path<I>(path: I) -> Result<Self, DtErr>
where
I: IntoIterator<Item = (Self, Spacetime)>,
{
let mut iter = path.into_iter();
let Some((mut prev_t, mut prev_ls)) = iter.next() else {
return Ok(Self::ZERO);
};
let mut accumulated = Self::ZERO;
for (t, ls) in iter {
if t.lt(&prev_t) {
return Err(an_err!(
DtErrKind::InvalidInput,
"proper_time_from_path requires monotonically non-decreasing coordinate times"
));
}
let dt = t.to_diff_raw(prev_t);
if !dt.is_zero() {
let sign = if dt.to_attos() < 0 { f!(-1.0) } else { f!(1.0) };
let dt_pos = if sign < f!(0.0) { dt.neg() } else { dt };
let dt_sec = dt_pos.to_sec_f();
let rate0 = Self::rate_from_local(&prev_ls);
let rate1 = Self::rate_from_local(&ls);
let integral = f!(0.5) * (rate0 + rate1 - f!(2.0)) * dt_sec;
let dtau_segment = Dt::span_f(sign * (dt_sec + integral));
accumulated = accumulated.add(dtau_segment);
}
prev_t = t;
prev_ls = ls;
}
Ok(accumulated)
}
/// Computes proper time advance over an interval when the proper-time rate
/// is constant.
///
/// This method is intended for trajectory segments where the physical
/// conditions remain unchanged, such as:
///
/// - a fixed ground station,
/// - a circular orbit, or
/// - a deep-space cruise phase with constant velocity and gravitational potential.
///
/// It is mathematically equivalent to integrating a constant rate using
/// the trapezoidal rule in [`proper_time_from_path`], but is more efficient
/// and makes the caller's intent explicit.
///
/// The method is called on the starting coordinate time (`self`). It
/// calculates the coordinate time interval to `end` and multiplies it by
/// the supplied constant rate `dtau_dt`.
///
/// ## Parameters
///
/// - `end`: Ending coordinate time of the interval.
/// - `dtau_dt`: Constant proper-time rate (dimensionless). In relativistic
/// contexts this value is typically slightly less than `1.0`. The caller
/// is responsible for providing an appropriate rate (for example, from
/// `Drift::proper_time_rate` or a precomputed constant).
///
/// ## Returns
///
/// The accumulated proper time advance (Δτ) over the interval as a [`Dt`].
///
/// If `end` occurs before `self`, the result will be negative.
#[inline]
pub const fn proper_time_between_constant_rate(self, end: Dt, dtau_dt: Real) -> Dt {
let dt_sec = end.to_diff_raw(self).to_sec_f();
Dt::span_f(dtau_dt * dt_sec)
}
/// Returns the instantaneous proper-time rate (dτ/dt) from a local
/// spacetime state.
///
/// This is a private helper used by the integration routines.
#[inline]
const fn rate_from_local(spacetime: &Spacetime) -> Real {
let drift = Drift::from_spacetime(spacetime);
f!(1.0) + drift.rate.to_sec_f()
}
}