Skip to content

Commit 3b7a07e

Browse files
committed
Extensions for NLPModels.jl
1 parent de346b7 commit 3b7a07e

12 files changed

+150
-105
lines changed

Project.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ uuid = "ff4d7338-4cf1-434d-91df-b86cb86fb843"
33
version = "0.3.8"
44

55
[deps]
6-
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
7-
NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6"
86
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
97

8+
[weakdeps]
9+
NLPModels = "a4795742-8479-5a88-8948-cc11e1c8c1a6"
10+
11+
[extensions]
12+
SolverCoreNLPModelsExt = "NLPModels"
13+
1014
[compat]
1115
NLPModels = "0.15, 0.16, 0.17, 0.18, 0.19, 0.20, 0.21"
1216
julia = "^1.10"

ext/SolverCoreNLPModelsExt.jl

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
module SolverCoreNLPModelsExt
2+
3+
using SolverCore
4+
using NLPModels:
5+
AbstractNLPModel,
6+
AbstractNLSModel,
7+
has_bounds,
8+
neval_cons,
9+
neval_obj,
10+
neval_residual,
11+
unconstrained
12+
13+
"""
14+
reset!(stats::GenericExecutionStats, nlp::AbstractNLPModel)
15+
16+
Reset the internal flags of `stats` to `false` to Indicate
17+
that the contents should not be trusted.
18+
If an `AbstractNLPModel` is also provided,
19+
the pre-allocated vectors are adjusted to the problem size.
20+
"""
21+
function SolverCore.reset!(
22+
stats::GenericExecutionStats{T, S},
23+
nlp::AbstractNLPModel{T, S},
24+
) where {T, S}
25+
stats.solution = similar(nlp.meta.x0)
26+
stats.multipliers = similar(nlp.meta.y0)
27+
stats.multipliers_L = similar(nlp.meta.y0, has_bounds(nlp) ? nlp.meta.nvar : 0)
28+
stats.multipliers_U = similar(nlp.meta.y0, has_bounds(nlp) ? nlp.meta.nvar : 0)
29+
SolverCore.reset!(stats)
30+
stats
31+
end
32+
33+
"""
34+
solve!(solver, model; kwargs...)
35+
solve!(solver, model, stats; kwargs...)
36+
37+
Apply `solver` to `model`.
38+
39+
# Arguments
40+
41+
- `solver::AbstractOptimizationSolver`: solver structure to hold all storage necessary for a solve
42+
- `model::AbstractNLPModel`: the model solved, see `NLPModels.jl`
43+
- `stats::GenericExecutionStats`: stats structure to hold solution information.
44+
45+
The first invocation allocates and returns a new `GenericExecutionStats`.
46+
The second one fills out a preallocated stats structure and allows for efficient re-solves.
47+
48+
The `kwargs` are passed to the solver.
49+
50+
# Return Value
51+
52+
- `stats::GenericExecutionStats`: stats structure holding solution information.
53+
"""
54+
function SolverCore.solve!(
55+
solver::AOS,
56+
model::AbstractNLPModel{T, S};
57+
kwargs...,
58+
) where {AOS <: AbstractOptimizationSolver, T, S}
59+
stats = GenericExecutionStats(model)
60+
solve!(solver, model, stats; kwargs...)
61+
end
62+
63+
function SolverCore.solve!(
64+
::AbstractOptimizationSolver,
65+
::AbstractNLPModel,
66+
::GenericExecutionStats;
67+
kwargs...,
68+
) end
69+
70+
function SolverCore.GenericExecutionStats(
71+
nlp::AbstractNLPModel{T, S};
72+
status::Symbol = :unknown,
73+
solution::S = similar(nlp.meta.x0),
74+
objective::T = T(Inf),
75+
dual_feas::T = T(Inf),
76+
primal_feas::T = unconstrained(nlp) ? zero(T) : T(Inf),
77+
multipliers::S = similar(nlp.meta.y0),
78+
multipliers_L::V = similar(nlp.meta.y0, has_bounds(nlp) ? nlp.meta.nvar : 0),
79+
multipliers_U::V = similar(nlp.meta.y0, has_bounds(nlp) ? nlp.meta.nvar : 0),
80+
iter::Int = -1,
81+
elapsed_time::Real = Inf,
82+
solver_specific::Dict{Symbol, Tsp} = Dict{Symbol, Any}(),
83+
) where {T, S, V, Tsp}
84+
SolverCore.check_status(status)
85+
return GenericExecutionStats{T, S, V, Tsp}(
86+
false,
87+
status,
88+
false,
89+
solution,
90+
false,
91+
objective,
92+
false,
93+
dual_feas,
94+
false,
95+
primal_feas,
96+
false,
97+
multipliers,
98+
false,
99+
multipliers_L,
100+
multipliers_U,
101+
false,
102+
iter,
103+
false,
104+
elapsed_time,
105+
false,
106+
solver_specific,
107+
)
108+
end
109+
110+
SolverCore.eval_fun(nlp::AbstractNLPModel) = neval_obj(nlp) + neval_cons(nlp)
111+
SolverCore.eval_fun(nls::AbstractNLSModel) = neval_residual(nls) + neval_cons(nls)
112+
113+
end # end of module

src/SolverCore.jl

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,9 @@
11
module SolverCore
22

3-
using LinearAlgebra: LinearAlgebra, Symmetric, factorize, ldiv!, mul!, norm, qr
4-
using NLPModels:
5-
NLPModels,
6-
AbstractNLPModel,
7-
AbstractNLSModel,
8-
cons!,
9-
grad!,
10-
has_bounds,
11-
hess_coord!,
12-
jac_coord!,
13-
neval_cons,
14-
neval_obj,
15-
neval_residual,
16-
obj,
17-
reset!,
18-
unconstrained
193
using Printf: Printf, @printf, @sprintf
204

215
include("logger.jl")
226
include("stats.jl")
237
include("solver.jl")
24-
include("dummy_solver.jl")
258

269
end

src/solver.jl

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
export AbstractSolver, AbstractOptimizationSolver, solve!
1+
export AbstractSolver, AbstractOptimizationSolver, solve!, reset!
22

33
"Abstract type from which JSO solvers derive."
44
abstract type AbstractSolver end
55

66
abstract type AbstractOptimizationSolver <: AbstractSolver end
77

88
"""
9-
reset!(solver::AbstractOptimizationSolver, model::AbstractNLPModel)
9+
reset!(solver::::AbstractSolver, model)
10+
reset!(solver::::AbstractSolver)
1011
1112
Use in the context of restarting or reusing the `solver` structure.
1213
Reset the internal fields of `solver` for the `model` before calling `solve!` on the same structure.
1314
`model` must have the same number of variables, bounds and constraints as that used to instantiate `solver`.
1415
"""
15-
function NLPModels.reset!(::AbstractOptimizationSolver, ::AbstractNLPModel) end
16+
function reset!(solver::AbstractSolver) end
17+
function reset!(solver::AbstractSolver, model) end
1618

1719
"""
1820
solve!(solver, model; kwargs...)
@@ -22,8 +24,8 @@ Apply `solver` to `model`.
2224
2325
# Arguments
2426
25-
- `solver::AbstractOptimizationSolver`: solver structure to hold all storage necessary for a solve
26-
- `model::AbstractNLPModel`: the model solved, see `NLPModels.jl`
27+
- `solver::::AbstractSolver`: solver structure to hold all storage necessary for a solve
28+
- `model`: the model solved
2729
- `stats::GenericExecutionStats`: stats structure to hold solution information.
2830
2931
The first invocation allocates and returns a new `GenericExecutionStats`.
@@ -35,14 +37,5 @@ The `kwargs` are passed to the solver.
3537
3638
- `stats::GenericExecutionStats`: stats structure holding solution information.
3739
"""
38-
function solve!(solver::AbstractOptimizationSolver, model::AbstractNLPModel; kwargs...)
39-
stats = GenericExecutionStats(model)
40-
solve!(solver, model, stats; kwargs...)
41-
end
42-
43-
function solve!(
44-
::AbstractOptimizationSolver,
45-
::AbstractNLPModel,
46-
::GenericExecutionStats;
47-
kwargs...,
48-
) end
40+
function solve!(solver::AbstractSolver, model; kwargs...) end
41+
function solve!(solver::AbstractSolver, model, stats; kwargs...) end

src/stats.jl

Lines changed: 9 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -170,56 +170,14 @@ function GenericExecutionStats{T, S, V, Tsp}(;
170170
)
171171
end
172172

173-
function GenericExecutionStats(
174-
nlp::AbstractNLPModel{T, S};
175-
status::Symbol = :unknown,
176-
solution::S = similar(nlp.meta.x0),
177-
objective::T = T(Inf),
178-
dual_feas::T = T(Inf),
179-
primal_feas::T = unconstrained(nlp) ? zero(T) : T(Inf),
180-
multipliers::S = similar(nlp.meta.y0),
181-
multipliers_L::V = similar(nlp.meta.y0, has_bounds(nlp) ? nlp.meta.nvar : 0),
182-
multipliers_U::V = similar(nlp.meta.y0, has_bounds(nlp) ? nlp.meta.nvar : 0),
183-
iter::Int = -1,
184-
elapsed_time::Real = Inf,
185-
solver_specific::Dict{Symbol, Tsp} = Dict{Symbol, Any}(),
186-
) where {T, S, V, Tsp}
187-
check_status(status)
188-
return GenericExecutionStats{T, S, V, Tsp}(
189-
false,
190-
status,
191-
false,
192-
solution,
193-
false,
194-
objective,
195-
false,
196-
dual_feas,
197-
false,
198-
primal_feas,
199-
false,
200-
multipliers,
201-
false,
202-
multipliers_L,
203-
multipliers_U,
204-
false,
205-
iter,
206-
false,
207-
elapsed_time,
208-
false,
209-
solver_specific,
210-
)
211-
end
212-
213173
"""
214174
reset!(stats::GenericExecutionStats)
215-
reset!(stats::GenericExecutionStats, nlp::AbstractNLPModel)
175+
reset!(stats::GenericExecutionStats, problem)
216176
217177
Reset the internal flags of `stats` to `false` to Indicate
218178
that the contents should not be trusted.
219-
If an `AbstractNLPModel` is also provided,
220-
the pre-allocated vectors are adjusted to the problem size.
221179
"""
222-
function NLPModels.reset!(stats::GenericExecutionStats)
180+
function reset!(stats::GenericExecutionStats{T, S, V, Tsp}) where {T, S, V, Tsp}
223181
stats.status_reliable = false
224182
stats.solution_reliable = false
225183
stats.objective_reliable = false
@@ -233,16 +191,8 @@ function NLPModels.reset!(stats::GenericExecutionStats)
233191
stats
234192
end
235193

236-
function NLPModels.reset!(
237-
stats::GenericExecutionStats{T, S},
238-
nlp::AbstractNLPModel{T, S},
239-
) where {T, S}
240-
stats.solution = similar(nlp.meta.x0)
241-
stats.multipliers = similar(nlp.meta.y0)
242-
stats.multipliers_L = similar(nlp.meta.y0, has_bounds(nlp) ? nlp.meta.nvar : 0)
243-
stats.multipliers_U = similar(nlp.meta.y0, has_bounds(nlp) ? nlp.meta.nvar : 0)
244-
reset!(stats)
245-
stats
194+
function reset!(stats::GenericExecutionStats, problem::Any)
195+
return reset!(stats)
246196
end
247197

248198
"""
@@ -508,7 +458,7 @@ function getStatus(stats::AbstractExecutionStats)
508458
end
509459

510460
"""
511-
get_status(nlp, kwargs...)
461+
get_status(problem, kwargs...)
512462
513463
Return the output of the solver based on the information in the keyword arguments.
514464
Use `show_statuses()` for the full list.
@@ -525,9 +475,11 @@ The keyword arguments may contain:
525475
- `max_eval::Integer`: limit on the number of evaluations defined by `eval_fun` (default: `typemax(Int)`);
526476
- `max_time::Float64 = Inf`: limit on the time (default: `Inf`);
527477
- `max_iter::Integer`: limit on the number of iterations (default: `typemax(Int)`).
478+
479+
The `problem` is used to check number of evaluations with SolverCore.eval_fun(problem).
528480
"""
529481
function get_status(
530-
nlp::AbstractNLPModel;
482+
nlp;
531483
elapsed_time::Float64 = 0.0,
532484
iter::Integer = 0,
533485
optimal::Bool = false,
@@ -565,6 +517,4 @@ function get_status(
565517
:unknown
566518
end
567519
end
568-
569-
eval_fun(nlp::AbstractNLPModel) = neval_obj(nlp) + neval_cons(nlp)
570-
eval_fun(nls::AbstractNLSModel) = neval_residual(nls) + neval_cons(nls)
520+
eval_fun(::Any) = typemax(Int)

src/dummy_solver.jl renamed to test/dummy-solver.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function dummy_solver(
3636
solve!(solver, nlp, stats, args...; kwargs...)
3737
end
3838

39-
function solve!(
39+
function SolverCore.solve!(
4040
solver::DummySolver{S},
4141
nlp::AbstractNLPModel{T, S},
4242
stats::GenericExecutionStats;

test/runtests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ using NLPModels, NLPModelsTest
55
# stdlib
66
using LinearAlgebra, Logging
77

8+
include("dummy-solver.jl")
9+
810
#=
911
Don't add your tests to runtests.jl. Instead, create files named
1012

test/test-callback.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
set_status!(stats, :user)
66
end
77
end
8-
stats = SolverCore.dummy_solver(nlp, max_eval = 20, callback = callback)
8+
stats = dummy_solver(nlp, max_eval = 20, callback = callback)
99
@test stats.iter == 3
1010
end

test/test-logging.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function test_logging()
88

99
with_logger(ConsoleLogger()) do
1010
@info "Testing dummy solver with logger"
11-
SolverCore.dummy_solver(nlp, max_eval = 20)
11+
dummy_solver(nlp, max_eval = 20)
1212
end
1313
end
1414

test/test-restart.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
@testset "test restart" begin
22
nlp = HS10()
3-
solver = SolverCore.DummySolver(nlp)
3+
solver = DummySolver(nlp)
44
stats = GenericExecutionStats(nlp)
55
solve!(solver, nlp, stats, verbose = false)
66
@test stats.status == :first_order
77
# Try with a new intial guess
88
nlp.meta.x0 .= 0.2
9-
reset!(solver, nlp)
9+
SolverCore.reset!(solver, nlp)
1010
solve!(solver, nlp, stats, verbose = false)
1111
@test stats.status == :first_order
1212
# Try with a new problem of the same size
1313
nlp = HS10()
14-
reset!(solver, nlp)
14+
SolverCore.reset!(solver, nlp)
1515
solve!(solver, nlp, stats, verbose = false)
1616
@test stats.status == :first_order
1717
end

0 commit comments

Comments
 (0)