Beispiel #1
0
 def compute_alpha(self, problem, expr, ctx):
     xs = self._collect_expr_variables(expr)
     x_bounds = []
     for x in xs:
         x_view = problem.variable_view(x)
         x_l = x_view.lower_bound()
         x_u = x_view.upper_bound()
         if x_l is None or x_u is None:
             raise ValueError(
                 'Variables must be bounded: name={}, lb={}, ub={}'.format(
                     x.name, x_l, x_u))
         x_bounds.append(Interval(x_l, x_u))
     tree_data = expr.expression_tree_data()
     f = tree_data.eval(x_bounds)
     H = f.hessian(x_bounds, [1.0])
     n = len(xs)
     min_ = 0
     H = [Interval.zero() + i for i in H]
     for i in range(n):
         h = H[i * n:(i + 1) * n]
         v = h[i].lower_bound - sum(
             max(abs(h[j].lower_bound), abs(h[j].upper_bound))
             for j in range(n) if j != i)
         if v < min_:
             min_ = v
     alpha = max(0, -0.5 * min_)
     return alpha
Beispiel #2
0
def _univariate_term_bound(a, b, x_bound):
    """Return the bound of the univariate term ax^2 + bx"""
    lower_bound = x_bound.lower_bound
    upper_bound = x_bound.upper_bound

    if lower_bound is None or isinf(lower_bound):
        univariate_lower_bound = -inf
    else:
        univariate_lower_bound = a * lower_bound**2 + b * lower_bound

    if upper_bound is None or isinf(upper_bound):
        univariate_upper_bound = inf
    else:
        univariate_upper_bound = a * upper_bound**2 + b * upper_bound

    if -b / (2 * a) in x_bound:
        t = -(b**2) / (4 * a)
        new_lower_bound = min(univariate_lower_bound, univariate_upper_bound,
                              t)
        new_upper_bound = max(univariate_lower_bound, univariate_upper_bound,
                              t)
        return Interval(new_lower_bound, new_upper_bound)
    new_lower_bound = min(univariate_lower_bound, univariate_upper_bound)
    new_upper_bound = max(univariate_lower_bound, univariate_upper_bound)
    return Interval(new_lower_bound, new_upper_bound)
def perform_fbbt(problem, maxiter, timelimit, objective_upper_bound=None,
                 branching_variable=None):
    """Perform FBBT on `problem` with the given `maxiter` and `timelimit`."""
    bounds = ExpressionDict(problem)
    bounds_tightener = BoundsTightener(
        FBBTStopCriterion(max_iter=maxiter, timelimit=timelimit),
    )

    for variable in problem.variables:
        lb = problem.lower_bound(variable)
        ub = problem.upper_bound(variable)
        bounds[variable] = Interval(lb, ub)

    # For all intents and purposes here an infinite upper bound is the same
    # as no upper bound.
    if objective_upper_bound is not None and is_inf(objective_upper_bound):
        objective_upper_bound = None

    if objective_upper_bound is not None:
        root_expr = problem.objective.root_expr
        expr_bounds = Interval(None, objective_upper_bound)
        if root_expr not in bounds:
            bounds[root_expr] = expr_bounds
        else:
            existing_bounds = bounds[root_expr]
            new_bounds = existing_bounds.intersect(expr_bounds)
            bounds[root_expr] = new_bounds

    def get_bound(b):
        def _f(expr):
            return b[expr]
        return _f

    def set_bound(b):
        def _f(expr, value):
            b[expr] = value
        return _f

    _initialize_bounds(problem, bounds, get_bound(bounds), set_bound(bounds))

    try:
        with timeout(timelimit, 'Timeout in FBBT'):
            try:
                bounds_tightener.tighten(
                    problem,
                    bounds,
                    branching_variable=branching_variable,
                    objective_changed=objective_upper_bound is not None,
                )
            except EmptyIntervalError:
                pass
    except TimeoutError:
        pass

    _manage_infinity_bounds(
        problem, bounds, get_bound(bounds), set_bound(bounds)
    )
    return bounds
def test_objective_bound(visitor, a, b):
    lb, ub = min(a, b), max(a, b)
    assume(lb < ub)
    child = pe.Var()
    o = Objective('obj', children=[child])
    bounds = ComponentMap()
    bounds[child] = Interval(lb, ub)
    matched, result = visitor.visit_expression(o, bounds)
    assert matched
    assert result == Interval(lb, ub)
Beispiel #5
0
def _inequality_bounds_and_expr(expr):
    if len(expr._args_) == 2:
        (lhs, rhs) = expr._args_
        if is_numeric(lhs):
            return Interval(numeric_value(lhs), None), rhs
        else:
            return Interval(None, numeric_value(rhs)), lhs
    elif len(expr._args_) == 3:
        (lhs, ex, rhs) = expr._args_
        return Interval(numeric_value(lhs), numeric_value(rhs)), ex
    else:
        raise ValueError('Malformed InequalityExpression')
Beispiel #6
0
    def apply(self, expr, bounds):
        assert len(expr.args) == 2
        _, expo = expr.args
        if type(expo) not in nonpyomo_leaf_types:
            if not expo.is_constant():
                return Interval(None, None)
            expo = expo.value

        is_even = almosteq(expo % 2, 0)
        is_positive = expo > 0
        if is_even and is_positive:
            return Interval(0, None)
        return Interval(None, None)
Beispiel #7
0
    def apply(self, expr, convexity, _mono, bounds):
        child = expr.args[0]
        child_bounds = bounds[child]
        cvx = convexity[child]

        concave_domain = Interval(-1, 0)
        if child_bounds in concave_domain and cvx.is_concave():
            return Convexity.Concave

        convex_domain = Interval(0, 1)
        if child_bounds in convex_domain and cvx.is_convex():
            return Convexity.Convex

        return Convexity.Unknown
def test_constraint_bound(visitor, a, b, c, d):
    e_lb, e_ub = min(a, b), max(a, b)
    c_lb, c_ub = min(c, d), max(c, d)
    assume(max(e_lb, c_lb) <= min(e_ub, c_ub))
    child = pe.Var()

    cons = Constraint('c0',
                      lower_bound=c_lb,
                      upper_bound=c_ub,
                      children=[child])
    bounds = ComponentMap()
    bounds[child] = Interval(e_lb, e_ub)
    matched, result = visitor.visit_expression(cons, bounds)
    expected = Interval(max(e_lb, c_lb), min(c_ub, e_ub))
    assert result == expected
Beispiel #9
0
def intervals(draw, allow_infinity=True):
    a = draw(reals(allow_infinity=allow_infinity))
    if np.isinf(a):
        allow_infinity = False
    b = draw(reals(allow_infinity=allow_infinity))
    lb, ub = min(a, b), max(a, b)
    return Interval(lb, ub)
Beispiel #10
0
 def handle_result(self, expr, value, bounds):
     if value is None:
         new_bounds = Interval(None, None)
     else:
         new_bounds = value
     bounds[expr] = new_bounds
     return True
def test_even_pow_negative_bound(visitor, base, n):
    c = -2 * n
    expr = base**c
    bounds = ComponentMap()
    matched, result = visitor.visit_expression(expr, bounds)
    assert matched
    assert result == Interval(None, None)
def test_even_pow_non_const_bound(visitor, base, expo):
    assume(not expo.is_constant())
    expr = base**expo
    bounds = ComponentMap()
    matched, result = visitor.visit_expression(expr, bounds)
    assert matched
    assert result == Interval(None, None)
Beispiel #13
0
class TestAbs(object):
    @pytest.mark.parametrize('it,expected', [
        (Interval(1, 2), Interval(1, 2)),
        (Interval(-3, -2), Interval(2, 3)),
        (Interval(-3, 2), Interval(0, 3)),
        (Interval(-2, 3), Interval(0, 3)),
    ])
    def test_abs(self, it, expected):
        assert expected == abs(it)
def test_variable_bound(visitor, a, b):
    lb = min(a, b)
    ub = max(a, b)
    var = pe.Var(bounds=(lb, ub))
    var.construct()
    matched, result = visitor.visit_expression(var, None)
    assert matched
    assert result == Interval(lb, ub)
Beispiel #15
0
def _variable_bound_from_univariate_lower_bound(a, b, c):
    t = c / a + (b**2) / (4 * a**2)

    if a > 0:
        return Interval(None, None)
        # if c + (b**2) / (4*a) <= 0:
        #     return Interval(None, None)
        # new_upper_bound = Interval(None, -sqrt(t, RM.RN) - b/(2*a))
        # new_lower_bound = Interval(sqrt(t, RM.RN) - b/(2*a), None)
        # return new_upper_bound.intersect(new_lower_bound)

    # a < 0
    if c + (b**2) / (4 * a) > 0:
        # The bound would be empty which is something that shouldn't happen.
        # Return [-inf, inf] so we don't tighten the bounds
        return Interval(None, None)

    return Interval(-sqrt(t, RM.RN) - b / (2 * a),
                    sqrt(t, RM.RN) - b / (2 * a))
def test_division_bound(visitor, bound, expected):
    num = pe.Var()
    child = pe.Var()
    expr = num / child
    bounds = ComponentMap()
    bounds[num] = Interval(1.0, 1.0)
    bounds[child] = bound
    matched, result = visitor.visit_expression(expr, bounds)
    assert matched
    assert result == expected
Beispiel #17
0
    def apply(self, expr, bounds):
        expr_bound = bounds[expr]
        child_bounds = {}
        if len(expr.terms) > self.max_expr_children:
            return None

        terms = expr.terms
        for term_idx, term in enumerate(terms):
            var1 = term.var1
            var2 = term.var2
            siblings_bound = sum(
                self._term_bound(t, bounds) for i, t in enumerate(terms)
                if i != term_idx)
            term_bound = (expr_bound - siblings_bound) / term.coefficient
            if var1 is var2:
                term_bound = term_bound.intersect(Interval(0, None))
                upper_bound = term_bound.sqrt().upper_bound
                new_bound = Interval(-upper_bound, upper_bound)

                if id(var1) in child_bounds:
                    existing = child_bounds[id(var1)]
                    child_bounds[id(var1)] = existing.intersect(new_bound)
                else:
                    child_bounds[id(var1)] = Interval(new_bound.lower_bound,
                                                      new_bound.upper_bound)

            else:
                new_bound_var1 = term_bound / bounds[var2]
                new_bound_var2 = term_bound / bounds[var1]

                if id(var1) in child_bounds:
                    existing = child_bounds[id(var1)]
                    child_bounds[id(var1)] = existing.intersect(new_bound_var1)
                else:
                    child_bounds[id(var1)] = new_bound_var1

                if id(var2) in child_bounds:
                    existing = child_bounds[id(var2)]
                    child_bounds[id(var2)] = existing.intersect(new_bound_var2)
                else:
                    child_bounds[id(var2)] = new_bound_var2

        return [child_bounds[id(v)] for v in expr.args]
Beispiel #18
0
    def apply(self, expr, convexity, monotonicity, bounds):
        g = expr.args[0]
        cvx_f = Convexity.Linear
        cvx_g = convexity[g]

        mono_f = Monotonicity.Constant
        mono_g = monotonicity[g]

        bounds_f = Interval(1.0, 1.0)
        bounds_g = bounds[g]

        return _division_convexity(cvx_f, cvx_g, mono_f, mono_g, bounds_f,
                                   bounds_g)
Beispiel #19
0
    def _underestimate_bilinear_term(self, problem, term, ctx):
        bilinear_aux_vars = ctx.metadata[BILINEAR_AUX_VAR_META]
        x_expr = term.var1
        y_expr = term.var2

        xy_tuple = self._bilinear_tuple(x_expr, y_expr)
        w = self._get_bilinear_aux_var(problem, ctx, xy_tuple)

        if w is None:
            x_l = problem.lower_bound(x_expr)
            x_u = problem.upper_bound(x_expr)

            y_l = problem.lower_bound(y_expr)
            y_u = problem.upper_bound(y_expr)

            if term.var1 == term.var2:
                assert np.isclose(x_l, y_l) and np.isclose(x_u, y_u)
                w_bounds = Interval(x_l, x_u) ** 2
            else:
                w_bounds = Interval(x_l, x_u) * Interval(y_l, y_u)

            w = Variable(
                self._format_aux_name(term.var1, term.var2),
                w_bounds.lower_bound,
                w_bounds.upper_bound,
                Domain.REAL,
            )

            reference = BilinearTermReference(x_expr, y_expr)

            w.reference = reference
            bilinear_aux_vars[xy_tuple] = w
            constraints = self._generate_envelope_constraints(problem, term, w)
        else:
            constraints = []

        new_expr = LinearExpression([w], [term.coefficient], 0.0)

        return new_expr, constraints
def _initialize_bounds(problem, bounds, get_bound, set_bound):
    """Set bounds of root_expr to constraints bounds.
    Since GALINI doesn't consider constraints as expression we have
    to do this manually.
    """
    for constraint in problem.constraints:
        root_expr = constraint.root_expr
        expr_bounds = Interval(constraint.lower_bound, constraint.upper_bound)
        if root_expr not in bounds:
            set_bound(root_expr, expr_bounds)
        else:
            existing_bounds = get_bound(root_expr)
            new_bounds = existing_bounds.intersect(expr_bounds)
            set_bound(root_expr, new_bounds)
Beispiel #21
0
def bound_description_to_bound(bound_str):
    if isinstance(bound_str, str):
        return {
            'zero': Interval.zero(),
            'nonpositive': Interval(None, 0),
            'nonnegative': Interval(0, None),
            'positive': Interval(1, None),
            'negative': Interval(None, -1),
            'unbounded': Interval(None, None),
        }[bound_str]
    elif isinstance(bound_str, Interval):
        return bound_str
    else:
        return Interval(bound_str, bound_str)
Beispiel #22
0
    def handle_result(self, expr, value, bounds):
        if value is None:
            new_bounds = Interval(None, None)
        else:
            new_bounds = value

        old_bounds = bounds.get(expr, None)
        if old_bounds is not None:
            new_bounds = old_bounds.intersect(
                new_bounds,
                abs_eps=self.intersect_abs_eps,
            )
            has_changed = old_bounds != new_bounds
        else:
            has_changed = True
        bounds[expr] = new_bounds
        return has_changed
def test_visitor_tightens_new_bounds(visitor, expr):
    bounds = ComponentMap()
    assert bounds.get(expr, None) is None

    assert visitor.handle_result(expr, Interval(0, 2), bounds)
    assert bounds[expr] == Interval(0, 2)

    assert not visitor.handle_result(expr, Interval(0, 2), bounds)
    assert bounds[expr] == Interval(0, 2)

    assert visitor.handle_result(expr, Interval(0, 1), bounds)
    assert bounds[expr] == Interval(0, 1)
def _manage_infinity_bounds(problem, _bounds, get_bound, set_bound):
    """In some cases variables bounds are numbers that are bigger than
    mc.infinity. Change them back to None.
    """
    for variable in problem.variables:
        expr_bounds = get_bound(variable)
        lower_bound = expr_bounds.lower_bound
        upper_bound = expr_bounds.upper_bound

        if is_inf(lower_bound):
            new_lower_bound = None
        else:
            new_lower_bound = lower_bound

        if is_inf(upper_bound):
            new_upper_bound = None
        else:
            new_upper_bound = upper_bound

        set_bound(variable, Interval(new_lower_bound, new_upper_bound))
Beispiel #25
0
    def apply(self, expr, bounds):
        # Look for Quadratic + Linear expressions
        if expr.nargs() != 2:
            return

        quadratic, linear = _quadratic_and_linear(expr.children)

        if quadratic is None or linear is None:
            return

        univariate_terms, quadratic_terms, linear_terms = \
            _collect_expression_types(quadratic, linear)

        # Compute bounds of non univariate terms. We will use this to perform
        # tightening on univariate terms.
        quadratic_terms_bound = sum(t.coefficient * bounds[t.var1] *
                                    bounds[t.var2] for t in quadratic_terms)
        linear_terms_bound = sum(bounds[v] * c for v, c in linear_terms)
        expr_bound = bounds.get(expr, Interval(None, None))

        non_univariate_bound = \
            expr_bound - quadratic_terms_bound - linear_terms_bound

        univariate_terms_bounds = [
            _univariate_term_bound(a, b, bounds[v])
            for (v, a, b) in univariate_terms
        ]

        tightened_univariate_terms_bounds = [
            _tighten_univariate_term_bound(term_idx, univariate_terms_bounds,
                                           non_univariate_bound)
            for term_idx, _ in enumerate(univariate_terms_bounds)
        ]

        vars_bounds = ComponentMap(
            (v, _variable_bound_from_univariate_bound(a, b, bound))
            for bound, (
                v, a,
                b) in zip(tightened_univariate_terms_bounds, univariate_terms))

        return vars_bounds
Beispiel #26
0
    def apply(self, expr, bounds):
        base, expo = expr.args
        if type(expo) not in nonpyomo_leaf_types:
            if not expo.is_constant():
                return None
            expo = expo.value

        if not almosteq(expo, 2):
            return None

        expr_bound = bounds[expr]
        # the bound of a square number is never negative, but check anyway to
        # avoid unexpected crashes.
        if not expr_bound.is_nonnegative():
            return None

        sqrt_bound = expr_bound.sqrt()
        return [
            Interval(-sqrt_bound.upper_bound, sqrt_bound.upper_bound),
            None,
        ]
Beispiel #27
0
def intervals(draw, allow_infinity=True, lower_bound=None, upper_bound=None):
    if lower_bound is None and allow_infinity:
        lower = draw(
            st.one_of(st.none(),
                      reals(max_value=upper_bound, allow_infinity=False)))
    else:
        lower = draw(
            reals(min_value=lower_bound,
                  max_value=upper_bound,
                  allow_infinity=False))

    if upper_bound is None and allow_infinity:
        upper = draw(
            st.one_of(st.none(), reals(min_value=lower, allow_infinity=False)))
    else:
        upper = draw(
            reals(
                min_value=lower,
                max_value=upper_bound,
                allow_infinity=False,
            ))
    return Interval(lower, upper)
Beispiel #28
0
class AcosRule(_UnaryFunctionBoundsRule):
    """Bound propagation rule for acos."""
    initial_bound = Interval(-1, 1)
Beispiel #29
0
class LogRule(_UnaryFunctionBoundsRule):
    """Bound propagation rule for log."""
    initial_bound = Interval(0, None)
Beispiel #30
0
class SqrtRule(_UnaryFunctionBoundsRule):
    """Bound propagation rule for sqrt."""
    initial_bound = Interval(0, None)