|
23 | 23 | import time
|
24 | 24 | import re # For manipulating scenario names
|
25 | 25 | import random
|
| 26 | +import math |
26 | 27 |
|
27 | 28 | from mpisppy import MPI
|
28 | 29 | from mpisppy import global_toc
|
@@ -163,8 +164,9 @@ def fwph_main(self, finalize=True):
|
163 | 164 | self._swap_nonant_vars()
|
164 | 165 | self._local_bound = 0
|
165 | 166 | # tbsdm = time.time()
|
| 167 | + generate_column = True |
166 | 168 | for name in self.local_subproblems:
|
167 |
| - dual_bound = self.SDM(name) |
| 169 | + dual_bound = self.SDM(name, generate_column) |
168 | 170 | if dual_bound is None:
|
169 | 171 | dual_bound = np.nan
|
170 | 172 | self._local_bound += self.local_subproblems[name]._mpisppy_probability * \
|
@@ -210,7 +212,7 @@ def fwph_main(self, finalize=True):
|
210 | 212 | return self._PHIter, weight_dict, xbars_dict
|
211 | 213 | return self._PHIter
|
212 | 214 |
|
213 |
| - def SDM(self, model_name): |
| 215 | + def SDM(self, model_name, generate_column=True): |
214 | 216 | ''' Algorithm 2 in Boland et al. (with small tweaks)
|
215 | 217 | '''
|
216 | 218 | mip = self.local_subproblems[model_name]
|
@@ -244,78 +246,81 @@ def SDM(self, model_name):
|
244 | 246 | mip_source = mip.scen_list if self.bundling else [model_name]
|
245 | 247 |
|
246 | 248 | for itr in range(self.FW_options['FW_iter_limit']):
|
247 |
| - # loop_start = time.time() |
248 |
| - # Algorithm 2 line 4 |
249 |
| - for scenario_name in mip_source: |
250 |
| - scen_mip = self.local_scenarios[scenario_name] |
251 |
| - for ndn_i in scen_mip._mpisppy_data.nonant_indices: |
252 |
| - scen_mip._mpisppy_model.W[ndn_i]._value = ( |
253 |
| - qp._mpisppy_model.W[ndn_i]._value |
254 |
| - + scen_mip._mpisppy_model.rho[ndn_i]._value |
255 |
| - * (xt[ndn_i] |
256 |
| - - scen_mip._mpisppy_model.xbars[ndn_i]._value)) |
257 |
| - |
258 |
| - cutoff = pyo.value(qp._mpisppy_model.mip_obj_in_qp) + pyo.value(qp.recourse_cost) |
259 |
| - epsilon = 0 #1e-6 |
260 |
| - rel_epsilon = abs(cutoff)*epsilon |
261 |
| - epsilon = max(epsilon, rel_epsilon) |
262 |
| - # tbmipsolve = time.time() |
263 |
| - active_objective = sputils.find_active_objective(mip) |
264 |
| - if self.is_minimizing: |
265 |
| - # obj <= cutoff |
266 |
| - obj_cutoff_constraint = (None, active_objective.expr, cutoff+epsilon) |
267 |
| - else: |
268 |
| - # obj >= cutoff |
269 |
| - obj_cutoff_constraint = (cutoff-epsilon, active_objective.expr, None) |
270 |
| - mip._mpisppy_model.obj_cutoff_constraint = pyo.Constraint(expr=obj_cutoff_constraint) |
271 |
| - if sputils.is_persistent(mip._solver_plugin): |
272 |
| - mip._solver_plugin.add_constraint(mip._mpisppy_model.obj_cutoff_constraint) |
273 |
| - # Algorithm 2 line 5 |
274 |
| - self.solve_one( |
275 |
| - self.options["iterk_solver_options"], |
276 |
| - model_name, |
277 |
| - mip, |
278 |
| - dtiming=dtiming, |
279 |
| - tee=teeme, |
280 |
| - verbose=verbose, |
281 |
| - ) |
282 |
| - if sputils.is_persistent(mip._solver_plugin): |
283 |
| - mip._solver_plugin.remove_constraint(mip._mpisppy_model.obj_cutoff_constraint) |
284 |
| - mip.del_component(mip._mpisppy_model.obj_cutoff_constraint) |
285 |
| - # tmipsolve = time.time() - tbmipsolve |
286 |
| - if mip._mpisppy_data.scenario_feasible: |
287 |
| - |
288 |
| - # Algorithm 2 lines 6--8 |
289 |
| - if (itr == 0): |
290 |
| - dual_bound = mip._mpisppy_data.outer_bound |
291 |
| - |
292 |
| - # Algorithm 2 line 9 (compute \Gamma^t) |
293 |
| - inner_bound = mip._mpisppy_data.inner_bound |
294 |
| - if abs(inner_bound) > 1e-9: |
295 |
| - stop_check = (cutoff - inner_bound) / abs(inner_bound) # \Gamma^t in Boland, but normalized |
| 249 | + if generate_column: |
| 250 | + # loop_start = time.time() |
| 251 | + # Algorithm 2 line 4 |
| 252 | + for scenario_name in mip_source: |
| 253 | + scen_mip = self.local_scenarios[scenario_name] |
| 254 | + for ndn_i in scen_mip._mpisppy_data.nonant_indices: |
| 255 | + scen_mip._mpisppy_model.W[ndn_i]._value = ( |
| 256 | + qp._mpisppy_model.W[ndn_i]._value |
| 257 | + + scen_mip._mpisppy_model.rho[ndn_i]._value |
| 258 | + * (xt[ndn_i] |
| 259 | + - scen_mip._mpisppy_model.xbars[ndn_i]._value)) |
| 260 | + |
| 261 | + cutoff = pyo.value(qp._mpisppy_model.mip_obj_in_qp) + pyo.value(qp.recourse_cost) |
| 262 | + epsilon = 0 #1e-6 |
| 263 | + rel_epsilon = abs(cutoff)*epsilon |
| 264 | + epsilon = max(epsilon, rel_epsilon) |
| 265 | + # tbmipsolve = time.time() |
| 266 | + active_objective = sputils.find_active_objective(mip) |
| 267 | + if self.is_minimizing: |
| 268 | + # obj <= cutoff |
| 269 | + obj_cutoff_constraint = (None, active_objective.expr, cutoff+epsilon) |
296 | 270 | else:
|
297 |
| - stop_check = cutoff - inner_bound # \Gamma^t in Boland |
298 |
| - # print(f"{model_name}, Gamma^t = {stop_check}") |
299 |
| - stop_check_tol = self.FW_options["stop_check_tol"]\ |
300 |
| - if "stop_check_tol" in self.FW_options else 1e-4 |
301 |
| - if (self.is_minimizing and stop_check < -stop_check_tol): |
302 |
| - print('Warning (fwph): convergence quantity Gamma^t = ' |
303 |
| - '{sc:.2e} (should be non-negative)'.format(sc=stop_check)) |
304 |
| - print('Try decreasing the MIP gap tolerance and re-solving') |
305 |
| - elif (not self.is_minimizing and stop_check > stop_check_tol): |
306 |
| - print('Warning (fwph): convergence quantity Gamma^t = ' |
307 |
| - '{sc:.2e} (should be non-positive)'.format(sc=stop_check)) |
308 |
| - print('Try decreasing the MIP gap tolerance and re-solving') |
309 |
| - |
310 |
| - # tbcol = time.time() |
311 |
| - self._add_QP_column(model_name) |
312 |
| - # tcol = time.time() - tbcol |
313 |
| - # print(f"{model_name} QP add_column time: {tcol}") |
| 271 | + # obj >= cutoff |
| 272 | + obj_cutoff_constraint = (cutoff-epsilon, active_objective.expr, None) |
| 273 | + mip._mpisppy_model.obj_cutoff_constraint = pyo.Constraint(expr=obj_cutoff_constraint) |
| 274 | + if sputils.is_persistent(mip._solver_plugin): |
| 275 | + mip._solver_plugin.add_constraint(mip._mpisppy_model.obj_cutoff_constraint) |
| 276 | + # Algorithm 2 line 5 |
| 277 | + self.solve_one( |
| 278 | + self.options["iterk_solver_options"], |
| 279 | + model_name, |
| 280 | + mip, |
| 281 | + dtiming=dtiming, |
| 282 | + tee=teeme, |
| 283 | + verbose=verbose, |
| 284 | + ) |
| 285 | + if sputils.is_persistent(mip._solver_plugin): |
| 286 | + mip._solver_plugin.remove_constraint(mip._mpisppy_model.obj_cutoff_constraint) |
| 287 | + mip.del_component(mip._mpisppy_model.obj_cutoff_constraint) |
| 288 | + # tmipsolve = time.time() - tbmipsolve |
| 289 | + if mip._mpisppy_data.scenario_feasible: |
| 290 | + |
| 291 | + # Algorithm 2 lines 6--8 |
| 292 | + if (itr == 0): |
| 293 | + dual_bound = mip._mpisppy_data.outer_bound |
| 294 | + |
| 295 | + # Algorithm 2 line 9 (compute \Gamma^t) |
| 296 | + inner_bound = mip._mpisppy_data.inner_bound |
| 297 | + if abs(inner_bound) > 1e-9: |
| 298 | + stop_check = (cutoff - inner_bound) / abs(inner_bound) # \Gamma^t in Boland, but normalized |
| 299 | + else: |
| 300 | + stop_check = cutoff - inner_bound # \Gamma^t in Boland |
| 301 | + # print(f"{model_name}, Gamma^t = {stop_check}") |
| 302 | + stop_check_tol = self.FW_options["stop_check_tol"]\ |
| 303 | + if "stop_check_tol" in self.FW_options else 1e-4 |
| 304 | + if (self.is_minimizing and stop_check < -stop_check_tol): |
| 305 | + print('Warning (fwph): convergence quantity Gamma^t = ' |
| 306 | + '{sc:.2e} (should be non-negative)'.format(sc=stop_check)) |
| 307 | + print('Try decreasing the MIP gap tolerance and re-solving') |
| 308 | + elif (not self.is_minimizing and stop_check > stop_check_tol): |
| 309 | + print('Warning (fwph): convergence quantity Gamma^t = ' |
| 310 | + '{sc:.2e} (should be non-positive)'.format(sc=stop_check)) |
| 311 | + print('Try decreasing the MIP gap tolerance and re-solving') |
| 312 | + |
| 313 | + # tbcol = time.time() |
| 314 | + self._add_QP_column(model_name) |
| 315 | + # tcol = time.time() - tbcol |
| 316 | + # print(f"{model_name} QP add_column time: {tcol}") |
314 | 317 |
|
| 318 | + else: |
| 319 | + dual_bound = None |
| 320 | + global_toc(f"{self.__class__.__name__}: Could not find an improving column for {model_name}!", True) |
| 321 | + # couldn't find an improving direction, the column would not become active |
315 | 322 | else:
|
316 | 323 | dual_bound = None
|
317 |
| - global_toc(f"{self.__class__.__name__}: Could not find an improving column for {model_name}!", True) |
318 |
| - # couldn't find an improving direction, the column would not become active |
319 | 324 |
|
320 | 325 | # add a shared column(s)
|
321 | 326 | shared_columns = self.options.get("FWPH_shared_columns_per_iteration", 0)
|
|
0 commit comments