Skip to content

Commit e3eb3cf

Browse files
committed
initial pass at optional column generation
1 parent fc8da6f commit e3eb3cf

File tree

1 file changed

+75
-70
lines changed

1 file changed

+75
-70
lines changed

mpisppy/opt/fwph.py

Lines changed: 75 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import time
2424
import re # For manipulating scenario names
2525
import random
26+
import math
2627

2728
from mpisppy import MPI
2829
from mpisppy import global_toc
@@ -163,8 +164,9 @@ def fwph_main(self, finalize=True):
163164
self._swap_nonant_vars()
164165
self._local_bound = 0
165166
# tbsdm = time.time()
167+
generate_column = True
166168
for name in self.local_subproblems:
167-
dual_bound = self.SDM(name)
169+
dual_bound = self.SDM(name, generate_column)
168170
if dual_bound is None:
169171
dual_bound = np.nan
170172
self._local_bound += self.local_subproblems[name]._mpisppy_probability * \
@@ -210,7 +212,7 @@ def fwph_main(self, finalize=True):
210212
return self._PHIter, weight_dict, xbars_dict
211213
return self._PHIter
212214

213-
def SDM(self, model_name):
215+
def SDM(self, model_name, generate_column=True):
214216
''' Algorithm 2 in Boland et al. (with small tweaks)
215217
'''
216218
mip = self.local_subproblems[model_name]
@@ -244,78 +246,81 @@ def SDM(self, model_name):
244246
mip_source = mip.scen_list if self.bundling else [model_name]
245247

246248
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)
296270
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}")
314317

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
315322
else:
316323
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
319324

320325
# add a shared column(s)
321326
shared_columns = self.options.get("FWPH_shared_columns_per_iteration", 0)

0 commit comments

Comments
 (0)