|
| 1 | +# Copyright 2019, Oscar Dowson and contributors |
| 2 | +# This Source Code Form is subject to the terms of the Mozilla Public License, |
| 3 | +# v.2.0. If a copy of the MPL was not distributed with this file, You can |
| 4 | +# obtain one at http://mozilla.org/MPL/2.0/. |
| 5 | + |
| 6 | +module MultiObjectiveAlgorithmsPolyhedraExt |
| 7 | + |
| 8 | +import MathOptInterface as MOI |
| 9 | +import MultiObjectiveAlgorithms as MOA |
| 10 | +import Polyhedra |
| 11 | + |
| 12 | +function _halfspaces(IPS::Vector{Vector{Float64}}) |
| 13 | + V = Polyhedra.vrep(IPS) |
| 14 | + H = Polyhedra.halfspaces(Polyhedra.doubledescription(V)) |
| 15 | + return [(-H_i.a, -H_i.β) for H_i in H] |
| 16 | +end |
| 17 | + |
| 18 | +function _distance(w̄, b̄, δ_OPS_optimizer) |
| 19 | + y = MOI.get(δ_OPS_optimizer, MOI.ListOfVariableIndices()) |
| 20 | + MOI.set( |
| 21 | + δ_OPS_optimizer, |
| 22 | + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), |
| 23 | + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(w̄, y), 0.0), |
| 24 | + ) |
| 25 | + MOI.set(δ_OPS_optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE) |
| 26 | + MOI.optimize!(δ_OPS_optimizer) |
| 27 | + return b̄ - MOI.get(δ_OPS_optimizer, MOI.ObjectiveValue()) |
| 28 | +end |
| 29 | + |
| 30 | +function _select_next_halfspace(H, δ_OPS_optimizer) |
| 31 | + distances = [_distance(w, b, δ_OPS_optimizer) for (w, b) in H] |
| 32 | + index = argmax(distances) |
| 33 | + w, b = H[index] |
| 34 | + return distances[index], w, b |
| 35 | +end |
| 36 | + |
| 37 | +function MOA.minimize_multiobjective!( |
| 38 | + algorithm::MOA.Sandwiching, |
| 39 | + model::MOA.Optimizer, |
| 40 | +) |
| 41 | + @assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE |
| 42 | + start_time = time() |
| 43 | + solutions = Dict{Vector{Float64},Dict{MOI.VariableIndex,Float64}}() |
| 44 | + variables = MOI.get(model.inner, MOI.ListOfVariableIndices()) |
| 45 | + n = MOI.output_dimension(model.f) |
| 46 | + scalars = MOI.Utilities.scalarize(model.f) |
| 47 | + status = MOI.OPTIMAL |
| 48 | + optimizer = typeof(model.inner.optimizer) |
| 49 | + δ_OPS_optimizer = optimizer() |
| 50 | + MOI.set(δ_OPS_optimizer, MOI.Silent(), true) |
| 51 | + y = MOI.add_variables(δ_OPS_optimizer, n) |
| 52 | + anchors = Dict{Vector{Float64},Dict{MOI.VariableIndex,Float64}}() |
| 53 | + yI, yUB = zeros(n), zeros(n) |
| 54 | + for (i, f_i) in enumerate(scalars) |
| 55 | + MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f_i)}(), f_i) |
| 56 | + MOI.optimize!(model.inner) |
| 57 | + status = MOI.get(model.inner, MOI.TerminationStatus()) |
| 58 | + if !MOA._is_scalar_status_optimal(model) |
| 59 | + return status, nothing |
| 60 | + end |
| 61 | + X, Y = MOA._compute_point(model, variables, model.f) |
| 62 | + model.ideal_point[i] = Y[i] |
| 63 | + yI[i] = Y[i] |
| 64 | + anchors[Y] = X |
| 65 | + MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MAX_SENSE) |
| 66 | + MOI.optimize!(model.inner) |
| 67 | + status = MOI.get(model.inner, MOI.TerminationStatus()) |
| 68 | + if !MOA._is_scalar_status_optimal(model) |
| 69 | + MOA._warn_on_nonfinite_anti_ideal(algorithm, MOI.MIN_SENSE, i) |
| 70 | + return status, nothing |
| 71 | + end |
| 72 | + _, Y = MOA._compute_point(model, variables, f_i) |
| 73 | + yUB[i] = Y |
| 74 | + MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE) |
| 75 | + e_i = Float64.(1:n .== i) |
| 76 | + MOI.add_constraint( |
| 77 | + δ_OPS_optimizer, |
| 78 | + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(e_i, y), 0.0), |
| 79 | + MOI.GreaterThan(yI[i]), |
| 80 | + ) |
| 81 | + MOI.add_constraint( |
| 82 | + δ_OPS_optimizer, |
| 83 | + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(e_i, y), 0.0), |
| 84 | + MOI.LessThan(yUB[i]), |
| 85 | + ) |
| 86 | + end |
| 87 | + IPS = [yUB, keys(anchors)...] |
| 88 | + merge!(solutions, anchors) |
| 89 | + u = MOI.add_variables(model.inner, n) |
| 90 | + u_constraints = [ # u_i >= 0 for all i = 1:n |
| 91 | + MOI.add_constraint(model.inner, u_i, MOI.GreaterThan{Float64}(0)) |
| 92 | + for u_i in u |
| 93 | + ] |
| 94 | + f_constraints = [ # f_i + u_i <= yUB_i for all i = 1:n |
| 95 | + MOI.Utilities.normalize_and_add_constraint( |
| 96 | + model.inner, |
| 97 | + scalars[i] + u[i], |
| 98 | + MOI.LessThan(yUB[i]), |
| 99 | + ) for i in 1:n |
| 100 | + ] |
| 101 | + H = _halfspaces(IPS) |
| 102 | + count = 0 |
| 103 | + while !isempty(H) |
| 104 | + if MOA._time_limit_exceeded(model, start_time) |
| 105 | + status = MOI.TIME_LIMIT |
| 106 | + break |
| 107 | + end |
| 108 | + count += 1 |
| 109 | + δ, w, b = _select_next_halfspace(H, δ_OPS_optimizer) |
| 110 | + if δ - 1e-3 <= algorithm.precision # added some convergence tolerance |
| 111 | + break |
| 112 | + end |
| 113 | + # would not terminate when precision is set to 0 |
| 114 | + new_f = sum(w[i] * (scalars[i] + u[i]) for i in 1:n) # w' * (f(x) + u) |
| 115 | + MOI.set(model.inner, MOI.ObjectiveFunction{typeof(new_f)}(), new_f) |
| 116 | + MOI.optimize!(model.inner) |
| 117 | + status = MOI.get(model.inner, MOI.TerminationStatus()) |
| 118 | + if !MOA._is_scalar_status_optimal(model) |
| 119 | + return status, nothing |
| 120 | + end |
| 121 | + β̄ = MOI.get(model.inner, MOI.ObjectiveValue()) |
| 122 | + X, Y = MOA._compute_point(model, variables, model.f) |
| 123 | + solutions[Y] = X |
| 124 | + MOI.add_constraint( |
| 125 | + δ_OPS_optimizer, |
| 126 | + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(w, y), 0.0), |
| 127 | + MOI.GreaterThan(β̄), |
| 128 | + ) |
| 129 | + IPS = push!(IPS, Y) |
| 130 | + H = _halfspaces(IPS) |
| 131 | + end |
| 132 | + MOI.delete.(model.inner, f_constraints) |
| 133 | + MOI.delete.(model.inner, u_constraints) |
| 134 | + MOI.delete.(model.inner, u) |
| 135 | + return status, [MOA.SolutionPoint(X, Y) for (Y, X) in solutions] |
| 136 | +end |
| 137 | + |
| 138 | +end # module MultiObjectiveAlgorithmsPolyhedraExt |
0 commit comments