Build analytic functions

The AnalyticFunction class allows to build analytic inclusion functions and evaluate them using interval analysis.

Considering a \(n\)-dimensional box \([\mathbf{x}]\) as input, a function \(\mathbf{f}:\mathbb{R}^n\to\mathbb{R}^m\) will output a set which is not necessarily a box. To obtain a reliable enclosure of the image set \(\mathbf{f}([\mathbf{x}])\), we use inclusion functions.

An inclusion function \([\mathbf{f}]:\mathbb{IR}^n\to\mathbb{IR}^m\) outputs an interval set containing all the feasible images \(\mathbf{f}(\mathbf{x})\) for any \(\mathbf{x}\in[\mathbf{x}]\). Hence it must satisfy: \(\forall [\mathbf{x}]\in\mathbb{IR}^n, \mathbf{f}([\mathbf{x}])\subset[\mathbf{f}]([\mathbf{x}])\). The output set \([\mathbf{f}]([\mathbf{x}])\) will also enclose unfeasible values, due to the wrapping effect of intervals, or because the inclusion function may not be minimal. The choice is made to have fast calculation times, rather than minimal enclosures. In all cases, calculations are guaranteed to contain all the images.

The class AnalyticFunction provides a convenient way to perform calculations on intervals, boxes, and interval matrices. It involves symbolic variables and expressions, and their evaluations rely on interval operators from interval analysis. Real values (reals of \(\mathbb{R}\), vectors, matrices) can also be involved, and will be automatically cast into their interval counterparts.

This page provides an overview of the AnalyticFunction class, its key features, and practical examples of how to use it.

Note

../../../_images/CtcInverse_small.png

For defining a contractor based on an AnalyticFunction, the CtcInverse class is available. It allows to solve constraints under the form \(\mathbf{f}(\mathbf{x})\in[\mathbf{y}]\).

See more.

Variables

Before defining an AnalyticFunction, you need to define the variables that will be used in the function. The following classes allow to represent arguments:

  • ScalarVar(): constructor for a scalar variable

  • VectorVar(n): constructor for a \(n\)-d vector variable

  • MatrixVar(r,c): constructor for a \(r\times c\) matrix variable

Expressions

An AnalyticFunction object is built from:

  • a list of arguments (ScalarVar, VectorVar or MatrixVar objects), that can be empty;

  • a scalar or vectorial expression involving the arguments, constants, and possibly other functions.

x1 = ScalarVar()
x2 = ScalarVar()
v = VectorVar(3)

# Example of scalar function: from R to R
f1 = AnalyticFunction([x1], x1*cos(x1))

# Example of vectorial function: from R to R²
f2 = AnalyticFunction([x1], [ x1*cos(x1), x1*sin(x1) ])

# Example of vectorial function: from R³ to R²
f3 = AnalyticFunction([v], Interval(-1,1)*v)

# Example of multivariate vectorial function: from R×R to R³
f4 = AnalyticFunction([x1,x2], [ x1+x2, Interval(0,1)*exp(x1), x2^(1+x1) ])

For VectorVar and MatrixVar, as well as for vector or matrix expressions, coefficients can be accessed using the operators [i] and (i,j) respectively. For vector elements, the .subvector(i,j) method allows to get a subvector of the variable or expression.

# Example of function: from R³ to R²
f5 = AnalyticFunction([v], (v[0]*v).subvector(1,2))

# Example of scalar function: from R^(2x2) to R
M = MatrixVar(2,2)
f6 = AnalyticFunction([M], M(0,0)*M(1,1)-M(1,0)*M(0,1))

Variables are temporary objects used only to construct expressions. They can be freed once the function has been instantiated: there is no memory error in using an AnalyticFunction without its *Var arguments in the same scope, as in this example:

def create_f():
  x = ScalarVar()
  return AnalyticFunction([x], x*cos(x))

f = create_f() # x is no longer useful here

Function composition

AnalyticFunction objects can be easily composed using the () operator. For vectorial functions, the [] operator is also available:

f = AnalyticFunction([x1], x1*cos(x1))
g = AnalyticFunction([x1], [ f(2*x1), x1*sin(x1) ])
h = AnalyticFunction([x1], g(x1)[0]) # output is 2*x*cos(2*x)

Evaluations

AnalyticFunction objects represent interval inclusion functions: their evaluations consist in computing interval output sets, either Interval or IntervalVector outputs depending on the definition.

Evaluations can be done by the .eval() method of AnalyticFunction.

x1 = ScalarVar() # scalar argument
f1 = AnalyticFunction([x1], x1*cos(x1))

y1 = f1.eval(0.)
y1 = f1.eval(PI)
y1 = f1.eval(Interval(0,1))

x2 = ScalarVar()
f2 = AnalyticFunction([x1,x2], x1^x2) # example of multivariate function

y2 = f2.eval(Interval(2,3), 2)

v = VectorVar(3) # vector arguments
w = VectorVar(3)
f3 = AnalyticFunction([v,w], v-w) # example of vectorial function

y3 = f3.eval(Vector([5,4,3]), IntervalVector([[3,oo],[2],[1,2]]))

For multivariate functions, it is possible to provide several inputs to the .eval() method, as in the above example with f2 or f3. In Codac C++, the arguments may be of different types (any mixed scalar, vector, or matrix types). In Python and Matlab however, the binding does not allow any combination of inputs, but several arguments of same structural types can be provided: a list of scalar values, or a list of vector values (Vector, IntervalVector), or a list of matrix values (Matrix, IntervalMatrix).

Evaluation modes

The class provides three ways of evaluating functions:

  • natural evaluations \([\mathbf{f}_n]([\mathbf{x}])\): each operator of the analytic function is evaluated by intervals in a natural way. However, natural evaluations may be pessimistic in case of multiple occurences in the expressions. For instance, the evaluation of \([x]-[x]\) will not be \(0\). This is a classic drawback of interval arithmetic.

  • centered form evaluations \([\mathbf{f}_c]([\mathbf{x}])\): computations are made using Eq. (1), depicting a centered form expression. Results are significantly improved for small boxes, but will be worse than a natural evaluation for larger boxes.

(1)\[[\mathbf{f}_c]([\mathbf{x}])=\mathbf{f}(\overline{\mathbf{x}})+[\mathbf{J}_{\mathbf{f}}]\left([\mathbf{x}]\right)\cdot\left([\mathbf{x}]-\overline{\mathbf{x}}\right)\]
  • both natural and centered form evaluations \([\mathbf{f}_n]([\mathbf{x}])\cap[\mathbf{f}_c]([\mathbf{x}])\): the two previous evaluations are carried out simultaneously, and the result is the intersection of the two evaluations. This allows to benefit from the best of both worlds: natural evaluations for large boxes and centered evaluations for small boxes.

By default, the latter mode is used for evaluations. To select a specific mode, the .eval() method can be configured with the desired evaluation mode prior to the inputs:

f1.eval(EvalMode.NATURAL, Interval(0,1))
f1.eval(EvalMode.CENTERED, Interval(0,1))

f1.eval(EvalMode.NATURAL | EvalMode.CENTERED, Interval(0,1))
# which is equivalent to: f1.eval(Interval(0,1))

Interval automatic differentiation

The class provides a simple and efficient way to compute the set of derivatives of functions through its .diff() method. The method computes derivatives with respect to the function’s interval inputs directly, avoiding approximation errors and providing guaranteed results. It works by breaking down complex functions into simpler operations, and then systematically applying the chain rule to compute derivatives of each operation. Symbolic differentiation is not involved in this process. The output is an IntervalMatrix corresponding to an enclosure \([\mathbf{J}_{\mathbf{f}}]([\mathbf{x}])\) of the Jacobian \(\mathbf{J}_{\mathbf{f}}([\mathbf{x}])\):

\[\begin{split}[\mathbf{J}_\mathbf{f}]([\mathbf{x}]) = \begin{pmatrix} [\frac{\partial f_1}{\partial x_1}]([\mathbf{x}]) & [\frac{\partial f_1}{\partial x_2}]([\mathbf{x}]) & \cdots & [\frac{\partial f_1}{\partial x_n}]([\mathbf{x}]) \\ [\frac{\partial f_2}{\partial x_1}]([\mathbf{x}]) & [\frac{\partial f_2}{\partial x_2}]([\mathbf{x}]) & \cdots & [\frac{\partial f_2}{\partial x_n}]([\mathbf{x}]) \\ \vdots & \vdots & \ddots & \vdots \\ [\frac{\partial f_m}{\partial x_1}]([\mathbf{x}]) & [\frac{\partial f_m}{\partial x_2}]([\mathbf{x}]) & \cdots & [\frac{\partial f_m}{\partial x_n}]([\mathbf{x}]) \end{pmatrix}.\end{split}\]

The .diff() method can be used in the same way as the .eval() method.

x1 = ScalarVar()
f1 = AnalyticFunction([x1], x1*cos(x1))
J1 = f1.diff(Interval(0,PI/2))
# J1 = intv. matrix 1x1: [[ [-(PI/2),1] ]]

x2 = ScalarVar()
f2 = AnalyticFunction([x1,x2], x1^x2) # example of multivariate function
J2 = f2.diff(2.,Interval(2,3))
# J2 = intv. matrix 1x2: [[ [4,12], [0,0] ]]

v = VectorVar(3)
f3 = AnalyticFunction([v], [ # vectorial function
  v[0]-(v[1]^2),
  Interval(-1,0)*v[2]
])
J3 = f3.diff(Vector([5,8,10]))
# J3 = intv. matrix 2x3: [[ 1,-16,0 ],[ 0,0,[-1,0] ]]

Other properties

Let us consider a function \([\mathbf{f}]:\mathbb{IR}^n\to\mathbb{IR}^m\), then the dimensions \(n\) and \(m\) can be accessed by:

n = f.input_size()
m = f.output_size()

In the case of multivariate functions, .input_size() returns the sum of the dimensions of the arguments.

The AnalyticFunction class supports many mathematical operations, and the full set of operators that can be used is described in the next page.