The expression system

The expression system#

As we saw in the previous section, heyoka needs to be able to represent the right-hand side of an ODE system in symbolic form in order to be able to compute its high-order derivatives via automatic differentiation. heyoka represents generic mathematical expressions via a simple abstract syntax tree (AST) in which the internal nodes are n-ary functions, and the leaf nodes can be:

Constants and parameters are mathematically equivalent, the only difference being that the value of a constant is determined when the expression is created, whereas the value of a parameter is loaded from a user-supplied data array at a later stage.

Thanks to the use of modern C++ features (user-defined literals, overloaded operators, etc.), the construction of the AST of an expression in heyoka can be accomplished via natural mathematical notation:

    // Create multiple symbolic variables in one go.
    auto [x, y] = make_vars("x", "y");

    // Another way of creating symbolic variables.
    auto z = "z"_var;

    // Create and print to screen a mathematical expression.
    std::cout << "The euclidean distance is: " << sqrt(x * x + y * y + z * z) << '\n';

Numerical constants can be created using any of the floating-point types supported by heyoka:

    // Create and print to screen a few constants using
    // different floating-point precisions.

    // Double precision.
    std::cout << 1.1_dbl << '\n'; // Prints '1.1000000000000001'

    // Long double precision (80-bit on x86).
    std::cout << 1.1_ldbl << '\n'; // Prints '1.10000000000000000002'

    // Quadruple precision on x86-64.
    std::cout << 1.1_f128 << '\n'; // Prints '1.10000000000000000000000000000000008'

Note that support for extended-precision floating-point types varies depending on the software/hardware platform.

In addition to the standard mathematical operators, heyoka’s expression system also supports several elementary and special functions:

  • square root,

  • exponentiation,

  • the basic trigonometric and hyperbolic functions, and their inverse counterparts,

  • the natural logarithm and exponential,

  • the standard logistic function (sigmoid),

  • the error function,

  • several variants of Kepler’s elliptic equation.

heyoka also provides an API for implementing new functions without modifying the library’s code.

It must be emphasised that heyoka’s expression system is not a full-fledged computer algebra system. In particular, its simplification capabilities are very limited. Because heyoka’s performance is sensitive to the complexity of the ODEs, in order to achieve optimal performance it is important to ensure that the mathematical expressions supplied to heyoka are simplified as much as possible.

Full code listing#

#include <iostream>

#include <heyoka/heyoka.hpp>

using namespace heyoka;

int main()
{
    // Create multiple symbolic variables in one go.
    auto [x, y] = make_vars("x", "y");

    // Another way of creating symbolic variables.
    auto z = "z"_var;

    // Create and print to screen a mathematical expression.
    std::cout << "The euclidean distance is: " << sqrt(x * x + y * y + z * z) << '\n';

    // Create and print to screen a few constants using
    // different floating-point precisions.

    // Double precision.
    std::cout << 1.1_dbl << '\n'; // Prints '1.1000000000000001'

    // Long double precision (80-bit on x86).
    std::cout << 1.1_ldbl << '\n'; // Prints '1.10000000000000000002'

#if defined(HEYOKA_HAVE_REAL128)
    // Quadruple precision on x86-64.
    std::cout << 1.1_f128 << '\n'; // Prints '1.10000000000000000000000000000000008'
#endif

    // An expression involving a few elementary functions.
    std::cout << cos(x + 2_dbl * y) * sqrt(z) - exp(x) << '\n';
}