Getting started
Ipopt solves the following class of nonlinear programming problems (NLP):
Lets start with a minimal example:
That is, \(\boldsymbol{x}_l = (-10, -10, -10)\), \(\boldsymbol{x}_u = (10, 10, 10)\).
Step 1: Define the problem in Python
We first define the corresponding Python function for f
and its derivative:
def f(x: np_array) -> float:
out: float = numpy.sum(x**2)
return out
def grad_f(x: np_array, out: np_array) -> np_array:
out[()] = 2.0 * x
return out
Note
For performance reasons, you have to write the value of
grad_f
into the out
argument of the function (this way we can
avoid unnecessary memory allocation).
Next, we define g
and its derivative:
def g(x: np_array, out: np_array) -> np_array:
"""Constraint function: squared distance to (1, 0, ..., 0)"""
out[0] = numpy.sum((x - _e_x) ** 2)
return out
def jac_g(x: np_array, out: np_array) -> np_array:
out[()] = 2.0 * (x - _e_x)
return out
Here, The Jacobian \(\mathrm{D} g = 2 (x_0 - 1, x_1, x_2)\), thus the non
zero slots (all slots in this case) are (i,j) = (0,0), (0,1), (0,2)
.
Translated into python code:
(numpy.array([0, 0, 0]), numpy.array([0, 1, 2]))
Note
While in the notation (i,j) = (0,0), (0,1), (0,2)
, we use index pairs,
you have to provide the collections of all i
components
(first tuple field) and the collection of all j
components (second
tuple field) as sparsity info arguments to ipyopt.
For maximal performance, we also can provide the Hessian of the Lagrangian \(L(x) = \sigma f(x) + \langle \lambda, g(x)\rangle\), where \(\langle \cdot, \cdot \rangle\) denotes the standard scalar product (in this case just the usual multiplication, as we are in \(\mathbb{R}^1\)). In our case:
Therefore, we have nonzero entries at the diagonal, i.e. (i,j) =
(0,0), (1,1), (2,2)
.
The corresponding sparsity info argument is
(numpy.array([0, 1, 2]), numpy.array([0, 1, 2]))
and this is how
to compute the non zero entries:
def h(
_x: np_array, lagrange: np_array, obj_factor: float, out: np_array
) -> np_array:
out[()] = numpy.full((_n,), 2.0 * (obj_factor + lagrange[0]))
return out
Here, lagrange
corresponds to \(\lambda\) and obj_factor
to
\(\sigma\).
If we don’t provide the Hessian, Ipopt will numerically approximate it
for us, at the price of some performance loss.
Now, we are ready to define the Python problem:
import ipyopt
nlp = ipyopt.Problem(
n=2,
x_l=numpy.array([-10.0, -10.0, -10.0]),
x_u=numpy.array([10.0, 10.0, 10.]),
m=1,
g_l=numpy.array([0.0]),
g_u=numpy.array([4.0]),
sparsity_indices_jac_g=(numpy.array([0, 0, 0]), numpy.array([0, 1, 2])),
sparsity_indices_h=(numpy.array([0, 1, 2]), numpy.array([0, 1, 2])),
f,
grad_f,
g,
jac_g,
h
)
Step 2: Solve the problem
We will use \(x_0 = (0.1, 0.1, 0.1)\) as initial guess.
x, obj, status = nlp.solve(x0=numpy.array([0.1, 0.1, 0.1]))
As a result, we should obtain the solution x = (0.,0.,0.)
, obj =
f(x) = 0.
and status = 0
(meaning, that Ipopt found the optimal
solution within tolerance).