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}