geoit 0.0.2

Exact geometric algebra with governed multivectors
Documentation
//! Generator participation profile: which generator kinds appear in an Mv's blades.
//!
//! This is dimensional analysis for geometric algebra. Tracking i/d/h participation
//! through operations gives the system a physics-aware type layer without external metadata.

use crate::algebra::blade_new::BladeMask;
use crate::algebra::mv::Mv;
use crate::algebra::signature::Signature;

/// Which generator kinds participate in an Mv's nonzero blades.
/// Compact representation: three bitmasks over the generator index space.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct GeneratorProfile {
    /// Bitmask: bit k set iff some nonzero blade contains generator k (where k is i-type).
    pub i_participation: u64,
    /// Bitmask: bit k set iff some nonzero blade contains generator k (where k is d-type).
    pub d_participation: u64,
    /// Bitmask: bit k set iff some nonzero blade contains generator k (where k is h-type).
    pub h_participation: u64,
}

impl GeneratorProfile {
    pub const EMPTY: GeneratorProfile = GeneratorProfile {
        i_participation: 0,
        d_participation: 0,
        h_participation: 0,
    };

    /// Compute the profile of an Mv under a given signature.
    pub fn compute(mv: &Mv, sig: &Signature) -> Self {
        let mut profile = Self::EMPTY;
        for (mask, _coeff) in mv.blades() {
            profile.absorb_mask(mask, sig);
        }
        profile
    }

    /// Absorb a single blade mask into this profile.
    fn absorb_mask(&mut self, mask: BladeMask, sig: &Signature) {
        for k in 0..sig.n() {
            if mask & (1u64 << k) != 0 {
                match sig.generator_square(k) {
                    -1 => self.i_participation |= 1u64 << k,
                    0 => self.d_participation |= 1u64 << k,
                    1 => self.h_participation |= 1u64 << k,
                    _ => {}
                }
            }
        }
    }

    /// Does this profile involve any degenerate (d-type) generators?
    #[inline]
    pub fn has_degenerate(&self) -> bool {
        self.d_participation != 0
    }

    /// Does this profile involve any imaginary (i-type) generators?
    #[inline]
    pub fn has_imaginary(&self) -> bool {
        self.i_participation != 0
    }

    /// Is this a purely Euclidean profile (only h-type generators)?
    #[inline]
    pub fn is_euclidean(&self) -> bool {
        self.i_participation == 0 && self.d_participation == 0
    }

    /// Union of two profiles (result of any binary operation).
    pub fn union(&self, other: &Self) -> Self {
        GeneratorProfile {
            i_participation: self.i_participation | other.i_participation,
            d_participation: self.d_participation | other.d_participation,
            h_participation: self.h_participation | other.h_participation,
        }
    }

    /// Total number of participating generators.
    pub fn count(&self) -> u32 {
        self.i_participation.count_ones()
            + self.d_participation.count_ones()
            + self.h_participation.count_ones()
    }

    /// All participation combined into a single bitmask.
    pub fn all_participation(&self) -> u64 {
        self.i_participation | self.d_participation | self.h_participation
    }

    /// Predict profile of geometric product result.
    /// Conservative: union of inputs (any generator can appear).
    pub fn predict_geometric(a: &Self, b: &Self) -> Self {
        a.union(b)
    }

    /// Predict profile of outer product result.
    pub fn predict_outer(a: &Self, b: &Self) -> Self {
        a.union(b)
    }

    /// Predict profile of sandwich product: m * v * rev(m).
    /// The result has the profile of v (sandwich cannot introduce generators
    /// not already in v, assuming m's generators are compatible).
    pub fn predict_sandwich(_m: &Self, v: &Self) -> Self {
        *v
    }

    /// Predict profile of reverse/grade involution (same as input).
    pub fn predict_unary(a: &Self) -> Self {
        *a
    }
}

impl std::fmt::Display for GeneratorProfile {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Profile(i={:#b}, d={:#b}, h={:#b})",
            self.i_participation, self.d_participation, self.h_participation
        )
    }
}

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

    #[test]
    fn vga_vector_profile() {
        let sig = Signature::new(0, 0, 3).unwrap();
        let mv = Mv::from_rat_terms(&[
            (0b001, Rat::from(3)),
            (0b010, Rat::from(4)),
            (0b100, Rat::from(5)),
        ]);
        let p = GeneratorProfile::compute(&mv, &sig);
        assert_eq!(p.h_participation, 0b111);
        assert_eq!(p.i_participation, 0);
        assert_eq!(p.d_participation, 0);
        assert!(p.is_euclidean());
    }

    #[test]
    fn pga_point_has_degenerate() {
        let sig = Signature::new(0, 1, 3).unwrap();
        let mv = Mv::from_rat_terms(&[
            (0b0111, Rat::from(1)), // e012 involves d0
            (0b1110, Rat::from(1)), // e123 h-only
        ]);
        let p = GeneratorProfile::compute(&mv, &sig);
        assert!(p.has_degenerate());
        assert!(!p.is_euclidean());
    }

    #[test]
    fn cga_point_has_imaginary() {
        let sig = Signature::new(1, 0, 4).unwrap();
        let mv = Mv::from_rat_terms(&[
            (0b00001, Rat::from(1)), // i0
            (0b00010, Rat::from(1)), // h0
            (0b00100, Rat::from(2)), // h1
        ]);
        let p = GeneratorProfile::compute(&mv, &sig);
        assert!(p.has_imaginary());
        assert!(!p.has_degenerate());
    }

    #[test]
    fn empty_mv_profile() {
        let sig = Signature::new(0, 0, 3).unwrap();
        let mv = Mv::new();
        let p = GeneratorProfile::compute(&mv, &sig);
        assert_eq!(p, GeneratorProfile::EMPTY);
    }

    #[test]
    fn union_profiles() {
        let a = GeneratorProfile {
            i_participation: 0b01,
            d_participation: 0,
            h_participation: 0b10,
        };
        let b = GeneratorProfile {
            i_participation: 0,
            d_participation: 0b01,
            h_participation: 0b01,
        };
        let u = a.union(&b);
        assert_eq!(u.i_participation, 0b01);
        assert_eq!(u.d_participation, 0b01);
        assert_eq!(u.h_participation, 0b11);
    }

    #[test]
    fn sandwich_preserves_v_profile() {
        let m = GeneratorProfile {
            i_participation: 0b11,
            d_participation: 0,
            h_participation: 0b1111,
        };
        let v = GeneratorProfile {
            i_participation: 0,
            d_participation: 0,
            h_participation: 0b111,
        };
        let result = GeneratorProfile::predict_sandwich(&m, &v);
        assert_eq!(result, v);
    }
}