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
AnalyticFunctionobjects.
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
sympyavailable 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
#include <codac-sympy.h>
% todo
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.))
ScalarVar x;
AnalyticFunction f({x}, sqr(sin(x)) + sqr(cos(x)));
auto fs = sympy_simplify(f);
assert(sympy_equal(fs, AnalyticFunction({x}, 1.)));
% todo
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))
ScalarVar x;
AnalyticFunction f({x}, x*(x + 1.) - sqr(x));
auto fs = sympy_simplify(f);
assert(sympy_equal(fs, AnalyticFunction({x}, x)));
% todo
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))
ScalarVar x,y;
AnalyticFunction f({x,y}, x*y + sin(x));
auto dfdx = sympy_partial_diff(f,x);
auto dfdy = sympy_partial_diff(f,y);
assert(sympy_equal(dfdx, AnalyticFunction({x,y}, y + cos(x))));
assert(sympy_equal(dfdy, AnalyticFunction({x,y}, x)));
% todo
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]))
VectorVar v(2);
AnalyticFunction g({v}, v[0]*v[1] + sin(v[0]));
auto dg_dv0 = sympy_partial_diff(g,v[0]);
auto 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])));
% todo
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
orderwith respect tox.
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)))
ScalarVar x;
AnalyticFunction f({x}, cos(x)*x + sin(x));
auto df = sympy_diff(f);
auto 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))));
% todo
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])))
VectorVar v(2);
AnalyticFunction g({v}, v[0]*v[1] + sin(v[0]));
auto grad_g = sympy_gradient(g);
assert(sympy_equal(
grad_g,
AnalyticFunction({v}, vec(v[1] + cos(v[0]), v[0]))));
% todo
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
its Hessian is
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.)
)))
ScalarVar x,y;
AnalyticFunction f({x,y}, x*y + sin(x) + sqr(y));
auto H = sympy_hessian(f);
assert(sympy_equal(
H,
AnalyticFunction(
{x,y},
mat(
vec(-sin(x), 1.),
vec(1., 2.)
))));
% todo
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.
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]))
)))
VectorVar v(2);
AnalyticFunction f({v}, {
v[0]*v[1] + sin(v[0]),
sqr(v[0]) + cos(v[1])
});
auto 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]))
))));
% todo
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)
ScalarVar x,y;
VectorVar v(2);
auto f = AnalyticFunction({x,y}, x + 2.*y);
auto g = AnalyticFunction({v}, v[0] + 2.*v[1]);
assert(sympy_equal(f, g));
% todo
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)))
ScalarVar x;
AnalyticFunction f({x}, 1. / (1. - x));
auto p = sympy_series(f, x, 0.0, 3);
assert(sympy_equal(p, AnalyticFunction({x}, 1. + x + sqr(x) + x*sqr(x))));
% todo
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)))
ScalarVar x,y;
AnalyticFunction f({x,y}, y + 1. / (1. - x));
auto p = sympy_series(f, x, 0.0, 2);
assert(sympy_equal(p, AnalyticFunction({x,y}, y + 1. + x + sqr(x))));
% todo
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
SymPy can rewrite the expression in Horner form as
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)))))
ScalarVar x;
AnalyticFunction p({x}, 2.*pow(x,5) + pow(x,3) - 3.*sqr(x));
auto h = sympy_horner(p);
assert(sympy_equal(h, AnalyticFunction({x}, sqr(x)*(-3+x*(1+2*sqr(x))))));
% todo
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
IntervalMatrixenclosure 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.