geoit 0.0.2

Exact geometric algebra with governed multivectors
Documentation
use crate::algebra::blade_new::BladeMask;
use crate::error::SignatureError;

/// A Clifford algebra signature: generators squaring to -1, 0, +1.
///
/// Generator ordering: g₀..g_{i-1} square to -1,
/// g_i..g_{i+d-1} square to 0, g_{i+d}..g_{i+d+h-1} square to +1.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Signature {
    pub(crate) i: u8,
    pub(crate) d: u8,
    pub(crate) h: u8,
}

impl Signature {
    /// Construct a signature Cl(i, d, h).
    ///
    /// Returns `Err` if i + d + h > 64.
    pub fn new(i: u8, d: u8, h: u8) -> Result<Self, SignatureError> {
        let total = i as u16 + d as u16 + h as u16;
        if total > 64 {
            return Err(SignatureError::TooManyGenerators { i, d, h, total });
        }
        Ok(Signature { i, d, h })
    }

    /// Number of generators squaring to -1 (imaginary / indefinite).
    #[inline]
    pub fn i(&self) -> u8 {
        self.i
    }

    /// Number of generators squaring to 0 (degenerate).
    #[inline]
    pub fn d(&self) -> u8 {
        self.d
    }

    /// Number of generators squaring to +1 (hyperbolic / Euclidean).
    #[inline]
    pub fn h(&self) -> u8 {
        self.h
    }

    /// Total number of generators.
    #[inline]
    pub fn n(&self) -> u8 {
        self.i + self.d + self.h
    }

    /// Dimension of the algebra as a vector space (2^n).
    #[inline]
    pub fn dimension(&self) -> u64 {
        1u64 << self.n()
    }

    /// The square of generator k: -1, 0, or +1.
    #[inline]
    pub fn generator_square(&self, k: u8) -> i8 {
        assert!(
            k < self.n(),
            "Signature: generator {} out of range (n={})",
            k,
            self.n()
        );
        if k < self.i {
            -1
        } else if k < self.i + self.d {
            0
        } else {
            1
        }
    }

    /// The square of generator k (u32 index): -1, 0, or +1.
    /// For use with BladeKey which uses u16/u32 generator indices.
    #[inline]
    pub fn generator_square_u32(&self, k: u32) -> i8 {
        let n = self.i as u32 + self.d as u32 + self.h as u32;
        assert!(k < n, "Signature: generator {} out of range (n={})", k, n);
        if k < self.i as u32 {
            -1
        } else if k < (self.i as u32 + self.d as u32) {
            0
        } else {
            1
        }
    }

    /// Is generator k degenerate (squares to 0)? (u32 index version)
    #[inline]
    pub fn is_degenerate_u32(&self, k: u32) -> bool {
        self.generator_square_u32(k) == 0
    }

    /// Is generator k degenerate (squares to 0)?
    #[inline]
    pub fn is_degenerate(&self, k: u8) -> bool {
        self.generator_square(k) == 0
    }

    /// The pseudoscalar blade mask: all generators participating.
    #[inline]
    pub fn pseudoscalar_mask(&self) -> BladeMask {
        (1u64 << self.n()) - 1
    }

    /// Semantic name for generator k: "i0", "d0", "h0", etc.
    pub fn generator_name(&self, k: u8) -> String {
        let (kind, idx) = self.generator_typed(k);
        format!("{}{}", kind, idx)
    }

    /// The kind ('i', 'd', 'h') and within-kind index for flat generator k.
    pub fn generator_typed(&self, k: u8) -> (char, u8) {
        assert!(
            k < self.n(),
            "Signature: generator {} out of range (n={})",
            k,
            self.n()
        );
        if k < self.i {
            ('i', k)
        } else if k < self.i + self.d {
            ('d', k - self.i)
        } else {
            ('h', k - self.i - self.d)
        }
    }

    /// Resolve a typed generator reference to a flat index.
    /// E.g., ('h', 2) on Cl(1,0,4) → Some(3).
    /// Returns None if the kind-index is out of range.
    pub fn resolve_typed(&self, kind: char, index: u8) -> Option<u8> {
        match kind {
            'i' => {
                if index < self.i {
                    Some(index)
                } else {
                    None
                }
            }
            'd' => {
                if index < self.d {
                    Some(self.i + index)
                } else {
                    None
                }
            }
            'h' => {
                if index < self.h {
                    Some(self.i + self.d + index)
                } else {
                    None
                }
            }
            _ => None,
        }
    }

    /// All semantic generator names in order.
    pub fn all_generator_names(&self) -> Vec<String> {
        (0..self.n()).map(|k| self.generator_name(k)).collect()
    }
}

impl std::fmt::Display for Signature {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Cl({},{},{})", self.i, self.d, self.h)
    }
}

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

    #[test]
    fn vga3() {
        let sig = Signature::new(0, 0, 3).unwrap();
        assert_eq!(sig.n(), 3);
        assert_eq!(sig.dimension(), 8);
        assert_eq!(sig.generator_square(0), 1);
        assert_eq!(sig.generator_square(1), 1);
        assert_eq!(sig.generator_square(2), 1);
        assert!(!sig.is_degenerate(0));
        assert_eq!(sig.pseudoscalar_mask(), 0b111);
    }

    #[test]
    fn pga3() {
        let sig = Signature::new(0, 1, 3).unwrap();
        assert_eq!(sig.n(), 4);
        assert_eq!(sig.dimension(), 16);
        assert_eq!(sig.generator_square(0), 0); // d-type
        assert_eq!(sig.generator_square(1), 1);
        assert_eq!(sig.generator_square(2), 1);
        assert_eq!(sig.generator_square(3), 1);
        assert!(sig.is_degenerate(0));
        assert!(!sig.is_degenerate(1));
    }

    #[test]
    fn cga3() {
        let sig = Signature::new(1, 0, 4).unwrap();
        assert_eq!(sig.n(), 5);
        assert_eq!(sig.dimension(), 32);
        assert_eq!(sig.generator_square(0), -1); // i-type
        assert_eq!(sig.generator_square(1), 1);
        assert_eq!(sig.generator_square(4), 1);
        assert!(!sig.is_degenerate(0));
        assert_eq!(sig.pseudoscalar_mask(), 0b11111);
    }

    #[test]
    fn sta_wc() {
        // West Coast: Cl(3,0,1)
        let sig = Signature::new(3, 0, 1).unwrap();
        assert_eq!(sig.n(), 4);
        assert_eq!(sig.generator_square(0), -1);
        assert_eq!(sig.generator_square(1), -1);
        assert_eq!(sig.generator_square(2), -1);
        assert_eq!(sig.generator_square(3), 1);
    }

    #[test]
    fn display() {
        assert_eq!(format!("{}", Signature::new(1, 0, 4).unwrap()), "Cl(1,0,4)");
    }

    #[test]
    fn boundary_values() {
        let sig = Signature::new(2, 3, 1).unwrap();
        // i-type: 0, 1
        assert_eq!(sig.generator_square(0), -1);
        assert_eq!(sig.generator_square(1), -1);
        // d-type: 2, 3, 4
        assert_eq!(sig.generator_square(2), 0);
        assert_eq!(sig.generator_square(3), 0);
        assert_eq!(sig.generator_square(4), 0);
        // h-type: 5
        assert_eq!(sig.generator_square(5), 1);
    }

    #[test]
    fn semantic_names_cga3() {
        let sig = Signature::new(1, 0, 4).unwrap();
        assert_eq!(sig.generator_name(0), "i0");
        assert_eq!(sig.generator_name(1), "h0");
        assert_eq!(sig.generator_name(2), "h1");
        assert_eq!(sig.generator_name(3), "h2");
        assert_eq!(sig.generator_name(4), "h3");
    }

    #[test]
    fn semantic_names_pga3() {
        let sig = Signature::new(0, 1, 3).unwrap();
        assert_eq!(sig.generator_name(0), "d0");
        assert_eq!(sig.generator_name(1), "h0");
        assert_eq!(sig.generator_name(2), "h1");
        assert_eq!(sig.generator_name(3), "h2");
    }

    #[test]
    fn semantic_names_sta_wc() {
        let sig = Signature::new(3, 0, 1).unwrap();
        assert_eq!(sig.generator_name(0), "i0");
        assert_eq!(sig.generator_name(1), "i1");
        assert_eq!(sig.generator_name(2), "i2");
        assert_eq!(sig.generator_name(3), "h0");
    }

    #[test]
    fn resolve_typed_cga3() {
        let sig = Signature::new(1, 0, 4).unwrap();
        assert_eq!(sig.resolve_typed('i', 0), Some(0));
        assert_eq!(sig.resolve_typed('h', 0), Some(1));
        assert_eq!(sig.resolve_typed('h', 2), Some(3));
        assert_eq!(sig.resolve_typed('h', 4), None); // out of range
        assert_eq!(sig.resolve_typed('d', 0), None); // no d-type in CGA3
    }

    #[test]
    fn resolve_typed_mixed() {
        let sig = Signature::new(2, 3, 1).unwrap();
        assert_eq!(sig.resolve_typed('i', 0), Some(0));
        assert_eq!(sig.resolve_typed('i', 1), Some(1));
        assert_eq!(sig.resolve_typed('d', 0), Some(2));
        assert_eq!(sig.resolve_typed('d', 2), Some(4));
        assert_eq!(sig.resolve_typed('h', 0), Some(5));
        assert_eq!(sig.resolve_typed('h', 1), None);
    }

    #[test]
    fn all_names() {
        let sig = Signature::new(1, 1, 2).unwrap();
        assert_eq!(sig.all_generator_names(), vec!["i0", "d0", "h0", "h1"]);
    }

    #[test]
    fn too_many_generators() {
        assert!(Signature::new(30, 20, 20).is_err());
        assert!(Signature::new(0, 0, 65).is_err());
        // Exactly 64 is ok
        assert!(Signature::new(0, 0, 64).is_ok());
    }
}