arael-sym 0.1.0

Symbolic math library: expression trees, automatic differentiation, simplification, CSE, code generation
Documentation

Symbolic math library for expression trees, automatic differentiation, simplification, and code generation.

arael-sym provides a lightweight computer algebra system built around a reference-counted expression tree ([E]). Expressions are constructed from symbols and constants, combined with standard arithmetic operators (which auto-simplify), and then differentiated, evaluated, pretty-printed, or compiled to Rust source code.

This crate is the symbolic engine behind the arael optimization framework, where it powers compile-time constraint differentiation and code generation. It can also be used independently for any symbolic math task.

Scope and limitations

arael-sym is focused on what's needed for nonlinear optimization: scalar expressions, differentiation, and code generation. Compared to a full CAS like Python's SymPy, it does not support:

  • Symbolic integration
  • Equation solving (solve for x)
  • Symbolic matrix algebra (symbolic determinant, inverse, eigenvalues)
  • Polynomial factoring, GCD, partial fractions
  • Limits, series expansion, Taylor series
  • Assumptions / domain reasoning (positive, real, integer)
  • Pattern matching / rewrite rules
  • Pretty-printing of intermediate simplification steps

Examples

The [sym!] macro auto-inserts .clone() on reused variables, so you can write natural math without ownership boilerplate.

Basics

use arael_sym::*;
let result = sym! {
    let x = symbol("x");
    let f = x * x + 3.0 * x + 1.0;
    format!("{}", f)
};
assert_eq!(result, "x^2 + 3 * x + 1");

Differentiation

use arael_sym::*;
let result = sym! {
    let x = symbol("x");
    let f = sin(x) * x;
    // Product rule + chain rule applied automatically:
    format!("{}", f.diff("x"))
};
assert_eq!(result, "x * cos(x) + sin(x)");

Evaluation

use arael_sym::*;
let val = sym! {
    let x = symbol("x");
    let f = x * x + 1.0;
    let vars = std::collections::HashMap::from([("x", 3.0)]);
    f.eval(&vars)
};
assert_eq!(val, 10.0);

Code generation

use arael_sym::*;
let (code1, code2) = sym! {
    let f = sin(symbol("x")) + 1.0;
    let g = atan2(symbol("y"), symbol("x"));
    (f.to_rust("f64"), g.to_rust("f32"))
};
assert_eq!(code1, "x.sin() + 1.0_f64");
assert_eq!(code2, "y.atan2(x)");

Common Subexpression Elimination (CSE)

use arael_sym::*;
sym! {
    let x = symbol("x");
    let shared = sin(x) * cos(x);
    let e1 = shared + 1.0;
    let e2 = shared * 2.0;
    let (intermediates, simplified) = cse(&[e1, e2]);
    for (name, val) in &intermediates {
        println!("let {} = {};", name, val);
    }
    for s in &simplified {
        println!("{}", s);
    }
};
// Output:
//   let __x0 = cos(x) * sin(x);
//   __x0 + 1
//   2 * __x0

Vectors and Matrices

use arael_sym::*;
let dot = sym! {
    let v = SymVec::new(vec![symbol("x"), symbol("y"), symbol("z")]);
    let w = SymVec::new(vec![c(1.0), c(2.0), c(3.0)]);
    format!("{}", v.dot(&w))
};
assert_eq!(dot, "x + 2 * y + 3 * z");

Jacobian

use arael_sym::*;
let (j00, j01, j10, j11) = sym! {
    let x = symbol("x");
    let y = symbol("y");
    let f = vec![x * y, sin(x) + y];
    let j = jacobian(&f, &["x", "y"]);
    // j is 2x2: [[df0/dx, df0/dy], [df1/dx, df1/dy]]
    (format!("{}", j.get(0, 0)),
     format!("{}", j.get(0, 1)),
     format!("{}", j.get(1, 0)),
     format!("{}", j.get(1, 1)))
};
assert_eq!(j00, "y");      // d(x*y)/dx
assert_eq!(j01, "x");      // d(x*y)/dy
assert_eq!(j10, "cos(x)"); // d(sin(x)+y)/dx
assert_eq!(j11, "1");      // d(sin(x)+y)/dy

Parsing

use arael_sym::*;
let f = parse("sin(x)^2 + cos(x)^2").unwrap();
assert_eq!(format!("{}", f), "sin(x)^2 + cos(x)^2");

let vars = std::collections::HashMap::from([("x", 1.0)]);
assert!((f.eval(&vars) - 1.0).abs() < 1e-10);