SymPy (for symbolic mathematics)

Main authors: Simon Rohou

The AnalyticFunction class already provides interval automatic differentiation through its .diff() method. This built-in differentiation computes enclosures of Jacobian values over interval inputs and is the natural tool for guaranteed interval analysis.

In some situations however, one may need an explicit symbolic expression obtained from an existing Codac analytic expression:

  • a simplified expression;

  • a symbolic derivative;

  • a symbolic gradient, Hessian, or Jacobian;

  • a truncated Taylor expansion;

  • a polynomial rewritten in Horner form;

  • or a symbolic equality test between two AnalyticFunction objects.

This is the purpose of the SymPy bridge presented on this page. It converts a Codac analytic expression into a SymPy expression, applies symbolic transformations in SymPy, and converts the result back into a Codac AnalyticFunction.

The result remains a first-class Codac object: it can be evaluated, composed, differentiated by intervals, or reused in contractors exactly like any other AnalyticFunction.

Installing the codac-sympy extension

This feature is provided as a Codac extension.

In C++, using it requires:

  • a Codac build configured with the same Python support as the Python binding, see in particular Building a local Python binding for Codac;

  • and the Python package sympy available in the Python environment used by the extension.

In Python, the extension is included by default in Codac. The remaining requirement is therefore simply to install the Python package sympy:

pip install sympy

The same package is also required by the C++ extension, since the symbolic backend relies on the Python SymPy runtime. The Python interpreter is managed internally by the extension in C++, and is of course already available when Codac is used from Python. No explicit interpreter initialization is needed in user code.

Finally, in order to include the features of the extension:

from codac import *
# sympy extension is already embedded in Codac

Simplification

Simplification is often the best first symbolic operation to apply. It can remove obvious redundancies, expose simpler formulas, and produce expressions that are easier to read, compare, or differentiate.

AnalyticFunction<ScalarType> codac2::sympy_simplify(const AnalyticFunction<ScalarType> &f)

Symbolically simplifies a scalar analytic function.

Parameters:

f – Function to simplify.

Returns:

A simplified analytic function.

A classical trigonometric identity is simplified as expected:

x = ScalarVar()
f = AnalyticFunction([x], sqr(sin(x)) + sqr(cos(x)))

fs = sympy_simplify(f)
assert sympy_equal(fs, AnalyticFunction([x], 1.))

Simplification is also useful on purely algebraic expressions:

x = ScalarVar()
f = AnalyticFunction([x], x*(x + 1.) - sqr(x))

fs = sympy_simplify(f)
assert sympy_equal(fs, AnalyticFunction([x], x))

Partial derivatives and symbolic derivatives

The primitive symbolic differentiation operator is sympy_partial_diff. It returns the symbolic partial derivative of a scalar-valued function with respect to one input variable.

AnalyticFunction<ScalarType> codac2::sympy_partial_diff(const AnalyticFunction<ScalarType> &f, const ScalarVar &x)

Returns the symbolic partial derivative of a scalar function with respect to a scalar input variable.

This overload is restricted to scalar variables that appear directly in the function argument list.

Parameters:
  • f – Scalar function.

  • x – Scalar input variable.

Returns:

The symbolic partial derivative.

For scalar arguments:

x = ScalarVar()
y = ScalarVar()
f = AnalyticFunction([x,y], x*y + sin(x))

dfdx = sympy_partial_diff(f, x)
dfdy = sympy_partial_diff(f, y)

assert sympy_equal(dfdx, AnalyticFunction([x,y], y + cos(x)))
assert sympy_equal(dfdy, AnalyticFunction([x,y], x))

When a vector argument is involved, a direct component can be used as the differentiation variable. Note that this notation is usually clearer than referring to a flattened input index, but this type of use is also possible.

v = VectorVar(2)
g = AnalyticFunction([v], v[0]*v[1] + sin(v[0]))

dg_dv0 = sympy_partial_diff(g, v[0])
dg_dv1 = sympy_partial_diff(g, v[1])

assert sympy_equal(dg_dv0, AnalyticFunction([v], v[1] + cos(v[0])))
assert sympy_equal(dg_dv1, AnalyticFunction([v], v[0]))

The function sympy_diff provides symbolic derivatives for scalar functions. For a univariate scalar function, sympy_diff(f) returns the first derivative. Higher-order derivatives are also available.

AnalyticFunction<ScalarType> codac2::sympy_diff(const AnalyticFunction<ScalarType> &f, const ScalarVar &x, Index order)

Returns the symbolic derivative of given order for a scalar function with respect to a scalar input variable.

Parameters:
  • f – Scalar function.

  • x – Scalar input variable.

  • order – Derivation order.

Returns:

The symbolic derivative of order order with respect to x.

x = ScalarVar()
f = AnalyticFunction([x], cos(x)*x + sin(x))

df = sympy_diff(f)
d3f = sympy_diff(f, x, 3)

assert sympy_equal(df, AnalyticFunction([x], 2.*cos(x) - x*sin(x)))
assert sympy_equal(d3f, AnalyticFunction([x], x*sin(x) - 4.*cos(x)))

Gradient, Hessian and Jacobian

The symbolic gradient of a scalar function is obtained with sympy_gradient.

AnalyticFunction<VectorType> codac2::sympy_gradient(const AnalyticFunction<ScalarType> &f)

Returns the symbolic gradient of a scalar function.

Parameters:

f – Scalar function.

Returns:

The symbolic gradient.

v = VectorVar(2)
g = AnalyticFunction([v], v[0]*v[1] + sin(v[0]))

grad_g = sympy_gradient(g)

assert sympy_equal(
  grad_g,
  AnalyticFunction([v], vec(v[1] + cos(v[0]), v[0])))

The symbolic Hessian matrix of a scalar function is obtained with sympy_hessian.

AnalyticFunction<MatrixType> codac2::sympy_hessian(const AnalyticFunction<ScalarType> &f)

Returns the symbolic Hessian matrix of a scalar function.

Parameters:

f – Scalar function.

Returns:

The symbolic Hessian matrix.

For the scalar function

\[f(x,y) = x y + \sin(x) + y^2,\]

its Hessian is

\[\begin{split}H_f(x,y) = \begin{pmatrix} -\sin(x) & 1 \\ 1 & 2 \end{pmatrix}.\end{split}\]

In Codac:

x = ScalarVar()
y = ScalarVar()
f = AnalyticFunction([x,y], x*y + sin(x) + sqr(y))

H = sympy_hessian(f)

assert sympy_equal(
  H,
  AnalyticFunction(
    [x,y],
    mat(
      vec(-sin(x), 1.),
      vec(1., 2.)
  )))

For a vector-valued function \(\mathbf{f} : \mathbb{R}^n \to \mathbb{R}^m\), symbolic differentiation returns a Jacobian matrix as another AnalyticFunction:

AnalyticFunction<MatrixType> codac2::sympy_diff(const AnalyticFunction<VectorType> &f)

Returns the symbolic Jacobian matrix of a vector function.

Parameters:

f – Vector function.

Returns:

The symbolic Jacobian matrix.

\[\begin{split}J_{\mathbf{f}}(\mathbf{x}) = \begin{pmatrix} \dfrac{\partial f_1}{\partial x_1} & \cdots & \dfrac{\partial f_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \dfrac{\partial f_m}{\partial x_1} & \cdots & \dfrac{\partial f_m}{\partial x_n} \end{pmatrix}.\end{split}\]
v = VectorVar(2)

f = AnalyticFunction([v], [
  v[0]*v[1] + sin(v[0]),
  sqr(v[0]) + cos(v[1])
])

J = sympy_diff(f)

assert sympy_equal(
  J,
  AnalyticFunction(
    [v],
    mat(
      vec(v[1] + cos(v[0]), 2.*v[0]),
      vec(v[0], -sin(v[1]))
    )))

Symbolic equality

Symbolic equality is especially useful to validate a transformation, a differentiation result, or a rewriting.

bool codac2::sympy_equal(const AnalyticFunction<ScalarType> &f, const AnalyticFunction<ScalarType> &g)

Tests symbolic equality of two scalar analytic functions through SymPy.

The two functions are compared on the same flattened domain: only the order and total number of flattened scalar inputs matter, not the original grouping into scalar, vector or matrix arguments.

Parameters:
  • f – First scalar function.

  • g – Second scalar function.

Returns:

True if the two functions are symbolically equal.

For example, the following two scalar functions are symbolically equal although they are expressed with different arguments:

x = ScalarVar()
y = ScalarVar()
v = VectorVar(2)

f = AnalyticFunction([x,y], x + 2.*y)
g = AnalyticFunction([v], v[0] + 2.*v[1])

assert sympy_equal(f, g)

Series expansions

The bridge can also compute truncated Taylor series for scalar functions.

AnalyticFunction<ScalarType> codac2::sympy_series(const AnalyticFunction<ScalarType> &f, const ScalarVar &x, double center, Index order)

Returns a truncated Taylor series of a scalar function with respect to a scalar input variable.

Other input variables are treated as constants.

Parameters:
  • f – Scalar function.

  • x – Scalar input variable.

  • center – Expansion center.

  • order – Truncation order.

Returns:

The truncated Taylor series.

For a univariate function:

x = ScalarVar()
f = AnalyticFunction([x], 1. / (1. - x))

p = sympy_series(f, x, 0.0, 3)

assert sympy_equal(p, AnalyticFunction([x], 1. + x + sqr(x) + x*sqr(x)))

A multivariate function can also be expanded with respect to one scalar variable while the other arguments are treated as constants:

x = ScalarVar()
y = ScalarVar()
f = AnalyticFunction([x,y], y + 1. / (1. - x))

p = sympy_series(f, x, 0.0, 2)

assert sympy_equal(p, AnalyticFunction([x,y], y + 1. + x + sqr(x)))

Horner form

For polynomial expressions, Horner form is a classical rewriting in interval methods. It is usually much more favorable for interval evaluations because it reduces repeated occurrences of the variables and therefore helps reduce pessimism. In practice, this may significantly improve interval polynomial evaluations.

AnalyticFunction<ScalarType> codac2::sympy_horner(const AnalyticFunction<ScalarType> &f)

Rewrites a scalar analytic function in Horner form when possible.

Parameters:

f – Function to rewrite.

Returns:

A rewritten analytic function.

For the polynomial

\[p(x) = 2x^5 + x^3 - 3x^2,\]

SymPy can rewrite the expression in Horner form as

\[p(x) = x^2\bigl(-3+x(1+2x^2)\bigr).\]

In Codac:

x = ScalarVar()
p = AnalyticFunction([x], 2.*pow(x,5) + pow(x,3) - 3.*sqr(x))

h = sympy_horner(p)

assert sympy_equal(h, AnalyticFunction([x], sqr(x)*(-3 + x*(1 + 2*sqr(x)))))

Relationship with interval automatic differentiation

Symbolic differentiation with SymPy and interval automatic differentiation address two related but different needs.

Interval automatic differentiation:

  • works directly on interval inputs;

  • returns an IntervalMatrix enclosure of Jacobian values;

  • is guaranteed and specifically designed for interval computations.

Symbolic differentiation with SymPy:

  • works on the symbolic structure of the expression;

  • returns another AnalyticFunction;

  • is useful when one needs an explicit derivative, Hessian, Jacobian, or rewritten expression that can later be evaluated, composed, or manipulated as a Codac function.

Symbolic differentiation does not replace interval differentiation. It complements it.

Extending the bridge

The SymPy bridge already covers a practical subset of Codac analytic expressions and symbolic transformations. It is however intended to grow with user needs.

If a missing operator, rewriting, simplification, or symbolic transformation would be useful for your applications, please contact the Codac developers. This extension can be enriched progressively.