def test_create_abstract_from_rule(self): def make_invalid(m): m.I = RangeSet(3) m.x = Var(m.I) m.c = Constraint(expr=sum(m.x[i] for i in m.I) >= 0) def make(m): m.I = RangeSet(3) m.x = Var(m.I) def c(b): return sum(m.x[i] for i in m.I) >= 0 m.c = Constraint(rule=c) model = AbstractModel(rule=make_invalid) self.assertRaises(RuntimeError, model.create_instance) model = AbstractModel(rule=make) instance = model.create_instance() self.assertEqual([x.local_name for x in model.component_objects()], []) self.assertEqual([x.local_name for x in instance.component_objects()], ['I', 'x', 'c']) self.assertEqual(len(list(identify_variables(instance.c.body))), 3) model = AbstractModel(rule=make) model.y = Var() instance = model.create_instance() self.assertEqual([x.local_name for x in instance.component_objects()], ['y', 'I', 'x', 'c']) self.assertEqual(len(list(identify_variables(instance.c.body))), 3)
def test_create_abstract_from_rule(self): def make_invalid(m): m.I = RangeSet(3) m.x = Var(m.I) m.c = Constraint( expr=sum(m.x[i] for i in m.I) >= 0 ) def make(m): m.I = RangeSet(3) m.x = Var(m.I) def c(b): return sum(m.x[i] for i in m.I) >= 0 m.c = Constraint( rule=c ) model = AbstractModel(rule=make_invalid) self.assertRaises(RuntimeError, model.create_instance) model = AbstractModel(rule=make) instance = model.create_instance() self.assertEqual( [x.cname() for x in model.component_objects()], [] ) self.assertEqual( [x.cname() for x in instance.component_objects()], ['I','x','c'] ) self.assertEqual( len(list(identify_variables(instance.c.body))), 3 ) model = AbstractModel(rule=make) model.y = Var() instance = model.create_instance() self.assertEqual( [x.cname() for x in instance.component_objects()], ['y','I','x','c'] ) self.assertEqual( len(list(identify_variables(instance.c.body))), 3 )
def test_1(self): ''' The simplest case that the black box has only two inputs and there is only one black block involved ''' def blackbox(a, b): return sin(a - b) m = self.m bb = ExternalFunction(blackbox) m.eflist = [bb] m.c1 = Constraint(expr=m.x[0] * m.z[0]**2 + bb(m.x[0], m.x[1]) == 2 * sqrt(2.0)) pI = PyomoInterface(m, [bb]) self.assertEqual(pI.lx, 2) self.assertEqual(pI.ly, 1) self.assertEqual(pI.lz, 3) self.assertEqual(len(list(identify_variables(m.c1.body))), 3) self.assertEqual(len(list(identify_variables(m.c2.body))), 2)
def test_create_concrete_from_rule(self): def make(m): m.I = RangeSet(3) m.x = Var(m.I) m.c = Constraint( expr=sum(m.x[i] for i in m.I) >= 0 ) model = ConcreteModel(rule=make) self.assertEqual( [x.cname() for x in model.component_objects()], ['I','x','c'] ) self.assertEqual( len(list(identify_variables(model.c.body))), 3 )
def test_2(self): ''' The simplest case that the black box has only one inputs and there is only a formula ''' def blackbox(a): return sin(a) m = self.m bb = ExternalFunction(blackbox) m.eflist = [bb] m.c1 = Constraint(expr=m.x[0] * m.z[0]**2 + bb(m.x[0] - m.x[1]) == 2 * sqrt(2.0)) pI = PyomoInterface(m, [bb]) self.assertEqual(pI.lx, 1) self.assertEqual(pI.ly, 1) self.assertEqual(pI.lz, 5) self.assertEqual(len(list(identify_variables(m.c1.body))), 3) self.assertEqual(len(list(identify_variables(m.c2.body))), 2) self.assertEqual(len(m.tR.conset), 1) self.assertEqual(len(list(identify_variables(m.tR.conset[1].body))), 3)
def differentiate(expr, wrt=None, wrt_list=None): if not _sympy_available: raise RuntimeError( "The sympy module is not available. " "Cannot perform automatic symbolic differentiation.") if not (( wrt is None ) ^ ( wrt_list is None )): raise ValueError( "differentiate(): Must specify exactly one of wrt and wrt_list") if wrt is not None: wrt_list = [ wrt ] else: # Copy the list because we will normalize things in place below wrt_list = list(wrt_list) pyomo_vars = list(EXPR.identify_variables(expr)) sympy_vars = [sympy.var('x%s'% i) for i in range(len(pyomo_vars))] sympy2pyomo = dict( zip(sympy_vars, pyomo_vars) ) pyomo2sympy = dict( (id(pyomo_vars[i]), sympy_vars[i]) for i in range(len(pyomo_vars)) ) ans = [] for i, target in enumerate(wrt_list): if target.__class__ is not tuple: wrt_list[i] = target = (target,) mismatch_target = False for var in target: if id(var) not in pyomo2sympy: mismatch_target = True break wrt_list[i] = tuple( pyomo2sympy.get(id(var),None) for var in target ) ans.append(0 if mismatch_target else None) # If there is nothing to do, do nothing if all(i is not None for i in ans): return ans if wrt is None else ans[0] tmp_expr = EXPR.clone_expression( expr, substitute=pyomo2sympy ) tmp_expr = _map_intrinsic_functions(tmp_expr, sympy2pyomo) tmp_expr = str(tmp_expr) sympy_expr = sympy.sympify( tmp_expr, locals=dict((str(x), x) for x in sympy_vars) ) for i, target in enumerate(wrt_list): if ans[i] is None: sympy_ans = sympy_expr.diff(*target) ans[i] = _map_sympy2pyomo(sympy_ans, sympy2pyomo) return ans if wrt is None else ans[0]
def differentiate(expr, wrt=None, wrt_list=None): if not _sympy_available: raise RuntimeError( "The sympy module is not available. " "Cannot perform automatic symbolic differentiation.") if not ((wrt is None) ^ (wrt_list is None)): raise ValueError( "differentiate(): Must specify exactly one of wrt and wrt_list") if wrt is not None: wrt_list = [wrt] else: # Copy the list because we will normalize things in place below wrt_list = list(wrt_list) pyomo_vars = list(EXPR.identify_variables(expr)) sympy_vars = [sympy.var('x%s' % i) for i in range(len(pyomo_vars))] sympy2pyomo = dict(zip(sympy_vars, pyomo_vars)) pyomo2sympy = dict( (id(pyomo_vars[i]), sympy_vars[i]) for i in range(len(pyomo_vars))) ans = [] for i, target in enumerate(wrt_list): if target.__class__ is not tuple: wrt_list[i] = target = (target, ) mismatch_target = False for var in target: if id(var) not in pyomo2sympy: mismatch_target = True break wrt_list[i] = tuple(pyomo2sympy.get(id(var), None) for var in target) ans.append(0 if mismatch_target else None) # If there is nothing to do, do nothing if all(i is not None for i in ans): return ans if wrt is None else ans[0] tmp_expr = EXPR.clone_expression(expr, substitute=pyomo2sympy) tmp_expr = _map_intrinsic_functions(tmp_expr, sympy2pyomo) tmp_expr = str(tmp_expr) sympy_expr = sympy.sympify(tmp_expr, locals=dict((str(x), x) for x in sympy_vars)) for i, target in enumerate(wrt_list): if ans[i] is None: sympy_ans = sympy_expr.diff(*target) ans[i] = _map_sympy2pyomo(sympy_ans, sympy2pyomo) return ans if wrt is None else ans[0]
def test_zero_term_removal(self): """Test for removing zero terms from linear constraints.""" m = ConcreteModel() m.v0 = Var() m.v1 = Var() m.v2 = Var() m.v3 = Var() m.c = Constraint(expr=m.v0 == m.v1 * m.v2 + m.v3) m.c2 = Constraint(expr=m.v1 * m.v2 + m.v3 <= m.v0) m.c3 = Constraint(expr=m.v0 <= m.v1 * m.v2 + m.v3) m.c4 = Constraint(expr=1 <= m.v1 * m.v2 + m.v3 <= 3) m.v1.fix(0) TransformationFactory('contrib.remove_zero_terms').apply_to(m) m.v1.unfix() # Check that the term no longer exists self.assertFalse( any(id(m.v1) == id(v) for v in identify_variables(m.c.body))) self.assertFalse( any(id(m.v1) == id(v) for v in identify_variables(m.c2.body))) self.assertFalse( any(id(m.v1) == id(v) for v in identify_variables(m.c3.body))) self.assertFalse( any(id(m.v1) == id(v) for v in identify_variables(m.c4.body)))
def _apply_to(self, instance, **kwds): if __debug__ and logger.isEnabledFor(logging.DEBUG): #pragma:nocover logger.debug("Calling ConnectorExpander") connectorsFound = False for c in instance.component_data_objects(Connector): connectorsFound = True break if not connectorsFound: return if __debug__ and logger.isEnabledFor(logging.DEBUG): #pragma:nocover logger.debug(" Connectors found!") # # At this point, there are connectors in the model, so we must # look for constraints that involve connectors and expand them. # connector_types = set([SimpleConnector, _ConnectorData]) constraint_list = [] connector_list = [] matched_connectors = {} found = dict() for constraint in instance.component_data_objects(Constraint): for c in expr.identify_variables( constraint.body, include_potentially_variable=True): if c.__class__ in connector_types: found[id(c)] = c if not found: continue # Note that it is important to copy the set of found # connectors, since the matching routine below will # manipulate sets in place. found_this_constraint = dict(found) constraint_list.append( (constraint, found_this_constraint) ) # Find all the connectors that are used in the constraint, # so we know which connectors to validate against each # other. Note that the validation must be transitive (that # is, if con1 has a & b and con2 has b & c, then a,b, and c # must all validate against each other. for cId, c in iteritems(found_this_constraint): if cId in matched_connectors: oldSet = matched_connectors[cId] found.update( oldSet ) for _cId in oldSet: matched_connectors[_cId] = found else: connector_list.append(c) matched_connectors[cId] = found # Reset found back to empty (this is more efficient as the # bulk of the constraints in the model will not have # connectors - so if we did this at the top of the loop, we # would spend a lot of time clearing empty sets found = {} # Validate all connector sets and expand the empty ones known_conn_sets = {} for connector in connector_list: conn_set = matched_connectors[id(connector)] if id(conn_set) in known_conn_sets: continue known_conn_sets[id(conn_set)] \ = self._validate_and_expand_connector_set(conn_set) # Expand each constraint for constraint, conn_set in constraint_list: cList = ConstraintList() constraint.parent_block().add_component( '%s.expanded' % ( constraint.local_name, ), cList ) connId = next(iterkeys(conn_set)) ref = known_conn_sets[id(matched_connectors[connId])] for k,v in sorted(iteritems(ref)): if v[1] >= 0: _iter = v[0] else: _iter = (v[0],) for idx in _iter: substitution = {} for c in itervalues(conn_set): if v[1] >= 0: new_v = c.vars[k][idx] elif k in c.aggregators: new_v = c.vars[k].add() else: new_v = c.vars[k] substitution[id(c)] = new_v cList.add(( constraint.lower, expr.clone_expression( constraint.body, substitution ), constraint.upper )) constraint.deactivate() # Now, go back and implement VarList aggregators for conn in connector_list: block = conn.parent_block() for var, aggregator in iteritems(conn.aggregators): c = Constraint(expr=aggregator(block, conn.vars[var])) block.add_component( '%s.%s.aggregate' % (conn.local_name, var), c )
def _apply_to(self, instance, **kwds): if __debug__ and logger.isEnabledFor(logging.DEBUG): #pragma:nocover logger.debug("Calling ConnectorExpander") connectorsFound = False for c in instance.component_data_objects(Connector): connectorsFound = True break if not connectorsFound: return if __debug__ and logger.isEnabledFor(logging.DEBUG): #pragma:nocover logger.debug(" Connectors found!") # # At this point, there are connectors in the model, so we must # look for constraints that involve connectors and expand them. # connector_types = set([SimpleConnector, _ConnectorData]) constraint_list = [] connector_list = [] matched_connectors = {} found = dict() for constraint in instance.component_data_objects(Constraint): for c in expr.identify_variables( constraint.body, include_potentially_variable=True): if c.__class__ in connector_types: found[id(c)] = c if not found: continue # Note that it is important to copy the set of found # connectors, since the matching routine below will # manipulate sets in place. found_this_constraint = dict(found) constraint_list.append((constraint, found_this_constraint)) # Find all the connectors that are used in the constraint, # so we know which connectors to validate against each # other. Note that the validation must be transitive (that # is, if con1 has a & b and con2 has b & c, then a,b, and c # must all validate against each other. for cId, c in iteritems(found_this_constraint): if cId in matched_connectors: oldSet = matched_connectors[cId] found.update(oldSet) for _cId in oldSet: matched_connectors[_cId] = found else: connector_list.append(c) matched_connectors[cId] = found # Reset found back to empty (this is more efficient as the # bulk of the constraints in the model will not have # connectors - so if we did this at the top of the loop, we # would spend a lot of time clearing empty sets found = {} # Validate all connector sets and expand the empty ones known_conn_sets = {} for connector in connector_list: conn_set = matched_connectors[id(connector)] if id(conn_set) in known_conn_sets: continue known_conn_sets[id(conn_set)] \ = self._validate_and_expand_connector_set(conn_set) # Expand each constraint for constraint, conn_set in constraint_list: cList = ConstraintList() constraint.parent_block().add_component( '%s.expanded' % (constraint.local_name, ), cList) connId = next(iterkeys(conn_set)) ref = known_conn_sets[id(matched_connectors[connId])] for k, v in sorted(iteritems(ref)): if v[1] >= 0: _iter = v[0] else: _iter = (v[0], ) for idx in _iter: substitution = {} for c in itervalues(conn_set): if v[1] >= 0: new_v = c.vars[k][idx] elif k in c.aggregators: new_v = c.vars[k].add() else: new_v = c.vars[k] substitution[id(c)] = new_v cList.add((constraint.lower, expr.clone_expression(constraint.body, substitution), constraint.upper)) constraint.deactivate() # Now, go back and implement VarList aggregators for conn in connector_list: block = conn.parent_block() for var, aggregator in iteritems(conn.aggregators): c = Constraint(expr=aggregator(block, conn.vars[var])) block.add_component('%s.%s.aggregate' % (conn.local_name, var), c)
def _xform_constraint(self, obj, disjunct, infodict, var_substitute_map, zero_substitute_map): # we will put a new transformed constraint on the relaxation block. relaxationBlock = infodict['chull']['relaxationBlock'] transBlock = relaxationBlock.parent_block() varMap = infodict['chull']['disaggregatedVars'] # Though rare, it is possible to get naming conflicts here # since constraints from all blocks are getting moved onto the # same block. So we get a unique name name = unique_component_name(relaxationBlock, obj.name) if obj.is_indexed(): try: newConstraint = Constraint(obj.index_set(), transBlock.lbub) except: # The original constraint may have been indexed by a # non-concrete set (like an Any). We will give up on # strict index verification and just blindly proceed. newConstraint = Constraint(Any) else: newConstraint = Constraint(transBlock.lbub) relaxationBlock.add_component(name, newConstraint) # add mapping of original constraint to transformed constraint # in transformation info dictionary infodict['chull']['relaxedConstraints'][obj] = newConstraint # add mapping of transformed constraint back to original constraint (we # know that the info dict is already created because this only got # called if we were transforming a disjunct...) relaxationBlock._gdp_transformation_info['srcConstraints'][ newConstraint] = obj for i in sorted(iterkeys(obj)): c = obj[i] if not c.active: continue NL = c.body.polynomial_degree() not in (0,1) EPS = self._config.EPS mode = self._config.perspective_function # We need to evaluate the expression at the origin *before* # we substitute the expression variables with the # disaggregated variables if not NL or mode == "FurmanSawayaGrossmann": h_0 = clone_without_expression_components( c.body, substitute=zero_substitute_map) y = disjunct.indicator_var if NL: if mode == "LeeGrossmann": sub_expr = clone_without_expression_components( c.body, substitute=dict( (var, subs/y) for var, subs in iteritems(var_substitute_map) ) ) expr = sub_expr * y elif mode == "GrossmannLee": sub_expr = clone_without_expression_components( c.body, substitute=dict( (var, subs/(y + EPS)) for var, subs in iteritems(var_substitute_map) ) ) expr = (y + EPS) * sub_expr elif mode == "FurmanSawayaGrossmann": sub_expr = clone_without_expression_components( c.body, substitute=dict( (var, subs/((1 - EPS)*y + EPS)) for var, subs in iteritems(var_substitute_map) ) ) expr = ((1-EPS)*y + EPS)*sub_expr - EPS*h_0*(1-y) else: raise RuntimeError("Unknown NL CHull mode") else: expr = clone_without_expression_components( c.body, substitute=var_substitute_map) if c.equality: if NL: newConsExpr = expr == c.lower*y else: v = list(identify_variables(expr)) if len(v) == 1 and not c.lower: # Setting a variable to 0 in a disjunct is # *very* common. We should recognize that in # that structure, the disaggregated variable # will also be fixed to 0. v[0].fix(0) continue newConsExpr = expr - (1-y)*h_0 == c.lower*y if obj.is_indexed(): newConstraint.add((i, 'eq'), newConsExpr) else: newConstraint.add('eq', newConsExpr) continue if c.lower is not None: # TODO: At the moment there is no reason for this to be in both # lower and upper... I think there could be though if I say what # the new constraint is going to be or something. if __debug__ and logger.isEnabledFor(logging.DEBUG): logger.debug("GDP(cHull): Transforming constraint " + "'%s'", c.name) if NL: newConsExpr = expr >= c.lower*y else: newConsExpr = expr - (1-y)*h_0 >= c.lower*y if obj.is_indexed(): newConstraint.add((i, 'lb'), newConsExpr) else: newConstraint.add('lb', newConsExpr) if c.upper is not None: if __debug__ and logger.isEnabledFor(logging.DEBUG): logger.debug("GDP(cHull): Transforming constraint " + "'%s'", c.name) if NL: newConsExpr = expr <= c.upper*y else: newConsExpr = expr - (1-y)*h_0 <= c.upper*y if obj.is_indexed(): newConstraint.add((i, 'ub'), newConsExpr) else: newConstraint.add('ub', newConsExpr)
def _transformDisjunctionData(self, obj, transBlock, index): # Convex hull doesn't work if this is an or constraint. So if # xor is false, give up if not obj.xor: raise GDP_Error("Cannot do convex hull transformation for " "disjunction %s with or constraint. Must be an xor!" % obj.name) parent_component = obj.parent_component() transBlock.disjContainers.add(parent_component) orConstraint, disaggregationConstraint \ = self._getDisjunctionConstraints(parent_component) # We first go through and collect all the variables that we # are going to disaggregate. varOrder_set = ComponentSet() varOrder = [] varsByDisjunct = ComponentMap() for disjunct in obj.disjuncts: # This is crazy, but if the disjunct has been previously # relaxed, the disjunct *could* be deactivated. not_active = not disjunct.active if not_active: disjunct._activate_without_unfixing_indicator() try: disjunctVars = varsByDisjunct[disjunct] = ComponentSet() for cons in disjunct.component_data_objects( Constraint, active = True, sort=SortComponents.deterministic, descend_into=Block): # we aren't going to disaggregate fixed # variables. This means there is trouble if they are # unfixed later... for var in identify_variables( cons.body, include_fixed=False): # Note the use of a list so that we will # eventually disaggregate the vars in a # deterministic order (the order that we found # them) disjunctVars.add(var) if var not in varOrder_set: varOrder.append(var) varOrder_set.add(var) finally: if not_active: disjunct._deactivate_without_fixing_indicator() # We will only disaggregate variables that # 1) appear in multiple disjuncts, or # 2) are not contained in this disjunct, or # 3) are not themselves disaggregated variables varSet = [] localVars = ComponentMap((d,[]) for d in obj.disjuncts) for var in varOrder: disjuncts = [d for d in varsByDisjunct if var in varsByDisjunct[d]] if len(disjuncts) > 1: varSet.append(var) elif self._contained_in(var, disjuncts[0]): localVars[disjuncts[0]].append(var) elif self._contained_in(var, transBlock): # There is nothing to do here: these are already # disaggregated vars that can/will be forced to 0 when # their disjunct is not active. pass else: varSet.append(var) # Now that we know who we need to disaggregate, we will do it # while we also transform the disjuncts. or_expr = 0 for disjunct in obj.disjuncts: or_expr += disjunct.indicator_var self._transform_disjunct(disjunct, transBlock, varSet, localVars[disjunct]) orConstraint.add(index, (or_expr, 1)) for i, var in enumerate(varSet): disaggregatedExpr = 0 for disjunct in obj.disjuncts: if 'chull' not in disjunct._gdp_transformation_info: if not disjunct.indicator_var.is_fixed() \ or value(disjunct.indicator_var) != 0: raise RuntimeError( "GDP chull: disjunct was not relaxed, but " "does not appear to be correctly deactivated.") continue disaggregatedVar = disjunct._gdp_transformation_info['chull'][ 'disaggregatedVars'][var] disaggregatedExpr += disaggregatedVar if type(index) is tuple: consIdx = index + (i,) elif parent_component.is_indexed(): consIdx = (index,) + (i,) else: consIdx = i disaggregationConstraint.add( consIdx, var == disaggregatedExpr)