1+ function _halfspaces (IPS:: Vector{Vector{Float64}} )
2+ error (" MOA.Sandwiching requires Polyhedra.jl to be loaded" )
3+ end
4+
5+ mutable struct Sandwiching <: AbstractAlgorithm
6+ precision:: Float64
7+ end
8+
9+ tol = 1e-3
10+
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
37+
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+ δ = b̄ - MOI. get (δ_optimizer, MOI. ObjectiveValue ())
59+ return δ
60+ end
61+
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 (
94+ model. inner,
95+ u_i,
96+ MOI. GreaterThan {Float64} (0 ),
97+ )
98+ for u_i in u
99+ ]
100+ f_constraints = [ # f_i + u_i <= yUB_i for all i = 1:n
101+ MOI. Utilities. normalize_and_add_constraint (
102+ model. inner,
103+ scalars[i] + u[i],
104+ MOI. LessThan (yUB[i]),
105+ ) for i in 1 : n
106+ ]
107+ H = _halfspaces (IPS)
108+
109+ count = 0
110+
111+ while ! isempty (H)
112+ count += 1
113+ @info " -- Iteration #$(count) --"
114+ @info " HalfSpaces: $(H) "
115+ δ, w, b = _select_next_halfspace (H, OPS, model)
116+ @info " Selected halfspace: w: $(w) , b: $(b) "
117+ @info " δ: $(δ) "
118+ if δ - tol > ε # added some convergence tolerance
119+ # would not terminate when precision is set to 0
120+ new_f = sum (w[i] * (scalars[i] + u[i]) for i in 1 : n) # w' * (f(x) + u)
121+ MOI. set (model. inner, MOI. ObjectiveFunction {typeof(new_f)} (), new_f)
122+ MOI. optimize! (model. inner)
123+ β̄ = MOI. get (model. inner, MOI. ObjectiveValue ())
124+ @info " β̄: $(β̄) "
125+ X, Y = _compute_point (model, variables, model. f)
126+ @info " Y: $(Y) "
127+ solutions[Y] = X
128+ push! (OPS, (w, β̄))
129+ @info " Added halfspace w: $(w) , b: $(β̄) to OPS"
130+ IPS = push! (IPS, Y)
131+ else
132+ break
133+ end
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, [SolutionPoint (X, Y) for (Y, X) in solutions]
145+ end
0 commit comments