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, "PySP_prob"): instance.PySP_prob = 1. / self.scenario_count PySP_prob = instance.PySP_prob 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(PySP_prob * 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(PySP_prob * 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_master: # 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): opt.set_instance(instance) 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_master: # 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_master_vars_map = pyo.ComponentMap() # creates the complicating var map that connects the first stage variables in the sub problem to those in # the master problem -- also set the bounds on the subproblem master vars to be none for better cuts for var, mvar in zip(nonant_list, self.master_vars): if var.name not in mvar.name: # mvar.name may be part of a bundle raise Exception( "Error: Complicating variable mismatch, sub-problem variables changed order" ) complicating_vars_map[mvar] = var subproblem_to_master_vars_map[var] = mvar # these are already enforced in the master # 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._subproblem_to_master_vars_map = subproblem_to_master_vars_map if self.store_subproblems: self.subproblems[scenario_name] = instance return instance, complicating_vars_map
def _create_master_no_scenarios(self): # using the first scenario as a basis master = self.scenario_creator(self.all_scenario_names[0], node_names=None, cb_data=self.cb_data) if self.relax_master: RelaxIntegerVars().apply_to(master) nonant_list, nonant_ids = _get_nonant_ids(master) self.master_vars = nonant_list for constr_data in list( itertools.chain( master.component_data_objects(SOSConstraint, active=True, descend_into=True), master.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( master.component_data_objects(Var, active=True, descend_into=True)): if id(var) not in nonant_ids: _del_var(var) self._add_master_etas(master, 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(master) 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 master.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))) master.del_component(obj) # set master objective function master.obj = pyo.Objective(expr=expr, sense=pyo.minimize) self.master = master