Specifying ODEs

clODE supports many common use cases for specifying systems of ordinary differential equations (ODEs). This document outlines the various ways to specify ODEs in clODE.

OpenCL

The ODE system can be specified directly as an OpenCL C-languange function, with the following signature:

void getRHS(const realtype t,
            const realtype variables[],
            const realtype parameters[],
            realtype derivatives[],
            realtype aux[],
            const realtype wiener[]);

Python

To support conversion to OpenCL, an ODE system specified as a python function must be fully typed, with the following signature:

def getRHS(float t,
           list[float] variables,
           list[float] parameters,
           list[float] derivatives,
           list[float] aux,
           list[float] weiner) -> None

To support using the same function with Scipy's `solve_ivp, we provide a wrapper that provides the signature expected there [TODO].

Arithmetic operations

clODE supports the following arithmetic operations:

  • Addition: +
  • Subtraction: -
  • Multiplication: *
  • Division: /
  • Exponentiation: **
  • Modulo: %
>>> from clode import OpenCLConverter
>>> def get_rhs(t: float,
...             variables: list[float],
...             parameters: list[float],
...             derivatives: list[float],
...             aux: list[float],
...             wiener: list[float]) -> None:
...     x: float = variables[0]
...     y: float = variables[1]
...     
...     derivatives[0] = x + y
...     derivatives[1] = x - y
...     aux[1] = x * y
...     aux[2] = x / y
...     aux[3] = x ** 3
...     aux[4] = x % y
... 
>>> converter = OpenCLConverter()
>>> print(converter.convert_to_opencl(get_rhs))
void get_rhs(const realtype t,
             const realtype variables[],
             const realtype parameters[],
             realtype derivatives[],
             realtype aux[],
             const realtype wiener[]) {
    realtype x = variables[0];
    realtype y = variables[1];
    derivatives[0] = (x + y);
    derivatives[1] = (x - y);
    aux[1] = (x * y);
    aux[2] = (x / y);
    aux[3] = (x * x * x);
    aux[4] = (x % y);
}

Note that the exponent operator x ** y is implicitly converted to x * ... * x for integer exponents if x < 5. If x >= 5, the exponent operator is converted to the pown function. If the exponent is a float, the pow function is used.

Using builtins (sin, exp, etc.)

Certain builtin functions are available in clODE. By convention, the floating point versions of these functions are used.

The functions trigonometric currently available are:

Sine Cosine Tangent Other
sin cos tan atan2
asin acos atan atan2pi
sinh cosh tanh
asinh acosh atanh
sinpi cospi tanpi
asinpi acospi atanpi

Additionally, the following functions are supported:

Exponential & Logarithmic Power & Root Rounding & Remainder Miscellaneous
exp cbrt ceil copysign
exp2 pow floor fabs
exp10 pown fmod fdim
expm1 powr remainder gamma
log sqrt rint hypot
log1p rsqrt trunc ilogb
log2 rootn ldexp
log10 lgamma
nextafter
erf
erfc

You can change between int and float numbers by using the Python builtins int() and float().

Note: You can import arithmetic functions from math, numpy or clode.

>>> from clode import OpenCLConverter, exp
>>> def get_rhs(
...     t: float,
...     variables: list[float],
...     parameters: list[float],
...     derivatives: list[float],
...     aux: list[float],
...     wiener: list[float],
... ) -> None:
...     x: float = variables[0]
...     y: float = variables[1]
...     p1: int = int(parameters[0])
... 
...     derivatives[0] = (x - y) / exp(y)
...     derivatives[1] = -x ** p1
... 
>>> converter = OpenCLConverter()
>>> print(converter.convert_to_opencl(get_rhs))
void get_rhs(const realtype t,
             const realtype variables[],
             const realtype parameters[],
             realtype derivatives[],
             const realtype aux[],
             const realtype wiener[]) {
    realtype x = variables[0];
    realtype y = variables[1];
    int p1 = (int)(parameters[0]);
    derivatives[0] = ((x - y) / exp(y));
    derivatives[1] = (-pown(x, p1));
}

Specifying helper functions

It is possible to specify helper functions in Python and call them from the main ODE function. This can be useful for breaking up complex ODEs into smaller, more manageable pieces.

>>> from clode import OpenCLConverter
>>> def helper_function(x: float, y: float) -> float:
...     return (x + y) / y
... 
>>> def get_rhs(t: float,
...             variables: list[float],
...             parameters: list[float],
...             derivatives: list[float],
...             aux: list[float],
...             wiener: list[float]) -> None:
...     x: float = variables[0]
...     y: float = variables[1]
...     
...     derivatives[0] = helper_function(x, y)
... 
>>> converter = OpenCLConverter()
>>> converter.convert_to_opencl(helper_function)
'realtype helper_function(const realtype x,\n                         const realtype y) {\n    return ((x + y) / y);\n}\n\n'
>>> print(converter.convert_to_opencl(get_rhs))
realtype helper_function(const realtype x,
                         const realtype y) {
    return ((x + y) / y);
}

realtype helper_function(const realtype x,
                         const realtype y) {
    return ((x + y) / y);
}

To load a helper function into the OpenCL program, you must use the list argument supplementary_equations in the Simulator initializers.

Note: The functions must be specified in the order that they are used. clODE will not automatically resolve dependencies between functions.

simulator = TrajectorySimulator(
    rhs_equation=get_rhs,
    variables={"x": 0.0, "y": 2.0},
    parameters={"a": 1.0},
    supplementary_equations=[helper_function],
    t_span=(0, 10),
    dt=0.01,
)