clifford_codegen/algebra/grade.rs
1//! Grade utility functions for blade algebra.
2//!
3//! In geometric algebra, the **grade** of a blade is the number of basis
4//! vectors it contains. For example:
5//! - Scalars have grade 0
6//! - Vectors have grade 1
7//! - Bivectors have grade 2
8//! - etc.
9//!
10//! This module provides utilities for:
11//! - Computing blade grades
12//! - Enumerating blades by grade
13//! - Computing binomial coefficients for grade counting
14
15/// Returns the grade (number of basis vectors) of a blade.
16///
17/// The grade equals the number of 1-bits in the blade's bitmask index.
18///
19/// # Example
20///
21/// ```
22/// use clifford_codegen::algebra::grade;
23///
24/// assert_eq!(grade(0b000), 0); // scalar
25/// assert_eq!(grade(0b001), 1); // e1
26/// assert_eq!(grade(0b011), 2); // e12
27/// assert_eq!(grade(0b111), 3); // e123
28/// ```
29#[inline]
30pub const fn grade(blade: usize) -> usize {
31 blade.count_ones() as usize
32}
33
34/// Returns all blade indices of a given grade in an n-dimensional algebra.
35///
36/// Blades are returned in ascending index order (canonical ordering).
37///
38/// # Arguments
39///
40/// * `dim` - The dimension of the algebra (number of basis vectors)
41/// * `g` - The target grade
42///
43/// # Returns
44///
45/// A vector of blade indices with the specified grade.
46///
47/// # Example
48///
49/// ```
50/// use clifford_codegen::algebra::blades_of_grade;
51///
52/// // 3D algebra: grade-2 blades (bivectors)
53/// let bivectors = blades_of_grade(3, 2);
54/// assert_eq!(bivectors, vec![0b011, 0b101, 0b110]); // e12, e13, e23
55///
56/// // 3D algebra: grade-0 blade (scalar)
57/// let scalars = blades_of_grade(3, 0);
58/// assert_eq!(scalars, vec![0]);
59///
60/// // 3D algebra: grade-3 blade (pseudoscalar)
61/// let pseudoscalar = blades_of_grade(3, 3);
62/// assert_eq!(pseudoscalar, vec![0b111]); // e123
63/// ```
64pub fn blades_of_grade(dim: usize, g: usize) -> Vec<usize> {
65 (0..(1 << dim)).filter(|&b| grade(b) == g).collect()
66}
67
68/// Returns all blade indices for the given grades in an n-dimensional algebra.
69///
70/// Blades are returned grouped by grade, with each grade's blades in
71/// ascending index order.
72///
73/// # Arguments
74///
75/// * `dim` - The dimension of the algebra (number of basis vectors)
76/// * `grades` - The target grades
77///
78/// # Example
79///
80/// ```
81/// use clifford_codegen::algebra::blades_of_grades;
82///
83/// // 3D even subalgebra: grades 0 and 2
84/// let even = blades_of_grades(3, &[0, 2]);
85/// assert_eq!(even, vec![0, 0b011, 0b101, 0b110]); // 1, e12, e13, e23
86/// ```
87pub fn blades_of_grades(dim: usize, grades: &[usize]) -> Vec<usize> {
88 grades
89 .iter()
90 .flat_map(|&g| blades_of_grade(dim, g))
91 .collect()
92}
93
94/// Computes the binomial coefficient C(n, k) = n! / (k! * (n-k)!).
95///
96/// This gives the number of blades of grade k in an n-dimensional algebra.
97///
98/// # Example
99///
100/// ```
101/// use clifford_codegen::algebra::binomial;
102///
103/// // Number of bivectors in 3D: C(3, 2) = 3
104/// assert_eq!(binomial(3, 2), 3);
105///
106/// // Number of vectors in 4D: C(4, 1) = 4
107/// assert_eq!(binomial(4, 1), 4);
108///
109/// // Number of scalars: always 1
110/// assert_eq!(binomial(5, 0), 1);
111///
112/// // Edge cases
113/// assert_eq!(binomial(5, 5), 1);
114/// assert_eq!(binomial(5, 6), 0); // k > n
115/// ```
116pub const fn binomial(n: usize, k: usize) -> usize {
117 if k > n {
118 return 0;
119 }
120 if k == 0 || k == n {
121 return 1;
122 }
123
124 // Use smaller k for efficiency: C(n, k) = C(n, n-k)
125 let k = if k > n - k { n - k } else { k };
126
127 let mut result = 1;
128 let mut i = 0;
129 while i < k {
130 result = result * (n - i) / (i + 1);
131 i += 1;
132 }
133 result
134}
135
136/// Grade selection for outer product (wedge).
137///
138/// The outer product `a ∧ b` has grade = grade(a) + grade(b), but only
139/// if the result doesn't exceed the algebra dimension. Otherwise the
140/// product is zero.
141///
142/// # Example
143///
144/// ```
145/// use clifford_codegen::algebra::outer_grade;
146///
147/// // In 3D: bivector ∧ vector = trivector (grade 3)
148/// assert_eq!(outer_grade(2, 1, 3), Some(3));
149///
150/// // In 3D: bivector ∧ bivector = 0 (grade 4 > dim 3)
151/// assert_eq!(outer_grade(2, 2, 3), None);
152/// ```
153pub const fn outer_grade(grade_a: usize, grade_b: usize, dim: usize) -> Option<usize> {
154 let result = grade_a + grade_b;
155 if result <= dim { Some(result) } else { None }
156}
157
158/// Grade selection for left contraction.
159///
160/// The left contraction `a ⌋ b` has grade = grade(b) - grade(a), but only
161/// if grade(a) ≤ grade(b). Otherwise the product is zero.
162///
163/// # Example
164///
165/// ```
166/// use clifford_codegen::algebra::left_contraction_grade;
167///
168/// // vector ⌋ bivector = vector (grade 1)
169/// assert_eq!(left_contraction_grade(1, 2), Some(1));
170///
171/// // bivector ⌋ vector = 0 (grade 2 > grade 1)
172/// assert_eq!(left_contraction_grade(2, 1), None);
173/// ```
174pub const fn left_contraction_grade(grade_a: usize, grade_b: usize) -> Option<usize> {
175 if grade_a <= grade_b {
176 Some(grade_b - grade_a)
177 } else {
178 None
179 }
180}
181
182/// Grade selection for inner product (symmetric).
183///
184/// The inner product `a · b` has grade = |grade(a) - grade(b)|.
185///
186/// # Example
187///
188/// ```
189/// use clifford_codegen::algebra::inner_grade;
190///
191/// // vector · vector = scalar (grade 0)
192/// assert_eq!(inner_grade(1, 1), 0);
193///
194/// // bivector · vector = vector (grade 1)
195/// assert_eq!(inner_grade(2, 1), 1);
196/// ```
197pub const fn inner_grade(grade_a: usize, grade_b: usize) -> usize {
198 grade_a.abs_diff(grade_b)
199}
200
201/// Returns all grades present in the geometric product of two blades.
202///
203/// The geometric product of a grade-a blade with a grade-b blade can
204/// produce blades of grades from |a - b| to a + b, stepping by 2
205/// (preserving parity).
206///
207/// # Example
208///
209/// ```
210/// use clifford_codegen::algebra::geometric_grades;
211///
212/// // vector * vector = scalar + bivector (grades 0, 2)
213/// assert_eq!(geometric_grades(1, 1, 4), vec![0, 2]);
214///
215/// // bivector * vector = vector + trivector (grades 1, 3)
216/// assert_eq!(geometric_grades(2, 1, 4), vec![1, 3]);
217/// ```
218pub fn geometric_grades(grade_a: usize, grade_b: usize, dim: usize) -> Vec<usize> {
219 let min_grade = grade_a.abs_diff(grade_b);
220 let max_grade = (grade_a + grade_b).min(dim);
221
222 (min_grade..=max_grade).step_by(2).collect()
223}
224
225/// Sign factor for the reverse of a k-blade.
226///
227/// The reverse operation flips the order of basis vectors in a blade,
228/// introducing a sign of `(-1)^(k(k-1)/2)` where k is the grade.
229///
230/// The pattern is: `++--++--...` (repeating every 4 grades)
231///
232/// | Grade | k(k-1)/2 | Sign |
233/// |-------|----------|------|
234/// | 0 | 0 | +1 |
235/// | 1 | 0 | +1 |
236/// | 2 | 1 | -1 |
237/// | 3 | 3 | -1 |
238/// | 4 | 6 | +1 |
239/// | 5 | 10 | +1 |
240/// | 6 | 15 | -1 |
241/// | 7 | 21 | -1 |
242///
243/// # Example
244///
245/// ```
246/// use clifford_codegen::algebra::reverse_sign;
247///
248/// assert_eq!(reverse_sign(0), 1); // scalar: +
249/// assert_eq!(reverse_sign(1), 1); // vector: +
250/// assert_eq!(reverse_sign(2), -1); // bivector: -
251/// assert_eq!(reverse_sign(3), -1); // trivector: -
252/// assert_eq!(reverse_sign(4), 1); // 4-vector: +
253/// ```
254#[inline]
255pub const fn reverse_sign(grade: usize) -> i8 {
256 // (-1)^(k(k-1)/2)
257 let exponent = (grade * grade.saturating_sub(1)) / 2;
258 if exponent.is_multiple_of(2) { 1 } else { -1 }
259}
260
261/// Sign factor for the antireverse of a k-blade in an n-dimensional algebra.
262///
263/// The antireverse is the dual of the reverse (or equivalently, the reverse
264/// of the dual). For a k-blade in n dimensions, the sign is the reverse sign
265/// of the dual grade (n - k).
266///
267/// # Arguments
268///
269/// * `grade` - The grade of the blade
270/// * `dim` - The dimension of the algebra
271///
272/// # Example
273///
274/// ```
275/// use clifford_codegen::algebra::antireverse_sign;
276///
277/// // In 3D algebra:
278/// assert_eq!(antireverse_sign(0, 3), -1); // dual is grade 3, reverse sign is -1
279/// assert_eq!(antireverse_sign(1, 3), -1); // dual is grade 2, reverse sign is -1
280/// assert_eq!(antireverse_sign(2, 3), 1); // dual is grade 1, reverse sign is +1
281/// assert_eq!(antireverse_sign(3, 3), 1); // dual is grade 0, reverse sign is +1
282/// ```
283#[inline]
284pub const fn antireverse_sign(grade: usize, dim: usize) -> i8 {
285 reverse_sign(dim - grade)
286}
287
288/// Sign factor for the grade involution of a k-blade.
289///
290/// The grade involution (also called main involution) negates blades of odd grade.
291/// The sign is `(-1)^k` where k is the grade.
292///
293/// The pattern is: `+-+-+-...` (alternating every grade)
294///
295/// | Grade | Sign |
296/// |-------|------|
297/// | 0 | +1 |
298/// | 1 | -1 |
299/// | 2 | +1 |
300/// | 3 | -1 |
301/// | 4 | +1 |
302///
303/// # Example
304///
305/// ```
306/// use clifford_codegen::algebra::grade_involution_sign;
307///
308/// assert_eq!(grade_involution_sign(0), 1); // scalar: +
309/// assert_eq!(grade_involution_sign(1), -1); // vector: -
310/// assert_eq!(grade_involution_sign(2), 1); // bivector: +
311/// assert_eq!(grade_involution_sign(3), -1); // trivector: -
312/// ```
313#[inline]
314pub const fn grade_involution_sign(grade: usize) -> i8 {
315 // (-1)^k
316 if grade.is_multiple_of(2) { 1 } else { -1 }
317}
318
319/// Sign factor for the Clifford conjugate of a k-blade.
320///
321/// The Clifford conjugate combines reverse and grade involution.
322/// The sign is `(-1)^(k(k+1)/2)` where k is the grade.
323///
324/// The pattern is: `+--++--+...` (repeating every 4 grades)
325///
326/// | Grade | k(k+1)/2 | Sign |
327/// |-------|----------|------|
328/// | 0 | 0 | +1 |
329/// | 1 | 1 | -1 |
330/// | 2 | 3 | -1 |
331/// | 3 | 6 | +1 |
332/// | 4 | 10 | +1 |
333/// | 5 | 15 | -1 |
334/// | 6 | 21 | -1 |
335/// | 7 | 28 | +1 |
336///
337/// # Example
338///
339/// ```
340/// use clifford_codegen::algebra::clifford_conjugate_sign;
341///
342/// assert_eq!(clifford_conjugate_sign(0), 1); // scalar: +
343/// assert_eq!(clifford_conjugate_sign(1), -1); // vector: -
344/// assert_eq!(clifford_conjugate_sign(2), -1); // bivector: -
345/// assert_eq!(clifford_conjugate_sign(3), 1); // trivector: +
346/// assert_eq!(clifford_conjugate_sign(4), 1); // 4-vector: +
347/// ```
348#[inline]
349pub const fn clifford_conjugate_sign(grade: usize) -> i8 {
350 // (-1)^(k(k+1)/2)
351 let exponent = (grade * (grade + 1)) / 2;
352 if exponent.is_multiple_of(2) { 1 } else { -1 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
360 fn grade_from_popcount() {
361 assert_eq!(grade(0b0000), 0);
362 assert_eq!(grade(0b0001), 1);
363 assert_eq!(grade(0b0011), 2);
364 assert_eq!(grade(0b0111), 3);
365 assert_eq!(grade(0b1111), 4);
366 assert_eq!(grade(0b10101), 3);
367 }
368
369 #[test]
370 fn blades_of_grade_3d() {
371 // Grade 0: scalar
372 assert_eq!(blades_of_grade(3, 0), vec![0]);
373
374 // Grade 1: vectors
375 assert_eq!(blades_of_grade(3, 1), vec![1, 2, 4]);
376
377 // Grade 2: bivectors
378 assert_eq!(blades_of_grade(3, 2), vec![3, 5, 6]);
379
380 // Grade 3: pseudoscalar
381 assert_eq!(blades_of_grade(3, 3), vec![7]);
382
383 // Grade 4: empty (exceeds dimension)
384 assert_eq!(blades_of_grade(3, 4), Vec::<usize>::new());
385 }
386
387 #[test]
388 fn blades_of_grades_even_subalgebra() {
389 // Even subalgebra in 3D: grades 0 and 2
390 let even = blades_of_grades(3, &[0, 2]);
391 assert_eq!(even, vec![0, 3, 5, 6]);
392 }
393
394 #[test]
395 fn binomial_coefficients() {
396 // Pascal's triangle row 4: 1 4 6 4 1
397 assert_eq!(binomial(4, 0), 1);
398 assert_eq!(binomial(4, 1), 4);
399 assert_eq!(binomial(4, 2), 6);
400 assert_eq!(binomial(4, 3), 4);
401 assert_eq!(binomial(4, 4), 1);
402
403 // Edge cases
404 assert_eq!(binomial(0, 0), 1);
405 assert_eq!(binomial(5, 6), 0);
406 }
407
408 #[test]
409 fn binomial_counts_blades() {
410 // Number of blades of each grade equals binomial coefficient
411 for dim in 0..=6 {
412 for g in 0..=dim {
413 let count = blades_of_grade(dim, g).len();
414 assert_eq!(count, binomial(dim, g), "dim={}, grade={}", dim, g);
415 }
416 }
417 }
418
419 #[test]
420 fn total_blades_is_power_of_two() {
421 // Total blades = sum of C(n,k) for k=0..n = 2^n
422 for dim in 0..=6 {
423 let total: usize = (0..=dim).map(|g| binomial(dim, g)).sum();
424 assert_eq!(total, 1 << dim);
425 }
426 }
427
428 #[test]
429 fn outer_grade_rules() {
430 // Valid outer products
431 assert_eq!(outer_grade(0, 1, 3), Some(1)); // scalar ∧ vector = vector
432 assert_eq!(outer_grade(1, 1, 3), Some(2)); // vector ∧ vector = bivector
433 assert_eq!(outer_grade(1, 2, 3), Some(3)); // vector ∧ bivector = trivector
434
435 // Invalid (exceeds dimension)
436 assert_eq!(outer_grade(2, 2, 3), None); // grade 4 > dim 3
437 assert_eq!(outer_grade(3, 1, 3), None); // grade 4 > dim 3
438 }
439
440 #[test]
441 fn left_contraction_grade_rules() {
442 assert_eq!(left_contraction_grade(0, 2), Some(2)); // scalar ⌋ bivector = bivector
443 assert_eq!(left_contraction_grade(1, 2), Some(1)); // vector ⌋ bivector = vector
444 assert_eq!(left_contraction_grade(2, 2), Some(0)); // bivector ⌋ bivector = scalar
445 assert_eq!(left_contraction_grade(3, 2), None); // trivector ⌋ bivector = 0
446 }
447
448 #[test]
449 fn geometric_product_grades() {
450 // vector * vector = scalar + bivector
451 assert_eq!(geometric_grades(1, 1, 4), vec![0, 2]);
452
453 // bivector * vector = vector + trivector
454 assert_eq!(geometric_grades(2, 1, 4), vec![1, 3]);
455
456 // bivector * bivector = scalar + bivector + 4-vector
457 assert_eq!(geometric_grades(2, 2, 4), vec![0, 2, 4]);
458
459 // Capped by dimension
460 assert_eq!(geometric_grades(2, 2, 3), vec![0, 2]);
461 }
462
463 #[test]
464 fn reverse_sign_pattern() {
465 // Pattern: ++--++--...
466 assert_eq!(reverse_sign(0), 1);
467 assert_eq!(reverse_sign(1), 1);
468 assert_eq!(reverse_sign(2), -1);
469 assert_eq!(reverse_sign(3), -1);
470 assert_eq!(reverse_sign(4), 1);
471 assert_eq!(reverse_sign(5), 1);
472 assert_eq!(reverse_sign(6), -1);
473 assert_eq!(reverse_sign(7), -1);
474 assert_eq!(reverse_sign(8), 1);
475 }
476
477 #[test]
478 fn antireverse_is_dual_of_reverse() {
479 // antireverse_sign(k, n) = reverse_sign(n - k)
480 for dim in 1..=6 {
481 for k in 0..=dim {
482 let dual_grade = dim - k;
483 assert_eq!(
484 antireverse_sign(k, dim),
485 reverse_sign(dual_grade),
486 "dim={}, k={}, dual_grade={}",
487 dim,
488 k,
489 dual_grade
490 );
491 }
492 }
493 }
494
495 #[test]
496 fn antireverse_3d() {
497 // In 3D algebra
498 assert_eq!(antireverse_sign(0, 3), -1); // dual grade 3 -> reverse_sign(3) = -1
499 assert_eq!(antireverse_sign(1, 3), -1); // dual grade 2 -> reverse_sign(2) = -1
500 assert_eq!(antireverse_sign(2, 3), 1); // dual grade 1 -> reverse_sign(1) = +1
501 assert_eq!(antireverse_sign(3, 3), 1); // dual grade 0 -> reverse_sign(0) = +1
502 }
503}