Skip to main content

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}