def _get_expr_from_pyomo_repn(self, repn, max_degree=2): referenced_vars = ComponentSet() degree = repn.polynomial_degree() if (degree is None) or (degree > max_degree): raise DegreeError( 'XpressDirect does not support expressions of degree {0}.'. format(degree)) # NOTE: xpress's python interface only allows for expresions # with native numeric types. Others, like numpy.float64, # will cause an exception when constructing expressions if len(repn.linear_vars) > 0: referenced_vars.update(repn.linear_vars) new_expr = self._xpress.Sum( float(coef) * self._pyomo_var_to_solver_var_map[var] for coef, var in zip(repn.linear_coefs, repn.linear_vars)) else: new_expr = 0.0 for coef, (x, y) in zip(repn.quadratic_coefs, repn.quadratic_vars): new_expr += float(coef) * self._pyomo_var_to_solver_var_map[ x] * self._pyomo_var_to_solver_var_map[y] referenced_vars.add(x) referenced_vars.add(y) new_expr += repn.constant return new_expr, referenced_vars
def _apply_to(self, instance, **kwds): config = self.CONFIG(kwds) if config.tmp and not hasattr(instance, '_tmp_propagate_fixed'): instance._tmp_propagate_fixed = ComponentSet() eq_var_map, relevant_vars = _build_equality_set(instance) #: ComponentSet: The set of all fixed variables fixed_vars = ComponentSet((v for v in relevant_vars if v.fixed)) newly_fixed = _detect_fixed_variables(instance) if config.tmp: instance._tmp_propagate_fixed.update(newly_fixed) fixed_vars.update(newly_fixed) processed = ComponentSet() # Go through each fixed variable to propagate the 'fixed' status to all # equality-linked variabes. for v1 in fixed_vars: # If we have already processed the variable, skip it. if v1 in processed: continue eq_set = eq_var_map.get(v1, ComponentSet([v1])) for v2 in eq_set: if (v2.fixed and value(v1) != value(v2)): raise ValueError( 'Variables {} and {} have conflicting fixed ' 'values of {} and {}, but are linked by ' 'equality constraints.'.format(v1.name, v2.name, value(v1), value(v2))) elif not v2.fixed: v2.fix(value(v1)) if config.tmp: instance._tmp_propagate_fixed.add(v2) # Add all variables in the equality set to the set of processed # variables. processed.update(eq_set)
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 _get_expr_from_pyomo_repn(self, repn, max_degree=2): referenced_vars = ComponentSet() degree = repn.polynomial_degree() if (degree is None) or (degree > max_degree): raise DegreeError( 'CPLEXDirect does not support expressions of degree {0}.'. format(degree)) new_expr = _CplexExpr() if len(repn.linear_vars) > 0: referenced_vars.update(repn.linear_vars) new_expr.variables.extend(self._pyomo_var_to_ndx_map[i] for i in repn.linear_vars) new_expr.coefficients.extend(repn.linear_coefs) for i, v in enumerate(repn.quadratic_vars): x, y = v new_expr.q_coefficients.append(repn.quadratic_coefs[i]) new_expr.q_variables1.append(self._pyomo_var_to_ndx_map[x]) new_expr.q_variables2.append(self._pyomo_var_to_ndx_map[y]) referenced_vars.add(x) referenced_vars.add(y) new_expr.offset = repn.constant return new_expr, referenced_vars
def disjunctive_obbt(model, solver): """Provides Optimality-based bounds tightening to a model using a solver.""" model._disjuncts_to_process = list(model.component_data_objects( ctype=Disjunct, active=True, descend_into=(Block, Disjunct), descent_order=TraversalStrategy.BreadthFirstSearch)) if model.type() == Disjunct: model._disjuncts_to_process.insert(0, model) linear_var_set = ComponentSet() for constr in model.component_data_objects( Constraint, active=True, descend_into=(Block, Disjunct)): if constr.body.polynomial_degree() in linear_degrees: linear_var_set.update(identify_variables(constr.body, include_fixed=False)) model._disj_bnds_linear_vars = list(linear_var_set) for disj_idx, disjunct in enumerate(model._disjuncts_to_process): var_bnds = obbt_disjunct(model, disj_idx, solver) if var_bnds is not None: # Add bounds to the disjunct if not hasattr(disjunct, '_disj_var_bounds'): # No bounds had been computed before. Attach the bounds dictionary. disjunct._disj_var_bounds = var_bnds else: # Update the bounds dictionary. for var, new_bnds in var_bnds.items(): old_lb, old_ub = disjunct._disj_var_bounds.get(var, (-inf, inf)) new_lb, new_ub = new_bnds disjunct._disj_var_bounds[var] = (max(old_lb, new_lb), min(old_ub, new_ub)) else: disjunct.deactivate() # prune disjunct
def test_update(self): cset = ComponentSet() self.assertEqual(len(cset), 0) cset.update(self._components) self.assertEqual(len(cset), len(self._components)) for c in self._components: self.assertTrue(c in cset)
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 _get_expr_from_pyomo_repn(self, repn, max_degree=2): referenced_vars = ComponentSet() degree = repn.polynomial_degree() if (degree is None) or (degree > max_degree): raise DegreeError( 'Mosek does not support expressions of degree {0}.'.format( degree)) # if len(repn.linear_vars) > 0: referenced_vars.update(repn.linear_vars) indexes = [] [ indexes.append(self._pyomo_var_to_solver_var_map[i]) for i in repn.linear_vars ] new_expr = [list(repn.linear_coefs), indexes, repn.constant] qsubi = [] qsubj = [] qval = [] for i, v in enumerate(repn.quadratic_vars): x, y = v qsubj.append(self._pyomo_var_to_solver_var_map[x]) qsubi.append(self._pyomo_var_to_solver_var_map[y]) qval.append(repn.quadratic_coefs[i] * ((qsubi == qsubj) + 1)) referenced_vars.add(x) referenced_vars.add(y) new_expr.extend([qval, qsubi, qsubj]) return new_expr, referenced_vars
def test_clear(self): cset = ComponentSet() self.assertEqual(len(cset), 0) cset.update(self._components) self.assertEqual(len(cset), len(self._components)) cset.clear() self.assertEqual(len(cset), 0)
def _get_expr_from_pyomo_repn(self, repn, max_degree=2): referenced_vars = ComponentSet() degree = repn.polynomial_degree() if (degree is None) or (degree > max_degree): raise DegreeError( 'GurobiDirect does not support expressions of degree {0}.'. format(degree)) if len(repn.linear_vars) > 0: referenced_vars.update(repn.linear_vars) new_expr = self._gurobipy.LinExpr(repn.linear_coefs, [ self._pyomo_var_to_solver_var_map[i] for i in repn.linear_vars ]) else: new_expr = 0.0 for i, v in enumerate(repn.quadratic_vars): x, y = v new_expr += repn.quadratic_coefs[ i] * self._pyomo_var_to_solver_var_map[ x] * self._pyomo_var_to_solver_var_map[y] referenced_vars.add(x) referenced_vars.add(y) new_expr += repn.constant return new_expr, referenced_vars
def test_len(self): cset = ComponentSet() self.assertEqual(len(cset), 0) cset.update(self._components) self.assertEqual(len(cset), len(self._components)) cset = ComponentSet(self._components) self.assertEqual(len(cset), len(self._components)) self.assertTrue(len(self._components) > 0)
def test_iter(self): cset = ComponentSet() self.assertEqual(list(iter(cset)), []) cset.update(self._components) ids_seen = set() for c in cset: ids_seen.add(id(c)) self.assertEqual(ids_seen, set(id(c) for c in self._components))
def _apply_to(self, instance, tmp=False): """Apply the transformation. Args: instance (Block): the block on which to search for x == y constraints. Note that variables may be located anywhere in the model. tmp (bool, optional): Whether the bound modifications will be temporary Returns: None """ if tmp and not hasattr(instance, '_tmp_propagate_original_bounds'): instance._tmp_propagate_original_bounds = Suffix( direction=Suffix.LOCAL) eq_var_map, relevant_vars = _build_equality_set(instance) processed = ComponentSet() # Go through each variable in an equality set to propagate the variable # bounds to all equality-linked variables. for var in relevant_vars: # If we have already processed the variable, skip it. if var in processed: continue var_equality_set = eq_var_map.get(var, ComponentSet([var])) #: variable lower bounds in the equality set lbs = [v.lb for v in var_equality_set if v.has_lb()] max_lb = max(lbs) if len(lbs) > 0 else None #: variable upper bounds in the equality set ubs = [v.ub for v in var_equality_set if v.has_ub()] min_ub = min(ubs) if len(ubs) > 0 else None # Check for error due to bound cross-over if max_lb is not None and min_ub is not None and max_lb > min_ub: # the lower bound is above the upper bound. Raise a ValueError. # get variable with the highest lower bound v1 = next(v for v in var_equality_set if v.lb == max_lb) # get variable with the lowest upper bound v2 = next(v for v in var_equality_set if v.ub == min_ub) raise ValueError( 'Variable {} has a lower bound {} ' ' > the upper bound {} of variable {}, ' 'but they are linked by equality constraints.' .format(v1.name, value(v1.lb), value(v2.ub), v2.name)) for v in var_equality_set: if tmp: # TODO warn if overwriting instance._tmp_propagate_original_bounds[v] = ( v.lb, v.ub) v.setlb(max_lb) v.setub(min_ub) processed.update(var_equality_set)
def detect_unfixed_discrete_vars(model): """Detect unfixed discrete variables in use on the model.""" var_set = ComponentSet() for constr in model.component_data_objects( Constraint, active=True, descend_into=True): var_set.update( v for v in EXPR.identify_variables( constr.body, include_fixed=False) if v.is_binary()) return var_set
def detect_unfixed_discrete_vars(model): """Detect unfixed discrete variables in use on the model.""" var_set = ComponentSet() for constr in model.component_data_objects( Constraint, active=True, descend_into=True): var_set.update( v for v in EXPR.identify_variables( constr.body, include_fixed=False) if not v.is_continuous()) return var_set
def test_discard(self): cset = ComponentSet() self.assertEqual(len(cset), 0) cset.update(self._components) self.assertEqual(len(cset), len(self._components)) for i, c in enumerate(self._components): cset.discard(c) self.assertEqual(len(cset), len(self._components) - (i + 1)) for c in self._components: self.assertTrue(c not in cset) cset.discard(c)
def test_remove(self): cset = ComponentSet() self.assertEqual(len(cset), 0) cset.update(self._components) self.assertEqual(len(cset), len(self._components)) for i, c in enumerate(self._components): cset.remove(c) self.assertEqual(len(cset), len(self._components) - (i + 1)) for c in self._components: self.assertTrue(c not in cset) with self.assertRaises(KeyError): cset.remove(c)
def _apply_to(self, instance, tmp=False): """Apply the transformation. Args: instance (Block): the block on which to search for x == y constraints. Note that variables may be located anywhere in the model. tmp (bool, optional): Whether the variable fixing will be temporary Returns: None Raises: ValueError: if two fixed variables x = y have different values. """ if tmp and not hasattr(instance, '_tmp_propagate_fixed'): instance._tmp_propagate_fixed = ComponentSet() eq_var_map, relevant_vars = _build_equality_set(instance) #: ComponentSet: The set of all fixed variables fixed_vars = ComponentSet((v for v in relevant_vars if v.fixed)) newly_fixed = _detect_fixed_variables(instance) if tmp: instance._tmp_propagate_fixed.update(newly_fixed) fixed_vars.update(newly_fixed) processed = ComponentSet() # Go through each fixed variable to propagate the 'fixed' status to all # equality-linked variabes. for v1 in fixed_vars: # If we have already processed the variable, skip it. if v1 in processed: continue eq_set = eq_var_map.get(v1, ComponentSet([v1])) for v2 in eq_set: if (v2.fixed and value(v1) != value(v2)): raise ValueError( 'Variables {} and {} have conflicting fixed ' 'values of {} and {}, but are linked by ' 'equality constraints.' .format(v1.name, v2.name, value(v1), value(v2))) elif not v2.fixed: v2.fix(value(v1)) if tmp: instance._tmp_propagate_fixed.add(v2) # Add all variables in the equality set to the set of processed # variables. processed.update(eq_set)
def obbt_disjunct(orig_model, idx, solver): model = orig_model.clone() # Fix the disjunct to be active disjunct = model._disjuncts_to_process[idx] disjunct.indicator_var.fix(1) for obj in model.component_data_objects(Objective, active=True): obj.deactivate() # Deactivate nonlinear constraints for constr in model.component_data_objects( Constraint, active=True, descend_into=(Block, Disjunct)): if constr.body.polynomial_degree() not in linear_degrees: constr.deactivate() # Only look at the variables participating in active constraints within the scope relevant_var_set = ComponentSet() for constr in disjunct.component_data_objects(Constraint, active=True): relevant_var_set.update(identify_variables(constr.body, include_fixed=False)) TransformationFactory('gdp.bigm').apply_to(model) model._var_bounding_obj = Objective(expr=1, sense=minimize) for var in relevant_var_set: model._var_bounding_obj.set_value(expr=var) var_lb = solve_bounding_problem(model, solver) if var_lb is None: return None # bounding problem infeasible model._var_bounding_obj.set_value(expr=-var) var_ub = solve_bounding_problem(model, solver) if var_ub is None: return None # bounding problem infeasible else: var_ub = -var_ub # sign correction var.setlb(var_lb) var.setub(var_ub) # Maps original variable --> (new computed LB, new computed UB) var_bnds = ComponentMap( ((orig_var, ( clone_var.lb if clone_var.has_lb() else -inf, clone_var.ub if clone_var.has_ub() else inf)) for orig_var, clone_var in zip( orig_model._disj_bnds_linear_vars, model._disj_bnds_linear_vars) if clone_var in relevant_var_set) ) return var_bnds
def obbt_disjunct(orig_model, idx, solver): model = orig_model.clone() # Fix the disjunct to be active disjunct = model._disjuncts_to_process[idx] disjunct.indicator_var.fix(1) for obj in model.component_data_objects(Objective, active=True): obj.deactivate() # Deactivate nonlinear constraints for constr in model.component_data_objects(Constraint, active=True, descend_into=(Block, Disjunct)): if constr.body.polynomial_degree() not in linear_degrees: constr.deactivate() # Only look at the variables participating in active constraints within the scope relevant_var_set = ComponentSet() for constr in disjunct.component_data_objects(Constraint, active=True): relevant_var_set.update( identify_variables(constr.body, include_fixed=False)) TransformationFactory('gdp.bigm').apply_to(model) model._var_bounding_obj = Objective(expr=1, sense=minimize) for var in relevant_var_set: model._var_bounding_obj.set_value(expr=var) var_lb = solve_bounding_problem(model, solver) if var_lb is None: return None # bounding problem infeasible model._var_bounding_obj.set_value(expr=-var) var_ub = solve_bounding_problem(model, solver) if var_ub is None: return None # bounding problem infeasible else: var_ub = -var_ub # sign correction var.setlb(var_lb) var.setub(var_ub) # Maps original variable --> (new computed LB, new computed UB) var_bnds = ComponentMap( ((orig_var, (clone_var.lb if clone_var.has_lb() else -inf, clone_var.ub if clone_var.has_ub() else inf)) for orig_var, clone_var in zip(orig_model._disj_bnds_linear_vars, model._disj_bnds_linear_vars) if clone_var in relevant_var_set)) return var_bnds
def vars_to_eliminate_list(x): if isinstance(x, (Var, _VarData)): if not x.is_indexed(): return ComponentSet([x]) ans = ComponentSet() for j in x.index_set(): ans.add(x[j]) return ans elif hasattr(x, '__iter__'): ans = ComponentSet() for i in x: ans.update(vars_to_eliminate_list(i)) return ans else: raise ValueError("Expected Var or list of Vars." "\n\tRecieved %s" % type(x))
def _apply_to(self, instance, **kwds): config = self.CONFIG(kwds) if config.tmp and not hasattr(instance, '_tmp_propagate_original_bounds'): instance._tmp_propagate_original_bounds = Suffix( direction=Suffix.LOCAL) eq_var_map, relevant_vars = _build_equality_set(instance) processed = ComponentSet() # Go through each variable in an equality set to propagate the variable # bounds to all equality-linked variables. for var in relevant_vars: # If we have already processed the variable, skip it. if var in processed: continue var_equality_set = eq_var_map.get(var, ComponentSet([var])) #: variable lower bounds in the equality set lbs = [v.lb for v in var_equality_set if v.has_lb()] max_lb = max(lbs) if len(lbs) > 0 else None #: variable upper bounds in the equality set ubs = [v.ub for v in var_equality_set if v.has_ub()] min_ub = min(ubs) if len(ubs) > 0 else None # Check for error due to bound cross-over if max_lb is not None and min_ub is not None and max_lb > min_ub: # the lower bound is above the upper bound. Raise a ValueError. # get variable with the highest lower bound v1 = next(v for v in var_equality_set if v.lb == max_lb) # get variable with the lowest upper bound v2 = next(v for v in var_equality_set if v.ub == min_ub) raise ValueError( 'Variable {} has a lower bound {} ' ' > the upper bound {} of variable {}, ' 'but they are linked by equality constraints.'.format( v1.name, value(v1.lb), value(v2.ub), v2.name)) for v in var_equality_set: if config.tmp: # TODO warn if overwriting instance._tmp_propagate_original_bounds[v] = (v.lb, v.ub) v.setlb(max_lb) v.setub(min_ub) processed.update(var_equality_set)
def _apply_to(self, instance, **kwds): config = self.CONFIG(kwds) if config.tmp and not hasattr(instance, '_tmp_propagate_original_bounds'): instance._tmp_propagate_original_bounds = Suffix( direction=Suffix.LOCAL) eq_var_map, relevant_vars = _build_equality_set(instance) processed = ComponentSet() # Go through each variable in an equality set to propagate the variable # bounds to all equality-linked variables. for var in relevant_vars: # If we have already processed the variable, skip it. if var in processed: continue var_equality_set = eq_var_map.get(var, ComponentSet([var])) #: variable lower bounds in the equality set lbs = [v.lb for v in var_equality_set if v.has_lb()] max_lb = max(lbs) if len(lbs) > 0 else None #: variable upper bounds in the equality set ubs = [v.ub for v in var_equality_set if v.has_ub()] min_ub = min(ubs) if len(ubs) > 0 else None # Check for error due to bound cross-over if max_lb is not None and min_ub is not None and max_lb > min_ub: # the lower bound is above the upper bound. Raise a ValueError. # get variable with the highest lower bound v1 = next(v for v in var_equality_set if v.lb == max_lb) # get variable with the lowest upper bound v2 = next(v for v in var_equality_set if v.ub == min_ub) raise ValueError( 'Variable {} has a lower bound {} ' ' > the upper bound {} of variable {}, ' 'but they are linked by equality constraints.' .format(v1.name, value(v1.lb), value(v2.ub), v2.name)) for v in var_equality_set: if config.tmp: # TODO warn if overwriting instance._tmp_propagate_original_bounds[v] = ( v.lb, v.ub) v.setlb(max_lb) v.setub(min_ub) processed.update(var_equality_set)
def test_eq(self): cset1 = ComponentSet() self.assertEqual(cset1, set()) self.assertTrue(cset1 == set()) self.assertNotEqual(cset1, list()) self.assertFalse(cset1 == list()) self.assertNotEqual(cset1, tuple()) self.assertFalse(cset1 == tuple()) self.assertNotEqual(cset1, dict()) self.assertFalse(cset1 == dict()) cset1.update(self._components) self.assertNotEqual(cset1, set()) self.assertFalse(cset1 == set()) self.assertNotEqual(cset1, list()) self.assertFalse(cset1 == list()) self.assertNotEqual(cset1, tuple()) self.assertFalse(cset1 == tuple()) self.assertNotEqual(cset1, dict()) self.assertFalse(cset1 == dict()) self.assertTrue(cset1 == cset1) self.assertEqual(cset1, cset1) cset2 = ComponentSet(self._components) self.assertTrue(cset2 == cset1) self.assertFalse(cset2 != cset1) self.assertEqual(cset2, cset1) self.assertTrue(cset1 == cset2) self.assertFalse(cset1 != cset2) self.assertEqual(cset1, cset2) cset2.remove(self._components[0]) self.assertFalse(cset2 == cset1) self.assertTrue(cset2 != cset1) self.assertNotEqual(cset2, cset1) self.assertFalse(cset1 == cset2) self.assertTrue(cset1 != cset2) self.assertNotEqual(cset1, cset2)
def _get_expr_from_pyomo_repn(self, repn, max_degree=2): referenced_vars = ComponentSet() degree = repn.polynomial_degree() if (degree is None) or (degree > max_degree): raise DegreeError('GurobiDirect does not support expressions of degree {0}.'.format(degree)) if len(repn.linear_vars) > 0: referenced_vars.update(repn.linear_vars) new_expr = self._gurobipy.LinExpr(repn.linear_coefs, [self._pyomo_var_to_solver_var_map[i] for i in repn.linear_vars]) else: new_expr = 0.0 for i,v in enumerate(repn.quadratic_vars): x,y = v new_expr += repn.quadratic_coefs[i] * self._pyomo_var_to_solver_var_map[x] * self._pyomo_var_to_solver_var_map[y] referenced_vars.add(x) referenced_vars.add(y) new_expr += repn.constant return new_expr, referenced_vars
def disjunctive_obbt(model, solver): """Provides Optimality-based bounds tightening to a model using a solver.""" model._disjuncts_to_process = list( model.component_data_objects( ctype=Disjunct, active=True, descend_into=(Block, Disjunct), descent_order=TraversalStrategy.BreadthFirstSearch)) if model.type() == Disjunct: model._disjuncts_to_process.insert(0, model) linear_var_set = ComponentSet() for constr in model.component_data_objects(Constraint, active=True, descend_into=(Block, Disjunct)): if constr.body.polynomial_degree() in linear_degrees: linear_var_set.update( identify_variables(constr.body, include_fixed=False)) model._disj_bnds_linear_vars = list(linear_var_set) for disj_idx, disjunct in enumerate(model._disjuncts_to_process): var_bnds = obbt_disjunct(model, disj_idx, solver) if var_bnds is not None: # Add bounds to the disjunct if not hasattr(disjunct, '_disj_var_bounds'): # No bounds had been computed before. Attach the bounds dictionary. disjunct._disj_var_bounds = var_bnds else: # Update the bounds dictionary. for var, new_bnds in var_bnds.items(): old_lb, old_ub = disjunct._disj_var_bounds.get( var, (-inf, inf)) new_lb, new_ub = new_bnds disjunct._disj_var_bounds[var] = (max(old_lb, new_lb), min(old_ub, new_ub)) else: disjunct.deactivate() # prune disjunct
def _apply_to(self, instance, **kwds): config = self.CONFIG(kwds) if config.tmp and not hasattr(instance, '_tmp_propagate_fixed'): instance._tmp_propagate_fixed = ComponentSet() eq_var_map, relevant_vars = _build_equality_set(instance) #: ComponentSet: The set of all fixed variables fixed_vars = ComponentSet((v for v in relevant_vars if v.fixed)) newly_fixed = _detect_fixed_variables(instance) if config.tmp: instance._tmp_propagate_fixed.update(newly_fixed) fixed_vars.update(newly_fixed) processed = ComponentSet() # Go through each fixed variable to propagate the 'fixed' status to all # equality-linked variabes. for v1 in fixed_vars: # If we have already processed the variable, skip it. if v1 in processed: continue eq_set = eq_var_map.get(v1, ComponentSet([v1])) for v2 in eq_set: if (v2.fixed and value(v1) != value(v2)): raise ValueError( 'Variables {} and {} have conflicting fixed ' 'values of {} and {}, but are linked by ' 'equality constraints.' .format(v1.name, v2.name, value(v1), value(v2))) elif not v2.fixed: v2.fix(value(v1)) if config.tmp: instance._tmp_propagate_fixed.add(v2) # Add all variables in the equality set to the set of processed # variables. processed.update(eq_set)
def _get_expr_from_pyomo_repn(self, repn, max_degree=2): referenced_vars = ComponentSet() degree = repn.polynomial_degree() if (degree is None) or (degree > max_degree): raise DegreeError('CPLEXDirect does not support expressions of degree {0}.'.format(degree)) new_expr = _CplexExpr() if len(repn.linear_vars) > 0: referenced_vars.update(repn.linear_vars) new_expr.variables.extend(self._pyomo_var_to_ndx_map[i] for i in repn.linear_vars) new_expr.coefficients.extend(repn.linear_coefs) for i, v in enumerate(repn.quadratic_vars): x, y = v new_expr.q_coefficients.append(repn.quadratic_coefs[i]) new_expr.q_variables1.append(self._pyomo_var_to_ndx_map[x]) new_expr.q_variables2.append(self._pyomo_var_to_ndx_map[y]) referenced_vars.add(x) referenced_vars.add(y) new_expr.offset = repn.constant return new_expr, referenced_vars
class BoundsManager(object): def __init__(self, comp): self._vars = ComponentSet() self._saved_bounds = list() if comp.type() == Constraint: if comp.is_indexed(): for c in comp.values(): self._vars.update(identify_variables(c.body)) else: self._vars.update(identify_variables(comp.body)) else: for c in comp.component_data_objects(Constraint, descend_into=True, active=True, sort=True): self._vars.update(identify_variables(c.body)) def save_bounds(self): bnds = ComponentMap() for v in self._vars: bnds[v] = (v.lb, v.ub) self._saved_bounds.append(bnds) def pop_bounds(self, ndx=-1): bnds = self._saved_bounds.pop(ndx) for v, _bnds in bnds.items(): lb, ub = _bnds v.setlb(lb) v.setub(ub) def load_bounds(self, bnds, save_current_bounds=True): if save_current_bounds: self.save_bounds() for v, _bnds in bnds.items(): if v in self._vars: lb, ub = _bnds v.setlb(lb) v.setub(ub)
class BoundsManager(object): def __init__(self, comp): self._vars = ComponentSet() self._saved_bounds = list() if comp.type() == Constraint: if comp.is_indexed(): for c in comp.values(): self._vars.update(identify_variables(c.body)) else: self._vars.update(identify_variables(comp.body)) else: for c in comp.component_data_objects(Constraint, descend_into=True, active=True, sort=True): self._vars.update(identify_variables(c.body)) def save_bounds(self): bnds = ComponentMap() for v in self._vars: bnds[v] = (v.lb, v.ub) self._saved_bounds.append(bnds) def pop_bounds(self, ndx=-1): bnds = self._saved_bounds.pop(ndx) for v, _bnds in bnds.items(): lb, ub = _bnds v.setlb(lb) v.setub(ub) def load_bounds(self, bnds, save_current_bounds=True): if save_current_bounds: self.save_bounds() for v, _bnds in bnds.items(): if v in self._vars: lb, ub = _bnds v.setlb(lb) v.setub(ub)
def _fourier_motzkin_elimination(self, constraints, vars_to_eliminate): """Performs FME on the constraint list in the argument (which is assumed to be all >= constraints and stored in the dictionary representation), projecting out each of the variables in vars_to_eliminate""" # We only need to eliminate variables that actually appear in # this set of constraints... Revise our list. vars_that_appear = [] for cons in constraints: std_repn = cons['body'] if not std_repn.is_linear(): # as long as none of vars_that_appear are in the nonlinear part, # we are actually okay. nonlinear_vars = ComponentSet( v for two_tuple in std_repn.quadratic_vars for v in two_tuple) nonlinear_vars.update(v for v in std_repn.nonlinear_vars) for var in nonlinear_vars: if var in vars_to_eliminate: raise RuntimeError( "Variable %s appears in a nonlinear " "constraint. The Fourier-Motzkin " "Elimination transformation can only " "be used to eliminate variables " "which only appear linearly." % var.name) for var in std_repn.linear_vars: if var in vars_to_eliminate: vars_that_appear.append(var) # we actually begin the recursion here while vars_that_appear: # first var we will project out the_var = vars_that_appear.pop() # we are 'reorganizing' the constraints, we sort based on the sign # of the coefficient of the_var: This tells us whether we have # the_var <= other stuff or vice versa. leq_list = [] geq_list = [] waiting_list = [] for cons in constraints: leaving_var_coef = cons['map'].get(the_var) if leaving_var_coef is None or leaving_var_coef == 0: waiting_list.append(cons) continue # we know the constraint is a >= constraint, using that # assumption below. # NOTE: neither of the scalar multiplications below flip the # constraint. So we are sure to have only geq constraints # forever, which is exactly what we want. if leaving_var_coef < 0: leq_list.append( self._nonneg_scalar_multiply_linear_constraint( cons, -1.0 / leaving_var_coef)) else: geq_list.append( self._nonneg_scalar_multiply_linear_constraint( cons, 1.0 / leaving_var_coef)) constraints = waiting_list for leq in leq_list: for geq in geq_list: constraints.append(self._add_linear_constraints(leq, geq)) return constraints
def _apply_to(self, model, detect_fixed_vars=True): """Apply the transformation to the given model.""" # Generate the equality sets eq_var_map = _build_equality_set(model) # Detect and process fixed variables. if detect_fixed_vars: _fix_equality_fixed_variables(model) # Generate aggregation infrastructure model._var_aggregator_info = Block( doc="Holds information for the variable aggregation " "transformation system.") z = model._var_aggregator_info.z = VarList(doc="Aggregated variables.") # Map of the aggregate var to the equalty set (ComponentSet) z_to_vars = model._var_aggregator_info.z_to_vars = ComponentMap() # Map of variables to their corresponding aggregate var var_to_z = model._var_aggregator_info.var_to_z = ComponentMap() processed_vars = ComponentSet() # TODO This iteritems is sorted by the variable name of the key in # order to preserve determinism. Unfortunately, var.name() is an # expensive operation right now. for var, eq_set in sorted(eq_var_map.items(), key=lambda tup: tup[0].name): if var in processed_vars: continue # Skip already-process variables # This would be weird. The variable hasn't been processed, but is # in the map. Raise an exception. assert var_to_z.get(var, None) is None z_agg = z.add() z_to_vars[z_agg] = eq_set var_to_z.update(ComponentMap((v, z_agg) for v in eq_set)) # Set the bounds of the aggregate variable based on the bounds of # the variables in its equality set. z_agg.setlb(max_if_not_None(v.lb for v in eq_set if v.has_lb())) z_agg.setub(min_if_not_None(v.ub for v in eq_set if v.has_ub())) # Set the fixed status of the aggregate var fixed_vars = [v for v in eq_set if v.fixed] if fixed_vars: # Check to make sure all the fixed values are the same. if any(var.value != fixed_vars[0].value for var in fixed_vars[1:]): raise ValueError( "Aggregate variable for equality set is fixed to " "multiple different values: %s" % (fixed_vars,)) z_agg.fix(fixed_vars[0].value) # Check that the fixed value lies within bounds. if z_agg.has_lb() and z_agg.value < value(z_agg.lb): raise ValueError( "Aggregate variable for equality set is fixed to " "a value less than its lower bound: %s < LB %s" % (z_agg.value, value(z_agg.lb)) ) if z_agg.has_ub() and z_agg.value > value(z_agg.ub): raise ValueError( "Aggregate variable for equality set is fixed to " "a value greater than its upper bound: %s > UB %s" % (z_agg.value, value(z_agg.ub)) ) else: # Set the value to be the average of the values within the # bounds only if the value is not already fixed. values_within_bounds = [ v.value for v in eq_set if ( v.value is not None and ((z_agg.has_lb() and v.value >= value(z_agg.lb)) or not z_agg.has_lb()) and ((z_agg.has_ub() and v.value <= value(z_agg.ub)) or not z_agg.has_ub()) )] num_vals = len(values_within_bounds) z_agg.value = ( sum(val for val in values_within_bounds) / num_vals) \ if num_vals > 0 else None processed_vars.update(eq_set) # Do the substitution substitution_map = {id(var): z_var for var, z_var in var_to_z.items()} for constr in model.component_data_objects( ctype=Constraint, active=True ): new_body = ExpressionReplacementVisitor( substitute=substitution_map ).dfs_postorder_stack(constr.body) constr.set_value((constr.lower, new_body, constr.upper)) for objective in model.component_data_objects( ctype=Objective, active=True ): new_expr = ExpressionReplacementVisitor( substitute=substitution_map ).dfs_postorder_stack(objective.expr) objective.set_value(new_expr)
def build_model_size_report(model): """Build a model size report object.""" report = ModelSizeReport() activated_disjunctions = ComponentSet() activated_disjuncts = ComponentSet() fixed_true_disjuncts = ComponentSet() activated_constraints = ComponentSet() activated_vars = ComponentSet() new_containers = (model,) while new_containers: new_activated_disjunctions = ComponentSet() new_activated_disjuncts = ComponentSet() new_fixed_true_disjuncts = ComponentSet() new_activated_constraints = ComponentSet() for container in new_containers: (next_activated_disjunctions, next_fixed_true_disjuncts, next_activated_disjuncts, next_activated_constraints ) = _process_activated_container(container) new_activated_disjunctions.update(next_activated_disjunctions) new_activated_disjuncts.update(next_activated_disjuncts) new_fixed_true_disjuncts.update(next_fixed_true_disjuncts) new_activated_constraints.update(next_activated_constraints) new_containers = ((new_activated_disjuncts - activated_disjuncts) | (new_fixed_true_disjuncts - fixed_true_disjuncts)) activated_disjunctions.update(new_activated_disjunctions) activated_disjuncts.update(new_activated_disjuncts) fixed_true_disjuncts.update(new_fixed_true_disjuncts) activated_constraints.update(new_activated_constraints) activated_vars.update( var for constr in activated_constraints for var in EXPR.identify_variables( constr.body, include_fixed=False)) activated_vars.update( disj.indicator_var for disj in activated_disjuncts) report.activated = Container() report.activated.variables = len(activated_vars) report.activated.binary_variables = sum( 1 for v in activated_vars if v.is_binary()) report.activated.integer_variables = sum( 1 for v in activated_vars if v.is_integer()) report.activated.continuous_variables = sum( 1 for v in activated_vars if v.is_continuous()) report.activated.disjunctions = len(activated_disjunctions) report.activated.disjuncts = len(activated_disjuncts) report.activated.constraints = len(activated_constraints) report.activated.nonlinear_constraints = sum( 1 for c in activated_constraints if c.body.polynomial_degree() not in (1, 0)) report.overall = Container() block_like = (Block, Disjunct) all_vars = ComponentSet( model.component_data_objects(Var, descend_into=block_like)) report.overall.variables = len(all_vars) report.overall.binary_variables = sum(1 for v in all_vars if v.is_binary()) report.overall.integer_variables = sum( 1 for v in all_vars if v.is_integer()) report.overall.continuous_variables = sum( 1 for v in all_vars if v.is_continuous()) report.overall.disjunctions = sum( 1 for d in model.component_data_objects( Disjunction, descend_into=block_like)) report.overall.disjuncts = sum( 1 for d in model.component_data_objects( Disjunct, descend_into=block_like)) report.overall.constraints = sum( 1 for c in model.component_data_objects( Constraint, descend_into=block_like)) report.overall.nonlinear_constraints = sum( 1 for c in model.component_data_objects( Constraint, descend_into=block_like) if c.body.polynomial_degree() not in (1, 0)) report.warning = Container() report.warning.unassociated_disjuncts = sum( 1 for d in model.component_data_objects( Disjunct, descend_into=block_like) if not d.indicator_var.fixed and d not in activated_disjuncts) return report
def _apply_to(self, model, **kwds): """Apply the transformation to the given model.""" config = self.CONFIG(kwds.pop('options', {})) config.set_value(kwds) integer_vars = list(v for v in model.component_data_objects( ctype=Var, descend_into=(Block, Disjunct)) if v.is_integer() and not v.fixed) if len(integer_vars) == 0: logger.info( "Model has no free integer variables. No reformulation needed." ) return vars_on_constr = ComponentSet() for c in model.component_data_objects(ctype=Constraint, descend_into=(Block, Disjunct), active=True): vars_on_constr.update( v for v in identify_variables(c.body, include_fixed=False) if v.is_integer()) if config.ignore_unused: num_vars_not_on_constr = len(integer_vars) - len(vars_on_constr) if num_vars_not_on_constr > 0: logger.info( "%s integer variables on the model are not attached to any constraints. " "Ignoring unused variables.") integer_vars = list(vars_on_constr) logger.info("Reformulating integer variables using the %s strategy." % config.strategy) # Set up reformulation block blk_name = unique_component_name(model, "_int_to_binary_reform") reform_block = Block( doc="Holds variables and constraints for reformulating " "integer variables to binary variables.") setattr(model, blk_name, reform_block) reform_block.int_var_set = RangeSet(0, len(integer_vars) - 1) reform_block.new_binary_var = Var( Any, domain=Binary, dense=False, initialize=0, doc="Binary variable with index (int_var_idx, idx)") reform_block.integer_to_binary_constraint = Constraint( reform_block.int_var_set, doc="Equality constraints mapping the binary variable values " "to the integer variable value.") # check that variables are bounded for idx, int_var in enumerate(integer_vars): if not (int_var.has_lb() and int_var.has_ub()): raise ValueError( "Integer variable %s is missing an " "upper or lower bound. LB: %s; UB: %s. " "Integer to binary reformulation does not support unbounded integer variables." % (int_var.name, int_var.lb, int_var.ub)) # do the reformulation highest_power = int(floor(log(value(int_var.ub - int_var.lb), 2))) # TODO potentially fragile due to floating point reform_block.integer_to_binary_constraint.add( idx, expr=int_var == sum(reform_block.new_binary_var[idx, pwr] * (2**pwr) for pwr in range(0, highest_power + 1)) + int_var.lb) # Relax the original integer variable if config.relax_integrality: int_var.domain = Reals logger.info("Reformulated %s integer variables using " "%s binary variables and %s constraints." % (len(integer_vars), len(reform_block.new_binary_var), len(reform_block.integer_to_binary_constraint)))
def _apply_to(self, model, detect_fixed_vars=True): """Apply the transformation to the given model.""" # Generate the equality sets eq_var_map = _build_equality_set(model) # Detect and process fixed variables. if detect_fixed_vars: _fix_equality_fixed_variables(model) # Generate aggregation infrastructure model._var_aggregator_info = Block( doc="Holds information for the variable aggregation " "transformation system.") z = model._var_aggregator_info.z = VarList(doc="Aggregated variables.") # Map of the aggregate var to the equalty set (ComponentSet) z_to_vars = model._var_aggregator_info.z_to_vars = ComponentMap() # Map of variables to their corresponding aggregate var var_to_z = model._var_aggregator_info.var_to_z = ComponentMap() processed_vars = ComponentSet() # TODO This iteritems is sorted by the variable name of the key in # order to preserve determinism. Unfortunately, var.name() is an # expensive operation right now. for var, eq_set in sorted(eq_var_map.items(), key=lambda tup: tup[0].name): if var in processed_vars: continue # Skip already-process variables # This would be weird. The variable hasn't been processed, but is # in the map. Raise an exception. assert var_to_z.get(var, None) is None z_agg = z.add() z_to_vars[z_agg] = eq_set var_to_z.update(ComponentMap((v, z_agg) for v in eq_set)) # Set the bounds of the aggregate variable based on the bounds of # the variables in its equality set. z_agg.setlb(max_if_not_None(v.lb for v in eq_set if v.has_lb())) z_agg.setub(min_if_not_None(v.ub for v in eq_set if v.has_ub())) # Set the fixed status of the aggregate var fixed_vars = [v for v in eq_set if v.fixed] if fixed_vars: # Check to make sure all the fixed values are the same. if any(var.value != fixed_vars[0].value for var in fixed_vars[1:]): raise ValueError( "Aggregate variable for equality set is fixed to " "multiple different values: %s" % (fixed_vars, )) z_agg.fix(fixed_vars[0].value) # Check that the fixed value lies within bounds. if z_agg.has_lb() and z_agg.value < value(z_agg.lb): raise ValueError( "Aggregate variable for equality set is fixed to " "a value less than its lower bound: %s < LB %s" % (z_agg.value, value(z_agg.lb))) if z_agg.has_ub() and z_agg.value > value(z_agg.ub): raise ValueError( "Aggregate variable for equality set is fixed to " "a value greater than its upper bound: %s > UB %s" % (z_agg.value, value(z_agg.ub))) else: # Set the value to be the average of the values within the # bounds only if the value is not already fixed. values_within_bounds = [ v.value for v in eq_set if (v.value is not None and ( (z_agg.has_lb() and v.value >= value(z_agg.lb)) or not z_agg.has_lb()) and ( (z_agg.has_ub() and v.value <= value(z_agg.ub)) or not z_agg.has_ub())) ] num_vals = len(values_within_bounds) z_agg.value = ( sum(val for val in values_within_bounds) / num_vals) \ if num_vals > 0 else None processed_vars.update(eq_set) # Do the substitution substitution_map = {id(var): z_var for var, z_var in var_to_z.items()} for constr in model.component_data_objects(ctype=Constraint, active=True): new_body = ExpressionReplacementVisitor( substitute=substitution_map).dfs_postorder_stack(constr.body) constr.set_value((constr.lower, new_body, constr.upper)) for objective in model.component_data_objects(ctype=Objective, active=True): new_expr = ExpressionReplacementVisitor( substitute=substitution_map).dfs_postorder_stack( objective.expr) objective.set_value(new_expr)
def build_model_size_report(model): """Build a model size report object.""" report = ModelSizeReport() activated_disjunctions = ComponentSet() activated_disjuncts = ComponentSet() fixed_true_disjuncts = ComponentSet() activated_constraints = ComponentSet() activated_vars = ComponentSet() new_containers = (model, ) while new_containers: new_activated_disjunctions = ComponentSet() new_activated_disjuncts = ComponentSet() new_fixed_true_disjuncts = ComponentSet() new_activated_constraints = ComponentSet() for container in new_containers: (next_activated_disjunctions, next_fixed_true_disjuncts, next_activated_disjuncts, next_activated_constraints ) = _process_activated_container(container) new_activated_disjunctions.update(next_activated_disjunctions) new_activated_disjuncts.update(next_activated_disjuncts) new_fixed_true_disjuncts.update(next_fixed_true_disjuncts) new_activated_constraints.update(next_activated_constraints) new_containers = ((new_activated_disjuncts - activated_disjuncts) | (new_fixed_true_disjuncts - fixed_true_disjuncts)) activated_disjunctions.update(new_activated_disjunctions) activated_disjuncts.update(new_activated_disjuncts) fixed_true_disjuncts.update(new_fixed_true_disjuncts) activated_constraints.update(new_activated_constraints) activated_vars.update( var for constr in activated_constraints for var in EXPR.identify_variables(constr.body, include_fixed=False)) activated_vars.update(disj.indicator_var for disj in activated_disjuncts) report.activated = Container() report.activated.variables = len(activated_vars) report.activated.binary_variables = sum(1 for v in activated_vars if v.is_binary()) report.activated.integer_variables = sum( 1 for v in activated_vars if v.is_integer() and not v.is_binary()) report.activated.continuous_variables = sum(1 for v in activated_vars if v.is_continuous()) report.activated.disjunctions = len(activated_disjunctions) report.activated.disjuncts = len(activated_disjuncts) report.activated.constraints = len(activated_constraints) report.activated.nonlinear_constraints = sum( 1 for c in activated_constraints if c.body.polynomial_degree() not in (1, 0)) report.overall = Container() block_like = (Block, Disjunct) all_vars = ComponentSet( model.component_data_objects(Var, descend_into=block_like)) report.overall.variables = len(all_vars) report.overall.binary_variables = sum(1 for v in all_vars if v.is_binary()) report.overall.integer_variables = sum( 1 for v in all_vars if v.is_integer() and not v.is_binary()) report.overall.continuous_variables = sum(1 for v in all_vars if v.is_continuous()) report.overall.disjunctions = sum(1 for d in model.component_data_objects( Disjunction, descend_into=block_like)) report.overall.disjuncts = sum(1 for d in model.component_data_objects( Disjunct, descend_into=block_like)) report.overall.constraints = sum(1 for c in model.component_data_objects( Constraint, descend_into=block_like)) report.overall.nonlinear_constraints = sum( 1 for c in model.component_data_objects(Constraint, descend_into=block_like) if c.body.polynomial_degree() not in (1, 0)) report.warning = Container() report.warning.unassociated_disjuncts = sum( 1 for d in model.component_data_objects(Disjunct, descend_into=block_like) if not d.indicator_var.fixed and d not in activated_disjuncts) return report
class GDP_Disjunct_Fixer(Transformation): """Fix disjuncts to their current logical values. This reclassifies all disjuncts as ctype Block and deactivates the constraints and disjunctions within inactive disjuncts. """ def __init__(self, *args, **kwargs): # TODO This uses an integer tolerance. At some point, these should be # standardized. self.integer_tolerance = kwargs.pop('int_tol', 1E-6) super(GDP_Disjunct_Fixer, self).__init__(*args, **kwargs) self._transformedDisjuncts = ComponentSet() alias('gdp.fix_disjuncts', doc=textwrap.fill(textwrap.dedent(__doc__.strip()))) def _apply_to(self, instance): """Apply the transformation to the given instance. The instance ctype is expected to be Block, Disjunct, or Disjunction. For a Block or Disjunct, the transformation will fix all disjuncts found in disjunctions within the container. """ t = instance if not t.active: return # do nothing for inactive containers # screen for allowable instance types if (type(t) not in (_DisjunctData, _BlockData, _DisjunctionData) and t.type() not in (Disjunct, Block, Disjunction)): raise GDP_Error( "Target %s was not a Block, Disjunct, or Disjunction. " "It was of type %s and can't be transformed." % (t.name, type(t))) # if the object is indexed, transform all of its _ComponentData if t.is_indexed(): for obj in itervalues(t): self._transformObject(obj) else: self._transformObject(t) def _transformObject(self, obj): # If the object is a disjunction, transform it. if obj.type() == Disjunction and not obj.is_indexed(): self._transformDisjunctionData(obj) # Otherwise, treat it like a container and transform its contents. else: self._transformContainer(obj) def _transformDisjunctionData(self, disjunction): # the sum of all the indicator variable values of disjuncts in the # disjunction logical_sum = sum( value(disj.indicator_var) for disj in disjunction.disjuncts) # Check that the disjunctions are not being fixed to an infeasible # realization. if disjunction.xor and not logical_sum == 1: # for XOR disjunctions, the sum of all disjunct values should be 1 raise GDP_Error( "Disjunction %s violated. " "Expected 1 disjunct to be active, but %s were active." % (disjunction.name, logical_sum)) elif not logical_sum >= 1: # for non-XOR disjunctions, the sum of all disjunct values should # be at least 1 raise GDP_Error("Disjunction %s violated. " "Expected at least 1 disjunct to be active, " "but none were active.") else: # disjunction is in feasible realization. Deactivate it. disjunction.deactivate() # Process the disjuncts associatd with the disjunction that have not # already been transformed. for disj in ComponentSet( disjunction.disjuncts) - self._transformedDisjuncts: self._transformDisjunctData(disj) # Update the set of transformed disjuncts with those from this # disjunction self._transformedDisjuncts.update(disjunction.disjuncts) def _transformDisjunctData(self, obj): """Fix the disjunct either to a Block or a deactivated Block.""" if fabs(value(obj.indicator_var) - 1) <= self.integer_tolerance: # Disjunct is active. Convert to Block. obj.parent_block().reclassify_component_type(obj, Block) obj.indicator_var.fix(1) # Process the components attached to this disjunct. self._transformContainer(obj) elif fabs(value(obj.indicator_var)) <= self.integer_tolerance: obj.parent_block().reclassify_component_type(obj, Block) obj.indicator_var.fix(0) # Deactivate all constituent constraints and disjunctions # HACK I do not deactivate the whole block because some writers # do not look for variables in deactivated blocks. for constr in obj.component_objects(ctype=(Constraint, Disjunction), active=True, descend_into=True): constr.deactivate() else: raise ValueError( 'Non-binary indicator variable value %s for disjunct %s' % (obj.name, value(obj.indicator_var))) def _transformContainer(self, obj): """Find all disjunctions in the container and transform them.""" for disjunction in obj.component_data_objects(ctype=Disjunction, active=True, descend_into=Block): self._transformDisjunctionData(disjunction)
def test_str(self): cset = ComponentSet() self.assertEqual(str(cset), "ComponentSet([])") cset.update(self._components) str(cset)
def _apply_to(self, model, **kwds): """Apply the transformation to the given model.""" config = self.CONFIG(kwds.pop('options', {})) config.set_value(kwds) integer_vars = list( v for v in model.component_data_objects( ctype=Var, descend_into=(Block, Disjunct)) if v.is_integer() and not v.fixed) if len(integer_vars) == 0: logger.info("Model has no free integer variables. No reformulation needed.") return vars_on_constr = ComponentSet() for c in model.component_data_objects( ctype=Constraint, descend_into=(Block, Disjunct), active=True): vars_on_constr.update(v for v in identify_variables(c.body, include_fixed=False) if v.is_integer()) if config.ignore_unused: num_vars_not_on_constr = len(integer_vars) - len(vars_on_constr) if num_vars_not_on_constr > 0: logger.info( "%s integer variables on the model are not attached to any constraints. " "Ignoring unused variables." ) integer_vars = list(vars_on_constr) logger.info( "Reformulating integer variables using the %s strategy." % config.strategy) # Set up reformulation block blk_name = unique_component_name(model, "_int_to_binary_reform") reform_block = Block( doc="Holds variables and constraints for reformulating " "integer variables to binary variables." ) setattr(model, blk_name, reform_block) reform_block.int_var_set = RangeSet(0, len(integer_vars) - 1) reform_block.new_binary_var = Var( Any, domain=Binary, dense=False, doc="Binary variable with index (int_var_idx, idx)") reform_block.integer_to_binary_constraint = Constraint( reform_block.int_var_set, doc="Equality constraints mapping the binary variable values " "to the integer variable value.") # check that variables are bounded and non-negative for idx, int_var in enumerate(integer_vars): if not (int_var.has_lb() and int_var.has_ub()): raise ValueError( "Integer variable %s is missing an " "upper or lower bound. LB: %s; UB: %s. " "Integer to binary reformulation does not support unbounded integer variables." % (int_var.name, int_var.lb, int_var.ub)) if int_var.lb < 0: raise ValueError( "Integer variable %s can be negative. " "Integer to binary reformulation currently only supports non-negative integer " "variables." % (int_var.name,) ) # do the reformulation highest_power = int(floor(log(value(int_var.ub), 2))) # TODO potentially fragile due to floating point reform_block.integer_to_binary_constraint.add( idx, expr=int_var == sum( reform_block.new_binary_var[idx, pwr] * (2 ** pwr) for pwr in range(0, highest_power + 1))) # Relax the original integer variable int_var.domain = NonNegativeReals logger.info( "Reformulated %s integer variables using " "%s binary variables and %s constraints." % (len(integer_vars), len(reform_block.new_binary_var), len(reform_block.integer_to_binary_constraint)))