Skip to content

Commit 0f51b74

Browse files
committed
Update
1 parent bf9ceda commit 0f51b74

6 files changed

Lines changed: 185 additions & 313 deletions

File tree

Project.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ authors = ["Oscar Dowson <o.dowson@gmail.com>"]
55

66
[deps]
77
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
8-
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
98
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
10-
Polyhedra = "67491407-f73d-577b-9b50-8179a7c68029"
119
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
1210

11+
[weakdeps]
12+
Polyhedra = "67491407-f73d-577b-9b50-8179a7c68029"
13+
1314
[extensions]
1415
MultiObjectiveAlgorithmsPolyhedraExt = "Polyhedra"
1516

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ depends on the algorithm and the problem class.
6767
| `MOA.Dichotomy()` | Exactly two objectives |
6868
| `MOA.DominguezRios()` | Discrete variables only |
6969
| `MOA.EpsilonConstraint()` | Exactly two objectives |
70+
| `MOA.GeneralDichotomy()` | Any |
7071
| `MOA.Hierarchical()` | Any |
7172
| `MOA.KirlikSayin()` | Discrete variables only |
7273
| `MOA.Lexicographic()` [default] | Any |

ext/MultiObjectiveAlgorithmsPolyhedraExt.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import MathOptInterface as MOI
99
import MultiObjectiveAlgorithms as MOA
1010
import Polyhedra
1111

12+
include("Polyhedra/GeneralDichotomy.jl")
1213
include("Polyhedra/Sandwiching.jl")
1314

1415
end # module MultiObjectiveAlgorithmsPolyhedraExt

ext/Polyhedra/GeneralDichotomy.jl

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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+
mutable struct Weight
7+
w::Vector{Float64} # weight vector
8+
z::Float64 # value of the weighted objective
9+
adj_bnd::Vector{Int} # weight to boundaries adjacency
10+
adj_sol::Vector{Int} # weight to solution adjacency
11+
tested::Bool # have the weights been tested?
12+
removed::Bool # weights that are no longer part of the decomposition
13+
end
14+
15+
struct CustomVec
16+
value::Vector{Float64}
17+
value_int::Vector{Int}
18+
19+
function CustomVec(vec::Vector{Float64}, scaling::Real)
20+
return new(vec, round.(Int, scaling .* vec))
21+
end
22+
end
23+
24+
Base.:(==)(a::CustomVec, b::CustomVec) = a.value_int == b.value_int
25+
26+
Base.hash(a::CustomVec, h::UInt64) = hash(a.value_int, h)
27+
28+
function MOA.minimize_multiobjective!(
29+
alg::MOA.GeneralDichotomy,
30+
model::MOA.Optimizer,
31+
)
32+
n_obj = MOI.output_dimension(model.f)
33+
wnorm = 100.0
34+
start_time = time()
35+
weights = Weight[]
36+
# Initial extreme weights.
37+
for i in 1:n_obj
38+
w = zeros(Float64, n_obj)
39+
w[i] = wnorm
40+
adj_bnd = Int[-j for j in 1:n_obj if j != i]
41+
push!(weights, Weight(w, NaN, adj_bnd, [1], false, false))
42+
end
43+
status, solution = MOA._solve_weighted_sum(model, alg, weights[1].w)
44+
if !MOA._is_scalar_status_optimal(status)
45+
# Return immediately if no solution nor unbounded.
46+
return status, nothing
47+
end
48+
solutions = MOA.SolutionPoint[solution]
49+
# Weight update for the new solution
50+
for weight in weights
51+
weight.z = sum(weight.w .* solutions[1].y)
52+
end
53+
weights[1].tested = true
54+
# Prevent solution duplicates.
55+
# existing_sol maps a CustomVec of the solution to the solution index.
56+
existing_sol = Dict(CustomVec(solution.y, alg.scaling) => 1)
57+
n_removed = 0
58+
solution_limit = MOI.get(alg, MOA.SolutionLimit())
59+
iteration = 0
60+
while !(iteration >= alg.max_iter > 0) && length(solutions) < solution_limit
61+
iteration += 1
62+
# Look for a new solution by testing the extreme weights.
63+
found, new_sol_ind, wind, target_weight = false, 0, 1, 0
64+
while wind <= length(weights) && !found
65+
if weights[wind].tested || weights[wind].removed
66+
wind += 1
67+
continue
68+
end
69+
status, sol = MOA._solve_weighted_sum(model, alg, weights[wind].w)
70+
# TODO(odow): what if this solve fails?
71+
weights[wind].tested = true
72+
sol_z = sum(sol.y .* weights[wind].w)
73+
if !haskey(existing_sol, CustomVec(sol.y, alg.scaling))
74+
push!(solutions, sol)
75+
# Prepare new weight index set for the new solution's adjacency.
76+
new_sol_ind = length(solutions)
77+
existing_sol[CustomVec(sol.y, alg.scaling)] = new_sol_ind
78+
if sol_z < weights[wind].z
79+
# Triggers weight set decomp. update.
80+
found = true
81+
target_weight = wind
82+
end
83+
end
84+
wind += 1
85+
end
86+
if !found
87+
break # Terminate the search when no solution can be found.
88+
end
89+
polytope_sol = Set{Int}()
90+
equal_weights = Dict{CustomVec,Int}()
91+
for (wind, weight) in enumerate(weights)
92+
sol_z = sum(solutions[new_sol_ind].y .* weight.w)
93+
if sol_z < weight.z - alg.epsilon
94+
# Improved weighted value.
95+
if length(weight.adj_bnd) < n_obj
96+
weight.removed = true
97+
n_removed += 1
98+
else
99+
weight.adj_sol = Int[new_sol_ind]
100+
weight.z = sol_z
101+
end
102+
union!(polytope_sol, weight.adj_sol)
103+
elseif sol_z <= weight.z + alg.epsilon
104+
# Equal weighted value.
105+
push!(weight.adj_sol, new_sol_ind)
106+
union!(polytope_sol, weight.adj_sol)
107+
equal_weights[CustomVec(weight.w, alg.scaling)] = wind
108+
end
109+
end
110+
# Construction of the weight polytope for the new solution.
111+
h = Polyhedra.HyperPlane(ones(n_obj), wnorm)
112+
for i in 1:n_obj
113+
vec = zeros(n_obj)
114+
vec[i] = -1
115+
h = intersect(h, Polyhedra.HalfSpace(vec, 0))
116+
end
117+
polytope_sol_vec = collect(polytope_sol)
118+
for other_sol_ind in polytope_sol_vec
119+
vec = solutions[new_sol_ind].y - solutions[other_sol_ind].y
120+
h = intersect(h, Polyhedra.HalfSpace(vec, 0))
121+
end
122+
poly = Polyhedra.polyhedron(h)
123+
# Update of the extreme weights from the new polytope vertices.
124+
for idx in eachindex(Polyhedra.points(poly))
125+
w = get(poly, idx)
126+
weight_ind = get(equal_weights, CustomVec(w, alg.scaling), nothing)
127+
if weight_ind !== nothing
128+
# Update an existing extreme weight.
129+
weights[weight_ind].z = sum(w .* solutions[new_sol_ind].y)
130+
weights[weight_ind].tested = true
131+
weights[weight_ind].adj_sol = Int[new_sol_ind]
132+
if length(weights[weight_ind].adj_bnd) < n_obj-1
133+
if !weights[weight_ind].removed
134+
weights[weight_ind].removed = true
135+
n_removed += 1
136+
end
137+
else
138+
weights[weight_ind].removed = false
139+
end
140+
else
141+
# Insert a new extreme weight.
142+
incidence = Polyhedra.incidenthalfspaceindices(poly, idx)
143+
new_weight = Weight(
144+
w,
145+
sum(w .* solutions[new_sol_ind].y),
146+
Int[-elt.value for elt in incidence if elt.value <= n_obj],
147+
Int[
148+
polytope_sol_vec[elt.value-n_obj] for
149+
elt in incidence if elt.value > n_obj
150+
],
151+
false,
152+
false,
153+
)
154+
push!(new_weight.adj_sol, new_sol_ind)
155+
push!(weights, new_weight)
156+
end
157+
end
158+
if 3*n_removed >= length(weights)
159+
filter!(w -> !w.removed, weights)
160+
n_removed = 0
161+
end
162+
end
163+
return status, solutions
164+
end

0 commit comments

Comments
 (0)