proof_engine/relativistic/
time_dilation.rs1use glam::{Vec2, Vec3, Vec4};
4use super::lorentz::lorentz_factor;
5
6pub fn time_dilation_factor(v: f64, c: f64) -> f64 {
8 lorentz_factor(v, c)
9}
10
11#[derive(Debug, Clone)]
13pub struct DilatedClock {
14 pub proper_time: f64,
16 pub velocity: f64,
18 pub c: f64,
20 pub accumulated: f64,
22}
23
24impl DilatedClock {
25 pub fn new(velocity: f64, c: f64) -> Self {
26 Self {
27 proper_time: 0.0,
28 velocity,
29 c,
30 accumulated: 0.0,
31 }
32 }
33
34 pub fn tick(&mut self, dt_observer: f64) {
37 self.accumulated += dt_observer;
38 let gamma = lorentz_factor(self.velocity, self.c);
39 self.proper_time += dt_observer / gamma;
40 }
41
42 pub fn rate(&self) -> f64 {
44 1.0 / lorentz_factor(self.velocity, self.c)
45 }
46
47 pub fn reset(&mut self) {
49 self.proper_time = 0.0;
50 self.accumulated = 0.0;
51 }
52
53 pub fn set_velocity(&mut self, v: f64) {
55 self.velocity = v;
56 }
57
58 pub fn lag(&self) -> f64 {
60 self.accumulated - self.proper_time
61 }
62}
63
64pub fn twin_paradox(distance: f64, v: f64, c: f64) -> (f64, f64) {
69 let gamma = lorentz_factor(v, c);
70 let stay_time = 2.0 * distance / v;
71 let traveler_time = stay_time / gamma;
72 (traveler_time, stay_time)
73}
74
75#[derive(Debug, Clone)]
77pub struct TimeDilationRenderer {
78 pub c: f64,
79 pub show_clock_hands: bool,
80 pub clock_size: f32,
81}
82
83impl TimeDilationRenderer {
84 pub fn new(c: f64) -> Self {
85 Self {
86 c,
87 show_clock_hands: true,
88 clock_size: 1.0,
89 }
90 }
91
92 pub fn tick_rate(&self, v: f64) -> f32 {
95 let gamma = lorentz_factor(v, self.c);
96 (1.0 / gamma) as f32
97 }
98
99 pub fn clock_hand_angle(&self, proper_time: f64) -> f32 {
102 let seconds = proper_time % 60.0;
103 (seconds / 60.0 * std::f64::consts::TAU) as f32
104 }
105
106 pub fn clock_glyph_data(
109 &self,
110 center: Vec2,
111 proper_time: f64,
112 ) -> (Vec<Vec2>, Vec2) {
113 let mut markers = Vec::with_capacity(12);
114 let radius = self.clock_size;
115 for i in 0..12 {
116 let angle = (i as f32 / 12.0) * std::f32::consts::TAU;
117 markers.push(center + Vec2::new(angle.cos() * radius, angle.sin() * radius));
118 }
119 let hand_angle = self.clock_hand_angle(proper_time);
120 let hand_tip = center + Vec2::new(
121 hand_angle.cos() * radius * 0.8,
122 hand_angle.sin() * radius * 0.8,
123 );
124 (markers, hand_tip)
125 }
126
127 pub fn render_clocks(
129 &self,
130 clocks: &[DilatedClock],
131 positions: &[Vec2],
132 ) -> Vec<(Vec<Vec2>, Vec2)> {
133 clocks.iter().zip(positions.iter()).map(|(clock, pos)| {
134 self.clock_glyph_data(*pos, clock.proper_time)
135 }).collect()
136 }
137}
138
139pub fn muon_lifetime(v: f64) -> f64 {
142 let c = 299_792_458.0;
143 let rest_lifetime = 2.2e-6; let gamma = lorentz_factor(v, c);
145 rest_lifetime * gamma
146}
147
148pub fn gravitational_time_dilation(height_diff: f64, g: f64, c: f64) -> f64 {
152 g * height_diff / (c * c)
153}
154
155#[derive(Debug, Clone)]
157pub struct ClockComparison {
158 pub clock_a: DilatedClock,
159 pub clock_b: DilatedClock,
160 pub position_a: Vec2,
161 pub position_b: Vec2,
162 pub label_a: String,
163 pub label_b: String,
164}
165
166impl ClockComparison {
167 pub fn new(
168 v_a: f64,
169 v_b: f64,
170 c: f64,
171 pos_a: Vec2,
172 pos_b: Vec2,
173 ) -> Self {
174 Self {
175 clock_a: DilatedClock::new(v_a, c),
176 clock_b: DilatedClock::new(v_b, c),
177 position_a: pos_a,
178 position_b: pos_b,
179 label_a: format!("v = {:.2}c", v_a / c),
180 label_b: format!("v = {:.2}c", v_b / c),
181 }
182 }
183
184 pub fn tick(&mut self, dt_observer: f64) {
186 self.clock_a.tick(dt_observer);
187 self.clock_b.tick(dt_observer);
188 }
189
190 pub fn time_difference(&self) -> f64 {
192 self.clock_a.proper_time - self.clock_b.proper_time
193 }
194
195 pub fn time_ratio(&self) -> f64 {
197 if self.clock_b.proper_time.abs() < 1e-15 {
198 return 1.0;
199 }
200 self.clock_a.proper_time / self.clock_b.proper_time
201 }
202
203 pub fn reset(&mut self) {
205 self.clock_a.reset();
206 self.clock_b.reset();
207 }
208
209 pub fn render_data(&self, renderer: &TimeDilationRenderer) -> ((Vec<Vec2>, Vec2), (Vec<Vec2>, Vec2)) {
211 let a = renderer.clock_glyph_data(self.position_a, self.clock_a.proper_time);
212 let b = renderer.clock_glyph_data(self.position_b, self.clock_b.proper_time);
213 (a, b)
214 }
215}
216
217pub fn gps_time_correction(orbit_radius: f64, earth_mass: f64, earth_radius: f64) -> f64 {
221 let c = 299_792_458.0;
222 let G = 6.674e-11;
223
224 let v_sat = (G * earth_mass / orbit_radius).sqrt();
226
227 let sr_correction = -v_sat * v_sat / (2.0 * c * c);
229
230 let gr_correction = G * earth_mass / (earth_radius * c * c) - G * earth_mass / (orbit_radius * c * c);
232
233 let total_fractional = sr_correction + gr_correction;
235 total_fractional * 86400.0
236}
237
238pub fn muon_travel_distance(v: f64) -> f64 {
240 let lifetime = muon_lifetime(v);
241 v * lifetime
242}
243
244pub fn integrated_proper_time(segments: &[(f64, f64)], c: f64) -> f64 {
247 let mut total = 0.0;
248 for &(dt, v) in segments {
249 let gamma = lorentz_factor(v, c);
250 total += dt / gamma;
251 }
252 total
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 const C: f64 = 299_792_458.0;
260
261 #[test]
262 fn test_time_dilation_factor_at_rest() {
263 let factor = time_dilation_factor(0.0, C);
264 assert!((factor - 1.0).abs() < 1e-10);
265 }
266
267 #[test]
268 fn test_time_dilation_factor_half_c() {
269 let factor = time_dilation_factor(0.5 * C, C);
270 let expected = 1.0 / (1.0 - 0.25_f64).sqrt();
271 assert!((factor - expected).abs() < 1e-6);
272 }
273
274 #[test]
275 fn test_dilated_clock() {
276 let mut clock = DilatedClock::new(0.866 * C, C);
277 clock.tick(10.0);
279 assert!((clock.proper_time - 5.0).abs() < 0.1);
281 assert!((clock.accumulated - 10.0).abs() < 1e-10);
282 }
283
284 #[test]
285 fn test_twin_paradox() {
286 let distance = 4.0 * C * 365.25 * 86400.0; let v = 0.8 * C;
289 let (traveler, stay) = twin_paradox(distance, v, C);
290
291 let stay_years = stay / (365.25 * 86400.0);
293 assert!((stay_years - 10.0).abs() < 0.01);
294
295 let traveler_years = traveler / (365.25 * 86400.0);
297 assert!((traveler_years - 6.0).abs() < 0.01);
298
299 assert!(traveler < stay);
301 }
302
303 #[test]
304 fn test_muon_reaches_ground() {
305 let v = 0.998 * C;
306 let distance = muon_travel_distance(v);
307 let rest_distance = v * 2.2e-6;
311 assert!(distance > rest_distance);
312 assert!(distance > 5.0 * rest_distance);
314 }
315
316 #[test]
317 fn test_muon_lifetime_dilation() {
318 let v = 0.99 * C;
319 let dilated = muon_lifetime(v);
320 let rest = 2.2e-6;
321 let gamma = lorentz_factor(v, C);
322 assert!((dilated - rest * gamma).abs() < 1e-15);
323 }
324
325 #[test]
326 fn test_gravitational_time_dilation_weak_field() {
327 let frac = gravitational_time_dilation(1.0, 9.8, C);
329 assert!(frac > 0.0);
331 assert!(frac < 1e-14);
332 }
333
334 #[test]
335 fn test_gps_correction() {
336 let orbit_r = 26_571_000.0;
338 let earth_mass = 5.972e24;
339 let earth_r = 6_371_000.0;
340
341 let correction = gps_time_correction(orbit_r, earth_mass, earth_r);
342 let correction_us = correction * 1e6;
344 assert!(
345 (correction_us - 38.0).abs() < 10.0,
346 "GPS correction: {} us/day, expected ~38",
347 correction_us
348 );
349 }
350
351 #[test]
352 fn test_clock_comparison() {
353 let mut comp = ClockComparison::new(
354 0.0, 0.866 * C, C,
355 Vec2::new(-5.0, 0.0),
356 Vec2::new(5.0, 0.0),
357 );
358 comp.tick(10.0);
359 assert!((comp.clock_a.proper_time - 10.0).abs() < 1e-10);
360 assert!((comp.clock_b.proper_time - 5.0).abs() < 0.1);
361 assert!(comp.time_difference() > 0.0);
362 }
363
364 #[test]
365 fn test_integrated_proper_time() {
366 let segments = vec![
367 (5.0, 0.0), (5.0, 0.866 * C), ];
370 let tau = integrated_proper_time(&segments, C);
371 assert!((tau - 7.5).abs() < 0.1);
373 }
374
375 #[test]
376 fn test_dilated_clock_lag() {
377 let mut clock = DilatedClock::new(0.6 * C, C);
378 clock.tick(100.0);
379 assert!(clock.lag() > 0.0);
380 assert!((clock.lag() - 20.0).abs() < 0.1);
382 }
383}