def prep_cs_cuts(self): # create a map scenario -> index, this index is used for various lists containing scenario dependent info. self.scenario_to_index = { scen : indx for indx, scen in enumerate(self.opt.all_scenario_names) } # create concrete model to use as pseudo-master self.opt.master = pyo.ConcreteModel() ##get the nonants off an arbitrary scenario arb_scen = self.opt.local_scenarios[self.opt.local_scenario_names[0]] non_ants = arb_scen._PySPnode_list[0].nonant_vardata_list # add copies of the nonanticipatory variables to the master problem # NOTE: the LShaped code expects the nonant vars to be in a particular # order and with a particular *name*. # We're also creating an index for reference against later nonant_vid_to_copy_map = dict() master_vars = list() for v in non_ants: non_ant_copy = pyo.Var(name=v.name) self.opt.master.add_component(v.name, non_ant_copy) master_vars.append(non_ant_copy) nonant_vid_to_copy_map[id(v)] = non_ant_copy self.opt.master_vars = master_vars # create an index of these non_ant_copies to be in the same # order as PH, used below nonants = dict() for ndn_i, nonant in arb_scen._nonant_indexes.items(): vid = id(nonant) nonants[ndn_i] = nonant_vid_to_copy_map[vid] self.master_nonants = nonants self.opt.master.eta = pyo.Var(self.opt.all_scenario_names) self.opt.master.bender = LShapedCutGenerator() self.opt.master.bender.set_input(master_vars=self.opt.master_vars, tol=1e-4, comm=self.intracomm) self.opt.master.bender.set_ls(self.opt) # add the subproblems for all for scen in self.opt.all_scenario_names: subproblem_fn_kwargs = dict() # need to modify this to accept in user kwargs as well subproblem_fn_kwargs['scenario_name'] = scen self.opt.master.bender.add_subproblem(subproblem_fn=self.opt.create_subproblem, subproblem_fn_kwargs=subproblem_fn_kwargs, master_eta=self.opt.master.eta[scen], subproblem_solver=self.opt.options["sp_solver"], subproblem_solver_options=self.opt.options["sp_solver_options"]) ## this can take some time, so return early if we get a kill signal if self.got_kill_signal(): return self.opt.set_eta_bounds() self._eta_lb_array = np.fromiter( (self.opt.valid_eta_lb[s] for s in self.opt.all_scenario_names), dtype='d', count=len(self.opt.all_scenario_names)) self.make_eta_lb_cut()
def lshaped_algorithm(self, converger=None): """ function that runs the lshaped.py algorithm """ if converger: converger = converger(self, self.rank, self.n_proc) max_iter = 30 if "max_iter" in self.options: max_iter = self.options["max_iter"] tol = 1e-8 if "tol" in self.options: tol = self.options["tol"] verbose = True if "verbose" in self.options: verbose = self.options["verbose"] master_solver = self.options["master_solver"] sp_solver = self.options["sp_solver"] # creates the master problem self.create_master() m = self.master assert hasattr(m, "obj") # prevents problems from first stage variables becoming unconstrained # after processing _init_vars(self.master_vars) # sets up the BendersCutGenerator object m.bender = LShapedCutGenerator() m.bender.set_input(master_vars=self.master_vars, tol=tol, comm=self.mpicomm) # let the cut generator know who's using it, probably should check that this is called after set input m.bender.set_ls(self) # set the eta variables, removing this from the add_suproblem function so we can # Pass all the scenarios in the problem to bender.add_subproblem # and let it internally handle which ranks get which scenarios if self.has_master_scens: sub_scenarios = [ scenario_name for scenario_name in self.local_scenario_names if scenario_name not in self.master_scenarios ] else: sub_scenarios = self.local_scenario_names for scenario_name in self.local_scenario_names: if scenario_name in sub_scenarios: subproblem_fn_kwargs = dict() subproblem_fn_kwargs['scenario_name'] = scenario_name m.bender.add_subproblem( subproblem_fn=self.create_subproblem, subproblem_fn_kwargs=subproblem_fn_kwargs, master_eta=m.eta[scenario_name], subproblem_solver=sp_solver, subproblem_solver_options=self.options["sp_solver_options"] ) else: self.attach_nonant_var_map(scenario_name) # set the eta bounds if computed # by self.create_subproblem self.set_eta_bounds() if self.rank == self.rank0: opt = pyo.SolverFactory(master_solver) if opt is None: raise Exception("Error: Failed to Create Master Solver") # set options for k, v in self.options["master_solver_options"].items(): opt.options[k] = v is_persistent = sputils.is_persistent(opt) if is_persistent: opt.set_instance(m) t = time.time() res, t1, t2 = None, None, None # benders solve loop, repeats the benders master - subproblem # loop until either a no more cuts can are generated # or the maximum iterations limit is reached for self.iter in range(max_iter): if verbose and self.rank == self.rank0: if self.iter > 0: print("Current Iteration:", self.iter + 1, "Time Elapsed:", "%7.2f" % (time.time() - t), "Time Spent on Last Master:", "%7.2f" % t1, "Time Spent Generating Last Cut Set:", "%7.2f" % t2, "Current Objective:", "%7.2f" % m.obj.expr()) else: print("Current Iteration:", self.iter + 1, "Time Elapsed:", "%7.2f" % (time.time() - t), "Current Objective: -Inf") t1 = time.time() x_vals = np.zeros(len(self.master_vars)) eta_vals = np.zeros(self.scenario_count) outer_bound = np.zeros(1) if self.rank == self.rank0: if is_persistent: res = opt.solve(tee=False) else: res = opt.solve(m, tee=False) # LShaped is always minimizing outer_bound[0] = res.Problem[0].Lower_bound for i, var in enumerate(self.master_vars): x_vals[i] = var.value for i, eta in enumerate(m.eta.values()): eta_vals[i] = eta.value self.mpicomm.Bcast(x_vals, root=self.rank0) self.mpicomm.Bcast(eta_vals, root=self.rank0) self.mpicomm.Bcast(outer_bound, root=self.rank0) if self.is_minimizing: self._LShaped_bound = outer_bound[0] else: # LShaped is always minimizing, so negate # the outer bound for sharing broadly self._LShaped_bound = -outer_bound[0] if self.rank != self.rank0: for i, var in enumerate(self.master_vars): var._value = x_vals[i] for i, eta in enumerate(m.eta.values()): eta._value = eta_vals[i] t1 = time.time() - t1 # The hub object takes precedence over the converger # We'll send the nonants now, and check for a for # convergence if self.spcomm: self.spcomm.sync(send_nonants=True) if self.spcomm.is_converged(): break t2 = time.time() cuts_added = m.bender.generate_cut() t2 = time.time() - t2 if self.rank == self.rank0: for c in cuts_added: if is_persistent: opt.add_constraint(c) if verbose and len(cuts_added) == 0: print(f"Converged in {self.iter+1} iterations.\n" f"Total Time Elapsed: {time.time()-t:7.2f} " f"Time Spent on Last Master: {t1:7.2f} " f"Time spent verifying second stage: {t2:7.2f} " f"Final Objective: {m.obj.expr():7.2f}") break if verbose and self.iter == max_iter - 1: print("WARNING MAX ITERATION LIMIT REACHED !!! ") else: if len(cuts_added) == 0: break # The hub object takes precedence over the converger if self.spcomm: self.spcomm.sync(send_nonants=False) if self.spcomm.is_converged(): break if converger: converger.convergence_value() if converger.is_converged(): if verbose and self.rank == self.rank0: print( f"Converged to user criteria in {self.iter+1} iterations.\n" f"Total Time Elapsed: {time.time()-t:7.2f} " f"Time Spent on Last Master: {t1:7.2f} " f"Time spent verifying second stage: {t2:7.2f} " f"Final Objective: {m.obj.expr():7.2f}") break return res