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}