def _setup_subproblems(self, instance, bigM): # create transformation block transBlockName, transBlock = self._add_relaxation_block( instance, '_pyomo_gdp_cuttingplane_relaxation') # We store a list of all vars so that we can efficiently # generate maps among the subproblems transBlock.all_vars = list(v for v in instance.component_data_objects( Var, descend_into=(Block, Disjunct), sort=SortComponents.deterministic) if not v.is_fixed()) # we'll store all the cuts we add together transBlock.cuts = Constraint(Any) # get bigM and chull relaxations bigMRelaxation = TransformationFactory('gdp.bigm') chullRelaxation = TransformationFactory('gdp.chull') relaxIntegrality = TransformationFactory('core.relax_integrality') # HACK: for the current writers, we need to also apply gdp.reclassify so # that the indicator variables stay where they are in the big M model # (since that is what we are eventually going to solve after we add our # cuts). reclassify = TransformationFactory('gdp.reclassify') # # Generalte the CHull relaxation (used for the separation # problem to generate cutting planes # instance_rCHull = chullRelaxation.create_using(instance) # This relies on relaxIntegrality relaxing variables on deactivated # blocks, which should be fine. reclassify.apply_to(instance_rCHull) relaxIntegrality.apply_to(instance_rCHull) # # Reformulate the instance using the BigM relaxation (this will # be the final instance returned to the user) # bigMRelaxation.apply_to(instance, bigM=bigM) reclassify.apply_to(instance) # # Generate the continuous relaxation of the BigM transformation # instance_rBigM = relaxIntegrality.create_using(instance) # # Add the xstar parameter for the CHull problem # transBlock_rCHull = instance_rCHull.component(transBlockName) # # this will hold the solution to rbigm each time we solve it. We # add it to the transformation block so that we don't have to # worry about name conflicts. transBlock_rCHull.xstar = Param(range(len(transBlock.all_vars)), mutable=True, default=None) transBlock_rBigM = instance_rBigM.component(transBlockName) # # Generate the mapping between the variables on all the # instances and the xstar parameter # var_info = tuple( (v, transBlock_rBigM.all_vars[i], transBlock_rCHull.all_vars[i], transBlock_rCHull.xstar[i]) for i, v in enumerate(transBlock.all_vars)) # # Add the separation objective to the chull subproblem # self._add_separation_objective(var_info, transBlock_rCHull) return instance_rBigM, instance_rCHull, var_info, transBlockName
def _apply_to(self, model, **kwds): submodel_name = kwds.pop('submodel', None) use_dual_objective = kwds.pop('use_dual_objective', False) # # Process options # self._preprocess('pao.bilevel.linear_dual', model) self._fix_all() for key, sub in self.submodel.items(): # # Generate the dual block # transform = TransformationFactory('pao.duality.linear_dual') dual = transform.create_using(sub, fixed=self._fixed_vardata) # # Figure out which objective is being used # if use_dual_objective: # # Deactivate the upper-level objective # # TODO: Warn if there are multiple objectives? # for odata in sub._parent().component_map(Objective, active=True).values(): odata.deactivate() else: # # Add a constraint that maps the dual objective to the primal objective # # NOTE: It might be numerically more stable to replace the upper # objective with a variable, and then set the dual equal to that variable. # But that transformation would not be limited to the submodel. If that's # an issue for a user, they can make that change, and see the benefit. # dual_obj = None for odata in dual.component_objects(Objective, active=True): dual_obj = odata dual_obj.deactivate() break primal_obj = None for odata in sub.component_objects(Objective, active=True): primal_obj = odata break dual.equiv_objs = Constraint(expr=dual_obj.expr == primal_obj.expr) # # Add the dual block # setattr(model, key +'_dual', dual) model.reclassify_component_type(key +'_dual', Block) # # Unfix the upper variables # self._unfix_all() # # Disable the original submodel # # Q: Are the last steps redundant? Will we recurse into deactivated blocks? # sub.deactivate() for data in sub.component_map(active=True).values(): if not isinstance(data, Var) and not isinstance(data, Set): data.deactivate()
def _apply_to(self, model, **kwds): submodel_name = kwds.pop('submodel', None) use_dual_objective = kwds.pop('use_dual_objective', False) subproblem_objective_weights = kwds.pop('subproblem_objective_weights', None) # # Process options # self._preprocess('pao.pyomo.linear_dual', model) self._fix_all() _dual_obj = 0. _dual_sense = None _primal_obj = 0. for key, sub in self.submodel.items(): _parent = sub.parent_block() # # Generate the dual block # transform = TransformationFactory('pao.duality.linear_dual') dual = transform.create_using(sub, fixed=self._fixed_vardata[sub.name]) # # Figure out which objective is being used # for odata in dual.component_objects(Objective, active=True): if subproblem_objective_weights: _dual_obj += subproblem_objective_weights[key] * odata.expr else: _dual_obj += odata.expr _dual_sense = odata.sense # TODO: currently assumes all subproblems have same sense odata.deactivate() if use_dual_objective: # # Deactivate the upper-level objective, and # defaults to use the aggregate objective of the SubModels. # for odata in model.component_objects(Objective, active=True): odata.deactivate() else: # # Add a constraint that maps the dual objective to the primal objective # # NOTE: It might be numerically more stable to replace the upper # objective with a variable, and then set the dual equal to that variable. # But that transformation would not be limited to the submodel. If that's # an issue for a user, they can make that change, and see the benefit. # for odata in sub.component_objects(Objective, active=True): if subproblem_objective_weights: _primal_obj += subproblem_objective_weights[key]*odata.expr else: _primal_obj += odata.expr # # Add the dual block # # first check if the submodel exists on a _BlockData, # otherwise add the dual block to the model directly _dual_name = key +'_dual' if type(_parent) == _BlockData: _dual_name = _dual_name.replace(_parent.name+".","") _parent.add_component(_dual_name, dual) _parent.reclassify_component_type(_dual_name, Block) else: model.add_component(_dual_name, dual) model.reclassify_component_type(_dual_name, Block) # # Unfix the upper variables # self._unfix_all() # # Disable the original submodel # sub.deactivate() for data in sub.component_map(active=True).values(): if not isinstance(data, Var) and not isinstance(data, Set): data.deactivate() # TODO: with multiple sub-problems, put the _obj or equiv_objs on a separate block if use_dual_objective: dual._obj = Objective(expr=_dual_obj, sense=_dual_sense) else: dual.equiv_objs = Constraint(expr=_dual_obj == _primal_obj)
def _setup_subproblems(self, instance, bigM, tighten_relaxation_callback): # create transformation block transBlockName, transBlock = self._add_transformation_block(instance) # We store a list of all vars so that we can efficiently # generate maps among the subproblems transBlock.all_vars = list(v for v in instance.component_data_objects( Var, descend_into=(Block, Disjunct), sort=SortComponents.deterministic) if not v.is_fixed()) # we'll store all the cuts we add together nm = self._config.cuts_name if nm is None: cuts_obj = transBlock.cuts = Constraint(NonNegativeIntegers) else: # check that this really is an available name if instance.component(nm) is not None: raise GDP_Error("cuts_name was specified as '%s', but this is " "already a component on the instance! Please " "specify a unique name." % nm) instance.add_component(nm, Constraint(NonNegativeIntegers)) cuts_obj = instance.component(nm) # get bigM and hull relaxations bigMRelaxation = TransformationFactory('gdp.bigm') hullRelaxation = TransformationFactory('gdp.hull') relaxIntegrality = TransformationFactory('core.relax_integer_vars') # # Generate the Hull relaxation (used for the separation # problem to generate cutting planes) # tighter_instance = tighten_relaxation_callback(instance) instance_rHull = hullRelaxation.create_using(tighter_instance) relaxIntegrality.apply_to(instance_rHull, transform_deactivated_blocks=True) # # Reformulate the instance using the BigM relaxation (this will # be the final instance returned to the user) # bigMRelaxation.apply_to(instance, bigM=bigM) # # Generate the continuous relaxation of the BigM transformation. We'll # restore it at the end. # relaxIntegrality.apply_to(instance, transform_deactivated_blocks=True) # # Add the xstar parameter for the Hull problem # transBlock_rHull = instance_rHull.component(transBlockName) # # this will hold the solution to rbigm each time we solve it. We # add it to the transformation block so that we don't have to # worry about name conflicts. transBlock_rHull.xstar = Param( range(len(transBlock.all_vars)), mutable=True, default=0, within=Reals) # we will add a block that we will deactivate to use to store the # extended space cuts. We never need to solve these, but we need them to # be constructed for the sake of Fourier-Motzkin Elimination extendedSpaceCuts = transBlock_rHull.extendedSpaceCuts = Block() extendedSpaceCuts.deactivate() extendedSpaceCuts.cuts = Constraint(Any) # # Generate the mapping between the variables on all the # instances and the xstar parameter. # var_info = [ (v, # this is the bigM variable transBlock_rHull.all_vars[i], transBlock_rHull.xstar[i]) for i,v in enumerate(transBlock.all_vars)] # NOTE: we wait to add the separation objective to the rHull problem # because it is best to do it in the first iteration, so that we can # skip stale variables. return (instance, cuts_obj, instance_rHull, var_info, transBlockName)
def _setup_subproblems(self, instance, bigM): # create transformation block transBlockName, transBlock = self._add_relaxation_block( instance, '_pyomo_gdp_cuttingplane_relaxation') # We store a list of all vars so that we can efficiently # generate maps among the subproblems transBlock.all_vars = list(v for v in instance.component_data_objects( Var, descend_into=(Block, Disjunct), sort=SortComponents.deterministic) if not v.is_fixed()) # we'll store all the cuts we add together transBlock.cuts = Constraint(Any) # get bigM and chull relaxations bigMRelaxation = TransformationFactory('gdp.bigm') chullRelaxation = TransformationFactory('gdp.chull') relaxIntegrality = TransformationFactory('core.relax_integrality') # HACK: for the current writers, we need to also apply gdp.reclassify so # that the indicator variables stay where they are in the big M model # (since that is what we are eventually going to solve after we add our # cuts). reclassify = TransformationFactory('gdp.reclassify') # # Generalte the CHull relaxation (used for the separation # problem to generate cutting planes # instance_rCHull = chullRelaxation.create_using(instance) # This relies on relaxIntegrality relaxing variables on deactivated # blocks, which should be fine. reclassify.apply_to(instance_rCHull) relaxIntegrality.apply_to(instance_rCHull) # # Reformulate the instance using the BigM relaxation (this will # be the final instance returned to the user) # bigMRelaxation.apply_to(instance, bigM=bigM) reclassify.apply_to(instance) # # Generate the continuous relaxation of the BigM transformation # instance_rBigM = relaxIntegrality.create_using(instance) # # Add the xstar parameter for the CHull problem # transBlock_rCHull = instance_rCHull.component(transBlockName) # # this will hold the solution to rbigm each time we solve it. We # add it to the transformation block so that we don't have to # worry about name conflicts. transBlock_rCHull.xstar = Param( range(len(transBlock.all_vars)), mutable=True, default=None) transBlock_rBigM = instance_rBigM.component(transBlockName) # # Generate the mapping between the variables on all the # instances and the xstar parameter # var_info = tuple( (v, transBlock_rBigM.all_vars[i], transBlock_rCHull.all_vars[i], transBlock_rCHull.xstar[i]) for i,v in enumerate(transBlock.all_vars)) # # Add the separation objective to the chull subproblem # self._add_separation_objective(var_info, transBlock_rCHull) return instance_rBigM, instance_rCHull, var_info, transBlockName