Beispiel #1
0
    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
Beispiel #2
0
    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
Beispiel #3
0
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)
Beispiel #4
0
    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
Beispiel #5
0
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)
Beispiel #6
0
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
Beispiel #7
0
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