# ___________________________________________________________________________ # # Pyomo: Python Optimization Modeling Objects # Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC # Under the terms of Contract DE-NA0003525 with National Technology and # Engineering Solutions of Sandia, LLC, the U.S. Government retains certain # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ import logging from pyomo.core import TransformationFactory, Transformation, Block, VarList, Set, SortComponents, Objective, Constraint from pyomo.gdp import Disjunct, Disjunction from pyomo.repn import generate_standard_repn logger = logging.getLogger('pyomo.gdp') @TransformationFactory.register('gdp.bilinear', doc="Creates a disjunctive model where bilinear terms are replaced with disjunctive expressions.") class Bilinear_Transformation(Transformation): def __init__(self): super(Bilinear_Transformation, self).__init__() def _apply_to(self, instance, **kwds): # TODO: This data should be stored differently. We cannot nest this transformation with itself if getattr(instance, 'bilinear_data_', None) is None: instance.bilinear_data_ = Block() instance.bilinear_data_.cache = {} instance.bilinear_data_.vlist = VarList() instance.bilinear_data_.vlist_boolean = [] instance.bilinear_data_.IDX = Set() instance.bilinear_data_.disjuncts_ = Disjunct(instance.bilinear_data_.IDX*[0,1]) instance.bilinear_data_.disjunction_data = {} instance.bilinear_data_.o_expr = {} instance.bilinear_data_.c_body = {} # # Iterate over all blocks # for block in instance.block_data_objects( active=True, sort=SortComponents.deterministic ): self._transformBlock(block, instance) # # WEH: I wish I had a DisjunctList and DisjunctionList object... # def rule(block, i): return instance.bilinear_data_.disjunction_data[i] instance.bilinear_data_.disjunction_ = Disjunction(instance.bilinear_data_.IDX, rule=rule) def _transformBlock(self, block, instance): for component in block.component_objects(Objective, active=True, descend_into=False): expr = self._transformExpression(component.expr, instance) instance.bilinear_data_.o_expr[ id(component) ] = component.expr component.expr = expr for component in block.component_data_objects(Constraint, active=True, descend_into=False): expr = self._transformExpression(component.body, instance) instance.bilinear_data_.c_body[ id(component) ] = component.body component._body = expr def _transformExpression(self, expr, instance): if expr.polynomial_degree() > 2: raise ValueError("Cannot transform polynomial terms with degree > 2") if expr.polynomial_degree() < 2: return expr # expr = self._replace_bilinear(expr, instance) return expr def _replace_bilinear(self, expr, instance): idMap = {} terms = generate_standard_repn(expr, idMap=idMap) # Constant e = terms.constant # Linear terms for var, coef in zip(terms.linear_vars, terms.linear_coefs): e += coef * var # Quadratic terms if len(terms.quadratic_coefs) > 0: for vars_, coef_ in zip(terms.quadratic_vars, terms.quadratic_coefs): # if vars_[0].is_binary(): v = instance.bilinear_data_.cache.get( (id(vars_[0]),id(vars_[1])), None ) if v is None: instance.bilinear_data_.vlist_boolean.append(vars_[0]) v = instance.bilinear_data_.vlist.add() instance.bilinear_data_.cache[id(vars_[0]), id(vars_[1])] = v bounds = vars_[1].bounds v.setlb(bounds[0]) v.setub(bounds[1]) id_ = len(instance.bilinear_data_.vlist) instance.bilinear_data_.IDX.add(id_) # First disjunct d0 = instance.bilinear_data_.disjuncts_[id_,0] d0.c1 = Constraint(expr=vars_[0] == 1) d0.c2 = Constraint(expr=v == vars_[1]) # Second disjunct d1 = instance.bilinear_data_.disjuncts_[id_,1] d1.c1 = Constraint(expr=vars_[0] == 0) d1.c2 = Constraint(expr=v == 0) # Disjunction instance.bilinear_data_.disjunction_data[id_] = [instance.bilinear_data_.disjuncts_[id_,0], instance.bilinear_data_.disjuncts_[id_,1]] instance.bilinear_data_.disjunction_data[id_] = [instance.bilinear_data_.disjuncts_[id_,0], instance.bilinear_data_.disjuncts_[id_,1]] # The disjunctive variable is the expression e += coef_*v # elif vars_[1].is_binary(): v = instance.bilinear_data_.cache.get( (id(vars_[1]),id(vars_[0])), None ) if v is None: instance.bilinear_data_.vlist_boolean.append(vars_[1]) v = instance.bilinear_data_.vlist.add() instance.bilinear_data_.cache[id(vars_[1]), id(vars_[0])] = v bounds = vars_[0].bounds v.setlb(bounds[0]) v.setub(bounds[1]) id_ = len(instance.bilinear_data_.vlist) instance.bilinear_data_.IDX.add(id_) # First disjunct d0 = instance.bilinear_data_.disjuncts_[id_,0] d0.c1 = Constraint(expr=vars_[1] == 1) d0.c2 = Constraint(expr=v == vars_[0]) # Second disjunct d1 = instance.bilinear_data_.disjuncts_[id_,1] d1.c1 = Constraint(expr=vars_[1] == 0) d1.c2 = Constraint(expr=v == 0) # Disjunction instance.bilinear_data_.disjunction_data[id_] = [instance.bilinear_data_.disjuncts_[id_,0], instance.bilinear_data_.disjuncts_[id_,1]] # The disjunctive variable is the expression e += coef_*v else: # If neither variable is boolean, just reinsert the original bilinear term e += coef_*vars_[0]*vars_[1] # return e