#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpacetimeEvent {
pub t: f64,
pub x: f64,
pub y: f64,
pub z: f64,
}
impl SpacetimeEvent {
pub const fn new(t: f64, x: f64, y: f64, z: f64) -> Self {
Self { t, x, y, z }
}
}
pub type Velocity = [f64; 3];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntervalKind {
Timelike,
Lightlike,
Spacelike,
}
#[inline]
pub fn interval_squared(a: SpacetimeEvent, b: SpacetimeEvent) -> f64 {
let dt = b.t - a.t;
let dx = b.x - a.x;
let dy = b.y - a.y;
let dz = b.z - a.z;
-dt * dt + dx * dx + dy * dy + dz * dz
}
pub fn interval_kind(
a: SpacetimeEvent,
b: SpacetimeEvent,
tolerance: f64,
) -> IntervalKind {
let ds2 = interval_squared(a, b);
if ds2.abs() <= tolerance {
IntervalKind::Lightlike
} else if ds2 < 0.0 {
IntervalKind::Timelike
} else {
IntervalKind::Spacelike
}
}
pub fn causally_connected(
a: SpacetimeEvent,
b: SpacetimeEvent,
tolerance: f64,
) -> bool {
interval_kind(a, b, tolerance) != IntervalKind::Spacelike
}
pub fn proper_time_between(a: SpacetimeEvent, b: SpacetimeEvent) -> f64 {
let ds2 = interval_squared(a, b);
if ds2 > 0.0 {
f64::NAN
} else {
(-ds2).sqrt()
}
}
pub fn proper_distance_between(a: SpacetimeEvent, b: SpacetimeEvent) -> f64 {
let ds2 = interval_squared(a, b);
if ds2 < 0.0 {
f64::NAN
} else {
ds2.sqrt()
}
}
pub fn proper_time_along(worldline: &[SpacetimeEvent]) -> Result<f64, &'static str> {
if worldline.len() < 2 {
return Err("proper_time_along requires at least two events");
}
let mut total = 0.0;
for window in worldline.windows(2) {
let tau = proper_time_between(window[0], window[1]);
if tau.is_nan() {
return Err("Worldline segment is spacelike; not a physical worldline");
}
total += tau;
}
Ok(total)
}
#[inline]
pub fn speed(beta: Velocity) -> f64 {
(beta[0] * beta[0] + beta[1] * beta[1] + beta[2] * beta[2]).sqrt()
}
pub fn gamma_from_speed(beta: f64) -> f64 {
let b = beta.abs();
if b > 1.0 {
f64::NAN
} else if b == 1.0 {
f64::INFINITY
} else {
1.0 / (1.0 - b * b).sqrt()
}
}
#[inline]
pub fn gamma(beta: Velocity) -> f64 {
gamma_from_speed(speed(beta))
}
#[inline]
pub fn rapidity(beta: f64) -> f64 {
beta.atanh()
}
#[inline]
pub fn add_velocities(u: f64, v: f64) -> f64 {
(u + v) / (1.0 + u * v)
}
pub fn boost(event: SpacetimeEvent, beta: Velocity) -> Result<SpacetimeEvent, &'static str> {
let [bx, by, bz] = beta;
let b2 = bx * bx + by * by + bz * bz;
if b2 >= 1.0 {
return Err("Lorentz boost requires |β| < 1");
}
if b2 == 0.0 {
return Ok(event);
}
let g = 1.0 / (1.0 - b2).sqrt();
let b_dot_x = bx * event.x + by * event.y + bz * event.z;
let k = (g - 1.0) / b2;
let coef = k * b_dot_x - g * event.t;
Ok(SpacetimeEvent {
t: g * (event.t - b_dot_x),
x: event.x + coef * bx,
y: event.y + coef * by,
z: event.z + coef * bz,
})
}
pub fn doppler_factor(beta: f64) -> f64 {
if beta.abs() >= 1.0 {
f64::NAN
} else {
((1.0 - beta) / (1.0 + beta)).sqrt()
}
}