def _bilinear_expressions(model): # TODO for now, we look for only expressions where the bilinearities are # exposed on the root level SumExpression, and thus accessible via # generate_standard_repn. This will not detect exp(x*y). We require a # factorization transformation to be applied beforehand in order to pick # these constraints up. pass # Bilinear map will be stored in the format: # x --> (y --> [constr1, constr2, ...], z --> [constr2, constr3]) bilinear_map = ComponentMap() for constr in model.component_data_objects(Constraint, active=True, descend_into=(Block, Disjunct)): if constr.body.polynomial_degree() in (1, 0): continue # Skip trivial and linear constraints repn = generate_standard_repn(constr.body) for pair in repn.quadratic_vars: v1, v2 = pair v1_pairs = bilinear_map.get(v1, ComponentMap()) if v2 in v1_pairs: # bilinear term has been found before. Simply add constraint to # the set associated with the bilinear term. v1_pairs[v2].add(constr) else: # We encounter the bilinear term for the first time. bilinear_map[v1] = v1_pairs bilinear_map[v2] = bilinear_map.get(v2, ComponentMap()) constraints_with_bilinear_pair = ComponentSet([constr]) bilinear_map[v1][v2] = constraints_with_bilinear_pair bilinear_map[v2][v1] = constraints_with_bilinear_pair return bilinear_map
def _build_equality_set(model): """Construct an equality set map. Maps all variables to the set of variables that are linked to them by equality. Mapping takes place using id(). That is, if you have x = y, then you would have id(x) -> ComponentSet([x, y]) and id(y) -> ComponentSet([x, y]) in the mapping. """ # Map of variables to their equality set (ComponentSet) eq_var_map = ComponentMap() # Loop through all the active constraints in the model for constraint in model.component_data_objects( ctype=Constraint, active=True, descend_into=True): eq_linked_vars = _get_equality_linked_variables(constraint) if not eq_linked_vars: continue # if we get an empty tuple, skip to next constraint. v1, v2 = eq_linked_vars set1 = eq_var_map.get(v1, ComponentSet((v1, v2))) set2 = eq_var_map.get(v2, (v2,)) # if set1 and set2 are equivalent, skip to next constraint. if set1 is set2: continue # add all elements of set2 to set 1 set1.update(set2) # Update all elements to point to set 1 for v in set1: eq_var_map[v] = set1 return eq_var_map
def _bilinear_expressions(model): # TODO for now, we look for only expressions where the bilinearities are # exposed on the root level SumExpression, and thus accessible via # generate_standard_repn. This will not detect exp(x*y). We require a # factorization transformation to be applied beforehand in order to pick # these constraints up. pass # Bilinear map will be stored in the format: # x --> (y --> [constr1, constr2, ...], z --> [constr2, constr3]) bilinear_map = ComponentMap() for constr in model.component_data_objects( Constraint, active=True, descend_into=(Block, Disjunct)): if constr.body.polynomial_degree() in (1, 0): continue # Skip trivial and linear constraints repn = generate_standard_repn(constr.body) for pair in repn.quadratic_vars: v1, v2 = pair v1_pairs = bilinear_map.get(v1, ComponentMap()) if v2 in v1_pairs: # bilinear term has been found before. Simply add constraint to # the set associated with the bilinear term. v1_pairs[v2].add(constr) else: # We encounter the bilinear term for the first time. bilinear_map[v1] = v1_pairs bilinear_map[v2] = bilinear_map.get(v2, ComponentMap()) constraints_with_bilinear_pair = ComponentSet([constr]) bilinear_map[v1][v2] = constraints_with_bilinear_pair bilinear_map[v2][v1] = constraints_with_bilinear_pair return bilinear_map
def _build_equality_set(model): """Construct an equality set map. Maps all variables to the set of variables that are linked to them by equality. Mapping takes place using id(). That is, if you have x = y, then you would have id(x) -> ComponentSet([x, y]) and id(y) -> ComponentSet([x, y]) in the mapping. """ # Map of variables to their equality set (ComponentSet) eq_var_map = ComponentMap() # Loop through all the active constraints in the model for constraint in model.component_data_objects(ctype=Constraint, active=True, descend_into=True): eq_linked_vars = _get_equality_linked_variables(constraint) if not eq_linked_vars: continue # if we get an empty tuple, skip to next constraint. v1, v2 = eq_linked_vars set1 = eq_var_map.get(v1, ComponentSet((v1, v2))) set2 = eq_var_map.get(v2, (v2, )) # if set1 and set2 are equivalent, skip to next constraint. if set1 is set2: continue # add all elements of set2 to set 1 set1.update(set2) # Update all elements to point to set 1 for v in set1: eq_var_map[v] = set1 return eq_var_map
def test_getsetdelitem(self): cmap = ComponentMap() for c, val in self._components: self.assertTrue(c not in cmap) for c, val in self._components: cmap[c] = val self.assertEqual(cmap[c], val) self.assertEqual(cmap.get(c), val) del cmap[c] with self.assertRaises(KeyError): cmap[c] with self.assertRaises(KeyError): del cmap[c] self.assertEqual(cmap.get(c), None)
def _build_equality_set(m): """Construct an equality set map. Maps all variables to the set of variables that are linked to them by equality. Mapping takes place using id(). That is, if you have x = y, then you would have id(x) -> ComponentSet([x, y]) and id(y) -> ComponentSet([x, y]) in the mapping. """ #: dict: map of var UID to the set of all equality-linked var UIDs eq_var_map = ComponentMap() relevant_vars = ComponentSet() for constr in m.component_data_objects(ctype=Constraint, active=True, descend_into=True): # Check to make sure the constraint is of form v1 - v2 == 0 if (value(constr.lower) == 0 and value(constr.upper) == 0 and constr.body.polynomial_degree() == 1): repn = generate_canonical_repn(constr.body) # only take the variables with nonzero coefficients vars_ = [v for i, v in enumerate(repn.variables) if repn.linear[i]] if (len(vars_) == 2 and sorted(l for l in repn.linear if l) == [-1, 1]): # this is an a == b constraint. v1 = vars_[0] v2 = vars_[1] set1 = eq_var_map.get(v1, ComponentSet([v1])) set2 = eq_var_map.get(v2, ComponentSet([v2])) relevant_vars.update([v1, v2]) set1.update(set2) # set1 is now the union for v in set1: eq_var_map[v] = set1 return eq_var_map, relevant_vars
def _build_equality_set(m): """Construct an equality set map. Maps all variables to the set of variables that are linked to them by equality. Mapping takes place using id(). That is, if you have x = y, then you would have id(x) -> ComponentSet([x, y]) and id(y) -> ComponentSet([x, y]) in the mapping. """ #: dict: map of var UID to the set of all equality-linked var UIDs eq_var_map = ComponentMap() relevant_vars = ComponentSet() for constr in m.component_data_objects(ctype=Constraint, active=True, descend_into=True): # Check to make sure the constraint is of form v1 - v2 == 0 if (value(constr.lower) == 0 and value(constr.upper) == 0 and constr.body.polynomial_degree() == 1): repn = generate_standard_repn(constr.body) # only take the variables with nonzero coefficients vars_ = [v for i, v in enumerate(repn.linear_vars) if repn.linear_coefs[i]] if (len(vars_) == 2 and sorted(l for l in repn.linear_coefs if l) == [-1, 1]): # this is an a == b constraint. v1 = vars_[0] v2 = vars_[1] set1 = eq_var_map.get(v1, ComponentSet([v1])) set2 = eq_var_map.get(v2, ComponentSet([v2])) relevant_vars.update([v1, v2]) set1.update(set2) # set1 is now the union for v in set1: eq_var_map[v] = set1 return eq_var_map, relevant_vars
def test_visitor_tightens_new_bounds(visitor, expr): bounds = ComponentMap() assert bounds.get(expr, None) is None assert visitor.handle_result(expr, Interval(0, 2), bounds) assert bounds[expr] == Interval(0, 2) assert not visitor.handle_result(expr, Interval(0, 2), bounds) assert bounds[expr] == Interval(0, 2) assert visitor.handle_result(expr, Interval(0, 1), bounds) assert bounds[expr] == Interval(0, 1)
def determine_valid_values(block, discr_var_to_constrs_map, config): """Calculate valid values for each effectively discrete variable. We need the set of possible values for the effectively discrete variable in order to do the reformulations. Right now, we select a naive approach where we look for variables in the discreteness-inducing constraints. We then adjust their values and see if things are stil feasible. Based on their coefficient values, we can infer a set of allowable values for the effectively discrete variable. Args: block: The model or a disjunct on the model. """ possible_values = ComponentMap() for eff_discr_var, constrs in discr_var_to_constrs_map.items(): # get the superset of possible values by looking through the # constraints for constr in constrs: repn = generate_standard_repn(constr.body) var_coef = sum(coef for i, coef in enumerate(repn.linear_coefs) if repn.linear_vars[i] is eff_discr_var) const = -(repn.constant - constr.upper) / var_coef possible_vals = set((const, )) for i, var in enumerate(repn.linear_vars): if var is eff_discr_var: continue coef = -repn.linear_coefs[i] / var_coef if var.is_binary(): var_values = (0, coef) elif var.is_integer(): var_values = [v * coef for v in range(var.lb, var.ub + 1)] else: raise ValueError( '%s has unacceptable variable domain: %s' % (var.name, var.domain)) possible_vals = set( (v1 + v2 for v1 in possible_vals for v2 in var_values)) old_possible_vals = possible_values.get(eff_discr_var, None) if old_possible_vals is not None: possible_values[ eff_discr_var] = old_possible_vals & possible_vals else: possible_values[eff_discr_var] = possible_vals possible_values = prune_possible_values(block, possible_values, config) return possible_values
def determine_valid_values(block, discr_var_to_constrs_map, config): """Calculate valid values for each effectively discrete variable. We need the set of possible values for the effectively discrete variable in order to do the reformulations. Right now, we select a naive approach where we look for variables in the discreteness-inducing constraints. We then adjust their values and see if things are stil feasible. Based on their coefficient values, we can infer a set of allowable values for the effectively discrete variable. Args: block: The model or a disjunct on the model. """ possible_values = ComponentMap() for eff_discr_var, constrs in discr_var_to_constrs_map.items(): # get the superset of possible values by looking through the # constraints for constr in constrs: repn = generate_standard_repn(constr.body) var_coef = sum(coef for i, coef in enumerate(repn.linear_coefs) if repn.linear_vars[i] is eff_discr_var) const = -(repn.constant - constr.upper) / var_coef possible_vals = set((const,)) for i, var in enumerate(repn.linear_vars): if var is eff_discr_var: continue coef = -repn.linear_coefs[i] / var_coef if var.is_binary(): var_values = (0, coef) elif var.is_integer(): var_values = [v * coef for v in range(var.lb, var.ub + 1)] else: raise ValueError( '%s has unacceptable variable domain: %s' % (var.name, var.domain)) possible_vals = set( (v1 + v2 for v1 in possible_vals for v2 in var_values)) old_possible_vals = possible_values.get(eff_discr_var, None) if old_possible_vals is not None: possible_values[eff_discr_var] = old_possible_vals & possible_vals else: possible_values[eff_discr_var] = possible_vals possible_values = prune_possible_values(block, possible_values, config) return possible_values
def _collect_expression_types(quadratic, linear): """Collect different expression types from quadratic and linear expression. Given an expression a0 x0^2 + a1 x1^2 + ...+ b0 x0 + b1 x1 + b2 x2 + ... Returns the quadratic univariate expressions like `a0 x0^2`, the bilinear expressions `c x1 x2`, and the linear expressions `bn xn`. :param quadratic: the quadratic expression :param linear: the linear expression :return: A tuple (univariate_expr, bilinear_terms, linear_terms) """ variables_coef = ComponentMap() for coef, var in zip(linear.linear_coefs, linear.linear_vars): variables_coef[var] = \ _VariableCoefficients(quadratic=0.0, linear=coef) non_univariate_terms = [] for term in quadratic.terms: if term.var1 is term.var2: var_coef = variables_coef.get(term.var1, None) if var_coef is None: non_univariate_terms.append(term) else: linear_coef = var_coef.linear var_coef = _VariableCoefficients(quadratic=term.coefficient, linear=linear_coef) variables_coef[term.var1] = var_coef else: non_univariate_terms.append(term) linear_terms = [(v, vc.linear) for v, vc in variables_coef.items() if almosteq(vc.quadratic, 0.0)] univariate_terms = [(v, vc.quadratic, vc.linear) for v, vc in variables_coef.items() if not almosteq(vc.quadratic, 0.0)] return univariate_terms, non_univariate_terms, linear_terms
def detect_effectively_discrete_vars(block, equality_tolerance): """Detect effectively discrete variables. These continuous variables are the sum of discrete variables. """ # Map of effectively_discrete var --> inducing constraints effectively_discrete = ComponentMap() for constr in block.component_data_objects(Constraint, active=True): if constr.lower is None or constr.upper is None: continue # skip inequality constraints if fabs(value(constr.lower) - value(constr.upper) ) > equality_tolerance: continue # not equality constriant. Skip. if constr.body.polynomial_degree() not in (1, 0): continue # skip nonlinear expressions repn = generate_standard_repn(constr.body) if len(repn.linear_vars) < 2: # TODO should this be < 2 or < 1? # TODO we should make sure that trivial equality relations are # preprocessed before this, or we will end up reformulating # expressions that we do not need to here. continue non_discrete_vars = list(v for v in repn.linear_vars if v.is_continuous()) if len(non_discrete_vars) == 1: # We know that this is an effectively discrete continuous # variable. Add it to our identified variable list. var = non_discrete_vars[0] inducing_constraints = effectively_discrete.get(var, []) inducing_constraints.append(constr) effectively_discrete[var] = inducing_constraints # TODO we should eventually also look at cases where all other # non_discrete_vars are effectively_discrete_vars return effectively_discrete
def detect_effectively_discrete_vars(block, equality_tolerance): """Detect effectively discrete variables. These continuous variables are the sum of discrete variables. """ # Map of effectively_discrete var --> inducing constraints effectively_discrete = ComponentMap() for constr in block.component_data_objects(Constraint, active=True): if constr.lower is None or constr.upper is None: continue # skip inequality constraints if fabs(value(constr.lower) - value(constr.upper)) > equality_tolerance: continue # not equality constriant. Skip. if constr.body.polynomial_degree() not in (1, 0): continue # skip nonlinear expressions repn = generate_standard_repn(constr.body) if len(repn.linear_vars) < 2: # TODO should this be < 2 or < 1? # TODO we should make sure that trivial equality relations are # preprocessed before this, or we will end up reformulating # expressions that we do not need to here. continue non_discrete_vars = list(v for v in repn.linear_vars if v.is_continuous()) if len(non_discrete_vars) == 1: # We know that this is an effectively discrete continuous # variable. Add it to our identified variable list. var = non_discrete_vars[0] inducing_constraints = effectively_discrete.get(var, []) inducing_constraints.append(constr) effectively_discrete[var] = inducing_constraints # TODO we should eventually also look at cases where all other # non_discrete_vars are effectively_discrete_vars return effectively_discrete
class _PortData(ComponentData): """ This class defines the data for a single Port Attributes ---------- vars:`dict` A dictionary mapping added names to variables """ __slots__ = ('vars', '_arcs', '_sources', '_dests', '_rules', '_splitfracs') def __init__(self, component=None): # # These lines represent in-lining of the # following constructors: # - ComponentData # - NumericValue self._component = weakref_ref(component) if (component is not None) \ else None self.vars = {} self._arcs = [] self._sources = [] self._dests = [] self._rules = {} self._splitfracs = ComponentMap() def __getstate__(self): state = super(_PortData, self).__getstate__() for i in _PortData.__slots__: state[i] = getattr(self, i) return state # Note: None of the slots on this class need to be edited, so we # don't need to implement a specialized __setstate__ method, and # can quietly rely on the super() class's implementation. def __getattr__(self, name): """Returns `self.vars[name]` if it exists""" if name in self.vars: return self.vars[name] # Since the base classes don't support getattr, we can just # throw the "normal" AttributeError raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) def arcs(self, active=None): """A list of Arcs in which this Port is a member""" return self._collect_ports(active, self._arcs) def sources(self, active=None): """A list of Arcs in which this Port is a destination""" return self._collect_ports(active, self._sources) def dests(self, active=None): """A list of Arcs in which this Port is a source""" return self._collect_ports(active, self._dests) def _collect_ports(self, active, port_list): # need to call the weakrefs if active is None: return [_a() for _a in port_list] tmp = [] for _a in port_list: a = _a() if a.active == active: tmp.append(a) return tmp def set_value(self, value): """Cannot specify the value of a port""" raise ValueError("Cannot specify the value of a port: '%s'" % self.name) def polynomial_degree(self): """Returns the maximum polynomial degree of all port members""" ans = 0 for v in self.iter_vars(): tmp = v.polynomial_degree() if tmp is None: return None ans = max(ans, tmp) return ans def is_fixed(self): """Return True if all vars/expressions in the Port are fixed""" return all(v.is_fixed() for v in self.iter_vars()) def is_potentially_variable(self): """Return True as ports may (should!) contain variables""" return True def is_binary(self): """Return True if all variables in the Port are binary""" return len(self) and all( v.is_binary() for v in self.iter_vars(expr_vars=True)) def is_integer(self): """Return True if all variables in the Port are integer""" return len(self) and all( v.is_integer() for v in self.iter_vars(expr_vars=True)) def is_continuous(self): """Return True if all variables in the Port are continuous""" return len(self) and all( v.is_continuous() for v in self.iter_vars(expr_vars=True)) def add(self, var, name=None, rule=None, **kwds): """ Add `var` to this Port, casting it to a Pyomo numeric if necessary Arguments --------- var A variable or some `NumericValue` like an expression name: `str` Name to associate with this member of the Port rule: `function` Function implementing the desired expansion procedure for this member. `Port.Equality` by default, other options include `Port.Extensive`. Customs are allowed. kwds Keyword arguments that will be passed to rule """ if var is not None: try: # indexed components are ok, but as_numeric will error on them # make sure they have this attribute var.is_indexed() except AttributeError: var = as_numeric(var) if name is None: name = var.local_name if name in self.vars and self.vars[name] is not None: # don't throw warning if replacing an implicit (None) var logger.warning("Implicitly replacing variable '%s' in Port '%s'.\n" "To avoid this warning, use Port.remove() first." % (name, self.name)) self.vars[name] = var if rule is None: rule = Port.Equality if rule is Port.Extensive: # avoid name collisions if (name.endswith("_split") or name.endswith("_equality") or name == "splitfrac"): raise ValueError( "Extensive variable '%s' on Port '%s' may not end " "with '_split' or '_equality'" % (name, self.name)) self._rules[name] = (rule, kwds) def remove(self, name): """Remove this member from the port""" if name not in self.vars: raise ValueError("Cannot remove member '%s' not in Port '%s'" % (name, self.name)) self.vars.pop(name) self._rules.pop(name) def rule_for(self, name): """Return the rule associated with the given port member""" return self._rules[name][0] def is_equality(self, name): """Return True if the rule for this port member is Port.Equality""" return self.rule_for(name) is Port.Equality def is_extensive(self, name): """Return True if the rule for this port member is Port.Extensive""" return self.rule_for(name) is Port.Extensive def fix(self): """ Fix all variables in the port at their current values. For expressions, fix every variable in the expression. """ for v in self.iter_vars(expr_vars=True, fixed=False): v.fix() def unfix(self): """ Unfix all variables in the port. For expressions, unfix every variable in the expression. """ for v in self.iter_vars(expr_vars=True, fixed=True): v.unfix() free = unfix def iter_vars(self, expr_vars=False, fixed=None, names=False): """ Iterate through every member of the port, going through the indices of indexed members. Arguments --------- expr_vars: `bool` If True, call `identify_variables` on expression type members fixed: `bool` Only include variables/expressions with this type of fixed names: `bool` If True, yield (name, var/expr) pairs """ for name, mem in iteritems(self.vars): if not mem.is_indexed(): itr = (mem,) else: itr = itervalues(mem) for v in itr: if fixed is not None and v.is_fixed() != fixed: continue if expr_vars and v.is_expression_type(): for var in identify_variables(v): if fixed is not None and var.is_fixed() != fixed: continue if names: yield name, var else: yield var else: if names: yield name, v else: yield v def set_split_fraction(self, arc, val, fix=True): """ Set the split fraction value to be used for an arc during arc expansion when using `Port.Extensive`. """ if arc not in self.dests(): raise ValueError("Port '%s' is not a source of Arc '%s', cannot " "set split fraction" % (self.name, arc.name)) self._splitfracs[arc] = (val, fix) def get_split_fraction(self, arc): """ Returns a tuple (val, fix) for the split fraction of this arc that was set via `set_split_fraction` if it exists, and otherwise None. """ res = self._splitfracs.get(arc, None) if res is None: return None else: return res
def _transform_disjunctionData(self, obj, index, transBlock=None): if not obj.active: return # Hull reformulation 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 hull reformulation for " "Disjunction '%s' with OR constraint. " "Must be an XOR!" % obj.name) if transBlock is None: # It's possible that we have already created a transformation block # for another disjunctionData from this same container. If that's # the case, let's use the same transformation block. (Else it will # be really confusing that the XOR constraint goes to that old block # but we create a new one here.) if obj.parent_component()._algebraic_constraint is not None: transBlock = obj.parent_component()._algebraic_constraint().\ parent_block() else: transBlock = self._add_transformation_block(obj.parent_block()) parent_component = obj.parent_component() orConstraint = self._add_xor_constraint(parent_component, transBlock) disaggregationConstraint = transBlock.disaggregationConstraints disaggregationConstraintMap = transBlock._disaggregationConstraintMap # Just because it's unlikely this is what someone meant to do... if len(obj.disjuncts) == 0: raise GDP_Error( "Disjunction '%s' is empty. This is " "likely indicative of a modeling error." % obj.getname(fully_qualified=True, name_buffer=NAME_BUFFER)) # We first go through and collect all the variables that we # are going to disaggregate. varOrder_set = ComponentSet() varOrder = [] varsByDisjunct = ComponentMap() localVarsByDisjunct = ComponentMap() include_fixed_vars = not self._config.assume_fixed_vars_permanent for disjunct in obj.disjuncts: disjunctVars = varsByDisjunct[disjunct] = ComponentSet() for cons in disjunct.component_data_objects( Constraint, active=True, sort=SortComponents.deterministic, descend_into=Block): # [ESJ 02/14/2020] By default, we disaggregate fixed variables # on the philosophy that fixing is not a promise for the future # and we are mathematically wrong if we don't transform these # correctly and someone later unfixes them and keeps playing # with their transformed model. However, the user may have set # assume_fixed_vars_permanent to True in which case we will skip # them for var in EXPR.identify_variables( cons.body, include_fixed=include_fixed_vars): # 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 not var in varOrder_set: varOrder.append(var) varOrder_set.add(var) # check for LocalVars Suffix localVarsByDisjunct = self._get_local_var_suffixes( disjunct, localVarsByDisjunct) # We will disaggregate all variables which are not explicitly declared # as being local. Note however, that we do declare our own disaggregated # variables as local, so they will not be re-disaggregated. varSet = [] # values of localVarsByDisjunct are ComponentSets, so we need this for # determinism (we iterate through the localVars later) localVars = [] for var in varOrder: disjuncts = [d for d in varsByDisjunct if var in varsByDisjunct[d]] # clearly not local if used in more than one disjunct if len(disjuncts) > 1: if __debug__ and logger.isEnabledFor(logging.DEBUG): logger.debug("Assuming '%s' is not a local var since it is" "used in multiple disjuncts." % var.getname(fully_qualified=True, name_buffer=NAME_BUFFER)) varSet.append(var) elif localVarsByDisjunct.get(disjuncts[0]) is not None: if var in localVarsByDisjunct[disjuncts[0]]: localVars.append(var) else: varSet.append(var) 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) orConstraint.add(index, (or_expr, 1)) # map the DisjunctionData to its XOR constraint to mark it as # transformed obj._algebraic_constraint = weakref_ref(orConstraint[index]) for i, var in enumerate(varSet): disaggregatedExpr = 0 for disjunct in obj.disjuncts: if disjunct._transformation_block is None: # Because we called _transform_disjunct in the loop above, # we know that if this isn't transformed it is because it # was cleanly deactivated, and we can just skip it. continue disaggregatedVar = disjunct._transformation_block().\ _disaggregatedVarMap['disaggregatedVar'][var] disaggregatedExpr += disaggregatedVar disaggregationConstraint.add((i, index), var == disaggregatedExpr) # and update the map so that we can find this later. We index by # variable and the particular disjunction because there is a # different one for each disjunction if disaggregationConstraintMap.get(var) is not None: disaggregationConstraintMap[var][ obj] = disaggregationConstraint[(i, index)] else: thismap = disaggregationConstraintMap[var] = ComponentMap() thismap[obj] = disaggregationConstraint[(i, index)] # deactivate for the writers obj.deactivate()
class _PortData(ComponentData): """This class defines the data for a single Port.""" __slots__ = ('vars', '_arcs', '_sources', '_dests', '_rules', '_splitfracs') def __init__(self, component=None): # # These lines represent in-lining of the # following constructors: # - ComponentData # - NumericValue self._component = weakref_ref(component) if (component is not None) \ else None self.vars = {} self._arcs = [] self._sources = [] self._dests = [] self._rules = {} self._splitfracs = ComponentMap() def __getstate__(self): state = super(_PortData, self).__getstate__() for i in _PortData.__slots__: state[i] = getattr(self, i) return state # Note: None of the slots on this class need to be edited, so we # don't need to implement a specialized __setstate__ method, and # can quietly rely on the super() class's implementation. def __getattr__(self, name): """Returns self.vars[name] if it exists""" if name in self.vars: return self.vars[name] # Since the base classes don't support getattr, we can just # throw the "normal" AttributeError raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) def arcs(self, active=None): """A list of Arcs in which this Port is a member""" return self._collect_ports(active, self._arcs) def sources(self, active=None): """A list of Arcs in which this Port is a destination""" return self._collect_ports(active, self._sources) def dests(self, active=None): """A list of Arcs in which this Port is a source""" return self._collect_ports(active, self._dests) def _collect_ports(self, active, port_list): # need to call the weakrefs if active is None: return [_a() for _a in port_list] tmp = [] for _a in port_list: a = _a() if a.active == active: tmp.append(a) return tmp def set_value(self, value): """Cannot specify the value of a port""" raise ValueError("Cannot specify the value of a port: '%s'" % self.name) def polynomial_degree(self): ans = 0 for v in self.iter_vars(): tmp = v.polynomial_degree() if tmp is None: return None ans = max(ans, tmp) return ans def is_fixed(self): """Return True if all vars/expressions in the Port are fixed""" return all(v.is_fixed() for v in self.iter_vars()) def is_potentially_variable(self): """Return True as ports may (should!) contain variables""" return True def is_binary(self): return len(self) and all(v.is_binary() for v in self.iter_vars(expr_vars=True)) def is_integer(self): return len(self) and all(v.is_integer() for v in self.iter_vars(expr_vars=True)) def is_continuous(self): return len(self) and all(v.is_continuous() for v in self.iter_vars(expr_vars=True)) def add(self, var, name=None, rule=None, **kwds): """ Add a variable to this Port. Arguments: var A variable or some NumericValue like an expression name Name to associate with this member of the Port rule Function implementing the desired expansion procedure for this member. Port.Equality by default, other options include Port.Extensive. Customs are allowed **kwds Keyword arguments that will be passed to rule """ if name is None: name = var.local_name if name in self.vars and self.vars[name] is not None: # don't throw warning if replacing an implicit (None) var logger.warning("Implicitly replacing variable '%s' in Port '%s'.\n" "To avoid this warning, use Port.remove() first." % (name, self.name)) self.vars[name] = var if rule is None: rule = Port.Equality if rule is Port.Extensive: # avoid name collisions if (name.endswith("_split") or name.endswith("_equality") or name == "splitfrac"): raise ValueError( "Extensive variable '%s' on Port '%s' may not end " "with '_split' or '_equality'" % (name, self.name)) self._rules[name] = (rule, kwds) def remove(self, name): """Remove this member from the port""" if name not in self.vars: raise ValueError("Cannot remove member '%s' not in Port '%s'" % (name, self.name)) self.vars.pop(name) self._rules.pop(name) def fix(self): """ Fix all variables in the port at their current values. For expressions, fix every variable in the expression. """ for v in self.iter_vars(expr_vars=True, fixed=False): v.fix() def iter_vars(self, expr_vars=False, fixed=True, with_names=False): """ Iterate through every member of the port, going through the indices of indexed members. If expr_vars, call identify_variables on expression type members. If not fixed, exclude fixed variables/expressions. """ for name, mem in iteritems(self.vars): if not mem.is_indexed(): itr = (mem, ) else: itr = itervalues(mem) for v in itr: if not fixed and v.is_fixed(): continue if v.is_expression_type() and expr_vars: for var in identify_variables(v, include_fixed=fixed): if with_names: yield name, var else: yield var else: if with_names: yield name, v else: yield v def set_split_fraction(self, arc, val, fix=True): """ Set the split fraction value for an arc when using Port.Extensive """ if arc not in self.dests(): raise ValueError("Port '%s' is not a source of Arc '%s', cannot " "set split fraction" % (self.name, arc.name)) self._splitfracs[arc] = dict(val=val, fix=fix) def get_split_fraction(self, arc): """ Returns a tuple (val, fix) for the split fraction of this arc if it exists, and otherwise None """ d = self._splitfracs.get(arc, None) if d is None: return None else: return d["val"], d["fix"]