Exemple #1
0
    def Eobjective(self, verbose=False):
        """ Compute the expected objective function across all scenarios.

        Note: 
            Assumes the optimization is done beforehand,
            therefore DOES NOT CHECK FEASIBILITY or NON-ANTICIPATIVITY!
            This method uses whatever the current value of the objective
            function is.

        Args:
            verbose (boolean, optional):
                If True, displays verbose output. Default False.

        Returns:
            float:
                The expected objective function value
        """
        local_Eobjs = []
        for k, s in self.local_scenarios.items():
            if self.bundling:
                objfct = self.saved_objs[k]
            else:
                objfct = sputils.find_active_objective(s)
            local_Eobjs.append(s._mpisppy_probability * pyo.value(objfct))
            if verbose:
                print("caller", inspect.stack()[1][3])
                print ("E_Obj Scenario {}, prob={}, Obj={}, ObjExpr={}"\
                       .format(k, s._mpisppy_probability, pyo.value(objfct), objfct.expr))

        local_Eobj = np.array([math.fsum(local_Eobjs)])
        global_Eobj = np.zeros(1)
        self.mpicomm.Allreduce(local_Eobj, global_Eobj, op=MPI.SUM)

        return global_Eobj[0]
Exemple #2
0
    def solve_one(self,
                  solver_options,
                  k,
                  s,
                  dtiming=False,
                  gripe=False,
                  tee=False,
                  verbose=False,
                  disable_pyomo_signal_handling=False,
                  update_objective=True,
                  compute_val_at_nonant=False):
        self._lazy_create_solvers()
        pyomo_solve_time = super().solve_one(
            solver_options,
            k,
            s,
            dtiming=dtiming,
            gripe=gripe,
            tee=tee,
            verbose=verbose,
            disable_pyomo_signal_handling=disable_pyomo_signal_handling,
            update_objective=update_objective)
        if compute_val_at_nonant:
            if self.bundling:
                objfct = self.saved_objs[k]

            else:
                objfct = sputils.find_active_objective(s)
                if self.verbose:
                    print("caller", inspect.stack()[1][3])
                    print ("E_Obj Scenario {}, prob={}, Obj={}, ObjExpr={}"\
                           .format(k, s._mpisppy_probability, pyo.value(objfct), objfct.expr))
            self.objs_dict[k] = pyo.value(objfct)
        return (pyomo_solve_time)
Exemple #3
0
    def _extract_objective(self, mip):
        ''' Extract the original part of the provided MIP's objective function
            (no dual or prox terms), and create a copy containing the QP
            variables in place of the MIP variables.

            Args:
                mip (Pyomo ConcreteModel): MIP model for a scenario or bundle.

            Returns:
                obj (Pyomo Objective): objective function extracted
                    from the MIP
                new (Pyomo Expression): expression from the MIP model
                    objective with the MIP variables replaced by QP variables.
                    Does not inculde dual or prox terms.

            Notes:
                Acts on either a single-scenario model or a bundle
        '''
        mip_to_qp = mip._mpisppy_data.mip_to_qp
        obj = find_active_objective(mip)
        repn = generate_standard_repn(obj.expr, quadratic=True)
        if len(repn.nonlinear_vars) > 0:
            raise ValueError("FWPH does not support models with nonlinear objective functions")
        linear_vars = [mip_to_qp[id(var)] for var in repn.linear_vars]
        new = LinearExpression(
            constant=repn.constant, linear_coefs=repn.linear_coefs, linear_vars=linear_vars
        )
        if repn.quadratic_vars:
            quadratic_vars = (
                (mip_to_qp[id(x)], mip_to_qp[id(y)]) for x,y in repn.quadratic_vars
            )
            new += pyo.quicksum(
                (coef*x*y for coef,(x,y) in zip(repn.quadratic_coefs, quadratic_vars))
            )
        return obj, new
Exemple #4
0
    def build_model_for_scenario(self,
                                 scenario_identifier: str) -> Tuple[_BlockData, Dict[Any, _GeneralVarData]]:
        m = self.local_scenario_models[scenario_identifier]

        _assert_continuous(m)

        active_obj = find_active_objective(m)
        active_obj.deactivate()
        m._mpisppy_model.weighted_obj = pyo.Objective(expr=m._mpisppy_probability * active_obj.expr, sense=active_obj.sense)

        nonant_vars = m._mpisppy_data.nonant_indices
        if len(nonant_vars) != len(self.nonant_vars):
            raise ValueError(f'Number of non-anticipative variables is not consistent in scenario {scenario_identifier}.')

        return m, nonant_vars
Exemple #5
0
 def write_loop(self):
     """ Bundles are special. Also: this code needs help
     from the ph object to be more efficient...
     """
     for sname, s in self.ph.local_scenarios.items():
         bundling = self.ph.bundling
         fname = self.dirname + os.sep + sname + ".dag"
         with open(fname, "a") as f:
             f.write(str(self.ph._PHIter) + ",")
             if not bundling:
                 objfct = find_active_objective(s)
                 f.write(str(pyo.value(objfct)))
             else:
                 f.write("Bundling" + ",")
                 f.write(str(pyo.value(self.ph.saved_objs[sname])))
             f.write("\n")
Exemple #6
0
    def _check_bound(self):
        opt = self.opt

        chached_ph_obj = dict()

        for k,s in opt.local_subproblems.items():
            phobj = find_active_objective(s)
            phobj.deactivate()
            chached_ph_obj[k] = phobj
            s._mpisppy_model.EF_Obj.activate()

        teeme = (
            "tee-rank0-solves" in opt.PHoptions
             and opt.PHoptions["tee-rank0-solves"]
        )
        opt.solve_loop(
                solver_options=opt.current_solver_options,
                dtiming=opt.PHoptions["display_timing"],
                gripe=True,
                disable_pyomo_signal_handling=False,
                tee=teeme,
                verbose=opt.PHoptions["verbose"],
        )

        local_obs = np.fromiter((s._mpisppy_data.outer_bound for s in opt.local_subproblems.values()),
                                dtype="d", count=len(opt.local_subproblems))

        local_ob = np.empty(1)
        if opt.is_minimizing:
            local_ob[0] = local_obs.max()
        else:
            local_ob[0] = local_obs.min()

        global_ob = np.empty(1)

        if opt.is_minimizing:
            opt.mpicomm.Allreduce(local_ob, global_ob, op=mpi.MAX)
        else: 
            opt.mpicomm.Allreduce(local_ob, global_ob, op=mpi.MIN)

        #print(f"CrossScenarioExtension OB: {global_ob[0]}")

        opt.spcomm.BestOuterBound = opt.spcomm.OuterBoundUpdate(global_ob[0], char='C')

        for k,s in opt.local_subproblems.items():
            s._mpisppy_model.EF_Obj.deactivate()
            chached_ph_obj[k].activate()
Exemple #7
0
    def post_iter0(self):
        opt = self.opt
        # NOTE: the LShaped code negates the objective, so
        #       we do the same here for consistency
        if 'cross_scen_options' in opt.options and \
                'valid_eta_bound' in opt.options['cross_scen_options']:
            valid_eta_bound = opt.options['cross_scen_options']['valid_eta_bound']
            if not opt.is_minimizing:
                _eta_init = { k: -v for k,v in valid_eta_bound.items() }
            else:
                _eta_init = valid_eta_bound
            _eta_bounds = lambda m,k : (_eta_init[k], None)
        else:
            lb = (-sys.maxsize - 1) * 1. / len(opt.all_scenario_names)
            _eta_init = lambda m,k : lb
            _eta_bounds = lambda m,k : (lb, None)

        # eta is attached to each subproblem, regardless of bundles
        bundling = opt.bundling
        for k,s in opt.local_subproblems.items():
            s._mpisppy_model.eta = pyo.Var(opt.all_scenario_names, initialize=_eta_init, bounds=_eta_bounds)
            if sputils.is_persistent(s._solver_plugin):
                for var in s._mpisppy_model.eta.values():
                    s._solver_plugin.add_var(var)
            if bundling: ## create a refence to eta on each subproblem
                for sn in s.scen_list:
                    scenario = opt.local_scenarios[sn]
                    scenario._mpisppy_model.eta = { k : s._mpisppy_model.eta[k] for k in opt.all_scenario_names }

        ## hold the PH object harmless
        self._disable_W_and_prox()
        
        for k,s in opt.local_subproblems.items():

            obj = find_active_objective(s)

            repn = generate_standard_repn(obj.expr, quadratic=True)
            if len(repn.nonlinear_vars) > 0:
                raise ValueError("CrossScenario does not support models with nonlinear objective functions")

            if bundling:
                ## NOTE: this is slighly wasteful, in that for a bundle
                ##       the first-stage cost appears len(s.scen_list) times
                ##       If this really made a difference, we could use s.ref_vars
                ##       to do the substitution
                nonant_vardata_list = list()
                for sn in s.scen_list:
                    nonant_vardata_list.extend( \
                            opt.local_scenarios[sn]._PySPnode_list[0].nonant_vardata_list)
            else:
                nonant_vardata_list = s._PySPnode_list[0].nonant_vardata_list

            nonant_ids = set((id(var) for var in nonant_vardata_list))

            linear_coefs = list(repn.linear_coefs)
            linear_vars = list(repn.linear_vars)

            quadratic_coefs = list(repn.quadratic_coefs)

            # adjust coefficients by scenario/bundle probability
            scen_prob = s.PySP_prob
            for i,var in enumerate(repn.linear_vars):
                if id(var) not in nonant_ids:
                    linear_coefs[i] *= scen_prob

            for i,(x,y) in enumerate(repn.quadratic_vars):
                # only multiply through once
                if id(x) not in nonant_ids:
                    quadratic_coefs[i] *= scen_prob
                elif id(y) not in nonant_ids:
                    quadratic_coefs[i] *= scen_prob

            # NOTE: the LShaped code negates the objective, so
            #       we do the same here for consistency
            if not opt.is_minimizing:
                for i,coef in enumerate(linear_coefs):
                    linear_coefs[i] = -coef
                for i,coef in enumerate(quadratic_coefs):
                    quadratic_coefs[i] = -coef

            # add the other etas
            if bundling:
                these_scenarios = set(s.scen_list)
            else:
                these_scenarios = [k]

            eta_scenarios = list()
            for sn in opt.all_scenario_names:
                if sn not in these_scenarios:
                    linear_coefs.append(1)
                    linear_vars.append(s._mpisppy_model.eta[sn])
                    eta_scenarios.append(sn)

            expr = LinearExpression(constant=repn.constant, linear_coefs=linear_coefs,
                                    linear_vars=linear_vars)

            if repn.quadratic_vars:
                expr += pyo.quicksum(
                    (coef*x*y for coef,(x,y) in zip(quadratic_coefs, repn.quadratic_vars))
                )

            s._mpisppy_model.EF_obj = pyo.Expression(expr=expr)

            if opt.is_minimizing:
                s._mpisppy_model.EF_Obj = pyo.Objective(expr=s._mpisppy_model.EF_obj, sense=pyo.minimize)
            else:
                s._mpisppy_model.EF_Obj = pyo.Objective(expr=-s._mpisppy_model.EF_obj, sense=pyo.maximize)
            s._mpisppy_model.EF_Obj.deactivate()

            # add cut constraint dicts
            s._mpisppy_model.benders_cuts = pyo.Constraint(pyo.Any)
            s._mpisppy_model.inner_bound_constr = pyo.Constraint(pyo.Any)

        self._enable_W_and_prox()

        # try to get the initial eta LB cuts
        # (may not be available)
        opt.spcomm.get_from_cross_cuts()
Exemple #8
0
    def FormEF(self, scen_dict, EF_name=None):
        """ Make the EF for a list of scenarios. 
        
        This function is mainly to build bundles. To build (and solve) the
        EF of the entire problem, use the EF class instead.

        Args:
            scen_dict (dict): 
                Subset of local_scenarios; the scenarios to put in the EF. THe
                dictionary maps sccneario names (strings) to scenarios (Pyomo
                concrete model objects).
            EF_name (string, optional):
                Name for the resulting EF model.

        Returns:
            :class:`pyomo.environ.ConcreteModel`: 
                The EF with explicit non-anticipativity constraints.

        Raises:
            RuntimeError:
                If the `scen_dict` is empty, or one of the scenarios in
                `scen_dict` is not owned locally (i.e. is not in
                `local_scenarios`).

        Note: 
            We attach a list of the scenario names called _PySP_subsecen_names
        Note:
            We deactivate the objective on the scenarios.
        Note:
            The scenarios are sub-blocks, so they naturally get the EF solution
            Also the EF objective references Vars and Parms on the scenarios
            and hence is automatically updated when the scenario
            objectives are. THIS IS ALL CRITICAL to bundles.
            xxxx TBD: ask JP about objective function transmittal to persistent solvers
        Note:
            Objectives are scaled (normalized) by _mpisppy_probability
        """
        if len(scen_dict) == 0:
            raise RuntimeError("Empty scenario list for EF")

        if len(scen_dict) == 1:
            sname, scenario_instance = list(scen_dict.items())[0]
            if EF_name is not None:
                print("WARNING: EF_name=" + EF_name + " not used; singleton=" +
                      sname)
                print(
                    "MAJOR WARNING: a bundle of size one encountered; if you try to compute bounds it might crash (Feb 2019)"
                )
            return scenario_instance

        # The individual scenario instances are sub-blocks of the binding
        # instance. Needed to facilitate bundles + persistent solvers
        if not hasattr(self, "saved_objs"):  # First bundle
            self.saved_objs = dict()

        for sname, scenario_instance in scen_dict.items():
            if sname not in self.local_scenarios:
                raise RuntimeError("EF scen not in local_scenarios=" + sname)
            self.saved_objs[sname] = sputils.find_active_objective(
                scenario_instance)

        EF_instance = sputils._create_EF_from_scen_dict(
            scen_dict, EF_name=EF_name, nonant_for_fixed_vars=False)
        return EF_instance
Exemple #9
0
    def attach_PH_to_objective(self, add_duals, add_prox):
        """ Attach dual weight and prox terms to the objective function of the
        models in `local_scenarios`.

        Args:
            add_duals (boolean):
                If True, adds dual weight (Ws) to the objective.
            add_prox (boolean):
                If True, adds the prox term to the objective.
        """

        if ('linearize_binary_proximal_terms' in self.options):
            lin_bin_prox = self.options['linearize_binary_proximal_terms']
        else:
            lin_bin_prox = False

        if ('linearize_proximal_terms' in self.options):
            self._prox_approx = self.options['linearize_proximal_terms']
            if 'proximal_linearization_tolerance' in self.options:
                self.prox_approx_tol = self.options[
                    'proximal_linearization_tolerance']
            else:
                self.prox_approx_tol = 1.e-1
            if 'initial_proximal_cut_count' in self.options:
                initial_prox_cuts = self.options['initial_proximal_cut_count']
            else:
                initial_prox_cuts = 2
        else:
            self._prox_approx = False

        for (sname, scenario) in self.local_scenarios.items():
            """Attach the dual and prox terms to the objective.
            """
            if ((not add_duals) and (not add_prox)):
                return
            objfct = sputils.find_active_objective(scenario)
            is_min_problem = objfct.is_minimizing()

            xbars = scenario._mpisppy_model.xbars

            if self._prox_approx:
                # set-up pyomo IndexVar, but keep it sparse
                # since some nonants might be binary
                # Define the first cut to be _xsqvar >= 0
                scenario._mpisppy_model.xsqvar = pyo.Var(
                    scenario._mpisppy_data.nonant_indices,
                    dense=False,
                    within=pyo.NonNegativeReals)
                scenario._mpisppy_model.xsqvar_cuts = pyo.Constraint(
                    scenario._mpisppy_data.nonant_indices, pyo.Integers)
                scenario._mpisppy_data.xsqvar_prox_approx = {}
            else:
                scenario._mpisppy_model.xsqvar = None
                scenario._mpisppy_data.xsqvar_prox_approx = False

            ph_term = 0
            # Dual term (weights W)
            if (add_duals):
                scenario._mpisppy_model.WExpr = pyo.Expression(expr=\
                        sum(scenario._mpisppy_model.W[ndn_i] * xvar \
                            for ndn_i, xvar in scenario._mpisppy_data.nonant_indices.items()) )
                ph_term += scenario._mpisppy_model.W_on * scenario._mpisppy_model.WExpr

            # Prox term (quadratic)
            if (add_prox):
                prox_expr = 0.
                for ndn_i, xvar in scenario._mpisppy_data.nonant_indices.items(
                ):
                    # expand (x - xbar)**2 to (x**2 - 2*xbar*x + xbar**2)
                    # x**2 is the only qradratic term, which might be
                    # dealt with differently depending on user-set options
                    if xvar.is_binary() and (lin_bin_prox
                                             or self._prox_approx):
                        xvarsqrd = xvar
                    elif self._prox_approx:
                        xvarsqrd = scenario._mpisppy_model.xsqvar[ndn_i]
                        scenario._mpisppy_data.xsqvar_prox_approx[ndn_i] = \
                                ProxApproxManager(xvar, xvarsqrd, scenario._mpisppy_model.xsqvar_cuts, ndn_i, initial_prox_cuts)
                    else:
                        xvarsqrd = xvar**2
                    prox_expr += (scenario._mpisppy_model.rho[ndn_i] / 2.0) * \
                                 (xvarsqrd - 2.0 * xbars[ndn_i] * xvar + xbars[ndn_i]**2)
                scenario._mpisppy_model.ProxExpr = pyo.Expression(
                    expr=prox_expr)
                ph_term += scenario._mpisppy_model.prox_on * scenario._mpisppy_model.ProxExpr

            if (is_min_problem):
                objfct.expr += ph_term
            else:
                objfct.expr -= ph_term
Exemple #10
0
    def APH_main(self, spcomm=None, finalize=True):
        """Execute the APH algorithm.
        Args:
            spcomm (SPCommunitator object): for intra or inter communications
            finalize (bool, optional, default=True):
                        If True, call self.post_loops(), if False, do not,
                        and return None for Eobj

        Returns:
            conv, Eobj, trivial_bound: 
                        The first two CANNOT BE EASILY INTERPRETED. 
                        Eobj is the expected,  weighted objective with 
                        proximal term. It is not directly useful.
                        The trivial bound is computed after iter 0
        NOTE:
            You need an xhat finder either in denoument or in an extension.
        """
        # Prep needs to be before iter 0 for bundling
        # (It could be split up)
        self.PH_Prep(attach_duals=False, attach_prox=False)

        # Begin APH-specific Prep
        for sname, scenario in self.local_scenarios.items():
            # ys is plural of y
            scenario._mpisppy_model.y = pyo.Param(
                scenario._mpisppy_data.nonant_indices.keys(),
                initialize=0.0,
                mutable=True)
            scenario._mpisppy_model.ybars = pyo.Param(
                scenario._mpisppy_data.nonant_indices.keys(),
                initialize=0.0,
                mutable=True)
            scenario._mpisppy_model.z = pyo.Param(
                scenario._mpisppy_data.nonant_indices.keys(),
                initialize=0.0,
                mutable=True)
            # lag: we will support lagging back only to the last solve
            # IMPORTANT: pyomo does not support a second reference so no:
            # scenario._mpisppy_model.z_foropt = scenario._mpisppy_model.z

            if self.use_lag:
                scenario._mpisppy_model.z_foropt = pyo.Param(
                    scenario._mpisppy_data.nonant_indices.keys(),
                    initialize=0.0,
                    mutable=True)
                scenario._mpisppy_model.W_foropt = pyo.Param(
                    scenario._mpisppy_data.nonant_indices.keys(),
                    initialize=0.0,
                    mutable=True)

            objfct = find_active_objective(scenario)

            if self.use_lag:
                for (ndn,
                     i), xvar in scenario._mpisppy_data.nonant_indices.items():
                    # proximal term
                    objfct.expr +=  scenario._mpisppy_model.prox_on * \
                        (scenario._mpisppy_model.rho[(ndn,i)] /2.0) * \
                        (xvar**2 - 2.0*xvar*scenario._mpisppy_model.z_foropt[(ndn,i)] + scenario._mpisppy_model.z_foropt[(ndn,i)]**2)
                    # W term
                    objfct.expr += scenario._mpisppy_model.W_on * scenario._mpisppy_model.W_foropt[
                        ndn, i] * xvar
            else:
                for (ndn,
                     i), xvar in scenario._mpisppy_data.nonant_indices.items():
                    # proximal term
                    objfct.expr +=  scenario._mpisppy_model.prox_on * \
                        (scenario._mpisppy_model.rho[(ndn,i)] /2.0) * \
                        (xvar**2 - 2.0*xvar*scenario._mpisppy_model.z[(ndn,i)] + scenario._mpisppy_model.z[(ndn,i)]**2)
                    # W term
                    objfct.expr += scenario._mpisppy_model.W_on * scenario._mpisppy_model.W[
                        ndn, i] * xvar

        # End APH-specific Prep

        self.subproblem_creation(self.options["verbose"])

        trivial_bound = self.Iter0()

        self.setup_Lens()
        self.setup_dispatchrecord()

        sleep_secs = self.options["async_sleep_secs"]

        lkwargs = None  # nothing beyond synchro
        listener_gigs = {
            "FirstReduce": (self.listener_side_gig, lkwargs),
            "SecondReduce": None
        }
        self.synchronizer = listener_util.Synchronizer(
            comms=self.comms,
            Lens=self.Lens,
            work_fct=self.APH_iterk,
            rank=self.cylinder_rank,
            sleep_secs=sleep_secs,
            asynch=True,
            listener_gigs=listener_gigs)
        args = [spcomm] if spcomm is not None else [fullcomm]
        kwargs = None  # {"extensions": extensions}
        self.synchronizer.run(args, kwargs)

        if finalize:
            Eobj = self.post_loops()
        else:
            Eobj = None

#        print(f"Debug: here's the dispatch record for rank={self.global_rank}")
#        for k,v in self.dispatchrecord.items():
#            print(k, v)
#            print()
#        print("End dispatch record")

        return self.conv, Eobj, trivial_bound
Exemple #11
0
    def create_subproblem(self, scenario_name):
        """ the subproblem creation function passed into the
            BendersCutsGenerator 
        """
        instance = self.local_scenarios[scenario_name]

        nonant_list, nonant_ids = _get_nonant_ids(instance) 

        # NOTE: since we use generate_standard_repn below, we need
        #       to unfix any nonants so they'll properly appear
        #       in the objective
        fixed_nonants = [ var for var in nonant_list if var.fixed ]
        for var in fixed_nonants:
            var.fixed = False

        # pulls the scenario objective expression, removes the first stage variables, and sets the new objective
        obj = find_active_objective(instance)

        if not hasattr(instance, "_mpisppy_probability"):
            instance._mpisppy_probability = 1. / self.scenario_count
        _mpisppy_probability = instance._mpisppy_probability

        repn = generate_standard_repn(obj.expr, quadratic=True)
        if len(repn.nonlinear_vars) > 0:
            raise ValueError("LShaped does not support models with nonlinear objective functions")

        linear_vars = list()
        linear_coefs = list()
        quadratic_vars = list()
        quadratic_coefs = list()
        ## we'll assume the constant is part of stage 1 (wlog it is), just
        ## like the first-stage bits of the objective
        constant = repn.constant 

        ## only keep the second stage variables in the objective
        for coef, var in zip(repn.linear_coefs, repn.linear_vars):
            id_var = id(var)
            if id_var not in nonant_ids:
                linear_vars.append(var)
                linear_coefs.append(_mpisppy_probability*coef)
        for coef, (x,y) in zip(repn.quadratic_coefs, repn.quadratic_vars):
            id_x = id(x)
            id_y = id(y)
            if id_x not in nonant_ids or id_y not in nonant_ids:
                quadratic_coefs.append(_mpisppy_probability*coef)
                quadratic_vars.append((x,y))

        # checks if model sense is max, if so negates the objective
        if not self.is_minimizing:
            for i,coef in enumerate(linear_coefs):
                linear_coefs[i] = -coef
            for i,coef in enumerate(quadratic_coefs):
                quadratic_coefs[i] = -coef

        expr = LinearExpression(constant=constant, linear_coefs=linear_coefs,
                                linear_vars=linear_vars)
        if quadratic_coefs:
            expr += pyo.quicksum(
                        (coef*x*y for coef,(x,y) in zip(quadratic_coefs, quadratic_vars))
                    )

        instance.del_component(obj)

        # set subproblem objective function
        instance.obj = pyo.Objective(expr=expr, sense=pyo.minimize)

        ## need to do this here for validity if computing the eta bound
        if self.relax_root:
            # relaxes any integrality constraints for the subproblem
            RelaxIntegerVars().apply_to(instance)

        if self.compute_eta_bound:
            for var in fixed_nonants:
                var.fixed = True
            opt = pyo.SolverFactory(self.options["sp_solver"])
            if self.options["sp_solver_options"]:
                for k,v in self.options["sp_solver_options"].items():
                    opt.options[k] = v

            if sputils.is_persistent(opt):
                set_instance_retry(instance, opt, scenario_name)
                res = opt.solve(tee=False)
            else:
                res = opt.solve(instance, tee=False)

            eta_lb = res.Problem[0].Lower_bound

            self.valid_eta_lb[scenario_name] = eta_lb

        # if not done above
        if not self.relax_root:
            # relaxes any integrality constraints for the subproblem
            RelaxIntegerVars().apply_to(instance)

        # iterates through constraints and removes first stage constraints from the model
        # the id dict is used to improve the speed of identifying the stage each variables belongs to
        for constr_data in list(itertools.chain(
                instance.component_data_objects(SOSConstraint, active=True, descend_into=True)
                , instance.component_data_objects(Constraint, active=True, descend_into=True))):
            if _first_stage_only(constr_data, nonant_ids):
                _del_con(constr_data)

        # creates the sub map to remove first stage variables from objective expression
        complicating_vars_map = pyo.ComponentMap()
        subproblem_to_root_vars_map = pyo.ComponentMap()

        # creates the complicating var map that connects the first stage variables in the sub problem to those in
        # the root problem -- also set the bounds on the subproblem root vars to be none for better cuts
        for var, rvar in zip(nonant_list, self.root_vars):
            if var.name not in rvar.name: # rvar.name may be part of a bundle
                raise Exception("Error: Complicating variable mismatch, sub-problem variables changed order")
            complicating_vars_map[rvar] = var
            subproblem_to_root_vars_map[var] = rvar 

            # these are already enforced in the root
            # don't need to be enfored in the subproblems
            var.setlb(None)
            var.setub(None)
            var.fixed = False

        # this is for interefacing with PH code
        instance._mpisppy_model.subproblem_to_root_vars_map = subproblem_to_root_vars_map

        if self.store_subproblems:
            self.subproblems[scenario_name] = instance

        return instance, complicating_vars_map
Exemple #12
0
    def _create_root_with_scenarios(self):

        ef_scenarios = self.root_scenarios

        ## we want the correct probabilities to be set when
        ## calling create_EF
        if len(ef_scenarios) > 1:
            def scenario_creator_wrapper(name, **creator_options):
                scenario = self.scenario_creator(name, **creator_options)
                if not hasattr(scenario, '_mpisppy_probability'):
                    scenario._mpisppy_probability = 1./len(self.all_scenario_names)
                return scenario
            root = sputils.create_EF(
                ef_scenarios,
                scenario_creator_wrapper,
                scenario_creator_kwargs=self.scenario_creator_kwargs,
            )

            nonant_list, nonant_ids = _get_nonant_ids_EF(root)
        else:
            root = self.scenario_creator(
                ef_scenarios[0],
                **self.scenario_creator_kwargs,
            )
            if not hasattr(root, '_mpisppy_probability'):
                root._mpisppy_probability = 1./len(self.all_scenario_names)

            nonant_list, nonant_ids = _get_nonant_ids(root)

        self.root_vars = nonant_list

        # creates the eta variables for scenarios that are NOT selected to be
        # included in the root problem
        eta_indx = [scenario_name for scenario_name in self.all_scenario_names
                        if scenario_name not in self.root_scenarios]
        self._add_root_etas(root, eta_indx)

        obj = find_active_objective(root)

        repn = generate_standard_repn(obj.expr, quadratic=True)
        if len(repn.nonlinear_vars) > 0:
            raise ValueError("LShaped does not support models with nonlinear objective functions")
        linear_vars = list(repn.linear_vars)
        linear_coefs = list(repn.linear_coefs)
        quadratic_coefs = list(repn.quadratic_coefs)

        # adjust coefficients by scenario/bundle probability
        scen_prob = root._mpisppy_probability
        for i,var in enumerate(repn.linear_vars):
            if id(var) not in nonant_ids:
                linear_coefs[i] *= scen_prob

        for i,(x,y) in enumerate(repn.quadratic_vars):
            # only multiply through once
            if id(x) not in nonant_ids:
                quadratic_coefs[i] *= scen_prob
            elif id(y) not in nonant_ids:
                quadratic_coefs[i] *= scen_prob

        # NOTE: the LShaped code negates the objective, so
        #       we do the same here for consistency
        if not self.is_minimizing:
            for i,coef in enumerate(linear_coefs):
                linear_coefs[i] = -coef
            for i,coef in enumerate(quadratic_coefs):
                quadratic_coefs[i] = -coef

        # add the etas
        for var in root.eta.values():
            linear_vars.append(var)
            linear_coefs.append(1)

        expr = LinearExpression(constant=repn.constant, linear_coefs=linear_coefs,
                                linear_vars=linear_vars)
        if repn.quadratic_vars:
            expr += pyo.quicksum(
                (coef*x*y for coef,(x,y) in zip(quadratic_coefs, repn.quadratic_vars))
            )

        root.del_component(obj)

        # set root objective function
        root.obj = pyo.Objective(expr=expr, sense=pyo.minimize)

        self.root = root
Exemple #13
0
    def _create_root_no_scenarios(self):

        # using the first scenario as a basis
        root = self.scenario_creator(
            self.all_scenario_names[0], **self.scenario_creator_kwargs
        )

        if self.relax_root:
            RelaxIntegerVars().apply_to(root)

        nonant_list, nonant_ids = _get_nonant_ids(root)

        self.root_vars = nonant_list

        for constr_data in list(itertools.chain(
                root.component_data_objects(SOSConstraint, active=True, descend_into=True)
                , root.component_data_objects(Constraint, active=True, descend_into=True))):
            if not _first_stage_only(constr_data, nonant_ids):
                _del_con(constr_data)

        # delete the second stage variables
        for var in list(root.component_data_objects(Var, active=True, descend_into=True)):
            if id(var) not in nonant_ids:
                _del_var(var)

        self._add_root_etas(root, self.all_scenario_names)

        # pulls the current objective expression, adds in the eta variables,
        # and removes the second stage variables from the expression
        obj = find_active_objective(root)

        repn = generate_standard_repn(obj.expr, quadratic=True)
        if len(repn.nonlinear_vars) > 0:
            raise ValueError("LShaped does not support models with nonlinear objective functions")

        linear_vars = list()
        linear_coefs = list()
        quadratic_vars = list()
        quadratic_coefs = list()
        ## we'll assume the constant is part of stage 1 (wlog it is), just
        ## like the first-stage bits of the objective
        constant = repn.constant 

        ## only keep the first stage variables in the objective
        for coef, var in zip(repn.linear_coefs, repn.linear_vars):
            id_var = id(var)
            if id_var in nonant_ids:
                linear_vars.append(var)
                linear_coefs.append(coef)
        for coef, (x,y) in zip(repn.quadratic_coefs, repn.quadratic_vars):
            id_x = id(x)
            id_y = id(y)
            if id_x in nonant_ids and id_y in nonant_ids:
                quadratic_coefs.append(coef)
                quadratic_vars.append((x,y))

        # checks if model sense is max, if so negates the objective
        if not self.is_minimizing:
            for i,coef in enumerate(linear_coefs):
                linear_coefs[i] = -coef
            for i,coef in enumerate(quadratic_coefs):
                quadratic_coefs[i] = -coef

        # add the etas
        for var in root.eta.values():
            linear_vars.append(var)
            linear_coefs.append(1)

        expr = LinearExpression(constant=constant, linear_coefs=linear_coefs,
                                linear_vars=linear_vars)
        if quadratic_coefs:
            expr += pyo.quicksum(
                        (coef*x*y for coef,(x,y) in zip(quadratic_coefs, quadratic_vars))
                    )

        root.del_component(obj)

        # set root objective function
        root.obj = pyo.Objective(expr=expr, sense=pyo.minimize)

        self.root = root
Exemple #14
0
    def SDM(self, model_name):
        '''  Algorithm 2 in Boland et al. (with small tweaks)
        '''
        mip = self.local_subproblems[model_name]
        qp  = self.local_QP_subproblems[model_name]
    
        # Set the QP dual weights to the correct values If we are bundling, we
        # initialize the QP dual weights to be the dual weights associated with
        # the first scenario in the bundle (this should be okay, because each
        # scenario in the bundle has the same dual weights, analytically--maybe
        # a numerical problem).
        arb_scen_mip = self.local_scenarios[mip.scen_list[0]] \
                       if self.bundling else mip
        for (node_name, ix) in arb_scen_mip._mpisppy_data.nonant_indices:
            qp._mpisppy_model.W[node_name, ix]._value = \
                arb_scen_mip._mpisppy_model.W[node_name, ix].value

        alpha = self.FW_options['FW_weight']
        # Algorithm 3 line 6
        xt = {ndn_i:
            (1 - alpha) * pyo.value(arb_scen_mip._mpisppy_model.xbars[ndn_i])
            + alpha * pyo.value(xvar)
            for ndn_i, xvar in arb_scen_mip._mpisppy_data.nonant_indices.items()
            }

        for itr in range(self.FW_options['FW_iter_limit']):
            # Algorithm 2 line 4
            mip_source = mip.scen_list if self.bundling else [model_name]
            for scenario_name in mip_source:
                scen_mip = self.local_scenarios[scenario_name]
                for ndn_i, nonant in scen_mip._mpisppy_data.nonant_indices.items():
                    x_source = xt[ndn_i] if itr==0 \
                               else nonant._value
                    scen_mip._mpisppy_model.W[ndn_i]._value = (
                        qp._mpisppy_model.W[ndn_i]._value
                        + scen_mip._mpisppy_model.rho[ndn_i]._value
                        * (x_source
                        -  scen_mip._mpisppy_model.xbars[ndn_i]._value))

            # Algorithm 2 line 5
            if (sputils.is_persistent(mip._solver_plugin)):
                mip_obj = find_active_objective(mip)
                mip._solver_plugin.set_objective(mip_obj)
            mip_results = mip._solver_plugin.solve(mip)
            self._check_solve(mip_results, model_name + ' (MIP)')

            # Algorithm 2 lines 6--8
            obj = find_active_objective(mip)
            if (itr == 0):
                if (self.is_minimizing):
                    dual_bound = mip_results.Problem[0].Lower_bound
                else:
                    dual_bound = mip_results.Problem[0].Upper_bound

            # Algorithm 2 line 9 (compute \Gamma^t)
            val0 = pyo.value(obj)
            new  = replace_expressions(obj.expr, mip._mpisppy_data.mip_to_qp)
            val1 = pyo.value(new)
            obj.expr = replace_expressions(new, qp._mpisppy_data.qp_to_mip)
            if abs(val0) > 1e-9:
                stop_check = (val1 - val0) / abs(val0) # \Gamma^t in Boland, but normalized
            else:
                stop_check = val1 - val0 # \Gamma^t in Boland
            stop_check_tol = self.FW_options["stop_check_tol"]\
                             if "stop_check_tol" in self.FW_options else 1e-4
            if (self.is_minimizing and stop_check < -stop_check_tol):
                print('Warning (fwph): convergence quantity Gamma^t = '
                     '{sc:.2e} (should be non-negative)'.format(sc=stop_check))
                print('Try decreasing the MIP gap tolerance and re-solving')
            elif (not self.is_minimizing and stop_check > stop_check_tol):
                print('Warning (fwph): convergence quantity Gamma^t = '
                     '{sc:.2e} (should be non-positive)'.format(sc=stop_check))
                print('Try decreasing the MIP gap tolerance and re-solving')

            self._add_QP_column(model_name)
            if (sputils.is_persistent(qp._QP_solver_plugin)):
                qp_obj = find_active_objective(qp)
                qp._QP_solver_plugin.set_objective(qp_obj)
            qp_results = qp._QP_solver_plugin.solve(qp)
            self._check_solve(qp_results, model_name + ' (QP)')

            if (stop_check < self.FW_options['FW_conv_thresh']):
                break

        # Re-set the mip._mpisppy_model.W so that the QP objective 
        # is correct in the next major iteration
        mip_source = mip.scen_list if self.bundling else [model_name]
        for scenario_name in mip_source:
            scen_mip = self.local_scenarios[scenario_name]
            for (node_name, ix) in scen_mip._mpisppy_data.nonant_indices:
                scen_mip._mpisppy_model.W[node_name, ix]._value = \
                    qp._mpisppy_model.W[node_name, ix]._value

        return dual_bound