Code generation
This tutorial shows how to generate C code to compute expressions found using BSeries.jl. Although BSeries.jl is compatible with three symbolic backends, it's currently easiest to perform code generation using Symbolics.jl.
First, we generate the B-series that we want work with. Here we take a generic 2nd-order RK method and generate terms only up to 3rd order, in order to work with the leading truncation error.
using BSeries, Latexify, Symbolics
@variables α
A = [0 0; 1/(2*α) 0]
b = [1-α, α]
c = [0, 1/(2*α)]
rk22 = bseries(A, b, c, 3)
exact = ExactSolution(rk22)
truncation_error = rk22 - exact
TruncatedBSeries{RootedTree{Int64, Vector{Int64}}, Symbolics.Num} with 5 entries:
RootedTree{Int64}: Int64[] => 0
RootedTree{Int64}: [1] => 0//1
RootedTree{Int64}: [1, 2] => 0//1
RootedTree{Int64}: [1, 2, 3] => -1//6
RootedTree{Int64}: [1, 2, 2] => -(1//3) + 1 / (4α)
Next we set up the ODE of interest, and evaluate the B-series with that right-hand side.
@variables h # time step size
@variables p q # variables of the ODE
f = [p * (2 - q), q * (p - 1)]
du = evaluate(f, [p, q], h, truncation_error)
\[ \begin{equation} \left[ \begin{array}{c} - \frac{1}{6} h^{3} \left( - p \left( \left( -1 + p \right)^{2} q + p q \left( 2 - q \right) \right) + \left( - p \left( -1 + p \right) q + \left( 2 - q \right)^{2} p \right) \left( 2 - q \right) \right) - h^{3} p \left( -1 + p \right) q \left( 2 - q \right) \left( \frac{-1}{3} + \frac{1}{4 \alpha} \right) \\ - \frac{1}{6} h^{3} \left( \left( -1 + p \right) \left( \left( -1 + p \right)^{2} q + p q \left( 2 - q \right) \right) + \left( - p \left( -1 + p \right) q + \left( 2 - q \right)^{2} p \right) q \right) + h^{3} p \left( -1 + p \right) q \left( 2 - q \right) \left( \frac{-1}{3} + \frac{1}{4 \alpha} \right) \\ \end{array} \right] \end{equation} \]
Finally, we generate a C function that evaluates the expressions above.
println(build_function(du, α, p, q, h, target = Symbolics.CTarget()))
#include <math.h>
void diffeqf(double* du, const double RHS1, const double RHS2, const double RHS3, const double RHS4) {
du[0] = -0.16666666666666666 * (RHS4 * RHS4 * RHS4) * (-1 * RHS2 * (pow(-1 + RHS2, 2) * RHS3 + RHS2 * RHS3 * (2 + -1 * RHS3 * 1)) + (-1 * RHS2 * (-1 + RHS2) * RHS3 + RHS2 * pow(2 + -1 * RHS3 * 1, 2)) * (2 + -1 * RHS3 * 1)) + -1.0 * (RHS4 * RHS4 * RHS4) * RHS2 * (-1 + RHS2) * RHS3 * (2 + -1 * RHS3 * 1) * (-0.3333333333333333 + 1 / (4 * RHS1 * 1));
du[1] = -0.16666666666666666 * (RHS4 * RHS4 * RHS4) * ((-1 + RHS2) * (pow(-1 + RHS2, 2) * RHS3 + RHS2 * RHS3 * (2 + -1 * RHS3 * 1)) + (-1 * RHS2 * (-1 + RHS2) * RHS3 + RHS2 * pow(2 + -1 * RHS3 * 1, 2)) * RHS3) + (RHS4 * RHS4 * RHS4) * RHS2 * (-1 + RHS2) * RHS3 * (2 + -1 * RHS3 * 1) * (-0.3333333333333333 + 1 / (4 * RHS1 * 1));
}
The Symbolics.jl function build_function
can also generate code in Julia, MATLAB, and Stan; see the documentation for details and other options.