proof_engine/relativistic/
grav_time.rs1use glam::{Vec2, Vec4};
4
5pub fn schwarzschild_time_dilation(r: f64, rs: f64) -> f64 {
8 if r <= rs {
9 return 0.0;
10 }
11 (1.0 - rs / r).sqrt()
12}
13
14pub fn gravitational_redshift(r_emit: f64, r_obs: f64, rs: f64) -> f64 {
18 let factor_emit = schwarzschild_time_dilation(r_emit, rs);
19 let factor_obs = schwarzschild_time_dilation(r_obs, rs);
20 if factor_obs.abs() < 1e-15 {
21 return f64::INFINITY;
22 }
23 factor_emit / factor_obs
24}
25
26#[allow(non_snake_case)]
29pub fn proper_time_rate(r: f64, mass: f64, G: f64, c: f64) -> f64 {
30 let rs = 2.0 * G * mass / (c * c);
31 schwarzschild_time_dilation(r, rs)
32}
33
34pub fn gps_correction(orbit_radius: f64, earth_mass: f64, earth_radius: f64) -> f64 {
40 let c = 299_792_458.0;
41 let G = 6.674e-11;
42 let rs = 2.0 * G * earth_mass / (c * c);
43
44 let gr_surface = schwarzschild_time_dilation(earth_radius, rs);
46 let gr_orbit = schwarzschild_time_dilation(orbit_radius, rs);
47 let gr_frac = gr_orbit / gr_surface - 1.0;
49
50 let v_orbit = (G * earth_mass / orbit_radius).sqrt();
52 let beta = v_orbit / c;
53 let sr_frac = -0.5 * beta * beta; let total_frac = gr_frac + sr_frac;
57 total_frac * 86400.0 }
59
60#[derive(Debug, Clone)]
62pub struct GravTimeDilationField {
63 pub width: usize,
64 pub height: usize,
65 pub center: Vec2,
66 pub rs: f64,
67 pub factors: Vec<f64>,
68 pub cell_size: f32,
69}
70
71impl GravTimeDilationField {
72 pub fn new(width: usize, height: usize, center: Vec2, mass: f64, c: f64, g_const: f64, cell_size: f32) -> Self {
73 let rs = 2.0 * g_const * mass / (c * c);
74 let mut factors = Vec::with_capacity(width * height);
75 for iy in 0..height {
76 for ix in 0..width {
77 let x = (ix as f32 - width as f32 / 2.0) * cell_size + center.x;
78 let y = (iy as f32 - height as f32 / 2.0) * cell_size + center.y;
79 let r = ((x - center.x).powi(2) + (y - center.y).powi(2)).sqrt() as f64;
80 factors.push(schwarzschild_time_dilation(r, rs));
81 }
82 }
83 Self { width, height, center, rs, factors, cell_size }
84 }
85
86 pub fn get(&self, ix: usize, iy: usize) -> f64 {
88 if ix < self.width && iy < self.height {
89 self.factors[iy * self.width + ix]
90 } else {
91 1.0
92 }
93 }
94
95 pub fn sample(&self, pos: Vec2) -> f64 {
97 let r = (pos - self.center).length() as f64;
98 schwarzschild_time_dilation(r, self.rs)
99 }
100
101 pub fn schwarzschild_radius(&self) -> f64 {
103 self.rs
104 }
105
106 pub fn radius_for_factor(&self, factor: f64) -> f64 {
108 if factor >= 1.0 {
110 return f64::INFINITY;
111 }
112 if factor <= 0.0 {
113 return self.rs;
114 }
115 self.rs / (1.0 - factor * factor)
116 }
117}
118
119#[derive(Debug, Clone)]
121pub struct GravTimeRenderer {
122 pub field: GravTimeDilationField,
123 pub clock_size: f32,
124}
125
126impl GravTimeRenderer {
127 pub fn new(field: GravTimeDilationField) -> Self {
128 Self {
129 field,
130 clock_size: 1.0,
131 }
132 }
133
134 pub fn tick_rate_at(&self, pos: Vec2) -> f32 {
136 self.field.sample(pos) as f32
137 }
138
139 pub fn clock_angle_at(&self, pos: Vec2, coordinate_time: f64) -> f32 {
141 let rate = self.field.sample(pos);
142 let proper_time = coordinate_time * rate;
143 let seconds = proper_time % 60.0;
144 (seconds / 60.0 * std::f64::consts::TAU) as f32
145 }
146
147 pub fn render_clocks(
149 &self,
150 positions: &[Vec2],
151 coordinate_time: f64,
152 ) -> Vec<(Vec2, f32, f32)> {
153 positions.iter().map(|pos| {
155 let rate = self.field.sample(*pos);
156 let angle = self.clock_angle_at(*pos, coordinate_time);
157 (*pos, angle, rate as f32)
158 }).collect()
159 }
160
161 pub fn clock_color(&self, pos: Vec2) -> Vec4 {
164 let rate = self.field.sample(pos) as f32;
165 Vec4::new(1.0 - rate, 0.2, rate, 1.0)
166 }
167}
168
169#[allow(non_snake_case)]
173pub fn shapiro_delay(mass: f64, r1: f64, r2: f64, b: f64, G: f64, c: f64) -> f64 {
174 if b <= 0.0 {
175 return f64::INFINITY;
176 }
177 let factor = 4.0 * G * mass / (c * c * c);
178 factor * (4.0 * r1 * r2 / (b * b)).ln()
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 const C: f64 = 299_792_458.0;
186 const G: f64 = 6.674e-11;
187
188 #[test]
189 fn test_schwarzschild_at_infinity() {
190 let factor = schwarzschild_time_dilation(1e20, 1.0);
191 assert!((factor - 1.0).abs() < 1e-10);
192 }
193
194 #[test]
195 fn test_schwarzschild_at_horizon() {
196 let factor = schwarzschild_time_dilation(1.0, 1.0);
197 assert!(factor.abs() < 1e-10, "Should be zero at horizon: {}", factor);
198 }
199
200 #[test]
201 fn test_schwarzschild_inside_horizon() {
202 let factor = schwarzschild_time_dilation(0.5, 1.0);
203 assert_eq!(factor, 0.0, "Inside horizon returns 0");
204 }
205
206 #[test]
207 fn test_gravitational_redshift() {
208 let rs = 1.0;
210 let ratio = gravitational_redshift(1.1 * rs, 1000.0 * rs, rs);
211 assert!(ratio < 1.0, "Should be redshifted: {}", ratio);
213 }
214
215 #[test]
216 fn test_gps_correction_approximately_38us() {
217 let orbit_r = 26_571_000.0; let earth_mass = 5.972e24;
219 let earth_r = 6_371_000.0;
220 let correction = gps_correction(orbit_r, earth_mass, earth_r);
221 let correction_us = correction * 1e6;
222 assert!(
223 (correction_us - 38.0).abs() < 10.0,
224 "GPS correction: {} us/day, expected ~38",
225 correction_us
226 );
227 }
228
229 #[test]
230 fn test_proper_time_rate() {
231 let earth_mass = 5.972e24;
232 let rate = proper_time_rate(6_371_000.0, earth_mass, G, C);
233 assert!((rate - 1.0).abs() < 1e-8);
235 assert!(rate < 1.0, "Surface clock runs slow vs infinity");
236 }
237
238 #[test]
239 fn test_grav_time_field() {
240 let field = GravTimeDilationField::new(
241 20, 20, Vec2::ZERO, 1e30, C, G, 100.0,
242 );
243 let center_factor = field.get(10, 10);
245 let corner_factor = field.get(0, 0);
247 assert!(center_factor >= 0.0);
249 assert!(corner_factor >= 0.0);
250 }
251
252 #[test]
253 fn test_radius_for_factor() {
254 let field = GravTimeDilationField::new(10, 10, Vec2::ZERO, 1e30, C, G, 1.0);
255 let r = field.radius_for_factor(0.5);
256 let expected = field.rs / 0.75;
258 assert!((r - expected).abs() / expected < 1e-10);
259 }
260
261 #[test]
262 fn test_shapiro_delay_positive() {
263 let m_sun = 1.989e30;
264 let delay = shapiro_delay(m_sun, 1.5e11, 1.5e11, 6.96e8, G, C);
265 assert!(delay > 0.0);
267 let delay_us = delay * 1e6;
268 assert!(delay_us > 100.0 && delay_us < 500.0,
269 "Shapiro delay: {} us", delay_us);
270 }
271
272 #[test]
273 fn test_grav_time_renderer() {
274 let field = GravTimeDilationField::new(10, 10, Vec2::ZERO, 1e30, C, G, 1000.0);
275 let renderer = GravTimeRenderer::new(field);
276 let rate = renderer.tick_rate_at(Vec2::new(1000.0, 0.0));
277 assert!(rate > 0.0 && rate <= 1.0);
278 }
279}