Skip to content

Commit f11a616

Browse files
authored
[FileFormats.NL] add support for other variable dual suffixes (#2567)
1 parent ee313be commit f11a616

File tree

3 files changed

+105
-18
lines changed

3 files changed

+105
-18
lines changed

src/FileFormats/NL/sol.jl

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,14 @@ struct SolFileResults <: MOI.ModelLike
1919
end
2020

2121
"""
22-
SolFileResults(filename::String, model::Model)
22+
SolFileResults(
23+
filename::String,
24+
model::Model;
25+
suffix_lower_bound_duals::Vector{String} =
26+
["ipopt_zL_out", "lower_bound_duals"],
27+
suffix_uuper_bound_duals::Vector{String} =
28+
["ipopt_zU_out", "upper_bound_duals"],
29+
)
2330
2431
Parse the `.sol` file `filename` created by solving `model` and return a
2532
`SolFileResults` struct.
@@ -28,8 +35,8 @@ The returned struct supports the `MOI.get` API for querying result attributes
2835
such as [`MOI.TerminationStatus`](@ref), [`MOI.VariablePrimal`](@ref), and
2936
[`MOI.ConstraintDual`](@ref).
3037
"""
31-
function SolFileResults(filename::String, model::Model)
32-
return open(io -> SolFileResults(io, model), filename, "r")
38+
function SolFileResults(filename::String, model::Model; kwargs...)
39+
return open(io -> SolFileResults(io, model; kwargs...), filename, "r")
3340
end
3441

3542
"""
@@ -46,7 +53,8 @@ All other attributes are un-set.
4653
"""
4754
function SolFileResults(
4855
raw_status::String,
49-
termination_status::MOI.TerminationStatusCode,
56+
termination_status::MOI.TerminationStatusCode;
57+
kwargs...,
5058
)
5159
return SolFileResults(
5260
nothing,
@@ -261,7 +269,18 @@ end
261269

262270
_readline(io::IO, T) = parse(T, _readline(io))
263271

264-
function SolFileResults(io::IO, model::Model)
272+
function SolFileResults(
273+
io::IO,
274+
model::Model;
275+
suffix_lower_bound_duals::Vector{String} = [
276+
"ipopt_zL_out",
277+
"lower_bound_duals",
278+
],
279+
suffix_upper_bound_duals::Vector{String} = [
280+
"ipopt_zU_out",
281+
"upper_bound_duals",
282+
],
283+
)
265284
# This function is based on a Julia translation of readsol.c, available at
266285
# https://github.com/ampl/asl/blob/64919f75fa7a438f4b41bce892dcbe2ae38343ee/src/solvers/readsol.c
267286
# and under the following license:
@@ -345,21 +364,17 @@ function SolFileResults(io::IO, model::Model)
345364
items = split(line, " ")
346365
n_suffix = parse(Int, items[3])
347366
suffix = _readline(io)
348-
if !(suffix == "ipopt_zU_out" || suffix == "ipopt_zL_out")
349-
for _ in 1:n_suffix
350-
_ = readline(io)
351-
end
352-
continue
353-
end
354367
for i in 1:n_suffix
355-
items = split(_readline(io), " ")
356-
x = model.order[parse(Int, items[1])+1]
357-
dual = parse(Float64, items[2])
358-
if suffix == "ipopt_zU_out"
359-
zU_out[x] = dual
368+
if suffix in suffix_upper_bound_duals
369+
items = split(_readline(io), " ")
370+
x = model.order[parse(Int, items[1])+1]
371+
zU_out[x] = parse(Float64, items[2])
372+
elseif suffix in suffix_lower_bound_duals
373+
items = split(_readline(io), " ")
374+
x = model.order[parse(Int, items[1])+1]
375+
zL_out[x] = parse(Float64, items[2])
360376
else
361-
@assert suffix == "ipopt_zL_out"
362-
zL_out[x] = dual
377+
_ = _readline(io)
363378
end
364379
end
365380
end
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
Ipopt 3.14.4: Optimal Solution Found
3+
4+
Options
5+
3
6+
1
7+
1
8+
0
9+
2
10+
2
11+
4
12+
4
13+
0.1787618002239518
14+
0.9850008232874167
15+
1.0999999890876724
16+
1.5735520521364392
17+
2.674683791567438
18+
5.400000053551036
19+
objno 0 0
20+
suffix 4 4 13 0 0
21+
upper_bound_duals
22+
0 -6.26468441989811e-10
23+
1 -6.909508053112211e-10
24+
2 -9.54407535695046e-10
25+
3 -5.582550550861189
26+
suffix 4 4 13 0 0
27+
lower_bound_duals
28+
0 28.590703805487557
29+
1 6.713713035054837e-09
30+
2 1.8232874186951734e-09
31+
3 6.26437836105288e-10

test/FileFormats/NL/sol.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,47 @@ function test_sol_hs071_variable_dual()
117117
return
118118
end
119119

120+
function test_sol_uno_hs071_variable_dual()
121+
model, v = _hs071()
122+
for (sign, sense) in [(1, MOI.MIN_SENSE), (-1, MOI.MAX_SENSE)]
123+
MOI.set(model, MOI.ObjectiveSense(), sense)
124+
nl_model = NL.Model()
125+
index_map = MOI.copy_to(nl_model, model)
126+
sol = NL.SolFileResults(
127+
joinpath(@__DIR__, "data", "hs071_uno.sol"),
128+
nl_model,
129+
)
130+
x1 = index_map[v[1]]
131+
F = MOI.VariableIndex
132+
dual = sign * 28.590703805487557
133+
ci = MOI.ConstraintIndex{F,MOI.GreaterThan{Float64}}(x1.value)
134+
@test (MOI.get(sol, MOI.ConstraintDual(), ci), dual, atol = 1e-8)
135+
ci = MOI.ConstraintIndex{F,MOI.LessThan{Float64}}(x1.value)
136+
@test (MOI.get(sol, MOI.ConstraintDual(), ci), 0.0, atol = 1e-8)
137+
ci = MOI.ConstraintIndex{F,MOI.EqualTo{Float64}}(x1.value)
138+
@test (MOI.get(sol, MOI.ConstraintDual(), ci), dual, atol = 1e-8)
139+
ci = MOI.ConstraintIndex{F,MOI.Interval{Float64}}(x1.value)
140+
@test (MOI.get(sol, MOI.ConstraintDual(), ci), dual, atol = 1e-8)
141+
end
142+
return
143+
end
144+
145+
function test_sol_lower_bound_dual_args()
146+
model, v = _hs071()
147+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
148+
nl_model = NL.Model()
149+
index_map = MOI.copy_to(nl_model, model)
150+
sol = NL.SolFileResults(
151+
joinpath(@__DIR__, "data", "hs071_uno.sol"),
152+
nl_model;
153+
suffix_lower_bound_duals = String[],
154+
suffix_upper_bound_duals = String[],
155+
)
156+
@test isempty(sol.zL_out)
157+
@test isempty(sol.zU_out)
158+
return
159+
end
160+
120161
"""
121162
test_sol_hs071_max_sense()
122163

0 commit comments

Comments
 (0)