From 904e48b146231bdae111d8dd09e0c41a98936cac Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 28 Oct 2024 09:03:01 +1300 Subject: [PATCH 1/4] [FileFormats.NL] add support for other variable dual suffixes --- src/FileFormats/NL/sol.jl | 47 ++++++++++++++++---------- test/FileFormats/NL/data/hs071_uno.sol | 31 +++++++++++++++++ test/FileFormats/NL/sol.jl | 41 ++++++++++++++++++++++ 3 files changed, 101 insertions(+), 18 deletions(-) create mode 100644 test/FileFormats/NL/data/hs071_uno.sol diff --git a/src/FileFormats/NL/sol.jl b/src/FileFormats/NL/sol.jl index 9b7d5c1b06..0be99a3e9c 100644 --- a/src/FileFormats/NL/sol.jl +++ b/src/FileFormats/NL/sol.jl @@ -19,7 +19,14 @@ struct SolFileResults <: MOI.ModelLike end """ - SolFileResults(filename::String, model::Model) + SolFileResults( + filename::String, + model::Model; + suffix_lower_bound_dual::Vector{String} = + ["ipopt_zL_out", "lower_bound_dual"], + suffix_uuper_bound_dual::Vector{String} = + ["ipopt_zU_out", "upper_bound_dual"], + ) Parse the `.sol` file `filename` created by solving `model` and return a `SolFileResults` struct. @@ -28,8 +35,8 @@ The returned struct supports the `MOI.get` API for querying result attributes such as [`MOI.TerminationStatus`](@ref), [`MOI.VariablePrimal`](@ref), and [`MOI.ConstraintDual`](@ref). """ -function SolFileResults(filename::String, model::Model) - return open(io -> SolFileResults(io, model), filename, "r") +function SolFileResults(filename::String, model::Model; kwargs...) + return open(io -> SolFileResults(io, model; kwargs...), filename, "r") end """ @@ -46,7 +53,8 @@ All other attributes are un-set. """ function SolFileResults( raw_status::String, - termination_status::MOI.TerminationStatusCode, + termination_status::MOI.TerminationStatusCode; + kwargs..., ) return SolFileResults( nothing, @@ -261,7 +269,14 @@ end _readline(io::IO, T) = parse(T, _readline(io)) -function SolFileResults(io::IO, model::Model) +function SolFileResults( + io::IO, + model::Model; + suffix_lower_bound_duals::Vector{String} = + ["ipopt_zL_out", "lower_bound_duals"], + suffix_upper_bound_duals::Vector{String} = + ["ipopt_zU_out", "upper_bound_duals"], +) # This function is based on a Julia translation of readsol.c, available at # https://github.com/ampl/asl/blob/64919f75fa7a438f4b41bce892dcbe2ae38343ee/src/solvers/readsol.c # and under the following license: @@ -345,21 +360,17 @@ function SolFileResults(io::IO, model::Model) items = split(line, " ") n_suffix = parse(Int, items[3]) suffix = _readline(io) - if !(suffix == "ipopt_zU_out" || suffix == "ipopt_zL_out") - for _ in 1:n_suffix - _ = readline(io) - end - continue - end for i in 1:n_suffix - items = split(_readline(io), " ") - x = model.order[parse(Int, items[1])+1] - dual = parse(Float64, items[2]) - if suffix == "ipopt_zU_out" - zU_out[x] = dual + if suffix in suffix_upper_bound_duals + items = split(_readline(io), " ") + x = model.order[parse(Int, items[1])+1] + zU_out[x] = parse(Float64, items[2]) + elseif suffix in suffix_lower_bound_duals + items = split(_readline(io), " ") + x = model.order[parse(Int, items[1])+1] + zL_out[x] = parse(Float64, items[2]) else - @assert suffix == "ipopt_zL_out" - zL_out[x] = dual + _ = _readline(io) end end end diff --git a/test/FileFormats/NL/data/hs071_uno.sol b/test/FileFormats/NL/data/hs071_uno.sol new file mode 100644 index 0000000000..b00e98a7ed --- /dev/null +++ b/test/FileFormats/NL/data/hs071_uno.sol @@ -0,0 +1,31 @@ + +Ipopt 3.14.4: Optimal Solution Found + +Options +3 +1 +1 +0 +2 +2 +4 +4 +0.1787618002239518 +0.9850008232874167 +1.0999999890876724 +1.5735520521364392 +2.674683791567438 +5.400000053551036 +objno 0 0 +suffix 4 4 13 0 0 +upper_bound_duals +0 -6.26468441989811e-10 +1 -6.909508053112211e-10 +2 -9.54407535695046e-10 +3 -5.582550550861189 +suffix 4 4 13 0 0 +lower_bound_duals +0 28.590703805487557 +1 6.713713035054837e-09 +2 1.8232874186951734e-09 +3 6.26437836105288e-10 diff --git a/test/FileFormats/NL/sol.jl b/test/FileFormats/NL/sol.jl index 905c6cf261..b0fad245e3 100644 --- a/test/FileFormats/NL/sol.jl +++ b/test/FileFormats/NL/sol.jl @@ -117,6 +117,47 @@ function test_sol_hs071_variable_dual() return end +function test_sol_uno_hs071_variable_dual() + model, v = _hs071() + for (sign, sense) in [(1, MOI.MIN_SENSE), (-1, MOI.MAX_SENSE)] + MOI.set(model, MOI.ObjectiveSense(), sense) + nl_model = NL.Model() + index_map = MOI.copy_to(nl_model, model) + sol = NL.SolFileResults( + joinpath(@__DIR__, "data", "hs071_uno.sol"), + nl_model, + ) + x1 = index_map[v[1]] + F = MOI.VariableIndex + dual = sign * 28.590703805487557 + ci = MOI.ConstraintIndex{F,MOI.GreaterThan{Float64}}(x1.value) + @test ≈(MOI.get(sol, MOI.ConstraintDual(), ci), dual, atol = 1e-8) + ci = MOI.ConstraintIndex{F,MOI.LessThan{Float64}}(x1.value) + @test ≈(MOI.get(sol, MOI.ConstraintDual(), ci), 0.0, atol = 1e-8) + ci = MOI.ConstraintIndex{F,MOI.EqualTo{Float64}}(x1.value) + @test ≈(MOI.get(sol, MOI.ConstraintDual(), ci), dual, atol = 1e-8) + ci = MOI.ConstraintIndex{F,MOI.Interval{Float64}}(x1.value) + @test ≈(MOI.get(sol, MOI.ConstraintDual(), ci), dual, atol = 1e-8) + end + return +end + +function test_sol_lower_bound_dual_args() + model, v = _hs071() + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + nl_model = NL.Model() + index_map = MOI.copy_to(nl_model, model) + sol = NL.SolFileResults( + joinpath(@__DIR__, "data", "hs071_uno.sol"), + nl_model; + lower_bound_duals = String[], + upper_bound_duals = String[], + ) + @test isempty(sol.zL_out) + @test isempty(sol.zU_out) + return +end + """ test_sol_hs071_max_sense() From dd643662a0ba29d66c4bb68fb03d1a7c504dfe32 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 28 Oct 2024 09:04:51 +1300 Subject: [PATCH 2/4] Update sol.jl --- src/FileFormats/NL/sol.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FileFormats/NL/sol.jl b/src/FileFormats/NL/sol.jl index 0be99a3e9c..15fc2c564e 100644 --- a/src/FileFormats/NL/sol.jl +++ b/src/FileFormats/NL/sol.jl @@ -22,10 +22,10 @@ end SolFileResults( filename::String, model::Model; - suffix_lower_bound_dual::Vector{String} = - ["ipopt_zL_out", "lower_bound_dual"], - suffix_uuper_bound_dual::Vector{String} = - ["ipopt_zU_out", "upper_bound_dual"], + suffix_lower_bound_duals::Vector{String} = + ["ipopt_zL_out", "lower_bound_duals"], + suffix_uuper_bound_duals::Vector{String} = + ["ipopt_zU_out", "upper_bound_duals"], ) Parse the `.sol` file `filename` created by solving `model` and return a From 1bae2703c9eb7680f233a534e19a4f2cd55c8400 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 28 Oct 2024 09:08:14 +1300 Subject: [PATCH 3/4] Update --- src/FileFormats/NL/sol.jl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/FileFormats/NL/sol.jl b/src/FileFormats/NL/sol.jl index 15fc2c564e..6d13988ecc 100644 --- a/src/FileFormats/NL/sol.jl +++ b/src/FileFormats/NL/sol.jl @@ -272,10 +272,14 @@ _readline(io::IO, T) = parse(T, _readline(io)) function SolFileResults( io::IO, model::Model; - suffix_lower_bound_duals::Vector{String} = - ["ipopt_zL_out", "lower_bound_duals"], - suffix_upper_bound_duals::Vector{String} = - ["ipopt_zU_out", "upper_bound_duals"], + suffix_lower_bound_duals::Vector{String} = [ + "ipopt_zL_out", + "lower_bound_duals", + ], + suffix_upper_bound_duals::Vector{String} = [ + "ipopt_zU_out", + "upper_bound_duals", + ], ) # This function is based on a Julia translation of readsol.c, available at # https://github.com/ampl/asl/blob/64919f75fa7a438f4b41bce892dcbe2ae38343ee/src/solvers/readsol.c From b11fab1221145051ff74f41a6ebf5224b945c700 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 28 Oct 2024 09:48:37 +1300 Subject: [PATCH 4/4] Update --- test/FileFormats/NL/sol.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/FileFormats/NL/sol.jl b/test/FileFormats/NL/sol.jl index b0fad245e3..2b505d78fb 100644 --- a/test/FileFormats/NL/sol.jl +++ b/test/FileFormats/NL/sol.jl @@ -150,8 +150,8 @@ function test_sol_lower_bound_dual_args() sol = NL.SolFileResults( joinpath(@__DIR__, "data", "hs071_uno.sol"), nl_model; - lower_bound_duals = String[], - upper_bound_duals = String[], + suffix_lower_bound_duals = String[], + suffix_upper_bound_duals = String[], ) @test isempty(sol.zL_out) @test isempty(sol.zU_out)