Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ version = "0.10.4"
[deps]
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6"
NLPModelsModifiers = "e01155f1-5c6f-4375-a9d8-616dd036575f"
SolverCore = "ff4d7338-4cf1-434d-91df-b86cb86fb843"

[compat]
Ipopt = "1"
NLPModels = "0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.20, 0.21"
SolverCore = "0.3"
julia = "^1.6"
NLPModelsModifiers = "0.7"

[extras]
ADNLPModels = "54578032-b7ea-4c30-94aa-7cbd1cce6c9a"
Expand Down
37 changes: 37 additions & 0 deletions src/NLPModelsIpopt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module NLPModelsIpopt
export ipopt, IpoptSolver, reset!, solve!

using NLPModels, Ipopt, SolverCore
using NLPModelsModifiers: FeasibilityFormNLS

const ipopt_statuses = Dict(
0 => :first_order,
Expand Down Expand Up @@ -181,6 +182,42 @@ function ipopt(nlp::AbstractNLPModel; kwargs...)
return solve!(solver, nlp, stats; kwargs...)
end

"""
ipopt(nls::AbstractNLSModel; kwargs...)

Solves the `AbstractNLSModel` problem `nls` using `IPOPT` by moving the nonlinear residual to the constraints using slack variables.

# Arguments
- `nls::AbstractNLSModel`: The nonlinear least-squares problem to solve.

For advanced usage, first define a `IpoptSolver` to preallocate the memory used in the algorithm, and then call `solve!`:
solver = IpoptSolver(nls)
solve!(solver, nls; kwargs...)

# Examples
```julia
using NLPModelsIpopt, ADNLPModels
nls = ADNLSModel(x -> [x[1] - 1, x[2] - 2], [0.0, 0.0], 2)
stats = ipopt(nls, print_level = 0)
```
"""
function ipopt(ff_nls::FeasibilityFormNLS; kwargs...)
solver = IpoptSolver(ff_nls)
stats = GenericExecutionStats(ff_nls)
stats = solve!(solver, ff_nls, stats; kwargs...)

return stats


function ipopt(nls::AbstractNLSModel; kwargs...)
ff_nls = isa(nls, FeasibilityFormNLS) ? nls : FeasibilityFormNLS(nls)
stats = ipopt(ff_nls; kwargs...)
# Only keep the original variables in the solution
stats.solution = stats.solution[1:nls.meta.nvar]
return stats
end
end

function SolverCore.solve!(
solver::IpoptSolver,
nlp::AbstractNLPModel,
Expand Down
22 changes: 15 additions & 7 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ADNLPModels, NLPModelsIpopt, NLPModels, Ipopt, SolverCore, Test
using NLPModelsModifiers: FeasibilityFormNLS

@testset "Restart NLPModelsIpopt" begin
nlp = ADNLPModel(x -> (x[1] - 1)^2 + 100 * (x[2] - x[1]^2)^2, [-1.2; 1.0])
Expand All @@ -7,7 +8,7 @@ using ADNLPModels, NLPModelsIpopt, NLPModels, Ipopt, SolverCore, Test
stats = solve!(solver, nlp, stats, print_level = 0)
@test isapprox(stats.solution, [1.0; 1.0], rtol = 1e-6)
@test stats.status == :first_order
@test stats.iter == 21
@test isapprox(stats.iter, 21; atol=1)
@test stats.elapsed_time > 0
@test stats.primal_feas ≈ 0.0
@test stats.dual_feas ≈ 0.0 atol = 1.49e-8
Expand All @@ -21,7 +22,7 @@ using ADNLPModels, NLPModelsIpopt, NLPModels, Ipopt, SolverCore, Test
@test stats.elapsed_time > 0
@test stats.primal_feas ≈ 0.0
@test stats.dual_feas ≈ 0.0 atol = 1.49e-8
end


@testset "Unit tests NLPModelsIpopt" begin
nlp = ADNLPModel(x -> (x[1] - 1)^2 + 100 * (x[2] - x[1]^2)^2, [-1.2; 1.0])
Expand All @@ -34,20 +35,18 @@ end
@test stats.dual_feas ≈ 0.0 atol = 1.49e-8

nlp = ADNLPModel(x -> (x[1] - 1)^2 + 100 * (x[2] - x[1]^2)^2, [-1.2; 1.0])
stats = ipopt(nlp, tol = 1e-12, print_level = 0)
@test isapprox(stats.solution, [1.0; 1.0], rtol = 1e-6)
@test stats.status == :first_order
@test stats.elapsed_time > 0
@test stats.iter == 22
@test isapprox(stats.iter, 22; atol=1)
@test stats.primal_feas ≈ 0.0
@test stats.dual_feas0.0
@test isapprox(stats.dual_feas, 0.0; atol=1e-9)

# solve again from solution
x0 = copy(stats.solution)
stats = ipopt(nlp, x0 = x0, tol = 1e-12, print_level = 0)
@test isapprox(stats.solution, x0, rtol = 1e-6)
@test stats.status == :first_order
@test stats.iter == 0
@test isapprox(stats.iter, 0; atol=1)
@test stats.elapsed_time >= 0
@test stats.primal_feas ≈ 0.0
@test stats.dual_feas ≈ 0.0
Expand Down Expand Up @@ -107,3 +106,12 @@ end
@test stats.primal_feas ≈ 0.0
@test stats.dual_feas ≈ 0.0 atol = 1.49e-8
end

@testset "ipopt with AbstractNLSModel" begin
nls = ADNLSModel(x -> [x[1] - 1, x[2] - 2], [0.0, 0.0], 2)
stats = ipopt(nls, print_level = 0)
@test isapprox(stats.solution[1:2], [1.0, 2.0], rtol = 1e-6)
@test stats.status == :first_order
end

end