Ejemplo n.º 1
0
    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()
Ejemplo n.º 2
0
    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