A simple optimization problem#
Here we provide a compact example on how csnlp can be employed to build and solve
an optimization problem. Similar to casadi.Opti, we instantiate a class which
represents the NLP and allows us to create its variables and parameters and model its
constraints and objective. For example, suppose we’d like to solve the following problem
We can do so with the following code:
from csnlp import Nlp
nlp = Nlp()
x = nlp.variable("x")[0] # create primal variable x
y = nlp.variable("y")[0] # create primal variable y
p = nlp.parameter("p") # create parameter p
# define the objective and constraints
nlp.minimize((1 - x) ** 2 + 0.2 * (y - x**2) ** 2)
g = (x + 0.5) ** 2 + y**2
nlp.constraint("c1", (p / 2) ** 2, "<=", g)
nlp.constraint("c2", g, "<=", p**2)
nlp.init_solver() # initializes IPOPT under the hood
sol = nlp.solve(pars={"p": 1.25}) # solves the NLP for parameter p=1.25
x_opt = sol.vals["x"] # optimal values can be retrieved via the dict .vals
y_opt = sol.value(y) # or the .value method
Enhancing the NLP class#
However, the package also allows to seamlessly enhance the standard csnlp.Nlp
with different capabilities. For instance, when the problem is highly nonlinear and
necessitates to be solved with multiple initial conditions, the csnlp.multistart
module offers various solutions to parallelize the computations (see, e.g.,
csnlp.multistart.ParallelMultistartNlp). The csnlp.wrappers module
offers instead a set of wrappers that can be used to augment the NLP with additional
capabilities, without modifying the original NLP instance: as of now, wrappers have been
implemented for
sensitivity analysis (see
csnlp.wrappers.NlpSensitivity[4])Model Predictive Control (see
csnlp.wrappers.Mpc[6] andcsnlp.wrappers.ScenarioBasedMpc[7])NLP scaling (see
csnlp.wrappers.NlpScalingandcsnlp.core.scaling).
For example, if we’d like to compute the sensitivity
\(\frac{\partial y}{\partial p}\) of the optimal primal variable \(y\) with
respect to the parameter \(p\), we just need to wrap the csnlp.Nlp instance
with the csnlp.wrappers.NlpSensitivity wrapper, which is specialized in
differentiating the optimization problem. This in turn allows us to compute the
first-order \(\frac{\partial y}{\partial p}\) and second sensitivities
\(\frac{\partial^2 y}{\partial p^2}\) (dydp and d2ydp2, respectively) as
such:
from csnlp import wrappers
nlp = wrappers.NlpSensitivity(nlp)
dydp, d2ydp2 = nlp.parametric_sensitivity()
In other words, these sensitivities provide the jacobian and hessian that locally approximate the solution w.r.t. the parameter \(p\). As shown in the corresponding example but not in this quick demonstation, the sensitivity can be also computed for any generic expression \(z(x(p),\lambda(p),p)\) that is a function of the primal \(x\) and dual \(\lambda\) variables, and the parameters \(p\). Moreover, the sensitivity computations can be carried out symbolically (more demanding) or numerically (more stable and reliable).
Similarly, a csnlp.Nlp can be wrapped in a csnlp.wrappers.Mpc wrapper
that makes it easier to build such finite-horizon optimal controllers for model-based
control applications.