Skip to content

Commit

Permalink
Add solution_summary function (#2512)
Browse files Browse the repository at this point in the history
  • Loading branch information
AtsushiSakai authored Mar 16, 2021
1 parent e89dbdc commit 8b13032
Show file tree
Hide file tree
Showing 5 changed files with 339 additions and 0 deletions.
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
MutableArithmetics = "d8a4904e-b15c-11e9-3269-09a3773c0cb0"
NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
Expand Down
50 changes: 50 additions & 0 deletions docs/src/manual/solutions.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,56 @@ Subject to
y[b] ≤ 1.0
```

## Solutions summary

[`solution_summary`](@ref) can be used for checking the summary of the optimization solutions.

```jldoctest solutions; filter=r"[0-9]+.[0-9]+"
julia> solution_summary(model)
* Solver : GLPK
* Status
Termination status : OPTIMAL
Primal status : FEASIBLE_POINT
Dual status : FEASIBLE_POINT
Message from the solver:
"Solution is optimal"
* Candidate solution
Objective value : -205.14285714285714
Objective bound : Inf
Dual objective value : -205.1428571428571
* Work counters
Solve time (sec) : 0.00008
julia> solution_summary(model, verbose=true)
* Solver : GLPK
* Status
Termination status : OPTIMAL
Primal status : FEASIBLE_POINT
Dual status : FEASIBLE_POINT
Result count : 1
Has duals : true
Message from the solver:
"Solution is optimal"
* Candidate solution
Objective value : -205.14285714285714
Objective bound : Inf
Dual objective value : -205.1428571428571
Primal solution :
x : 15.428571428571429
y[a] : 1.0
y[b] : 1.0
Dual solution :
c1 : 1.7142857142857142
* Work counters
Solve time (sec) : 0.00008
```

## Why did the solver stop?

Use[`termination_status`](@ref) to understand why the solver stopped.
Expand Down
1 change: 1 addition & 0 deletions docs/src/reference/solutions.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ More information can be found in the [Solutions](@ref) section of the manual.
JuMP.optimize!
NoOptimizer
OptimizeNotCalled
solution_summary
```

## Termination status
Expand Down
170 changes: 170 additions & 0 deletions src/print.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
# separately defined.
#############################################################################

using Printf

# Used for dispatching
"""
PrintMode
Expand Down Expand Up @@ -324,6 +326,174 @@ function objective_function_string(print_mode, model::Model)
end
end

struct _SolutionSummary
verbose::Bool
solver::String
# Status
termination_status::MOI.TerminationStatusCode
primal_status::MOI.ResultStatusCode
dual_status::MOI.ResultStatusCode
raw_status::String
result_count::Int
has_values::Bool
has_duals::Bool
# Candidate solution
objective_value::Float64
objective_bound::Union{Missing,Float64}
dual_objective_value::Union{Missing,Float64}
primal_solution::Union{Missing,Dict{String,Float64}}
dual_solution::Union{Missing,Dict{String,Float64}}
# Work counters
solve_time::Float64
barrier_iterations::Union{Missing,Int}
simplex_iterations::Union{Missing,Int}
node_count::Union{Missing,Int}
end

"""
solution_summary(model::Model; verbose::Bool = false)
Return a struct that can be used print a summary of the solution.
If `verbose=true`, write out the primal solution for every variable and the
dual solution for every constraint, excluding those with empty names.
## Examples
When called at the REPL, the summary is automatically printed:
```julia
julia> solution_summary(model)
[...]
```
Use `print` to force the printing of the summary from inside a function:
```julia
function foo(model)
print(solution_summary(model))
return
end
```
"""
function solution_summary(model::Model; verbose::Bool = false)
return _SolutionSummary(
verbose,
solver_name(model),
termination_status(model),
primal_status(model),
dual_status(model),
raw_status(model),
result_count(model),
has_values(model),
has_duals(model),
objective_value(model),
_try_get(objective_bound, model),
_try_get(dual_objective_value, model),
verbose ? _get_solution_dict(model) : missing,
verbose ? _get_constraint_dict(model) : missing,
solve_time(model),
_try_get(simplex_iterations, model),
_try_get(barrier_iterations, model),
_try_get(node_count, model),
)
end

"""
Base.show([io::IO], summary::SolutionSummary; verbose::Bool = false)
Write a summary of the solution results to `io` (or to `stdout` if `io` is not
given).
"""
function Base.show(io::IO, summary::_SolutionSummary)
println(io, "* Solver : ", summary.solver)
println(io)
_show_status_summary(io, summary)
_show_candidate_solution_summary(io, summary)
_show_work_counters_summary(io, summary)
return
end

function _show_status_summary(io::IO, summary::_SolutionSummary)
println(io, "* Status")
println(io, " Termination status : ", summary.termination_status)
println(io, " Primal status : ", summary.primal_status)
println(io, " Dual status : ", summary.dual_status)
summary.verbose && println(io, " Result count : ", summary.result_count)
summary.verbose && println(io, " Has duals : ", summary.has_duals)
println(io, " Message from the solver:")
println(io, " \"", summary.raw_status, "\"")
println(io)
return
end

function _show_candidate_solution_summary(io::IO, summary::_SolutionSummary)
println(io, "* Candidate solution")
println(io, " Objective value : ", summary.objective_value)
_print_if_not_missing(io, " Objective bound : ", summary.objective_bound)
_print_if_not_missing(io, " Dual objective value : ", summary.dual_objective_value)
if summary.verbose && summary.has_values
println(io, " Primal solution : ")
for variable_name in sort(collect(keys(summary.primal_solution)))
println(io, " ", variable_name, " : ", summary.primal_solution[variable_name])
end
end
if summary.verbose && summary.has_duals
println(io, " Dual solution : ")
for constraint_name in sort(collect(keys(summary.dual_solution)))
println(io, " ", constraint_name, " : ", summary.dual_solution[constraint_name])
end
end
println(io)
return
end

function _show_work_counters_summary(io::IO, summary::_SolutionSummary)
println(io, "* Work counters")
println(io, " Solve time (sec) : ", @sprintf("%.5f", summary.solve_time))
_print_if_not_missing(io, " Simplex iterations : ", summary.simplex_iterations)
_print_if_not_missing(io, " Barrier iterations : ", summary.barrier_iterations)
_print_if_not_missing(io, " Node count : ", summary.node_count)
return
end

function _get_solution_dict(model)
dict = Dict{String,Float64}()
if has_values(model)
for x in all_variables(model)
variable_name = name(x)
if !isempty(variable_name)
dict[variable_name] = value(x)
end
end
end
return dict
end

function _get_constraint_dict(model)
dict = Dict{String,Float64}()
if has_duals(model)
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)
dict[constraint_name] = dual(constraint)
end
end
end
end
return dict
end

function _try_get(f, model)
try
return f(model)
catch
return missing
end
end

_print_if_not_missing(io, header, ::Missing) = nothing
_print_if_not_missing(io, header, value) = println(io, header, value)

#------------------------------------------------------------------------
## VariableRef
#------------------------------------------------------------------------
Expand Down
117 changes: 117 additions & 0 deletions test/print.jl
Original file line number Diff line number Diff line change
Expand Up @@ -820,3 +820,120 @@ end
@variable(model, z, base_name = "z^1")
io_test(IJuliaMode, z, "z\\^1")
end

@testset "Print solution summary" begin

model = Model()
@variable(model, x <= 2.0)
@variable(model, y >= 0.0)
@objective(model, Min, -x)
c = @constraint(model, x + y <= 1) # anonymous constraint

JuMP.set_name(JuMP.UpperBoundRef(x), "xub")
JuMP.set_name(JuMP.LowerBoundRef(y), "ylb")

set_optimizer(
model,
() -> MOIU.MockOptimizer(
MOIU.Model{Float64}(),
eval_objective_value = false,
),
)
optimize!(model)

mockoptimizer = JuMP.backend(model).optimizer.model
MOI.set(mockoptimizer, MOI.TerminationStatus(), MOI.OPTIMAL)
MOI.set(mockoptimizer, MOI.RawStatusString(), "solver specific string")
MOI.set(mockoptimizer, MOI.ObjectiveValue(), -1.0)
MOI.set(mockoptimizer, MOI.ObjectiveBound(), 3.0)
MOI.set(mockoptimizer, MOI.ResultCount(), 1)
MOI.set(mockoptimizer, MOI.PrimalStatus(), MOI.FEASIBLE_POINT)
MOI.set(mockoptimizer, MOI.DualStatus(), MOI.FEASIBLE_POINT)
MOI.set(
mockoptimizer,
MOI.VariablePrimal(),
JuMP.optimizer_index(x),
1.0,
)
MOI.set(
mockoptimizer,
MOI.VariablePrimal(),
JuMP.optimizer_index(y),
0.0,
)
MOI.set(
mockoptimizer,
MOI.ConstraintDual(),
JuMP.optimizer_index(c),
-1.0,
)
MOI.set(
mockoptimizer,
MOI.ConstraintDual(),
JuMP.optimizer_index(JuMP.UpperBoundRef(x)),
0.0,
)
MOI.set(
mockoptimizer,
MOI.ConstraintDual(),
JuMP.optimizer_index(JuMP.LowerBoundRef(y)),
1.0,
)
MOI.set(mockoptimizer, MOI.SimplexIterations(), 1)
MOI.set(mockoptimizer, MOI.BarrierIterations(), 1)
MOI.set(mockoptimizer, MOI.NodeCount(), 1)
MOI.set(mockoptimizer, MOI.SolveTime(), 5.0)

@test sprint(show, solution_summary(model)) == """
* Solver : Mock
* Status
Termination status : OPTIMAL
Primal status : FEASIBLE_POINT
Dual status : FEASIBLE_POINT
Message from the solver:
"solver specific string"
* Candidate solution
Objective value : -1.0
Objective bound : 3.0
Dual objective value : -1.0
* Work counters
Solve time (sec) : 5.00000
Simplex iterations : 1
Barrier iterations : 1
Node count : 1
"""

@test sprint((io, model) -> show(io, solution_summary(model, verbose=true)), model) == """
* Solver : Mock
* Status
Termination status : OPTIMAL
Primal status : FEASIBLE_POINT
Dual status : FEASIBLE_POINT
Result count : 1
Has duals : true
Message from the solver:
"solver specific string"
* Candidate solution
Objective value : -1.0
Objective bound : 3.0
Dual objective value : -1.0
Primal solution :
x : 1.0
y : 0.0
Dual solution :
xub : 0.0
ylb : 1.0
* Work counters
Solve time (sec) : 5.00000
Simplex iterations : 1
Barrier iterations : 1
Node count : 1
"""

end

0 comments on commit 8b13032

Please sign in to comment.