3 unstable releases
0.2.0 | Oct 6, 2024 |
---|---|
0.1.1 | Feb 14, 2024 |
0.1.0 | Feb 14, 2024 |
#276 in Math
320KB
8K
SLoC
Eiche
A library for manipulating and analyzing symbolic expression. The name means 'Oak' in German because symbolic expressions in Eiche are represented as trees.
Usage
Creating Expressions
The deftree
macro is useful for defining expressions using the s-expressions
(like LISP). You can create an expression and print it out as LaTex as follows:
let tree = deftree!(/ (+ (* k x) (* k y)) (+ x y)).unwrap();
println!("$`{}`$\n", tree.to_latex());
$\dfrac{{{k}.{x}} + {{k}.{y}}}{{x} + {y}}
$
Simplifying Expressions
You can simplify the above tree expressions using the reduce
function as
follows:
let steps = reduce(tree, 8).unwrap();
for step in steps {
println!("$`= {}`$\n", step.to_latex());
}
$= \dfrac{{k}.{\left({x} + {y}\right)}}{{x} + {y}}
$
$= {k}.{\dfrac{{x} + {y}}{{x} + {y}}}
$
$= k
$
Eiche can also simplify more complicated expressions. Below expressions represents the length of a normalized vector:
$\sqrt{{{\left(\dfrac{x}{\sqrt{{{x}^{2}} + {{y}^{2}}}}\right)}^{2}} + {{\left(\dfrac{y}{\sqrt{{{x}^{2}} + {{y}^{2}}}}\right)}^{2}}}
$
$= \sqrt{{{\left(\dfrac{x}{\sqrt{{{x}^{2}} + {{y}^{2}}}}\right)}^{2}} + {\dfrac{{y}^{2}}{{\left(\sqrt{{{x}^{2}} + {{y}^{2}}}\right)}^{2}}}}
$
$= \sqrt{{\dfrac{{x}^{2}}{{\left(\sqrt{{{x}^{2}} + {{y}^{2}}}\right)}^{2}}} + {\dfrac{{y}^{2}}{{\left(\sqrt{{{x}^{2}} + {{y}^{2}}}\right)}^{2}}}}
$
$= \sqrt{\dfrac{{{x}^{2}} + {{y}^{2}}}{{\left(\sqrt{{{x}^{2}} + {{y}^{2}}}\right)}^{2}}}
$
$= \sqrt{\dfrac{{{x}^{2}} + {{y}^{2}}}{{{x}^{2}} + {{y}^{2}}}}
$
$= 1
$
Differentiating
Eiche can symbolically differentiate expressions with respect to one or more independent variables. The example below defines a function in two variables, x and y:
let tree = deftree!(- (+ (pow x 3) (pow y 3)) 5).unwrap();
println!("$`f(x, y) = {}`$\n", tree.to_latex());
$f(x, y) = {\left({{x}^{3}} + {{y}^{3}}\right)} - {5}
$
You can differentate above function with respect to x and y to produce a a row vector containing the partial derivatives:
$\begin{bmatrix}{{{x}^{3}}.{\left({3}.{\dfrac{1}{x}}\right)}} & {{{y}^{3}}.{\left({3}.{\dfrac{1}{y}}\right)}}\end{bmatrix}
$
You can differentiate the result again with respect to x and y to get the hessian matrix:
let hessian = deriv.symbolic_deriv("xy").unwrap();
println!("$`{}`$\n", hessian.to_latex());
$\begin{bmatrix}{{{{x}^{3}}.{\left({3}.{\dfrac{-1}{{x}^{2}}}\right)}} + {{\left({3}.{\dfrac{1}{x}}\right)}.{\left({{x}^{3}}.{\left({3}.{\dfrac{1}{x}}\right)}\right)}}} & {0} \\ {0} & {{{{y}^{3}}.{\left({3}.{\dfrac{-1}{{y}^{2}}}\right)}} + {{\left({3}.{\dfrac{1}{y}}\right)}.{\left({{y}^{3}}.{\left({3}.{\dfrac{1}{y}}\right)}\right)}}}\end{bmatrix}
$
This expressions for the derivative in the hessian are unnecessarily
complicated. This is because of how Eiche applies the chain rule when
differentiating the expressions. Let's use the reduce
function to simplify:
let steps = reduce(hessian, 256).unwrap();
// The last step is the most simplified form:
println!("$`{}`$\n", steps.last().unwrap().to_latex());
$\begin{bmatrix}{{x}.{6}} & {0} \\ {0} & {{y}.{6}}\end{bmatrix}
$
Dependencies
~0.2–0.8MB
~14K SLoC