2 releases

0.1.1 Feb 14, 2024
0.1.0 Feb 14, 2024

#196 in Math

35 downloads per month

GPL-3.0-only

200KB
5K 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 and 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

~325KB