From ecb8ffce74bd82799f761dba847a65e10ba5fc77 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 10 Oct 2024 13:25:15 +1300 Subject: [PATCH 1/3] Fix solution_summary when there are duplicate names --- src/solution_summary.jl | 50 +++++++++++++++++++++++++---------- test/test_solution_summary.jl | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 14 deletions(-) diff --git a/src/solution_summary.jl b/src/solution_summary.jl index 97a43b18ca9..c615780d9e1 100644 --- a/src/solution_summary.jl +++ b/src/solution_summary.jl @@ -20,7 +20,7 @@ struct _SolutionSummary{T} objective_bound::Union{Missing,T,Vector{T}} relative_gap::Union{Missing,T} dual_objective_value::Union{Missing,T} - primal_solution::Union{Missing,Dict{String,T}} + primal_solution::Union{Missing,Dict{String,Union{Missing,T}}} dual_solution::Union{Missing,Dict{String,Any}} # Work counters solve_time::Union{Missing,Float64} @@ -182,21 +182,35 @@ function _show_candidate_solution_summary(io::IO, summary::_SolutionSummary) if summary.verbose && summary.has_values println(io, " Primal solution :") for variable_name in sort(collect(keys(summary.primal_solution))) - _print_if_not_missing( - io, - " $(variable_name) : ", - summary.primal_solution[variable_name], - ) + if summary.primal_solution[variable_name] === missing + println( + io, + " $variable_name : multiple variables with the same name", + ) + else + _print_if_not_missing( + io, + " $(variable_name) : ", + summary.primal_solution[variable_name], + ) + end end end if summary.verbose && summary.has_duals println(io, " Dual solution :") for constraint_name in sort(collect(keys(summary.dual_solution))) - _print_if_not_missing( - io, - " $(constraint_name) : ", - summary.dual_solution[constraint_name], - ) + if summary.dual_solution[constraint_name] === missing + println( + io, + " $constraint_name : multiple constraints with the same name", + ) + else + _print_if_not_missing( + io, + " $(constraint_name) : ", + summary.dual_solution[constraint_name], + ) + end end end return @@ -221,10 +235,14 @@ function _show_work_counters_summary(io::IO, summary::_SolutionSummary) end function _get_solution_dict(model, result) - dict = Dict{String,value_type(typeof(model))}() + dict = Dict{String,Union{Missing,value_type(typeof(model))}}() for x in all_variables(model) variable_name = name(x) - if !isempty(variable_name) + if isempty(variable_name) + continue + elseif haskey(dict, variable_name) + dict[variable_name] = missing + else dict[variable_name] = value(x; result = result) end end @@ -236,7 +254,11 @@ function _get_constraint_dict(model, result) for (F, S) in list_of_constraint_types(model) for constraint in all_constraints(model, F, S) constraint_name = name(constraint) - if !isempty(constraint_name) + if isempty(constraint_name) + continue + elseif haskey(dict, constraint_name) + dict[constraint_name] = missing + else dict[constraint_name] = dual(constraint; result = result) end end diff --git a/test/test_solution_summary.jl b/test/test_solution_summary.jl index 17f770d0aec..8ff8d3d4dde 100644 --- a/test/test_solution_summary.jl +++ b/test/test_solution_summary.jl @@ -205,4 +205,51 @@ function test_solution_summary_vector_dual() return end +function test_solution_summary_same_names() + model = Model() do + return MOI.Utilities.MockOptimizer(MOI.Utilities.Model{Float64}()) + end + @variable(model, x[1:2]) + @variable(model, y) + set_name.(x, "x") + @constraint(model, c, x .>= 0) + @constraint(model, d, 2x[1] <= 1) + optimize!(model) + mock = unsafe_backend(model) + MOI.set(mock, MOI.TerminationStatus(), MOI.OPTIMAL) + MOI.set(mock, MOI.RawStatusString(), "solver specific string") + MOI.set(mock, MOI.ResultCount(), 1) + MOI.set(mock, MOI.PrimalStatus(), MOI.FEASIBLE_POINT) + MOI.set(mock, MOI.DualStatus(), MOI.FEASIBLE_POINT) + MOI.set(mock, MOI.VariablePrimal(), optimizer_index.(x), [1.0, 2.0]) + MOI.set(mock, MOI.VariablePrimal(), optimizer_index(y), 3.0) + MOI.set(mock, MOI.ConstraintDual(), optimizer_index.(c), [3.0, 4.0]) + MOI.set(mock, MOI.ConstraintDual(), optimizer_index(d), 5.0) + ret = """ + * Solver : Mock + + * Status + Result count : 1 + Termination status : OPTIMAL + Message from the solver: + "solver specific string" + + * Candidate solution (result #1) + Primal status : FEASIBLE_POINT + Dual status : FEASIBLE_POINT + Objective value : 0.00000e+00 + Dual objective value : 5.00000e+00 + Primal solution : + x : multiple variables with the same name + y : 3.00000e+00 + Dual solution : + c : multiple constraints with the same name + d : 5.00000e+00 + + * Work counters + """ + @test sprint(show, solution_summary(model; verbose = true)) == ret + return +end + end # module From 3a54e5a57d611c897a4b82dafeab030d570153cd Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 10 Oct 2024 14:04:11 +1300 Subject: [PATCH 2/3] Update --- test/test_solution_summary.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_solution_summary.jl b/test/test_solution_summary.jl index 8ff8d3d4dde..00a7a05881e 100644 --- a/test/test_solution_summary.jl +++ b/test/test_solution_summary.jl @@ -211,9 +211,11 @@ function test_solution_summary_same_names() end @variable(model, x[1:2]) @variable(model, y) + z = @variable(model) set_name.(x, "x") @constraint(model, c, x .>= 0) @constraint(model, d, 2x[1] <= 1) + e = @constraint(model, 2x[2] == 1) optimize!(model) mock = unsafe_backend(model) MOI.set(mock, MOI.TerminationStatus(), MOI.OPTIMAL) @@ -223,8 +225,10 @@ function test_solution_summary_same_names() MOI.set(mock, MOI.DualStatus(), MOI.FEASIBLE_POINT) MOI.set(mock, MOI.VariablePrimal(), optimizer_index.(x), [1.0, 2.0]) MOI.set(mock, MOI.VariablePrimal(), optimizer_index(y), 3.0) + MOI.set(mock, MOI.VariablePrimal(), optimizer_index(z), 4.0) MOI.set(mock, MOI.ConstraintDual(), optimizer_index.(c), [3.0, 4.0]) MOI.set(mock, MOI.ConstraintDual(), optimizer_index(d), 5.0) + MOI.set(mock, MOI.ConstraintDual(), optimizer_index(e), 6.0) ret = """ * Solver : Mock From f50f3862d653846b63d5e4347d33dbcaa6f11d52 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 10 Oct 2024 14:28:23 +1300 Subject: [PATCH 3/3] Update test/test_solution_summary.jl --- test/test_solution_summary.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_solution_summary.jl b/test/test_solution_summary.jl index 00a7a05881e..35242ab2aa8 100644 --- a/test/test_solution_summary.jl +++ b/test/test_solution_summary.jl @@ -242,7 +242,7 @@ function test_solution_summary_same_names() Primal status : FEASIBLE_POINT Dual status : FEASIBLE_POINT Objective value : 0.00000e+00 - Dual objective value : 5.00000e+00 + Dual objective value : 1.10000e+01 Primal solution : x : multiple variables with the same name y : 3.00000e+00