use super::FixedPoint;
use super::FixedVector;
use super::FixedMatrix;
use super::tensor::Tensor;
use super::linalg::{
compute_product, compute_tier_dot_raw, downscale_to_storage, round_to_storage,
sincos_at_compute_tier, upscale_to_compute, ComputeStorage,
};
use super::derived::inverse;
use super::compute_matrix::{compute_lu_decompose, ComputeMatrix};
use super::ode::{OdeSystem, rk4_step_compute, state_to_compute};
use crate::fixed_point::universal::fasc::stack_evaluator::BinaryStorage;
use crate::fixed_point::universal::fasc::stack_evaluator::compute::{
compute_add, compute_checked_add, compute_checked_divide, compute_divide, compute_halve,
compute_mul_div_int, compute_multiply, compute_negate, compute_subtract,
};
use crate::fixed_point::core_types::errors::OverflowDetected;
pub fn differentiation_step() -> FixedPoint {
#[cfg(table_format = "q32_32")]
{ FixedPoint::from_raw(1i64 << (32 - 11)) }
#[cfg(table_format = "q16_16")]
{ FixedPoint::from_raw(1i32 << (crate::fixed_point::frac_config::FRAC_BITS - step_exponent())) }
#[cfg(table_format = "q64_64")]
{ FixedPoint::from_raw(1i128 << (64 - 21)) }
#[cfg(table_format = "q128_128")]
{
use crate::fixed_point::I256;
FixedPoint::from_raw(I256::from_i128(1) << (128usize - 43))
}
#[cfg(table_format = "q256_256")]
{
use crate::fixed_point::I512;
FixedPoint::from_raw(I512::from_i128(1) << (256usize - 85))
}
}
#[cfg(table_format = "q16_16")]
#[inline]
fn step_exponent() -> u32 {
(crate::fixed_point::frac_config::FRAC_BITS + 1) / 3
}
pub trait MetricProvider {
fn dimension(&self) -> usize;
fn metric(&self, p: &FixedVector) -> FixedMatrix;
fn metric_inverse(&self, p: &FixedVector) -> Result<FixedMatrix, OverflowDetected> {
inverse(&self.metric(p))
}
fn christoffel_closed_form(&self, _p: &FixedVector) -> Option<Tensor> {
None
}
#[doc(hidden)]
fn christoffel_closed_form_compute(&self, _p: &FixedVector) -> Option<ComputeChristoffel> {
None
}
fn scalar_curvature_closed_form(&self, _p: &FixedVector) -> Option<FixedPoint> {
None
}
}
#[doc(hidden)]
#[derive(Clone, Debug)]
pub struct ComputeChristoffel {
data: Vec<ComputeStorage>,
}
fn central_difference(a: ComputeStorage, b: ComputeStorage) -> Result<ComputeStorage, OverflowDetected> {
let mut d = checked_sub(a, b)?;
for _ in 1..differentiation_exponent() {
d = compute_checked_add(d, d)?;
}
Ok(d)
}
fn differentiation_exponent() -> u32 {
#[cfg(table_format = "q32_32")]
{ 11 }
#[cfg(table_format = "q16_16")]
{ step_exponent() }
#[cfg(table_format = "q64_64")]
{ 21 }
#[cfg(table_format = "q128_128")]
{ 43 }
#[cfg(table_format = "q256_256")]
{ 85 }
}
fn compute_zero() -> ComputeStorage {
upscale_to_compute(FixedPoint::ZERO.raw())
}
fn compute_one() -> ComputeStorage {
upscale_to_compute(FixedPoint::one().raw())
}
fn round_tensor(shape: &[usize], data: &[ComputeStorage]) -> Result<Tensor, OverflowDetected> {
let values = data.iter()
.map(|&c| downscale_to_storage(c).map(FixedPoint::from_raw))
.collect::<Result<Vec<_>, _>>()?;
Ok(Tensor::from_data(shape, &values))
}
fn metric_inverse_compute(
provider: &dyn MetricProvider,
p: &FixedVector,
) -> Result<Vec<ComputeStorage>, OverflowDetected> {
let n = provider.dimension();
let given = provider.metric_inverse(p)?;
let given_c: Vec<ComputeStorage> = (0..n * n)
.map(|e| upscale_to_compute(given.get(e / n, e % n).raw()))
.collect();
let wide = compute_lu_decompose(&ComputeMatrix::from_fixed_matrix(&provider.metric(p)))
.and_then(|lu| lu.inverse());
let Ok(wide) = wide else { return Ok(given_c) };
let unit = compute_one_unit();
let mut out = Vec::with_capacity(n * n);
for e in 0..n * n {
let c = wide.get(e / n, e % n);
let diff = checked_sub(c, given_c[e])?;
let diff = if diff < compute_zero() { compute_negate(diff) } else { diff };
if diff > unit { return Ok(given_c); }
out.push(c);
}
Ok(out)
}
fn compute_one_unit() -> ComputeStorage {
upscale_to_compute(FixedPoint::from_raw(unit_raw()).raw())
}
fn unit_raw() -> BinaryStorage {
#[cfg(table_format = "q16_16")]
{ 1i32 }
#[cfg(table_format = "q32_32")]
{ 1i64 }
#[cfg(table_format = "q64_64")]
{ 1i128 }
#[cfg(table_format = "q128_128")]
{ crate::fixed_point::I256::from_i128(1) }
#[cfg(table_format = "q256_256")]
{ crate::fixed_point::I512::from_i128(1) }
}
fn metric_partial_compute(
provider: &dyn MetricProvider,
p: &FixedVector,
m: usize,
) -> Result<Vec<ComputeStorage>, OverflowDetected> {
let h = differentiation_step();
let n = provider.dimension();
let mut p_plus = p.clone();
let mut p_minus = p.clone();
p_plus[m] = p_plus[m] + h;
p_minus[m] = p_minus[m] - h;
let g_plus = provider.metric(&p_plus);
let g_minus = provider.metric(&p_minus);
let mut result = Vec::with_capacity(n * n);
for i in 0..n {
for j in 0..n {
result.push(central_difference(
upscale_to_compute(g_plus.get(i, j).raw()),
upscale_to_compute(g_minus.get(i, j).raw()),
)?);
}
}
Ok(result)
}
pub fn christoffel(
provider: &dyn MetricProvider,
p: &FixedVector,
) -> Result<Tensor, OverflowDetected> {
let n = provider.dimension();
round_tensor(&[n, n, n], &christoffel_compute(provider, p)?)
}
fn christoffel_compute(
provider: &dyn MetricProvider,
p: &FixedVector,
) -> Result<Vec<ComputeStorage>, OverflowDetected> {
let n = provider.dimension();
if let Some(gamma) = provider.christoffel_closed_form_compute(p) {
return Ok(gamma.data);
}
if let Some(gamma) = provider.christoffel_closed_form(p) {
let mut out = Vec::with_capacity(n * n * n);
for k in 0..n { for i in 0..n { for j in 0..n {
out.push(upscale_to_compute(gamma.get(&[k, i, j]).raw()));
} } }
return Ok(out);
}
let g_inv = metric_inverse_compute(provider, p)?;
let dg: Vec<Vec<ComputeStorage>> = (0..n)
.map(|m| metric_partial_compute(provider, p, m))
.collect::<Result<_, _>>()?;
let mut gamma = Vec::with_capacity(n * n * n);
for k in 0..n {
for i in 0..n {
for j in 0..n {
let mut acc = compute_zero();
for l in 0..n {
let bracket = checked_sub(
compute_checked_add(dg[i][j * n + l], dg[j][l * n + i])?,
dg[l][i * n + j],
)?;
acc = compute_checked_add(acc, compute_product(g_inv[k * n + l], bracket)?)?;
}
gamma.push(compute_halve(acc));
}
}
}
Ok(gamma)
}
pub fn riemann_curvature(
provider: &dyn MetricProvider,
p: &FixedVector,
) -> Result<Tensor, OverflowDetected> {
let n = provider.dimension();
round_tensor(&[n, n, n, n], &riemann_compute(provider, p)?)
}
fn riemann_compute(
provider: &dyn MetricProvider,
p: &FixedVector,
) -> Result<Vec<ComputeStorage>, OverflowDetected> {
let n = provider.dimension();
let h = differentiation_step();
let at3 = |a: usize, b: usize, c: usize| (a * n + b) * n + c;
let gamma = christoffel_compute(provider, p)?;
let mut dgamma: Vec<Vec<ComputeStorage>> = Vec::with_capacity(n);
for j in 0..n {
let mut p_plus = p.clone();
let mut p_minus = p.clone();
p_plus[j] = p_plus[j] + h;
p_minus[j] = p_minus[j] - h;
let gamma_plus = christoffel_compute(provider, &p_plus)?;
let gamma_minus = christoffel_compute(provider, &p_minus)?;
dgamma.push(gamma_plus.iter().zip(&gamma_minus)
.map(|(&a, &b)| central_difference(a, b))
.collect::<Result<_, _>>()?);
}
let mut riemann = Vec::with_capacity(n * n * n * n);
for l in 0..n {
for i in 0..n {
for j in 0..n {
for k in 0..n {
let mut acc = checked_sub(dgamma[j][at3(l, i, k)], dgamma[k][at3(l, i, j)])?;
for m in 0..n {
acc = compute_checked_add(acc, compute_product(gamma[at3(l, j, m)], gamma[at3(m, i, k)])?)?;
acc = checked_sub(acc, compute_product(gamma[at3(l, k, m)], gamma[at3(m, i, j)])?)?;
}
riemann.push(acc);
}
}
}
}
Ok(riemann)
}
pub fn ricci_tensor(
provider: &dyn MetricProvider,
p: &FixedVector,
) -> Result<FixedMatrix, OverflowDetected> {
let n = provider.dimension();
let ricci = ricci_compute(provider, p)?;
let mut out = FixedMatrix::new(n, n);
for i in 0..n {
for j in 0..n {
out.set(i, j, FixedPoint::from_raw(downscale_to_storage(ricci[i * n + j])?));
}
}
Ok(out)
}
fn ricci_compute(
provider: &dyn MetricProvider,
p: &FixedVector,
) -> Result<Vec<ComputeStorage>, OverflowDetected> {
let n = provider.dimension();
let riemann = riemann_compute(provider, p)?;
let at4 = |a: usize, b: usize, c: usize, d: usize| ((a * n + b) * n + c) * n + d;
let mut ricci = Vec::with_capacity(n * n);
for i in 0..n {
for j in 0..n {
let mut acc = compute_zero();
for k in 0..n {
acc = compute_checked_add(acc, riemann[at4(k, i, k, j)])?;
}
ricci.push(acc);
}
}
Ok(ricci)
}
pub fn ricci_from_riemann(riemann: &Tensor, n: usize) -> FixedMatrix {
let mut ricci = FixedMatrix::new(n, n);
for i in 0..n {
for j in 0..n {
let mut sum = FixedPoint::ZERO;
for k in 0..n {
sum = sum + riemann.get(&[k, i, k, j]);
}
ricci.set(i, j, sum);
}
}
ricci
}
pub fn scalar_curvature(
provider: &dyn MetricProvider,
p: &FixedVector,
) -> Result<FixedPoint, OverflowDetected> {
if let Some(r) = provider.scalar_curvature_closed_form(p) {
return Ok(r);
}
let n = provider.dimension();
let g_inv = metric_inverse_compute(provider, p)?;
let ricci = ricci_compute(provider, p)?;
let mut acc = compute_zero();
for e in 0..n * n {
acc = compute_checked_add(acc, compute_product(g_inv[e], ricci[e])?)?;
}
Ok(FixedPoint::from_raw(downscale_to_storage(acc)?))
}
pub fn scalar_from_ricci(g_inv: &FixedMatrix, ricci: &FixedMatrix) -> FixedPoint {
let n = g_inv.rows();
let mut g_flat = Vec::with_capacity(n * n);
let mut r_flat = Vec::with_capacity(n * n);
for i in 0..n {
for j in 0..n {
g_flat.push(g_inv.get(i, j).raw());
r_flat.push(ricci.get(i, j).raw());
}
}
FixedPoint::from_raw(compute_tier_dot_raw(&g_flat, &r_flat))
}
pub fn sectional_curvature(
provider: &dyn MetricProvider,
p: &FixedVector,
u: &FixedVector,
v: &FixedVector,
) -> Result<FixedPoint, OverflowDetected> {
let n = provider.dimension();
let g = provider.metric(p);
let riemann = riemann_compute(provider, p)?;
let at4 = |a: usize, b: usize, c: usize, d: usize| ((a * n + b) * n + c) * n + d;
let uc: Vec<ComputeStorage> = (0..n).map(|i| upscale_to_compute(u[i].raw())).collect();
let vc: Vec<ComputeStorage> = (0..n).map(|i| upscale_to_compute(v[i].raw())).collect();
let lower = |x: &[ComputeStorage]| -> Result<Vec<ComputeStorage>, OverflowDetected> {
(0..n).map(|i| {
let mut acc = compute_zero();
for j in 0..n {
acc = compute_checked_add(acc, compute_product(upscale_to_compute(g.get(i, j).raw()), x[j])?)?;
}
Ok(acc)
}).collect()
};
let gu = lower(&uc)?;
let gv = lower(&vc)?;
let mut numerator = compute_zero();
for s in 0..n {
for i in 0..n {
for j in 0..n {
for k in 0..n {
let r = riemann[at4(s, i, j, k)];
if r == compute_zero() { continue; }
let term = compute_product(compute_product(compute_product(r, vc[i])?, uc[j])?, vc[k])?;
numerator = compute_checked_add(numerator, compute_product(term, gu[s])?)?;
}
}
}
}
let inner = |x: &[ComputeStorage], gy: &[ComputeStorage]| -> Result<ComputeStorage, OverflowDetected> {
let mut acc = compute_zero();
for i in 0..n { acc = compute_checked_add(acc, compute_product(x[i], gy[i])?)?; }
Ok(acc)
};
let uu = inner(&uc, &gu)?;
let vv = inner(&vc, &gv)?;
let uv = inner(&uc, &gv)?;
let denom = checked_sub(compute_product(uu, vv)?, compute_product(uv, uv)?)?;
if denom == compute_zero() {
return Err(OverflowDetected::DomainError);
}
Ok(FixedPoint::from_raw(downscale_to_storage(compute_checked_divide(numerator, denom)?)?))
}
pub struct EuclideanMetric {
pub dim: usize,
}
impl MetricProvider for EuclideanMetric {
fn dimension(&self) -> usize { self.dim }
fn metric(&self, _p: &FixedVector) -> FixedMatrix {
FixedMatrix::identity(self.dim)
}
fn metric_inverse(&self, _p: &FixedVector) -> Result<FixedMatrix, OverflowDetected> {
Ok(FixedMatrix::identity(self.dim))
}
}
pub struct SphereMetric {
pub radius: FixedPoint,
}
impl SphereMetric {
fn radius_sq_compute(&self) -> ComputeStorage {
let r = upscale_to_compute(self.radius.raw());
compute_multiply(r, r)
}
}
impl MetricProvider for SphereMetric {
fn dimension(&self) -> usize { 2 }
fn metric(&self, p: &FixedVector) -> FixedMatrix {
let (sin_t, _) = sincos_at_compute_tier(upscale_to_compute(p[0].raw()));
let r_sq = self.radius_sq_compute();
let g11 = compute_multiply(r_sq, compute_multiply(sin_t, sin_t));
let z = FixedPoint::ZERO;
FixedMatrix::from_slice(2, 2, &[
FixedPoint::from_raw(round_to_storage(r_sq)), z,
z, FixedPoint::from_raw(round_to_storage(g11)),
])
}
fn christoffel_closed_form(&self, p: &FixedVector) -> Option<Tensor> {
let gamma = self.christoffel_closed_form_compute(p)?;
Some(round_tensor(&[2, 2, 2], &gamma.data).expect("sphere: Christoffel symbol exceeds storage"))
}
#[doc(hidden)]
fn christoffel_closed_form_compute(&self, p: &FixedVector) -> Option<ComputeChristoffel> {
let (sin_t, cos_t) = sincos_at_compute_tier(upscale_to_compute(p[0].raw()));
let mut data = vec![compute_zero(); 8];
data[3] = compute_negate(compute_multiply(sin_t, cos_t));
if sin_t != compute_zero() {
let cot_t = compute_divide(cos_t, sin_t).expect("sphere: cot(theta) exceeds the compute tier");
data[5] = cot_t;
data[6] = cot_t;
}
Some(ComputeChristoffel { data })
}
fn scalar_curvature_closed_form(&self, _p: &FixedVector) -> Option<FixedPoint> {
let two = compute_add(compute_one(), compute_one());
let r = compute_divide(two, self.radius_sq_compute())
.expect("sphere: 2 / r^2 exceeds the compute tier");
Some(FixedPoint::from_raw(round_to_storage(r)))
}
}
pub struct HyperbolicMetric;
impl MetricProvider for HyperbolicMetric {
fn dimension(&self) -> usize { 2 }
fn metric(&self, p: &FixedVector) -> FixedMatrix {
let y = upscale_to_compute(p[1].raw());
let scale = compute_divide(compute_one(), compute_multiply(y, y))
.expect("hyperbolic metric: 1/y^2 exceeds the compute tier");
let scale = FixedPoint::from_raw(round_to_storage(scale));
let z = FixedPoint::ZERO;
FixedMatrix::from_slice(2, 2, &[
scale, z,
z, scale,
])
}
fn christoffel_closed_form(&self, p: &FixedVector) -> Option<Tensor> {
let gamma = self.christoffel_closed_form_compute(p)?;
Some(round_tensor(&[2, 2, 2], &gamma.data).expect("hyperbolic: Christoffel symbol exceeds storage"))
}
#[doc(hidden)]
fn christoffel_closed_form_compute(&self, p: &FixedVector) -> Option<ComputeChristoffel> {
let y = p[1];
if y.is_zero() { return None; }
let inv_y = compute_divide(compute_one(), upscale_to_compute(y.raw()))
.expect("hyperbolic: 1/y exceeds the compute tier");
let mut data = vec![compute_zero(); 8];
data[1] = compute_negate(inv_y);
data[2] = compute_negate(inv_y);
data[4] = inv_y;
data[7] = compute_negate(inv_y);
Some(ComputeChristoffel { data })
}
fn scalar_curvature_closed_form(&self, _p: &FixedVector) -> Option<FixedPoint> {
Some(FixedPoint::from_int(-2))
}
}
pub struct GeodesicOde<'a> {
provider: &'a dyn MetricProvider,
}
impl<'a> OdeSystem for GeodesicOde<'a> {
fn eval(&self, _t: FixedPoint, state: &FixedVector) -> FixedVector {
let n = self.provider.dimension();
let mut x = FixedVector::new(n);
let mut v = FixedVector::new(n);
for i in 0..n { x[i] = state[i]; v[i] = state[n + i]; }
let gamma = match christoffel_compute(self.provider, &x) {
Ok(g) => g,
Err(_) => return FixedVector::new(2 * n), };
let mut dstate = FixedVector::new(2 * n);
for k in 0..n { dstate[k] = v[k]; }
let v_c = state_to_compute(&v);
for k in 0..n {
let mut acc = upscale_to_compute(FixedPoint::ZERO.raw());
for i in 0..n {
for j in 0..n {
let vv = compute_multiply(v_c[i], v_c[j]);
acc = compute_add(acc, compute_multiply(gamma[(k * n + i) * n + j], vv));
}
}
dstate[n + k] = FixedPoint::from_raw(round_to_storage(compute_negate(acc)));
}
dstate
}
}
pub fn geodesic_integrate(
provider: &dyn MetricProvider,
initial_point: &FixedVector,
initial_velocity: &FixedVector,
total_time: FixedPoint,
num_steps: usize,
) -> Result<Vec<FixedVector>, OverflowDetected> {
let n = provider.dimension();
let time_at = |k: usize| -> ComputeStorage {
compute_mul_div_int(upscale_to_compute(total_time.raw()), k as i64, num_steps as i64)
.expect("geodesic: num_steps > 0 and the time fits")
};
let mut state = FixedVector::new(2 * n);
for i in 0..n { state[i] = initial_point[i]; state[n + i] = initial_velocity[i]; }
let mut state = state_to_compute(&state);
let sys = GeodesicOde { provider };
let mut points = Vec::with_capacity(num_steps + 1);
let mut t = time_at(0);
let extract_pos = |s: &[ComputeStorage]| -> Result<FixedVector, OverflowDetected> {
let mut p = FixedVector::new(n);
for i in 0..n { p[i] = FixedPoint::from_raw(downscale_to_storage(s[i])?); }
Ok(p)
};
points.push(initial_point.clone());
for k in 0..num_steps {
let t_next = time_at(k + 1);
state = rk4_step_compute(&sys, t, &state, compute_subtract(t_next, t));
t = t_next;
points.push(extract_pos(&state)?);
}
Ok(points)
}
pub fn parallel_transport_ode(
provider: &dyn MetricProvider,
curve: &[FixedVector],
initial_vector: &FixedVector,
reorthog_interval: usize,
) -> Result<FixedVector, OverflowDetected> {
if curve.len() < 2 {
return Ok(initial_vector.clone());
}
let n = provider.dimension();
let mut v = state_to_compute(initial_vector);
let zero = upscale_to_compute(FixedPoint::ZERO.raw());
for step in 0..curve.len() - 1 {
let p = &curve[step];
let p_next = &curve[step + 1];
let dx: Vec<FixedPoint> = (0..n).map(|i| p_next[i] - p[i]).collect();
let gamma = christoffel_compute(provider, p)?;
let dx_c: Vec<ComputeStorage> = dx.iter().map(|d| upscale_to_compute(d.raw())).collect();
let mut v_new = Vec::with_capacity(n);
for k in 0..n {
let mut correction = zero;
for j in 0..n {
let mut gamma_v = zero;
for i in 0..n {
gamma_v = compute_checked_add(gamma_v, compute_product(gamma[(k * n + i) * n + j], v[i])?)?;
}
correction = compute_checked_add(correction, compute_product(gamma_v, dx_c[j])?)?;
}
v_new.push(checked_sub(v[k], correction)?);
}
v = v_new;
if reorthog_interval > 0 && (step + 1) % reorthog_interval == 0 {
let mut dx_norm_sq = zero;
let mut v_dot_dx = zero;
for i in 0..n {
dx_norm_sq = compute_checked_add(dx_norm_sq, compute_product(dx_c[i], dx_c[i])?)?;
v_dot_dx = compute_checked_add(v_dot_dx, compute_product(v[i], dx_c[i])?)?;
}
if dx_norm_sq != zero {
let coeff = compute_checked_divide(v_dot_dx, dx_norm_sq)?;
for i in 0..n {
v[i] = checked_sub(v[i], compute_product(dx_c[i], coeff)?)?;
}
}
}
}
let mut out = FixedVector::new(n);
for i in 0..n { out[i] = FixedPoint::from_raw(downscale_to_storage(v[i])?); }
Ok(out)
}
fn checked_sub(a: ComputeStorage, b: ComputeStorage) -> Result<ComputeStorage, OverflowDetected> {
#[cfg(not(table_format = "q256_256"))]
{ a.checked_sub(b).ok_or(OverflowDetected::TierOverflow) }
#[cfg(table_format = "q256_256")]
{
if b == ComputeStorage::min_value() { return Err(OverflowDetected::TierOverflow); }
compute_checked_add(a, compute_negate(b))
}
}