Rust port of SymbolicRegression.jl with support for WebAssembly.
Try out a fully browser-based demo of WebAssembly-compiled symbolic regression here.
Warning
This package is an experiment. The API is not stabilized, and you should expect large breaking changes in the syntax. This library is not ready for use.
This workspace contains three crates:
| Crate | crates.io | CI |
|---|---|---|
symbolic_regression |
||
dynamic_expressions |
||
symbolic_regression_wasm |
Execute examples/example.rs, which is the standard example from the SymbolicRegression.jl README.
cargo run -p symbolic_regression --example example --releaseThe code executed is:
use ndarray::{Array1, Array2};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use symbolic_regression::prelude::*;
// Mirrors `SymbolicRegression.jl/example.jl`.
fn main() {
const D: usize = 3;
let n_features = 5;
let n_rows = 100;
let mut rng = StdRng::seed_from_u64(0);
let mut x = Array2::zeros((n_features, n_rows));
let mut y = Array1::zeros(n_rows);
for i in 0..n_rows {
for j in 0..n_features {
x[(j, i)] = rng.random_range(-3.0f32..3.0f32);
}
let x1 = x[(1, i)];
let x4 = x[(4, i)];
y[i] = 2.0 * x4.cos() + x1 * x1 - 2.0;
}
let dataset = Dataset::new(x, y);
let operators = BuiltinOpsF32::from_names(["cos", "exp", "sin", "+", "sub", "*", "/"]).unwrap();
let options = Options::<f32, D> {
operators,
niterations: 200,
..Default::default()
};
let result = equation_search::<f32, BuiltinOpsF32, D>(&dataset, &options);
let dominating = result.hall_of_fame.pareto_front();
println!("Final Pareto front:");
println!("Complexity\tMSE\tEquation");
for member in dominating {
println!("{}\t{}\t{}", member.complexity, member.loss, member.expr);
}
// To evaluate the expression, use:
/*
let tree = dominating
.last()
.unwrap()
.expr
.clone();
let _ = eval_tree_array::<f32, BuiltinOpsF32, D>(
&tree,
dataset.x.view(),
&EvalOptions::default(),
);
*/
}Define custom operators with op!, then build an operator set with opset!:
use symbolic_regression::prelude::*;
op!(Square for f64 {
eval: |[x]| { x * x },
partial: |[x], _idx| { 2.0 * x },
});
op!(Exp for f64 {
eval: |[x]| { x.exp() },
partial: |[x], _idx| { x.exp() },
});
op!(Add for f64 {
infix: "+",
commutative: true,
associative: true,
eval: |[x, y]| { x + y },
partial: |[_x, _y], _idx| { 1.0 },
});
op!(Sub for f64 {
infix: "-", // optional
complexity: 2, // optional
eval: |[x, y]| { x - y },
partial: |[_x, _y], idx| { if idx == 0 { 1.0 } else { -1.0 } },
});
opset! {
pub CustomOps for f64 {
Square,
Exp,
Add,
Sub,
}
}
let operators = CustomOps::from_names(["square", "exp", "add", "sub"]).unwrap();
let options = Options::<f64, _> { operators, ..Default::default() };This workspace includes a thin wasm-bindgen wrapper crate (symbolic_regression_wasm) at web/wasm/ and a minimal browser UI at web/ui/ (Vite + WebWorker).
rustup target add wasm32-unknown-unknown
# one-time
cargo install wasm-pack
# build the wasm package into the Vite app
cd web/wasm
wasm-pack build --target web --out-dir ../ui/src/pkg
# run the dev server
cd ../ui
npm install
npm run devSee web/ui/README.md for details.
