Expand description
§Overview
This crate behaves like a staged compiler that turns Clifford algebra declarations into generated Rust. Macros parse input (spec), build an intermediate multivector representation, run optimisation passes, then emit TokenStreams.
Two entry points exist:
- The module attribute macro
#[reefer::algebra](attach it to an empty module) - The generated
expr!macro that expands symbolic expressions within that module
Both flow through the same pipeline described in “Optimization Pipeline”.
§Quickstart
This five‑minute guide gets you from zero to a working algebra and a compiled expression.
§Installation
Add Reefer to your manifest (not on crates.io yet):
[dependencies]
reefer = { git = "https://github.com/kgullion/reefer", branch = "main" }Heads up: building the default CAS backend compiles GMP/MPFR through
rug, which requires them4macro processor to be available during the build. Install it via your system package manager (for example,sudo apt install m4on Debian/Ubuntu orbrew install m4on macOS) before runningcargo build.
§Example CGA 2D
#[reefer::algebra(f32, 3, 1)]
mod cga2d {
basis! { e0 = P0 + N0 }
basis! { eI = P0 - N0 }
basis! { e1 = P1 }
basis! { e2 = P2 }
#[derive(Debug, Clone, Copy, PartialEq)]
shape!(Vector { e0, eI, e1, e2 });
}
// Use the generated `expr!` macro to build expressions:
let mv: cga2d::Vector = cga2d::expr!(|| e1 + 2.0 * e2)();
// Methods are available directly on symbolic values:
let norm = cga2d::expr!(|| (e1 + e2).norm_squared())();
let rotor = cga2d::expr!(|| ((-0.5 * (e1 ^ e2)).exp()))();
// Closures can take typed arguments too
let reflect = cga2d::expr!(|v: cga2d::Vector| {
let r = (-0.5 * (e1 ^ e2)).exp();
r.sandwich(v)
});
let result = reflect(mv);- Operators:
*(geometric),^(wedge),|(inner),&(regressive/meet) - Methods:
.reverse(),.dual(),.undual(),.left_contract(rhs),.right_contract(rhs),.complement(),.conjugate(),.automorphism(),.sandwich(x),.exp(),.sqrt(),.norm_squared(),.scalar() - Shapes returned from
expr!are concrete structs. Inspect the generated module to see fields.
See the “Expressions & Operators” chapter for details.
§Using the Macros
§Module setup
#[reefer::algebra(f32, 3, 1)]
mod cga2d {
basis! { e0 = P0 + N0 }
basis! { eI = P0 - N0 }
basis! { e1 = P1 }
basis! { e2 = P2 }
#[derive(Debug, Clone, Copy, PartialEq)]
shape!(Vector { e0, eI, e1, e2 });
}§Basis and shape declarations
- Alias grammar matches
[a-z]+[0-9A-Z]?and must remain prefix-free (e1,e12,rotorA). - Frame slots on the right-hand side stick to the uppercase
[PN]\d+(shorthand[PQ]\d+]) pattern such asP0,N1, so the parser cleanly separates them from alias names. These frame slots are the basis of the compile time mother algebra. - Basis aliases may include scalar coefficients:
basis! { idem = 0.5 + 0.5 * P0 }. - Compound aliases such as
e12ort0x1are expanded automatically using the alias map. - Shapes enumerate chosen blades; at expansion time each
shape!becomes a public struct populated with scalar fields.
§Expressions
Use the generated expr! macro to define typed closures:
let f = cga2d::expr!(|v: cga2d::Vector| (v ^ e1).reverse());
let result = f(sample_vector);Supported operators: *, ^, |, &.
Supported methods:
reverse,conjugate,automorphism(involutions)dual,complement,scalar,norm_squaredleft_contract,right_contractsandwich,exp,sqrt
Unsupported today: arbitrary function calls, method chains with arguments other than the documented list, or pattern let bindings inside the closure. The optimizer will report unsupported syntax at compile time.
For use in the expr! macro of your algebra.
§Expressions & Operators
*Geometric product^Exterior (wedge) product|Inner product (contraction)&Regressive product (meet)
§Methods
.reverse()– reversion involution.conjugate()– grade involution (negates odd grades).automorphism()– main involution (negates grades ≡ 2,3 mod 4).dual()– right dual with respect to pseudoscalar.complement()– orthogonal complement.sandwich(target)–a * target * a.reverse()shortcut.exp()– multivector exponential (sums by grades when applicable).sqrt()– principal square root where defined.norm_squared()– returns scalar squared norm.left_contract(rhs),.right_contract(rhs)– contractions
See tests in tests/smoke.rs for end‑to‑end examples of each.
⚠️ Heads up: The remaining chapters dive into the compiler internals that power Reefer. If you’re here for day-to-day algebra usage, you can safely skip what’s ahead or come back later when you’re ready for the gory details.
§Optimization Pipeline
Reefer behaves like a tiny compiler that runs entirely during macro expansion. The input is the Rust module annotated with #[reefer::algebra] plus any later calls to the generated expr! macro; the output is more Rust code that Rustc can type-check and inline. This chapter walks through the pipeline end to end so you can reason about where behaviours or diagnostics originate.
§Stage 1 - Attribute parsing (front end)
- Responsibilities: Decode the attribute payload and gather
basis!/shape!invocations. - Key modules:
src/spec.rs,src/algebra.rs. - Inputs & outputs: The procedural macro receives a module annotated with
#[reefer::algebra(scalar, p, q)]. It lowers the arguments intoAlgebraAttr, then aggregates basis and shape declarations into anAlgebraSpec. - Failure surface: Any syntactic issue (unknown argument, missing module body, malformed
basis!) is surfaced immediately viasyn::Errortied to the offending span.
At the end of this stage we have a structured description of the algebra, no code has been emitted yet, but the spec contains everything downstream passes need.
§Stage 2 - Module elaboration
- Responsibilities: Turn the declarative spec into concrete Rust items.
- Key modules:
src/algebra.rs(build_algebra),src/spec.rs(ShapeItem). - What happens:
- Every
basis!block is recorded and removed from the expanded module. - Each
shape!block desugars into a public struct with scalar fields, constructors, and basic trait impls. - The module receives an
expr!helper backed byreefer::build_expr!so user crates can author symbolic closures.
- Every
If anything goes wrong here, such as the module being empty, the macro emits a compile-time error and stops.
§Stage 3 - Alias rewrite
- Responsibilities: Normalise user-friendly aliases (
e12,rotorA) to canonical basis masks. - Key modules:
src/build_expr/rewrite.rs,src/optimizer.rs(Optimizer::alias_mapping). - Inputs & outputs: When a user invokes
ga::expr!(...), the macro captures the closure and builds aBuildExprrecord. Alias mapping produces a deterministic table from strings to basis masks; the rewrite pass walks the syntax tree, replacing identifiers so the rest of the pipeline only sees canonical names. - Failure surface: Rewriting is designed to be total. If an identifier cannot be mapped, the optimiser records a diagnostic that materialises as a compile-time error attached to the original token.
This stage keeps the symbolic IR free from any naming quirks and enables deterministic simplifications.
§Stage 4 - IR construction
- Responsibilities: Interpret the rewritten closure into a Clifford-aware intermediate representation.
- Key modules:
src/optimizer/parser.rs,src/clifford/multivector.rs,src/clifford/mod.rs. - Inputs & outputs: The parser walks the
syn::Exprtree, calling algebraic helpers to produce aSymbolMultivector(backed bySynFieldcoefficients). Each node records the blade mask and the coefficient expression, preserving structure like wedges, inner products, involutions, and exponentials. - Failure surface: If the expression uses unsupported syntax (for example a method call the parser does not recognise),
analysis_erroris set onBuildExpr. Expansion continues with a fallback closure so compilation succeeds, but later optimisation steps are skipped.
Think of this stage as turning syntax into a typed, algebra-specific IR; much like translating Rust HIR into MIR.
§Stage 5 - Optimisation passes
- Responsibilities: Simplify the multivector and prepare literals.
- Key modules:
src/build_expr.rs(BuildExpr::optimise,constant_shape_literal),src/clifford/multivector.rs(algebraic helpers). - Pass highlights:
- Zero pruning: Any blade whose coefficient evaluates to zero is dropped immediately, keeping the representation sparse.
- Term merging: Like-grade blades with compatible masks are merged via
accumulate_term, reducing redundant arithmetic.
Feature flags (backend_cas, backend_egg) control which scalar backend drives simplification. The default CAS stack works quite well. Egg is an alternative that is being explored but is not yet functional and thus is behind a feature flag.
§Stage 6 - Backend emission
- Responsibilities: Convert the final IR back into Rust tokens.
- Key modules:
src/build_expr.rs(BuildExpr::into_stream),src/algebra.rs(module generation). - What happens:
- Successful constant folding swaps the closure body for a literal shape struct value.
- The closure is rehydrated into tokens using
quote!, referencing the generated shape/basis types. - The resulting
TokenStreamis emitted to Rustc, which then performs its normal type checking and optimisation pipeline.
§Cross-cutting behaviours
- Diagnostics: Every stage propagates
syn::Erroror human-friendly messages. Front-end issues halt expansion; middle-end failures record context onBuildExprso users receive actionable compiler notes. There are still sometodo!()s sprinkled in (egdivstill needs implemented) but every other path should not panic. - Testing hooks: Unit tests in
src/algebra.rsvalidate attribute handling,optimizer::parser::testscover IR construction, and integration suites intests/smoke.rsexercise the complete pipeline. - Extensibility: New passes should slot naturally into Stage 5. They operate in place on
SymbolMultivector.
Understanding these stages makes it easier to diagnose compile-time errors and to extend Reefer with new algebraic tricks. When you add a feature, ask which stage owns the logic, wire a couple tests into the matching module, and verify that the emitted tokens still type-check under both backends.
§Backends & Features
Reefer ships with a minimal default configuration width of 32. When you need different performance or algebra sizes, toggle Cargo features to swap backends or widen the internal bitset type. This chapter summarises what each flag does, when to use it, and the chores that come with it.
§Default configuration
- Scalar backend:
backend_cas– enables the CAS-based coefficient field (cas-rs,cas-compute, andrug). This gives the optimiser access to symbolic evaluation and constant folding out of the box. - Blade mask width: Falls back to
u8(BladeBits = u8), supporting algebras with up to 8 basis vectors (n <= 8). - System requirement: The
rugdependency pulls in GMP/MPFR and needs them4macro processor during compilation. Install it via your package manager (e.g.sudo apt install m4orbrew install m4) before building.
If the defaults match your algebra (most 2D/3D examples do), you can stay here and treat Reefer like any other dependency: cargo add reefer and you are done.
§Selecting a scalar backend
Exactly one backend should be active at a time. The backend_cas feature is enabled by default; switch to backend_egg by disabling default features. Enabling both simultaneously will produce duplicate type definitions and break the build.
§backend_cas (default)
- What you get: A symbolic algebra field powered by CAS-RS. It performs aggressive constant folding and can represent rational coefficients exactly.
- Trade-offs: Missing some nice optimazations available to egg, like folding to
mul_addexpressions. There’s also the externalm4requirement mentioned above.
§backend_egg (experimental)
- What you get: An e-graph-based field that relies on the
eggcrate. The optimiser rewrites coefficient expressions using equality saturation. - Trade-offs: Currently offers a smaller rule set than the CAS backend and is marked experimental in the source tree. Will likely refactor in the future to find certain optimizations that cas-rs misses. The feature is still exploratory though, expect missing rewrites.
§Controlling blade mask width
The generated algebra stores basis combinations inside a bitset type BladeBits. Choose the width that matches the largest blade mask you plan to represent (number of basis vectors n must satisfy n <= bit_width). Only enable one of these flags at a time; otherwise the compiler will complain about conflicting type aliases.
| Feature | BladeBits | Max basis vectors |
| — | — | — | — |
| bits_u8 | u8 | 8 |
| bits_u16 | u16 | 16 |
| bits_u32 | u32 | 32 |
| bits_u64 | u64 | 64 |
| bits_u128 | u128 | 128 |
Switching width is as simple as:
reefer = { features = ["bits_u64"], .. }
§Troubleshooting
This chapter collects the failure modes run into most often while building or using Reefer. Start with the scenario that matches your compiler output, then follow the checklist to unblock yourself quickly.
§Build failures & prerequisites
cc/linkerrors mentioning GMP/MPFR or missingm4: The defaultbackend_casfeature pulls in therugcrate, which relies on them4macro processor during its native build. Install it (sudo apt install m4,brew install m4, etc.), then retrycargo build.- Feature conflicts: Only enable one blade-width flag (
bits_u8,bits_u16, …) and one backend (backend_casorbackend_egg). Cargo will emit duplicate type alias errors if multiple options are toggled simultaneously. no module itemsafter applying#[reefer::algebra]: Ensure the annotated module isn’t empty. At minimum, include onebasis!declaration; an empty module triggers asyn::Errorwith this wording.- Proc-macro panics: These are considered bugs; macros should return structured diagnostics. If you hit one, re-run with
RUST_BACKTRACE=1and capture the output when filing an issue (if this is atodo!panic, please check for existing issue first).
§Macro expansion & alias issues
- Unknown identifier diagnostics: The optimiser could not map a symbol to a declared basis alias. Confirm the alias is listed in your
basis!blocks. expr!rejects pattern bindings or method chains: The current parser accepts simpleletbindings and the supported method list (reverse,dual,sandwich, contractions, etc.). Remove unsupported syntax or break expressions into smaller helpers.
§Debugging techniques
- Inspect emitted code: Use
cargo expand -p your_crate --test smoke(or whichever target invokes Reefer) to dump the generated tokens. For quick peeks inside this repository, run the relevant tests with-- --nocaptureto print the emitted closure bodies.
Keep this page handy while iterating on new algebra specs or optimiser passes. When you discover a new failure pattern, add it here so the next contributor benefits from the breadcrumb trail.
Macros§
- build_
expr - Proc-macro helper that builds optimized Clifford algebra expressions at compile time.
Attribute Macros§
- algebra
- Attribute macro that expands a module definition into a full Clifford algebra implementation.