diff --git a/Project.toml b/Project.toml index c7005f7..6d324f5 100644 --- a/Project.toml +++ b/Project.toml @@ -1,11 +1,12 @@ name = "MultiObjectiveAlgorithms" uuid = "0327d340-17cd-11ea-3e99-2fd5d98cecda" -authors = ["Oscar Dowson "] version = "1.7.0" +authors = ["Oscar Dowson "] [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" +Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" [weakdeps] Polyhedra = "67491407-f73d-577b-9b50-8179a7c68029" @@ -20,6 +21,7 @@ Ipopt = "1" JSON = "1" MathOptInterface = "1.19" Polyhedra = "0.8" +Printf = "1.10.0" Test = "1" julia = "1.10" @@ -27,8 +29,8 @@ julia = "1.10" HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Polyhedra = "67491407-f73d-577b-9b50-8179a7c68029" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] test = ["HiGHS", "Ipopt", "JSON", "Test", "Polyhedra"] diff --git a/ext/MultiObjectiveAlgorithmsPolyhedraExt.jl b/ext/MultiObjectiveAlgorithmsPolyhedraExt.jl index efaadc9..b6c1601 100644 --- a/ext/MultiObjectiveAlgorithmsPolyhedraExt.jl +++ b/ext/MultiObjectiveAlgorithmsPolyhedraExt.jl @@ -39,7 +39,6 @@ function MOA.minimize_multiobjective!( model::MOA.Optimizer, ) @assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE - start_time = time() solutions = Dict{Vector{Float64},Dict{MOI.VariableIndex,Float64}}() variables = MOI.get(model.inner, MOI.ListOfVariableIndices()) n = MOI.output_dimension(model.f) @@ -102,7 +101,7 @@ function MOA.minimize_multiobjective!( H = _halfspaces(IPS) count = 0 while !isempty(H) - ret = MOA._check_premature_termination(model, start_time) + ret = MOA._check_premature_termination(model) if ret !== nothing status = ret break diff --git a/src/MultiObjectiveAlgorithms.jl b/src/MultiObjectiveAlgorithms.jl index 8d25df0..2882a60 100644 --- a/src/MultiObjectiveAlgorithms.jl +++ b/src/MultiObjectiveAlgorithms.jl @@ -7,6 +7,7 @@ module MultiObjectiveAlgorithms import Combinatorics import MathOptInterface as MOI +import Printf """ struct SolutionPoint @@ -170,7 +171,9 @@ mutable struct Optimizer <: MOI.AbstractOptimizer f::Union{Nothing,MOI.AbstractVectorFunction} solutions::Vector{SolutionPoint} termination_status::MOI.TerminationStatusCode + silent::Bool time_limit_sec::Union{Nothing,Float64} + start_time::Float64 solve_time::Float64 ideal_point::Vector{Float64} compute_ideal_point::Bool @@ -178,14 +181,20 @@ mutable struct Optimizer <: MOI.AbstractOptimizer optimizer_factory::Any function Optimizer(optimizer_factory) + inner = MOI.instantiate(optimizer_factory; with_cache_type = Float64) + if MOI.supports(inner, MOI.Silent()) + MOI.set(inner, MOI.Silent(), true) + end return new( - MOI.instantiate(optimizer_factory; with_cache_type = Float64), + inner, nothing, nothing, SolutionPoint[], MOI.OPTIMIZE_NOT_CALLED, + false, nothing, NaN, + NaN, Float64[], _default(ComputeIdealPoint()), 0, @@ -200,6 +209,7 @@ function MOI.empty!(model::Optimizer) empty!(model.solutions) model.termination_status = MOI.OPTIMIZE_NOT_CALLED model.solve_time = NaN + model.start_time = NaN empty!(model.ideal_point) model.subproblem_count = 0 return @@ -220,6 +230,17 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike) return MOI.Utilities.default_copy_to(dest, src) end +### Silent + +MOI.supports(::Optimizer, ::MOI.Silent) = true + +MOI.get(model::Optimizer, ::MOI.Silent) = model.silent + +function MOI.set(model::Optimizer, ::MOI.Silent, value::Bool) + model.silent = value + return +end + ### TimeLimitSec function MOI.supports(model::Optimizer, attr::MOI.TimeLimitSec) @@ -646,9 +667,9 @@ function optimize_inner!(model::Optimizer) return end -function _compute_ideal_point(model::Optimizer, start_time) +function _compute_ideal_point(model::Optimizer) for (i, f) in enumerate(MOI.Utilities.eachscalar(model.f)) - if _check_premature_termination(model, start_time) !== nothing + if _check_premature_termination(model) !== nothing return end if !isnan(model.ideal_point[i]) @@ -733,11 +754,11 @@ function _check_interrupt(f) end end -function _check_premature_termination(model::Optimizer, start_time::Float64) +function _check_premature_termination(model::Optimizer) return _check_interrupt() do time_limit = MOI.get(model, MOI.TimeLimitSec()) if time_limit !== nothing - time_remaining = time_limit - (time() - start_time) + time_remaining = time_limit - (time() - model.start_time) if time_remaining <= 0 return MOI.TIME_LIMIT end @@ -754,8 +775,82 @@ function MOI.optimize!(model::Optimizer) return end +function _print_header(io::IO, model::Optimizer) + rule = "-"^(7 + 13 * (MOI.output_dimension(model.f) + 1)) + println(io, rule) + println(io, " MultiObjectiveAlgorithms.jl") + println(io, rule) + println( + io, + "Algorithm: ", + replace( + string(typeof(model.algorithm)), + "MultiObjectiveAlgorithms." => "", + ), + ) + println(io, rule) + print(io, "solve #") + for i in 1:MOI.output_dimension(model.f) + print(io, lpad("Obj. $i ", 13)) + end + println(io, " Time ") + println(io, rule) + return +end + +function _print_footer(io::IO, model::Optimizer) + rule = "-"^(7 + 13 * (MOI.output_dimension(model.f) + 1)) + println(io, rule) + println(io, "TerminationStatus: ", model.termination_status) + println(io, "ResultCount: ", length(model.solutions)) + println(io, rule) + return +end + +""" + _log_subproblem_solve(model::Optimizer, variables::Vector{MOI.VariableIndex}) + +Log the solution. We don't have a pre-computed point, so compute one from the +variable values. +""" +function _log_subproblem_solve(model::Optimizer, arg) + if !model.silent + _log_subproblem_inner(model, arg) + end + return +end + +# Variables; compute the associated Y +function _log_subproblem_inner(model::Optimizer, x::Vector{MOI.VariableIndex}) + _, Y = _compute_point(model, x, model.f) + _log_subproblem_solve(model, Y) + return +end + +# We have a pre-computed point. +function _log_subproblem_inner(model::Optimizer, Y::Vector) + print(_format(model.subproblem_count), " ") + for y in Y + print(" ", _format(y)) + end + println(" ", _format(time() - model.start_time)) + return +end + +# Assume the subproblem failed to solve. +function _log_subproblem_inner(model::Optimizer, msg::String) + print(_format(model.subproblem_count), " ") + print(rpad(msg, 13 * MOI.output_dimension(model.f))) + println(" ", _format(time() - model.start_time)) + return +end + +_format(x::Int) = Printf.@sprintf("%5d", x) + +_format(x::Float64) = Printf.@sprintf("% .5e", x) + function _optimize!(model::Optimizer) - start_time = time() + model.start_time = time() empty!(model.solutions) model.termination_status = MOI.OPTIMIZE_NOT_CALLED model.subproblem_count = 0 @@ -764,6 +859,9 @@ function _optimize!(model::Optimizer) empty!(model.ideal_point) return end + if !model.silent + _print_header(stdout, model) + end # We need to clear the ideal point prior to starting the solve. Algorithms # may update this during the solve, otherwise we will update it at the end. model.ideal_point = fill(NaN, MOI.output_dimension(model.f)) @@ -774,13 +872,16 @@ function _optimize!(model::Optimizer) model.solutions = solutions _sort!(model.solutions, MOI.get(model, MOI.ObjectiveSense())) end + if !model.silent + _print_footer(stdout, model) + end if MOI.get(model, ComputeIdealPoint()) - _compute_ideal_point(model, start_time) + _compute_ideal_point(model) end if MOI.supports(model.inner, MOI.TimeLimitSec()) MOI.set(model.inner, MOI.TimeLimitSec(), nothing) end - model.solve_time = time() - start_time + model.solve_time = time() - model.start_time return end diff --git a/src/algorithms/Chalmet.jl b/src/algorithms/Chalmet.jl index 4b8a662..33e2c4b 100644 --- a/src/algorithms/Chalmet.jl +++ b/src/algorithms/Chalmet.jl @@ -38,18 +38,19 @@ function _solve_constrained_model( optimize_inner!(model) status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) + _log_subproblem_solve(model, "subproblem not optimal") MOI.delete.(model, c) return status, nothing end variables = MOI.get(model.inner, MOI.ListOfVariableIndices()) X, Y = _compute_point(model, variables, model.f) + _log_subproblem_solve(model, Y) MOI.delete.(model, c) return status, SolutionPoint(X, Y) end function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer) @assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE - start_time = time() if MOI.output_dimension(model.f) != 2 error("Chalmet requires exactly two objectives") end @@ -66,6 +67,7 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer) return status, solutions end _, y1[2] = _compute_point(model, variables, f2) + _log_subproblem_solve(model, variables) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f1)}(), f1) y1_constraint = MOI.Utilities.normalize_and_add_constraint( model.inner, @@ -78,6 +80,7 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer) return status, solutions end x1, y1[1] = _compute_point(model, variables, f1) + _log_subproblem_solve(model, y1) MOI.delete(model.inner, y1_constraint) push!(solutions, SolutionPoint(x1, y1)) MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f1)}(), f1) @@ -87,6 +90,7 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer) return status, solutions end _, y2[1] = _compute_point(model, variables, f1) + _log_subproblem_solve(model, variables) if y2[1] ≈ solutions[1].y[1] return MOI.OPTIMAL, solutions end @@ -102,12 +106,13 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer) return status, solutions end x2, y2[2] = _compute_point(model, variables, f2) + _log_subproblem_solve(model, y2) MOI.delete(model.inner, y2_constraint) push!(solutions, SolutionPoint(x2, y2)) push!(Q, (1, 2)) t = 3 while !isempty(Q) - if (ret = _check_premature_termination(model, start_time)) !== nothing + if (ret = _check_premature_termination(model)) !== nothing return ret, solutions end r, s = pop!(Q) diff --git a/src/algorithms/Dichotomy.jl b/src/algorithms/Dichotomy.jl index b55e244..7332c86 100644 --- a/src/algorithms/Dichotomy.jl +++ b/src/algorithms/Dichotomy.jl @@ -74,16 +74,16 @@ function _solve_weighted_sum( end variables = MOI.get(model.inner, MOI.ListOfVariableIndices()) X, Y = _compute_point(model, variables, model.f) + _log_subproblem_solve(model, Y) return status, SolutionPoint(X, Y) end function optimize_multiobjective!(algorithm::Dichotomy, model::Optimizer) - start_time = time() if MOI.output_dimension(model.f) > 2 error("Only scalar or bi-objective problems supported.") end if MOI.output_dimension(model.f) == 1 - if (ret = _check_premature_termination(model, start_time)) !== nothing + if (ret = _check_premature_termination(model)) !== nothing return ret, nothing end status, solution = _solve_weighted_sum(model, algorithm, [1.0]) @@ -106,7 +106,7 @@ function optimize_multiobjective!(algorithm::Dichotomy, model::Optimizer) limit = MOI.get(algorithm, SolutionLimit()) status = MOI.OPTIMAL while length(queue) > 0 && length(solutions) < limit - if (ret = _check_premature_termination(model, start_time)) !== nothing + if (ret = _check_premature_termination(model)) !== nothing status = ret break end diff --git a/src/algorithms/DominguezRios.jl b/src/algorithms/DominguezRios.jl index 1e0d0d7..e5d8d4c 100644 --- a/src/algorithms/DominguezRios.jl +++ b/src/algorithms/DominguezRios.jl @@ -64,8 +64,8 @@ function _p_partition( ẑ = max.(z, B.l) ret = _DominguezRiosBox[] for i in 1:length(z) - new_l = vcat(B.l[1:i], ẑ[i+1:end]) - new_u = vcat(B.u[1:i-1], ẑ[i], B.u[i+1:end]) + new_l = vcat(B.l[1:i], ẑ[(i+1):end]) + new_u = vcat(B.u[1:(i-1)], ẑ[i], B.u[(i+1):end]) new_priority = _reduced_scaled_priority(new_l, new_u, i, ẑ, yI, yN) push!(ret, _DominguezRiosBox(new_l, new_u, new_priority)) end @@ -150,7 +150,6 @@ end function minimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) @assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE - start_time = time() n = MOI.output_dimension(model.f) L = [_DominguezRiosBox[] for i in 1:n] scalars = MOI.Utilities.scalarize(model.f) @@ -166,6 +165,7 @@ function minimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) return status, nothing end _, Y = _compute_point(model, variables, f_i) + _log_subproblem_solve(model, variables) yI[i] = Y model.ideal_point[i] = Y MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MAX_SENSE) @@ -176,6 +176,7 @@ function minimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) return status, nothing end _, Y = _compute_point(model, variables, f_i) + _log_subproblem_solve(model, variables) yN[i] = Y + 1 end MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE) @@ -190,7 +191,7 @@ function minimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) k = 0 status = MOI.OPTIMAL while any(!isempty(l) for l in L) - if (ret = _check_premature_termination(model, start_time)) !== nothing + if (ret = _check_premature_termination(model)) !== nothing status = ret break end @@ -216,6 +217,7 @@ function minimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) optimize_inner!(model) if _is_scalar_status_optimal(model) X, Y = _compute_point(model, variables, model.f) + _log_subproblem_solve(model, Y) obj = MOI.get(model.inner, MOI.ObjectiveValue()) # We need to undo the scaling of the scalar objective. There's no # need to unscale `Y` because we have evaluated this explicitly from @@ -233,6 +235,7 @@ function minimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) # fail and return something like OTHER_ERROR (e.g., because the # numerics are challenging). Rather than error completely, let's # just skip this box. + _log_subproblem_solve(model, "subproblem not optimal") deleteat!(L[k], i) end MOI.delete.(model.inner, constraints) diff --git a/src/algorithms/EpsilonConstraint.jl b/src/algorithms/EpsilonConstraint.jl index d98c043..3bcdb94 100644 --- a/src/algorithms/EpsilonConstraint.jl +++ b/src/algorithms/EpsilonConstraint.jl @@ -76,7 +76,6 @@ function minimize_multiobjective!( model::Optimizer, ) @assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE - start_time = time() if MOI.output_dimension(model.f) != 2 error("EpsilonConstraint requires exactly two objectives") end @@ -117,7 +116,7 @@ function minimize_multiobjective!( bound -= constant status = MOI.OPTIMAL for _ in 3:n_points - if (ret = _check_premature_termination(model, start_time)) !== nothing + if (ret = _check_premature_termination(model)) !== nothing status = ret break end @@ -127,6 +126,7 @@ function minimize_multiobjective!( break end X, Y = _compute_point(model, variables, model.f) + _log_subproblem_solve(model, Y) if isempty(solutions) || !(Y ≈ solutions[end].y) push!(solutions, SolutionPoint(X, Y)) end diff --git a/src/algorithms/Hierarchical.jl b/src/algorithms/Hierarchical.jl index 8684a24..33aabc4 100644 --- a/src/algorithms/Hierarchical.jl +++ b/src/algorithms/Hierarchical.jl @@ -112,6 +112,10 @@ function minimize_multiobjective!(algorithm::Hierarchical, model::Optimizer) if round == length(objective_subsets) break end + if !model.silent + X, Y = _compute_point(model, variables, model.f) + _log_subproblem_solve(model, Y) + end # Add tolerance constraints X, Y = _compute_point(model, variables, new_vector_f) for (i, fi) in enumerate(MOI.Utilities.eachscalar(new_vector_f)) @@ -122,6 +126,7 @@ function minimize_multiobjective!(algorithm::Hierarchical, model::Optimizer) end end X, Y = _compute_point(model, variables, model.f) + _log_subproblem_solve(model, Y) # Remove tolerance constraints for c in constraints MOI.delete(model, c) diff --git a/src/algorithms/KirlikSayin.jl b/src/algorithms/KirlikSayin.jl index 05e080f..8f36040 100644 --- a/src/algorithms/KirlikSayin.jl +++ b/src/algorithms/KirlikSayin.jl @@ -83,7 +83,6 @@ end function minimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer) @assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE - start_time = time() solutions = SolutionPoint[] # Problem with p objectives. # Set k = 1, meaning the nondominated points will get projected @@ -106,6 +105,7 @@ function minimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer) return status, nothing end _, Y = _compute_point(model, variables, f_i) + _log_subproblem_solve(model, variables) model.ideal_point[i] = yI[i] = Y # Nadir point MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MAX_SENSE) @@ -118,13 +118,14 @@ function minimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer) return status, nothing end _, Y = _compute_point(model, variables, f_i) + _log_subproblem_solve(model, variables) yN[i] = Y + δ MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE) end L = [_Rectangle(_project(yI, k), _project(yN, k))] status = MOI.OPTIMAL while !isempty(L) - if (ret = _check_premature_termination(model, start_time)) !== nothing + if (ret = _check_premature_termination(model)) !== nothing status = ret break end @@ -152,6 +153,7 @@ function minimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer) push!(ε_constraints, ci) end optimize_inner!(model) + _log_subproblem_solve(model, "auxillary subproblem") if !_is_scalar_status_optimal(model) # If this fails, it likely means that the solver experienced a # numerical error with this box. Just skip it. @@ -172,6 +174,7 @@ function minimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer) ) optimize_inner!(model) if !_is_scalar_status_optimal(model) + _log_subproblem_solve(model, "subproblem not optimal") # If this fails, it likely means that the solver experienced a # numerical error with this box. Just skip it. MOI.delete.(model, ε_constraints) @@ -180,6 +183,7 @@ function minimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer) continue end X, Y = _compute_point(model, variables, model.f) + _log_subproblem_solve(model, Y) Y_proj = _project(Y, k) if !(Y in YN) push!(solutions, SolutionPoint(X, Y)) diff --git a/src/algorithms/Lexicographic.jl b/src/algorithms/Lexicographic.jl index d81b139..7ca354d 100644 --- a/src/algorithms/Lexicographic.jl +++ b/src/algorithms/Lexicographic.jl @@ -69,11 +69,10 @@ function MOI.set(alg::Lexicographic, ::LexicographicAllPermutations, val::Bool) end function optimize_multiobjective!(algorithm::Lexicographic, model::Optimizer) - start_time = time() sequence = 1:MOI.output_dimension(model.f) perm = MOI.get(algorithm, LexicographicAllPermutations()) if !something(perm, _default(LexicographicAllPermutations())) - return _solve_in_sequence(algorithm, model, sequence, start_time) + return _solve_in_sequence(algorithm, model, sequence) end if perm === nothing && length(sequence) >= 5 o, n = length(sequence), factorial(length(sequence)) @@ -103,8 +102,7 @@ function optimize_multiobjective!(algorithm::Lexicographic, model::Optimizer) solutions = SolutionPoint[] status = MOI.OPTIMAL for sequence in Combinatorics.permutations(sequence) - status, solution = - _solve_in_sequence(algorithm, model, sequence, start_time) + status, solution = _solve_in_sequence(algorithm, model, sequence) if !isempty(solution) push!(solutions, solution[1]) end @@ -120,7 +118,6 @@ function _solve_in_sequence( algorithm::Lexicographic, model::Optimizer, sequence::AbstractVector{Int}, - start_time::Float64, ) variables = MOI.get(model.inner, MOI.ListOfVariableIndices()) constraints = Any[] @@ -128,7 +125,7 @@ function _solve_in_sequence( solution = SolutionPoint[] status = MOI.OPTIMAL for i in sequence - if (ret = _check_premature_termination(model, start_time)) !== nothing + if (ret = _check_premature_termination(model)) !== nothing status = ret break end @@ -139,6 +136,7 @@ function _solve_in_sequence( primal_status = MOI.get(model.inner, MOI.PrimalStatus()) if _is_scalar_status_feasible_point(primal_status) X, Y = _compute_point(model, variables, model.f) + _log_subproblem_solve(model, Y) solution = [SolutionPoint(X, Y)] end if !_is_scalar_status_optimal(status) diff --git a/src/algorithms/RandomWeighting.jl b/src/algorithms/RandomWeighting.jl index 68bf7fb..10182c8 100644 --- a/src/algorithms/RandomWeighting.jl +++ b/src/algorithms/RandomWeighting.jl @@ -43,7 +43,6 @@ function optimize_multiobjective!(algorithm::RandomWeighting, model::Optimizer) algorithm.solution_limit === nothing error("At least `MOI.TimeLimitSec` or `MOA.SolutionLimit` must be set") end - start_time = time() solutions = SolutionPoint[] sense = MOI.get(model, MOI.ObjectiveSense()) P = MOI.output_dimension(model.f) @@ -54,6 +53,7 @@ function optimize_multiobjective!(algorithm::RandomWeighting, model::Optimizer) status = MOI.get(model.inner, MOI.TerminationStatus()) if _is_scalar_status_optimal(status) X, Y = _compute_point(model, variables, model.f) + _log_subproblem_solve(model, Y) push!(solutions, SolutionPoint(X, Y)) else return status, nothing @@ -64,7 +64,7 @@ function optimize_multiobjective!(algorithm::RandomWeighting, model::Optimizer) # * then the outer loop goes again while length(solutions) < MOI.get(algorithm, SolutionLimit()) while length(solutions) < MOI.get(algorithm, SolutionLimit()) - ret = _check_premature_termination(model, start_time) + ret = _check_premature_termination(model) if ret !== nothing return ret, filter_nondominated(sense, solutions) end @@ -75,6 +75,7 @@ function optimize_multiobjective!(algorithm::RandomWeighting, model::Optimizer) status = MOI.get(model.inner, MOI.TerminationStatus()) if _is_scalar_status_optimal(status) X, Y = _compute_point(model, variables, model.f) + _log_subproblem_solve(model, Y) push!(solutions, SolutionPoint(X, Y)) end end diff --git a/src/algorithms/TambyVanderpooten.jl b/src/algorithms/TambyVanderpooten.jl index ef89e03..1d4fdbd 100644 --- a/src/algorithms/TambyVanderpooten.jl +++ b/src/algorithms/TambyVanderpooten.jl @@ -93,7 +93,6 @@ function minimize_multiobjective!( model::Optimizer, ) @assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE - start_time = time() warm_start_supported = false if MOI.supports(model, MOI.VariablePrimalStart(), MOI.VariableIndex) warm_start_supported = true @@ -112,6 +111,7 @@ function minimize_multiobjective!( return status, nothing end _, Y = _compute_point(model, variables, f_i) + _log_subproblem_solve(model, variables) yI[i] = Y model.ideal_point[i] = Y MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MAX_SENSE) @@ -122,6 +122,7 @@ function minimize_multiobjective!( return status, nothing end _, Y = _compute_point(model, variables, f_i) + _log_subproblem_solve(model, variables) yN[i] = Y + 1 end MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE) @@ -130,7 +131,7 @@ function minimize_multiobjective!( U_N[yN] = [[_get_child(yN, yI, k)] for k in 1:n] status = MOI.OPTIMAL while !isempty(U_N) - if (ret = _check_premature_termination(model, start_time)) !== nothing + if (ret = _check_premature_termination(model)) !== nothing status = ret break end @@ -165,6 +166,7 @@ function minimize_multiobjective!( end end optimize_inner!(model) + _log_subproblem_solve(model, "auxillary subproblem") status = MOI.get(model.inner, MOI.TerminationStatus()) if !_is_scalar_status_optimal(status) MOI.delete.(model, ε_constraints) @@ -186,6 +188,7 @@ function minimize_multiobjective!( return status, nothing end X, Y = _compute_point(model, variables, model.f) + _log_subproblem_solve(model, Y) MOI.delete.(model, ε_constraints) MOI.delete(model, y_k_constraint) push!(V[k], (u, Y)) diff --git a/test/test_model.jl b/test/test_model.jl index db61d86..619c11a 100644 --- a/test/test_model.jl +++ b/test/test_model.jl @@ -234,6 +234,43 @@ function test_check_interrupt() return end +function test_printing() + model = MOA.Optimizer(HiGHS.Optimizer) + MOI.set(model, MOA.Algorithm(), MOA.KirlikSayin()) + x = MOI.add_variables(model, 2) + MOI.add_constraint.(model, x, MOI.GreaterThan(0.0)) + MOI.add_constraint(model, x[2], MOI.LessThan(3.0)) + MOI.add_constraint(model, 3.0 * x[1] - 1.0 * x[2], MOI.LessThan(6.0)) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + f = MOI.Utilities.vectorize([3.0 * x[1] + x[2], -1.0 * x[1] - 2.0 * x[2]]) + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + dir = mktempdir() + open(joinpath(dir, "log.txt"), "w") do io + redirect_stdout(() -> MOI.optimize!(model), io) + return + end + contents = read(joinpath(dir, "log.txt"), String) + for line in [ + "Algorithm: KirlikSayin", + "auxillary subproblem", + "1 0.00000e+00 0.00000e+00", + "----------------------------------------------", + "TerminationStatus: OPTIMAL", + "ResultCount: 10", + ] + @test occursin(line, contents) + end + @test MOI.supports(model, MOI.Silent()) + @test MOI.get(model, MOI.Silent()) == false + MOI.set(model, MOI.Silent(), true) + open(joinpath(dir, "log2.txt"), "w") do io + redirect_stdout(() -> MOI.optimize!(model), io) + return + end + @test read(joinpath(dir, "log2.txt"), String) == "" + return +end + end # module TestModel.run_tests()