def visiting_potential_leaf(self, node): if node.__class__ in nonpyomo_leaf_types: self.bnds_dict[node] = (node, node) return True, None if node.is_variable_type(): if node.is_fixed(): lb = value(node.value) ub = lb else: lb = value(node.lb) ub = value(node.ub) if lb is None: lb = -math.inf if ub is None: ub = math.inf self.bnds_dict[node] = (lb, ub) return True, None if not node.is_expression_type(): assert is_fixed(node) val = value(node) self.bnds_dict[node] = (val, val) return True, None return False, None
def visiting_potential_leaf(self, node): if node.__class__ in nonpyomo_leaf_types: return True, None if node.is_variable_type(): lb, ub = self.bnds_dict[node] if node.is_binary() or node.is_integer(): """ This bit of code has two purposes: 1) Improve the bounds on binary and integer variables with the fact that they are integer. 2) Account for roundoff error. If the lower bound of a binary variable comes back as 1e-16, the lower bound may actually be 0. This could potentially cause problems when handing the problem to a MIP solver. Some solvers are robust to this, but some may not be and may give the wrong solution. Even if the correct solution is found, this could introduce numerical problems. """ lb = max(math.floor(lb), math.ceil(lb - self.integer_tol)) ub = min(math.ceil(ub), math.floor(ub + self.integer_tol)) if lb < value(node.lb): lb = value( node.lb ) # don't make the bounds worse than the original bounds if ub > value(node.ub): ub = value( node.ub ) # don't make the bounds worse than the original bounds self.bnds_dict[node] = (lb, ub) if self.update_var_bounds: lb, ub = self.bnds_dict[node] if lb != -math.inf: node.setlb(lb) if ub != math.inf: node.setub(ub) return True, None if not node.is_expression_type(): return True, None if node.__class__ in _prop_bnds_root_to_leaf_map: _prop_bnds_root_to_leaf_map[node.__class__](node, self.bnds_dict) else: logger.warning( 'Unsupported expression type for FBBT: {0}. Bounds will not be improved in this part of ' 'the tree.' ''.format(str(type(node)))) return False, None
def fbbt_con(con): """ Feasibility based bounds tightening for a constraint. This function attempts to improve the bounds of each variable in the constraint based on the bounds of the constraint and the bounds of the other variables in the constraint. For example: >>> import pyomo.environ as pe >>> from pyomo.contrib.fbbt.fbbt import fbbt >>> m = pe.ConcreteModel() >>> m.x = pe.Var(bounds=(-1,1)) >>> m.y = pe.Var(bounds=(-2,2)) >>> m.z = pe.Var() >>> m.c = pe.Constraint(expr=m.x*m.y + m.z == 1) >>> fbbt(m.c) >>> print(m.z.lb, m.z.ub) -1.0 3.0 Parameters ---------- con: pyomo.core.base.constraint.Constraint constraint on which to perform fbbt """ bnds_dict = ComponentMap() # a dictionary to store the bounds of # every node in the tree # a walker to propagate bounds from the variables to the root visitorA = _FBBTVisitorLeafToRoot(bnds_dict) visitorA.dfs_postorder_stack(con.body) # Now we need to replace the bounds in bnds_dict for the root # node with the bounds on the constraint (if those bounds are # better). _lb = value(con.lower) _ub = value(con.upper) if _lb is None: _lb = -math.inf if _ub is None: _ub = math.inf lb, ub = bnds_dict[con.body] if _lb > lb: lb = _lb if _ub < ub: ub = _ub bnds_dict[con.body] = (lb, ub) # Now, propagate bounds back from the root to the variables visitorB = _FBBTVisitorRootToLeaf(bnds_dict) visitorB.dfs_postorder_stack(con.body)
def visiting_potential_leaf(self, node): if node.__class__ in nonpyomo_leaf_types: self.val_dict[node] = node if node not in self.der_dict: self.der_dict[node] = 0 return True, node if not node.is_expression_type(): val = value(node) self.val_dict[node] = val if node not in self.der_dict: self.der_dict[node] = 0 return True, val return False, None
def fbbt_block(m, tol=1e-4): """ Feasibility based bounds tightening (FBBT) for a block or model. This loops through all of the constraints in the block and performs FBBT on each constraint (see the docstring for fbbt_con()). Through this processes, any variables whose bounds improve by more than tol are collected, and FBBT is performed again on all constraints involving those variables. This process is continued until no variable bounds are improved by more than tol. Parameters ---------- m: pyomo.core.base.block.Block or pyomo.core.base.PyomoModel.ConcreteModel tol: float """ var_to_con_map = ComponentMap() var_lbs = ComponentMap() var_ubs = ComponentMap() for c in m.component_data_objects(ctype=Constraint, active=True, descend_into=True, sort=True): for v in identify_variables(c.body): if v not in var_to_con_map: var_to_con_map[v] = list() if v.lb is None: var_lbs[v] = -math.inf else: var_lbs[v] = value(v.lb) if v.ub is None: var_ubs[v] = math.inf else: var_ubs[v] = value(v.ub) var_to_con_map[v].append(c) improved_vars = ComponentSet() for c in m.component_data_objects(ctype=Constraint, active=True, descend_into=True, sort=True): fbbt_con(c) for v in identify_variables(c.body): if v.lb is not None: if value(v.lb) > var_lbs[v] + tol: improved_vars.add(v) var_lbs[v] = value(v.lb) if v.ub is not None: if value(v.ub) < var_ubs[v] - tol: improved_vars.add(v) var_ubs[v] = value(v.ub) while len(improved_vars) > 0: v = improved_vars.pop() for c in var_to_con_map[v]: fbbt_con(c) for _v in identify_variables(c.body): if _v.lb is not None: if value(_v.lb) > var_lbs[_v] + tol: improved_vars.add(_v) var_lbs[_v] = value(_v.lb) if _v.ub is not None: if value(_v.ub) < var_ubs[_v] - tol: improved_vars.add(_v) var_ubs[_v] = value(_v.ub)
def fbbt_block(m, tol=1e-4, deactivate_satisfied_constraints=False, update_variable_bounds=True, integer_tol=1e-4): """ Feasibility based bounds tightening (FBBT) for a block or model. This loops through all of the constraints in the block and performs FBBT on each constraint (see the docstring for fbbt_con()). Through this processes, any variables whose bounds improve by more than tol are collected, and FBBT is performed again on all constraints involving those variables. This process is continued until no variable bounds are improved by more than tol. Parameters ---------- m: pyomo.core.base.block.Block or pyomo.core.base.PyomoModel.ConcreteModel tol: float deactivate_satisfied_constraints: bool If deactivate_satisfied_constraints is True and a constraint is always satisfied, then the constranit will be deactivated update_variable_bounds: bool If update_variable_bounds is True, then the bounds on variables will be automatically updated. integer_tol: float If the lower bound computed on a binary variable is less than or equal to integer_tol, then the lower bound is left at 0. Otherwise, the lower bound is increased to 1. If the upper bound computed on a binary variable is greater than or equal to 1-integer_tol, then the upper bound is left at 1. Otherwise the upper bound is decreased to 0. Returns ------- new_var_bounds: ComponentMap A ComponentMap mapping from variables a tuple containing the lower and upper bounds, respectively, computed from FBBT. """ new_var_bounds = ComponentMap() var_to_con_map = ComponentMap() var_lbs = ComponentMap() var_ubs = ComponentMap() for c in m.component_data_objects(ctype=Constraint, active=True, descend_into=True, sort=True): for v in identify_variables(c.body): if v not in var_to_con_map: var_to_con_map[v] = list() if v.lb is None: var_lbs[v] = -math.inf else: var_lbs[v] = value(v.lb) if v.ub is None: var_ubs[v] = math.inf else: var_ubs[v] = value(v.ub) var_to_con_map[v].append(c) improved_vars = ComponentSet() for c in m.component_data_objects(ctype=Constraint, active=True, descend_into=True, sort=True): _new_var_bounds = fbbt_con( c, deactivate_satisfied_constraints=deactivate_satisfied_constraints, update_variable_bounds=update_variable_bounds, integer_tol=integer_tol) new_var_bounds.update(_new_var_bounds) for v, bnds in _new_var_bounds.items(): vlb, vub = bnds if vlb is not None: if vlb > var_lbs[v] + tol: improved_vars.add(v) var_lbs[v] = vlb if vub is not None: if vub < var_ubs[v] - tol: improved_vars.add(v) var_ubs[v] = vub while len(improved_vars) > 0: v = improved_vars.pop() for c in var_to_con_map[v]: _new_var_bounds = fbbt_con( c, deactivate_satisfied_constraints= deactivate_satisfied_constraints, update_variable_bounds=update_variable_bounds, integer_tol=integer_tol) new_var_bounds.update(_new_var_bounds) for _v, bnds in _new_var_bounds.items(): _vlb, _vub = bnds if _vlb is not None: if _vlb > var_lbs[_v] + tol: improved_vars.add(_v) var_lbs[_v] = _vlb if _vub is not None: if _vub < var_ubs[_v] - tol: improved_vars.add(_v) var_ubs[_v] = _vub return new_var_bounds
def fbbt_con(con, deactivate_satisfied_constraints=False, update_variable_bounds=True, integer_tol=1e-4): """ Feasibility based bounds tightening for a constraint. This function attempts to improve the bounds of each variable in the constraint based on the bounds of the constraint and the bounds of the other variables in the constraint. For example: >>> import pyomo.environ as pe >>> from pyomo.contrib.fbbt.fbbt import fbbt >>> m = pe.ConcreteModel() >>> m.x = pe.Var(bounds=(-1,1)) >>> m.y = pe.Var(bounds=(-2,2)) >>> m.z = pe.Var() >>> m.c = pe.Constraint(expr=m.x*m.y + m.z == 1) >>> fbbt(m.c) >>> print(m.z.lb, m.z.ub) -1.0 3.0 Parameters ---------- con: pyomo.core.base.constraint.Constraint constraint on which to perform fbbt deactivate_satisfied_constraints: bool If deactivate_satisfied_constraints is True and the constraint is always satisfied, then the constranit will be deactivated update_variable_bounds: bool If update_variable_bounds is True, then the bounds on variables will be automatically updated. integer_tol: float If the lower bound computed on a binary variable is less than or equal to integer_tol, then the lower bound is left at 0. Otherwise, the lower bound is increased to 1. If the upper bound computed on a binary variable is greater than or equal to 1-integer_tol, then the upper bound is left at 1. Otherwise the upper bound is decreased to 0. Returns ------- new_var_bounds: ComponentMap A ComponentMap mapping from variables a tuple containing the lower and upper bounds, respectively, computed from FBBT. """ if not con.active: return ComponentMap() bnds_dict = ComponentMap( ) # a dictionary to store the bounds of every node in the tree # a walker to propagate bounds from the variables to the root visitorA = _FBBTVisitorLeafToRoot(bnds_dict) visitorA.dfs_postorder_stack(con.body) # Now we need to replace the bounds in bnds_dict for the root # node with the bounds on the constraint (if those bounds are # better). _lb = value(con.lower) _ub = value(con.upper) if _lb is None: _lb = -math.inf if _ub is None: _ub = math.inf # first check if the constraint is always satisfied lb, ub = bnds_dict[con.body] if deactivate_satisfied_constraints: if lb >= _lb and ub <= _ub: con.deactivate() if _lb > lb: lb = _lb if _ub < ub: ub = _ub bnds_dict[con.body] = (lb, ub) # Now, propagate bounds back from the root to the variables visitorB = _FBBTVisitorRootToLeaf( bnds_dict, update_variable_bounds=update_variable_bounds, integer_tol=integer_tol) visitorB.dfs_postorder_stack(con.body) new_var_bounds = ComponentMap() for _node, _bnds in bnds_dict.items(): if _node.__class__ in nonpyomo_leaf_types: continue if _node.is_variable_type(): lb, ub = bnds_dict[_node] if lb == -math.inf: lb = None if ub == math.inf: ub = None new_var_bounds[_node] = (lb, ub) return new_var_bounds