use crate::clifford::{CliffordAlgebra, Metric, Multivector};
use crate::scalar::Scalar;
use std::collections::BTreeMap;
fn s_int<S: Scalar>(k: usize) -> S {
let one = S::one();
let mut acc = S::zero();
for _ in 0..k {
acc = acc.add(&one);
}
acc
}
pub struct Cga<S: Scalar> {
alg: CliffordAlgebra<S>,
n: usize,
no: usize,
ninf: usize,
}
impl<S: Scalar> Cga<S> {
pub fn new(n: usize) -> Self {
assert_eq!(
S::characteristic(),
0,
"CGA is a characteristic-0 Euclidean construction"
);
let two = S::one().add(&S::one());
assert!(
two.inv().is_some(),
"CGA needs 1/2, so 2 must be invertible in the scalar backend"
);
let mut q = vec![S::one(); n];
q.push(S::zero()); q.push(S::zero()); let mut b = BTreeMap::new();
b.insert((n, n + 1), S::one().add(&S::one()).neg());
let alg = CliffordAlgebra::new(n + 2, Metric::new(q, b));
Cga {
alg,
n,
no: n,
ninf: n + 1,
}
}
pub fn alg(&self) -> &CliffordAlgebra<S> {
&self.alg
}
pub fn n(&self) -> usize {
self.n
}
fn half(&self) -> S {
S::one()
.add(&S::one())
.inv()
.expect("½ exists in characteristic 0")
}
pub fn n_o(&self) -> Multivector<S> {
self.alg.e(self.no)
}
pub fn n_inf(&self) -> Multivector<S> {
self.alg.e(self.ninf)
}
pub fn inner(&self, x: &Multivector<S>, y: &Multivector<S>) -> S {
let xy = self.alg.mul(x, y);
let yx = self.alg.mul(y, x);
self.half()
.mul(&self.alg.scalar_part(&self.alg.add(&xy, &yx)))
}
pub fn up(&self, p: &[S]) -> Multivector<S> {
assert_eq!(p.len(), self.n, "point dimension mismatch");
let mut acc = self.n_o();
let mut s = S::zero();
for (i, pi) in p.iter().enumerate() {
acc = self.alg.add(&acc, &self.alg.scalar_mul(pi, &self.alg.e(i)));
s = s.add(&pi.mul(pi));
}
let coeff = self.half().mul(&s);
self.alg
.add(&acc, &self.alg.scalar_mul(&coeff, &self.n_inf()))
}
pub fn down(&self, x: &Multivector<S>) -> Option<Vec<S>> {
let f = self.inner(x, &self.n_inf()); let factor = f.neg();
let inv = factor.inv()?;
let norm = self.alg.scalar_mul(&inv, x);
Some(
(0..self.n)
.map(|i| {
norm.terms
.get(&(1u128 << i))
.cloned()
.unwrap_or_else(S::zero)
})
.collect(),
)
}
pub fn sphere(&self, c: &[S], r2: &S) -> Multivector<S> {
let coeff = self.half().mul(r2).neg();
self.alg
.add(&self.up(c), &self.alg.scalar_mul(&coeff, &self.n_inf()))
}
pub fn plane(&self, normal: &[S], d: &S) -> Multivector<S> {
assert_eq!(normal.len(), self.n, "normal dimension mismatch");
let mut acc = self.alg.scalar_mul(d, &self.n_inf());
for (i, ni) in normal.iter().enumerate() {
acc = self.alg.add(&acc, &self.alg.scalar_mul(ni, &self.alg.e(i)));
}
acc
}
pub fn outer_join(&self, a: &Multivector<S>, b: &Multivector<S>) -> Multivector<S> {
self.alg.wedge(a, b)
}
pub fn point_pair(&self, a: &Multivector<S>, b: &Multivector<S>) -> Multivector<S> {
self.outer_join(a, b)
}
}
pub fn pga<S: Scalar>(n: usize) -> CliffordAlgebra<S> {
let mut q = vec![S::zero()]; q.extend(std::iter::repeat_n(S::one(), n));
CliffordAlgebra::new(n + 1, Metric::diagonal(q))
}
pub fn exp_nilpotent<S: Scalar>(
alg: &CliffordAlgebra<S>,
b: &Multivector<S>,
) -> Option<Multivector<S>> {
let cap = 2 * alg.dim() + 2;
let mut acc = alg.scalar(S::one());
let mut power = alg.scalar(S::one()); let mut fact = S::one(); for k in 1..=cap {
power = alg.mul(&power, b);
if power.is_zero() {
return Some(acc); }
fact = fact.mul(&s_int::<S>(k));
let finv = fact.inv()?;
acc = alg.add(&acc, &alg.scalar_mul(&finv, &power));
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::Fp;
use crate::scalar::Integer;
use crate::scalar::Rational;
use crate::scalar::Surreal;
fn r(n: i128) -> Rational {
Rational::from_int(n)
}
fn rs(num: i128, den: i128) -> Rational {
Rational::new(num, den)
}
#[test]
fn cga_rejects_rings_without_one_half() {
assert!(std::panic::catch_unwind(|| Cga::<Integer>::new(1)).is_err());
}
#[test]
fn cga_rejects_positive_characteristic() {
assert!(std::panic::catch_unwind(|| Cga::<Fp<3>>::new(1)).is_err());
}
#[test]
fn up_is_null() {
let cga = Cga::<Rational>::new(2);
for p in [[r(3), r(4)], [r(0), r(0)], [r(-2), r(5)]] {
assert_eq!(cga.inner(&cga.up(&p), &cga.up(&p)), r(0));
}
}
#[test]
fn inner_product_is_euclidean_distance() {
let cga = Cga::<Rational>::new(2);
let p = [r(1), r(0)];
let q = [r(4), r(4)]; assert_eq!(cga.inner(&cga.up(&p), &cga.up(&q)), rs(-25, 2));
}
#[test]
fn down_inverts_up() {
let cga = Cga::<Rational>::new(3);
let p = vec![r(2), r(-3), r(5)];
assert_eq!(cga.down(&cga.up(&p)).unwrap(), p);
let scaled = cga.alg.scalar_mul(&r(7), &cga.up(&p));
assert_eq!(cga.down(&scaled).unwrap(), p);
}
#[test]
fn point_lies_on_sphere() {
let cga = Cga::<Rational>::new(2);
let c = [r(0), r(0)];
let s = cga.sphere(&c, &r(25)); assert_eq!(cga.inner(&cga.up(&[r(3), r(4)]), &s), r(0));
assert_ne!(cga.inner(&cga.up(&[r(1), r(1)]), &s), r(0));
}
#[test]
fn point_lies_on_plane() {
let cga = Cga::<Rational>::new(2);
let pl = cga.plane(&[r(1), r(0)], &r(3));
assert_eq!(cga.inner(&cga.up(&[r(3), r(9)]), &pl), r(0));
assert_ne!(cga.inner(&cga.up(&[r(2), r(9)]), &pl), r(0));
}
#[test]
fn meet_of_planes_is_nonzero() {
let cga = Cga::<Rational>::new(3);
let a = cga.plane(&[r(1), r(0), r(0)], &r(0));
let b = cga.plane(&[r(0), r(1), r(0)], &r(0));
let m = cga.outer_join(&a, &b);
assert!(!m.is_zero());
assert_eq!(cga.alg.grade_part(&m, 2), m); }
#[test]
fn surreal_point_at_infinite_scale_is_still_null() {
let cga = Cga::<Surreal>::new(2);
let p = [Surreal::omega(), Surreal::zero()];
assert_eq!(cga.inner(&cga.up(&p), &cga.up(&p)), Surreal::zero());
}
#[test]
fn surreal_sphere_of_infinitesimal_radius() {
let cga = Cga::<Surreal>::new(2);
let eps = Surreal::epsilon();
let eps2 = eps.mul(&eps);
let s = cga.sphere(&[Surreal::zero(), Surreal::zero()], &eps2);
let on = cga.up(&[eps.clone(), Surreal::zero()]);
assert_eq!(cga.inner(&on, &s), Surreal::zero());
let off = cga.up(&[eps.mul(&Surreal::from_int(2)), Surreal::zero()]);
assert_ne!(cga.inner(&off, &s), Surreal::zero());
}
#[test]
fn pga_nilpotent_exp_is_exact() {
let alg = pga::<Rational>(2);
let (e0, e1) = (alg.e(0), alg.e(1));
let b = alg.wedge(&e0, &e1);
assert!(alg.mul(&b, &b).is_zero());
assert_eq!(
exp_nilpotent(&alg, &b).unwrap(),
alg.add(&alg.scalar(r(1)), &b)
);
let b3 = alg.scalar_mul(&r(3), &b);
assert_eq!(
exp_nilpotent(&alg, &b3).unwrap(),
alg.add(&alg.scalar(r(1)), &b3)
);
}
#[test]
fn pga_motor_translates_exactly() {
let alg = pga::<Rational>(2);
let (e0, e1) = (alg.e(0), alg.e(1));
let b = alg.wedge(&e0, &e1);
let motor = exp_nilpotent(&alg, &b).unwrap(); let moved = alg.sandwich(&motor, &e1).unwrap();
let expect = alg.add(&e1, &alg.scalar_mul(&r(2), &e0));
assert_eq!(moved, expect);
}
#[test]
fn non_nilpotent_exp_returns_none() {
let alg = CliffordAlgebra::new(2, Metric::diagonal(vec![r(1), r(1)]));
let b = alg.wedge(&alg.e(0), &alg.e(1));
assert!(alg.mul(&b, &b) == alg.scalar(r(-1)));
assert!(exp_nilpotent(&alg, &b).is_none());
}
}