Skip to main content

clifford_codegen/algebra/
blade.rs

1//! Blade representation and operations.
2//!
3//! A blade is a basis element in a geometric algebra, represented by its
4//! bitmask index where bit `i` indicates the presence of basis vector `eᵢ`.
5
6use std::fmt;
7
8/// Maximum supported dimension.
9///
10/// Limits the algebra to at most 6 basis vectors (64 blades).
11pub const MAX_DIM: usize = 6;
12
13/// A basis blade in a geometric algebra.
14///
15/// Blades are represented as bitmasks where bit `i` indicates the presence
16/// of basis vector `eᵢ`. This representation is always canonical since
17/// bits are inherently ordered.
18///
19/// # Examples
20///
21/// ```
22/// use clifford_codegen::algebra::Blade;
23///
24/// // Scalar (grade 0)
25/// let scalar = Blade::scalar();
26/// assert_eq!(scalar.grade(), 0);
27/// assert_eq!(scalar.index(), 0);
28///
29/// // Basis vector e₁
30/// let e1 = Blade::basis(0);
31/// assert_eq!(e1.grade(), 1);
32/// assert_eq!(e1.index(), 1);
33///
34/// // Bivector e₁₂
35/// let e12 = Blade::from_index(0b11);
36/// assert_eq!(e12.grade(), 2);
37/// ```
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
39pub struct Blade {
40    /// Bitmask representation where bit `i` = 1 means `eᵢ` is present.
41    index: usize,
42}
43
44impl Blade {
45    /// Creates a blade from its bitmask index.
46    ///
47    /// # Example
48    ///
49    /// ```
50    /// use clifford_codegen::algebra::Blade;
51    ///
52    /// let e12 = Blade::from_index(0b11);
53    /// assert_eq!(e12.index(), 3);
54    /// ```
55    #[inline]
56    pub const fn from_index(index: usize) -> Self {
57        Self { index }
58    }
59
60    /// Creates the scalar blade (grade 0, index 0).
61    ///
62    /// # Example
63    ///
64    /// ```
65    /// use clifford_codegen::algebra::Blade;
66    ///
67    /// let s = Blade::scalar();
68    /// assert_eq!(s.index(), 0);
69    /// assert_eq!(s.grade(), 0);
70    /// ```
71    #[inline]
72    pub const fn scalar() -> Self {
73        Self { index: 0 }
74    }
75
76    /// Creates a basis vector blade.
77    ///
78    /// `Blade::basis(i)` creates blade `eᵢ₊₁` (using 0-based indexing internally).
79    ///
80    /// # Example
81    ///
82    /// ```
83    /// use clifford_codegen::algebra::Blade;
84    ///
85    /// let e1 = Blade::basis(0);
86    /// assert_eq!(e1.index(), 1);
87    ///
88    /// let e2 = Blade::basis(1);
89    /// assert_eq!(e2.index(), 2);
90    ///
91    /// let e3 = Blade::basis(2);
92    /// assert_eq!(e3.index(), 4);
93    /// ```
94    #[inline]
95    pub const fn basis(i: usize) -> Self {
96        Self { index: 1 << i }
97    }
98
99    /// Returns the blade's bitmask index.
100    ///
101    /// # Example
102    ///
103    /// ```
104    /// use clifford_codegen::algebra::Blade;
105    ///
106    /// let e12 = Blade::from_index(3);
107    /// assert_eq!(e12.index(), 3);
108    /// ```
109    #[inline]
110    pub const fn index(&self) -> usize {
111        self.index
112    }
113
114    /// Returns the grade (number of basis vectors in this blade).
115    ///
116    /// The grade equals the number of 1-bits in the index.
117    ///
118    /// # Example
119    ///
120    /// ```
121    /// use clifford_codegen::algebra::Blade;
122    ///
123    /// assert_eq!(Blade::scalar().grade(), 0);
124    /// assert_eq!(Blade::basis(0).grade(), 1);
125    /// assert_eq!(Blade::from_index(0b111).grade(), 3);
126    /// ```
127    #[inline]
128    pub const fn grade(&self) -> usize {
129        self.index.count_ones() as usize
130    }
131
132    /// Checks if this blade contains basis vector `i`.
133    ///
134    /// # Example
135    ///
136    /// ```
137    /// use clifford_codegen::algebra::Blade;
138    ///
139    /// let e12 = Blade::from_index(0b11);
140    /// assert!(e12.contains(0)); // contains e1
141    /// assert!(e12.contains(1)); // contains e2
142    /// assert!(!e12.contains(2)); // does not contain e3
143    /// ```
144    #[inline]
145    pub const fn contains(&self, i: usize) -> bool {
146        (self.index >> i) & 1 == 1
147    }
148
149    /// Returns an iterator over the basis vector indices in this blade.
150    ///
151    /// Indices are yielded in ascending order.
152    ///
153    /// # Example
154    ///
155    /// ```
156    /// use clifford_codegen::algebra::Blade;
157    ///
158    /// let e135 = Blade::from_index(0b10101);
159    /// let indices: Vec<_> = e135.basis_vectors().collect();
160    /// assert_eq!(indices, vec![0, 2, 4]);
161    /// ```
162    pub fn basis_vectors(&self) -> impl Iterator<Item = usize> + '_ {
163        (0..MAX_DIM).filter(move |&i| self.contains(i))
164    }
165
166    /// Returns the number of basis vectors in this blade.
167    ///
168    /// This is equivalent to `self.grade()`.
169    #[inline]
170    pub const fn len(&self) -> usize {
171        self.grade()
172    }
173
174    /// Returns true if this is the scalar blade.
175    #[inline]
176    pub const fn is_empty(&self) -> bool {
177        self.index == 0
178    }
179
180    /// Computes the XOR of two blade indices.
181    ///
182    /// This gives the result blade of the geometric product (before
183    /// considering sign).
184    ///
185    /// # Example
186    ///
187    /// ```
188    /// use clifford_codegen::algebra::Blade;
189    ///
190    /// let e1 = Blade::basis(0);
191    /// let e2 = Blade::basis(1);
192    /// let e12 = e1.xor(e2);
193    /// assert_eq!(e12.index(), 3);
194    /// ```
195    #[inline]
196    pub const fn xor(&self, other: Self) -> Self {
197        Self {
198            index: self.index ^ other.index,
199        }
200    }
201
202    /// Returns true if this blade shares any basis vectors with another.
203    ///
204    /// # Example
205    ///
206    /// ```
207    /// use clifford_codegen::algebra::Blade;
208    ///
209    /// let e12 = Blade::from_index(0b11);
210    /// let e23 = Blade::from_index(0b110);
211    /// let e45 = Blade::from_index(0b11000);
212    ///
213    /// assert!(e12.overlaps(e23)); // share e2
214    /// assert!(!e12.overlaps(e45)); // disjoint
215    /// ```
216    #[inline]
217    pub const fn overlaps(&self, other: Self) -> bool {
218        (self.index & other.index) != 0
219    }
220
221    /// Returns the blade name using standard notation.
222    ///
223    /// Uses 1-based indexing for display: `e1`, `e12`, `e123`, etc.
224    ///
225    /// # Example
226    ///
227    /// ```
228    /// use clifford_codegen::algebra::Blade;
229    ///
230    /// assert_eq!(Blade::scalar().name(), "1");
231    /// assert_eq!(Blade::basis(0).name(), "e1");
232    /// assert_eq!(Blade::from_index(0b11).name(), "e12");
233    /// ```
234    pub fn name(&self) -> String {
235        if self.index == 0 {
236            return "1".to_string();
237        }
238
239        let mut name = String::from("e");
240        for i in self.basis_vectors() {
241            name.push_str(&(i + 1).to_string());
242        }
243        name
244    }
245}
246
247impl fmt::Display for Blade {
248    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249        write!(f, "{}", self.name())
250    }
251}
252
253impl Default for Blade {
254    fn default() -> Self {
255        Self::scalar()
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn scalar_properties() {
265        let s = Blade::scalar();
266        assert_eq!(s.index(), 0);
267        assert_eq!(s.grade(), 0);
268        assert!(s.is_empty());
269        assert_eq!(s.name(), "1");
270    }
271
272    #[test]
273    fn basis_vector_properties() {
274        for i in 0..MAX_DIM {
275            let e = Blade::basis(i);
276            assert_eq!(e.index(), 1 << i);
277            assert_eq!(e.grade(), 1);
278            assert!(e.contains(i));
279            assert!(!e.is_empty());
280
281            for j in 0..MAX_DIM {
282                if j != i {
283                    assert!(!e.contains(j));
284                }
285            }
286        }
287    }
288
289    #[test]
290    fn bivector_properties() {
291        let e12 = Blade::from_index(0b11);
292        assert_eq!(e12.grade(), 2);
293        assert!(e12.contains(0));
294        assert!(e12.contains(1));
295        assert!(!e12.contains(2));
296        assert_eq!(e12.name(), "e12");
297    }
298
299    #[test]
300    fn xor_computes_product_blade() {
301        let e1 = Blade::basis(0);
302        let e2 = Blade::basis(1);
303        let e12 = e1.xor(e2);
304        assert_eq!(e12.index(), 0b11);
305
306        // e12 * e2 = e1
307        let e1_back = e12.xor(e2);
308        assert_eq!(e1_back.index(), e1.index());
309    }
310
311    #[test]
312    fn basis_vectors_iterator() {
313        let e135 = Blade::from_index(0b10101);
314        let indices: Vec<_> = e135.basis_vectors().collect();
315        assert_eq!(indices, vec![0, 2, 4]);
316    }
317
318    #[test]
319    fn overlaps_detection() {
320        let e12 = Blade::from_index(0b11);
321        let e23 = Blade::from_index(0b110);
322        let e34 = Blade::from_index(0b1100);
323
324        assert!(e12.overlaps(e23)); // share e2
325        assert!(e23.overlaps(e34)); // share e3
326        assert!(!e12.overlaps(e34)); // disjoint
327    }
328
329    #[test]
330    fn ordering() {
331        let blades: Vec<Blade> = (0..8).map(Blade::from_index).collect();
332
333        // Verify they sort by index
334        let mut sorted = blades.clone();
335        sorted.sort();
336        assert_eq!(blades, sorted);
337    }
338}