# Quickstart

```@meta
CurrentModule = MadNLP
```
```@setup quickstart
using NLPModels

```


This page presents a quickstart guide to solve a nonlinear problem with MadNLP.

As a demonstration, we show how to implement
the HS15 nonlinear problem from the Hock & Schittkowski collection,
first by using [JuMP](https://github.com/jump-dev/Jump.jl), and then by specifying the derivatives manually in [NLPModels](https://github.com/JuliaSmoothOptimizers/NLPModels.jl/).

The HS15 problem is defined as:
```math
\begin{aligned}
\min_{x_1, x_2} \; &   100 \times (x_2 - x_1^2)^2 +(1 - x_1)^2 \\
\text{subject to} \quad &  x_1  \times x_2 \geq 1 \\
        & x_1 + x_2^2 \geq 0 \\
        & x_1 \leq 0.5
\end{aligned}

```
Despite its small dimension, its resolution remains challenging
as the problem is nonlinear nonconvex. Note that HS15 has
one bound constraint ($$x_1 \leq 0.5$$) and two generic constraints.
We display in the following picture the feasible set of HS15. The problem
has two local solutions: ``(x_1, x_2) = (0.5, 2)`` and ``(x_1, x_2) \approx (-0.792, -1.262)``.

![HS15](assets/hs15.png)


## Using MadNLP with JuMP

JuMP is the easiest way to implement a nonlinear problem.
In JuMP, the user just has only to pass the structure of the problem,
the computation of the first- and second-order derivatives being handled automatically.

Using JuMP's syntax, the HS15 problem translates to
```@example quickstart
using JuMP
model = Model()
@variable(model, x1 <= 0.5)
@variable(model, x2)

@objective(model, Min, 100.0 * (x2 - x1^2)^2 + (1.0 - x1)^2)
@constraint(model, x1 * x2 >= 1.0)
@constraint(model, x1 + x2^2 >= 0.0)

println(model)

```

Then, solving HS15 with MadNLP directly translates to
```@example quickstart
using MadNLP
JuMP.set_optimizer(model, MadNLP.Optimizer)
JuMP.optimize!(model)

```

Under the hood, JuMP builds a sparse AD backend to evaluate the first and second-order
derivatives of the objective and the constraints. Then, MadNLP takes as input the callbacks generated by JuMP
and wraps them inside a `MadNLP.MOIModel`.

## Using MadNLP with NLPModels

Alternatively, we can compute the derivatives manually in an `AbstractNLPModel`.
This second option, although more complicated, gives us more flexibility and comes without boilerplate.

We define the `HS15Model` as :
```@example quickstart
struct HS15Model <: NLPModels.AbstractNLPModel{Float64,Vector{Float64}}
    meta::NLPModels.NLPModelMeta{Float64, Vector{Float64}}
    counters::NLPModels.Counters
end

function HS15Model(x0)
    return HS15Model(
        NLPModels.NLPModelMeta(
            2,     #nvar
            ncon = 2,
            nnzj = 4,
            nnzh = 3,
            x0 = x0,
            y0 = zeros(2),
            lvar = [-Inf, -Inf],
            uvar = [0.5, Inf],
            lcon = [1.0, 0.0],
            ucon = [Inf, Inf],
            minimize = true
        ),
        NLPModels.Counters()
    )
end
```
This structure takes as input the initial position `x0` and generates
an `AbstractNLPModel`. `NLPModelMeta` stores the information about
the structure of the problem (variables and constraints' lower and upper bounds,
number of variables, number of constraints, ...). `Counters` is a utility
storing the number of time each callbacks is being called.

The objective function takes as input a `HS15Model` instance
and a vector with dimension 2 storing the current values for $$x_1$$ and $$x_2$$:
```@example quickstart
function NLPModels.obj(nlp::HS15Model, x::AbstractVector)
    return 100.0 * (x[2] - x[1]^2)^2 + (1.0 - x[1])^2
end
```
The corresponding gradient writes (note that we update the values of the gradient
`g` inplace):
```@example quickstart
function NLPModels.grad!(nlp::HS15Model, x::AbstractVector, g::AbstractVector)
    z = x[2] - x[1]^2
    g[1] = -400.0 * z * x[1] - 2.0 * (1.0 - x[1])
    g[2] = 200.0 * z
    return g
end
```

Similarly, we define the constraints
```@example quickstart
function NLPModels.cons!(nlp::HS15Model, x::AbstractVector, c::AbstractVector)
    c[1] = x[1] * x[2]
    c[2] = x[1] + x[2]^2
    return c
end
```

!!! note
    Observe that we update the values of the constraint vector `c` and the gradient vector `g` inplace.


The Jacobian is defined as
```@example quickstart
function NLPModels.jac_structure!(nlp::HS15Model, I::AbstractVector{T}, J::AbstractVector{T}) where T
    copyto!(I, [1, 1, 2, 2])
    copyto!(J, [1, 2, 1, 2])
end

function NLPModels.jac_coord!(nlp::HS15Model, x::AbstractVector, J::AbstractVector)
    J[1] = x[2]    # (1, 1)
    J[2] = x[1]    # (1, 2)
    J[3] = 1.0     # (2, 1)
    J[4] = 2*x[2]  # (2, 2)
    return J
end
```

!!! note
    As the Jacobian is sparse, we have to provide explicitly its sparsity structure.


It remains to implement the Hessian of the Lagrangian for a `HS15Model`.
The Lagrangian of the problem writes
```math
L(x_1, x_2, y_1, y_2) = 100 \times (x_2 - x_1^2)^2 +(1 - x_1)^2
+ y_1 \times (x_1 \times x_2) + y_2 \times (x_1 + x_2^2)
```
and we aim at evaluating its second-order derivative $$\nabla^2_{xx}L(x_1, x_2, y_1, y_2)$$.

We first have to define the sparsity structure of the Hessian, which is
assumed to be sparse. The Hessian is a symmetric matrix, and by convention
we pass only the lower-triangular part of the matrix to the solver. Hence,
we define the sparsity structure as
```@example quickstart
function NLPModels.hess_structure!(nlp::HS15Model, I::AbstractVector{T}, J::AbstractVector{T}) where T
    copyto!(I, [1, 2, 2])
    copyto!(J, [1, 1, 2])
end
```

Now that the sparsity structure is defined, the associated Hessian writes:
```@example quickstart
function NLPModels.hess_coord!(nlp::HS15Model, x, y, H::AbstractVector; obj_weight=1.0)
    # Objective
    H[1] = obj_weight * (-400.0 * x[2] + 1200.0 * x[1]^2 + 2.0)
    H[2] = obj_weight * (-400.0 * x[1])
    H[3] = obj_weight * 200.0
    # First constraint
    H[2] += y[1] * 1.0
    # Second constraint
    H[3] += y[2] * 2.0
    return H
end

```

Once the problem specified in NLPModels, we can create a new MadNLP instance and solve it:
```@example quickstart
x0 = zeros(2) # initial position
nlp = HS15Model(x0)
solver = MadNLP.MadNLPSolver(nlp; print_level=MadNLP.INFO)
results = MadNLP.solve!(solver)
nothing
```

MadNLP converges in 19 iterations to a (local) optimal solution.
MadNLP returns a [`MadNLPExecutionStats`](@ref) storing all the
results. We can query the primal and the dual solutions respectively by
```@example quickstart
results.solution
```
and
```@example quickstart
results.multipliers
```
If we come back at the picture displayed at the beginning of this page,
the solution found is the one at the bottom left. At this solution, only
one constraint is active, explaining why only the first element in `results.multipliers`
is non-null (up to floating point approximation).

The dual multipliers associated to the variable's lower bounds (resp. upper bounds) are stored
in `results.multipliers_L` (resp. `results.multipliers_U`):
```@example quickstart
results.multipliers_L
```

By changing the initial point in `HS15Model`, MadNLP converges to the second local solution
of the non-convex problem HS15:
```@example quickstart
x0 = ones(2)
nlp = HS15Model(x0)
results = madnlp(nlp)
nothing
```

```@example quickstart
results.solution
```
Note that we don't know beforehand towards each solution MadNLP is converging.

