Skip to content

Latest commit

 

History

History
108 lines (87 loc) · 3.75 KB

README.org

File metadata and controls

108 lines (87 loc) · 3.75 KB

astGrad - Symbolic differentiation based on the Nim AST

This library performs symbolic differentiation based on the nodes of the Nim AST. This allows for compile time generation of derivatives to avoid approximations due to numerical methods.

Note: as this is dealing with symbolic differentiation and the code isn’t extremely smart about doing simplification yet, the resulting function can become relatively large on higher orders. For example the 8th derivative of tanh(x) produces about ~7000 lines of code…

For lower orders it works perfectly fine though and as the additional code generation is exponential in nature, even something like the 5th order is still reasonable in case of tanh (O(70) lines).

Some simple simplification (of same addition / subtraction terms, multiplication / division) should help a lot.

Usage

Using this library is pretty straightforward. There is essentially only a single public macro:

macro derivative(arg, wrt: untyped): untyped

This macro takes a Nim expression and a symbol to differentiate by.

For example:

echo derivative(x * x, x) == 2 * x
echo derivative(x * y, y) == x
echo derivative(exp(x), x) == exp(x)
echo derivative(sin(x) * cos(μ)^2 + exp(-((x - μ)^2) / (2 * σ^2)), μ)
#...

you get the idea. Of course every symbol used in the expression for which the derivative is to be computed must exist in the Nim code (otherwise you get “undeclared identifier” errors after the macro computed your gradient).

In addition there is a helper template:

template ∂(arg, wrt: untyped): untyped

to make the code a bit more pretty. In addition to this template higher order versions are defined using superscript unicode, e.g. ∂², ∂³ etc.

Feel free to wrap the call in a procedure to generate the full gradient procedure:

proc gradSin(x: float): float =
  result = ∂(sin(x), x)

doAssert gradSin(-Pi) == cos(-Pi)
doAssert gradSin(0.0) == cos(0.0)
doAssert gradSin(Pi/2.0) == cos(Pi/2.0)

Feel free to go crazy on your derivatives.

Note: the library currently has no introspection functionality to compute derivatives of user defined functions. For a purely mathematical procedure it should be rather straight forward. More complex statements are not really the goal of this library. Its aim is to provide a convenient way to generate gradients of functions that a) one is too lazy to write down or b) that might already be a bit annoying to compute by hand.

Extra fun

Guess what we can do 😎:

import unchained
import scinim/experimental/sugar # just used for the mathScope macro

mathScope:
  f(t, a) = ∂(1.0/2.0 * a * t^2, t)
echo "Speed after ", 1.s, ": ", f(1.0.s, 9.81.m•s⁻²)
echo "Speed after ", 2.s, ": ", f(2.0.s, 9.81.m•s⁻²)
echo "Speed after ", 2.s, ": ", f(3.0.s, 9.81.m•s⁻²)
# Speed after 1 Second: 9.81 Meter•Second⁻¹
# Speed after 2 Second: 19.62 Meter•Second⁻¹
# Speed after 2 Second: 29.43 Meter•Second⁻¹

Doing gradients with units? Pretty neat, huh?

You want more? What if you have some measurement uncertainties associated with your time values? And maybe include the variation of g around the world?

import measuremancer
# And guess what if you have some measurement errors on top of your
# measurement?
echo "Speed after ", 1.s, ": ", f(1.0.s ± 0.05.s, 9.81.m•s⁻² ± 0.03.m•s⁻²)
echo "Speed after ", 2.s, ": ", f(2.0.s ± 0.05.s, 9.81.m•s⁻² ± 0.03.m•s⁻²)
echo "Speed after ", 2.s, ": ", f(3.0.s ± 0.05.s, 9.81.m•s⁻² ± 0.03.m•s⁻²)
# Speed after 1 Second: 9.81 ± 0.491 Meter•Second⁻¹
# Speed after 2 Second: 19.6 ± 0.494 Meter•Second⁻¹
# Speed after 2 Second: 29.4 ± 0.499 Meter•Second⁻¹

Yup.