deep_time/dt/trajectory.rs
1use crate::{C_SQUARED, Drift, Dt, DtErr, DtErrKind, Real, Spacetime, Velocity, an_err};
2
3impl Dt {
4 /// Computes the relativistic clock drift (proper time minus coordinate time)
5 /// over an interval.
6 ///
7 /// This returns how much a physical clock has gained or lost time compared
8 /// with coordinate time between `start` and `end`.
9 ///
10 /// - A positive result means the onboard clock ran fast.
11 /// - A negative result means the onboard clock ran slow.
12 ///
13 /// # Parameters
14 ///
15 /// - `start`: Starting coordinate time of the interval.
16 /// - `end`: Ending coordinate time of the interval.
17 /// - `states`: Iterator of physical states. Coordinate times must be
18 /// monotonically non-decreasing. **It is the caller’s responsibility**
19 /// to ensure the provided states cover the time range from `start` to `end`.
20 /// The function integrates proper time over whatever states are supplied
21 /// and subtracts the requested coordinate interval (`end - start`).
22 /// Exact matching of the first and last state times to `start` and `end`
23 /// is **not** validated.
24 /// - `characteristic_length_scale`: See [`proper_time_from_states`].
25 ///
26 /// # Returns
27 ///
28 /// `Ok(drift)` — the accumulated drift (Δτ − Δt) as a [`Dt`].
29 ///
30 /// `Err(DtErr)` — if the states are not monotonically increasing in time.
31 pub fn proper_time_from_states<I>(
32 samples: I,
33 characteristic_length_scale: Real,
34 ) -> Result<Self, DtErr>
35 where
36 I: IntoIterator<Item = (Self, Velocity, Real)>,
37 {
38 let path_iter = samples.into_iter().map(|(t, vel, phi)| {
39 let phi_over_c2 = phi / C_SQUARED;
40 let ls = Spacetime::from_potential_velocity_and_scale(
41 phi_over_c2,
42 vel,
43 characteristic_length_scale,
44 );
45 (t, ls)
46 });
47
48 Self::proper_time_from_path(path_iter)
49 }
50
51 /// Computes the relativistic clock drift (proper time minus coordinate time)
52 /// over an interval.
53 ///
54 /// This returns how much a physical clock has gained or lost time compared
55 /// with coordinate time between `start` and `end`.
56 ///
57 /// - A positive result means the onboard clock ran fast.
58 /// - A negative result means the onboard clock ran slow.
59 ///
60 /// # Parameters
61 ///
62 /// - `start`: Starting coordinate time.
63 /// - `end`: Ending coordinate time.
64 /// - `states`: Iterator of physical states covering the interval.
65 /// Coordinate times must be monotonically non-decreasing.
66 /// It is the caller’s responsibility to ensure the states span
67 /// the requested interval (exact first/last time matching is not checked).
68 /// - `characteristic_length_scale`: See [`proper_time_from_states`].
69 ///
70 /// # Returns
71 ///
72 /// `Ok(drift)` — the accumulated drift (Δτ − Δt) as a [`Dt`].
73 ///
74 /// `Err(DtErr)` — if the states are not monotonically increasing in time.
75 pub fn proper_time_drift_from_states<I>(
76 start: Dt,
77 end: Dt,
78 states: I,
79 characteristic_length_scale: Real,
80 ) -> Result<Dt, DtErr>
81 where
82 I: IntoIterator<Item = (Self, Velocity, Real)>,
83 {
84 if start.eq(&end) {
85 return Ok(Dt::ZERO);
86 }
87 let dtau = Self::proper_time_from_states(states, characteristic_length_scale)?;
88 Ok(dtau.sub(end.to_diff_raw(start)))
89 }
90
91 /// Computes accumulated proper time along an arbitrary trajectory.
92 ///
93 /// This is the core integration function of the library. It walks the
94 /// supplied path segment by segment and applies the trapezoidal rule
95 /// to the instantaneous proper-time rate at each step.
96 ///
97 /// This approach is commonly used when integrating clock rates along
98 /// sampled trajectories in astrodynamics and high-precision timing work.
99 ///
100 /// The function enforces that coordinate times are monotonically
101 /// non-decreasing. It performs a single pass with no heap allocation.
102 ///
103 /// # Parameters
104 ///
105 /// - `path`: An iterator of `(coordinate_time, Spacetime)` pairs.
106 /// Coordinate times must be monotonically non-decreasing.
107 ///
108 /// # Returns
109 ///
110 /// `Ok(total_proper_time)` — the accumulated proper time as a [`Dt`].
111 ///
112 /// `Err(DtErr)` — if the path is empty or contains any decrease in
113 /// coordinate time.
114 pub fn proper_time_from_path<I>(path: I) -> Result<Self, DtErr>
115 where
116 I: IntoIterator<Item = (Self, Spacetime)>,
117 {
118 let mut iter = path.into_iter();
119
120 let Some((mut prev_t, mut prev_ls)) = iter.next() else {
121 return Ok(Self::ZERO);
122 };
123
124 let mut accumulated = Self::ZERO;
125
126 for (t, ls) in iter {
127 if t.lt(&prev_t) {
128 return Err(an_err!(
129 DtErrKind::InvalidInput,
130 "proper_time_from_path requires monotonically non-decreasing coordinate times"
131 ));
132 }
133
134 let dt = t.to_diff_raw(prev_t);
135 if !dt.is_zero() {
136 let sign = if dt.sec < 0 { f!(-1.0) } else { f!(1.0) };
137 let dt_pos = if sign < f!(0.0) { dt.neg() } else { dt };
138 let dt_sec = dt_pos.to_sec_f();
139
140 let rate0 = Self::rate_from_local(&prev_ls);
141 let rate1 = Self::rate_from_local(&ls);
142
143 let integral = f!(0.5) * (rate0 + rate1 - f!(2.0)) * dt_sec;
144 let dtau_segment = Dt::from_sec_f(sign * (dt_sec + integral));
145
146 accumulated = accumulated.add(dtau_segment);
147 }
148
149 prev_t = t;
150 prev_ls = ls;
151 }
152
153 Ok(accumulated)
154 }
155
156 /// Computes proper time advance over an interval when the rate is constant.
157 ///
158 /// Use this for segments where conditions do not change, such as
159 /// a ground station, a circular orbit, or a deep-space cruise phase
160 /// with constant velocity and gravitational potential.
161 ///
162 /// This is mathematically equivalent to integrating a constant rate
163 /// but is more efficient and expresses intent clearly.
164 ///
165 /// # Parameters
166 ///
167 /// - `end`: Ending coordinate time.
168 /// - `dtau_dt`: Constant proper-time rate (dimensionless, usually between 0 and 1).
169 ///
170 /// # Returns
171 ///
172 /// The accumulated proper time advance as a [`Dt`].
173 #[inline]
174 pub const fn proper_time_between_constant_rate(self, end: Dt, dtau_dt: Real) -> Dt {
175 let dt_sec = end.to_diff_raw(self).to_sec_f();
176 Dt::from_sec_f(dtau_dt * dt_sec)
177 }
178
179 /// Returns the instantaneous proper-time rate (dτ/dt) from a local
180 /// spacetime state.
181 ///
182 /// This is a private helper used by the integration routines.
183 #[inline]
184 const fn rate_from_local(spacetime: &Spacetime) -> Real {
185 let drift = Drift::from_spacetime(spacetime);
186 f!(1.0) + drift.rate().to_sec_f()
187 }
188}