SYMEXPR

symexpr is a Rust library for symbolic expressions and evaluation, supporting Rust primitive types and most of their functions. Easily extend symbolic operations and turning custom type to symbolic type.
Features
- Symbolic representation and evaluation for Rust primitive types (integers, floats, bools, char, etc.)
- Supports most functions and operators for Rust primitive types symbolic representation
- Easy to turn regular function in value type as experssion for sybmolic representation
- Easy to extend to custom type as symbolic representation Sym<T, C, E>
- Support custom context type during evaluation
- Support Symoblic experssion stored as Rc / Arc or other pointer type
Installation
cargo add symexpr
Usage
use symexpr::{Context, SymCtx, SymValue, SymUsize, SymF32};
type Usize = SymUsize;
type F32 = SymF32;
fn main() {
let x = &Usize::symbol("a");
let y = &Usize::constant(4);
let k = &Usize::constant(2);
let mut ctx = Context::default();
ctx.set_symbol("a", 2usize);
assert_eq!(x.eval(&ctx).unwrap(), 2);
assert_eq!(y.eval(&ctx).unwrap(), 4);
let b = x.eq(y);
assert!(!b.eval(&ctx).unwrap());
let b = x.ge(y);
assert!(!b.eval(&ctx).unwrap());
let z = x + y;
assert_eq!(z.eval(&ctx).unwrap(), 6);
let c = 3;
let w = c + z + 2 + k + 3;
assert_eq!(w.eval(&ctx).unwrap(), 16);
let xf = F32::symbol("f");
ctx.set_symbol("f", 3.7f32);
let floor_xf = xf.floor();
assert_eq!(floor_xf.eval(&ctx).unwrap(), 3.0);
}
Extending symbolic operations
You can extend symbolic operations in two main ways:
1. Implement symbolic functions using SymFn
For most functions (e.g., abs, floor, sqrt), you can wrap the function using SymFn:
use symexpr::{Sym, SymCtx, SymFn, SymF32};
impl<C, E> SymF32<C, E>
where
C: SymCtx<f32>,
E: SymExpr<f32>,
{
pub fn abs(&self) -> SymF32<C, E> {
SymF32::<C, E>::Expr(E::lift(SymFn::new("abs", (self.clone(),), f32::abs)))
}
pub fn max(&self, other: impl Into<SymF32<C, E>>) -> SymF32<C, E> {
#[inline(always)]
fn _max(x: (f32, f32)) -> f32 {
let (self_val, other_val) = x;
self_val.max(other_val)
}
let other = other.into();
SymF32::<C, E>::Expr(E::lift(SymFn::new(
"max",
(self.clone(), other),
_max,
)))
}
}
let x = SymF32::symbol("x");
let abs_x = x.abs();
2. Implement a trait with an Op struct
For custom or more complex operations, define an Op struct and implement the corresponding trait (e.g., for symbolic equality):
use crate::{Result, Sym, SymCtx, SymExpr, SymValue, Value};
pub trait SymEq<C, E, R>
where
C: SymCtx<bool>,
E: SymExpr<bool>,
{
fn eq(self, rhs: R) -> Sym<bool, C, E>;
}
#[derive(Debug, Clone)]
pub struct Eq<C, E, L, R>
where
L: Value,
R: Value,
C: SymCtx<L> + SymCtx<R>,
E: SymExpr<L> + SymExpr<R>,
{
lhs: Sym<L, C, E>,
rhs: Sym<R, C, E>,
}
impl<C, E, L, R> Eq<C, E, L, R>
where
L: Value,
R: Value,
C: SymCtx<L> + SymCtx<R>,
E: SymExpr<L> + SymExpr<R>,
{
pub fn new(lhs: Sym<L, C, E>, rhs: Sym<R, C, E>) -> Self {
Eq { lhs, rhs }
}
}
impl<C, E, L, R> SymValue<C> for Eq<C, E, L, R>
where
L: Value,
R: Value,
C: SymCtx<L> + SymCtx<R>,
E: SymExpr<L> + SymExpr<R>,
L: std::cmp::PartialEq<R>,
{
type Value = bool;
fn eval(&self, ctx: &C) -> Result<Self::Value> {
let lhs = self.lhs.eval(ctx)?;
let rhs = self.rhs.eval(ctx)?;
Ok(lhs == rhs)
}
fn display(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("(")?;
self.lhs.display(f)?;
f.write_str(" == ")?;
self.rhs.display(f)?;
f.write_str(")")
}
}
impl<C, E, LHS, RHS> SymEq<C, E, Sym<RHS, C, E>> for Sym<LHS, C, E>
where
C: SymCtx<LHS> + SymCtx<RHS> + SymCtx<bool>,
E: SymExpr<LHS> + SymExpr<RHS> + SymExpr<bool>,
LHS: Value + std::cmp::PartialEq<RHS>,
LHS: Value,
RHS: Value,
bool: Value,
{
fn eq(self, rhs: Sym<RHS, C, E>) -> Sym<bool, C, E> {
Sym::<bool, C, E>::Expr(E::lift(Eq::new(self, rhs)))
}
}