Awesome
SymbolicNim
A symbolic library written purely in Nim with the ability to compile expressions into efficient functions.
Requires Nim 1.2.6 to run!
Basic usage
SymbolicNim exposes a interface of variables and expressions to the user. Symbolic variables are created with the newVariable
proc:
let x = newSymbol("x")
let y = newSymbol("y")
The name of the variable is used to identify it and is used in printing. If two variables are given the same name, they will be interpreted as equal. If you want to create a lot of variables easily you can do it with the createSymbols
macro:
createSymbols(x, y, coolVar)
This is transformed to:
let x = newSymbol("x")
let y = newSymbol("y")
let coolVar = newSymbol("coolVar")
Expressions are created when we do arithmetic with variables and constants:
let expr1 = 2 * x
let expr2 = x * y + 1 - x ^ 2
let expr3 = sin(x*y) / cos(2*sym_pi) # sym_pi is an exported variable that SymbolicNim interprets as pi.
let expr4 = exp(3*x) * ln(exp(y^5))
# expressions can be used as well:
let expr5 = expr1 - expr2
let expr6 = expr4 / expr3
Derivatives
SymbolicNim can perform symbolic differentitation:
let x = newSymbol("x")
let y = newSymbol("y")
echo diff(x^2, x) # 2*x
echo diff(sin(2*y), y) # 2*cos(2*y)
echo diff(x^2, x, 2) # d^2/dx^2(x^2) = 2
echo diff(x*y, x, y) # d/dy(d/dx(x*y)) = 1
As you can see there are two different ways to call diff
:
- ONE variable and an int:
diff(x^2, x, 2)
. That is the second derivative with respect tox
. - A varargs of variables:
diff(x*y, x, y)
. The derivatives are performed from left to right so the first variable is associated with the innermost derivative.
Note: SymbolicNim's simplification algorithms aren't anything to brag about really so expect that some expressions can look quite ugly and long even though there is a "easy" simplification that could make it neater.
Subs
subs
does what you would expect, it substitutes one expression with another:
echo subs(sin(x+y), x + y, z) # sin(z)
Taylor expansions
SymbolicNim can calculate Taylor expansions up to 20 terms at the moment. If we want to get the first 10 terms of the taylor series of sin(x)
around x = 0
we just have to:
echo taylor(sin(x), x, 0, 10)
Generating efficient Nim procs
One of SymbolicNim's main features is that it can do the symbolic calculations at compiletime and generate a fast Nim proc that get's optimized by the compiler. It could be useful if it's used in a tight loop for example. The syntax for generating the procs is to use the generate
macro:
let x {.compileTime.} = newSymbol("x") # we must define all the variables we want to use at compileTime with the {.compileTime.} pragma.
let y {.compileTime.} = newSymbol("y")
let z {.compileTime.} = newSymbol("z")
let exprToCompile {.compileTime.} = exp(x*y) ^ sin(z/x) + 1 # this is the expression we want to generate into a function
exprToCompile.generate:
proc f(x, y, z: float): float
proc fInline(x, y, z: float): float {.inline.} # you can define how many procs as you want with pragmas and different types
echo f(2.0, 3.0, 4.0)
That wasn't that hard, wasn't it? If you are not sure of the types of the arguments you can try auto
and see if it works. One thing to be aware of is that you must be careful when mixing floats and ints.
Symbolic Matricies
Constants
Symbolic constants are represented as Nim's stdlib Rational[int]
from rationals. Ints and floats are automatically converted to it when entered. Hence if you input a float it will be converted to the best matching fraction and the output may look quite ugly. If you want to write a fraction exactly you can use the //
proc that will create a Rational:
let frac = 1 // 3 # exactly 1 / 3 and not 0.3333333333332 or something.
Status: Alpha
This package is in it's very first stages and will have tonnes of bugs everywhere. If you find something not working, I would very much appreciate if you filed a issue :D
Credits
- @jiro4989 for adding the CI workflow.