*fsolve*. I was not sure if and how this could make use of N-dimensional arrays and to find that out I had to understand the function. In the end it turned out that the only generalization that could be done were trivial and required very few changes. However I did find some other problems with the functions that I have been able to fix. Connected to

*fsolve*are the functions

*ctc_intersect*and

*ctc_union*. The also needed only minor changes to allow for N-dimensional input. I will start by giving an introduction to

*fsolve, ctc_union*and

*ctc_intersect*and then I will mention the changes I have done to them.

##
Introduction to *fsolve*

The standard version of *fsolve*in Octave is used to solve systems of nonlinear equations

*.*That is, given a functions $f$ and a starting point $x_0$ it returns a value $x$ such that $f(x)$ is close to zero. The interval version of

*fsolve*does much more than this. It is used to enclose the preimage of a set $Y$ under $f$. Given a domain $X$, a set $Y$ and a function $f$ it returns an enclosure of the set

$$

f^{-1}(Y) = \{x \in X: f(x) \in Y\}.

$$

By letting $Y = 0$ we get similar functionality as the standard

*fsolve*, with the difference that the output is an enclosure of all zeros to the function (compared to one point for which $f$ returns close to zero).

### Example: The Unit Circle

Consider the function $f(x, y) = \sqrt{x^2 + y^2} - 1$ which is zero exactly on the unit circle. Plugging this in to the standard*fsolve*we get with $(0.5, 0.5)$ as a starting guess

> x = fsolve (@(x) f(x(1), x(2)), [0.5, 0.5])

x = 0.70711 0.70711

which indeed is close to a zero. But we get no information about other zeros.

Using the interval version of

*fsolve*with $X = [-3, 3] \times [-3, 3]$ as starting domain we get

> [x paving] = fsolve (f, infsup ([-3, -3], [3, 3]));

> x

x ⊂ 2×1 interval vector

[-1.002, +1.002]

[-1.0079, +1.0079]

Plotting the paving we get the picture

which indeed is a good enclosure of the unit circle.

### How it works

In its simplest form*fsolve*uses a simple bisection scheme to find the enclosure. Using interval methods we can find enclosure to images of sets. Given a set $X_0 \subset X$ we have three different possibilities

- $f(X_0) \subset Y$ in which case we add $X_0$ to the paving
- $f(X_0) \cap Y = \emptyset$ in which case we discard $X_0$
- Otherwise we bisect $X_0$ and continue on the parts

### Contractors

Using bisection is not always very efficient, especially when the domain has many dimensions. One way to speed up the convergence is with what's called contractors. In short a contractor is a function that can take the set $X_0$ and returns a set $X_0' \subset X_0$ with the property that $f(X_0 \setminus X_0') \cap Y = \emptyset$. It's a way of making $X_0$ smaller without having to bisect it that many times.When you construct a contractor you use the reverse operations definer on intervals. I will not go into how this works, if you are interested you can find more information in the package documentation [1] and in these youtube videos about Set Inversion Via Interval Analysis (SIVIA) [2].

The functions

*ctc_union*and

*ctc_intersect*are used to combine contractors on sets into contractors on unions or intersections of these sets.

## Generalization to N-dimensional arrays

How can*fsolve*be generalized to N-dimensional arrays? The only natural thing to do is to allow for the input and output of $f$ to be N-dimensional arrays. This also is no problem to do. While you mathematically probably would say that

*fsolve*is used to do set inversion for functions $f: \mathbb{R}^n \to \mathbb{R}^m$ it can of course also be used for example on functions $f: \mathbb{R}^{n_1}\times \mathbb{R}^{n_2} \to \mathbb{R}^{m_1}\times \mathbb{R}^{m_2}$.

This is however a bit different when using vectorization. When not using vectorization (and not using contractions)

*fsolve*expects that the functions takes one argument which is an array with each element corresponding to a variable. If vectorization is used it instead assumes that the functions takes one argument for each variable. Every argument is then given as a vector with each element corresponding to one value of the variable for which to compute the function. Here we have no use of N-dimensional arrays.

## Modifications

The only change in functionality that I have done to the functions is to allow for N-dimensional arrays as input and output when vectorization is not used. This required only minor changes, essentially changing expressions likemax (max (wid (interval)))

to

max (wid (interval)(:))

It was also enough to do these changes in

*ctc_union*and

*ctc_intersect*to have these support N-dimensional arrays.

I have made no functional changes when vectorization is used. I have however made an optimization in the construction of the arguments to the function. The arguments are stored in an array but before being given to the function they need to be split up into the different variables. This is done by creating a cell array with each element being a vector with the values of one of the variables. Previously the construction of this cell array was very inefficient, it split the interval into its lower and upper part and then called the constructor to create an interval again. Now it copies the intervals into the cell without having to call the constructor. This actually seems have been quite a big improvement, using the old version the example with the unit circle from above took around 0.129 seconds and with the new version it takes about 0.092 seconds. This is of course only one benchmark, but a speed up of about 40% for this test is promising!

Lastly I noticed a problem in the example used in the documentation of the function. The function used is

# Solve x1 ^ 2 + x2 ^ 2 = 1 for -3 ≤ x1, x2 ≤ 3 again,

# but now contractions speed up the algorithm.

function [fval, cx1, cx2] = f (y, x1, x2)

# Forward evaluation

x1_sqr = x1 .^ 2;

x2_sqr = x2 .^ 2;

fval = hypot (x1, x2);

# Reverse evaluation and contraction

y = intersect (y, fval);

# Contract the squares

x1_sqr = intersect (x1_sqr, y - x2_sqr);

x2_sqr = intersect (x2_sqr, y - x1_sqr);

# Contract the parameters

cx1 = sqrrev (x1_sqr, x1);

cx2 = sqrrev (x2_sqr, x2);

endfunction

Do you see the problem? I think it took me more than a day to realize that the problems I was having was not because of a bug in

*fsolve*but because this function computes the wrong thing. The function is supposed to be $f(x_1, x_2) = x_1^2 + x_2^2$ but when calculating the value it calls hypot which is given by $hypot(x_1, x_2) = \sqrt{x_1^2 + x_2^2}$. For $f(x_1, x_2) = 1$, which is used in the example, it gives the same result, but otherwise it will of course not work.

[1] https://octave.sourceforge.io/interval/package_doc/Parameter-Estimation.html#Parameter-Estimation

[2] https://www.youtube.com/watch?v=kxYh2cXHdNQ

Joel, thank you for your blog post. I really enjoyed reading and have just a few questions/remarks:

ReplyDeleteIn the first paragraph you write: "Connected to fsolve are the functions ctc_intersect and ctc_intersect." You mean ctc_union and ctc_intersect, right?

Your references [1] and [2] are no hyperlinks, right? What do they reference to?

What problem did you face with the example function in the end? I am lucky, that you found the problem with the example and must admit, that I never used hypot before and did not have such a close look at this function. Does Oliver consider to adopt your example improvements?

I'm glad to hear that!

DeleteYou are of course right, I mean ctc_intersect and ctc_union. I also apparently forgot to add the links. The post is now updated with this!

I have commited the changes to the example to my repository so the should be added to the interval package when Oliver merge them.