versorc-math 0.1.0

Low-level Clifford Geometric Algebra, Versor Operators, and High-Dimensional Matrix Reductions
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
// ============================================================================
// VERSORC-MATH: GENERALIZED CLIFFORD MULTIVECTOR IMPLEMENTATION
// License: Apache-2.0
// ============================================================================

use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
use crate::blade::Blade;

/// Fixed-size stack-allocated Multivector in Clifford Algebra Cl(p, q, r).
/// - P, Q, R: Metric signature.
/// - N: Algebra dimension (2^(P + Q + R)).
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Multivector<const P: usize, const Q: usize, const R: usize, const N: usize> {
    pub coords: [f32; N],
}

impl<const P: usize, const Q: usize, const R: usize, const N: usize> Default
    for Multivector<P, Q, R, N>
{
    #[inline(always)]
    fn default() -> Self {
        Self::zero()
    }
}

impl<const P: usize, const Q: usize, const R: usize, const N: usize> Multivector<P, Q, R, N> {
    /// Zero multivector.
    #[inline(always)]
    pub const fn zero() -> Self {
        Self { coords: [0.0f32; N] }
    }

    /// Scalar multivector: alpha * 1.
    #[inline]
    pub fn from_scalar(alpha: f32) -> Self {
        let mut mv = Self::zero();
        mv.coords[0] = alpha;
        mv
    }

    /// Creates a 1-vector from coordinate slice.
    #[inline]
    pub fn from_vector(vec: &[f32]) -> Self {
        let dim = P + Q + R;
        assert!(vec.len() <= dim, "Vector dimension exceeds algebra basis");
        let mut mv = Self::zero();
        for (i, &val) in vec.iter().enumerate() {
            mv.coords[1 << i] = val;
        }
        mv
    }

    /// Basis element e_i (1-indexed: e_1, e_2, ...).
    #[inline]
    pub fn basis(i: usize) -> Self {
        assert!(i >= 1 && i <= P + Q + R, "Basis index out of range");
        let mut mv = Self::zero();
        mv.coords[1 << (i - 1)] = 1.0;
        mv
    }

    /// Scalar part <A>_0.
    #[inline(always)]
    pub fn scalar(&self) -> f32 {
        self.coords[0]
    }

    /// Extracts the k-th grade projection <A>_k.
    #[inline]
    pub fn grade_projection(&self, grade: usize) -> Self {
        let mut res = Self::zero();
        for (mask, &coeff) in self.coords.iter().enumerate() {
            if (mask as u32).count_ones() as usize == grade {
                res.coords[mask] = coeff;
            }
        }
        res
    }

    /// Clifford Reverse: A~ = sum (-1)^{k(k-1)/2} <A>_k.
    #[inline]
    pub fn reverse(&self) -> Self {
        let mut res = Self::zero();
        for (mask, &coeff) in self.coords.iter().enumerate() {
            let sign = Blade::new(mask as u32).reverse_sign();
            res.coords[mask] = coeff * (sign as f32);
        }
        res
    }

    /// Grade Involution: A^ = sum (-1)^k <A>_k.
    #[inline]
    pub fn grade_involution(&self) -> Self {
        let mut res = Self::zero();
        for (mask, &coeff) in self.coords.iter().enumerate() {
            let sign = Blade::new(mask as u32).involution_sign();
            res.coords[mask] = coeff * (sign as f32);
        }
        res
    }

    /// Clifford Conjugation: \bar{A} = sum (-1)^{k(k+1)/2} <A>_k.
    #[inline]
    pub fn conjugation(&self) -> Self {
        let mut res = Self::zero();
        for (mask, &coeff) in self.coords.iter().enumerate() {
            let sign = Blade::new(mask as u32).conjugation_sign();
            res.coords[mask] = coeff * (sign as f32);
        }
        res
    }

    /// Computes the Clifford Geometric Product: C = A * B.
    #[inline]
    pub fn geometric_product(&self, rhs: &Self) -> Self {
        let mut res = Self::zero();
        for (i, &a) in self.coords.iter().enumerate() {
            if a == 0.0 {
                continue;
            }
            let blade_a = Blade::new(i as u32);
            for (j, &b) in rhs.coords.iter().enumerate() {
                if b == 0.0 {
                    continue;
                }
                let blade_b = Blade::new(j as u32);
                if let Some((blade_c, sign)) = blade_a.geometric_product::<P, Q, R>(blade_b) {
                    res.coords[blade_c.0 as usize] += a * b * (sign as f32);
                }
            }
        }
        res
    }

    /// Computes the Exterior Wedge Product: C = A ^ B.
    #[inline]
    pub fn wedge(&self, rhs: &Self) -> Self {
        let mut res = Self::zero();
        for (i, &a) in self.coords.iter().enumerate() {
            if a == 0.0 {
                continue;
            }
            let blade_a = Blade::new(i as u32);
            for (j, &b) in rhs.coords.iter().enumerate() {
                if b == 0.0 {
                    continue;
                }
                let blade_b = Blade::new(j as u32);
                if let Some((blade_c, sign)) = blade_a.wedge_product(blade_b) {
                    res.coords[blade_c.0 as usize] += a * b * (sign as f32);
                }
            }
        }
        res
    }

    /// Computes the Left Contraction: C = A _| B.
    #[inline]
    pub fn left_contraction(&self, rhs: &Self) -> Self {
        let mut res = Self::zero();
        for (i, &a) in self.coords.iter().enumerate() {
            if a == 0.0 {
                continue;
            }
            let blade_a = Blade::new(i as u32);
            for (j, &b) in rhs.coords.iter().enumerate() {
                if b == 0.0 {
                    continue;
                }
                let blade_b = Blade::new(j as u32);
                if let Some((blade_c, sign)) = blade_a.left_contraction::<P, Q, R>(blade_b) {
                    res.coords[blade_c.0 as usize] += a * b * (sign as f32);
                }
            }
        }
        res
    }

    /// Computes the Right Contraction: C = A |_ B.
    #[inline]
    pub fn right_contraction(&self, rhs: &Self) -> Self {
        let mut res = Self::zero();
        for (i, &a) in self.coords.iter().enumerate() {
            if a == 0.0 {
                continue;
            }
            let blade_a = Blade::new(i as u32);
            for (j, &b) in rhs.coords.iter().enumerate() {
                if b == 0.0 {
                    continue;
                }
                let blade_b = Blade::new(j as u32);
                if let Some((blade_c, sign)) = blade_a.right_contraction::<P, Q, R>(blade_b) {
                    res.coords[blade_c.0 as usize] += a * b * (sign as f32);
                }
            }
        }
        res
    }

    /// Scalar product <A, B> = <A B~>_0.
    #[inline]
    pub fn scalar_product(&self, rhs: &Self) -> f32 {
        self.geometric_product(&rhs.reverse()).scalar()
    }

    /// Squared magnitude: ||A||^2 = <A A~>_0.
    #[inline]
    pub fn norm_sq(&self) -> f32 {
        self.scalar_product(self)
    }

    /// Magnitude / L2 norm.
    #[inline]
    pub fn norm(&self) -> f32 {
        self.norm_sq().max(0.0).sqrt()
    }

    /// Normalized multivector: A / ||A||.
    #[inline]
    pub fn normalize(&self) -> Self {
        let n = self.norm();
        if n > 1e-12 {
            *self * (1.0 / n)
        } else {
            *self
        }
    }

    /// Versor sandwich product: X' = V X V~.
    #[inline]
    pub fn sandwich(&self, x: &Self) -> Self {
        self.geometric_product(x).geometric_product(&self.reverse())
    }

    /// Returns the unit pseudoscalar I = e_1 ^ e_2 ^ ... ^ e_n.
    #[inline]
    pub fn pseudoscalar() -> Self {
        let mut mv = Self::zero();
        mv.coords[N - 1] = 1.0;
        mv
    }

    /// Computes the pseudoscalar inverse I^{-1} for non-degenerate metrics (R == 0).
    /// Under signature (P, Q, 0): I^2 = (-1)^{n(n-1)/2 + Q}, so I^{-1} = I * (-1)^{n(n-1)/2 + Q}.
    #[inline]
    pub fn pseudoscalar_inverse() -> Option<Self> {
        if R > 0 {
            return None; // Degenerate metric has I^2 == 0
        }
        let n = P + Q;
        let sign_power = (n * (n - 1) / 2 + Q) % 2;
        let sign = if sign_power == 0 { 1.0f32 } else { -1.0f32 };
        let mut mv = Self::zero();
        mv.coords[N - 1] = sign;
        Some(mv)
    }

    /// Computes the Hodge Dual: A* = A I^{-1} (Macdonald Def 6.16, Hestenes Section 1.2).
    #[inline]
    pub fn dual(&self) -> Option<Self> {
        let inv_i = Self::pseudoscalar_inverse()?;
        Some(self.geometric_product(&inv_i))
    }

    /// Computes the Undual (reverse duality): A I.
    #[inline]
    pub fn undual(&self) -> Self {
        self.geometric_product(&Self::pseudoscalar())
    }

    /// Computes the Lie Commutator Product: [A, B] = 1/2 (A B - B A) (Hestenes Eq 1.55).
    /// Anticommutative: [A, B] = -[B, A]. Satisfies the Jacobi identity.
    #[inline]
    pub fn commutator(&self, rhs: &Self) -> Self {
        (*self * *rhs - *rhs * *self) * 0.5
    }

    /// Computes the Jordan Anticommutator (symmetric product): A ⊙ B = 1/2 (A B + B A).
    #[inline]
    pub fn anticommutator(&self, rhs: &Self) -> Self {
        (*self * *rhs + *rhs * *self) * 0.5
    }

    /// Computes the Regressive (Meet) Product: A ∨ B = (A* ∧ B*).undual()
    /// In non-degenerate spaces, gives the geometric intersection of blade subspaces.
    #[inline]
    pub fn regressive_product(&self, rhs: &Self) -> Option<Self> {
        let dual_a = self.dual()?;
        let dual_b = rhs.dual()?;
        let wedge_dual = dual_a.wedge(&dual_b);
        Some(wedge_dual.undual())
    }

    /// Computes the Hestenes Inner Product (Fat Dot): A • B = sum_{r>=1, s>=1} < <A>_r <B>_s >_{|r - s|}.
    /// As defined in Hestenes & Sobczyk (1984), Section 1.1, Eq. 1.8.
    #[inline]
    pub fn hestenes_inner_product(&self, rhs: &Self) -> Self {
        let dim = P + Q + R;
        let mut res = Self::zero();
        for r in 1..=dim {
            let a_r = self.grade_projection(r);
            if a_r.norm_sq() == 0.0 {
                continue;
            }
            for s in 1..=dim {
                let b_s = rhs.grade_projection(s);
                if b_s.norm_sq() == 0.0 {
                    continue;
                }
                let target_grade = if r >= s { r - s } else { s - r };
                let prod = a_r.geometric_product(&b_s);
                res += prod.grade_projection(target_grade);
            }
        }
        res
    }

    /// Computes the exact closed-form algebraic inverse A^{-1} for multivectors:
    /// - For Cl(2, 0, 0): A^{-1} = \bar{A} / (A \bar{A})_0
    /// - For Cl(3, 0, 0): A^{-1} = \bar{A} (A \bar{A})~ / det(A)
    /// - General fallback for versors: A~ / ||A||^2
    /// Returns None if multivector is singular (det(A) == 0).
    #[inline]
    pub fn inverse(&self) -> Option<Self> {
        if N == 4 && P == 2 && Q == 0 && R == 0 {
            // Cl(2) exact inverse
            let conj = self.conjugation();
            let denom = (*self * conj).scalar();
            if denom.abs() > 1e-12 {
                Some(conj * (1.0 / denom))
            } else {
                None
            }
        } else if N == 8 && P == 3 && Q == 0 && R == 0 {
            // Cl(3) exact closed-form inverse
            let conj = self.conjugation();
            let c = *self * conj; // C in grade 0 and 3
            let c_rev = c.reverse();
            let det = (c * c_rev).scalar();
            if det.abs() > 1e-12 {
                let num = conj * c_rev;
                Some(num * (1.0 / det))
            } else {
                None
            }
        } else {
            // General versor fallback: if A A~ is a nonzero scalar
            let rev = self.reverse();
            let norm_sq = self.norm_sq();
            if norm_sq.abs() > 1e-12 {
                let candidate = rev * (1.0 / norm_sq);
                let test = *self * candidate;
                if (test.scalar() - 1.0).abs() < 1e-4 && (test - Self::from_scalar(1.0)).norm() < 1e-4 {
                    return Some(candidate);
                }
            }
            None
        }
    }
}

// Operator overloads
impl<const P: usize, const Q: usize, const R: usize, const N: usize> Add for Multivector<P, Q, R, N> {
    type Output = Self;
    #[inline]
    fn add(self, rhs: Self) -> Self {
        let mut coords = [0.0f32; N];
        for i in 0..N {
            coords[i] = self.coords[i] + rhs.coords[i];
        }
        Self { coords }
    }
}

impl<const P: usize, const Q: usize, const R: usize, const N: usize> AddAssign for Multivector<P, Q, R, N> {
    #[inline]
    fn add_assign(&mut self, rhs: Self) {
        for i in 0..N {
            self.coords[i] += rhs.coords[i];
        }
    }
}

impl<const P: usize, const Q: usize, const R: usize, const N: usize> Sub for Multivector<P, Q, R, N> {
    type Output = Self;
    #[inline]
    fn sub(self, rhs: Self) -> Self {
        let mut coords = [0.0f32; N];
        for i in 0..N {
            coords[i] = self.coords[i] - rhs.coords[i];
        }
        Self { coords }
    }
}

impl<const P: usize, const Q: usize, const R: usize, const N: usize> SubAssign for Multivector<P, Q, R, N> {
    #[inline]
    fn sub_assign(&mut self, rhs: Self) {
        for i in 0..N {
            self.coords[i] -= rhs.coords[i];
        }
    }
}

impl<const P: usize, const Q: usize, const R: usize, const N: usize> Mul<f32> for Multivector<P, Q, R, N> {
    type Output = Self;
    #[inline]
    fn mul(self, scalar: f32) -> Self {
        let mut coords = [0.0f32; N];
        for i in 0..N {
            coords[i] = self.coords[i] * scalar;
        }
        Self { coords }
    }
}

impl<const P: usize, const Q: usize, const R: usize, const N: usize> Mul for Multivector<P, Q, R, N> {
    type Output = Self;
    #[inline]
    fn mul(self, rhs: Self) -> Self {
        self.geometric_product(&rhs)
    }
}

impl<const P: usize, const Q: usize, const R: usize, const N: usize> Neg for Multivector<P, Q, R, N> {
    type Output = Self;
    #[inline]
    fn neg(self) -> Self {
        let mut coords = [0.0f32; N];
        for i in 0..N {
            coords[i] = -self.coords[i];
        }
        Self { coords }
    }
}

// Canonical Type Aliases
pub type Cl2 = Multivector<2, 0, 0, 4>;
pub type Cl3 = Multivector<3, 0, 0, 8>;
pub type ClMinkowski = Multivector<1, 3, 0, 16>;
pub type ClPGA3D = Multivector<3, 0, 1, 16>;
pub type ClCGA3D = Multivector<4, 1, 0, 32>;
pub type ClHamilton = Multivector<0, 2, 0, 4>;