@@ -102,13 +102,18 @@ def fw_prep(self):
102
102
self ._attach_MIP_QP_maps ()
103
103
self ._set_QP_objective ()
104
104
self ._initialize_QP_var_values ()
105
+ self ._setup_shared_column_generation ()
105
106
106
107
self ._QP_nonants = {}
107
108
self ._MIP_nonants = {}
108
109
109
- if self .FW_options ["FW_iter_limit" ] == 1 :
110
- number_points = self ._generate_starting_point ()
111
- if number_points == 0 :
110
+ number_initial_column_tries = self .options .get ("FW_initialization_attempts" , 20 )
111
+ if self .FW_options ["FW_iter_limit" ] == 1 and number_initial_column_tries < 1 :
112
+ global_toc (f"{ self .__class__ .__name__ } : Warning: FWPH needs an initial shared column if FW_iter_limit == 1. Increasing FW_iter_limit to 2 to ensure convergence" )
113
+ self .FW_options ["FW_iter_limit" ] = 2
114
+ if self .FW_options ["FW_iter_limit" ] == 1 or number_initial_column_tries > 0 :
115
+ number_points = self ._generate_shared_column (number_initial_column_tries )
116
+ if number_points == 0 and self .FW_options ["FW_iter_limit" ] == 1 :
112
117
global_toc (f"{ self .__class__ .__name__ } : Warning: FWPH failed to find an initial feasible solution. Increasing FW_iter_limit to 2 to ensure convergence" )
113
118
self .FW_options ["FW_iter_limit" ] = 2
114
119
@@ -193,6 +198,19 @@ def fwph_main(self, finalize=True):
193
198
secs = time .time () - self .t0
194
199
self ._output (self ._local_bound , best_bound , diff , secs )
195
200
201
+ # add a shared column
202
+ shared_columns = self .options .get ("FWPH_shared_columns_per_iteration" , 1 )
203
+ if shared_columns > 0 :
204
+ self .mpicomm .Barrier ()
205
+ self ._disable_W ()
206
+ for s in self .local_subproblems .values ():
207
+ if sputils .is_persistent (s ._solver_plugin ):
208
+ active_objective_datas = list (s .component_data_objects (
209
+ pyo .Objective , active = True , descend_into = True ))
210
+ s ._solver_plugin .set_objective (active_objective_datas [0 ])
211
+ self ._generate_shared_column (shared_columns )
212
+ self ._reenable_W ()
213
+
196
214
## Hubs/spokes take precedence over convergers
197
215
if self .spcomm :
198
216
if isinstance (self .spcomm , FWPHHub ):
@@ -214,7 +232,6 @@ def fwph_main(self, finalize=True):
214
232
# tphloop = time.time() - tbphloop
215
233
# print(f"PH iter {self._PHIter}, total time: {tphloop}")
216
234
217
-
218
235
if finalize :
219
236
weight_dict = self ._gather_weight_dict () # None if rank != 0
220
237
xbars_dict = self ._get_xbars () # None if rank != 0
@@ -281,6 +298,8 @@ def SDM(self, model_name):
281
298
tee = teeme ,
282
299
verbose = verbose ,
283
300
)
301
+ # TODO: fixme for maxmimization / larger objectives
302
+ self .options ["iterk_solver_options" ]["MIPABSCUTOFF" ] = 1e40
284
303
# tmipsolve = time.time() - tbmipsolve
285
304
286
305
# Algorithm 2 lines 6--8
@@ -681,41 +700,44 @@ def _initialize_QP_var_values(self):
681
700
qp .xr [node_name , ix ].set_value (
682
701
mip ._mpisppy_data .nonant_vars [arb_scenario , node_name , ix ].value )
683
702
684
- def _generate_starting_point (self ):
685
- """ Called after iter 0 to satisfy the condition of equation (17)
686
- in Boland et al., if t_max / FW_iter_limit == 1
687
- """
703
+ def _setup_shared_column_generation (self ):
704
+ """ helper for shared column generation """
688
705
#We need to keep track of the way scenario_names were sorted
689
706
scen_names = list (enumerate (self .all_scenario_names ))
690
707
691
- self .random_seed = 42
708
+ self ._random_seed = 42
692
709
# Have a separate stream for shuffling
693
- self . random_stream = random .Random ()
694
- self . random_stream .seed (self .random_seed )
710
+ random_stream = random .Random ()
711
+ random_stream .seed (self ._random_seed )
695
712
696
713
# shuffle the scenarios associated (i.e., sample without replacement)
697
- shuffled_scenarios = self .random_stream .sample (scen_names ,
698
- len (scen_names ))
714
+ shuffled_scenarios = random_stream .sample (scen_names , len (scen_names ))
699
715
700
- scenario_cycler = ScenarioCycler (shuffled_scenarios ,
716
+ self . _scenario_cycler = ScenarioCycler (shuffled_scenarios ,
701
717
self .nonleaves ,
702
718
False ,
703
719
None )
704
720
705
- xhatter = XhatBase (self )
706
- xhatter .post_iter0 ()
721
+ self ._xhatter = XhatBase (self )
722
+ self ._xhatter .post_iter0 ()
723
+
724
+ def _generate_shared_column (self , tries = 1 ):
725
+ """ Called after iter 0 to satisfy the condition of equation (17)
726
+ in Boland et al., if t_max / FW_iter_limit == 1
727
+ """
707
728
708
729
stage2EFsolvern = self .options .get ("stage2EFsolvern" , None )
709
730
branching_factors = self .options .get ("branching_factors" , None ) # for stage2ef
710
731
711
732
number_points = 0
712
- for _ in range (self . options . get ( "FW_initialization_attempts" , 20 )):
733
+ for t in range (min ( tries , len ( self . all_scenario_names ) )):
713
734
# will save in best solution
714
- snamedict = scenario_cycler .get_next ()
735
+ snamedict = self . _scenario_cycler .get_next ()
715
736
if snamedict is None :
716
- return number_points
717
- obj = xhatter ._try_one (snamedict ,
718
- solver_options = self .options ["mip_solver_options" ],
737
+ self ._scenario_cycler .begin_epoch ()
738
+ snamedict = self ._scenario_cycler .get_next ()
739
+ obj = self ._xhatter ._try_one (snamedict ,
740
+ solver_options = self .options ["iterk_solver_options" ],
719
741
verbose = False ,
720
742
restore_nonants = False ,
721
743
stage2EFsolvern = stage2EFsolvern ,
0 commit comments