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 34571675215..4f07a5bd5dd 100644 --- a/test/test_solution_summary.jl +++ b/test/test_solution_summary.jl @@ -211,4 +211,55 @@ 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) + 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) + 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.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 + + * 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 : 1.10000e+01 + 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