geoit 0.0.2

Exact geometric algebra with governed multivectors
Documentation
//! `Algebra` binds a `Signature` and derived generators into a single context,
//! so every operation carries its algebra implicitly.
//!
//! ```ignore
//! let alg = Algebra::new(Signature::new(1, 0, 3));
//! alg.add_derived("eo", mv![0 => 1/2, 1 => 1/2]);
//! let v = alg.geometric(&a, &b);
//! ```

use crate::algebra::inverse::{self, InverseError};
use crate::algebra::mv::Mv;
use crate::algebra::ops;
use crate::algebra::signature::Signature;
use crate::scalar::Scalar;

/// An algebra context: signature + named derived generators.
///
/// All operations use the bound signature. Derived generators are
/// available by name for construction formulas.
#[derive(Clone, Debug)]
pub struct Algebra {
    sig: Signature,
    derived_gens: Vec<Mv>,
    derived_names: Vec<String>,
}

impl Algebra {
    /// Create a new algebra from a signature.
    pub fn new(sig: Signature) -> Self {
        Algebra {
            sig,
            derived_gens: Vec::new(),
            derived_names: Vec::new(),
        }
    }

    /// Add a named derived generator (e.g., "eo", "einf").
    pub fn add_derived(&mut self, name: &str, mv: Mv) -> &mut Self {
        self.derived_names.push(name.to_string());
        self.derived_gens.push(mv);
        self
    }

    /// The signature.
    pub fn sig(&self) -> &Signature {
        &self.sig
    }

    /// Number of generators.
    pub fn n(&self) -> u8 {
        self.sig.n()
    }

    /// Basis generator g_k as an Mv.
    pub fn gen(&self, k: u8) -> Mv {
        Mv::generator(k)
    }

    /// Named derived generator. Returns `Err` if name not found.
    pub fn dgen(&self, name: &str) -> Result<&Mv, crate::error::NotFoundError> {
        let idx = self
            .derived_names
            .iter()
            .position(|n| n == name)
            .ok_or_else(|| crate::error::NotFoundError::DerivedGen(name.to_string()))?;
        Ok(&self.derived_gens[idx])
    }

    /// Derived generator by index.
    pub fn dgen_by_index(&self, i: usize) -> &Mv {
        &self.derived_gens[i]
    }

    /// All derived generators (for Governance construction).
    pub fn derived_gens(&self) -> &[Mv] {
        &self.derived_gens
    }

    // ─── Binary operations ───

    pub fn geometric(&self, a: &Mv, b: &Mv) -> Mv {
        ops::geometric(a, b, &self.sig)
    }
    pub fn outer(&self, a: &Mv, b: &Mv) -> Mv {
        ops::outer(a, b, &self.sig)
    }
    pub fn inner(&self, a: &Mv, b: &Mv) -> Mv {
        ops::inner(a, b, &self.sig)
    }
    pub fn left_contract(&self, a: &Mv, b: &Mv) -> Mv {
        ops::left_contract(a, b, &self.sig)
    }
    pub fn right_contract(&self, a: &Mv, b: &Mv) -> Mv {
        ops::right_contract(a, b, &self.sig)
    }
    pub fn scalar_product(&self, a: &Mv, b: &Mv) -> Mv {
        ops::scalar_product(a, b, &self.sig)
    }
    pub fn scalar_product_value(&self, a: &Mv, b: &Mv) -> Scalar {
        ops::scalar_product_value(a, b, &self.sig)
    }
    pub fn commutator(&self, a: &Mv, b: &Mv) -> Mv {
        ops::commutator(a, b, &self.sig)
    }
    pub fn sandwich(&self, m: &Mv, v: &Mv) -> Mv {
        ops::sandwich(m, v, &self.sig)
    }
    pub fn regressive(&self, a: &Mv, b: &Mv) -> Mv {
        ops::regressive(a, b, &self.sig)
    }

    // ─── Unary operations ───

    pub fn reverse(&self, a: &Mv) -> Mv {
        ops::reverse(a)
    }
    pub fn grade_involution(&self, a: &Mv) -> Mv {
        ops::grade_involution(a)
    }
    pub fn conjugate(&self, a: &Mv) -> Mv {
        ops::conjugate(a)
    }
    pub fn dual(&self, a: &Mv) -> Mv {
        ops::dual(a, &self.sig)
    }
    pub fn undual(&self, a: &Mv) -> Mv {
        ops::undual(a, &self.sig)
    }
    pub fn grade_project(&self, a: &Mv, k: u8) -> Mv {
        ops::grade_project(a, k)
    }
    pub fn norm_squared(&self, a: &Mv) -> Scalar {
        ops::norm_squared(a, &self.sig)
    }

    // ─── Inverse ───

    pub fn inverse(&self, m: &Mv) -> Result<Mv, InverseError> {
        inverse::inverse(m, &self.sig)
    }
    pub fn is_versor(&self, m: &Mv) -> bool {
        inverse::is_versor(m, &self.sig)
    }
    pub fn sandwich_normalized(&self, m: &Mv, v: &Mv) -> Result<Mv, InverseError> {
        inverse::sandwich_normalized(m, v, &self.sig)
    }
}

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

    #[test]
    fn algebra_basic_ops() {
        let alg = Algebra::new(Signature::new(0, 0, 3).unwrap());
        let e0 = alg.gen(0);
        let e1 = alg.gen(1);
        let geo = alg.geometric(&e0, &e1);
        assert_eq!(geo.coefficient(0b11), Scalar::from(1));
    }

    #[test]
    fn algebra_derived_gen() {
        let mut alg = Algebra::new(Signature::new(1, 0, 3).unwrap());
        let eo = Mv::from_rat_terms(&[(0b0001, Rat::new(1, 2)), (0b0010, Rat::new(1, 2))]);
        alg.add_derived("eo", eo.clone());
        assert_eq!(*alg.dgen("eo").unwrap(), eo);
    }

    #[test]
    fn algebra_missing_dgen() {
        let alg = Algebra::new(Signature::new(0, 0, 3).unwrap());
        assert!(alg.dgen("nonexistent").is_err());
    }

    #[test]
    fn algebra_inverse() {
        let alg = Algebra::new(Signature::new(0, 0, 3).unwrap());
        let v = alg.gen(0);
        let inv = alg.inverse(&v).unwrap();
        let product = alg.geometric(&v, &inv);
        assert_eq!(product.coefficient(0), Scalar::from(1));
    }
}