Skip to content

Commit 12f04e5

Browse files
committed
Refactor
1 parent b87d690 commit 12f04e5

File tree

6 files changed

+181
-165
lines changed

6 files changed

+181
-165
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ jobs:
2626
- version: '1' # The latest point-release (Windows)
2727
os: windows-latest
2828
arch: x64
29-
- version: '1.6' # 1.6 LTS (64-bit Linux)
29+
- version: '1.10' # 1.10 LTS (64-bit Linux)
3030
os: ubuntu-latest
3131
arch: x64
32-
- version: '1.6' # 1.6 LTS (32-bit Linux)
32+
- version: '1.10' # 1.10 LTS (32-bit Linux)
3333
os: ubuntu-latest
3434
arch: x86
3535
steps:

Project.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@ MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
1111
Polyhedra = "67491407-f73d-577b-9b50-8179a7c68029"
1212

1313
[extensions]
14-
PolyhedraExt = "Polyhedra"
14+
MultiObjectiveAlgorithmsPolyhedraExt = "Polyhedra"
1515

1616
[compat]
1717
Combinatorics = "1"
1818
HiGHS = "1"
1919
Ipopt = "1"
2020
JSON = "0.21"
2121
MathOptInterface = "1.19"
22-
Test = "<0.0.1, 1.6"
23-
julia = "1.6"
22+
Polyhedra = "0.8"
23+
Test = "1"
24+
julia = "1.10"
2425

2526
[extras]
2627
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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 _compute_anchors(model::MOA.Optimizer)
19+
anchors = Dict{Vector{Float64},Dict{MOI.VariableIndex,Float64}}()
20+
n = MOI.output_dimension(model.f)
21+
scalars = MOI.Utilities.scalarize(model.f)
22+
variables = MOI.get(model.inner, MOI.ListOfVariableIndices())
23+
yI, yUB = zeros(n), zeros(n)
24+
for (i, f_i) in enumerate(scalars)
25+
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f_i)}(), f_i)
26+
MOI.optimize!(model.inner)
27+
# status check
28+
X, Y = MOA._compute_point(model, variables, model.f)
29+
model.ideal_point[i] = Y[i]
30+
yI[i] = Y[i]
31+
anchors[Y] = X
32+
MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MAX_SENSE)
33+
MOI.optimize!(model.inner)
34+
# status check
35+
_, Y = MOA._compute_point(model, variables, f_i)
36+
yUB[i] = Y
37+
MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE)
38+
end
39+
40+
return yI, yUB, anchors
41+
end
42+
43+
function _distance(w̄, b̄, OPS, model)
44+
n = MOI.output_dimension(model.f)
45+
optimizer = typeof(model.inner.optimizer)
46+
δ_optimizer = optimizer()
47+
MOI.set(δ_optimizer, MOI.Silent(), true)
48+
x = MOI.add_variables(δ_optimizer, n)
49+
for (w, b) in OPS
50+
MOI.add_constraint(
51+
δ_optimizer,
52+
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(w, x), 0.0),
53+
MOI.GreaterThan(b),
54+
)
55+
end
56+
MOI.set(
57+
δ_optimizer,
58+
MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
59+
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(w̄, x), 0.0),
60+
)
61+
MOI.set(δ_optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE)
62+
MOI.optimize!(δ_optimizer)
63+
return- MOI.get(δ_optimizer, MOI.ObjectiveValue())
64+
end
65+
66+
function _select_next_halfspace(H, OPS, model)
67+
distances = [_distance(w, b, OPS, model) for (w, b) in H]
68+
@info "Distances: $(Dict(zip(H, distances)))"
69+
index = argmax(distances)
70+
w, b = H[index]
71+
return distances[index], w, b
72+
end
73+
74+
function MOA.minimize_multiobjective!(
75+
algorithm::MOA.Sandwiching,
76+
model::MOA.Optimizer,
77+
)
78+
@assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE
79+
ε = algorithm.precision
80+
start_time = time()
81+
solutions = Dict{Vector{Float64},Dict{MOI.VariableIndex,Float64}}()
82+
variables = MOI.get(model.inner, MOI.ListOfVariableIndices())
83+
n = MOI.output_dimension(model.f)
84+
scalars = MOI.Utilities.scalarize(model.f)
85+
yI, yUB, anchors = _compute_anchors(model)
86+
merge!(solutions, anchors)
87+
@info "yI: $(yI)"
88+
@info "yUB: $(yUB)"
89+
IPS = [yUB, keys(anchors)...]
90+
OPS = Tuple{Vector{Float64},Float64}[]
91+
for i in 1:n
92+
e_i = Float64.(1:n .== i)
93+
push!(OPS, (e_i, yI[i])) # e_i' * y >= yI_i
94+
push!(OPS, (-e_i, -yUB[i])) # -e_i' * y >= -yUB_i ⟹ e_i' * y <= yUB_i
95+
end
96+
@info "IPS: $(IPS)"
97+
@info "OPS: $(OPS)"
98+
u = MOI.add_variables(model.inner, n)
99+
u_constraints = [ # u_i >= 0 for all i = 1:n
100+
MOI.add_constraint(model.inner, u_i, MOI.GreaterThan{Float64}(0))
101+
for u_i in u
102+
]
103+
f_constraints = [ # f_i + u_i <= yUB_i for all i = 1:n
104+
MOI.Utilities.normalize_and_add_constraint(
105+
model.inner,
106+
scalars[i] + u[i],
107+
MOI.LessThan(yUB[i]),
108+
) for i in 1:n
109+
]
110+
H = _halfspaces(IPS)
111+
count = 0
112+
while !isempty(H)
113+
count += 1
114+
@info "-- Iteration #$(count) --"
115+
@info "HalfSpaces: $(H)"
116+
δ, w, b = _select_next_halfspace(H, OPS, model)
117+
@info "Selected halfspace: w: $(w), b: $(b)"
118+
@info "δ: $(δ)"
119+
if δ - 1e-3 <= ε # added some convergence tolerance
120+
break
121+
end
122+
# would not terminate when precision is set to 0
123+
new_f = sum(w[i] * (scalars[i] + u[i]) for i in 1:n) # w' * (f(x) + u)
124+
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(new_f)}(), new_f)
125+
MOI.optimize!(model.inner)
126+
β̄ = MOI.get(model.inner, MOI.ObjectiveValue())
127+
@info "β̄: $(β̄)"
128+
X, Y = MOA._compute_point(model, variables, model.f)
129+
@info "Y: $(Y)"
130+
solutions[Y] = X
131+
push!(OPS, (w, β̄))
132+
@info "Added halfspace w: $(w), b: $(β̄) to OPS"
133+
IPS = push!(IPS, Y)
134+
@info "IPS: $(IPS)"
135+
@info "OPS: $(OPS)"
136+
H = _halfspaces(IPS)
137+
if count == 10
138+
break
139+
end
140+
end
141+
MOI.delete.(model.inner, f_constraints)
142+
MOI.delete.(model.inner, u_constraints)
143+
MOI.delete.(model.inner, u)
144+
return MOI.OPTIMAL, [MOA.SolutionPoint(X, Y) for (Y, X) in solutions]
145+
end
146+
147+
end # module MultiObjectiveAlgorithmsPolyhedraExt

ext/PolyhedraExt.jl

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/algorithms/Sandwiching.jl

Lines changed: 17 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,23 @@
1-
function _halfspaces(IPS)
2-
return error("MOA.Sandwiching requires Polyhedra.jl to be loaded")
3-
end
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/.
45

5-
mutable struct Sandwiching <: AbstractAlgorithm
6-
precision::Float64
7-
end
6+
"""
7+
Sandwiching(precision::Float64)
88
9-
tol = 1e-3
9+
An algorithm that implemennts the paper described in XXX.
1010
11-
function _compute_anchors(model::Optimizer)
12-
anchors = Dict{Vector{Float64},Dict{MOI.VariableIndex,Float64}}()
13-
n = MOI.output_dimension(model.f)
14-
scalars = MOI.Utilities.scalarize(model.f)
15-
variables = MOI.get(model.inner, MOI.ListOfVariableIndices())
16-
yI, yUB = zeros(n), zeros(n)
17-
for (i, f_i) in enumerate(scalars)
18-
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f_i)}(), f_i) # ρ * sum(f_j for (j, f_j) in enumerate(model.f) if j != i)
19-
MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE)
20-
MOI.optimize!(model.inner)
21-
# status check
22-
X, Y = _compute_point(model, variables, model.f)
23-
model.ideal_point[i] = Y[i]
24-
yI[i] = Y[i]
25-
anchors[Y] = X
26-
MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MAX_SENSE)
27-
MOI.optimize!(model.inner)
28-
# status check
29-
_, Y = _compute_point(model, variables, f_i)
30-
yUB[i] = Y
31-
end
32-
MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE)
33-
return yI, yUB, anchors
34-
end
35-
36-
@enum DistanceMeasure SUB CUR SOL
11+
## Compat
3712
38-
function _distance(w̄, b̄, OPS, model)
39-
n = MOI.output_dimension(model.f)
40-
optimizer = typeof(model.inner.optimizer)
41-
δ_optimizer = optimizer()
42-
MOI.set(δ_optimizer, MOI.Silent(), true)
43-
x = MOI.add_variables(δ_optimizer, n)
44-
for (w, b) in OPS
45-
MOI.add_constraint(
46-
δ_optimizer,
47-
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(w, x), 0.0),
48-
MOI.GreaterThan(b),
49-
)
50-
end
51-
MOI.set(
52-
δ_optimizer,
53-
MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
54-
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(w̄, x), 0.0),
55-
)
56-
MOI.set(δ_optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE)
57-
MOI.optimize!(δ_optimizer)
58-
δ =- MOI.get(δ_optimizer, MOI.ObjectiveValue())
59-
return δ
60-
end
13+
To use this algorithm you MUST first load the Polyhedra.jl Julia package:
6114
62-
function _select_next_halfspace(H, OPS, model)
63-
distances = [_distance(w, b, OPS, model) for (w, b) in H]
64-
@info "Distances: $(Dict(zip(H, distances)))"
65-
index = argmax(distances)
66-
w, b = H[index]
67-
return distances[index], w, b
68-
end
69-
70-
function optimize_multiobjective!(algorithm::Sandwiching, model::Optimizer)
71-
@assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE
72-
ε = algorithm.precision
73-
start_time = time()
74-
solutions = Dict{Vector{Float64},Dict{MOI.VariableIndex,Float64}}()
75-
variables = MOI.get(model.inner, MOI.ListOfVariableIndices())
76-
n = MOI.output_dimension(model.f)
77-
scalars = MOI.Utilities.scalarize(model.f)
78-
yI, yUB, anchors = _compute_anchors(model)
79-
merge!(solutions, anchors)
80-
@info "yI: $(yI)"
81-
@info "yUB: $(yUB)"
82-
IPS = [yUB, keys(anchors)...]
83-
OPS = Tuple{Vector{Float64},Float64}[]
84-
for i in 1:n
85-
e_i = Float64.(1:n .== i)
86-
push!(OPS, (e_i, yI[i])) # e_i' * y >= yI_i
87-
push!(OPS, (-e_i, -yUB[i])) # -e_i' * y >= -yUB_i ⟹ e_i' * y <= yUB_i
88-
end
89-
@info "IPS: $(IPS)"
90-
@info "OPS: $(OPS)"
91-
u = MOI.add_variables(model.inner, n)
92-
u_constraints = [ # u_i >= 0 for all i = 1:n
93-
MOI.add_constraint(model.inner, u_i, MOI.GreaterThan{Float64}(0))
94-
for u_i in u
95-
]
96-
f_constraints = [ # f_i + u_i <= yUB_i for all i = 1:n
97-
MOI.Utilities.normalize_and_add_constraint(
98-
model.inner,
99-
scalars[i] + u[i],
100-
MOI.LessThan(yUB[i]),
101-
) for i in 1:n
102-
]
103-
H = _halfspaces(IPS)
104-
105-
count = 0
106-
107-
while !isempty(H)
108-
count += 1
109-
@info "-- Iteration #$(count) --"
110-
@info "HalfSpaces: $(H)"
111-
δ, w, b = _select_next_halfspace(H, OPS, model)
112-
@info "Selected halfspace: w: $(w), b: $(b)"
113-
@info "δ: $(δ)"
114-
if δ - tol > ε # added some convergence tolerance
115-
# would not terminate when precision is set to 0
116-
new_f = sum(w[i] * (scalars[i] + u[i]) for i in 1:n) # w' * (f(x) + u)
117-
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(new_f)}(), new_f)
118-
MOI.optimize!(model.inner)
119-
β̄ = MOI.get(model.inner, MOI.ObjectiveValue())
120-
@info "β̄: $(β̄)"
121-
X, Y = _compute_point(model, variables, model.f)
122-
@info "Y: $(Y)"
123-
solutions[Y] = X
124-
push!(OPS, (w, β̄))
125-
@info "Added halfspace w: $(w), b: $(β̄) to OPS"
126-
IPS = push!(IPS, Y)
127-
else
128-
break
129-
end
130-
@info "IPS: $(IPS)"
131-
@info "OPS: $(OPS)"
132-
H = _halfspaces(IPS)
133-
if count == 10
134-
break
135-
end
136-
end
137-
MOI.delete.(model.inner, f_constraints)
138-
MOI.delete.(model.inner, u_constraints)
139-
MOI.delete.(model.inner, u)
140-
return MOI.OPTIMAL, [SolutionPoint(X, Y) for (Y, X) in solutions]
15+
```julia
16+
import MultiObjectiveAlgorithms as MOA
17+
import Polyhedra
18+
algorithm = MOA.Sandwiching(0.0)
19+
```
20+
"""
21+
mutable struct Sandwiching <: AbstractAlgorithm
22+
precision::Float64
14123
end

0 commit comments

Comments
 (0)