コード例 #1
0
    def _apply_to(self, instance, **kwargs):
        config = self.CONFIG(kwargs)
        if config.tmp and not hasattr(instance,
                                      '_tmp_trivial_deactivated_constrs'):
            instance._tmp_trivial_deactivated_constrs = ComponentSet()
        elif config.tmp:
            logger.warning(
                'Deactivating trivial constraints on the block {} for which '
                'trivial constraints were previously deactivated. '
                'Reversion will affect all deactivated constraints.'.format(
                    instance.name))

        # Trivial constraints are those that do not contain any variables, ie.
        # the polynomial degree is 0
        for constr in instance.component_data_objects(ctype=Constraint,
                                                      active=True,
                                                      descend_into=True):
            repn = generate_standard_repn(constr.body)
            if not repn.is_constant():
                # This constraint is not trivial
                continue

            # We need to check each constraint to sure that it is not violated.
            constr_lb = value(
                constr.lower) if constr.has_lb() else float('-inf')
            constr_ub = value(
                constr.upper) if constr.has_ub() else float('inf')
            constr_value = repn.constant

            # Check if the lower bound is violated outside a given tolerance
            if (constr_value + config.tolerance <= constr_lb):
                if config.ignore_infeasible:
                    continue
                else:
                    raise InfeasibleConstraintException(
                        'Trivial constraint {} violates LB {} ≤ BODY {}.'.
                        format(constr.name, constr_lb, constr_value))

            # Check if the upper bound is violated outside a given tolerance
            if (constr_value >= constr_ub + config.tolerance):
                if config.ignore_infeasible:
                    continue
                else:
                    raise InfeasibleConstraintException(
                        'Trivial constraint {} violates BODY {} ≤ UB {}.'.
                        format(constr.name, constr_value, constr_ub))

            # Constraint is not infeasible. Deactivate it.
            if config.tmp:
                instance._tmp_trivial_deactivated_constrs.add(constr)
            config.return_trivial.append(constr)
            constr.deactivate()
コード例 #2
0
def inv(xl, xu, feasibility_tol):
    """
    The case where xl is very slightly positive but should be very slightly negative (or xu is very slightly negative
    but should be very slightly positive) should not be an issue. Suppose xu is 2 and xl is 1e-15 but should be -1e-15.
    The bounds obtained from this function will be [0.5, 1e15] or [0.5, inf), depending on the value of
    feasibility_tol. The true bounds are (-inf, -1e15] U [0.5, inf), where U is union. The exclusion of (-inf, -1e15]
    should be acceptable. Additionally, it very important to return a non-negative interval when xl is non-negative.
    """
    if xu - xl <= -feasibility_tol:
        raise InfeasibleConstraintException(
            f'lower bound is greater than upper bound in inv; xl: {xl}; xu: {xu}'
        )
    elif xu <= 0 <= xl:
        raise IntervalException(f'Division by zero in inv; xl: {xl}; xu: {xu}')
    elif 0 <= xl <= feasibility_tol:
        # xu must be strictly positive
        ub = inf
        lb = 1.0 / xu
    elif xl > feasibility_tol:
        # xl and xu must be strictly positive
        ub = 1.0 / xl
        lb = 1.0 / xu
    elif -feasibility_tol <= xu <= 0:
        # xl must be strictly negative
        lb = -inf
        ub = 1.0 / xl
    elif xu < -feasibility_tol:
        # xl and xu must be strictly negative
        ub = 1.0 / xl
        lb = 1.0 / xu
    else:
        # everything else
        lb = -inf
        ub = inf
    return lb, ub
コード例 #3
0
    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 InfeasibleConstraintException(
                        '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)
コード例 #4
0
    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 an exception.
                # 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 InfeasibleConstraintException(
                    '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)
コード例 #5
0
def _inverse_power2(zl, zu, xl, xu, feasiblity_tol):
    """
    z = x**y => compute bounds on y
    y = ln(z) / ln(x)

    This function assumes the exponent can be fractional, so x must be positive. This method should not be called
    if the exponent is an integer.
    """
    if xu <= 0:
        raise IntervalException(
            'Cannot raise a negative variable to a fractional power.')
    if (xl > 0 and zu <= 0) or (xl >= 0 and zu < 0):
        raise InfeasibleConstraintException(
            'A positive variable raised to the power of anything must be positive.'
        )
    lba, uba = log(zl, zu)
    lbb, ubb = log(xl, xu)
    yl, yu = div(lba, uba, lbb, ubb, feasiblity_tol)
    return yl, yu
コード例 #6
0
def power(xl, xu, yl, yu, feasibility_tol):
    """
    Compute bounds on x**y.
    """
    if xl > 0:
        """
        If x is always positive, things are simple. We only need to worry about the sign of y.
        """
        if yl < 0 < yu:
            lb = min(xu**yl, xl**yu)
            ub = max(xl**yl, xu**yu)
        elif yl >= 0:
            lb = min(xl**yl, xl**yu)
            ub = max(xu**yl, xu**yu)
        else:  # yu <= 0:
            lb = min(xu**yl, xu**yu)
            ub = max(xl**yl, xl**yu)
    elif xl == 0:
        if yl >= 0:
            lb = min(xl**yl, xl**yu)
            ub = max(xu**yl, xu**yu)
        elif yu <= 0:
            lb, ub = inv(*power(xl, xu, *sub(0, 0, yl, yu), feasibility_tol),
                         feasibility_tol)
        else:
            lb1, ub1 = power(xl, xu, 0, yu, feasibility_tol)
            lb2, ub2 = power(xl, xu, yl, 0, feasibility_tol)
            lb = min(lb1, lb2)
            ub = max(ub1, ub2)
    elif yl == yu and yl == round(yl):
        # the exponent is an integer, so x can be negative
        """
        The logic here depends on several things:
        1) The sign of x
        2) The sign of y
        3) Whether y is even or odd.
        
        There are also special cases to avoid math domain errors.
        """
        y = yl
        if xu <= 0:
            if y < 0:
                if y % 2 == 0:
                    lb = xl**y
                    if xu == 0:
                        ub = inf
                    else:
                        ub = xu**y
                else:
                    if xu == 0:
                        lb = -inf
                        ub = inf
                    else:
                        lb = xu**y
                        ub = xl**y
            else:
                if y % 2 == 0:
                    lb = xu**y
                    ub = xl**y
                else:
                    lb = xl**y
                    ub = xu**y
        else:
            if y < 0:
                if y % 2 == 0:
                    lb = min(xl**y, xu**y)
                    ub = inf
                else:
                    lb = -inf
                    ub = inf
            else:
                if y % 2 == 0:
                    lb = 0
                    ub = max(xl**y, xu**y)
                else:
                    lb = xl**y
                    ub = xu**y
    elif yl == yu:
        # the exponent has to be fractional, so x must be positive
        if xu < 0:
            msg = 'Cannot raise a negative number to the power of {0}.\n'.format(
                yl)
            msg += 'The upper bound of a variable raised to the power of {0} is {1}'.format(
                yl, xu)
            raise InfeasibleConstraintException(msg)
        xl = 0
        lb, ub = power(xl, xu, yl, yu, feasibility_tol)
    else:
        lb = -inf
        ub = inf

    return lb, ub
コード例 #7
0
def _inverse_power1(zl, zu, yl, yu, orig_xl, orig_xu, feasibility_tol):
    """
    z = x**y => compute bounds on x.

    First, start by computing bounds on x with

        x = exp(ln(z) / y)

    However, if y is an integer, then x can be negative, so there are several special cases. See the docs below.
    """
    xl, xu = log(zl, zu)
    xl, xu = div(xl, xu, yl, yu, feasibility_tol)
    xl, xu = exp(xl, xu)

    # if y is an integer, then x can be negative
    if yl == yu and yl == round(yl):  # y is a fixed integer
        y = yl
        if y == 0:
            # Anything to the power of 0 is 1, so if y is 0, then x can be anything
            # (assuming zl <= 1 <= zu, which is enforced when traversing the tree in the other direction)
            xl = -inf
            xu = inf
        elif y % 2 == 0:
            """
            if y is even, then there are two primary cases (note that it is much easier to walk through these
            while looking at plots):
            case 1: y is positive
                x**y is convex, positive, and symmetric. The bounds on x depend on the lower bound of z. If zl <= 0, 
                then xl should simply be -xu. However, if zl > 0, then we may be able to say something better. For 
                example, if the original lower bound on x is positive, then we can keep xl computed from 
                x = exp(ln(z) / y). Furthermore, if the original lower bound on x is larger than -xl computed from 
                x = exp(ln(z) / y), then we can still keep the xl computed from x = exp(ln(z) / y). Similar logic
                applies to the upper bound of x.
            case 2: y is negative
                The ideas are similar to case 1.
            """
            if zu + feasibility_tol < 0:
                raise InfeasibleConstraintException(
                    'Infeasible. Anything to the power of {0} must be positive.'
                    .format(y))
            if y > 0:
                if zu <= 0:
                    _xl = 0
                    _xu = 0
                elif zl <= 0:
                    _xl = -xu
                    _xu = xu
                else:
                    if orig_xl <= -xl + feasibility_tol:
                        _xl = -xu
                    else:
                        _xl = xl
                    if orig_xu < xl - feasibility_tol:
                        _xu = -xl
                    else:
                        _xu = xu
                xl = _xl
                xu = _xu
            else:
                if zu == 0:
                    raise InfeasibleConstraintException(
                        'Infeasible. Anything to the power of {0} must be positive.'
                        .format(y))
                elif zl <= 0:
                    _xl = -inf
                    _xu = inf
                else:
                    if orig_xl <= -xl + feasibility_tol:
                        _xl = -xu
                    else:
                        _xl = xl
                    if orig_xu < xl - feasibility_tol:
                        _xu = -xl
                    else:
                        _xu = xu
                xl = _xl
                xu = _xu
        else:  # y % 2 == 1
            """
            y is odd. 
            Case 1: y is positive
                x**y is monotonically increasing. If y is positive, then we can can compute the bounds on x using
                x = z**(1/y) and the signs on xl and xu depend on the signs of zl and zu.
            Case 2: y is negative
                Again, this is easier to visualize with a plot. x**y approaches zero when x approaches -inf or inf. 
                Thus, if zl < 0 < zu, then no bounds can be inferred for x. If z is positive (zl >=0 ) then we can
                use the bounds computed from x = exp(ln(z) / y). If z is negative (zu <= 0), then we live in the
                bottom left quadrant, xl depends on zu, and xu depends on zl.
            """
            if y > 0:
                xl = abs(zl)**(1.0 / y)
                xl = math.copysign(xl, zl)
                xu = abs(zu)**(1.0 / y)
                xu = math.copysign(xu, zu)
            else:
                if zl >= 0:
                    pass
                elif zu <= 0:
                    if zu == 0:
                        xl = -inf
                    else:
                        xl = -abs(zu)**(1.0 / y)
                    if zl == 0:
                        xu = -inf
                    else:
                        xu = -abs(zl)**(1.0 / y)
                else:
                    xl = -inf
                    xu = inf

    return xl, xu
コード例 #8
0
ファイル: fbbt.py プロジェクト: sdesai1097/pyomo
def fbbt_con(con,
             deactivate_satisfied_constraints=False,
             integer_tol=1e-5,
             infeasible_tol=1e-8):
    """
    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
    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.
    infeasible_tol: float
        If the bounds computed on the body of a constraint violate the bounds of the constraint by more than
        infeasible_tol, then the constraint is considered infeasible and an exception is raised.

    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

    lb, ub = bnds_dict[con.body]

    # check if the constraint is infeasible
    if lb > _ub + infeasible_tol or ub < _lb - infeasible_tol:
        raise InfeasibleConstraintException(
            'Detected an infeasible constraint during FBBT: {0}'.format(
                str(con)))

    # check if the constraint is always satisfied
    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, 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