Skip to main content

clifford_codegen/algebra/
signature.rs

1//! Algebra type representing a geometric algebra with a specific metric signature.
2//!
3//! A geometric algebra is defined by its **metric signature** (p, q, r):
4//! - `p` basis vectors that square to `+1` (Euclidean)
5//! - `q` basis vectors that square to `-1` (anti-Euclidean/Minkowski)
6//! - `r` basis vectors that square to `0` (degenerate/null)
7//!
8//! Common algebras:
9//! - **Euclidean 3D**: (3, 0, 0) - standard 3D geometry
10//! - **PGA 3D**: (3, 0, 1) - projective geometric algebra with e₀² = 0
11//! - **CGA 3D**: (4, 1, 0) - conformal geometric algebra with e₊² = 1, e₋² = -1
12//! - **Minkowski**: (3, 1, 0) - spacetime algebra
13
14use std::collections::HashMap;
15
16use super::blade::Blade;
17use super::grade::blades_of_grade;
18use super::sign::basis_product;
19
20/// A geometric algebra defined by its metric signature.
21///
22/// The algebra encapsulates:
23/// - The metric for each basis vector (can be +1, -1, or 0)
24/// - Optional custom names for basis vectors and blades
25///
26/// # Metric Flexibility
27///
28/// Unlike the traditional (p, q, r) signature notation which assumes a fixed
29/// ordering (positive bases first, then negative, then degenerate), this struct
30/// supports arbitrary metric assignments per basis vector. This allows algebras
31/// like Minkowski spacetime to use physics conventions (e.g., e1=space with -1,
32/// e2=time with +1) without being constrained by positional ordering.
33///
34/// # Example
35///
36/// ```
37/// use clifford_codegen::algebra::Algebra;
38///
39/// // Create a 3D Euclidean algebra
40/// let alg = Algebra::euclidean(3);
41/// assert_eq!(alg.dim(), 3);
42/// assert_eq!(alg.num_blades(), 8);
43///
44/// // Metric: all basis vectors square to +1
45/// assert_eq!(alg.metric(0), 1);
46/// assert_eq!(alg.metric(1), 1);
47/// assert_eq!(alg.metric(2), 1);
48///
49/// // Create Minkowski with arbitrary metric assignment
50/// let minkowski = Algebra::from_metrics(vec![-1, 1]); // e1²=-1 (space), e2²=+1 (time)
51/// assert_eq!(minkowski.metric(0), -1);
52/// assert_eq!(minkowski.metric(1), 1);
53/// ```
54#[derive(Clone, Debug)]
55pub struct Algebra {
56    /// Metric value for each basis vector (indexed by basis index).
57    /// Values are +1 (positive), -1 (negative), or 0 (degenerate).
58    metrics: Vec<i8>,
59    /// Custom names for basis vectors (1-indexed in display).
60    basis_names: Vec<String>,
61    /// Custom names for blades (blade index -> name).
62    blade_names: HashMap<usize, String>,
63}
64
65impl Algebra {
66    /// Creates a new algebra with signature (p, q, r).
67    ///
68    /// Basis vectors are ordered: first p positive, then q negative, then r null.
69    /// For arbitrary metric assignments, use [`from_metrics`](Self::from_metrics).
70    ///
71    /// # Example
72    ///
73    /// ```
74    /// use clifford_codegen::algebra::Algebra;
75    ///
76    /// // PGA: 3 Euclidean + 1 degenerate
77    /// let pga = Algebra::new(3, 0, 1);
78    /// assert_eq!(pga.dim(), 4);
79    /// assert_eq!(pga.metric(0), 1);  // e1 squares to +1
80    /// assert_eq!(pga.metric(3), 0);  // e4 squares to 0
81    /// ```
82    pub fn new(p: usize, q: usize, r: usize) -> Self {
83        let dim = p + q + r;
84        let basis_names = (1..=dim).map(|i| format!("e{}", i)).collect();
85
86        // Build metrics vector: p positive, then q negative, then r zero
87        let mut metrics = Vec::with_capacity(dim);
88        metrics.extend(std::iter::repeat_n(1i8, p));
89        metrics.extend(std::iter::repeat_n(-1i8, q));
90        metrics.extend(std::iter::repeat_n(0i8, r));
91
92        Self {
93            metrics,
94            basis_names,
95            blade_names: HashMap::new(),
96        }
97    }
98
99    /// Creates a new algebra from explicit per-basis metric values.
100    ///
101    /// This allows arbitrary metric assignments without positional constraints.
102    /// Each element in the vector specifies the metric for the corresponding
103    /// basis vector: +1 (positive), -1 (negative), or 0 (degenerate).
104    ///
105    /// # Example
106    ///
107    /// ```
108    /// use clifford_codegen::algebra::Algebra;
109    ///
110    /// // Minkowski with physics convention: e1=space(-1), e2=time(+1)
111    /// let minkowski = Algebra::from_metrics(vec![-1, 1]);
112    /// assert_eq!(minkowski.dim(), 2);
113    /// assert_eq!(minkowski.metric(0), -1);  // e1 squares to -1
114    /// assert_eq!(minkowski.metric(1), 1);   // e2 squares to +1
115    /// assert_eq!(minkowski.signature(), (1, 1, 0));  // still reports (p,q,r)
116    /// ```
117    pub fn from_metrics(metrics: Vec<i8>) -> Self {
118        let dim = metrics.len();
119        let basis_names = (1..=dim).map(|i| format!("e{}", i)).collect();
120        Self {
121            metrics,
122            basis_names,
123            blade_names: HashMap::new(),
124        }
125    }
126
127    /// Returns the indices of degenerate (metric=0) basis vectors.
128    ///
129    /// This is useful for PGA calculations that need to identify the
130    /// projective basis vectors regardless of their position.
131    ///
132    /// # Example
133    ///
134    /// ```
135    /// use clifford_codegen::algebra::Algebra;
136    ///
137    /// // PGA with e0 as degenerate
138    /// let pga = Algebra::from_metrics(vec![0, 1, 1, 1]);
139    /// let degenerate: Vec<_> = pga.degenerate_indices().collect();
140    /// assert_eq!(degenerate, vec![0]);
141    /// ```
142    pub fn degenerate_indices(&self) -> impl Iterator<Item = usize> + '_ {
143        self.metrics
144            .iter()
145            .enumerate()
146            .filter(|(_, m)| **m == 0)
147            .map(|(i, _)| i)
148    }
149
150    /// Creates a Euclidean algebra of dimension n.
151    ///
152    /// All basis vectors square to +1.
153    ///
154    /// # Example
155    ///
156    /// ```
157    /// use clifford_codegen::algebra::Algebra;
158    ///
159    /// let e3 = Algebra::euclidean(3);
160    /// assert_eq!(e3.signature(), (3, 0, 0));
161    /// ```
162    pub fn euclidean(n: usize) -> Self {
163        Self::new(n, 0, 0)
164    }
165
166    /// Creates a Projective Geometric Algebra for n-dimensional space.
167    ///
168    /// PGA(n) has signature (n, 0, 1) where the extra degenerate basis
169    /// represents the point at infinity.
170    ///
171    /// # Example
172    ///
173    /// ```
174    /// use clifford_codegen::algebra::Algebra;
175    ///
176    /// let pga3 = Algebra::pga(3);
177    /// assert_eq!(pga3.dim(), 4);
178    /// assert_eq!(pga3.signature(), (3, 0, 1));
179    /// ```
180    pub fn pga(n: usize) -> Self {
181        Self::new(n, 0, 1)
182    }
183
184    /// Creates a Conformal Geometric Algebra for n-dimensional space.
185    ///
186    /// CGA(n) has signature (n+1, 1, 0), adding two extra basis vectors
187    /// e₊ (positive) and e₋ (negative) for the conformal model.
188    ///
189    /// # Example
190    ///
191    /// ```
192    /// use clifford_codegen::algebra::Algebra;
193    ///
194    /// let cga3 = Algebra::cga(3);
195    /// assert_eq!(cga3.dim(), 5);
196    /// assert_eq!(cga3.signature(), (4, 1, 0));
197    /// ```
198    pub fn cga(n: usize) -> Self {
199        Self::new(n + 1, 1, 0)
200    }
201
202    /// Creates a Minkowski algebra with n spatial dimensions.
203    ///
204    /// Minkowski(n) has signature (n, 1, 0), with n spatial dimensions
205    /// and 1 time-like dimension (squares to -1).
206    ///
207    /// # Example
208    ///
209    /// ```
210    /// use clifford_codegen::algebra::Algebra;
211    ///
212    /// let minkowski = Algebra::minkowski(3);
213    /// assert_eq!(minkowski.dim(), 4);
214    /// assert_eq!(minkowski.signature(), (3, 1, 0));
215    /// ```
216    pub fn minkowski(n: usize) -> Self {
217        Self::new(n, 1, 0)
218    }
219
220    /// Returns the total dimension (number of basis vectors).
221    #[inline]
222    pub fn dim(&self) -> usize {
223        self.metrics.len()
224    }
225
226    /// Returns the signature (p, q, r) by counting metric values.
227    ///
228    /// Note: This counts the actual metrics, so it works correctly even
229    /// for algebras created with [`from_metrics`](Self::from_metrics) where
230    /// positive/negative/zero bases may be interleaved.
231    #[inline]
232    pub fn signature(&self) -> (usize, usize, usize) {
233        let p = self.metrics.iter().filter(|&&m| m == 1).count();
234        let q = self.metrics.iter().filter(|&&m| m == -1).count();
235        let r = self.metrics.iter().filter(|&&m| m == 0).count();
236        (p, q, r)
237    }
238
239    /// Returns the total number of blades (2^dim).
240    #[inline]
241    pub fn num_blades(&self) -> usize {
242        1 << self.dim()
243    }
244
245    /// Returns the metric value for basis vector i.
246    ///
247    /// Returns the metric for the i-th basis vector:
248    /// - `+1` for positive (Euclidean) bases
249    /// - `-1` for negative (anti-Euclidean/Minkowski) bases
250    /// - `0` for degenerate (null/projective) bases
251    ///
252    /// # Example
253    ///
254    /// ```
255    /// use clifford_codegen::algebra::Algebra;
256    ///
257    /// // Standard (p,q,r) ordering
258    /// let cga = Algebra::cga(3);  // signature (4, 1, 0)
259    /// assert_eq!(cga.metric(0), 1);   // e1 (positive)
260    /// assert_eq!(cga.metric(3), 1);   // e+ (positive)
261    /// assert_eq!(cga.metric(4), -1);  // e- (negative)
262    ///
263    /// // Arbitrary metric assignment
264    /// let minkowski = Algebra::from_metrics(vec![-1, 1]);
265    /// assert_eq!(minkowski.metric(0), -1);  // e1 (negative/spacelike)
266    /// assert_eq!(minkowski.metric(1), 1);   // e2 (positive/timelike)
267    /// ```
268    #[inline]
269    pub fn metric(&self, i: usize) -> i8 {
270        self.metrics[i]
271    }
272
273    /// Computes the product of two basis blades.
274    ///
275    /// # Returns
276    ///
277    /// A tuple `(sign, result)` where sign ∈ {-1, 0, +1} and result
278    /// is the blade index of the product.
279    ///
280    /// # Example
281    ///
282    /// ```
283    /// use clifford_codegen::algebra::Algebra;
284    ///
285    /// let alg = Algebra::euclidean(3);
286    ///
287    /// // e1 * e2 = e12
288    /// let (sign, result) = alg.basis_product(0b001, 0b010);
289    /// assert_eq!(sign, 1);
290    /// assert_eq!(result, 0b011);
291    ///
292    /// // e2 * e1 = -e12
293    /// let (sign, result) = alg.basis_product(0b010, 0b001);
294    /// assert_eq!(sign, -1);
295    /// assert_eq!(result, 0b011);
296    /// ```
297    pub fn basis_product(&self, a: usize, b: usize) -> (i8, usize) {
298        basis_product(a, b, |i| self.metric(i))
299    }
300
301    /// Returns all blades of a given grade.
302    ///
303    /// # Example
304    ///
305    /// ```
306    /// use clifford_codegen::algebra::Algebra;
307    ///
308    /// let alg = Algebra::euclidean(3);
309    /// let bivectors = alg.blades_of_grade(2);
310    ///
311    /// assert_eq!(bivectors.len(), 3);
312    /// assert_eq!(bivectors[0].index(), 0b011); // e12
313    /// assert_eq!(bivectors[1].index(), 0b101); // e13
314    /// assert_eq!(bivectors[2].index(), 0b110); // e23
315    /// ```
316    pub fn blades_of_grade(&self, grade: usize) -> Vec<Blade> {
317        blades_of_grade(self.dim(), grade)
318            .into_iter()
319            .map(Blade::from_index)
320            .collect()
321    }
322
323    /// Returns all blades in the algebra.
324    ///
325    /// Blades are returned in index order (canonical ordering).
326    pub fn all_blades(&self) -> Vec<Blade> {
327        (0..self.num_blades()).map(Blade::from_index).collect()
328    }
329
330    /// Sets a custom name for a basis vector.
331    ///
332    /// # Arguments
333    ///
334    /// * `i` - The basis vector index (0-based)
335    /// * `name` - The custom name
336    ///
337    /// # Example
338    ///
339    /// ```
340    /// use clifford_codegen::algebra::Algebra;
341    ///
342    /// let mut alg = Algebra::euclidean(3);
343    /// alg.set_basis_name(0, "x".to_string());
344    /// alg.set_basis_name(1, "y".to_string());
345    /// alg.set_basis_name(2, "z".to_string());
346    ///
347    /// assert_eq!(alg.basis_name(0), "x");
348    /// assert_eq!(alg.basis_name(1), "y");
349    /// assert_eq!(alg.basis_name(2), "z");
350    /// ```
351    pub fn set_basis_name(&mut self, i: usize, name: String) {
352        if i < self.basis_names.len() {
353            self.basis_names[i] = name;
354        }
355    }
356
357    /// Returns the name of a basis vector.
358    pub fn basis_name(&self, i: usize) -> &str {
359        &self.basis_names[i]
360    }
361
362    /// Sets a custom name for a blade.
363    ///
364    /// # Example
365    ///
366    /// ```
367    /// use clifford_codegen::algebra::{Algebra, Blade};
368    ///
369    /// let mut alg = Algebra::euclidean(3);
370    /// alg.set_blade_name(Blade::from_index(0b011), "xy".to_string());
371    ///
372    /// assert_eq!(alg.blade_name(Blade::from_index(0b011)), "xy");
373    /// ```
374    pub fn set_blade_name(&mut self, blade: Blade, name: String) {
375        self.blade_names.insert(blade.index(), name);
376    }
377
378    /// Returns the name for a blade.
379    ///
380    /// If a custom name was set, returns that. Otherwise, builds
381    /// the name from basis vector names.
382    ///
383    /// # Example
384    ///
385    /// ```
386    /// use clifford_codegen::algebra::{Algebra, Blade};
387    ///
388    /// let alg = Algebra::euclidean(3);
389    ///
390    /// // Default names
391    /// assert_eq!(alg.blade_name(Blade::scalar()), "s");
392    /// assert_eq!(alg.blade_name(Blade::basis(0)), "e1");
393    /// assert_eq!(alg.blade_name(Blade::from_index(0b011)), "e1e2");
394    /// ```
395    pub fn blade_name(&self, blade: Blade) -> String {
396        if let Some(name) = self.blade_names.get(&blade.index()) {
397            return name.clone();
398        }
399
400        if blade.index() == 0 {
401            return "s".to_string();
402        }
403
404        // Build from basis names
405        blade
406            .basis_vectors()
407            .map(|i| self.basis_names[i].as_str())
408            .collect::<Vec<_>>()
409            .join("")
410    }
411
412    /// Returns the canonical index-based name for a blade.
413    ///
414    /// This produces names compatible with the TOML parser format:
415    /// - Scalar: "s" (but scalars shouldn't be in blades section)
416    /// - Basis vectors: "e1", "e2", etc. (1-indexed)
417    /// - Higher blades: "e12", "e123", etc.
418    ///
419    /// # Example
420    ///
421    /// ```
422    /// use clifford_codegen::algebra::{Algebra, Blade};
423    ///
424    /// let alg = Algebra::euclidean(3);
425    ///
426    /// assert_eq!(alg.blade_index_name(Blade::basis(0)), "e1");
427    /// assert_eq!(alg.blade_index_name(Blade::basis(1)), "e2");
428    /// assert_eq!(alg.blade_index_name(Blade::from_index(0b011)), "e12");
429    /// assert_eq!(alg.blade_index_name(Blade::from_index(0b111)), "e123");
430    /// ```
431    pub fn blade_index_name(&self, blade: Blade) -> String {
432        if blade.index() == 0 {
433            return "s".to_string();
434        }
435
436        // Build from 1-indexed basis indices
437        let indices: String = blade
438            .basis_vectors()
439            .map(|i| char::from_digit((i + 1) as u32, 10).unwrap())
440            .collect();
441
442        format!("e{}", indices)
443    }
444}
445
446impl Default for Algebra {
447    fn default() -> Self {
448        Self::euclidean(3)
449    }
450}
451
452#[cfg(test)]
453mod tests {
454    use super::*;
455
456    #[test]
457    fn euclidean_signature() {
458        let alg = Algebra::euclidean(3);
459        assert_eq!(alg.signature(), (3, 0, 0));
460        assert_eq!(alg.dim(), 3);
461        assert_eq!(alg.num_blades(), 8);
462
463        for i in 0..3 {
464            assert_eq!(alg.metric(i), 1);
465        }
466    }
467
468    #[test]
469    fn pga_signature() {
470        let alg = Algebra::pga(3);
471        assert_eq!(alg.signature(), (3, 0, 1));
472        assert_eq!(alg.dim(), 4);
473        assert_eq!(alg.num_blades(), 16);
474
475        // First 3 square to +1, last one to 0
476        assert_eq!(alg.metric(0), 1);
477        assert_eq!(alg.metric(1), 1);
478        assert_eq!(alg.metric(2), 1);
479        assert_eq!(alg.metric(3), 0);
480    }
481
482    #[test]
483    fn cga_signature() {
484        let alg = Algebra::cga(3);
485        assert_eq!(alg.signature(), (4, 1, 0));
486        assert_eq!(alg.dim(), 5);
487        assert_eq!(alg.num_blades(), 32);
488
489        // First 4 square to +1, last one to -1
490        assert_eq!(alg.metric(0), 1);
491        assert_eq!(alg.metric(1), 1);
492        assert_eq!(alg.metric(2), 1);
493        assert_eq!(alg.metric(3), 1);
494        assert_eq!(alg.metric(4), -1);
495    }
496
497    #[test]
498    fn minkowski_signature() {
499        let alg = Algebra::minkowski(3);
500        assert_eq!(alg.signature(), (3, 1, 0));
501        assert_eq!(alg.dim(), 4);
502
503        assert_eq!(alg.metric(0), 1);
504        assert_eq!(alg.metric(1), 1);
505        assert_eq!(alg.metric(2), 1);
506        assert_eq!(alg.metric(3), -1);
507    }
508
509    #[test]
510    fn blades_by_grade() {
511        let alg = Algebra::euclidean(3);
512
513        let scalars = alg.blades_of_grade(0);
514        assert_eq!(scalars.len(), 1);
515        assert_eq!(scalars[0].index(), 0);
516
517        let vectors = alg.blades_of_grade(1);
518        assert_eq!(vectors.len(), 3);
519        assert_eq!(vectors[0].index(), 1);
520        assert_eq!(vectors[1].index(), 2);
521        assert_eq!(vectors[2].index(), 4);
522
523        let bivectors = alg.blades_of_grade(2);
524        assert_eq!(bivectors.len(), 3);
525        assert_eq!(bivectors[0].index(), 3);
526        assert_eq!(bivectors[1].index(), 5);
527        assert_eq!(bivectors[2].index(), 6);
528    }
529
530    #[test]
531    fn custom_names() {
532        let mut alg = Algebra::euclidean(3);
533        alg.set_basis_name(0, "x".to_string());
534        alg.set_basis_name(1, "y".to_string());
535        alg.set_basis_name(2, "z".to_string());
536
537        assert_eq!(alg.blade_name(Blade::basis(0)), "x");
538        assert_eq!(alg.blade_name(Blade::basis(1)), "y");
539        assert_eq!(alg.blade_name(Blade::basis(2)), "z");
540        assert_eq!(alg.blade_name(Blade::from_index(0b011)), "xy");
541        assert_eq!(alg.blade_name(Blade::from_index(0b111)), "xyz");
542    }
543
544    #[test]
545    fn product_euclidean() {
546        let alg = Algebra::euclidean(3);
547
548        // e1 * e2 = e12
549        let (sign, result) = alg.basis_product(1, 2);
550        assert_eq!(sign, 1);
551        assert_eq!(result, 3);
552
553        // e2 * e1 = -e12
554        let (sign, result) = alg.basis_product(2, 1);
555        assert_eq!(sign, -1);
556        assert_eq!(result, 3);
557
558        // e1 * e1 = 1
559        let (sign, result) = alg.basis_product(1, 1);
560        assert_eq!(sign, 1);
561        assert_eq!(result, 0);
562    }
563
564    #[test]
565    fn product_pga_degenerate() {
566        let alg = Algebra::pga(3);
567
568        // e4 * e4 = 0 (degenerate)
569        let (sign, result) = alg.basis_product(8, 8);
570        assert_eq!(sign, 0);
571        assert_eq!(result, 0);
572
573        // e1 * e1 = 1 (non-degenerate)
574        let (sign, result) = alg.basis_product(1, 1);
575        assert_eq!(sign, 1);
576        assert_eq!(result, 0);
577    }
578
579    #[test]
580    fn product_minkowski() {
581        let alg = Algebra::minkowski(3);
582
583        // e4 * e4 = -1 (time-like)
584        let (sign, result) = alg.basis_product(8, 8);
585        assert_eq!(sign, -1);
586        assert_eq!(result, 0);
587
588        // e1 * e1 = +1 (space-like)
589        let (sign, result) = alg.basis_product(1, 1);
590        assert_eq!(sign, 1);
591        assert_eq!(result, 0);
592    }
593}