# SymClaw Architecture This document describes the internal architecture of SymClaw for contributors and anyone curious about how a modern symbolic math engine works. ## System Overview ``` User Input │ ┌────────────┼────────────┐ ▼ ▼ ▼ ┌────────┐ ┌─────────┐ ┌──────────┐ │ CLI │ │ WASM │ │ OpenClaw │ │ REPL │ │ Browser │ │ Skill │ └───┬────┘ └────┬────┘ └────┬─────┘ │ │ │ └────────────┼────────────┘ ▼ ┌─────────────────┐ │ Action Router │ │ (symclaw-skill)│ └────────┬────────┘ ▼ ┌────────────────────────────────┐ │ symclaw-core │ │ │ │ ┌────────┐ ┌───────────┐ │ │ │ Parser │───▶│ AST │ │ │ │ (nom) │ │ Arc │ │ │ └────────┘ └─────┬─────┘ │ │ │ │ │ ┌────────────┼───────┐ │ │ ▼ ▼ ▼ │ │ ┌──────────┐ ┌────────┐ ┌──┴──────┐ │ │ Simplify │ │E-graph │ │ Diff │ │ │(pipeline)│ │ (egg) │ │ Integ │ │ └──────────┘ └────────┘ │ Solve │ │ │ Series │ │ │ Eval │ │ └─────────┘ │ │ │ ┌──────────────────────┘ │ ▼ │ ┌──────────┐ │ │ LaTeX │ │ │ Renderer │ │ └──────────┘ └────────────────────────────────┘ ``` ## Expression AST All symbolic expressions are represented as `Arc`, defined in `ast.rs`: ```rust pub enum Expr { Num(Rational64), // Exact rational: 3/4, -7, 0 Float(f64), // IEEE 754 when exactness isn't needed Sym(Symbol), // Interned symbol (fast equality) Add(Vec>), // Flattened sum: a + b + c Mul(Vec>), // Flattened product: a * b * c Pow(Arc, Arc), // Exponentiation: base^exp Func(Symbol, Vec>), // Named function: sin(x), ln(x+1) Neg(Arc), // Unary negation Inv(Arc), // Multiplicative inverse: 1/x } ``` ### Design Principles - **`Arc`-based sharing**: Subtrees are reference-counted, so `x^2 + x^2` shares the same `x^2` node. Cloning is O(1). - **Canonical ordering**: `Add` and `Mul` children are sorted by a deterministic ordering (symbols alphabetically, numbers first). This makes structural equality reliable. - **Interned symbols**: Variable names are stored as integer IDs via `string-interner`. Comparing `x == x` is a single integer comparison, not a string comparison. - **Flattened n-ary operators**: `a + b + c` is `Add([a, b, c])`, not `Add(Add(a, b), c)`. This simplifies pattern matching and avoids arbitrary associativity choices. - **Exact rational arithmetic**: Core operations use `Rational64` (from `num-rational`) to avoid floating-point error. `1/3 + 1/3 + 1/3` is exactly `1`, not `0.9999...`. ## Simplification Pipeline Simplification runs in `simplify.rs` as a multi-pass pipeline. Each pass is a pure function `Arc → Arc`: ``` Input Expression │ ▼ ┌──────────────┐ │ Constant Fold│ 2 + 3 → 5, sin(0) → 0 └──────┬───────┘ ▼ ┌──────────────┐ │ Identity │ x + 0 → x, x * 1 → x, x^1 → x │ Elimination │ └──────┬───────┘ ▼ ┌──────────────┐ │ Like-Term │ 2x + 3x → 5x, x·x → x² │ Collection │ └──────┬───────┘ ▼ ┌──────────────┐ │ Canonical │ Sort children, flatten nested Add/Mul │ Form │ └──────┬───────┘ ▼ ┌──────────────┐ │ E-graph │ Optional: equality saturation for │ Saturation │ deep algebraic identities └──────┬───────┘ ▼ Simplified Expression ``` The pipeline iterates until a fixed point (expression stops changing) or a maximum iteration count is reached. In practice, 2-3 passes suffice for most expressions. ## E-graph Integration SymClaw uses the [egg](https://egraphs-good.github.io/) crate for equality saturation — a technique that explores all equivalent forms of an expression simultaneously, then extracts the "best" one. ### How It Works 1. **Insert** the expression into an e-graph 2. **Apply rewrite rules** (30+ rules covering algebra, trig, logarithms, exponents) 3. **Saturate** until no new equalities are discovered (or a node/time limit is hit) 4. **Extract** the smallest equivalent expression by AST cost ### Rewrite Rules (Sample) ``` // Algebra a + 0 ⟶ a a * 1 ⟶ a a * 0 ⟶ 0 a - a ⟶ 0 a / a ⟶ 1 (a ≠ 0) a^0 ⟶ 1 a^1 ⟶ a (a^m)^n ⟶ a^(m*n) // Trig sin(x)^2 + cos(x)^2 ⟶ 1 sin(0) ⟶ 0 cos(0) ⟶ 1 // Logarithms ln(e^x) ⟶ x e^(ln(x)) ⟶ x (x > 0) ln(a*b) ⟶ ln(a) + ln(b) (a,b > 0) ln(a/b) ⟶ ln(a) - ln(b) (a,b > 0) ``` ### Explain Mode `explain_equivalence(a, b)` returns a step-by-step proof of why two expressions are equal, listing which rewrite rules were applied at each step. Used for educational output and debugging. ## Differentiation `differentiate.rs` implements symbolic differentiation via recursive structural rules: | Expression | Derivative (w.r.t. x) | |---|---| | `c` (constant) | `0` | | `x` | `1` | | `f + g` | `f' + g'` | | `f · g` | `f'·g + f·g'` (product rule) | | `f^n` (n const) | `n·f^(n-1)·f'` (chain rule) | | `f^g` (general) | `f^g·(g'·ln(f) + g·f'/f)` | | `sin(f)` | `cos(f)·f'` | | `cos(f)` | `-sin(f)·f'` | | `exp(f)` | `exp(f)·f'` | | `ln(f)` | `f'/f` | **Higher-order**: `differentiate_n(expr, var, n)` applies differentiation `n` times. **Partial derivatives**: Differentiate with respect to any variable; other variables are treated as constants. The result is always passed through `simplify()` to reduce to canonical form. ## Integration `integrate.rs` implements symbolic integration via pattern matching with fallback strategies: 1. **Linearity**: `∫(a·f + b·g) dx = a·∫f dx + b·∫g dx` 2. **Power rule**: `∫x^n dx = x^(n+1)/(n+1)` for n ≠ -1 3. **Known antiderivatives**: sin, cos, exp, ln, tan, sec², etc. 4. **U-substitution**: Detect `∫f(g(x))·g'(x) dx` patterns 5. **Integration by parts**: `∫u dv = u·v - ∫v du` with LIATE heuristic for choosing u 6. **Definite integrals**: Evaluate antiderivative at bounds (Fundamental Theorem) Returns `Option>` — `None` when no closed-form antiderivative is found. ## Equation Solver `solve.rs` solves equations of the form `expr = 0`: 1. **Polynomial detection**: Extract coefficients of `x^0, x^1, x^2, ...` 2. **Linear**: `ax + b = 0` → `x = -b/a` 3. **Quadratic**: `ax² + bx + c = 0` → Exact quadratic formula with rational simplification 4. **Higher polynomial**: Rational root theorem to find integer/rational roots, then synthetic division 5. **Transcendental**: Pattern matching for forms like `e^x = k`, `sin(x) = k` 6. **Systems (2×2)**: Gaussian elimination for pairs of linear equations All solutions are returned as `Vec>` in simplified form. ## Taylor Series `series.rs` computes Taylor expansions `f(x) ≈ Σ f^(n)(a)/n! · (x-a)^n`: ### Fast Path For known functions (sin, cos, exp, ln, sinh, cosh), series coefficients are computed directly from known formulas without repeated differentiation. This is O(n) instead of O(n²). ### General Path For arbitrary expressions, the engine differentiates `n` times at the expansion point, evaluating each derivative numerically or symbolically. The coefficients are assembled into a polynomial. ### Maclaurin When `point = 0`, this is a Maclaurin series. The fast path handles the most common cases. ## WASM Compilation `symclaw-wasm` compiles `symclaw-core` to WebAssembly via `wasm-bindgen`: - **No std dependencies**: Core engine uses only `alloc`, making WASM compilation clean - **12 exported functions**: `parse`, `simplify`, `differentiate`, `integrate`, `solve`, `taylor`, `eval_expr`, `to_latex`, `simplify_egraph`, `explain`, `plot_data`, `version` - **String-based API**: WASM boundary passes strings (JSON for complex types) - **Size**: ~800 KB gzipped The WASM module runs entirely client-side — no server roundtrips for computation. ## OpenClaw Skill Protocol `symclaw-skill` implements the OpenClaw skill protocol: - **Transport**: JSON-RPC over stdin/stdout (one JSON object per line) - **Actions**: 8 tool endpoints (`simplify`, `differentiate`, `integrate`, `solve`, `taylor`, `eval`, `latex`, `plot_data`) - **Canvas**: Two HTML canvases — `manipulate.html` (interactive plotting with sliders) and `result.html` (formatted result display) ### Request/Response Flow ``` OpenClaw Gateway │ │ {"action": "differentiate", "expr": "sin(x^2)", "var": "x"} ▼ symclaw-skill (stdin) │ │ parse → differentiate → simplify → latex │ ▼ symclaw-skill (stdout) │ │ {"success": true, "result": "2*x*cos(x^2)", "latex": "2 x \\cos(x^{2})"} ▼ OpenClaw Gateway → User's channel ``` ### Canvas Manipulate The `manipulate.html` canvas provides Mathematica-style interactive exploration: 1. User specifies an expression and parameter ranges (e.g., `a*sin(b*x)` with `a ∈ [0,5]`, `b ∈ [1,10]`) 2. Canvas renders sliders for each parameter 3. The WASM module evaluates the expression at 200+ points per slider change 4. A canvas-based plot updates in real time (~60 fps) All computation happens client-side via the WASM module — no server calls during interaction. ## Performance Characteristics | Component | Complexity | Notes | |---|---|---| | Parse | O(n) | n = input length, nom zero-copy | | Simplify (algebraic) | O(n log n) | Sorting + single-pass rewrites | | E-graph saturation | O(rules × nodes) | Bounded by iteration/node limits | | Differentiate | O(n) | n = AST nodes, single structural pass | | Integrate | O(n²) worst | Pattern matching + recursive attempts | | Solve (quadratic) | O(1) | Direct formula | | Taylor (fast path) | O(order) | Known function coefficients | | Taylor (general) | O(order²) | Repeated differentiation | Memory: All expressions are `Arc`-shared. A deeply nested expression with shared subterms uses far less memory than its unfolded size. ## Dependencies | Crate | Purpose | Why | |---|---|---| | `egg` | E-graph equality saturation | Best-in-class Rust e-graph library | | `nom` | Parser combinators | Zero-copy, composable, fast | | `num-rational` | Exact rational arithmetic | Avoid floating-point errors | | `num-bigint` | Big integer support | Overflow-safe coefficients | | `string-interner` | Symbol interning | O(1) symbol comparison | | `ordered-float` | Hashable f64 | For numeric expression keys | | `serde` / `serde_json` | Serialization | Skill protocol + WASM boundary | | `ahash` | Fast hashing | Expression caching | | `lru` | LRU cache | Memoize expensive operations |