Skip to main content

amari_core/
verified.rs

1//! Formally verified geometric algebra with phantom types and Creusot annotations
2//!
3//! This module provides type-safe, mathematically verified implementations of
4//! geometric algebra operations using phantom types for compile-time invariants
5//! and Creusot annotations for formal verification.
6
7use num_traits::{Float, One, Zero};
8use std::marker::PhantomData;
9
10#[cfg(feature = "formal-verification")]
11use creusot_contracts::macros::{ensures, requires};
12
13/// Phantom type encoding the metric signature of a Clifford algebra Cl(p,q,r)
14/// - P: number of positive basis vectors (e_i² = +1)
15/// - Q: number of negative basis vectors (e_i² = -1)
16/// - R: number of null basis vectors (e_i² = 0)
17pub struct Signature<const P: usize, const Q: usize, const R: usize>;
18
19/// Phantom type for compile-time grade tracking
20pub struct Grade<const G: usize>;
21
22/// Type-level dimension marker for vectors
23pub struct Dim<const D: usize>;
24
25/// A verified multivector with compile-time signature guarantees
26///
27/// This structure ensures at the type level that:
28/// 1. The signature is fixed and consistent
29/// 2. Operations preserve algebraic laws
30/// 3. Dimension bounds are respected
31#[derive(Debug, Clone)]
32pub struct VerifiedMultivector<T, const P: usize, const Q: usize, const R: usize>
33where
34    T: Float + Zero + One,
35{
36    /// Coefficients in lexicographic basis blade order
37    pub(crate) coefficients: Vec<T>,
38    /// Phantom marker for signature
39    _signature: PhantomData<Signature<P, Q, R>>,
40}
41
42impl<T, const P: usize, const Q: usize, const R: usize> VerifiedMultivector<T, P, Q, R>
43where
44    T: Float + Zero + One,
45{
46    /// The total dimension of the algebra
47    pub const DIM: usize = P + Q + R;
48
49    /// The number of basis blades (2^n)
50    pub const BASIS_SIZE: usize = 1 << (P + Q + R);
51
52    /// Create a new verified multivector from coefficients
53    ///
54    /// # Type Invariants
55    /// - Coefficients array must have exactly 2^(P+Q+R) elements
56    /// - Signature is encoded at type level and cannot be violated
57    #[cfg_attr(feature = "formal-verification",
58        requires(coefficients.len() == Self::BASIS_SIZE))]
59    pub fn new(coefficients: Vec<T>) -> Result<Self, &'static str> {
60        if coefficients.len() != Self::BASIS_SIZE {
61            return Err("Coefficient array size must equal 2^(P+Q+R)");
62        }
63
64        Ok(Self {
65            coefficients,
66            _signature: PhantomData,
67        })
68    }
69
70    /// Create a scalar multivector
71    #[cfg_attr(feature = "formal-verification",
72        ensures(result.is_scalar()),
73        ensures(result.coefficients[0] == value),
74        ensures(result.coefficients.len() == Self::BASIS_SIZE))]
75    pub fn scalar(value: T) -> Self {
76        let mut coefficients = vec![T::zero(); Self::BASIS_SIZE];
77        coefficients[0] = value;
78
79        Self {
80            coefficients,
81            _signature: PhantomData,
82        }
83    }
84
85    /// Create a basis vector e_i
86    ///
87    /// # Type Safety
88    /// The index is bounds-checked against the signature dimensions
89    #[cfg_attr(feature = "formal-verification",
90        requires(index < Self::DIM),
91        ensures(result.grade() == 1))]
92    pub fn basis_vector(index: usize) -> Result<Self, &'static str> {
93        if index >= Self::DIM {
94            return Err("Basis vector index exceeds dimension");
95        }
96
97        let mut coefficients = vec![T::zero(); Self::BASIS_SIZE];
98        coefficients[1 << index] = T::one();
99
100        Ok(Self {
101            coefficients,
102            _signature: PhantomData,
103        })
104    }
105
106    /// Check if this is a pure scalar
107    #[cfg_attr(feature = "formal-verification",
108        ensures(result == (self.coefficients[1..].iter().all(|c| c.is_zero()))))]
109    pub fn is_scalar(&self) -> bool {
110        self.coefficients[1..].iter().all(|c| c.is_zero())
111    }
112
113    /// Compute the grade (highest non-zero grade component)
114    #[cfg_attr(feature = "formal-verification",
115        ensures(result <= Self::DIM))]
116    pub fn grade(&self) -> usize {
117        // Count bits to determine grade of each basis blade
118        for (i, coeff) in self.coefficients.iter().enumerate().rev() {
119            if !coeff.is_zero() {
120                return i.count_ones() as usize;
121            }
122        }
123        0
124    }
125
126    /// Addition of multivectors (same signature enforced by types)
127    #[cfg_attr(feature = "formal-verification",
128        ensures(result.coefficients.len() == self.coefficients.len()))]
129    pub fn add(&self, other: &Self) -> Self {
130        let coefficients: Vec<T> = self
131            .coefficients
132            .iter()
133            .zip(&other.coefficients)
134            .map(|(a, b)| *a + *b)
135            .collect();
136
137        Self {
138            coefficients,
139            _signature: PhantomData,
140        }
141    }
142
143    /// Geometric product with compile-time signature matching
144    ///
145    /// # Mathematical Properties (verified by Creusot when enabled)
146    /// - Associativity: (AB)C = A(BC)
147    /// - Distributivity: A(B+C) = AB + AC
148    /// - Identity: 1*A = A*1 = A
149    #[cfg_attr(feature = "formal-verification",
150        requires(self.coefficients.len() == Self::BASIS_SIZE),
151        requires(other.coefficients.len() == Self::BASIS_SIZE),
152        ensures(result.coefficients.len() == Self::BASIS_SIZE))]
153    pub fn geometric_product(&self, other: &Self) -> Self {
154        // Simplified geometric product implementation
155        // In practice, this would use the Cayley table for the signature
156        let mut result = vec![T::zero(); Self::BASIS_SIZE];
157
158        for i in 0..Self::BASIS_SIZE {
159            for j in 0..Self::BASIS_SIZE {
160                let sign = self.compute_product_sign(i, j);
161                let target_index = i ^ j; // XOR gives the resulting basis blade
162                result[target_index] = result[target_index]
163                    + self.coefficients[i] * other.coefficients[j] * T::from(sign).unwrap();
164            }
165        }
166
167        Self {
168            coefficients: result,
169            _signature: PhantomData,
170        }
171    }
172
173    /// Compute the sign (and zero factor) of the product of two basis blades
174    ///
175    /// Accounts for the metric signature (P,Q,R):
176    /// - Basis vectors 0..P square to +1
177    /// - Basis vectors P..P+Q square to -1
178    /// - Basis vectors P+Q..P+Q+R square to 0
179    fn compute_product_sign(&self, blade1: usize, blade2: usize) -> i32 {
180        // Step 1: Count transposition swaps for reorder sign
181        let swaps = self.count_swaps(blade1, blade2);
182        let mut sign: i32 = if swaps.is_multiple_of(2) { 1 } else { -1 };
183
184        // Step 2: Apply metric signature for shared basis vectors
185        // When e_i appears in both blades, e_i * e_i = metric(i)
186        let shared = blade1 & blade2;
187        for i in 0..Self::DIM {
188            if shared & (1 << i) != 0 {
189                if i >= P + Q {
190                    // Null basis vector (R range): e_i² = 0
191                    return 0;
192                } else if i >= P {
193                    // Negative basis vector (Q range): e_i² = -1
194                    sign = -sign;
195                }
196                // Positive basis vector (P range): e_i² = +1, no change
197            }
198        }
199
200        sign
201    }
202
203    /// Count the number of swaps needed to reorder basis vectors
204    fn count_swaps(&self, blade1: usize, blade2: usize) -> usize {
205        // Count inversions when combining blade indices
206        let mut count = 0;
207        for i in 0..Self::DIM {
208            if blade1 & (1 << i) != 0 {
209                for j in 0..i {
210                    if blade2 & (1 << j) != 0 {
211                        count += 1;
212                    }
213                }
214            }
215        }
216        count
217    }
218}
219
220/// Type-safe k-vector (homogeneous grade element)
221///
222/// This type guarantees at compile-time that the multivector
223/// contains only elements of grade K
224pub struct KVector<T, const K: usize, const P: usize, const Q: usize, const R: usize>
225where
226    T: Float + Zero + One,
227{
228    multivector: VerifiedMultivector<T, P, Q, R>,
229    _grade: PhantomData<Grade<K>>,
230}
231
232impl<T, const K: usize, const P: usize, const Q: usize, const R: usize> KVector<T, K, P, Q, R>
233where
234    T: Float + Zero + One,
235{
236    /// Create a k-vector from a general multivector by grade projection
237    #[cfg_attr(feature = "formal-verification",
238        requires(K <= P + Q + R),
239        ensures(result.grade() == K))]
240    pub fn from_multivector(mv: VerifiedMultivector<T, P, Q, R>) -> Self {
241        let mut coefficients = vec![T::zero(); VerifiedMultivector::<T, P, Q, R>::BASIS_SIZE];
242
243        // Extract only grade-K components
244        for (i, coeff) in mv.coefficients.iter().enumerate() {
245            if i.count_ones() as usize == K {
246                coefficients[i] = *coeff;
247            }
248        }
249
250        Self {
251            multivector: VerifiedMultivector {
252                coefficients,
253                _signature: PhantomData,
254            },
255            _grade: PhantomData,
256        }
257    }
258
259    /// Get the grade (always returns K due to type constraint)
260    pub const fn grade(&self) -> usize {
261        K
262    }
263
264    /// Inner product with another k-vector (produces a scalar)
265    pub fn inner_product(&self, other: &Self) -> T {
266        // Inner product of k-vectors of same grade
267        self.multivector
268            .coefficients
269            .iter()
270            .zip(&other.multivector.coefficients)
271            .map(|(a, b)| *a * *b)
272            .fold(T::zero(), |acc, x| acc + x)
273    }
274}
275
276// Trait for outer product with specific grade combinations
277// This works around const generic arithmetic limitations
278pub trait OuterProduct<T, const J: usize, const P: usize, const Q: usize, const R: usize>
279where
280    T: Float + Zero + One,
281{
282    type Output;
283    fn outer_product(&self, other: &KVector<T, J, P, Q, R>) -> Self::Output;
284}
285
286// Implement outer product for specific grade combinations
287// Grade 1 ∧ Grade 1 = Grade 2 (vector ∧ vector = bivector)
288impl<T, const P: usize, const Q: usize, const R: usize> OuterProduct<T, 1, P, Q, R>
289    for KVector<T, 1, P, Q, R>
290where
291    T: Float + Zero + One,
292{
293    type Output = KVector<T, 2, P, Q, R>;
294
295    fn outer_product(&self, other: &KVector<T, 1, P, Q, R>) -> Self::Output {
296        let mut coefficients = vec![T::zero(); VerifiedMultivector::<T, P, Q, R>::BASIS_SIZE];
297
298        // Compute outer product for grade-1 vectors
299        for i in 0..VerifiedMultivector::<T, P, Q, R>::DIM {
300            for j in i + 1..VerifiedMultivector::<T, P, Q, R>::DIM {
301                let blade_i = 1 << i;
302                let blade_j = 1 << j;
303                let blade_ij = blade_i | blade_j;
304
305                coefficients[blade_ij] = self.multivector.coefficients[blade_i]
306                    * other.multivector.coefficients[blade_j]
307                    - self.multivector.coefficients[blade_j]
308                        * other.multivector.coefficients[blade_i];
309            }
310        }
311
312        KVector {
313            multivector: VerifiedMultivector {
314                coefficients,
315                _signature: PhantomData,
316            },
317            _grade: PhantomData,
318        }
319    }
320}
321
322// Grade 1 ∧ Grade 2 = Grade 3 (vector ∧ bivector = trivector)
323impl<T, const P: usize, const Q: usize, const R: usize> OuterProduct<T, 2, P, Q, R>
324    for KVector<T, 1, P, Q, R>
325where
326    T: Float + Zero + One,
327{
328    type Output = KVector<T, 3, P, Q, R>;
329
330    fn outer_product(&self, other: &KVector<T, 2, P, Q, R>) -> Self::Output {
331        let dim = VerifiedMultivector::<T, P, Q, R>::DIM;
332        let basis_size = VerifiedMultivector::<T, P, Q, R>::BASIS_SIZE;
333        let mut coefficients = vec![T::zero(); basis_size];
334
335        // For each grade-1 blade (vectors) and grade-2 blade (bivectors),
336        // compute their wedge product contributing to grade-3 (trivectors)
337        for i in 0..dim {
338            let blade_i = 1usize << i;
339            for j in 0..basis_size {
340                if j.count_ones() != 2 {
341                    continue;
342                }
343                // Only wedge if basis vectors don't overlap
344                if blade_i & j == 0 {
345                    let target = blade_i | j;
346                    // Compute sign from reordering e_i into canonical position within target
347                    let mut swaps = 0;
348                    for k in 0..i {
349                        if j & (1 << k) != 0 {
350                            swaps += 1;
351                        }
352                    }
353                    let sign = if swaps % 2 == 0 { T::one() } else { -T::one() };
354                    coefficients[target] = coefficients[target]
355                        + sign
356                            * self.multivector.coefficients[blade_i]
357                            * other.multivector.coefficients[j];
358                }
359            }
360        }
361
362        KVector {
363            multivector: VerifiedMultivector {
364                coefficients,
365                _signature: PhantomData,
366            },
367            _grade: PhantomData,
368        }
369    }
370}
371
372// Grade 2 ∧ Grade 1 = Grade 3 (bivector ∧ vector = trivector)
373impl<T, const P: usize, const Q: usize, const R: usize> OuterProduct<T, 1, P, Q, R>
374    for KVector<T, 2, P, Q, R>
375where
376    T: Float + Zero + One,
377{
378    type Output = KVector<T, 3, P, Q, R>;
379
380    fn outer_product(&self, other: &KVector<T, 1, P, Q, R>) -> Self::Output {
381        let dim = VerifiedMultivector::<T, P, Q, R>::DIM;
382        let basis_size = VerifiedMultivector::<T, P, Q, R>::BASIS_SIZE;
383        let mut coefficients = vec![T::zero(); basis_size];
384
385        // For each grade-2 blade (bivectors) and grade-1 blade (vectors),
386        // compute their wedge product contributing to grade-3 (trivectors)
387        for i in 0..basis_size {
388            if i.count_ones() != 2 {
389                continue;
390            }
391            for j in 0..dim {
392                let blade_j = 1usize << j;
393                // Only wedge if basis vectors don't overlap
394                if i & blade_j == 0 {
395                    let target = i | blade_j;
396                    // Compute sign: count how many bits in i are above j
397                    let mut swaps = 0;
398                    for k in (j + 1)..dim {
399                        if i & (1 << k) != 0 {
400                            swaps += 1;
401                        }
402                    }
403                    let sign = if swaps % 2 == 0 { T::one() } else { -T::one() };
404                    coefficients[target] = coefficients[target]
405                        + sign
406                            * self.multivector.coefficients[i]
407                            * other.multivector.coefficients[blade_j];
408                }
409            }
410        }
411
412        KVector {
413            multivector: VerifiedMultivector {
414                coefficients,
415                _signature: PhantomData,
416            },
417            _grade: PhantomData,
418        }
419    }
420}
421
422/// A verified rotor with compile-time guarantees
423///
424/// Rotors are even-grade multivectors with unit norm that
425/// represent rotations in the geometric algebra
426pub struct VerifiedRotor<T, const P: usize, const Q: usize, const R: usize>
427where
428    T: Float + Zero + One,
429{
430    multivector: VerifiedMultivector<T, P, Q, R>,
431    /// Phantom data ensuring this is a valid rotor
432    _rotor_invariant: PhantomData<()>,
433}
434
435impl<T, const P: usize, const Q: usize, const R: usize> VerifiedRotor<T, P, Q, R>
436where
437    T: Float + Zero + One,
438{
439    /// Create a rotor from a multivector
440    ///
441    /// # Invariants (verified at runtime, would be proven with Creusot)
442    /// - Must be even grade
443    /// - Must have unit norm
444    #[cfg_attr(feature = "formal-verification",
445        requires(mv.is_even_grade()),
446        requires((mv.norm() - T::one()).abs() < T::from(0.0001).unwrap()),
447        ensures(result.is_ok()))]
448    pub fn new(mv: VerifiedMultivector<T, P, Q, R>) -> Result<Self, &'static str> {
449        if !Self::is_even_grade(&mv) {
450            return Err("Rotor must have even grade");
451        }
452
453        let norm = Self::compute_norm(&mv);
454        if (norm - T::one()).abs() > T::from(0.0001).unwrap() {
455            return Err("Rotor must have unit norm");
456        }
457
458        Ok(Self {
459            multivector: mv,
460            _rotor_invariant: PhantomData,
461        })
462    }
463
464    /// Check if multivector has only even grade components
465    fn is_even_grade(mv: &VerifiedMultivector<T, P, Q, R>) -> bool {
466        for (i, coeff) in mv.coefficients.iter().enumerate() {
467            let grade = i.count_ones() as usize;
468            if !grade.is_multiple_of(2) && !coeff.is_zero() {
469                return false;
470            }
471        }
472        true
473    }
474
475    /// Compute the norm of a multivector
476    fn compute_norm(mv: &VerifiedMultivector<T, P, Q, R>) -> T {
477        mv.coefficients
478            .iter()
479            .map(|c| *c * *c)
480            .fold(T::zero(), |acc, x| acc + x)
481            .sqrt()
482    }
483
484    /// Compose two rotors (rotation composition)
485    ///
486    /// # Type Safety
487    /// - Result is guaranteed to be a valid rotor
488    /// - Unit norm is preserved
489    #[cfg_attr(feature = "formal-verification",
490        ensures(Self::compute_norm(&result.multivector) == T::one()))]
491    pub fn compose(&self, other: &Self) -> Self {
492        let composed = self.multivector.geometric_product(&other.multivector);
493
494        // Normalize to ensure unit norm (should already be close due to properties)
495        let norm = Self::compute_norm(&composed);
496        let normalized = VerifiedMultivector {
497            coefficients: composed.coefficients.iter().map(|c| *c / norm).collect(),
498            _signature: PhantomData,
499        };
500
501        Self {
502            multivector: normalized,
503            _rotor_invariant: PhantomData,
504        }
505    }
506
507    /// Apply rotor to a multivector (rotation/reflection)
508    ///
509    /// Computes R v R† where R† is the reverse of the rotor.
510    pub fn apply_to_multivector(
511        &self,
512        v: &VerifiedMultivector<T, P, Q, R>,
513    ) -> VerifiedMultivector<T, P, Q, R> {
514        // Compute R† (reverse): negate grades where k*(k-1)/2 is odd
515        let mut rev_coeffs = self.multivector.coefficients.clone();
516        for (i, coeff) in rev_coeffs.iter_mut().enumerate() {
517            let grade = i.count_ones() as usize;
518            if grade >= 2 && (grade * (grade - 1) / 2) % 2 == 1 {
519                *coeff = -*coeff;
520            }
521        }
522        let r_rev = VerifiedMultivector::<T, P, Q, R> {
523            coefficients: rev_coeffs,
524            _signature: PhantomData,
525        };
526
527        // Compute R * v * R†
528        let rv = self.multivector.geometric_product(v);
529        rv.geometric_product(&r_rev)
530    }
531}
532
533/// Type-safe vector with compile-time dimension
534pub struct Vector<T, const D: usize>
535where
536    T: Float + Zero + One,
537{
538    pub(crate) data: Vec<T>,
539    _dim: PhantomData<Dim<D>>,
540}
541
542impl<T, const D: usize> Vector<T, D>
543where
544    T: Float + Zero + One,
545{
546    /// Create a new vector (dimension checked at compile time)
547    #[cfg_attr(feature = "formal-verification",
548        requires(data.len() == D),
549        ensures(result.dimension() == D))]
550    pub fn new(data: Vec<T>) -> Result<Self, &'static str> {
551        if data.len() != D {
552            return Err("Vector data length must match dimension");
553        }
554
555        Ok(Self {
556            data,
557            _dim: PhantomData,
558        })
559    }
560
561    /// Get the dimension (always D due to type parameter)
562    pub const fn dimension(&self) -> usize {
563        D
564    }
565
566    /// Dot product (only defined for same dimension - enforced by types)
567    #[cfg_attr(feature = "formal-verification",
568        ensures(result >= T::zero()))] // For positive-definite metrics
569    pub fn dot(&self, other: &Self) -> T {
570        self.data
571            .iter()
572            .zip(&other.data)
573            .map(|(a, b)| *a * *b)
574            .fold(T::zero(), |acc, x| acc + x)
575    }
576
577    /// Add two vectors (same dimension enforced at compile time)
578    pub fn add(&self, other: &Self) -> Self {
579        Self {
580            data: self
581                .data
582                .iter()
583                .zip(&other.data)
584                .map(|(a, b)| *a + *b)
585                .collect(),
586            _dim: PhantomData,
587        }
588    }
589}
590
591#[cfg(test)]
592mod tests {
593    use super::*;
594
595    #[test]
596    fn test_phantom_type_dimension_safety() {
597        // This would fail to compile if dimensions don't match
598        let v1 = Vector::<f64, 3>::new(vec![1.0, 2.0, 3.0]).unwrap();
599        let v2 = Vector::<f64, 3>::new(vec![4.0, 5.0, 6.0]).unwrap();
600        let _v3 = v1.add(&v2); // OK - same dimensions
601
602        // The following would not compile:
603        // let v4 = Vector::<f64, 2>::new(vec![1.0, 2.0]).unwrap();
604        // let v5 = v1.add(&v4); // ERROR: type mismatch
605    }
606
607    #[test]
608    fn test_signature_type_safety() {
609        // Clifford algebra Cl(3,0,0) - 3D Euclidean space
610        let mv1 = VerifiedMultivector::<f64, 3, 0, 0>::scalar(2.0);
611        let mv2 = VerifiedMultivector::<f64, 3, 0, 0>::scalar(3.0);
612        let _mv3 = mv1.add(&mv2); // OK - same signature
613
614        // The following would not compile:
615        // let mv4 = VerifiedMultivector::<f64, 2, 1, 0>::scalar(1.0); // Different signature
616        // let mv5 = mv1.add(&mv4); // ERROR: type mismatch
617    }
618
619    #[test]
620    fn test_grade_preservation() {
621        // Create a bivector (grade 2)
622        let bivector =
623            KVector::<f64, 2, 3, 0, 0>::from_multivector(VerifiedMultivector::scalar(1.0));
624
625        assert_eq!(bivector.grade(), 2);
626        // Grade is guaranteed at compile time
627    }
628
629    #[test]
630    fn test_outer_product_type_safety() {
631        use super::OuterProduct;
632
633        // Create two vectors (grade 1) in 3D Euclidean space
634        let v1 = KVector::<f64, 1, 3, 0, 0>::from_multivector(
635            VerifiedMultivector::basis_vector(0).unwrap(),
636        );
637        let v2 = KVector::<f64, 1, 3, 0, 0>::from_multivector(
638            VerifiedMultivector::basis_vector(1).unwrap(),
639        );
640
641        // Outer product of two vectors gives a bivector (grade 2)
642        let bivector: KVector<f64, 2, 3, 0, 0> = v1.outer_product(&v2);
643
644        // The type system guarantees this is grade 2
645        assert_eq!(bivector.grade(), 2);
646
647        // The following would not compile due to type mismatch:
648        // let wrong: KVector<f64, 3, 3, 0, 0> = v1.outer_product(&v2);
649        // Error: expected KVector<_, 3, _, _, _>, found KVector<_, 2, _, _, _>
650    }
651}