Skip to content

Commit 23ae157

Browse files
authored
Add Sandwiching (#130)
1 parent e5ebe9b commit 23ae157

File tree

6 files changed

+369
-5
lines changed

6 files changed

+369
-5
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: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,28 @@ version = "1.4.3"
77
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
88
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
99

10+
[weakdeps]
11+
Polyhedra = "67491407-f73d-577b-9b50-8179a7c68029"
12+
13+
[extensions]
14+
MultiObjectiveAlgorithmsPolyhedraExt = "Polyhedra"
15+
1016
[compat]
1117
Combinatorics = "1"
1218
HiGHS = "1"
1319
Ipopt = "1"
1420
JSON = "0.21"
1521
MathOptInterface = "1.19"
16-
Test = "<0.0.1, 1.6"
17-
julia = "1.6"
22+
Polyhedra = "0.8"
23+
Test = "1"
24+
julia = "1.10"
1825

1926
[extras]
2027
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
2128
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
2229
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
2330
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
31+
Polyhedra = "67491407-f73d-577b-9b50-8179a7c68029"
2432

2533
[targets]
26-
test = ["HiGHS", "Ipopt", "JSON", "Test"]
34+
test = ["HiGHS", "Ipopt", "JSON", "Test", "Polyhedra"]

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ The value must be one of the algorithms supported by MOA:
6363
* `MOA.KirlikSayin()`
6464
* `MOA.Lexicographic()` [default]
6565
* `MOA.RandomWeighting()`
66+
* `MOA.Sandwiching()`
6667
* `MOA.TambyVanderpooten()`
6768

6869
Consult their docstrings for details.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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- 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

src/algorithms/Sandwiching.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
"""
7+
Sandwiching(precision::Float64)
8+
9+
An algorithm that implemennts the paper described in Koenen, M., Balvert, M., & Fleuren, H. A. (2023). A Renewed Take on Weighted Sum in Sandwich Algorithms: Modification of the Criterion Space. (Center Discussion Paper; Vol. 2023-012). CentER, Center for Economic Research.
10+
11+
## Compat
12+
13+
To use this algorithm you MUST first load the Polyhedra.jl Julia package:
14+
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
23+
end

0 commit comments

Comments
 (0)