Skip to main content

clifford_codegen/spec/
ir.rs

1//! Intermediate representation for parsed algebra specifications.
2//!
3//! These types represent the validated, processed form of a TOML specification.
4//! They contain all information needed for code generation.
5
6use std::collections::HashMap;
7
8/// Involution kind for norm computation.
9///
10/// Specifies which involution the algebra uses for its canonical norm.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum InvolutionKind {
13    /// Reverse: (-1)^(k(k-1)/2) for grade k.
14    ///
15    /// Default for most algebras (Euclidean, PGA, Minkowski).
16    /// Used for versor operations regardless of norm involution.
17    #[default]
18    Reverse,
19    /// Grade involution: (-1)^k for grade k.
20    ///
21    /// Used for split-complex/hyperbolic numbers.
22    GradeInvolution,
23    /// Clifford conjugate: composition of reverse and grade involution.
24    ///
25    /// Sign = (-1)^(k(k+1)/2) for grade k.
26    CliffordConjugate,
27}
28
29/// Norm configuration for an algebra.
30///
31/// Specifies which involution produces the "primary" norm for the algebra.
32/// The codegen generates `Involute` based on this setting.
33#[derive(Debug, Clone, Default)]
34pub struct NormSpec {
35    /// Which involution to use for primary norm computation.
36    ///
37    /// This determines what `involute()` returns for types in this algebra.
38    pub primary_involution: InvolutionKind,
39}
40
41/// Parsed algebra specification.
42///
43/// This is the main output of the parser, containing all information
44/// needed to generate code for an algebra.
45#[derive(Debug, Clone)]
46pub struct AlgebraSpec {
47    /// Algebra identifier (e.g., "euclidean3").
48    pub name: String,
49    /// Rust module path (e.g., "euclidean::dim3").
50    pub module_path: Option<String>,
51    /// Documentation string.
52    pub description: Option<String>,
53    /// Metric signature.
54    pub signature: SignatureSpec,
55    /// Norm configuration.
56    pub norm: NormSpec,
57    /// Custom blade name mappings (blade index -> name).
58    pub blade_names: HashMap<usize, String>,
59    /// Type definitions.
60    pub types: Vec<TypeSpec>,
61    /// Product specifications.
62    pub products: ProductsSpec,
63    /// Whether completeness checking is enabled.
64    ///
65    /// When `true`, the parser verified that all products between defined types
66    /// have matching output types.
67    pub complete: bool,
68}
69
70/// Metric signature specification.
71///
72/// Defines the basis vectors and their metric (how they square).
73#[derive(Debug, Clone)]
74pub struct SignatureSpec {
75    /// All basis vectors with their properties.
76    pub basis: Vec<BasisVector>,
77    /// Number of positive-square basis vectors.
78    pub p: usize,
79    /// Number of negative-square basis vectors.
80    pub q: usize,
81    /// Number of zero-square (degenerate) basis vectors.
82    pub r: usize,
83}
84
85impl SignatureSpec {
86    /// Total dimension (number of basis vectors).
87    #[inline]
88    pub fn dim(&self) -> usize {
89        self.p + self.q + self.r
90    }
91
92    /// Total number of blades (2^dim).
93    #[inline]
94    pub fn num_blades(&self) -> usize {
95        1 << self.dim()
96    }
97
98    /// Returns indices of basis vectors that square to +1 (positive metric).
99    pub fn positive_indices(&self) -> impl Iterator<Item = usize> + '_ {
100        self.basis.iter().filter(|b| b.metric == 1).map(|b| b.index)
101    }
102
103    /// Returns a vector of metric values indexed by basis index.
104    ///
105    /// This is useful for creating an `Algebra` with the correct per-basis metrics:
106    /// ```ignore
107    /// let metrics = signature.metrics_by_index();
108    /// let algebra = Algebra::from_metrics(metrics);
109    /// ```
110    pub fn metrics_by_index(&self) -> Vec<i8> {
111        let mut metrics = vec![0i8; self.dim()];
112        for bv in &self.basis {
113            metrics[bv.index] = bv.metric;
114        }
115        metrics
116    }
117
118    /// Returns indices of basis vectors that square to -1 (negative metric).
119    pub fn negative_indices(&self) -> impl Iterator<Item = usize> + '_ {
120        self.basis
121            .iter()
122            .filter(|b| b.metric == -1)
123            .map(|b| b.index)
124    }
125
126    /// Returns indices of basis vectors that square to 0 (degenerate/null).
127    ///
128    /// In PGA, this is typically the e0 basis (projective origin).
129    pub fn degenerate_indices(&self) -> impl Iterator<Item = usize> + '_ {
130        self.basis.iter().filter(|b| b.metric == 0).map(|b| b.index)
131    }
132
133    /// Returns true if this algebra has a degenerate metric (r > 0).
134    ///
135    /// Algebras with degenerate metrics (like PGA) have different normalization
136    /// behavior and require bulk/weight decomposition.
137    #[inline]
138    pub fn is_degenerate(&self) -> bool {
139        self.r > 0
140    }
141
142    /// Returns true if this algebra has an indefinite metric (q > 0).
143    ///
144    /// Algebras with indefinite metrics (like Minkowski) can have timelike,
145    /// spacelike, and lightlike vectors.
146    #[inline]
147    pub fn is_indefinite(&self) -> bool {
148        self.q > 0
149    }
150
151    /// Returns the signature type name derived from (p, q, r).
152    ///
153    /// This generates a generic name like `Cl3_0_1` instead of algebra-specific
154    /// names like "Projective3".
155    pub fn signature_type_name(&self) -> String {
156        format!("Cl{}_{}{}", self.p, self.q, self.r)
157    }
158}
159
160/// A single basis vector in the signature.
161#[derive(Debug, Clone)]
162pub struct BasisVector {
163    /// Name of the basis vector (e.g., "e1", "x").
164    pub name: String,
165    /// Index in the algebra (0-based).
166    pub index: usize,
167    /// Metric value: +1, -1, or 0.
168    pub metric: i8,
169}
170
171/// A type definition in the specification.
172#[derive(Debug, Clone)]
173pub struct TypeSpec {
174    /// Type name (e.g., "Rotor", "Vector").
175    pub name: String,
176    /// Grades contained in this type.
177    pub grades: Vec<usize>,
178    /// Documentation string.
179    pub description: Option<String>,
180    /// Fields in order (matching blade layout).
181    pub fields: Vec<FieldSpec>,
182    /// If this type aliases another (same storage layout).
183    pub alias_of: Option<String>,
184    /// Versor information (if this type is a versor).
185    ///
186    /// If present, this type can transform other elements via the sandwich
187    /// product: `X' = V * X * rev(V)`.
188    pub versor: Option<VersorSpec>,
189    /// Whether this type is sparse (uses only a subset of blades within its grades).
190    ///
191    /// Sparse types have explicit blade mappings and don't use all blades of their grades.
192    /// For example, a Line in CGA uses only 6 of the 10 grade-3 blades.
193    pub is_sparse: bool,
194    /// Types that can be transformed via inverse sandwich product.
195    ///
196    /// This allows non-versor types (like Circle in CGA) to perform
197    /// inverse sandwich transformations: `X' = T * X * T⁻¹`.
198    ///
199    /// For versors, this is typically empty (uses auto-inferred targets).
200    /// For blades like Circle, this explicitly lists valid targets.
201    pub inverse_sandwich_targets: Vec<String>,
202}
203
204/// Versor specification for a type.
205///
206/// Versors are elements that can transform other elements via the sandwich
207/// product. This includes rotors (rotations), motors (rigid motions),
208/// and flectors (reflections).
209#[derive(Debug, Clone)]
210pub struct VersorSpec {
211    /// Whether this versor has unit norm (`V * rev(V) = 1`).
212    ///
213    /// Unit versors have simpler sandwich formulas since `V⁻¹ = rev(V)`.
214    pub is_unit: bool,
215    /// Types that this versor can transform via sandwich product.
216    ///
217    /// Empty means auto-detect based on grade compatibility.
218    pub sandwich_targets: Vec<String>,
219}
220
221/// A field in a type.
222#[derive(Debug, Clone)]
223pub struct FieldSpec {
224    /// Field name (e.g., "x", "xy").
225    pub name: String,
226    /// Blade index this field holds (canonical bitmask form).
227    pub blade_index: usize,
228    /// Grade of the blade.
229    pub grade: usize,
230    /// Sign relative to canonical blade ordering (+1 or -1).
231    ///
232    /// When a blade is specified in non-canonical order (e.g., "e20" instead of "e02"),
233    /// this sign captures the parity of the permutation needed to reach canonical form.
234    /// For example, e20 = -e02, so sign = -1.
235    ///
236    /// This sign is applied during product generation to ensure correct results.
237    pub sign: i8,
238}
239
240/// Product specifications for all product types.
241///
242/// Product naming follows [Rigid Geometric Algebra](https://rigidgeometricalgebra.org/) conventions:
243/// - `∧` = wedge (exterior product)
244/// - `∨` = antiwedge (regressive product)
245/// - `★` = dual (bulk dual)
246/// - `☆` = antidual (weight dual)
247/// - `•` = dot (metric inner product, same-grade only)
248/// - `⊚` = antidot (metric antiproduct inner, same-antigrade only)
249#[derive(Debug, Clone, Default)]
250pub struct ProductsSpec {
251    /// Geometric product entries (used for Mul operator on versors).
252    pub geometric: Vec<ProductEntry>,
253    /// Wedge product entries (∧, exterior, grade-raising).
254    pub wedge: Vec<ProductEntry>,
255    /// Left contraction entries.
256    pub left_contraction: Vec<ProductEntry>,
257    /// Right contraction entries.
258    pub right_contraction: Vec<ProductEntry>,
259    /// Antiwedge product entries (∨, regressive/meet).
260    pub antiwedge: Vec<ProductEntry>,
261    /// Scalar product entries.
262    pub scalar: Vec<ProductEntry>,
263    /// Antigeometric product entries (used for antisandwich computation).
264    pub antigeometric: Vec<ProductEntry>,
265    /// Antiscalar product entries.
266    pub antiscalar: Vec<ProductEntry>,
267    /// Bulk contraction entries (a ∨ b★).
268    pub bulk_contraction: Vec<ProductEntry>,
269    /// Weight contraction entries (a ∨ b☆).
270    pub weight_contraction: Vec<ProductEntry>,
271    /// Bulk expansion entries (a ∧ b★).
272    pub bulk_expansion: Vec<ProductEntry>,
273    /// Weight expansion entries (a ∧ b☆).
274    pub weight_expansion: Vec<ProductEntry>,
275    /// Dot product entries (•, metric inner, same-grade only, returns scalar).
276    pub dot: Vec<ProductEntry>,
277    /// Antidot product entries (⊚, metric antiproduct inner, same-antigrade only, returns scalar).
278    pub antidot: Vec<ProductEntry>,
279    /// Projection entries: b ∨ (a ∧ b☆).
280    pub project: Vec<ProductEntry>,
281    /// Antiprojection entries: b ∧ (a ∨ b☆).
282    pub antiproject: Vec<ProductEntry>,
283}
284
285/// A single product entry specifying lhs × rhs → output.
286#[derive(Debug, Clone)]
287pub struct ProductEntry {
288    /// Left-hand side type name (e.g., "Vector", "UnitRotor").
289    pub lhs: String,
290    /// Right-hand side type name.
291    pub rhs: String,
292    /// Output type name.
293    pub output: String,
294    /// Whether the output is a constrained type (uses new_unchecked).
295    pub output_constrained: bool,
296}
297
298/// Wrapper constraint kinds for constraint simplification.
299///
300/// These represent the different normalization and constraint wrappers
301/// that can be applied to geometric types. Each wrapper has specific
302/// algebraic constraints that can be used during Groebner basis simplification.
303#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
304pub enum WrapperKind {
305    /// Unit wrapper: `norm() == 1` (Euclidean norm).
306    ///
307    /// Constraint: `norm_squared - 1 = 0`
308    Unit,
309    /// Bulk wrapper: `bulk_norm() == 1` (PGA versors).
310    ///
311    /// Constraint: `bulk_norm_squared - 1 = 0`
312    Bulk,
313    /// Unitized wrapper: `weight_norm() == 1` (PGA standard form).
314    ///
315    /// Constraint: `weight_norm_squared - 1 = 0`
316    Unitized,
317    /// Ideal wrapper: `weight_norm() ≈ 0` (PGA elements at infinity).
318    ///
319    /// Constraint: each weight component = 0
320    Ideal,
321    /// Proper wrapper: timelike, `|norm²| == 1` (Minkowski 4-velocities).
322    ///
323    /// Constraint: `norm_squared - 1 = 0`
324    Proper,
325    /// Spacelike wrapper: spacelike, `|norm²| == 1` (Minkowski spatial).
326    ///
327    /// Constraint: `norm_squared + 1 = 0`
328    Spacelike,
329    /// Null wrapper: `norm_squared ≈ 0` (Minkowski lightlike).
330    ///
331    /// Constraint: `norm_squared = 0`
332    Null,
333}