geoit 0.0.2

Exact geometric algebra with governed multivectors
Documentation
//! Test utilities: random Mv generation, standard algebras, assertion helpers.

use geoit::algebra::blade_new::{grade, BladeMask};
use geoit::algebra::mv::Mv;
use geoit::algebra::signature::Signature;
use geoit::scalar::Rat;

/// Simple deterministic pseudo-random for reproducible tests.
/// Not cryptographic — just needs to produce varied values.
pub struct TestRng {
    state: u64,
}

impl TestRng {
    pub fn new(seed: u64) -> Self {
        TestRng { state: seed }
    }

    pub fn next_u64(&mut self) -> u64 {
        // xorshift64
        self.state ^= self.state << 13;
        self.state ^= self.state >> 7;
        self.state ^= self.state << 17;
        self.state
    }

    /// Random i64 in [-range, range].
    pub fn next_i64(&mut self, range: i64) -> i64 {
        let v = self.next_u64();
        ((v % (2 * range as u64 + 1)) as i64) - range
    }

    /// Random nonzero i64 in [-range, range].
    pub fn next_nonzero_i64(&mut self, range: i64) -> i64 {
        loop {
            let v = self.next_i64(range);
            if v != 0 {
                return v;
            }
        }
    }
}

/// Generate a random sparse Mv for a given signature.
/// `max_terms`: maximum number of nonzero blades.
/// `coeff_range`: coefficients in [-range, range].
pub fn random_mv(sig: &Signature, max_terms: usize, coeff_range: i64, rng: &mut TestRng) -> Mv {
    let n = sig.n();
    let dim = 1u64 << n;
    let num_terms = ((rng.next_u64() % max_terms as u64) + 1) as usize;
    let mut terms = Vec::new();
    for _ in 0..num_terms {
        let mask = rng.next_u64() % dim;
        let coeff = rng.next_nonzero_i64(coeff_range);
        terms.push((mask as BladeMask, Rat::from(coeff)));
    }
    Mv::from_rat_terms(&terms)
}

/// Generate a random Mv with terms only at specified grades.
pub fn random_mv_at_grades(
    sig: &Signature,
    grades: &[u8],
    max_terms: usize,
    coeff_range: i64,
    rng: &mut TestRng,
) -> Mv {
    let n = sig.n();
    // Collect all masks at permitted grades
    let mut allowed: Vec<BladeMask> = Vec::new();
    for mask in 0..(1u64 << n) {
        if grades.contains(&grade(mask)) {
            allowed.push(mask);
        }
    }
    if allowed.is_empty() {
        return Mv::new();
    }
    let num_terms = ((rng.next_u64() % max_terms as u64) + 1).min(allowed.len() as u64) as usize;
    let mut terms = Vec::new();
    for _ in 0..num_terms {
        let idx = (rng.next_u64() as usize) % allowed.len();
        let coeff = rng.next_nonzero_i64(coeff_range);
        terms.push((allowed[idx], Rat::from(coeff)));
    }
    Mv::from_rat_terms(&terms)
}

/// Assert two Mvs are equal, with detailed diff on failure.
pub fn assert_mv_eq(a: &Mv, b: &Mv, context: &str) {
    if a == b {
        return;
    }
    let mut diffs = Vec::new();
    // Collect all masks from both
    let mut all_masks: Vec<BladeMask> = Vec::new();
    for (m, _) in a.blades() {
        all_masks.push(m);
    }
    for (m, _) in b.blades() {
        if !all_masks.contains(&m) {
            all_masks.push(m);
        }
    }
    all_masks.sort();
    for mask in &all_masks {
        let ca = a.coefficient(*mask);
        let cb = b.coefficient(*mask);
        if ca != cb {
            diffs.push(format!(
                "  blade {:#b} (grade {}): left={}, right={}",
                mask,
                grade(*mask),
                ca,
                cb
            ));
        }
    }
    panic!("Mv mismatch [{}]:\n{}", context, diffs.join("\n"));
}

/// All 10 standard algebra signatures.
pub fn all_signatures() -> Vec<(&'static str, Signature)> {
    vec![
        ("VGA2", Signature::new(0, 0, 2).unwrap()),
        ("VGA3", Signature::new(0, 0, 3).unwrap()),
        ("PGA2", Signature::new(0, 1, 2).unwrap()),
        ("PGA3", Signature::new(0, 1, 3).unwrap()),
        ("CGA2", Signature::new(1, 0, 3).unwrap()),
        ("CGA3", Signature::new(1, 0, 4).unwrap()),
        ("STA-WC", Signature::new(3, 0, 1).unwrap()),
        ("STA-EC", Signature::new(1, 0, 3).unwrap()),
        ("QGA3", Signature::new(3, 0, 6).unwrap()),
        ("VGA5", Signature::new(0, 0, 5).unwrap()),
    ]
}