@@ -12,86 +12,81 @@ mutable struct Weight
1212 removed:: Bool # weights that are no longer part of the decomposition
1313end
1414
15- struct CustomVec
15+ struct _ApproxVector
1616 value:: Vector{Float64}
1717 value_int:: Vector{Int}
1818
19- function CustomVec (vec :: Vector{Float64} , scaling :: Real )
20- return new (vec , round .(Int, scaling .* vec ))
19+ function _ApproxVector (x :: Vector{Float64} ; atol :: Float64 = 1e-6 )
20+ return new (x , round .(Int, x ./ atol ))
2121 end
2222end
2323
24- Base.:(== )(a:: CustomVec , b:: CustomVec ) = a. value_int == b. value_int
24+ Base.:(== )(a:: _ApproxVector , b:: _ApproxVector ) = a. value_int == b. value_int
2525
26- Base. hash (a:: CustomVec , h:: UInt64 ) = hash (a. value_int, h)
26+ Base. hash (a:: _ApproxVector , h:: UInt64 ) = hash (a. value_int, h)
2727
2828function MOA. minimize_multiobjective! (
2929 alg:: MOA.GeneralDichotomy ,
3030 model:: MOA.Optimizer ,
3131)
32+ # Some constants. These could be converted into algorithm options.
33+ # - atol: the absolute tolerance used to compare solutions in objective space
34+ # - wnorm: ???
35+ atol, wnorm = 1e-6 , 1e2
36+ # Storage we need for the algorithm.
37+ weights, solutions = Weight[], MOA. SolutionPoint[]
3238 n_obj = MOI. output_dimension (model. f)
33- wnorm = 100.0
34- start_time = time ()
35- weights = Weight[]
36- # Initial extreme weights.
39+ # First, minimize the first objective to obtain a primal feasible point.
40+ w = zeros (Float64, n_obj)
41+ w[1 ] = 1.0
42+ status, solution = MOA. _solve_weighted_sum (model, alg, w)
43+ if solution === nothing
44+ return status, nothing
45+ end
46+ push! (solutions, solution)
47+ # Initialize the weights. There is one weight vector for each objective, and
48+ # the weight is set to wnorm for each objective. We use the current solution
49+ # obtained by minimizing the 1st objective as the reference.
3750 for i in 1 : n_obj
3851 w = zeros (Float64, n_obj)
3952 w[i] = wnorm
53+ z = w' * solution. y
4054 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
55+ push! (weights, Weight (w, z, adj_bnd, [1 ], i == 1 , false ))
4756 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+ # Prevent solution duplicates: existing_sol maps an _ApproxVector of the
58+ # solution to the index in `solutions::Vector{MOA.SolutionPoint}`.
59+ existing_sol = Dict (_ApproxVector (solution. y) => 1 )
5760 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
61+ while length (solutions) < MOI. get (alg, MOA. SolutionLimit ())
6262 # 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
63+ improving_solution = false
64+ for (i, weight) in enumerate (weights)
65+ if weight. tested || weight. removed
6766 continue
6867 end
69- status, sol = MOA. _solve_weighted_sum (model, alg, weights[wind] . w)
68+ status, sol = MOA. _solve_weighted_sum (model, alg, weight . w)
7069 # 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))
70+ weight. tested = true
71+ if ! haskey (existing_sol, _ApproxVector (sol. y))
7472 push! (solutions, sol)
7573 # 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
74+ existing_sol[_ApproxVector (sol. y)] = length (solutions)
75+ if weight. w' * sol. y < weight. z
76+ improving_solution = true
77+ break
8278 end
8379 end
84- wind += 1
8580 end
86- if ! found
87- break # Terminate the search when no solution can be found.
81+ if ! improving_solution
82+ break # Terminate the search when no new solution can be found.
8883 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 .
84+ new_sol, new_sol_ind = last (solutions), length (solutions )
85+ polytope_sol, equal_weights = Set {Int} (), Dict {_ApproxVector ,Int} ()
86+ for (i , weight) in enumerate (weights)
87+ sol_z = weight . w ' * new_sol . y
88+ if sol_z < weight. z - atol
89+ # The new solution is strictly better than the previous .
9590 if length (weight. adj_bnd) < n_obj
9691 weight. removed = true
9792 n_removed += 1
@@ -100,62 +95,61 @@ function MOA.minimize_multiobjective!(
10095 weight. z = sol_z
10196 end
10297 union! (polytope_sol, weight. adj_sol)
103- elseif sol_z <= weight. z + alg . epsilon
104- # Equal weighted value.
98+ elseif sol_z <= weight. z + atol
99+ # The new solution is equal in value to the previous .
105100 push! (weight. adj_sol, new_sol_ind)
106101 union! (polytope_sol, weight. adj_sol)
107- equal_weights[CustomVec (weight. w, alg . scaling )] = wind
102+ equal_weights[_ApproxVector (weight. w)] = i
108103 end
109104 end
110105 # Construction of the weight polytope for the new solution.
111106 h = Polyhedra. HyperPlane (ones (n_obj), wnorm)
112107 for i in 1 : n_obj
113- vec = zeros (n_obj)
114- vec[i] = - 1
108+ vec = zeros (Float64, n_obj)
109+ vec[i] = - 1.0
115110 h = intersect (h, Polyhedra. HalfSpace (vec, 0 ))
116111 end
112+ # Convert the set of polytope solutions into a vector. It's important
113+ # that iteration is ordered because we're going to rely on this later.
117114 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 ))
115+ for i in polytope_sol_vec
116+ h = intersect (h, Polyhedra. HalfSpace (new_sol. y - solutions[i]. y, 0 ))
121117 end
122118 poly = Polyhedra. polyhedron (h)
123119 # Update of the extreme weights from the new polytope vertices.
124120 for idx in eachindex (Polyhedra. points (poly))
125121 w = get (poly, idx)
126- weight_ind = get (equal_weights, CustomVec (w, alg . scaling), nothing )
127- if weight_ind != = nothing
122+ z = w ' * new_sol . y
123+ if (i = get (equal_weights, _ApproxVector (w), nothing )) != = nothing
128124 # 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
125+ weights[i ]. z = z
126+ weights[i ]. tested = true
127+ weights[i ]. adj_sol = Int[new_sol_ind]
128+ if length (weights[i ]. adj_bnd) < n_obj - 1
129+ if ! weights[i ]. removed
130+ weights[i ]. removed = true
135131 n_removed += 1
136132 end
137133 else
138- weights[weight_ind ]. removed = false
134+ weights[i ]. removed = false
139135 end
140136 else
141137 # 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)
138+ adj_bnd, adj_sol = Int[], Int[]
139+ for elt in Polyhedra. incidenthalfspaceindices (poly, idx)
140+ if elt. value <= n_obj
141+ push! (adj_bnd, - elt. value)
142+ else
143+ push! (adj_sol, polytope_sol_vec[elt. value- n_obj])
144+ end
145+ end
146+ push! (adj_sol, new_sol_ind)
147+ push! (weights, Weight (w, z, adj_bnd, adj_sol, false , false ))
156148 end
157149 end
158- if 3 * n_removed >= length (weights)
150+ # This is a heuristic: filter the weights if approximately 1/3 of them
151+ # have been removed.
152+ if n_removed >= length (weights) / 3
159153 filter! (w -> ! w. removed, weights)
160154 n_removed = 0
161155 end
0 commit comments