def is_converged(self): ## might as well get a bound, in this case if self.opt._PHIter == 1: self.BestOuterBound = self.OuterBoundUpdate(self.opt.trivial_bound) if not self.has_innerbound_spokes: if self.opt._PHIter == 1: logger.warning("PHHub cannot compute convergence without " "inner bound spokes.") ## you still want to output status, even without inner bounders configured if self.rank_global == 0: self.screen_trace() return False if not self.has_outerbound_spokes: if self.opt._PHIter == 1 and self.rank_global == 0: tt_timer.toc( "Without outer bound spokes, no progress " "will be made on the Best Bound", delta=False) ## log some output if self.rank_global == 0: self.screen_trace() return self.determine_termination()
def hub_finalize(self): if self.has_outerbound_spokes: self.receive_outerbounds() if self.has_innerbound_spokes: self.receive_innerbounds() if self.rank_global == 0: self.print_init = True tt_timer.toc(f" ", delta=False) tt_timer.toc(f"Statistics at termination", delta=False) self.screen_trace()
def screen_trace(self): current_iteration = self.current_iteration() rel_gap = self.compute_gap(compute_relative=True) abs_gap = self.compute_gap(compute_relative=False) best_solution = self.BestInnerBound best_bound = self.BestOuterBound update_source = self.get_update_string() if self.print_init: row = f'{"Iter.":>5s} {" "} {"Best Bound":>14s} {"Best Incumbent":>14s} {"Rel. Gap":>12s} {"Abs. Gap":>14s}' tt_timer.toc(row, delta=False) self.print_init = False row = f"{current_iteration:5d} {update_source} {best_bound:14.4f} {best_solution:14.4f} {rel_gap*100:12.4f} {abs_gap:14.4f}" tt_timer.toc(row, delta=False) self.clear_latest_chars()
def determine_termination(self): abs_gap_satisfied = False rel_gap_satisfied = False if hasattr(self,"options") and self.options is not None: if "rel_gap" in self.options: rel_gap = self.compute_gap(compute_relative=True) rel_gap_satisfied = rel_gap <= self.options["rel_gap"] if "abs_gap" in self.options: abs_gap = self.compute_gap(compute_relative=False) abs_gap_satisfied = abs_gap <= self.options["abs_gap"] if abs_gap_satisfied and self.rank_global == 0: tt_timer.toc(f"Terminating based on inter-cylinder absolute gap {abs_gap:12.4f}", delta=False) if rel_gap_satisfied and self.rank_global == 0: tt_timer.toc(f"Terminating based on inter-cylinder relative gap {rel_gap*100:12.3f}%", delta=False) return abs_gap_satisfied or rel_gap_satisfied
def miditer(self): self.iter_since_last_check += 1 ib = self.opt.spcomm.BestInnerBound if ib != self.cur_ib: self.cur_ib = ib self.iter_at_cur_ib = 1 elif self.cur_ib is not None and math.isfinite(self.cur_ib): self.iter_at_cur_ib += 1 ob = self.opt.spcomm.BestOuterBound if self.cur_ob is not None and math.isclose(ob, self.cur_ob): ob_new = False else: self.cur_ob = ob ob_new = True if not self.any_cuts: if self.opt.spcomm.new_cuts: self.any_cuts = True ## if its the second time or more with this IB, we'll only check ## if the last improved the OB, or if the OB is new itself (from somewhere else) check = (self.check_bound_iterations is not None) and self.any_cuts and ( \ (self.iter_at_cur_ib == self.check_bound_iterations) or \ (self.iter_at_cur_ib > self.check_bound_iterations and ob_new) or \ ((self.iter_since_last_check%self.check_bound_iterations == 0) and self.opt.spcomm.new_cuts)) # if there hasn't been OB movement, check every so often if we have new cuts if check: if self.opt.spcomm.rank_global == 0: tt_timer.toc( f"Attempting to update Best Bound with CrossScenarioExtension", delta=False) self._check_bound() self.opt.spcomm.new_cuts = False self.iter_since_last_check = 0
def is_converged(self): if not (self.has_innerbound_spokes and self.has_outerbound_spokes): if self.opt._PHIter == 1: logger.warning("PHHub cannot compute convergence without " "both inner and outer bound spokes.") return False if self.opt._PHIter == 1: self.BestOuterBound = self.OuterBoundUpdate(self.opt.trivial_bound) ## log some output if self.rank_global == 0: rel_gap = self.compute_gap(compute_relative=True) abs_gap = self.compute_gap(compute_relative=False) best_solution = self.BestInnerBound best_bound = self.BestOuterBound elapsed = time.time() - self.inst_time if self.opt._PHIter == 1: row = f'{"Best Bound":>14s} {"Best Incumbent":>14s} {"Rel. Gap (%)":>12s} {"Abs. Gap":>14s}' tt_timer.toc(row, delta=False) row = f"{best_bound:14.4f} {best_solution:14.4f} {rel_gap*100:12.4f} {abs_gap:14.4f}" tt_timer.toc(row, delta=False) abs_gap_satisfied = False rel_gap_satisfied = False if hasattr(self, "options") and self.options is not None: if "rel_gap" in self.options: rel_gap = self.compute_gap(compute_relative=True) rel_gap_satisfied = rel_gap <= self.options["rel_gap"] if "abs_gap" in self.options: abs_gap = self.compute_gap(compute_relative=False) abs_gap_satisfied = abs_gap <= self.options["abs_gap"] if abs_gap_satisfied and self.rank_global == 0: tt_timer.toc( f"Terminating based on cylinder absolute gap {abs_gap:14.4f}", delta=False) if rel_gap_satisfied and self.rank_global == 0: tt_timer.toc( f"Terminating based on cylinder relative gap {rel_gap*100:12.4f}", delta=False) return abs_gap_satisfied or rel_gap_satisfied
def __init__( self, options, all_scenario_names, scenario_creator, scenario_denouement=None, all_nodenames=None, mpicomm=None, rank0=0, cb_data=None, ): self.startdt = dt.datetime.now() self.start_time = time.time() self.options = options self.all_scenario_names = all_scenario_names self.scenario_creator = scenario_creator self.scenario_denouement = scenario_denouement self.comms = dict() self.local_scenarios = dict() self.local_scenario_names = list() self.E1_tolerance = 1e-5 # probs must sum to almost 1 self.names_in_bundles = None self.scenarios_constructed = False if all_nodenames is None: self.all_nodenames = ["ROOT"] elif "ROOT" in all_nodenames: self.all_nodenames = all_nodenames else: raise RuntimeError("'ROOT' must be in the list of node names") # Set up MPI communicator and rank if mpicomm is not None: self.mpicomm = mpicomm else: self.mpicomm = MPI.COMM_WORLD self.rank = self.mpicomm.Get_rank() self.n_proc = self.mpicomm.Get_size() self.rank0 = rank0 self.rank_global = MPI.COMM_WORLD.Get_rank() if self.rank_global == 0: tt_timer.toc("Start SPBase.__init__", delta=False) # This doesn't seemed to be checked anywhere else if self.n_proc > len(self.all_scenario_names): raise RuntimeError("More ranks than scenarios") # Call various initialization methods if "branching_factors" in self.options: self.branching_factors = self.options["branching_factors"] else: self.branching_factors = [len(self.all_scenario_names)] self.calculate_scenario_ranks() self.attach_scenario_rank_maps() if "bundles_per_rank" in self.options and self.options[ "bundles_per_rank"] > 0: self.assign_bundles() self.bundling = True else: self.bundling = False self.create_scenarios(cb_data) self.look_before_leap_all() self.attach_nlens() self.attach_nonant_indexes() self.create_communicators() self.set_sense() self.set_multistage() ## SPCommunicator object self._spcomm = None
def spin_the_wheel(hub_dict, list_of_spoke_dict, comm_world=None): """ top level for the hub and spoke system Args: hub_dict(dict): controls hub creation list_of_spoke_dict(list dict): controls creation of spokes comm_world (MPI comm): the world for this hub-spoke system Returns: spcomm (Hub or Spoke object): the object that did the work (windowless) opt_dict (dict): the dictionary that controlled creation for this rank NOTE: the return is after termination; the objects are provided for query. """ if not haveMPI: raise RuntimeError("spin_the_wheel called, but cannot import mpi4py") # Confirm that the provided dictionaries specifying # the hubs and spokes contain the appropriate keys if "hub_class" not in hub_dict: raise RuntimeError( "The hub_dict must contain a 'hub_class' key specifying " "the hub class to use") if "opt_class" not in hub_dict: raise RuntimeError( "The hub_dict must contain an 'opt_class' key specifying " "the SPBase class to use (e.g. PHBase, etc.)") if "hub_kwargs" not in hub_dict: hub_dict["hub_kwargs"] = dict() if "opt_kwargs" not in hub_dict: hub_dict["opt_kwargs"] = dict() for spoke_dict in list_of_spoke_dict: if "spoke_class" not in spoke_dict: raise RuntimeError( "Each spoke_dict must contain a 'spoke_class' key " "specifying the spoke class to use") if "opt_class" not in spoke_dict: raise RuntimeError( "Each spoke_dict must contain an 'opt_class' key " "specifying the SPBase class to use (e.g. PHBase, etc.)") if "spoke_kwargs" not in spoke_dict: spoke_dict["spoke_kwargs"] = dict() if "opt_kwargs" not in spoke_dict: spoke_dict["opt_kwargs"] = dict() if comm_world is None: comm_world = MPI.COMM_WORLD n_spokes = len(list_of_spoke_dict) # Create the necessary communicators fullcomm = comm_world intercomm, intracomm = make_comms(n_spokes, fullcomm=fullcomm) rank_inter = intercomm.Get_rank() rank_intra = intracomm.Get_rank() rank_global = fullcomm.Get_rank() # Assign hub/spokes to individual ranks if rank_inter == 0: # This rank is a hub sp_class = hub_dict["hub_class"] sp_kwargs = hub_dict["hub_kwargs"] opt_class = hub_dict["opt_class"] opt_kwargs = hub_dict["opt_kwargs"] opt_dict = hub_dict else: # This rank is a spoke spoke_dict = list_of_spoke_dict[rank_inter - 1] sp_class = spoke_dict["spoke_class"] sp_kwargs = spoke_dict["spoke_kwargs"] opt_class = spoke_dict["opt_class"] opt_kwargs = spoke_dict["opt_kwargs"] opt_dict = spoke_dict # Create the appropriate opt object locally opt_kwargs["mpicomm"] = intracomm opt = opt_class(**opt_kwargs) # Create the SPCommunicator object (hub/spoke) with # the appropriate SPBase object attached if rank_inter == 0: # Hub spcomm = sp_class(opt, fullcomm, intercomm, intracomm, list_of_spoke_dict, **sp_kwargs) else: # Spokes spcomm = sp_class(opt, fullcomm, intercomm, intracomm, **sp_kwargs) # Create the windows, run main(), destroy the windows spcomm.make_windows() if rank_inter == 0: spcomm.setup_hub() if rank_global == 0: tt_timer.toc("Starting spcomm.main()", delta=False) spcomm.main() if rank_inter == 0: # If this is the hub spcomm.send_terminate() # Anything that's left to do spcomm.finalize() if rank_global == 0: tt_timer.toc("Hub algorithm complete, waiting for termination barrier", delta=False) fullcomm.Barrier() ## give the hub the chance to catch new values spcomm.hub_finalize() spcomm.free_windows() if rank_global == 0: tt_timer.toc("Windows freed", delta=False) return spcomm, opt_dict