ogdoad 1.0.0

Clifford algebras (with nilpotents) over the field-like subclasses of combinatorial games: nimbers, surreals, surcomplex.
Documentation
//! The GENERAL multivector inverse: `multivector_inverse` solves for a
//! two-sided inverse of any element (not just versors) by building the
//! left-multiplication matrix of `v` over the full `2^dim`-blade basis and
//! solving `v x = 1` with the shared `linalg::field` solver. This is the
//! fallback used when an element is invertible but not a versor — e.g. `1+B`
//! in the Cayley transform — where `versor_inverse`'s `v ṽ`-is-scalar shortcut
//! does not apply.

use super::algebra::CliffordAlgebra;
use super::multivector::Multivector;
use crate::linalg::field;
use crate::scalar::Scalar;
use std::collections::BTreeMap;

impl<S: Scalar> CliffordAlgebra<S> {
    /// The **general multivector inverse** `v⁻¹` (two-sided), for any element.
    pub fn multivector_inverse(&self, v: &Multivector<S>) -> Option<Multivector<S>> {
        if v.is_zero() {
            return None;
        }
        if v.terms.len() == 1 {
            if let Some(c) = v.terms.get(&0) {
                return Some(self.scalar(c.inv()?));
            }
        }
        let n = 1usize.checked_shl(self.dim().try_into().ok()?)?;
        let mut mat = vec![vec![S::zero(); n]; n];
        for col in 0..n {
            let mut t = BTreeMap::new();
            t.insert(col as u128, S::one());
            let prod = self.mul(v, &Multivector { terms: t });
            for (&blade, c) in &prod.terms {
                mat[blade as usize][col] = c.clone();
            }
        }
        let mut rhs = vec![S::zero(); n];
        rhs[0] = S::one();
        let x = field::solve(mat, rhs)?;
        let mut terms = BTreeMap::new();
        for (bm, c) in x.into_iter().enumerate() {
            if !c.is_zero() {
                terms.insert(bm as u128, c);
            }
        }
        Some(Multivector { terms })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::clifford::Metric;
    use crate::scalar::Rational;

    #[test]
    fn inverse_refuses_huge_non_scalar_without_shift_overflow() {
        let alg: CliffordAlgebra<Rational> = CliffordAlgebra::new(64, Metric::grassmann(64));
        assert_eq!(alg.multivector_inverse(&alg.e(0)), None);
    }

    #[test]
    fn scalar_inverse_still_works_at_huge_dimension() {
        let alg = CliffordAlgebra::new(64, Metric::grassmann(64));
        let two = alg.scalar(Rational::from_int(2));
        assert_eq!(
            alg.multivector_inverse(&two),
            Some(alg.scalar(Rational::new(1, 2)))
        );
    }
}