use core::f64::consts::PI;
use metrology::{C, G, HBAR, K_B};
pub fn alcubierre_energy(radius: f64, wall: f64, speed: f64) -> f64 {
-speed * speed / 12.0 * (radius * radius / wall + wall / 12.0) * C * C / G
}
pub fn induced_acceleration(m: f64, a_flow: f64, r: f64) -> f64 {
4.0 * G * m * a_flow / (C * C * r)
}
pub fn quantum_inequality(tau: f64) -> f64 {
-3.0 * HBAR * C / (32.0 * PI * PI * (C * tau).powi(4))
}
pub fn casimir_energy_density(d: f64) -> f64 {
-PI * PI * HBAR * C / (720.0 * d.powi(4))
}
pub fn casimir_pressure(d: f64) -> f64 {
-PI * PI * HBAR * C / (240.0 * d.powi(4))
}
pub fn casimir_sphere_plate(radius: f64, d: f64) -> f64 {
-PI.powi(3) * HBAR * C * radius / (360.0 * d.powi(3))
}
pub fn wormhole_throat_tension(b0: f64) -> f64 {
C.powi(4) / (8.0 * PI * G * b0 * b0)
}
pub fn wormhole_edge_tension() -> f64 {
-C.powi(4) / (8.0 * G)
}
pub fn horizon_temperature(kappa: f64) -> f64 {
HBAR * kappa / (2.0 * PI * K_B)
}
pub fn unruh_temperature(a: f64) -> f64 {
horizon_temperature(a / C)
}
pub fn hawking_temperature(m: f64) -> f64 {
horizon_temperature(C.powi(3) / (4.0 * G * m))
}
pub fn gibbons_hawking_temperature(h: f64) -> f64 {
horizon_temperature(h)
}
pub fn hawking_lifetime(m: f64) -> f64 {
5120.0 * PI * G * G * m.powi(3) / (HBAR * C.powi(4))
}
pub fn zero_point_energy_density(omega_c: f64) -> f64 {
HBAR * omega_c.powi(4) / (8.0 * PI * PI * C.powi(3))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Metric {
GeneralRelativity,
PolarizableVacuum,
}
pub fn light_bending(mass: f64, b: f64, metric: Metric) -> f64 {
let x = G * mass / (C * C * b);
let second = match metric {
Metric::GeneralRelativity => 15.0 * PI / 4.0,
Metric::PolarizableVacuum => 4.0 * PI,
};
4.0 * x + second * x * x
}
pub fn pv_radiated_power_ratio(e: f64) -> f64 {
let e2 = e * e;
0.5 * (51.0 * e2 * e2 + 396.0 * e2 + 128.0) / (37.0 * e2 * e2 + 292.0 * e2 + 96.0)
}
pub fn exact_deflection(mass: f64, b: f64, metric: Metric) -> Option<f64> {
let b = b * C * C / (G * mass);
let (g, photon): (fn(f64) -> f64, f64) = match metric {
Metric::GeneralRelativity => (|u| u * u * (1.0 - 2.0 * u), 1.0 / 3.0),
Metric::PolarizableVacuum => (|u| u * u * (-4.0 * u).exp(), 0.5),
};
if b * b * g(photon) <= 1.0 {
return None;
}
let (mut lo, mut hi) = (0.0, photon);
while hi - lo > f64::EPSILON * hi {
let mid = 0.5 * (lo + hi);
if b * b * g(mid) < 1.0 { lo = mid } else { hi = mid }
}
let u0 = 0.5 * (lo + hi);
let q = |u: f64| match metric {
Metric::GeneralRelativity => (u + u0) - 2.0 * (u * u + u * u0 + u0 * u0),
Metric::PolarizableVacuum => {
let d = u - u0;
let slope = if d == 0.0 { 4.0 } else { (4.0 * d).exp_m1() / d };
(u + u0) - (4.0 * u0).exp() * slope / (b * b)
}
};
let (x, w) = geopotential::gauss_legendre(16);
let mut sum = 0.0;
let mut upper = 1.0;
for k in 1..=41 {
let lower = if k == 41 { 0.0 } else { 0.5f64.powi(k) };
let (mid, half) = (0.5 * (upper + lower), 0.5 * (upper - lower));
sum += half * x.iter().zip(&w).map(|(x, w)| w * 2.0 * u0.sqrt() / q(u0 * (1.0 - (mid + half * x).powi(2))).sqrt()).sum::<f64>();
upper = lower;
}
Some(2.0 * sum - PI)
}
pub fn multipole_deflection(field: &geopotential::Coefficients, point: [f64; 3], dir: [f64; 3], gamma: f64) -> [f64; 3] {
let dot = |a: [f64; 3], b: [f64; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
let n = dir.map(|x| x / dot(dir, dir).sqrt());
let along = dot(point, n);
let closest: [f64; 3] = core::array::from_fn(|i| point[i] - along * n[i]);
let b = dot(closest, closest).sqrt();
let (x, w) = geopotential::gauss_legendre(128);
let mut sum = [0.0; 3];
for (x, w) in x.iter().zip(&w) {
let theta = core::f64::consts::FRAC_PI_2 * x;
let s = b * theta.tan();
let p: [f64; 3] = core::array::from_fn(|i| closest[i] + s * n[i]);
let r = dot(p, p).sqrt();
let (lat, lon) = ((p[2] / r).asin(), p[1].atan2(p[0]));
let [north, east, up] = field.partials(r, lat, lon).acceleration_neu(r, lat);
let ((sf, cf), (sl, cl)) = (lat.sin_cos(), lon.sin_cos());
let g = [-sf * cl * north - sl * east + cf * cl * up, -sf * sl * north + cl * east + cf * sl * up, cf * north + sf * up];
let gn = dot(g, n);
let weight = core::f64::consts::FRAC_PI_2 * w * b / (theta.cos() * theta.cos());
for i in 0..3 {
sum[i] += weight * (g[i] - gn * n[i]);
}
}
sum.map(|x| (1.0 + gamma) * x / (C * C))
}
pub fn shadow_diameter(mass: f64, distance: f64) -> f64 {
6.0 * 3f64.sqrt() * G * mass / (C * C * distance)
}
pub fn shadow_deviation(diameter: f64, theta_g: f64) -> f64 {
diameter / (6.0 * 3f64.sqrt() * theta_g) - 1.0
}
pub fn kerr_pole_on_shadow(a: f64) -> f64 {
let mut r = 3.0;
for _ in 0..100 {
let step = (r * r * r - 3.0 * r * r + a * a * r + a * a) / (3.0 * r * r - 6.0 * r + a * a);
r -= step;
if step.abs() <= 1e-16 * r {
break;
}
}
(8.0 * r * r * r / (r * r - 1.0)).sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
use metrology::PLANCK_LENGTH;
fn close(a: f64, b: f64, rel: f64) -> bool {
(a / b - 1.0).abs() < rel
}
#[test]
fn warp_bubbles_and_quantum_inequalities() {
assert!(close(alcubierre_energy(100.0, 100.0 * PLANCK_LENGTH, 1.0), -6.94e62, 5e-3));
assert!(close(alcubierre_energy(100.0, 1.0, 1.0) / 1.989e30, -0.564, 5e-3));
assert!(close(quantum_inequality(1e-9), -3.72e-26, 5e-3));
assert!(close(quantum_inequality(1e-15), -3.72e-2, 5e-3));
}
#[test]
fn casimir_closed_forms() {
assert!(close(casimir_pressure(1e-6), -1.300e-3, 1e-3));
let d = 1e-6;
let energy = |d: f64| casimir_energy_density(d) * d;
let h = 1e-12;
assert!(close(-(energy(d + h) - energy(d - h)) / (2.0 * h), casimir_pressure(d), 1e-6));
assert!(close(casimir_sphere_plate(1e-4, d), 2.0 * PI * 1e-4 * energy(d), 1e-12));
}
#[test]
fn wormholes() {
assert!(close(wormhole_throat_tension(10.0), 4.82e40, 5e-3));
assert!(close(wormhole_edge_tension(), -1.513e43, 1e-3));
}
#[test]
fn horizons() {
assert!(close(unruh_temperature(1.0), 4.055e-21, 1e-3));
assert!(close(1.0 / unruh_temperature(1.0), 2.466e20, 1e-3));
let sun = metrology::SOLAR_MASS;
assert!(close(hawking_temperature(sun), 6.17e-8, 2e-3));
assert!(close(hawking_lifetime(sun) / 3.155_76e7, 2.095e67, 3e-3));
assert!(close(gibbons_hawking_temperature(67.4e3 / 3.085_677_581e22), 2.655e-30, 2e-3));
let compton = metrology::M_E * C * C / HBAR;
assert!(close(zero_point_energy_density(compton), 1.801e22, 2e-3));
}
#[test]
fn schwinger_is_a_boltzmann_factor_at_twice_the_unruh_temperature() {
use crate::strong_field::{E_SCHWINGER, schwinger_exponent};
let (m_e, e) = (metrology::M_E, metrology::E_CHARGE);
for f in [1.0, 0.1, 1e-3] {
let field = f * E_SCHWINGER;
let t_u = unruh_temperature(e * field / m_e);
assert!(close(schwinger_exponent(field), m_e * C * C / (2.0 * K_B * t_u), 1e-12));
}
assert!(close(unruh_temperature(e * E_SCHWINGER / m_e), 9.438e8, 1e-3));
}
#[test]
fn polarizable_vacuum_against_general_relativity() {
let (sun, limb) = (metrology::SOLAR_MASS, 6.957e8);
let arcsec = 180.0 / PI * 3600.0;
let gr = light_bending(sun, limb, Metric::GeneralRelativity);
assert!((gr * arcsec - 1.7512).abs() < 1e-4);
let diff = light_bending(sun, limb, Metric::PolarizableVacuum) - gr;
assert!(close(diff * arcsec * 1e6, 0.730, 5e-3));
}
#[test]
fn forward_antigravity() {
assert!((G / (C * C) / 7.426e-28 - 1.0).abs() < 1e-3);
assert!((induced_acceleration(1e4, 1e4, 1.0) / 2.97046e-19 - 1.0).abs() < 1e-5);
assert!((9.8 / induced_acceleration(1.0, 1e6, 1.0) / 3.29915e21 - 1.0).abs() < 1e-5);
}
#[test]
fn the_shadow_of_sgr_a_star() {
let uas = PI / 180.0 / 3600.0 * 1e-6;
let pc = metrology::PARSEC;
let vlti = shadow_diameter(4.297e6 * metrology::SOLAR_MASS, 8277.0 * pc) / uas;
let keck = shadow_diameter(3.951e6 * metrology::SOLAR_MASS, 7935.0 * pc) / uas;
assert!((vlti / (6.0 * 3f64.sqrt() * 5.125) - 1.0).abs() < 1e-3 && (keck / (6.0 * 3f64.sqrt() * 4.92) - 1.0).abs() < 2e-3);
assert!((vlti - 53.3).abs() < 0.05 && (keck - 51.1).abs() < 0.1, "{vlti} {keck}");
assert!((shadow_deviation(48.9, 5.125) + 0.08).abs() < 0.005 && (shadow_deviation(48.9, 4.92) + 0.04).abs() < 0.005);
}
#[test]
fn a_kerr_shadow_seen_from_the_pole() {
assert!(close(kerr_pole_on_shadow(0.0), 27f64.sqrt(), 1e-15) && close(kerr_pole_on_shadow(1.0), 2.0 * (1.0 + 2f64.sqrt()), 1e-15));
for a in [0.1, 0.5, 0.94, -0.7] {
let cubic = |r: f64| r * r * r - 3.0 * r * r + a * a * r + a * a;
let (mut lo, mut hi) = (1.0 + 2f64.sqrt(), 3.0);
for _ in 0..200 {
let mid = 0.5 * (lo + hi);
if cubic(mid) > 0.0 { hi = mid } else { lo = mid }
}
let r = 0.5 * (lo + hi);
let eta = r.powi(3) * (4.0 * a * a - r * (r - 3.0).powi(2)) / (a * a * (r - 1.0).powi(2));
let shadow = kerr_pole_on_shadow(a);
assert!(close(shadow, (eta + a * a).sqrt(), 1e-13), "a = {a}");
assert!(shadow < 27f64.sqrt() && shadow > 2.0 * (1.0 + 2f64.sqrt()));
}
assert!((kerr_pole_on_shadow(1.0) / 27f64.sqrt() - 1.0 + 0.0708).abs() < 1e-3);
}
#[test]
fn exact_deflection_against_darwin_bozza_and_the_series() {
let m = metrology::SOLAR_MASS;
let rg = G * m / (C * C);
let at = |b: f64| exact_deflection(m, b * rg, Metric::GeneralRelativity).unwrap();
for r0 in [3.05f64, 3.5, 4.0, 6.0, 10.0, 100.0] {
let q = ((r0 - 2.0) * (r0 + 6.0)).sqrt();
let k2 = (q - r0 + 6.0) / (2.0 * q);
let s2 = (q + 2.0 - r0) / (q + 6.0 - r0);
let (big_k, _) = geopotential::special::complete_elliptic(k2);
let f = geopotential::special::elliptic_f(s2.sqrt().asin(), k2);
let darwin = 4.0 * (r0 / q).sqrt() * (big_k - f) - PI;
let b = (r0.powi(3) / (r0 - 2.0)).sqrt();
assert!((at(b) - darwin).abs() < 2e-13, "r₀ = {r0}: {} vs {darwin}", at(b));
}
let bbar = (216.0 * (7.0 - 4.0 * 3f64.sqrt())).ln() - PI;
assert!((bbar + 0.4002).abs() < 5e-5);
for (eps, gap) in [(1e-2, 2.5e-2), (1e-3, 3.1e-3), (1e-4, 3.8e-4)] {
let d = at(27f64.sqrt() * (1.0 + eps)) + eps.ln() - bbar;
assert!((d / gap - 1.0).abs() < 0.03, "{eps}: {d:e}");
}
let h = 1e-3;
let series = 4.0 * h + (-4.0 + 15.0 * PI / 4.0) * h * h + (122.0 / 3.0 - 7.5 * PI) * h.powi(3) + (-130.0 + 3465.0 * PI / 64.0) * h.powi(4)
+ (778.3 - 3465.0 * PI / 16.0) * h.powi(5);
assert!((at((1e9f64 / 998.0).sqrt()) - series).abs() < 1e-15, "{:e}", at((1e9f64 / 998.0).sqrt()) - series);
let b = 2e4;
let rest = at(b) - light_bending(m, b * rg, Metric::GeneralRelativity);
assert!((rest / (128.0 / 3.0 / b.powi(3)) - 1.0).abs() < 2e-3, "{rest:e}");
assert!(((rest / at(b)) / 2.67e-8 - 1.0).abs() < 2e-3);
assert!(exact_deflection(m, 27f64.sqrt() * rg * (1.0 - 1e-12), Metric::GeneralRelativity).is_none());
}
#[test]
fn exact_deflection_in_the_polarizable_vacuum() {
let m = metrology::SOLAR_MASS;
let rg = G * m / (C * C);
let b = 2e4;
let pv = exact_deflection(m, b * rg, Metric::PolarizableVacuum).unwrap();
assert!((pv / light_bending(m, b * rg, Metric::PolarizableVacuum) - 1.0).abs() < 1e-7);
let critical = 2.0 * core::f64::consts::E;
assert!(exact_deflection(m, critical * rg * (1.0 - 1e-9), Metric::PolarizableVacuum).is_none());
assert!(exact_deflection(m, critical * rg * (1.0 + 1e-6), Metric::PolarizableVacuum).unwrap() > 10.0);
let gr = exact_deflection(m, 10.0 * rg, Metric::GeneralRelativity).unwrap();
assert!(exact_deflection(m, 10.0 * rg, Metric::PolarizableVacuum).unwrap() > gr);
let at = |b: f64, metric| exact_deflection(m, b * rg, metric).unwrap();
for (b, gr, pv) in [(6.0, 1.719388310, 1.999746113), (10.0, 0.590395788, 0.610437437)] {
assert!((at(b, Metric::GeneralRelativity) - gr).abs() < 1e-9 && (at(b, Metric::PolarizableVacuum) - pv).abs() < 1e-9, "b = {b}");
}
assert!((at(27f64.sqrt() * 1.001, Metric::GeneralRelativity) - 6.5106448).abs() < 1e-7);
assert!((at(critical * 1.001, Metric::PolarizableVacuum) - 6.4454619).abs() < 1e-7);
let eps = 1e-6;
assert!((at(critical * (1.0 + eps), Metric::PolarizableVacuum) + eps.ln() + 0.46581756).abs() < 1e-5);
}
#[test]
fn jupiters_oblateness_in_the_deflection() {
let (gm, radius, j2) = (1.266_865_3e17, 7.1492e7, 14_696.572e-6);
let mut jupiter = geopotential::Coefficients::zeros(gm, radius, 2);
jupiter.set(0, 0, 1.0, 0.0);
jupiter.set(2, 0, -j2 / 5f64.sqrt(), 0.0);
let mas = PI / 180.0 / 3600.0 * 1e-3;
for (b, lon) in [(radius, 0.0), (2.0 * radius, 0.7), (radius * 1.1, -2.0)] {
let point = [b * f64::cos(lon), b * f64::sin(lon), 0.0];
let dir = [-f64::sin(lon), f64::cos(lon), 0.0];
let alpha = multipole_deflection(&jupiter, point, dir, 1.0);
let want = 4.0 * gm / (C * C * b) * (1.0 + j2 * radius * radius / (b * b));
let size = (alpha[0] * alpha[0] + alpha[1] * alpha[1] + alpha[2] * alpha[2]).sqrt();
assert!(close(size, want, 1e-13) && alpha[2].abs() < 1e-15 * size, "b = {b}");
assert!(close(-(alpha[0] * point[0] + alpha[1] * point[1]) / (b * size), 1.0, 1e-13));
}
let limb = 4.0 * gm / (C * C * radius);
assert!((limb / mas - 16.27).abs() < 0.005 && (limb * j2 / mas * 1e3 - 239.0).abs() < 0.5);
let mut point = geopotential::Coefficients::zeros(gm, radius, 0);
point.set(0, 0, 1.0, 0.0);
let alpha = multipole_deflection(&point, [1e8, 2e8, -0.5e8], [0.3, -0.2, 0.9], 1.0);
let n = [0.3, -0.2, 0.9].map(|x: f64| x / 0.94f64.sqrt());
let p = [1e8, 2e8, -0.5e8];
let along = p[0] * n[0] + p[1] * n[1] + p[2] * n[2];
let b = (0..3).map(|i| (p[i] - along * n[i]).powi(2)).sum::<f64>().sqrt();
let size = (alpha[0] * alpha[0] + alpha[1] * alpha[1] + alpha[2] * alpha[2]).sqrt();
assert!(close(size, 4.0 * gm / (C * C * b), 1e-13));
let half = multipole_deflection(&point, [1e8, 2e8, -0.5e8], [0.3, -0.2, 0.9], 0.0);
assert!(close(half[0], alpha[0] / 2.0, 1e-14));
}
}