geoit 0.0.2

Exact geometric algebra with governed multivectors
Documentation
//! Geometric product on BladeKey.
//!
//! The single generative function of the Clifford algebra, now
//! operating on sorted-index blade keys instead of u64 bitmasks.

use crate::algebra::blade_new::{canonical_reorder, BladeKey};
use crate::algebra::signature::Signature;

/// The geometric product of two blades.
///
/// Given two blade keys and a signature, returns the result blade
/// and a sign (+1, -1, or 0).
///
/// Algorithm:
/// 1. result = symmetric_difference(a, b) (surviving generators)
/// 2. sign = canonical_reorder(a, b) (transposition parity)
/// 3. For each generator in intersection(a, b): multiply sign by its square
/// 4. If any cancelled generator squares to 0, the product is zero
pub fn blade_product_new(a: &BladeKey, b: &BladeKey, sig: &Signature) -> (BladeKey, i8) {
    let result = BladeKey::symmetric_difference(a, b);
    let mut sign = canonical_reorder(a, b) as i16;

    let shared = BladeKey::intersection(a, b);
    for &k in shared.indices() {
        let sq = sig.generator_square_u32(k as u32);
        if sq == 0 {
            return (BladeKey::SCALAR, 0);
        }
        sign *= sq as i16;
    }

    (result, sign as i8)
}

/// Legacy mask-based blade product. Delegates to the same algorithm
/// using u64 XOR/AND instead of BladeKey set operations.
///
/// Kept for the GradeGroups optimization path in ops.rs.
pub fn blade_product(a: BladeMask, b: BladeMask, sig: &Signature) -> (BladeMask, i8) {
    let result = a ^ b;
    let mut sign = crate::algebra::blade_new::canonical_reorder_mask(a, b) as i16;
    let mut shared = a & b;
    while shared != 0 {
        let k = shared.trailing_zeros() as u8;
        let sq = sig.generator_square(k);
        if sq == 0 {
            return (0, 0);
        }
        sign *= sq as i16;
        shared &= shared - 1;
    }
    (result, sign as i8)
}

use crate::algebra::blade_new::BladeMask;

#[cfg(test)]
mod tests {
    use super::*;
    use crate::algebra::blade_new::mask_to_blade;
    use crate::algebra::blade_new::BladeMask;
    use crate::algebra::product_new::blade_product;

    fn sig_vga3() -> Signature {
        Signature::new(0, 0, 3).unwrap()
    }
    fn sig_pga3() -> Signature {
        Signature::new(0, 1, 3).unwrap()
    }
    fn sig_cga3() -> Signature {
        Signature::new(1, 0, 4).unwrap()
    }

    fn bk(k: u16) -> BladeKey {
        BladeKey::generator(k)
    }

    #[test]
    fn e0_squared_positive() {
        let sig = sig_vga3();
        let (mask, sign) = blade_product_new(&bk(0), &bk(0), &sig);
        assert!(mask.is_scalar());
        assert_eq!(sign, 1);
    }

    #[test]
    fn e0_squared_negative() {
        let sig = Signature::new(1, 0, 0).unwrap();
        let (mask, sign) = blade_product_new(&bk(0), &bk(0), &sig);
        assert!(mask.is_scalar());
        assert_eq!(sign, -1);
    }

    #[test]
    fn e0_squared_degenerate() {
        let sig = Signature::new(0, 1, 0).unwrap();
        let (_, sign) = blade_product_new(&bk(0), &bk(0), &sig);
        assert_eq!(sign, 0);
    }

    #[test]
    fn e0_e1() {
        let sig = sig_vga3();
        let (mask, sign) = blade_product_new(&bk(0), &bk(1), &sig);
        assert_eq!(mask.indices(), &[0, 1]);
        assert_eq!(sign, 1);
    }

    #[test]
    fn e1_e0_anticommute() {
        let sig = sig_vga3();
        let (mask, sign) = blade_product_new(&bk(1), &bk(0), &sig);
        assert_eq!(mask.indices(), &[0, 1]);
        assert_eq!(sign, -1);
    }

    #[test]
    fn degenerate_kills_product() {
        let sig = sig_pga3();
        let (_, sign) = blade_product_new(&bk(0), &bk(0), &sig);
        assert_eq!(sign, 0);
    }

    /// Exhaustive check: new blade_product matches legacy for all blade pairs in CGA(3)
    #[test]
    fn matches_legacy_cga3() {
        let sig = sig_cga3();
        let n = sig.n();
        let max_mask: BladeMask = (1u64 << n) - 1;

        // Test all pairs of blades up to mask 31 (5 generators)
        for a_mask in 0..=max_mask {
            for b_mask in 0..=max_mask {
                let (legacy_mask, legacy_sign) = blade_product(a_mask, b_mask, &sig);

                let a_blade = mask_to_blade(a_mask);
                let b_blade = mask_to_blade(b_mask);
                let (new_blade, new_sign) = blade_product_new(&a_blade, &b_blade, &sig);

                assert_eq!(
                    legacy_sign, new_sign,
                    "sign mismatch: a={:#b} b={:#b}",
                    a_mask, b_mask
                );

                if legacy_sign != 0 {
                    let new_mask = crate::algebra::blade_new::blade_to_mask(&new_blade);
                    assert_eq!(
                        legacy_mask, new_mask,
                        "mask mismatch: a={:#b} b={:#b}",
                        a_mask, b_mask
                    );
                }
            }
        }
    }

    /// Associativity with new blade product
    #[test]
    fn associativity_fuzz() {
        let sig = sig_cga3();
        let blades: Vec<BladeKey> = vec![
            bk(0),
            bk(1),
            bk(2),
            bk(3),
            bk(4),
            BladeKey::from_sorted(&[0, 1]),
            BladeKey::from_sorted(&[0, 2]),
            BladeKey::from_sorted(&[1, 3]),
            BladeKey::from_sorted(&[0, 1, 2, 3, 4]),
        ];

        for a in &blades {
            for b in &blades {
                for c in &blades {
                    let (ab, ab_sign) = blade_product_new(a, b, &sig);
                    let (abc_left, left_sign) = if ab_sign == 0 {
                        (BladeKey::SCALAR, 0)
                    } else {
                        let (m, s) = blade_product_new(&ab, c, &sig);
                        (m, (ab_sign as i16 * s as i16) as i8)
                    };

                    let (bc, bc_sign) = blade_product_new(b, c, &sig);
                    let (abc_right, right_sign) = if bc_sign == 0 {
                        (BladeKey::SCALAR, 0)
                    } else {
                        let (m, s) = blade_product_new(a, &bc, &sig);
                        (m, (bc_sign as i16 * s as i16) as i8)
                    };

                    assert_eq!(
                        left_sign, right_sign,
                        "associativity sign: a={:?} b={:?} c={:?}",
                        a, b, c
                    );
                    if left_sign != 0 {
                        assert_eq!(
                            abc_left, abc_right,
                            "associativity blade: a={:?} b={:?} c={:?}",
                            a, b, c
                        );
                    }
                }
            }
        }
    }
}