1use glam::{Vec2, Vec3, Vec4};
4use super::lorentz;
5
6#[derive(Debug, Clone)]
8pub struct SpacetimeEvent {
9 pub t: f64,
10 pub x: f64,
11 pub label: String,
12}
13
14impl SpacetimeEvent {
15 pub fn new(t: f64, x: f64, label: &str) -> Self {
16 Self { t, x, label: label.to_string() }
17 }
18
19 pub fn interval_sq(&self, other: &SpacetimeEvent, c: f64) -> f64 {
21 let dt = self.t - other.t;
22 let dx = self.x - other.x;
23 c * c * dt * dt - dx * dx
24 }
25
26 pub fn as_point(&self) -> Vec2 {
28 Vec2::new(self.x as f32, self.t as f32)
29 }
30}
31
32#[derive(Debug, Clone)]
34pub struct Worldline {
35 pub events: Vec<(f64, f64)>, pub color: Vec4,
37 pub label: String,
38}
39
40impl Worldline {
41 pub fn new(label: &str, color: Vec4) -> Self {
42 Self {
43 events: Vec::new(),
44 color,
45 label: label.to_string(),
46 }
47 }
48
49 pub fn add_event(&mut self, t: f64, x: f64) {
50 self.events.push((t, x));
51 }
52
53 pub fn at_rest(x: f64, t_range: (f64, f64), steps: usize, label: &str) -> Self {
55 let mut wl = Self::new(label, Vec4::new(1.0, 1.0, 1.0, 1.0));
56 let dt = (t_range.1 - t_range.0) / steps.max(1) as f64;
57 for i in 0..=steps {
58 wl.add_event(t_range.0 + dt * i as f64, x);
59 }
60 wl
61 }
62
63 pub fn constant_velocity(x0: f64, v: f64, t_range: (f64, f64), steps: usize, label: &str) -> Self {
65 let mut wl = Self::new(label, Vec4::new(0.5, 1.0, 0.5, 1.0));
66 let dt = (t_range.1 - t_range.0) / steps.max(1) as f64;
67 for i in 0..=steps {
68 let t = t_range.0 + dt * i as f64;
69 wl.add_event(t, x0 + v * t);
70 }
71 wl
72 }
73
74 pub fn as_points(&self) -> Vec<Vec2> {
76 self.events.iter().map(|(t, x)| Vec2::new(*x as f32, *t as f32)).collect()
77 }
78
79 pub fn velocity_at(&self, index: usize) -> f64 {
81 if index + 1 >= self.events.len() {
82 return 0.0;
83 }
84 let (t1, x1) = self.events[index];
85 let (t2, x2) = self.events[index + 1];
86 let dt = t2 - t1;
87 if dt.abs() < 1e-15 { return 0.0; }
88 (x2 - x1) / dt
89 }
90}
91
92#[derive(Debug, Clone)]
94pub struct MinkowskiDiagram {
95 pub events: Vec<SpacetimeEvent>,
96 pub worldlines: Vec<Worldline>,
97}
98
99impl MinkowskiDiagram {
100 pub fn new() -> Self {
101 Self {
102 events: Vec::new(),
103 worldlines: Vec::new(),
104 }
105 }
106
107 pub fn add_event(&mut self, event: SpacetimeEvent) {
108 self.events.push(event);
109 }
110
111 pub fn add_worldline(&mut self, wl: Worldline) {
112 self.worldlines.push(wl);
113 }
114
115 pub fn event_points(&self) -> Vec<Vec2> {
117 self.events.iter().map(|e| e.as_point()).collect()
118 }
119}
120
121pub fn light_cone(
124 event: &SpacetimeEvent,
125 c: f64,
126) -> (Vec<(f64, f64)>, Vec<(f64, f64)>) {
127 let extent = 10.0; let steps = 50;
129 let dt = extent / steps as f64;
130
131 let mut future = Vec::with_capacity(steps * 2);
132 let mut past = Vec::with_capacity(steps * 2);
133
134 for i in 0..=steps {
135 let t_off = dt * i as f64;
136 future.push((event.x + c * t_off, event.t + t_off));
138 future.push((event.x - c * t_off, event.t + t_off));
139 past.push((event.x + c * t_off, event.t - t_off));
141 past.push((event.x - c * t_off, event.t - t_off));
142 }
143
144 (future, past)
145}
146
147pub fn is_timelike(event_a: &SpacetimeEvent, event_b: &SpacetimeEvent, c: f64) -> bool {
149 event_a.interval_sq(event_b, c) > 0.0
150}
151
152pub fn is_spacelike(event_a: &SpacetimeEvent, event_b: &SpacetimeEvent, c: f64) -> bool {
154 event_a.interval_sq(event_b, c) < 0.0
155}
156
157pub fn is_lightlike(event_a: &SpacetimeEvent, event_b: &SpacetimeEvent, c: f64) -> bool {
159 event_a.interval_sq(event_b, c).abs() < 1e-10
160}
161
162pub fn proper_time_along_worldline(worldline: &Worldline, c: f64) -> f64 {
165 let mut tau = 0.0;
166 for i in 1..worldline.events.len() {
167 let (t1, x1) = worldline.events[i - 1];
168 let (t2, x2) = worldline.events[i];
169 let dt = t2 - t1;
170 let dx = x2 - x1;
171 let ds2 = c * c * dt * dt - dx * dx;
172 if ds2 > 0.0 {
173 tau += ds2.sqrt() / c;
174 }
175 }
176 tau
177}
178
179pub fn boost_diagram(diagram: &MinkowskiDiagram, velocity: f64, c: f64) -> MinkowskiDiagram {
181 let gamma = lorentz::lorentz_factor(velocity, c);
182 let beta = velocity / c;
183
184 let transform = |t: f64, x: f64| -> (f64, f64) {
185 let t_new = gamma * (t - beta * x / c);
186 let x_new = gamma * (x - velocity * t);
187 (t_new, x_new)
188 };
189
190 let mut new_diagram = MinkowskiDiagram::new();
191
192 for event in &diagram.events {
193 let (t_new, x_new) = transform(event.t, event.x);
194 new_diagram.add_event(SpacetimeEvent::new(t_new, x_new, &event.label));
195 }
196
197 for wl in &diagram.worldlines {
198 let mut new_wl = Worldline::new(&wl.label, wl.color);
199 for &(t, x) in &wl.events {
200 let (t_new, x_new) = transform(t, x);
201 new_wl.add_event(t_new, x_new);
202 }
203 new_diagram.add_worldline(new_wl);
204 }
205
206 new_diagram
207}
208
209#[derive(Debug, Clone)]
211pub struct PenroseDiagram {
212 pub events: Vec<(f64, f64)>, pub worldlines: Vec<Vec<(f64, f64)>>,
214}
215
216impl PenroseDiagram {
217 pub fn new() -> Self {
218 Self {
219 events: Vec::new(),
220 worldlines: Vec::new(),
221 }
222 }
223
224 pub fn add_event(&mut self, t: f64, r: f64) {
226 let (pt, px) = penrose_transform(t, r);
227 self.events.push((pt, px));
228 }
229
230 pub fn add_worldline(&mut self, points: &[(f64, f64)]) {
232 let transformed: Vec<(f64, f64)> = points.iter()
233 .map(|&(t, r)| penrose_transform(t, r))
234 .collect();
235 self.worldlines.push(transformed);
236 }
237
238 pub fn boundary(&self, n_points: usize) -> Vec<(f64, f64)> {
240 let pi = std::f64::consts::PI;
241 let mut boundary = Vec::new();
242 let dp = pi / n_points as f64;
243
244 for i in 0..=n_points {
246 let T = -pi / 2.0 + dp * i as f64;
247 let X = pi / 2.0 - T.abs();
248 boundary.push((T, X));
249 }
250 for i in (0..=n_points).rev() {
252 let T = -pi / 2.0 + dp * i as f64;
253 let X = -(pi / 2.0 - T.abs());
254 boundary.push((T, X));
255 }
256
257 boundary
258 }
259}
260
261pub fn penrose_transform(t: f64, r: f64) -> (f64, f64) {
266 let u = (t + r).atan();
267 let v = (t - r).atan();
268 let big_t = u + v;
269 let big_x = u - v;
270 (big_t, big_x)
271}
272
273#[derive(Debug, Clone)]
275pub struct SpacetimeRenderer {
276 pub c: f64,
277 pub show_light_cones: bool,
278 pub show_simultaneity: bool,
279 pub x_range: (f32, f32),
280 pub t_range: (f32, f32),
281 pub grid_color: Vec4,
282 pub light_cone_color: Vec4,
283}
284
285impl SpacetimeRenderer {
286 pub fn new(c: f64) -> Self {
287 Self {
288 c,
289 show_light_cones: true,
290 show_simultaneity: true,
291 x_range: (-10.0, 10.0),
292 t_range: (-10.0, 10.0),
293 grid_color: Vec4::new(0.2, 0.2, 0.3, 1.0),
294 light_cone_color: Vec4::new(1.0, 1.0, 0.0, 0.5),
295 }
296 }
297
298 pub fn render_diagram(
300 &self,
301 diagram: &MinkowskiDiagram,
302 ) -> (Vec<(Vec2, Vec2, Vec4)>, Vec<(Vec2, Vec4)>) {
303 let mut lines = Vec::new();
304 let mut points = Vec::new();
305
306 for event in &diagram.events {
308 points.push((event.as_point(), Vec4::new(1.0, 0.3, 0.3, 1.0)));
309
310 if self.show_light_cones {
312 let p = event.as_point();
313 let extent = 5.0;
314 let c = self.c as f32;
315 lines.push((p, p + Vec2::new(extent, extent / c), self.light_cone_color));
317 lines.push((p, p + Vec2::new(-extent, extent / c), self.light_cone_color));
318 lines.push((p, p + Vec2::new(extent, -extent / c), self.light_cone_color));
320 lines.push((p, p + Vec2::new(-extent, -extent / c), self.light_cone_color));
321 }
322 }
323
324 for wl in &diagram.worldlines {
326 let pts = wl.as_points();
327 for i in 1..pts.len() {
328 lines.push((pts[i - 1], pts[i], wl.color));
329 }
330 }
331
332 (lines, points)
333 }
334
335 pub fn simultaneity_lines(
337 &self,
338 velocity: f64,
339 n_lines: usize,
340 ) -> Vec<(Vec2, Vec2)> {
341 let beta = velocity / self.c;
342 let mut lines = Vec::new();
343 let dt = (self.t_range.1 - self.t_range.0) / n_lines as f32;
344
345 for i in 0..n_lines {
346 let t0 = self.t_range.0 + dt * i as f32;
347 let x1 = self.x_range.0;
349 let x2 = self.x_range.1;
350 let t1 = t0 + beta as f32 * x1;
351 let t2 = t0 + beta as f32 * x2;
352 lines.push((Vec2::new(x1, t1), Vec2::new(x2, t2)));
353 }
354 lines
355 }
356}
357
358pub fn causal_structure(events: &[SpacetimeEvent], c: f64) -> Vec<Vec<bool>> {
361 let n = events.len();
362 let mut matrix = vec![vec![false; n]; n];
363
364 for i in 0..n {
365 for j in 0..n {
366 if i == j {
367 matrix[i][j] = true;
368 continue;
369 }
370 let dt = events[j].t - events[i].t;
371 if dt < 0.0 {
372 continue; }
374 let dx = (events[j].x - events[i].x).abs();
375 if c * dt >= dx {
377 matrix[i][j] = true;
378 }
379 }
380 }
381
382 matrix
383}
384
385pub fn invariant_interval(a: &SpacetimeEvent, b: &SpacetimeEvent, c: f64) -> f64 {
387 a.interval_sq(b, c)
388}
389
390pub fn light_cone_grid(
392 x_range: (f64, f64),
393 t_range: (f64, f64),
394 c: f64,
395 nx: usize,
396 nt: usize,
397) -> Vec<(SpacetimeEvent, Vec<(f64, f64)>, Vec<(f64, f64)>)> {
398 let dx = (x_range.1 - x_range.0) / nx.max(1) as f64;
399 let dt = (t_range.1 - t_range.0) / nt.max(1) as f64;
400 let mut grid = Vec::new();
401
402 for it in 0..=nt {
403 for ix in 0..=nx {
404 let x = x_range.0 + dx * ix as f64;
405 let t = t_range.0 + dt * it as f64;
406 let event = SpacetimeEvent::new(t, x, "");
407 let (future, past) = light_cone(&event, c);
408 grid.push((event, future, past));
409 }
410 }
411 grid
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 const C: f64 = 1.0; #[test]
421 fn test_timelike_separation() {
422 let a = SpacetimeEvent::new(0.0, 0.0, "A");
423 let b = SpacetimeEvent::new(2.0, 1.0, "B");
424 assert!(is_timelike(&a, &b, C));
426 assert!(!is_spacelike(&a, &b, C));
427 }
428
429 #[test]
430 fn test_spacelike_separation() {
431 let a = SpacetimeEvent::new(0.0, 0.0, "A");
432 let b = SpacetimeEvent::new(1.0, 3.0, "B");
433 assert!(is_spacelike(&a, &b, C));
435 assert!(!is_timelike(&a, &b, C));
436 }
437
438 #[test]
439 fn test_lightlike_separation() {
440 let a = SpacetimeEvent::new(0.0, 0.0, "A");
441 let b = SpacetimeEvent::new(5.0, 5.0, "B");
442 assert!(is_lightlike(&a, &b, C));
444 }
445
446 #[test]
447 fn test_light_cone_structure() {
448 let event = SpacetimeEvent::new(0.0, 0.0, "O");
449 let (future, past) = light_cone(&event, C);
450 assert!(!future.is_empty());
451 assert!(!past.is_empty());
452 assert!((future[0].0).abs() < 1e-10);
454 assert!((future[0].1).abs() < 1e-10);
455 }
456
457 #[test]
458 fn test_proper_time_at_rest() {
459 let wl = Worldline::at_rest(0.0, (0.0, 10.0), 100, "rest");
460 let tau = proper_time_along_worldline(&wl, C);
461 assert!((tau - 10.0).abs() < 0.1, "Proper time at rest: {}", tau);
463 }
464
465 #[test]
466 fn test_proper_time_moving() {
467 let v = 0.866; let wl = Worldline::constant_velocity(0.0, v, (0.0, 10.0), 1000, "moving");
469 let tau = proper_time_along_worldline(&wl, C);
470 assert!((tau - 5.0).abs() < 0.1, "Moving proper time: {}", tau);
472 }
473
474 #[test]
475 fn test_boost_diagram_preserves_interval() {
476 let mut diagram = MinkowskiDiagram::new();
477 let a = SpacetimeEvent::new(0.0, 0.0, "A");
478 let b = SpacetimeEvent::new(3.0, 1.0, "B");
479 let interval_before = a.interval_sq(&b, C);
480 diagram.add_event(a);
481 diagram.add_event(b);
482
483 let boosted = boost_diagram(&diagram, 0.5, C);
484 let interval_after = boosted.events[0].interval_sq(&boosted.events[1], C);
485
486 assert!(
487 (interval_before - interval_after).abs() < 1e-6,
488 "Interval not preserved: {} vs {}",
489 interval_before, interval_after
490 );
491 }
492
493 #[test]
494 fn test_causal_structure() {
495 let events = vec![
496 SpacetimeEvent::new(0.0, 0.0, "A"),
497 SpacetimeEvent::new(1.0, 0.5, "B"), SpacetimeEvent::new(0.5, 3.0, "C"), ];
500 let matrix = causal_structure(&events, C);
501
502 assert!(matrix[0][1], "A should causally influence B");
504 assert!(!matrix[0][2], "A should not influence C (spacelike)");
506 assert!(matrix[0][0]);
508 assert!(matrix[1][1]);
509 }
510
511 #[test]
512 fn test_causal_structure_no_backward() {
513 let events = vec![
514 SpacetimeEvent::new(5.0, 0.0, "A"),
515 SpacetimeEvent::new(0.0, 0.0, "B"), ];
517 let matrix = causal_structure(&events, C);
518 assert!(!matrix[0][1]);
520 assert!(matrix[1][0]);
522 }
523
524 #[test]
525 fn test_penrose_transform_origin() {
526 let (t, x) = penrose_transform(0.0, 0.0);
527 assert!(t.abs() < 1e-10);
528 assert!(x.abs() < 1e-10);
529 }
530
531 #[test]
532 fn test_penrose_transform_finite() {
533 let (t, x) = penrose_transform(1e10, 0.0);
535 assert!(t.is_finite());
536 assert!(x.is_finite());
537 assert!(t.abs() < std::f64::consts::PI);
538 }
539
540 #[test]
541 fn test_invariant_interval() {
542 let a = SpacetimeEvent::new(0.0, 0.0, "A");
543 let b = SpacetimeEvent::new(3.0, 4.0, "B");
544 let ds2 = invariant_interval(&a, &b, C);
545 assert!((ds2 - (-7.0)).abs() < 1e-10);
547 }
548
549 #[test]
550 fn test_spacetime_renderer() {
551 let renderer = SpacetimeRenderer::new(C);
552 let mut diagram = MinkowskiDiagram::new();
553 diagram.add_event(SpacetimeEvent::new(0.0, 0.0, "O"));
554 diagram.add_worldline(Worldline::at_rest(0.0, (-5.0, 5.0), 10, "static"));
555
556 let (lines, points) = renderer.render_diagram(&diagram);
557 assert!(!points.is_empty());
558 assert!(!lines.is_empty()); }
560
561 #[test]
562 fn test_penrose_diagram() {
563 let mut pd = PenroseDiagram::new();
564 pd.add_event(0.0, 0.0);
565 pd.add_event(5.0, 3.0);
566 assert_eq!(pd.events.len(), 2);
567
568 let boundary = pd.boundary(10);
569 assert!(!boundary.is_empty());
570 }
571
572 #[test]
573 fn test_worldline_velocity() {
574 let wl = Worldline::constant_velocity(0.0, 0.5, (0.0, 10.0), 100, "v=0.5");
575 let v = wl.velocity_at(50);
576 assert!((v - 0.5).abs() < 0.01, "Velocity: {}", v);
577 }
578}