Skip to content

Commit 3685ced

Browse files
authoredApr 21, 2025
Specify reset! SolverCore and NLPModels - Use other dummy (#183)
* Use SolverTest.dummy * add dummy solver
1 parent bdfc9c8 commit 3685ced

File tree

4 files changed

+215
-14
lines changed

4 files changed

+215
-14
lines changed
 

‎src/run_solver.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ function solve_problems(
9898

9999
for (id, problem) in enumerate(problems)
100100
if reset_problem
101-
reset!(problem)
101+
NLPModels.reset!(problem)
102102
end
103103
nequ = problem isa AbstractNLSModel ? problem.nls_meta.nequ : 0
104104
problem_info = [id; problem.meta.name; problem.meta.nvar; problem.meta.ncon; nequ]

‎test/dummy-solver.jl

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
using NLPModels, SolverCore
2+
3+
# non-allocating reshape
4+
# see https://github.com/JuliaLang/julia/issues/36313
5+
reshape_array(a, dims) = invoke(Base._reshape, Tuple{AbstractArray, typeof(dims)}, a, dims)
6+
7+
mutable struct DummySolver{S} <: AbstractOptimizationSolver
8+
x::S # primal approximation
9+
gx::S # gradient of objective
10+
y::S # multipliers estimates
11+
rhs::S # right-hand size of Newton system
12+
jval::S # flattened Jacobian
13+
hval::S # flattened Hessian
14+
wval::S # flattened augmented matrix
15+
Δxy::S # search direction
16+
end
17+
18+
function DummySolver(nlp::AbstractNLPModel{T, S}) where {T, S <: AbstractVector{T}}
19+
nvar, ncon = nlp.meta.nvar, nlp.meta.ncon
20+
x = similar(nlp.meta.x0)
21+
gx = similar(nlp.meta.x0)
22+
y = similar(nlp.meta.y0)
23+
rhs = similar(nlp.meta.x0, nvar + ncon)
24+
jval = similar(nlp.meta.x0, ncon * nvar)
25+
hval = similar(nlp.meta.x0, nvar * nvar)
26+
wval = similar(nlp.meta.x0, (nvar + ncon) * (nvar + ncon))
27+
Δxy = similar(nlp.meta.x0, nvar + ncon)
28+
DummySolver{S}(x, gx, y, rhs, jval, hval, wval, Δxy)
29+
end
30+
31+
function dummy(
32+
nlp::AbstractNLPModel{T, S},
33+
args...;
34+
kwargs...,
35+
) where {T, S <: AbstractVector{T}}
36+
solver = DummySolver(nlp)
37+
stats = GenericExecutionStats(nlp)
38+
solve!(solver, nlp, stats, args...; kwargs...)
39+
end
40+
41+
function solve!(
42+
solver::DummySolver{S},
43+
nlp::AbstractNLPModel{T, S},
44+
stats::GenericExecutionStats;
45+
callback = (args...) -> nothing,
46+
x0::S = nlp.meta.x0,
47+
atol::Real = sqrt(eps(T)),
48+
rtol::Real = sqrt(eps(T)),
49+
max_eval::Int = 1000,
50+
max_time::Float64 = 30.0,
51+
verbose::Bool = true,
52+
) where {T, S <: AbstractVector{T}}
53+
start_time = time()
54+
elapsed_time = 0.0
55+
set_time!(stats, elapsed_time)
56+
57+
nvar, ncon = nlp.meta.nvar, nlp.meta.ncon
58+
x = solver.x .= x0
59+
rhs = solver.rhs
60+
dual = view(rhs, 1:nvar)
61+
cx = view(rhs, (nvar + 1):(nvar + ncon))
62+
gx = solver.gx
63+
y = solver.y
64+
jval = solver.jval
65+
hval = solver.hval
66+
wval = solver.wval
67+
Δxy = solver.Δxy
68+
nnzh = Int(nvar * (nvar + 1) / 2)
69+
nnzh == nlp.meta.nnzh || error("solver assumes Hessian is dense")
70+
nvar * ncon == nlp.meta.nnzj || error("solver assumes Jacobian is dense")
71+
72+
grad!(nlp, x, gx)
73+
dual .= gx
74+
75+
# assume the model returns a dense Jacobian in column-major order
76+
if ncon > 0
77+
cons!(nlp, x, cx)
78+
jac_coord!(nlp, x, jval)
79+
Jx = reshape_array(jval, (ncon, nvar))
80+
Jqr = qr(Jx')
81+
82+
# compute least-squares multipliers
83+
# by solving Jx' y = -gx
84+
gx .*= -1
85+
ldiv!(y, Jqr, gx)
86+
87+
# update dual <- dual + Jx' * y
88+
mul!(dual, Jx', y, one(T), one(T))
89+
end
90+
91+
iter = 0
92+
set_iter!(stats, iter)
93+
ϵd = atol + rtol * norm(dual)
94+
ϵp = atol
95+
96+
fx = obj(nlp, x)
97+
set_objective!(stats, fx)
98+
verbose && @info log_header([:iter, :f, :c, :dual, :t, :x], [Int, T, T, T, Float64, Char])
99+
verbose && @info log_row(Any[iter, fx, norm(cx), norm(dual), elapsed_time, 'c'])
100+
solved = norm(dual) < ϵd && norm(cx) < ϵp
101+
102+
set_status!(
103+
stats,
104+
get_status(
105+
nlp,
106+
elapsed_time = elapsed_time,
107+
iter = iter,
108+
optimal = solved,
109+
max_eval = max_eval,
110+
max_time = max_time,
111+
),
112+
)
113+
114+
while stats.status == :unknown
115+
# assume the model returns a dense Hessian in column-major order
116+
# NB: hess_coord!() only returns values in the lower triangle
117+
hess_coord!(nlp, x, y, view(hval, 1:nnzh))
118+
119+
# rearrange nonzeros so they correspond to a dense nvar x nvar matrix
120+
j = nvar * nvar
121+
i = nnzh
122+
k = 1
123+
while i > nvar
124+
for _ = 1:k
125+
hval[j] = hval[i]
126+
hval[i] = 0
127+
j -= 1
128+
i -= 1
129+
end
130+
j -= nvar - k
131+
k += 1
132+
end
133+
134+
# fill in augmented matrix
135+
# W = [H J']
136+
# [J 0 ]
137+
wval .= 0
138+
Wxy = reshape_array(wval, (nvar + ncon, nvar + ncon))
139+
Hxy = reshape_array(hval, (nvar, nvar))
140+
Wxy[1:nvar, 1:nvar] .= Hxy
141+
for i = 1:nvar
142+
Wxy[i, i] += sqrt(eps(T))
143+
end
144+
if ncon > 0
145+
Wxy[(nvar + 1):(nvar + ncon), 1:nvar] .= Jx
146+
end
147+
LBL = factorize(Symmetric(Wxy, :L))
148+
149+
ldiv!(Δxy, LBL, rhs)
150+
Δxy .*= -1
151+
@views Δx = Δxy[1:nvar]
152+
@views Δy = Δxy[(nvar + 1):(nvar + ncon)]
153+
x .+= Δx
154+
y .+= Δy
155+
156+
grad!(nlp, x, gx)
157+
dual .= gx
158+
if ncon > 0
159+
cons!(nlp, x, cx)
160+
jac_coord!(nlp, x, jval)
161+
Jx = reshape_array(jval, (ncon, nvar))
162+
Jqr = qr(Jx')
163+
gx .*= -1
164+
ldiv!(y, Jqr, gx)
165+
mul!(dual, Jx', y, one(T), one(T))
166+
end
167+
elapsed_time = time() - start_time
168+
set_time!(stats, elapsed_time)
169+
presid = norm(cx)
170+
dresid = norm(dual)
171+
set_residuals!(stats, presid, dresid)
172+
solved = dresid < ϵd && presid < ϵp
173+
174+
iter += 1
175+
fx = obj(nlp, x)
176+
set_iter!(stats, iter)
177+
set_objective!(stats, fx)
178+
179+
set_status!(
180+
stats,
181+
get_status(
182+
nlp,
183+
elapsed_time = elapsed_time,
184+
iter = iter,
185+
optimal = solved,
186+
max_eval = max_eval,
187+
max_time = max_time,
188+
),
189+
)
190+
191+
callback(nlp, solver, stats)
192+
193+
verbose && @info log_row(Any[iter, fx, norm(cx), norm(dual), elapsed_time, 'd'])
194+
end
195+
196+
set_residuals!(stats, norm(cx), norm(dual))
197+
z = has_bounds(nlp) ? zeros(T, nvar) : zeros(T, 0)
198+
set_multipliers!(stats, y, z, z)
199+
set_solution!(stats, x)
200+
return stats
201+
end

‎test/runtests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ using Plots
1717
# this package
1818
using SolverBenchmark
1919

20+
include("dummy-solver.jl")
21+
2022
include("data.jl")
2123
include("tables.jl")
2224
include("profiles.jl")

‎test/test_bmark.jl

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ using Logging
33
using NLPModels, ADNLPModels
44
using SolverCore
55

6-
import SolverCore.dummy_solver
7-
86
mutable struct CallableSolver end
97

108
function (solver::CallableSolver)(nlp::AbstractNLPModel; kwargs...)
@@ -33,14 +31,14 @@ function test_bmark()
3331
),
3432
]
3533
callable = CallableSolver()
36-
stats = solve_problems(dummy_solver, "dummy", problems)
34+
stats = solve_problems(dummy, "dummy", problems)
3735
@test stats isa DataFrame
38-
stats = solve_problems(dummy_solver, "dummy", problems, reset_problem = false)
39-
stats = solve_problems(dummy_solver, "dummy", problems, reset_problem = true)
36+
stats = solve_problems(dummy, "dummy", problems, reset_problem = false)
37+
stats = solve_problems(dummy, "dummy", problems, reset_problem = true)
4038

4139
solve_problems(callable, "callable", problems)
4240

43-
solvers = Dict(:dummy => dummy_solver, :callable => callable)
41+
solvers = Dict(:dummy => dummy, :callable => callable)
4442
stats = bmark_solvers(solvers, problems)
4543
@test stats isa Dict{Symbol, DataFrame}
4644
for k in keys(solvers)
@@ -73,22 +71,22 @@ function test_bmark()
7371
)
7472
with_logger(ConsoleLogger()) do
7573
@info "Testing simple logger on `solve_problems`"
76-
solve_problems(dummy_solver, "dummy", nlps)
77-
reset!.(nlps)
74+
solve_problems(dummy, "dummy", nlps)
75+
NLPModels.reset!.(nlps)
7876

7977
@info "Testing logger with specific columns on `solve_problems`"
8078
solve_problems(
81-
dummy_solver,
79+
dummy,
8280
"dummy",
8381
nlps,
8482
colstats = [:name, :nvar, :elapsed_time, :objective, :dual_feas],
8583
)
86-
reset!.(nlps)
84+
NLPModels.reset!.(nlps)
8785

8886
@info "Testing logger with hdr_override on `solve_problems`"
8987
hdr_override = Dict(:dual_feas => "‖∇L(x)‖", :primal_feas => "‖c(x)‖")
90-
solve_problems(dummy_solver, "dummy", nlps, info_hdr_override = hdr_override)
91-
reset!.(nlps)
88+
solve_problems(dummy, "dummy", nlps, info_hdr_override = hdr_override)
89+
NLPModels.reset!.(nlps)
9290
end
9391
end
9492

@@ -115,7 +113,7 @@ function test_bmark()
115113

116114
solvers = Dict(
117115
:dummy_solver_specific =>
118-
nlp -> dummy_solver(
116+
nlp -> dummy(
119117
nlp,
120118
callback = (nlp, solver, stats) -> set_solver_specific!(stats, :foo, 1),
121119
),

0 commit comments

Comments
 (0)