Proper contexts has arrived!
In this update, ODE and integrate have gotten support for a context NumContext
which provides a persistent storage between function calls. It can be used to pass in parameters or to save extra information during the processing. For example:
- If you have a matrix you want to reuse often in your function, you can save it in the
NumContext
once and then access it with a key. This way you only have to evaluate it once instead of creating it on every function call or making it a global variable. - You want to modify a parameter during the processing and access the modified value during later calls.
Warning: NumContext
is a ref-type. So it means that the context variable you pass in will be altered if you do anything to it in your function. The ctx
in your function is the exact same as the one you pass in, so if you change it, you will also change the original variable.
What does this mean for your old code? It means you have to change the proc signature for your functions:
- ODE:
# Old code
proc f[T](x: float, y: T): T =
# do your calculation here
# New code
proc(t: float, y: T, ctx: NumContext[T]): T =
# do your calculations here
- integrate:
# Old code
proc(x: float, optional: seq[T]): T =
# do your calculations here
# New code
proc(x: float, ctx: NumContext[T]): T =
# do your calculations here
So how do we create a NumContext
then? Here is an example:
import arraymancer, numericalnim
var ctx = newNumContext[Tensor[float]]()
# Values of type `T`, `Tensor[float]` in this case is accessed using `[]` with either a string or enum as key.
ctx["A"] = @[@[1.0, 2.0], @[3.0, 4.0]].toTensor
# `NumContext` does always have a float storage as well accessed using `setF` and `getF`.
ctx.setF("k", 3.14)
# it can then be accessed using ctx.getF("k")
As for passing in the NumContext
you pass it in as the parameter ctx
to the integration proc or solveODE
:
proc f_integrate(x: float, ctx: NumContext[float]): float = sin(x)
let I = adaptiveGauss(f_integrate, 0.0, 2.0, ctx = ctx)
proc f_ode(t: float, y: float, ctx: NumContext[float]): float = sin(t)*y
let (ts, ys) = solveODE(f_ode, y0 = 1.0, tspan = [0.0, 2.0], ctx = ctx)
If you don't pass in a ctx
, an empty one will be created for you but you won't be able to access after the proc has finished and returned the results.
Using enums as keys for NumContext
If you want to avoid KeyErrors regarding mistyped keys, you can use enums for the keys instead. The enum value will be converted to a string internally so there is no constraint that all keys must be from the same enum. Here is one example of how to use it:
type
MyKeys = enum
key1
key2
key3
var ctx = newNumContext[float]()
ctx[key1] = 3.14
ctx[key2] = 6.28