Skip to content

Commit a045fda

Browse files
authored
Add logging for each algorithm (#164)
1 parent b443161 commit a045fda

13 files changed

+193
-35
lines changed

Project.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
name = "MultiObjectiveAlgorithms"
22
uuid = "0327d340-17cd-11ea-3e99-2fd5d98cecda"
3-
authors = ["Oscar Dowson <[email protected]>"]
43
version = "1.7.0"
4+
authors = ["Oscar Dowson <[email protected]>"]
55

66
[deps]
77
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
88
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
9+
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
910

1011
[weakdeps]
1112
Polyhedra = "67491407-f73d-577b-9b50-8179a7c68029"
@@ -20,15 +21,16 @@ Ipopt = "1"
2021
JSON = "1"
2122
MathOptInterface = "1.19"
2223
Polyhedra = "0.8"
24+
Printf = "1.10.0"
2325
Test = "1"
2426
julia = "1.10"
2527

2628
[extras]
2729
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
2830
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
2931
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
30-
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3132
Polyhedra = "67491407-f73d-577b-9b50-8179a7c68029"
33+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3234

3335
[targets]
3436
test = ["HiGHS", "Ipopt", "JSON", "Test", "Polyhedra"]

ext/MultiObjectiveAlgorithmsPolyhedraExt.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ function MOA.minimize_multiobjective!(
3939
model::MOA.Optimizer,
4040
)
4141
@assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE
42-
start_time = time()
4342
solutions = Dict{Vector{Float64},Dict{MOI.VariableIndex,Float64}}()
4443
variables = MOI.get(model.inner, MOI.ListOfVariableIndices())
4544
n = MOI.output_dimension(model.f)
@@ -102,7 +101,7 @@ function MOA.minimize_multiobjective!(
102101
H = _halfspaces(IPS)
103102
count = 0
104103
while !isempty(H)
105-
ret = MOA._check_premature_termination(model, start_time)
104+
ret = MOA._check_premature_termination(model)
106105
if ret !== nothing
107106
status = ret
108107
break

src/MultiObjectiveAlgorithms.jl

Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module MultiObjectiveAlgorithms
77

88
import Combinatorics
99
import MathOptInterface as MOI
10+
import Printf
1011

1112
"""
1213
struct SolutionPoint
@@ -170,22 +171,30 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
170171
f::Union{Nothing,MOI.AbstractVectorFunction}
171172
solutions::Vector{SolutionPoint}
172173
termination_status::MOI.TerminationStatusCode
174+
silent::Bool
173175
time_limit_sec::Union{Nothing,Float64}
176+
start_time::Float64
174177
solve_time::Float64
175178
ideal_point::Vector{Float64}
176179
compute_ideal_point::Bool
177180
subproblem_count::Int
178181
optimizer_factory::Any
179182

180183
function Optimizer(optimizer_factory)
184+
inner = MOI.instantiate(optimizer_factory; with_cache_type = Float64)
185+
if MOI.supports(inner, MOI.Silent())
186+
MOI.set(inner, MOI.Silent(), true)
187+
end
181188
return new(
182-
MOI.instantiate(optimizer_factory; with_cache_type = Float64),
189+
inner,
183190
nothing,
184191
nothing,
185192
SolutionPoint[],
186193
MOI.OPTIMIZE_NOT_CALLED,
194+
false,
187195
nothing,
188196
NaN,
197+
NaN,
189198
Float64[],
190199
_default(ComputeIdealPoint()),
191200
0,
@@ -200,6 +209,7 @@ function MOI.empty!(model::Optimizer)
200209
empty!(model.solutions)
201210
model.termination_status = MOI.OPTIMIZE_NOT_CALLED
202211
model.solve_time = NaN
212+
model.start_time = NaN
203213
empty!(model.ideal_point)
204214
model.subproblem_count = 0
205215
return
@@ -220,6 +230,17 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
220230
return MOI.Utilities.default_copy_to(dest, src)
221231
end
222232

233+
### Silent
234+
235+
MOI.supports(::Optimizer, ::MOI.Silent) = true
236+
237+
MOI.get(model::Optimizer, ::MOI.Silent) = model.silent
238+
239+
function MOI.set(model::Optimizer, ::MOI.Silent, value::Bool)
240+
model.silent = value
241+
return
242+
end
243+
223244
### TimeLimitSec
224245

225246
function MOI.supports(model::Optimizer, attr::MOI.TimeLimitSec)
@@ -646,9 +667,9 @@ function optimize_inner!(model::Optimizer)
646667
return
647668
end
648669

649-
function _compute_ideal_point(model::Optimizer, start_time)
670+
function _compute_ideal_point(model::Optimizer)
650671
for (i, f) in enumerate(MOI.Utilities.eachscalar(model.f))
651-
if _check_premature_termination(model, start_time) !== nothing
672+
if _check_premature_termination(model) !== nothing
652673
return
653674
end
654675
if !isnan(model.ideal_point[i])
@@ -733,11 +754,11 @@ function _check_interrupt(f)
733754
end
734755
end
735756

736-
function _check_premature_termination(model::Optimizer, start_time::Float64)
757+
function _check_premature_termination(model::Optimizer)
737758
return _check_interrupt() do
738759
time_limit = MOI.get(model, MOI.TimeLimitSec())
739760
if time_limit !== nothing
740-
time_remaining = time_limit - (time() - start_time)
761+
time_remaining = time_limit - (time() - model.start_time)
741762
if time_remaining <= 0
742763
return MOI.TIME_LIMIT
743764
end
@@ -754,8 +775,82 @@ function MOI.optimize!(model::Optimizer)
754775
return
755776
end
756777

778+
function _print_header(io::IO, model::Optimizer)
779+
rule = "-"^(7 + 13 * (MOI.output_dimension(model.f) + 1))
780+
println(io, rule)
781+
println(io, " MultiObjectiveAlgorithms.jl")
782+
println(io, rule)
783+
println(
784+
io,
785+
"Algorithm: ",
786+
replace(
787+
string(typeof(model.algorithm)),
788+
"MultiObjectiveAlgorithms." => "",
789+
),
790+
)
791+
println(io, rule)
792+
print(io, "solve #")
793+
for i in 1:MOI.output_dimension(model.f)
794+
print(io, lpad("Obj. $i ", 13))
795+
end
796+
println(io, " Time ")
797+
println(io, rule)
798+
return
799+
end
800+
801+
function _print_footer(io::IO, model::Optimizer)
802+
rule = "-"^(7 + 13 * (MOI.output_dimension(model.f) + 1))
803+
println(io, rule)
804+
println(io, "TerminationStatus: ", model.termination_status)
805+
println(io, "ResultCount: ", length(model.solutions))
806+
println(io, rule)
807+
return
808+
end
809+
810+
"""
811+
_log_subproblem_solve(model::Optimizer, variables::Vector{MOI.VariableIndex})
812+
813+
Log the solution. We don't have a pre-computed point, so compute one from the
814+
variable values.
815+
"""
816+
function _log_subproblem_solve(model::Optimizer, arg)
817+
if !model.silent
818+
_log_subproblem_inner(model, arg)
819+
end
820+
return
821+
end
822+
823+
# Variables; compute the associated Y
824+
function _log_subproblem_inner(model::Optimizer, x::Vector{MOI.VariableIndex})
825+
_, Y = _compute_point(model, x, model.f)
826+
_log_subproblem_solve(model, Y)
827+
return
828+
end
829+
830+
# We have a pre-computed point.
831+
function _log_subproblem_inner(model::Optimizer, Y::Vector)
832+
print(_format(model.subproblem_count), " ")
833+
for y in Y
834+
print(" ", _format(y))
835+
end
836+
println(" ", _format(time() - model.start_time))
837+
return
838+
end
839+
840+
# Assume the subproblem failed to solve.
841+
function _log_subproblem_inner(model::Optimizer, msg::String)
842+
print(_format(model.subproblem_count), " ")
843+
print(rpad(msg, 13 * MOI.output_dimension(model.f)))
844+
println(" ", _format(time() - model.start_time))
845+
return
846+
end
847+
848+
_format(x::Int) = Printf.@sprintf("%5d", x)
849+
850+
_format(x::Float64) = Printf.@sprintf("% .5e", x)
851+
757852
function _optimize!(model::Optimizer)
758-
start_time = time()
853+
model.start_time = time()
759854
empty!(model.solutions)
760855
model.termination_status = MOI.OPTIMIZE_NOT_CALLED
761856
model.subproblem_count = 0
@@ -764,6 +859,9 @@ function _optimize!(model::Optimizer)
764859
empty!(model.ideal_point)
765860
return
766861
end
862+
if !model.silent
863+
_print_header(stdout, model)
864+
end
767865
# We need to clear the ideal point prior to starting the solve. Algorithms
768866
# may update this during the solve, otherwise we will update it at the end.
769867
model.ideal_point = fill(NaN, MOI.output_dimension(model.f))
@@ -774,13 +872,16 @@ function _optimize!(model::Optimizer)
774872
model.solutions = solutions
775873
_sort!(model.solutions, MOI.get(model, MOI.ObjectiveSense()))
776874
end
875+
if !model.silent
876+
_print_footer(stdout, model)
877+
end
777878
if MOI.get(model, ComputeIdealPoint())
778-
_compute_ideal_point(model, start_time)
879+
_compute_ideal_point(model)
779880
end
780881
if MOI.supports(model.inner, MOI.TimeLimitSec())
781882
MOI.set(model.inner, MOI.TimeLimitSec(), nothing)
782883
end
783-
model.solve_time = time() - start_time
884+
model.solve_time = time() - model.start_time
784885
return
785886
end
786887

src/algorithms/Chalmet.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,19 @@ function _solve_constrained_model(
3838
optimize_inner!(model)
3939
status = MOI.get(model.inner, MOI.TerminationStatus())
4040
if !_is_scalar_status_optimal(status)
41+
_log_subproblem_solve(model, "subproblem not optimal")
4142
MOI.delete.(model, c)
4243
return status, nothing
4344
end
4445
variables = MOI.get(model.inner, MOI.ListOfVariableIndices())
4546
X, Y = _compute_point(model, variables, model.f)
47+
_log_subproblem_solve(model, Y)
4648
MOI.delete.(model, c)
4749
return status, SolutionPoint(X, Y)
4850
end
4951

5052
function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer)
5153
@assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE
52-
start_time = time()
5354
if MOI.output_dimension(model.f) != 2
5455
error("Chalmet requires exactly two objectives")
5556
end
@@ -66,6 +67,7 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer)
6667
return status, solutions
6768
end
6869
_, y1[2] = _compute_point(model, variables, f2)
70+
_log_subproblem_solve(model, variables)
6971
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f1)}(), f1)
7072
y1_constraint = MOI.Utilities.normalize_and_add_constraint(
7173
model.inner,
@@ -78,6 +80,7 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer)
7880
return status, solutions
7981
end
8082
x1, y1[1] = _compute_point(model, variables, f1)
83+
_log_subproblem_solve(model, y1)
8184
MOI.delete(model.inner, y1_constraint)
8285
push!(solutions, SolutionPoint(x1, y1))
8386
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f1)}(), f1)
@@ -87,6 +90,7 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer)
8790
return status, solutions
8891
end
8992
_, y2[1] = _compute_point(model, variables, f1)
93+
_log_subproblem_solve(model, variables)
9094
if y2[1] solutions[1].y[1]
9195
return MOI.OPTIMAL, solutions
9296
end
@@ -102,12 +106,13 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer)
102106
return status, solutions
103107
end
104108
x2, y2[2] = _compute_point(model, variables, f2)
109+
_log_subproblem_solve(model, y2)
105110
MOI.delete(model.inner, y2_constraint)
106111
push!(solutions, SolutionPoint(x2, y2))
107112
push!(Q, (1, 2))
108113
t = 3
109114
while !isempty(Q)
110-
if (ret = _check_premature_termination(model, start_time)) !== nothing
115+
if (ret = _check_premature_termination(model)) !== nothing
111116
return ret, solutions
112117
end
113118
r, s = pop!(Q)

src/algorithms/Dichotomy.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,16 @@ function _solve_weighted_sum(
7474
end
7575
variables = MOI.get(model.inner, MOI.ListOfVariableIndices())
7676
X, Y = _compute_point(model, variables, model.f)
77+
_log_subproblem_solve(model, Y)
7778
return status, SolutionPoint(X, Y)
7879
end
7980

8081
function optimize_multiobjective!(algorithm::Dichotomy, model::Optimizer)
81-
start_time = time()
8282
if MOI.output_dimension(model.f) > 2
8383
error("Only scalar or bi-objective problems supported.")
8484
end
8585
if MOI.output_dimension(model.f) == 1
86-
if (ret = _check_premature_termination(model, start_time)) !== nothing
86+
if (ret = _check_premature_termination(model)) !== nothing
8787
return ret, nothing
8888
end
8989
status, solution = _solve_weighted_sum(model, algorithm, [1.0])
@@ -106,7 +106,7 @@ function optimize_multiobjective!(algorithm::Dichotomy, model::Optimizer)
106106
limit = MOI.get(algorithm, SolutionLimit())
107107
status = MOI.OPTIMAL
108108
while length(queue) > 0 && length(solutions) < limit
109-
if (ret = _check_premature_termination(model, start_time)) !== nothing
109+
if (ret = _check_premature_termination(model)) !== nothing
110110
status = ret
111111
break
112112
end

0 commit comments

Comments
 (0)