@@ -32,6 +32,7 @@ This algorithm is restricted to problems with:
3232"""
3333struct TambyVanderpooten <: AbstractAlgorithm end
3434
35+ # This is Algorithm 1 from the paper.
3536function _update_search_region (
3637 U_N:: Dict {Vector{Float64},Vector{Vector{Vector{Float64}}}},
3738 y:: Vector{Float64} ,
@@ -40,23 +41,23 @@ function _update_search_region(
4041 p = length (y)
4142 bounds_to_remove = Vector{Float64}[]
4243 bounds_to_add = Dict{Vector{Float64},Vector{Vector{Vector{Float64}}}}()
43- for u in keys ( U_N)
44- if all (y .< u)
44+ for (u, u_n) in U_N
45+ if _is_less (y, u, 0 ) # k=0 here because we want to check every element
4546 push! (bounds_to_remove, u)
4647 for l in 1 : p
4748 u_l = _get_child (u, y, l)
4849 N = [
49- k == l ? [y] : [yi for yi in U_N[u][ k] if yi[l] < y[l]]
50- for k in 1 : p
50+ k == l ? [y] : [yi for yi in u_n[ k] if yi[l] < y[l]] for
51+ k in 1 : p
5152 ]
5253 if all (! isempty (N[k]) for k in 1 : p if k != l && u_l[k] != yN[k])
5354 bounds_to_add[u_l] = N
5455 end
5556 end
5657 else
5758 for k in 1 : p
58- if (y[k] == u[k]) && all ( _project (y, k) .< _project ( u, k) )
59- push! (U_N[u] [k], y)
59+ if _is_less (y, u, k)
60+ push! (u_n [k], y)
6061 end
6162 end
6263 end
@@ -68,156 +69,227 @@ function _update_search_region(
6869 return
6970end
7071
72+ function _is_less (y, u, k)
73+ for i in 1 : length (y)
74+ if i == k
75+ if ! (y[i] == u[i])
76+ return false
77+ end
78+ else
79+ if ! (y[i] < u[i])
80+ return false
81+ end
82+ end
83+ end
84+ return true
85+ end
86+
7187function _get_child (u:: Vector{Float64} , y:: Vector{Float64} , k:: Int )
7288 @assert length (u) == length (y)
73- return vcat (u[1 : (k- 1 )], y[k], u[(k+ 1 ): length (y)])
89+ child = copy (u)
90+ child[k] = y[k]
91+ return child
92+ end
93+
94+ # This is function h(u, k) in the paper.
95+ function _h (k:: Int , u:: Vector{Float64} , yI:: Vector{Float64} )
96+ h = 1.0
97+ for (i, (u_i, yI_i)) in enumerate (zip (u, yI))
98+ if i != k
99+ h *= u_i - yI_i
100+ end
101+ end
102+ return h
74103end
75104
105+ # This is Problem (6) in the paper. The upper bound M is our nadir point yN.
76106function _select_search_zone (
77107 U_N:: Dict {Vector{Float64},Vector{Vector{Vector{Float64}}}},
78108 yI:: Vector{Float64} ,
79109 yN:: Vector{Float64} ,
80110)
81- upper_bounds = collect (keys (U_N))
82- p = length (yI)
83- hvs = [
84- u[k] == yN[k] ? 0.0 : prod (_project (u, k) .- _project (yI, k)) for
85- k in 1 : p, u in upper_bounds
86- ]
87- k_star, j_star = argmax (hvs). I
88- return k_star, upper_bounds[j_star]
111+ k_star, u_star, v_star = 1 , yN, - Inf
112+ for k in 1 : length (yI), u in keys (U_N)
113+ if ! isapprox (u[k], yN[k]; atol = 1e-6 )
114+ v = _h (k, u, yI)
115+ if v > v_star
116+ k_star, u_star, v_star = k, u, v
117+ end
118+ end
119+ end
120+ return k_star, u_star
89121end
90122
91123function minimize_multiobjective! (
92124 algorithm:: TambyVanderpooten ,
93125 model:: Optimizer ,
94126)
95- @assert MOI. get (model. inner, MOI. ObjectiveSense ()) == MOI. MIN_SENSE
96- warm_start_supported = false
97- if MOI. supports (model, MOI. VariablePrimalStart (), MOI. VariableIndex)
98- warm_start_supported = true
99- end
100127 solutions = Dict {Vector{Float64},Dict{MOI.VariableIndex,Float64}} ()
101- variables = MOI. get (model. inner, MOI. ListOfVariableIndices ())
102- n = MOI. output_dimension (model. f)
128+ status = _minimize_multiobjective! (
129+ algorithm,
130+ model,
131+ model. inner,
132+ model. f,
133+ solutions,
134+ ):: MOI.TerminationStatusCode
135+ return status, SolutionPoint[SolutionPoint (X, Y) for (Y, X) in solutions]
136+ end
137+
138+ # To ensure the type stability of `.inner` and `.f`, we use a function barrier.
139+ #
140+ # We also pass in `solutions` to simplify the function and to ensure that we can
141+ # return at any point from the this inner function with a partial list of primal
142+ # solutions.
143+ function _minimize_multiobjective! (
144+ algorithm:: TambyVanderpooten ,
145+ model:: Optimizer ,
146+ inner:: MOI.ModelLike ,
147+ f:: MOI.AbstractVectorFunction ,
148+ solutions:: Dict{Vector{Float64},Dict{MOI.VariableIndex,Float64}} ,
149+ )
150+ @assert MOI. get (inner, MOI. ObjectiveSense ()) == MOI. MIN_SENSE
151+ warm_start_supported =
152+ MOI. supports (inner, MOI. VariablePrimalStart (), MOI. VariableIndex)
153+ variables = MOI. get (inner, MOI. ListOfVariableIndices ())
154+ n = MOI. output_dimension (f)
155+ # Compute the ideal (yI) and nadir (yN) points
103156 yI, yN = zeros (n), zeros (n)
104- scalars = MOI. Utilities. scalarize (model . f)
157+ scalars = MOI. Utilities. scalarize (f)
105158 for (i, f_i) in enumerate (scalars)
106- MOI. set (model . inner, MOI. ObjectiveFunction {typeof(f_i)} (), f_i)
107- MOI. set (model . inner, MOI. ObjectiveSense (), MOI. MIN_SENSE)
159+ MOI. set (inner, MOI. ObjectiveFunction {typeof(f_i)} (), f_i)
160+ MOI. set (inner, MOI. ObjectiveSense (), MOI. MIN_SENSE)
108161 optimize_inner! (model)
109- status = MOI. get (model . inner, MOI. TerminationStatus ())
162+ status = MOI. get (inner, MOI. TerminationStatus ())
110163 if ! _is_scalar_status_optimal (status)
111- return status, nothing
164+ return status
112165 end
113- _, Y = _compute_point (model, variables, f_i)
166+ _, y_i = _compute_point (model, variables, f_i)
114167 _log_subproblem_solve (model, variables)
115- yI[i] = Y
116- model. ideal_point[i] = Y
117- MOI. set (model. inner, MOI. ObjectiveSense (), MOI. MAX_SENSE)
168+ model. ideal_point[i] = yI[i] = y_i
169+ MOI. set (inner, MOI. ObjectiveSense (), MOI. MAX_SENSE)
118170 optimize_inner! (model)
119- status = MOI. get (model . inner, MOI. TerminationStatus ())
171+ status = MOI. get (inner, MOI. TerminationStatus ())
120172 if ! _is_scalar_status_optimal (status)
121173 _warn_on_nonfinite_anti_ideal (algorithm, MOI. MIN_SENSE, i)
122- return status, nothing
174+ return status
123175 end
124- _, Y = _compute_point (model, variables, f_i)
176+ _, y_i = _compute_point (model, variables, f_i)
125177 _log_subproblem_solve (model, variables)
126- yN[i] = Y + 1
178+ # This is not really the nadir point, it's an upper bound on the
179+ # nadir point because we later add epsilon constraints with a -1.
180+ yN[i] = y_i + 1
127181 end
128- MOI. set (model. inner, MOI. ObjectiveSense (), MOI. MIN_SENSE)
129- U_N = Dict{Vector{Float64},Vector{Vector{Vector{Float64}}}}()
182+ MOI. set (inner, MOI. ObjectiveSense (), MOI. MIN_SENSE)
183+ # Instead of adding and deleting constraints during the algorithm, we add
184+ # them all here at the beginning.
185+ ε_constraints = [
186+ MOI. Utilities. normalize_and_add_constraint (
187+ inner,
188+ f_i,
189+ MOI. LessThan (yN[i]),
190+ ) for (i, f_i) in enumerate (scalars)
191+ ]
192+ obj_constants = MOI. constant .(scalars)
193+ U_N = Dict{Vector{Float64},Vector{Vector{Vector{Float64}}}}(
194+ # The nadir point, except for the ideal point in position k
195+ yN => [[_get_child (yN, yI, k)] for k in 1 : n],
196+ )
130197 V = [Tuple{Vector{Float64},Vector{Float64}}[] for k in 1 : n]
131- U_N[yN] = [[_get_child (yN, yI, k)] for k in 1 : n]
132198 status = MOI. OPTIMAL
199+ sum_f = MOI. Utilities. operate (+ , Float64, scalars... )
133200 while ! isempty (U_N)
134201 if (ret = _check_premature_termination (model)) != = nothing
135202 status = ret
136203 break
137204 end
138205 k, u = _select_search_zone (U_N, yI, yN)
139- MOI. set (
140- model. inner,
141- MOI. ObjectiveFunction {typeof(scalars[k])} (),
142- scalars[k],
143- )
144- ε_constraints = Any[]
145- for (i, f_i) in enumerate (scalars)
146- if i != k
147- ci = MOI. Utilities. normalize_and_add_constraint (
148- model. inner,
149- f_i,
150- MOI. LessThan {Float64} (u[i] - 1 ),
151- )
152- push! (ε_constraints, ci)
153- end
206+ # Solve problem Π¹(k, u)
207+ MOI. set (inner, MOI. ObjectiveFunction {typeof(scalars[k])} (), scalars[k])
208+ # Update the constraints y_i < u_i. Note that this is a strict
209+ # equality. We use an ε of 1.0. This is also why we use yN+1 when
210+ # computing the nadir point.
211+ for i in 1 : n
212+ u_i = ifelse (i == k, yN[i], u[i])
213+ set = MOI. LessThan (u_i - 1.0 - obj_constants[i])
214+ MOI. set (inner, MOI. ConstraintSet (), ε_constraints[i], set)
154215 end
155- if u[k] ≠ yN[k]
156- if warm_start_supported
157- variables_start = solutions[first (U_N[u][k])]
158- for x_i in variables
159- MOI. set (
160- model. inner,
161- MOI. VariablePrimalStart (),
162- x_i,
163- variables_start[x_i],
164- )
165- end
216+ if warm_start_supported && ! isempty (solutions)
217+ for (x_i, start_i) in solutions[last (U_N[u][k])]
218+ MOI. set (inner, MOI. VariablePrimalStart (), x_i, start_i)
166219 end
167220 end
168- optimize_inner! (model)
169- _log_subproblem_solve (model, " auxillary subproblem" )
170- status = MOI. get (model. inner, MOI. TerminationStatus ())
221+ optimize_inner! (model) # We don't log this first-stage subproblem.
222+ status = MOI. get (inner, MOI. TerminationStatus ())
171223 if ! _is_scalar_status_optimal (status)
172- MOI. delete .(model, ε_constraints)
173- return status, nothing
174- end
175- y_k = MOI. get (model. inner, MOI. ObjectiveValue ())
176- sum_f = sum (1.0 * s for s in scalars)
177- MOI. set (model. inner, MOI. ObjectiveFunction {typeof(sum_f)} (), sum_f)
178- y_k_constraint = MOI. Utilities. normalize_and_add_constraint (
179- model. inner,
180- scalars[k],
181- MOI. EqualTo (y_k),
182- )
224+ break
225+ end
226+ # Now solve problem Π²(k, u)
227+ y_k = MOI. get (inner, MOI. ObjectiveValue ()):: Float64
228+ set = MOI. LessThan (y_k - obj_constants[k])
229+ MOI. set (inner, MOI. ConstraintSet (), ε_constraints[k], set)
230+ MOI. set (inner, MOI. ObjectiveFunction {typeof(sum_f)} (), sum_f)
183231 optimize_inner! (model)
184- status = MOI. get (model . inner, MOI. TerminationStatus ())
232+ status = MOI. get (inner, MOI. TerminationStatus ())
185233 if ! _is_scalar_status_optimal (status)
186- MOI. delete .(model, ε_constraints)
187- MOI. delete (model, y_k_constraint)
188- return status, nothing
234+ break
189235 end
190- X, Y = _compute_point (model, variables, model . f)
236+ X, Y = _compute_point (model, variables, f)
191237 _log_subproblem_solve (model, Y)
192- MOI. delete .(model, ε_constraints)
193- MOI. delete (model, y_k_constraint)
194238 push! (V[k], (u, Y))
195- # We want `if !(Y in U_N[u][k])` but this tests exact equality. We want
196- # an approximate comparison.
239+ # We want `if !(Y in U_N[u][k])` but this tests exact equality. We
240+ # want an approximate comparison.
197241 if all (! isapprox (Y; atol = 1e-6 ), U_N[u][k])
198242 _update_search_region (U_N, Y, yN)
199243 solutions[Y] = X
200244 end
201- bounds_to_remove = Vector{Float64}[]
202- for u_i in keys (U_N)
203- for k in 1 : n
204- if isapprox (u_i[k], yI[k]; atol = 1e-6 )
205- push! (bounds_to_remove, u_i)
206- else
207- for (u_j, y_j) in V[k]
208- if all (_project (u_i, k) .<= _project (u_j, k)) &&
209- isapprox (y_j[k], u_i[k]; atol = 1e-6 )
210- push! (bounds_to_remove, u_i)
211- end
212- end
213- end
214- end
245+ _clean_search_region (U_N, yI, V, k)
246+ end
247+ MOI. delete .(model, ε_constraints)
248+ return status
249+ end
250+
251+ # This function is lines 10-17 of the paper. We re-order things a bit. The outer
252+ # loop over u′ is the same, but we break the inner `foreach k` loop up into
253+ # separate loops so that we don't need to loop over all `V` if one of the u′ has
254+ # reached the ideal point.
255+ function _clean_search_region (U_N, yI, V, k)
256+ map_keys = collect (enumerate (keys (U_N)))
257+ to_delete = fill (false , length (map_keys))
258+ Threads. @threads for (i, u′) in map_keys
259+ to_delete[i] = _clean_search_region_inner (u′, U_N, yI, V, k)
260+ end # COV_EXCL_LINE
261+ for (i, u′) in map_keys
262+ if to_delete[i]
263+ delete! (U_N, u′)
215264 end
216- if ! isempty (bounds_to_remove)
217- for bound_to_remove in bounds_to_remove
218- delete! (U_N, bound_to_remove)
265+ end
266+ return
267+ end
268+
269+ function _clean_search_region_inner (u′, U_N, yI, V, k)
270+ for (u′_k, yI_k) in zip (u′, yI)
271+ if isapprox (u′_k, yI_k; atol = 1e-6 )
272+ return true
273+ end
274+ end
275+ for (k, V_k) in enumerate (V)
276+ for (u, y_k) in V_k
277+ if _comparison_line_16 (u′, u, y_k, k)
278+ return true
219279 end
220280 end
221281 end
222- return status, [SolutionPoint (X, Y) for (Y, X) in solutions]
282+ return false
283+ end
284+
285+ function _comparison_line_16 (u′, u, y_k, k)
286+ if ! ≈ (y_k[k], u′[k]; atol = 1e-6 )
287+ return false
288+ end
289+ for (i, (u′_i, u_i)) in enumerate (zip (u′, u))
290+ if i != k && ! (u′_i <= u_i)
291+ return false
292+ end
293+ end
294+ return true
223295end
0 commit comments