Skip to main content

clifford_codegen/codegen/
conversions.rs

1//! Multivector conversion generation.
2//!
3//! This module generates `From` implementations for converting specialized
4//! types to and from generic `Multivector`.
5
6use proc_macro2::TokenStream;
7use quote::{format_ident, quote};
8
9use crate::algebra::Algebra;
10use crate::spec::{AlgebraSpec, TypeSpec};
11
12/// Generates Multivector conversion implementations.
13///
14/// # Example
15///
16/// ```
17/// use clifford_codegen::algebra::Algebra;
18/// use clifford_codegen::codegen::ConversionsGenerator;
19/// use clifford_codegen::spec::parse_spec;
20///
21/// let spec = parse_spec(r#"
22/// [algebra]
23/// name = "euclidean2"
24/// complete = false
25///
26/// [signature]
27/// positive = ["e1", "e2"]
28///
29/// [types.Vector]
30/// grades = [1]
31/// field_map = [
32///   { name = "x", blade = "e1" },
33///   { name = "y", blade = "e2" }
34/// ]
35/// "#).unwrap();
36///
37/// let algebra = Algebra::euclidean(2);
38/// let generator = ConversionsGenerator::new(&spec, &algebra);
39///
40/// let tokens = generator.generate_conversions_file();
41/// let code = tokens.to_string();
42///
43/// assert!(code.contains("From < Vector"));
44/// assert!(code.contains("for Multivector"));
45/// ```
46pub struct ConversionsGenerator<'a> {
47    /// The algebra specification.
48    spec: &'a AlgebraSpec,
49    /// The algebra for computations.
50    algebra: &'a Algebra,
51}
52
53impl<'a> ConversionsGenerator<'a> {
54    /// Creates a new conversions generator.
55    pub fn new(spec: &'a AlgebraSpec, algebra: &'a Algebra) -> Self {
56        Self { spec, algebra }
57    }
58
59    /// Generates the complete conversions file.
60    pub fn generate_conversions_file(&self) -> TokenStream {
61        let header = self.generate_header();
62        let imports = self.generate_imports();
63        let to_mv = self.generate_all_to_multivector();
64        let from_mv = self.generate_all_from_multivector();
65
66        quote! {
67            #header
68            #imports
69
70            // ============================================================
71            // From<Type> for Multivector
72            // ============================================================
73            #to_mv
74
75            // ============================================================
76            // From<Multivector> for Type (lossy projection)
77            // ============================================================
78            #from_mv
79        }
80    }
81
82    /// Generates the file header.
83    fn generate_header(&self) -> TokenStream {
84        let name = &self.spec.name;
85        let header_doc = format!(
86            r#"//! Multivector conversions for {}.
87//!
88//! This file is auto-generated by clifford-codegen.
89//! Do not edit manually."#,
90            name
91        );
92
93        header_doc.parse().unwrap_or_else(|_| quote! {})
94    }
95
96    /// Generates import statements.
97    fn generate_imports(&self) -> TokenStream {
98        let signature_name = self.generate_signature_name();
99        let type_names: Vec<_> = self
100            .spec
101            .types
102            .iter()
103            .filter(|t| t.alias_of.is_none())
104            .map(|t| format_ident!("{}", t.name))
105            .collect();
106
107        quote! {
108            use crate::algebra::Multivector;
109            use crate::basis::Blade;
110            use crate::scalar::Float;
111            use crate::signature::#signature_name;
112            use super::types::{#(#type_names),*};
113        }
114    }
115
116    /// Generates the signature type name.
117    ///
118    /// Uses the signature tuple (p, q, r) to determine the signature type,
119    /// not the algebra name. This ensures generic handling of all algebras.
120    fn generate_signature_name(&self) -> proc_macro2::Ident {
121        let sig = &self.spec.signature;
122        let (p, q, r) = (sig.p, sig.q, sig.r);
123
124        // Derive signature type from (p, q, r)
125        let sig_name = match (p, q, r) {
126            // Euclidean: Cl(n, 0, 0)
127            (2, 0, 0) => "Euclidean2",
128            (3, 0, 0) => "Euclidean3",
129
130            // Projective (PGA): Cl(n, 0, 1)
131            (2, 0, 1) => "Projective2",
132            (3, 0, 1) => "Projective3",
133
134            // Conformal (CGA): Cl(n+1, 1, 0)
135            (4, 1, 0) => "Conformal3",
136
137            // Minkowski spacetime: Cl(1, 3, 0)
138            (1, 3, 0) => "Minkowski4",
139
140            // Generic: use Cl{p}_{q}_{r} format
141            _ => {
142                return format_ident!("Cl{}_{}_{}", p, q, r);
143            }
144        };
145        format_ident!("{}", sig_name)
146    }
147
148    /// Generates all `From<Type> for Multivector` implementations.
149    fn generate_all_to_multivector(&self) -> TokenStream {
150        let impls: Vec<TokenStream> = self
151            .spec
152            .types
153            .iter()
154            .filter(|t| t.alias_of.is_none())
155            .map(|ty| self.generate_to_multivector(ty))
156            .collect();
157
158        quote! { #(#impls)* }
159    }
160
161    /// Generates `From<Type> for Multivector` for a single type.
162    fn generate_to_multivector(&self, ty: &TypeSpec) -> TokenStream {
163        let name = format_ident!("{}", ty.name);
164        let signature_name = self.generate_signature_name();
165
166        // Build array of coefficient assignments
167        let num_blades = 1usize << self.algebra.dim();
168        let mut coeffs: Vec<TokenStream> = vec![quote! { T::zero() }; num_blades];
169
170        for field in &ty.fields {
171            let field_name = format_ident!("{}", field.name);
172            let idx = field.blade_index;
173            // Apply field sign: if field stores e31 (sign=-1), we need to negate
174            // to get the canonical e13 coefficient for the multivector.
175            if field.sign < 0 {
176                coeffs[idx] = quote! { -value.#field_name() };
177            } else {
178                coeffs[idx] = quote! { value.#field_name() };
179            }
180        }
181
182        quote! {
183            impl<T: Float> From<#name<T>> for Multivector<T, #signature_name> {
184                fn from(value: #name<T>) -> Self {
185                    Self::from_coeffs(&[#(#coeffs),*])
186                }
187            }
188        }
189    }
190
191    /// Generates all `From<Multivector> for Type` implementations.
192    fn generate_all_from_multivector(&self) -> TokenStream {
193        let impls: Vec<TokenStream> = self
194            .spec
195            .types
196            .iter()
197            .filter(|t| t.alias_of.is_none())
198            .map(|ty| self.generate_from_multivector(ty))
199            .collect();
200
201        quote! { #(#impls)* }
202    }
203
204    /// Generates `From<Multivector> for Type` for a single type.
205    ///
206    /// Note: This is a lossy projection that only extracts the relevant grades.
207    /// For constrained types, uses `new_unchecked()` since the multivector may not
208    /// satisfy the geometric constraint.
209    fn generate_from_multivector(&self, ty: &TypeSpec) -> TokenStream {
210        let name = format_ident!("{}", ty.name);
211        let signature_name = self.generate_signature_name();
212
213        // Build constructor arguments from multivector coefficients
214        let field_extracts: Vec<TokenStream> = ty
215            .fields
216            .iter()
217            .map(|field| {
218                let idx = field.blade_index;
219                // Apply field sign: if field stores e31 (sign=-1), we need to negate
220                // the canonical e13 coefficient from the multivector.
221                if field.sign < 0 {
222                    quote! { -mv.get(Blade::from_index(#idx)) }
223                } else {
224                    quote! { mv.get(Blade::from_index(#idx)) }
225                }
226            })
227            .collect();
228
229        let constructor = quote! { Self::new_unchecked(#(#field_extracts),*) };
230
231        let doc = quote! {
232            /// Extracts this type from a multivector.
233            ///
234            /// Note: This is a lossy projection that only extracts the relevant
235            /// grades. Other components of the multivector are discarded.
236        };
237
238        quote! {
239            impl<T: Float> From<Multivector<T, #signature_name>> for #name<T> {
240                #doc
241                fn from(mv: Multivector<T, #signature_name>) -> Self {
242                    #constructor
243                }
244            }
245        }
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252    use crate::spec::parse_spec;
253
254    #[test]
255    fn generates_to_multivector() {
256        let spec = parse_spec(include_str!("../../algebras/euclidean2.toml")).unwrap();
257        let algebra = Algebra::euclidean(2);
258        let generator = ConversionsGenerator::new(&spec, &algebra);
259
260        let tokens = generator.generate_conversions_file();
261        let code = tokens.to_string();
262
263        assert!(code.contains("From < Vector"));
264        assert!(code.contains("for Multivector"));
265    }
266
267    #[test]
268    fn generates_from_multivector() {
269        let spec = parse_spec(include_str!("../../algebras/euclidean2.toml")).unwrap();
270        let algebra = Algebra::euclidean(2);
271        let generator = ConversionsGenerator::new(&spec, &algebra);
272
273        let tokens = generator.generate_conversions_file();
274        let code = tokens.to_string();
275
276        assert!(code.contains("From < Multivector"));
277        assert!(code.contains("for Vector"));
278    }
279}