Skip to main content

cliffy_test/
generators.rs

1//! Generators for property-based testing with QuickCheck
2//!
3//! These generators create random geometric algebra elements for testing:
4//! - Arbitrary GA3 multivectors
5//! - Unit vectors
6//! - Rotors (unit versors)
7//! - Bivectors
8
9use crate::{bivector, vector, GA3};
10use quickcheck::{Arbitrary, Gen};
11
12/// Generate a random f64 in a range using QuickCheck's Gen
13fn gen_f64_range(g: &mut Gen, min: f64, max: f64) -> f64 {
14    // Use u64::arbitrary and normalize to the desired range
15    // This avoids NaN/infinity issues from f64::arbitrary
16    let val: u64 = u64::arbitrary(g);
17    // Normalize to [0, 1)
18    let normalized = (val as f64) / (u64::MAX as f64);
19    min + normalized * (max - min)
20}
21
22/// Generate an arbitrary GA3 multivector
23pub fn arbitrary_ga3(g: &mut Gen) -> GA3 {
24    let coeffs: Vec<f64> = vec![
25        gen_f64_range(g, -10.0, 10.0), // scalar
26        gen_f64_range(g, -10.0, 10.0), // e1
27        gen_f64_range(g, -10.0, 10.0), // e2
28        gen_f64_range(g, -10.0, 10.0), // e12
29        gen_f64_range(g, -10.0, 10.0), // e3
30        gen_f64_range(g, -10.0, 10.0), // e13
31        gen_f64_range(g, -10.0, 10.0), // e23
32        gen_f64_range(g, -10.0, 10.0), // e123
33    ];
34    GA3::from_coefficients(coeffs)
35}
36
37/// Generate an arbitrary vector (grade 1 element)
38pub fn arbitrary_vector(g: &mut Gen) -> GA3 {
39    vector(
40        gen_f64_range(g, -10.0, 10.0),
41        gen_f64_range(g, -10.0, 10.0),
42        gen_f64_range(g, -10.0, 10.0),
43    )
44}
45
46/// Generate an arbitrary unit vector
47pub fn arbitrary_unit_vector(g: &mut Gen) -> GA3 {
48    loop {
49        let v = vector(
50            gen_f64_range(g, -1.0, 1.0),
51            gen_f64_range(g, -1.0, 1.0),
52            gen_f64_range(g, -1.0, 1.0),
53        );
54        let mag = v.magnitude();
55        if mag > 0.1 {
56            // Avoid division by very small numbers
57            return v.normalize().unwrap_or_else(GA3::zero);
58        }
59    }
60}
61
62/// Generate an arbitrary rotor (unit versor)
63///
64/// A rotor is constructed as exp(B/2) where B is a bivector representing
65/// the rotation plane and angle.
66pub fn arbitrary_rotor(g: &mut Gen) -> GA3 {
67    // Generate random bivector for rotation plane
68    let angle: f64 = gen_f64_range(g, 0.0, std::f64::consts::TAU);
69    let half_angle = angle / 2.0;
70
71    // Generate a random unit bivector
72    let bx: f64 = gen_f64_range(g, -1.0, 1.0);
73    let by: f64 = gen_f64_range(g, -1.0, 1.0);
74    let bz: f64 = gen_f64_range(g, -1.0, 1.0);
75    let b = bivector(bx, by, bz);
76    let b_mag = b.magnitude();
77
78    if b_mag > 0.1 {
79        let b_unit = b.normalize().unwrap_or_else(GA3::zero);
80
81        // Rotor = cos(θ/2) + sin(θ/2) * B
82        let scalar_part = GA3::scalar(half_angle.cos());
83        let bivector_part = &b_unit * half_angle.sin();
84        &scalar_part + &bivector_part
85    } else {
86        // Degenerate case - return identity rotor
87        GA3::scalar(1.0)
88    }
89}
90
91/// Generate an arbitrary bivector (grade 2 element)
92pub fn arbitrary_bivector(g: &mut Gen) -> GA3 {
93    bivector(
94        gen_f64_range(g, -10.0, 10.0), // e12
95        gen_f64_range(g, -10.0, 10.0), // e13
96        gen_f64_range(g, -10.0, 10.0), // e23
97    )
98}
99
100/// Wrapper for QuickCheck Arbitrary trait
101#[derive(Clone, Debug)]
102pub struct ArbitraryGA3(pub GA3);
103
104impl Arbitrary for ArbitraryGA3 {
105    fn arbitrary(g: &mut Gen) -> Self {
106        ArbitraryGA3(arbitrary_ga3(g))
107    }
108
109    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
110        // Shrink by reducing magnitude
111        let mv = self.0.clone();
112        let mag = mv.magnitude();
113        if mag < 0.01 {
114            Box::new(std::iter::empty())
115        } else {
116            let shrunk = &mv * 0.5;
117            Box::new(std::iter::once(ArbitraryGA3(shrunk)))
118        }
119    }
120}
121
122/// Wrapper for arbitrary vectors
123#[derive(Clone, Debug)]
124pub struct ArbitraryVector(pub GA3);
125
126impl Arbitrary for ArbitraryVector {
127    fn arbitrary(g: &mut Gen) -> Self {
128        ArbitraryVector(arbitrary_vector(g))
129    }
130}
131
132/// Wrapper for arbitrary unit vectors
133#[derive(Clone, Debug)]
134pub struct ArbitraryUnitVector(pub GA3);
135
136impl Arbitrary for ArbitraryUnitVector {
137    fn arbitrary(g: &mut Gen) -> Self {
138        ArbitraryUnitVector(arbitrary_unit_vector(g))
139    }
140}
141
142/// Wrapper for arbitrary rotors
143#[derive(Clone, Debug)]
144pub struct ArbitraryRotor(pub GA3);
145
146impl Arbitrary for ArbitraryRotor {
147    fn arbitrary(g: &mut Gen) -> Self {
148        ArbitraryRotor(arbitrary_rotor(g))
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_arbitrary_vector() {
158        let mut gen = Gen::new(10);
159        let v = arbitrary_vector(&mut gen);
160        // Vector should only have grade 1 components
161        assert!(v.get(0).abs() < 1e-10); // No scalar
162        assert!(v.get(3).abs() < 1e-10); // No e12
163        assert!(v.get(5).abs() < 1e-10); // No e13
164        assert!(v.get(6).abs() < 1e-10); // No e23
165        assert!(v.get(7).abs() < 1e-10); // No e123
166    }
167
168    #[test]
169    fn test_arbitrary_unit_vector() {
170        let mut gen = Gen::new(10);
171        for _ in 0..10 {
172            let v = arbitrary_unit_vector(&mut gen);
173            let mag = v.magnitude();
174            assert!((mag - 1.0).abs() < 1e-10, "Unit vector magnitude: {}", mag);
175        }
176    }
177
178    #[test]
179    fn test_arbitrary_rotor_is_unit() {
180        let mut gen = Gen::new(10);
181        for _ in 0..10 {
182            let r = arbitrary_rotor(&mut gen);
183            // A rotor should have magnitude close to 1
184            let norm_sq = r.geometric_product(&r.reverse()).get(0);
185            assert!(
186                (norm_sq - 1.0).abs() < 0.1,
187                "Rotor norm squared: {}",
188                norm_sq
189            );
190        }
191    }
192
193    #[test]
194    fn test_quickcheck_arbitrary() {
195        fn prop_magnitude_positive(v: ArbitraryVector) -> bool {
196            v.0.magnitude() >= 0.0
197        }
198
199        quickcheck::quickcheck(prop_magnitude_positive as fn(ArbitraryVector) -> bool);
200    }
201}