use serde::{Deserialize, Serialize};
use tracing::{instrument, warn};
use crate::error::{MimamsaError, ensure_finite, require_all_finite, require_finite};
pub use crate::constants::{C, C2};
#[instrument(level = "trace")]
#[inline]
pub fn lorentz_factor(v: f64) -> Result<f64, MimamsaError> {
require_finite(v, "lorentz_factor")?;
let beta = v / C;
let beta2 = beta * beta;
if beta2 >= 1.0 {
warn!(v, beta = beta, "superluminal velocity rejected");
return Err(MimamsaError::Superluminal { v });
}
ensure_finite(1.0 / (1.0 - beta2).sqrt(), "lorentz_factor")
}
#[inline]
pub fn beta(v: f64) -> Result<f64, MimamsaError> {
require_finite(v, "beta")?;
Ok(v / C)
}
#[instrument(level = "trace")]
#[inline]
pub fn time_dilation(proper_time: f64, v: f64) -> Result<f64, MimamsaError> {
require_all_finite(&[proper_time, v], "time_dilation")?;
ensure_finite(proper_time * lorentz_factor(v)?, "time_dilation")
}
#[instrument(level = "trace")]
#[inline]
pub fn length_contraction(proper_length: f64, v: f64) -> Result<f64, MimamsaError> {
require_all_finite(&[proper_length, v], "length_contraction")?;
ensure_finite(proper_length / lorentz_factor(v)?, "length_contraction")
}
#[instrument(level = "trace")]
#[inline]
pub fn kinetic_energy(mass_kg: f64, v: f64) -> Result<f64, MimamsaError> {
require_all_finite(&[mass_kg, v], "kinetic_energy")?;
let gamma = lorentz_factor(v)?;
ensure_finite((gamma - 1.0) * mass_kg * C2, "kinetic_energy")
}
#[instrument(level = "trace")]
#[inline]
pub fn total_energy(mass_kg: f64, v: f64) -> Result<f64, MimamsaError> {
require_all_finite(&[mass_kg, v], "total_energy")?;
ensure_finite(lorentz_factor(v)? * mass_kg * C2, "total_energy")
}
#[inline]
pub fn rest_energy(mass_kg: f64) -> Result<f64, MimamsaError> {
require_finite(mass_kg, "rest_energy")?;
ensure_finite(mass_kg * C2, "rest_energy")
}
#[instrument(level = "trace")]
#[inline]
pub fn relativistic_momentum(mass_kg: f64, v: f64) -> Result<f64, MimamsaError> {
require_all_finite(&[mass_kg, v], "relativistic_momentum")?;
ensure_finite(lorentz_factor(v)? * mass_kg * v, "relativistic_momentum")
}
#[instrument(level = "trace")]
#[inline]
pub fn velocity_addition(u: f64, v: f64) -> Result<f64, MimamsaError> {
require_all_finite(&[u, v], "velocity_addition")?;
ensure_finite((u + v) / (1.0 + u * v / C2), "velocity_addition")
}
#[instrument(level = "trace")]
#[inline]
pub fn doppler_factor(v: f64) -> Result<f64, MimamsaError> {
require_finite(v, "doppler_factor")?;
let b = beta(v)?;
if b.abs() >= 1.0 {
warn!(v, beta = b, "superluminal velocity in Doppler calculation");
return Err(MimamsaError::Superluminal { v });
}
ensure_finite(((1.0 - b) / (1.0 + b)).sqrt(), "doppler_factor")
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct FourVector {
pub ct: f64,
pub x: f64,
pub y: f64,
pub z: f64,
}
impl FourVector {
pub fn new(ct: f64, x: f64, y: f64, z: f64) -> Result<Self, MimamsaError> {
require_all_finite(&[ct, x, y, z], "FourVector::new")?;
const MAX_COMPONENT: f64 = 1.34e154; for &c in &[ct, x, y, z] {
if c.abs() > MAX_COMPONENT {
warn!(ct, x, y, z, "FourVector component magnitude too large");
return Err(MimamsaError::Computation(
"FourVector component magnitude too large".to_string(),
));
}
}
Ok(Self { ct, x, y, z })
}
#[must_use]
pub fn new_unchecked(ct: f64, x: f64, y: f64, z: f64) -> Self {
Self { ct, x, y, z }
}
#[must_use]
#[inline]
pub fn invariant_interval(&self) -> f64 {
-self.ct * self.ct + self.x * self.x + self.y * self.y + self.z * self.z
}
#[must_use]
pub fn interval_type(&self) -> IntervalType {
let s2 = self.invariant_interval();
if s2 < -1e-12 {
IntervalType::Timelike
} else if s2 > 1e-12 {
IntervalType::Spacelike
} else {
IntervalType::Lightlike
}
}
#[instrument(level = "trace", skip(self))]
pub fn boost_x(&self, v: f64) -> Result<Self, MimamsaError> {
require_finite(v, "boost_x")?;
let gamma = lorentz_factor(v)?;
let b = beta(v)?;
Ok(Self {
ct: gamma * (self.ct - b * self.x),
x: gamma * (self.x - b * self.ct),
y: self.y,
z: self.z,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum IntervalType {
Timelike,
Lightlike,
Spacelike,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lorentz_factor_zero() {
let gamma = lorentz_factor(0.0).unwrap();
assert!((gamma - 1.0).abs() < 1e-12);
}
#[test]
fn test_lorentz_factor_high_v() {
let v = 0.866 * C;
let gamma = lorentz_factor(v).unwrap();
assert!((gamma - 2.0).abs() < 0.01);
}
#[test]
fn test_superluminal_rejected() {
assert!(lorentz_factor(C * 1.01).is_err());
}
#[test]
fn test_rest_energy_electron() {
let e0 = rest_energy(9.109e-31).unwrap();
let mev = e0 / 1.602e-13;
assert!((mev - 0.511).abs() < 0.001);
}
#[test]
fn test_velocity_addition_subluminal() {
let u = velocity_addition(0.9 * C, 0.9 * C).unwrap();
assert!(u < C);
assert!((u / C - 0.9945).abs() < 0.001);
}
#[test]
fn test_four_vector_lightlike() {
let photon = FourVector::new(C, C, 0.0, 0.0).unwrap();
assert_eq!(photon.interval_type(), IntervalType::Lightlike);
}
#[test]
fn test_four_vector_timelike() {
let rest = FourVector::new(C, 0.0, 0.0, 0.0).unwrap();
assert_eq!(rest.interval_type(), IntervalType::Timelike);
}
#[test]
fn test_four_vector_overflow_rejected() {
assert!(FourVector::new(1.5e154, 0.0, 0.0, 0.0).is_err());
}
#[test]
fn test_boost_preserves_interval() {
let event = FourVector::new(3.0, 2.0, 0.0, 0.0).unwrap();
let boosted = event.boost_x(0.5 * C).unwrap();
let s2_orig = event.invariant_interval();
let s2_boosted = boosted.invariant_interval();
assert!((s2_orig - s2_boosted).abs() / s2_orig.abs() < 1e-10);
}
#[test]
fn test_time_dilation_muon() {
let v = 0.994 * C;
let observed = time_dilation(2.2e-6, v).unwrap();
assert!((observed / 2.2e-6 - 9.14).abs() < 0.1);
}
#[test]
fn test_doppler_blueshift() {
let f = doppler_factor(-0.5 * C).unwrap();
assert!(f > 1.0);
}
#[test]
fn test_doppler_redshift() {
let f = doppler_factor(0.5 * C).unwrap();
assert!(f < 1.0);
}
}