Example #1
0
def _relax_leaf_to_root_ReciprocalExpression(node, values, aux_var_map, degree_map, parent_block, relaxation_side_map, counter):
    arg = values[0]
    degree = degree_map[arg]
    if degree == 0:
        res = 1/arg
        degree_map[res] = 0
        return res
    elif (id(arg), 'reciprocal') in aux_var_map:
        _aux_var, relaxation = aux_var_map[id(arg), 'reciprocal']
        relaxation_side = relaxation_side_map[node]
        if relaxation_side != relaxation.relaxation_side:
            relaxation.relaxation_side = RelaxationSide.BOTH
        return _aux_var
    else:
        _aux_var = _get_aux_var(parent_block, 1/arg)
        arg = replace_sub_expression_with_aux_var(arg, parent_block)
        relaxation_side = relaxation_side_map[node]
        degree_map[_aux_var] = 1
        if compute_bounds_on_expr(arg)[0] > 0:
            relaxation = PWUnivariateRelaxation()
            relaxation.set_input(x=arg, aux_var=_aux_var, relaxation_side=relaxation_side, f_x_expr=1/arg,
                                 shape=FunctionShape.CONVEX)
        elif compute_bounds_on_expr(arg)[1] < 0:
            relaxation = PWUnivariateRelaxation()
            relaxation.set_input(x=arg, aux_var=_aux_var, relaxation_side=relaxation_side, f_x_expr=1/arg,
                                 shape=FunctionShape.CONCAVE)
        else:
            _one = parent_block.aux_vars.add()
            _one.fix(1.0)
            relaxation = PWMcCormickRelaxation()
            relaxation.set_input(x1=arg, x2=_aux_var, aux_var=_one, relaxation_side=relaxation_side)
        aux_var_map[id(arg), 'reciprocal'] = (_aux_var, relaxation)
        setattr(parent_block.relaxations, 'rel'+str(counter), relaxation)
        counter.increment()
        return _aux_var
Example #2
0
def epigraph_reformulation(exp, slack_var_list, constraint_list, use_mcpp, sense):
    """Epigraph reformulation.

    Generate the epigraph reformuation for objective expressions.

    Parameters
    ----------
    slack_var_list : VarList
        Slack vars for epigraph reformulation.
    constraint_list : ConstraintList
        Epigraph constraint list.
    use_mcpp : Bool
        Whether to use mcpp to tighten the bound of slack variables.
    exp : Expression
        The expression to reformualte.
    sense : objective sense
        The objective sense.
    """
    slack_var = slack_var_list.add()
    if mcpp_available() and use_mcpp:
        mc_obj = McCormick(exp)
        slack_var.setub(mc_obj.upper())
        slack_var.setlb(mc_obj.lower())
    else:
        # Use Pyomo's contrib.fbbt package
        lb, ub = compute_bounds_on_expr(exp)
        if sense == minimize:
            slack_var.setlb(lb)
        else:
            slack_var.setub(ub)
    if sense == minimize:
        constraint_list.add(expr=slack_var >= exp)
    else:
        constraint_list.add(expr=slack_var <= exp)
Example #3
0
    def _estimate_M(self, expr, name):
        # If there are fixed variables here, unfix them for this calculation,
        # and we'll restore them at the end.
        fixed_vars = ComponentMap()
        if not self.assume_fixed_vars_permanent:
            for v in EXPR.identify_variables(expr, include_fixed=True):
                if v.fixed:
                    fixed_vars[v] = value(v)
                    v.fixed = False

        expr_lb, expr_ub = compute_bounds_on_expr(expr)
        if expr_lb is None or expr_ub is None:
            raise GDP_Error("Cannot estimate M for unbounded "
                            "expressions.\n\t(found while processing "
                            "constraint '%s'). Please specify a value of M "
                            "or ensure all variables that appear in the "
                            "constraint are bounded." % name)
        else:
            M = (expr_lb, expr_ub)

        # clean up if we unfixed things (fixed_vars is empty if we were assuming
        # fixed vars are fixed for life)
        for v, val in fixed_vars.items():
            v.fix(val)

        return tuple(M)
Example #4
0
 def test_inf_bounds_on_expr(self):
     m = pyo.ConcreteModel()
     m.x = pyo.Var(bounds=(-1, 1))
     m.y = pyo.Var()
     lb, ub = compute_bounds_on_expr(m.x + m.y)
     self.assertEqual(lb, None)
     self.assertEqual(ub, None)
Example #5
0
def solve_with_gdp_opt():
    m = MethanolModel().model
    for _d in m.component_data_objects(gdp.Disjunct,
                                       descend_into=True,
                                       active=True,
                                       sort=True):
        _d.BigM = pe.Suffix()
        for _c in _d.component_data_objects(pe.Constraint,
                                            descend_into=True,
                                            active=True,
                                            sort=True):
            lb, ub = compute_bounds_on_expr(_c.body)
            _d.BigM[_c] = max(abs(lb), abs(ub))
    opt = pe.SolverFactory('gdpopt')
    opt.CONFIG.strategy = 'LOA'
    opt.CONFIG.mip_solver = 'gams'
    opt.CONFIG.nlp_solver = 'gams'
    opt.CONFIG.tee = True
    res = opt.solve(m)
    for d in m.component_data_objects(ctype=gdp.Disjunct,
                                      active=True,
                                      sort=True,
                                      descend_into=True):
        if d.indicator_var.value == 1:
            print(d.name)
    print(res)

    return m
def compute_fbbt_bounds(expr, global_constraints, opt):
    """
    Calls fbbt on expr and returns the lower and upper bounds on the expression
    based on the bounds of the Vars that appear in the expression. Ignores
    the global_constraints and opt arguments.
    """
    return compute_bounds_on_expr(expr)
Example #7
0
def compute_float_bounds_on_expr(expr):
    lb, ub = compute_bounds_on_expr(expr)
    if lb is None:
        lb = -math.inf
    if ub is None:
        ub = math.inf

    return lb, ub
Example #8
0
def _compute_alpha(xs, f_x_expr):
    hess = _hessian(xs, f_x_expr)
    alpha = 0.0
    for i, x in enumerate(xs):
        a_ii_expr = hess[x][x]
        a_ii = compute_bounds_on_expr(a_ii_expr)
        tot = a_ii[0]
        for j, y in enumerate(xs):
            if i == j:
                continue
            a_ij_expr = hess[x][y]
            a_ij = compute_bounds_on_expr(a_ij_expr)
            tot -= max(abs(a_ij[0]), abs(a_ij[1]))
        tot = -0.5 * tot
        if tot > alpha:
            alpha = tot
    return alpha
Example #9
0
 def test_negative_power(self):
     m = pyo.ConcreteModel()
     m.x = pyo.Var()
     m.y = pyo.Var()
     e = (m.x**2 + m.y**2)**(-0.5)
     lb, ub = compute_bounds_on_expr(e)
     self.assertAlmostEqual(lb, 0)
     self.assertIsNone(ub)
Example #10
0
 def test_compute_expr_bounds(self):
     m = pyo.ConcreteModel()
     m.x = pyo.Var(bounds=(-1, 1))
     m.y = pyo.Var(bounds=(-1, 1))
     e = m.x + m.y
     lb, ub = compute_bounds_on_expr(e)
     self.assertAlmostEqual(lb, -2, 14)
     self.assertAlmostEqual(ub, 2, 14)
Example #11
0
 def test_compute_expr_bounds(self):
     m = pe.ConcreteModel()
     m.x = pe.Var(bounds=(-1,1))
     m.y = pe.Var(bounds=(-1,1))
     e = m.x + m.y
     lb, ub = compute_bounds_on_expr(e)
     self.assertAlmostEqual(lb, -2, 14)
     self.assertAlmostEqual(ub, 2, 14)
Example #12
0
    def _get_subs(self, instance):
        subs = {} # Substitute one var for another from a * x + b * y + c = 0
        subs_map = {} # id -> var
        fixes = [] # fix a variable from a * x + c = 0
        cnstr = set() # constraints to deactivate
        rset = set() # variables used in a sub or fixed
        for c in instance.component_data_objects(pyo.Constraint, active=True):
            if (
                pyo.value(c.lower) is not None and
                pyo.value(c.lower) == pyo.value(c.upper) and
                c.body.polynomial_degree() == 1
            ):
                repn = generate_standard_repn(c.body)
                if len(repn.nonlinear_vars) != 0 or len(repn.quadratic_vars) != 0:
                    continue
                if len(repn.linear_vars) > 2:
                    continue
                elif len(repn.linear_vars) < 1:
                    _log.warning("Constraint with no vars {}: {}".format(c, c.expr))
                    continue
                b0 = repn.constant - pyo.value(c.upper)
                v0 = repn.linear_vars[0]
                a0 = repn.linear_coefs[0]
                if id(v0) in rset:
                    continue
                elif len(repn.linear_vars) == 1:
                    fixes.append((v0, -b0/a0))
                    rset.add(id(v0))
                    cnstr.add(c)
                    continue

                v1 = repn.linear_vars[1]
                a1 = repn.linear_coefs[1]
                if id(v1) in rset:
                    continue

                subs[id(v0)] = -(b0 + a1 * v1) / a0
                cnstr.add(c)
                rset.add(id(v0))
                rset.add(id(v1))

                if self.reversible:
                    subs_map[id(v0)] = v0

                _log.debug("Sub: {} = {}".format(v0, subs[id(v0)]))

                # Use the tightest set of bounds from v0 and v1
                lb, ub = compute_bounds_on_expr(-(b0 + a0 * v0) / a1)

                if lb is not None:
                    if v1.lb is None or lb > v1.lb:
                        v1.setlb(lb)
                if ub is not None:
                    if v1.ub is None or ub < v1.ub:
                        v1.setub(ub)

        return subs, cnstr, fixes, subs_map
Example #13
0
    def test_quadratic_as_product(self):
        m = pyo.ConcreteModel()
        m.x = pyo.Var([1, 2], bounds=(-2, 6))

        e1 = m.x[1] * m.x[1] + m.x[2] * m.x[2]
        e2 = m.x[1]**2 + m.x[2]**2

        lb1, ub1 = compute_bounds_on_expr(e1)
        lb2, ub2 = compute_bounds_on_expr(e2)

        self.assertAlmostEqual(lb1, lb2)
        self.assertAlmostEqual(ub1, ub2)

        m.c = pyo.Constraint(expr=m.x[1] * m.x[1] + m.x[2] * m.x[2] == 0)
        fbbt(m.c)
        self.assertAlmostEqual(m.x[1].lb, 0)
        self.assertAlmostEqual(m.x[1].ub, 0)
        self.assertAlmostEqual(m.x[2].lb, 0)
        self.assertAlmostEqual(m.x[2].ub, 0)
Example #14
0
 def exitNode(self, node, values):
     if type(node) == AndExpression:
         return list((v if type(v) in _numeric_relational_types else v == 1)
                     for v in values)
     elif type(node) == OrExpression:
         return sum(values) >= 1
     elif type(node) == NotExpression:
         return 1 - values[0]
     # Note: the following special atoms should only be encountered as root nodes.
     # If they are encountered otherwise, something went wrong.
     sum_values = sum(values[1:])
     num_args = node.nargs() - 1  # number of logical arguments
     if self._indicator is None:
         if type(node) == AtLeastExpression:
             return sum_values >= values[0]
         elif type(node) == AtMostExpression:
             return sum_values <= values[0]
         elif type(node) == ExactlyExpression:
             return sum_values == values[0]
     else:
         rhs_lb, rhs_ub = compute_bounds_on_expr(values[0])
         if rhs_lb == float('-inf') or rhs_ub == float('inf'):
             raise ValueError(
                 "Cannnot generate linear constraints for %s([N, *logical_args]) with unbounded N. "
                 "Detected %s <= N <= %s." %
                 (type(node).__name__, rhs_lb, rhs_ub))
         indicator_binary = self._indicator.get_associated_binary()
         if type(node) == AtLeastExpression:
             return [
                 sum_values >= values[0] - rhs_ub * (1 - indicator_binary),
                 sum_values <= values[0] - 1 +
                 (-(rhs_lb - 1) + num_args) * indicator_binary
             ]
         elif type(node) == AtMostExpression:
             return [
                 sum_values <= values[0] + (-rhs_lb + num_args) *
                 (1 - indicator_binary), sum_values >=
                 (values[0] + 1) - (rhs_ub + 1) * indicator_binary
             ]
         elif type(node) == ExactlyExpression:
             less_than_binary = self._binary_varlist.add()
             more_than_binary = self._binary_varlist.add()
             return [
                 sum_values <= values[0] + (-rhs_lb + num_args) *
                 (1 - indicator_binary),
                 sum_values >= values[0] - rhs_ub * (1 - indicator_binary),
                 indicator_binary + less_than_binary + more_than_binary >=
                 1,
                 sum_values <= values[0] - 1 + (-(rhs_lb - 1) + num_args) *
                 (1 - less_than_binary),
                 sum_values >= values[0] + 1 - (rhs_ub + 1) *
                 (1 - more_than_binary),
             ]
         pass
Example #15
0
    def _estimate_M(self, expr, name):
        # If there are fixed variables here, unfix them for this calculation,
        # and we'll restore them at the end.
        fixed_vars = ComponentMap()
        if not self.assume_fixed_vars_permanent:
            for v in EXPR.identify_variables(expr, include_fixed=True):
                if v.fixed:
                    fixed_vars[v] = value(v)
                    v.fixed = False

        # Calculate a best guess at M
        repn = generate_standard_repn(expr, quadratic=False)
        M = [0, 0]

        if not repn.is_nonlinear():
            if repn.constant is not None:
                for i in (0, 1):
                    if M[i] is not None:
                        M[i] += repn.constant

            for i, coef in enumerate(repn.linear_coefs or []):
                var = repn.linear_vars[i]
                bounds = (value(var.lb), value(var.ub))
                for i in (0, 1):
                    # reverse the bounds if the coefficient is negative
                    if coef > 0:
                        j = i
                    else:
                        j = 1 - i

                    if bounds[i] is not None:
                        M[j] += value(bounds[i]) * coef
                    else:
                        raise GDP_Error(
                            "Cannot estimate M for "
                            "expressions with unbounded variables."
                            "\n\t(found unbounded var '%s' while processing "
                            "constraint '%s')" % (var.name, name))
        else:
            # expression is nonlinear. Try using `contrib.fbbt` to estimate.
            expr_lb, expr_ub = compute_bounds_on_expr(expr)
            if expr_lb is None or expr_ub is None:
                raise GDP_Error("Cannot estimate M for unbounded nonlinear "
                                "expressions.\n\t(found while processing "
                                "constraint '%s')" % name)
            else:
                M = (expr_lb, expr_ub)

        # clean up if we unfixed things (fixed_vars is empty if we were assuming
        # fixed vars are fixed for life)
        for v, val in fixed_vars.items():
            v.fix(val)

        return tuple(M)
Example #16
0
def _get_aux_var(parent_block, expr):
    _aux_var = parent_block.aux_vars.add()
    lb, ub = compute_bounds_on_expr(expr)
    _aux_var.setlb(lb)
    _aux_var.setub(ub)
    try:
        expr_value = pe.value(expr, exception=False)
    except ArithmeticError:
        expr_value = None
    if expr_value is not None and pe.value(_aux_var, exception=False) is None:
        _aux_var.set_value(expr_value)
    return _aux_var
Example #17
0
def _check_coefficents(comp, expr, too_large, too_small, largs_coef_map,
                       small_coef_map):
    ders = reverse_sd(expr)
    for _v, _der in ders.items():
        if isinstance(_v, _GeneralVarData):
            if _v.is_fixed():
                continue
            der_lb, der_ub = compute_bounds_on_expr(_der)
            der_lb, der_ub = _bounds_to_float(der_lb, der_ub)
            if der_lb <= -too_large or der_ub >= too_large:
                if comp not in largs_coef_map:
                    largs_coef_map[comp] = list()
                largs_coef_map[comp].append((_v, der_lb, der_ub))
            if abs(der_lb) <= too_small and abs(der_ub) < too_small:
                if der_lb != 0 or der_ub != 0:
                    if comp not in small_coef_map:
                        small_coef_map[comp] = list()
                    small_coef_map[comp].append((_v, der_lb, der_ub))
Example #18
0
    def _estimate_M(self, expr, name):
        # Calculate a best guess at M
        repn = generate_standard_repn(expr, quadratic=False)
        M = [0, 0]

        if not repn.is_nonlinear():
            if repn.constant is not None:
                for i in (0, 1):
                    if M[i] is not None:
                        M[i] += repn.constant

            for i, coef in enumerate(repn.linear_coefs or []):
                var = repn.linear_vars[i]
                bounds = (value(var.lb), value(var.ub))
                for i in (0, 1):
                    # reverse the bounds if the coefficient is negative
                    if coef > 0:
                        j = i
                    else:
                        j = 1 - i

                    if bounds[i] is not None:
                        M[j] += value(bounds[i]) * coef
                    else:
                        raise GDP_Error(
                            "Cannot estimate M for "
                            "expressions with unbounded variables."
                            "\n\t(found unbounded var %s while processing "
                            "constraint %s)" % (var.name, name))
        else:
            # expression is nonlinear. Try using `contrib.fbbt` to estimate.
            expr_lb, expr_ub = compute_bounds_on_expr(expr)
            if expr_lb is None or expr_ub is None:
                raise GDP_Error("Cannot estimate M for unbounded nonlinear "
                                "expressions.\n\t(found while processing "
                                "constraint %s)" % name)
            else:
                M = (expr_lb, expr_ub)

        return tuple(M)
Example #19
0
def _relax_leaf_to_root_QuadraticExpression(node, values, aux_var_map,
                                            degree_map, parent_block,
                                            relaxation_side_map, counter):

    relaxation_side = relaxation_side_map[node]

    term_graph = nx.Graph()
    term_graph.add_edges_from((id(term.var1), id(term.var2), {
        'term': term
    }) for term in node.terms)

    disaggregated_exprs = []
    aux_sum = 0.0

    for connected_component in nx.connected_components(term_graph):
        connected_graph = term_graph.subgraph(connected_component)
        expr = 0.0
        aux_var_expr = 0.0
        bilinear_count = 0
        for _, _, data in connected_graph.edges(data=True):
            bilinear_count += 1
            term = data['term']
            var1_id = id(term.var1)
            var2_id = id(term.var2)
            if term.var1 is term.var2:
                aux_var, _ = aux_var_map[var1_id, 'quadratic']
            else:
                aux_var, _ = aux_var_map.get((var1_id, var2_id, 'mul'),
                                             (None, None))
                if aux_var is None:
                    aux_var, _ = aux_var_map.get((var2_id, var1_id, 'mul'),
                                                 (None, None))
                    assert aux_var is not None
            expr += term.coefficient * term.var1 * term.var2
            aux_var_expr += term.coefficient * aux_var

        if bilinear_count == 1:
            aux_sum += aux_var_expr
            continue

        expr = QuadraticExpression(expr)

        cvx = _convexity_rule.apply(expr, None, None, None)

        if relaxation_side == RelaxationSide.UNDER and cvx.is_convex():
            disaggregated_exprs.append((expr, aux_var_expr))
        elif relaxation_side == RelaxationSide.OVER and cvx.is_concave():
            disaggregated_exprs.append((expr, aux_var_expr))
        elif relaxation_side == RelaxationSide.BOTH and cvx.is_convex():
            disaggregated_exprs.append((expr, aux_var_expr))
        else:
            aux_sum += aux_var_expr

    if len(disaggregated_exprs) == 0:
        res = sum(values)
        degree_map[res] = max(degree_map[arg] for arg in values)
        return res

    # replace convex nonlinear expressions with an auxiliary variable
    res = aux_sum
    for expr, aux_var_expr in disaggregated_exprs:
        lb, ub = compute_bounds_on_expr(aux_var_expr)
        new_aux_var = parent_block.aux_vars.add()
        new_aux_var.setlb(lb)
        new_aux_var.setub(ub)
        new_aux_var.value = 0.0
        degree_map[new_aux_var] = 1.0
        relaxation = FactorableConvexExpressionRelaxation()
        relaxation.build(
            aux_var=new_aux_var,
            relaxed_f_x_expr=aux_var_expr,
            original_f_x_expr=expr,
        )
        setattr(parent_block.relaxations, 'rel' + str(counter), relaxation)
        counter.increment()
        res += new_aux_var
    degree_map[res] = 1.0
    return res
Example #20
0
def enumerate_solutions():
    import time
    feed_choices = ['cheap', 'expensive']
    feed_compressor_choices = ['single_stage', 'two_stage']
    reactor_choices = ['cheap', 'expensive']
    recycle_compressor_choices = ['single_stage', 'two_stage']

    print('{0:<20}{1:<20}{2:<20}{3:<20}{4:<20}{5:<20}'.format(
        'feed choice', 'feed compressor', 'reactor choice',
        'recycle compressor', 'termination cond', 'profit'))
    since = time.time()
    for feed_choice in feed_choices:
        for feed_compressor_choice in feed_compressor_choices:
            for reactor_choice in reactor_choices:
                for recycle_compressor_choice in recycle_compressor_choices:
                    m = MethanolModel()
                    m = m.model
                    for _d in m.component_data_objects(gdp.Disjunct,
                                                       descend_into=True,
                                                       active=True,
                                                       sort=True):
                        _d.BigM = pe.Suffix()
                        for _c in _d.component_data_objects(pe.Constraint,
                                                            descend_into=True,
                                                            active=True,
                                                            sort=True):
                            lb, ub = compute_bounds_on_expr(_c.body)
                            _d.BigM[_c] = max(abs(lb), abs(ub))

                    if feed_choice == 'cheap':
                        m.cheap_feed_disjunct.indicator_var.fix(1)
                        m.expensive_feed_disjunct.indicator_var.fix(0)
                    else:
                        m.cheap_feed_disjunct.indicator_var.fix(0)
                        m.expensive_feed_disjunct.indicator_var.fix(1)

                    if feed_compressor_choice == 'single_stage':
                        m.single_stage_feed_compressor_disjunct.indicator_var.fix(
                            1)
                        m.two_stage_feed_compressor_disjunct.indicator_var.fix(
                            0)
                    else:
                        m.single_stage_feed_compressor_disjunct.indicator_var.fix(
                            0)
                        m.two_stage_feed_compressor_disjunct.indicator_var.fix(
                            1)

                    if reactor_choice == 'cheap':
                        m.cheap_reactor.indicator_var.fix(1)
                        m.expensive_reactor.indicator_var.fix(0)
                    else:
                        m.cheap_reactor.indicator_var.fix(0)
                        m.expensive_reactor.indicator_var.fix(1)

                    if recycle_compressor_choice == 'single_stage':
                        m.single_stage_recycle_compressor_disjunct.indicator_var.fix(
                            1)
                        m.two_stage_recycle_compressor_disjunct.indicator_var.fix(
                            0)
                    else:
                        m.single_stage_recycle_compressor_disjunct.indicator_var.fix(
                            0)
                        m.two_stage_recycle_compressor_disjunct.indicator_var.fix(
                            1)

                    pe.TransformationFactory('gdp.fix_disjuncts').apply_to(m)

                    fbbt(m, deactivate_satisfied_constraints=True)
                    fix_vars_with_equal_bounds(m)

                    for v in m.component_data_objects(pe.Var,
                                                      descend_into=True):
                        if not v.is_fixed():
                            if v.has_lb() and v.has_ub():
                                v.value = 0.5 * (v.lb + v.ub)
                            else:
                                v.value = 1.0

                    opt = pe.SolverFactory('ipopt')
                    res = opt.solve(m, tee=False)
                    print('{0:<20}{1:<20}{2:<20}{3:<20}{4:<20}{5:<20}'.format(
                        feed_choice, feed_compressor_choice, reactor_choice,
                        recycle_compressor_choice,
                        str(res.solver.termination_condition),
                        str(-pe.value(m.objective))))
    time_elapsed = time.time() - since
    print('The code run {:.0f}m {:.0f}s'.format(time_elapsed // 60,
                                                time_elapsed % 60))

    return m
Example #21
0
def process_objective(solve_data, config, move_linear_objective=False, use_mcpp=True, updata_var_con_list=True):
    """Process model objective function.

    Check that the model has only 1 valid objective.
    If the objective is nonlinear, move it into the constraints.
    If no objective function exists, emit a warning and create a dummy objective.

    Parameters
    ----------
    solve_data (GDPoptSolveData): solver environment data class
    config (ConfigBlock): solver configuration options
    move_linear_objective (bool): if True, move even linear
        objective functions to the constraints

    """
    m = solve_data.working_model
    util_blk = getattr(m, solve_data.util_block_name)
    # Handle missing or multiple objectives
    active_objectives = list(m.component_data_objects(
        ctype=Objective, active=True, descend_into=True))
    solve_data.results.problem.number_of_objectives = len(active_objectives)
    if len(active_objectives) == 0:
        config.logger.warning(
            'Model has no active objectives. Adding dummy objective.')
        util_blk.dummy_objective = Objective(expr=1)
        main_obj = util_blk.dummy_objective
    elif len(active_objectives) > 1:
        raise ValueError('Model has multiple active objectives.')
    else:
        main_obj = active_objectives[0]
    solve_data.results.problem.sense = ProblemSense.minimize if main_obj.sense == 1 else ProblemSense.maximize
    solve_data.objective_sense = main_obj.sense

    # Move the objective to the constraints if it is nonlinear
    if main_obj.expr.polynomial_degree() not in (1, 0) \
            or move_linear_objective:
        if move_linear_objective:
            config.logger.info("Moving objective to constraint set.")
        else:
            config.logger.info(
                "Objective is nonlinear. Moving it to constraint set.")

        util_blk.objective_value = Var(domain=Reals, initialize=0)
        if mcpp_available() and use_mcpp:
            mc_obj = McCormick(main_obj.expr)
            util_blk.objective_value.setub(mc_obj.upper())
            util_blk.objective_value.setlb(mc_obj.lower())
        else:
            # Use Pyomo's contrib.fbbt package
            lb, ub = compute_bounds_on_expr(main_obj.expr)
            if solve_data.results.problem.sense == ProblemSense.minimize:
                util_blk.objective_value.setlb(lb)
            else:
                util_blk.objective_value.setub(ub)

        if main_obj.sense == minimize:
            util_blk.objective_constr = Constraint(
                expr=util_blk.objective_value >= main_obj.expr)
        else:
            util_blk.objective_constr = Constraint(
                expr=util_blk.objective_value <= main_obj.expr)
        # Deactivate the original objective and add this new one.
        main_obj.deactivate()
        util_blk.objective = Objective(
            expr=util_blk.objective_value, sense=main_obj.sense)
        # Add the new variable and constraint to the working lists
        if main_obj.expr.polynomial_degree() not in (1, 0) or (move_linear_objective and updata_var_con_list):
            util_blk.variable_list.append(util_blk.objective_value)
            util_blk.continuous_variable_list.append(util_blk.objective_value)
            util_blk.constraint_list.append(util_blk.objective_constr)
            util_blk.objective_list.append(util_blk.objective)
            if util_blk.objective_constr.body.polynomial_degree() in (0, 1):
                util_blk.linear_constraint_list.append(util_blk.objective_constr)
            else:
                util_blk.nonlinear_constraint_list.append(
                    util_blk.objective_constr)
Example #22
0
def report_scaling(m: _BlockData,
                   too_large: float = 5e4,
                   too_small: float = 1e-6) -> bool:
    """
    This function logs potentially poorly scaled parts of the model.
    It requires that all variables be bounded.

    It is important to note that this check is neither necessary nor sufficient
    to ensure a well-scaled model. However, it is a useful tool to help identify
    problematic parts of a model.

    This function uses symbolic differentiation and interval arithmetic
    to compute bounds on each entry in the jacobian of the constraints.

    Note that logging has to be turned on to get the output

    Parameters
    ----------
    m: _BlockData
        The pyomo model or block
    too_large: float
        Values above too_large will generate a log entry
    too_small: float
        Coefficients below too_small will generate a log entry

    Returns
    -------
    success: bool
        Returns False if any potentially poorly scaled components were found
    """
    vars_without_bounds, vars_with_large_bounds = _check_var_bounds(
        m, too_large)

    cons_with_large_bounds = dict()
    cons_with_large_coefficients = dict()
    cons_with_small_coefficients = dict()

    objs_with_large_coefficients = pyo.ComponentMap()
    objs_with_small_coefficients = pyo.ComponentMap()

    for c in m.component_data_objects(pyo.Constraint,
                                      active=True,
                                      descend_into=True):
        _check_coefficents(c, c.body, too_large, too_small,
                           cons_with_large_coefficients,
                           cons_with_small_coefficients)

    for c in m.component_data_objects(pyo.Constraint,
                                      active=True,
                                      descend_into=True):
        c_lb, c_ub = compute_bounds_on_expr(c.body)
        c_lb, c_ub = _bounds_to_float(c_lb, c_ub)
        if c_lb <= -too_large or c_ub >= too_large:
            cons_with_large_bounds[c] = (c_lb, c_ub)

    for c in m.component_data_objects(pyo.Objective,
                                      active=True,
                                      descend_into=True):
        _check_coefficents(c, c.expr, too_large, too_small,
                           objs_with_large_coefficients,
                           objs_with_small_coefficients)

    s = '\n\n'

    if len(vars_without_bounds) > 0:
        s += 'The following variables are not bounded. Please add bounds.\n'
        s += _print_var_set(vars_without_bounds)

    if len(vars_with_large_bounds) > 0:
        s += 'The following variables have large bounds. Please scale them.\n'
        s += _print_var_set(vars_with_large_bounds)

    if len(objs_with_large_coefficients) > 0:
        s += 'The following objectives have potentially large coefficients. Please scale them.\n'
        s += _print_coefficients(objs_with_large_coefficients)

    if len(objs_with_small_coefficients) > 0:
        s += 'The following objectives have small coefficients.\n'
        s += _print_coefficients(objs_with_small_coefficients)

    if len(cons_with_large_coefficients) > 0:
        s += 'The following constraints have potentially large coefficients. Please scale them.\n'
        s += _print_coefficients(cons_with_large_coefficients)

    if len(cons_with_small_coefficients) > 0:
        s += 'The following constraints have small coefficients.\n'
        s += _print_coefficients(cons_with_small_coefficients)

    if len(cons_with_large_bounds) > 0:
        s += 'The following constraints have bodies with large bounds. Please scale them.\n'
        s += f'{"LB":>12}{"UB":>12}    Constraint\n'
        for c, (c_lb, c_ub) in cons_with_large_bounds.items():
            s += f'{c_lb:>12.2e}{c_ub:>12.2e}    {str(c)}\n'

    if (len(vars_without_bounds) > 0 or len(vars_with_large_bounds) > 0
            or len(cons_with_large_coefficients) > 0
            or len(cons_with_small_coefficients) > 0
            or len(objs_with_small_coefficients) > 0
            or len(objs_with_large_coefficients) > 0
            or len(cons_with_large_bounds) > 0):
        logger.info(s)
        return False
    return True
Example #23
0
ub = pe.value(nlp.obj)

# Build the relaxation
"""
Reformulate the NLP as

min x4 - 3*x2 + x
s.t.
    x2 = x**2
    x4 = x2**2

Then relax the two constraints with PWXSquaredRelaxation objects.
"""
rel = pe.ConcreteModel()
rel.x = pe.Var(bounds=(-2, 2))
rel.x2 = pe.Var(bounds=compute_bounds_on_expr(rel.x**2))
rel.x4 = pe.Var(bounds=compute_bounds_on_expr(rel.x2**2))
rel.x2_con = coramin.relaxations.PWXSquaredRelaxation()
rel.x2_con.build(x=rel.x, aux_var=rel.x2, use_linear_relaxation=True)
rel.x4_con = coramin.relaxations.PWXSquaredRelaxation()
rel.x4_con.build(x=rel.x2, aux_var=rel.x4, use_linear_relaxation=True)
rel.obj = pe.Objective(expr=rel.x4 - 3 * rel.x2 + rel.x)

# Now solve the relaxation and refine the convex sides of the constraints with add_cut
print('*********************************')
print('OA Cut Generation')
print('*********************************')
opt = pe.SolverFactory('gurobi_direct')
res = opt.solve(rel)
lb = pe.value(rel.obj)
print('gap: ' + str(100 * abs(ub - lb) / abs(ub)) + ' %')
Example #24
0
def _relax_leaf_to_root_DivisionExpression(node, values, aux_var_map, degree_map, parent_block, relaxation_side_map, counter):
    arg1, arg2 = values
    if arg1.__class__ == numeric_expr.MonomialTermExpression:
        coef1, arg1 = arg1.args
    else:
        coef1 = 1
    if arg2.__class__ == numeric_expr.MonomialTermExpression:
        coef2, arg2 = arg2.args
    else:
        coef2 = 1
    coef = coef1/coef2
    degree_1 = degree_map[arg1]
    degree_2 = degree_map[arg2]

    if degree_2 == 0:
        res = (coef / arg2) * arg1
        degree_map[res] = degree_1
        return res
    elif (id(arg1), id(arg2), 'div') in aux_var_map:
        _aux_var, relaxation = aux_var_map[id(arg1), id(arg2), 'div']
        relaxation_side = relaxation_side_map[node]
        if relaxation_side != relaxation.relaxation_side:
            relaxation.relaxation_side = RelaxationSide.BOTH
        res = coef * _aux_var
        degree_map[_aux_var] = 1
        degree_map[res] = 1
        return res
    elif degree_1 == 0:
        if (id(arg2), 'reciprocal') in aux_var_map:
            _aux_var, relaxation = aux_var_map[id(arg2), 'reciprocal']
            relaxation_side = relaxation_side_map[node]
            if relaxation_side != relaxation.relaxation_side:
                relaxation.relaxation_side = RelaxationSide.BOTH
            res = coef * arg1 * _aux_var
            degree_map[_aux_var] = 1
            degree_map[res] = 1
            return res
        else:
            _aux_var = _get_aux_var(parent_block, 1/arg2)
            arg2 = replace_sub_expression_with_aux_var(arg2, parent_block)
            relaxation_side = relaxation_side_map[node]
            degree_map[_aux_var] = 1
            if compute_bounds_on_expr(arg2)[0] > 0:
                relaxation = PWUnivariateRelaxation()
                relaxation.set_input(x=arg2, aux_var=_aux_var, relaxation_side=relaxation_side, f_x_expr=1/arg2,
                                     shape=FunctionShape.CONVEX)
            elif compute_bounds_on_expr(arg2)[1] < 0:
                relaxation = PWUnivariateRelaxation()
                relaxation.set_input(x=arg2, aux_var=_aux_var, relaxation_side=relaxation_side, f_x_expr=1/arg2,
                                     shape=FunctionShape.CONCAVE)
            else:
                _one = parent_block.aux_vars.add()
                _one.fix(1)
                relaxation = PWMcCormickRelaxation()
                relaxation.set_input(x1=arg2, x2=_aux_var, aux_var=_one, relaxation_side=relaxation_side)
            aux_var_map[id(arg2), 'reciprocal'] = (_aux_var, relaxation)
            setattr(parent_block.relaxations, 'rel'+str(counter), relaxation)
            counter.increment()
            res = coef * arg1 * _aux_var
            degree_map[res] = 1
            return res
    else:
        _aux_var = _get_aux_var(parent_block, arg1 / arg2)
        arg1 = replace_sub_expression_with_aux_var(arg1, parent_block)
        arg2 = replace_sub_expression_with_aux_var(arg2, parent_block)
        relaxation_side = relaxation_side_map[node]
        relaxation = PWMcCormickRelaxation()
        relaxation.set_input(x1=arg2, x2=_aux_var, aux_var=arg1, relaxation_side=relaxation_side)
        aux_var_map[id(arg1), id(arg2), 'div'] = (_aux_var, relaxation)
        setattr(parent_block.relaxations, 'rel'+str(counter), relaxation)
        counter.increment()
        res = coef * _aux_var
        degree_map[_aux_var] = 1
        degree_map[res] = 1
        return res
Example #25
0
def _get_aux_var(parent_block, expr):
    _aux_var = parent_block.aux_vars.add()
    lb, ub = compute_bounds_on_expr(expr)
    _aux_var.setlb(lb)
    _aux_var.setub(ub)
    return _aux_var
Example #26
0
def _relax_leaf_to_root_PowExpression(node, values, aux_var_map, degree_map, parent_block, relaxation_side_map, counter):
    arg1, arg2 = values
    degree1 = degree_map[arg1]
    degree2 = degree_map[arg2]
    if degree2 == 0:
        if degree1 == 0:
            res = arg1 ** arg2
            degree_map[res] = 0
            return res
        if not is_constant(arg2):
            logger.warning('Only constant exponents are supported: ' + str(arg1**arg2) + '\nReplacing ' + str(arg2) + ' with its value.')
        arg2 = pe.value(arg2)
        if arg2 == 1:
            return arg1
        elif arg2 == 0:
            res = 1
            degree_map[res] = 0
            return res
        elif arg2 == 2:
            return _relax_quadratic(arg1=arg1, aux_var_map=aux_var_map, relaxation_side=relaxation_side_map[node],
                                    degree_map=degree_map, parent_block=parent_block, counter=counter)
        elif arg2 >= 0:
            if arg2 == round(arg2):
                if arg2 % 2 == 0 or compute_bounds_on_expr(arg1)[0] >= 0:
                    return _relax_convex_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map,
                                             relaxation_side=relaxation_side_map[node], degree_map=degree_map,
                                             parent_block=parent_block, counter=counter)
                elif compute_bounds_on_expr(arg1)[1] <= 0:
                    return _relax_concave_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map,
                                              relaxation_side=relaxation_side_map[node], degree_map=degree_map,
                                              parent_block=parent_block, counter=counter)
                else:  # reformulate arg1 ** arg2 as arg1 * arg1 ** (arg2 - 1)
                    _new_relaxation_side_map = ComponentMap()
                    _reformulated = arg1 * arg1 ** (arg2 - 1)
                    _new_relaxation_side_map[_reformulated] = relaxation_side_map[node]
                    res = _relax_expr(expr=_reformulated, aux_var_map=aux_var_map, parent_block=parent_block,
                                      relaxation_side_map=_new_relaxation_side_map, counter=counter,
                                      degree_map=degree_map)
                    degree_map[res] = 1
                    return res
            else:
                if arg2 < 1:
                    return _relax_concave_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map,
                                              relaxation_side=relaxation_side_map[node], degree_map=degree_map,
                                              parent_block=parent_block, counter=counter)
                else:
                    return _relax_convex_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map,
                                             relaxation_side=relaxation_side_map[node], degree_map=degree_map,
                                             parent_block=parent_block, counter=counter)
        else:
            if arg2 == round(arg2):
                if compute_bounds_on_expr(arg1)[0] >= 0:
                    return _relax_convex_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map,
                                             relaxation_side=relaxation_side_map[node], degree_map=degree_map,
                                             parent_block=parent_block, counter=counter)
                elif compute_bounds_on_expr(arg1)[1] <= 0:
                    if arg2 % 2 == 0:
                        return _relax_convex_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map,
                                                 relaxation_side=relaxation_side_map[node], degree_map=degree_map,
                                                 parent_block=parent_block, counter=counter)
                    else:
                        return _relax_concave_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map,
                                                  relaxation_side=relaxation_side_map[node], degree_map=degree_map,
                                                  parent_block=parent_block, counter=counter)
                else:
                    # reformulate arg1 ** arg2 as 1 / arg1 ** (-arg2)
                    _new_relaxation_side_map = ComponentMap()
                    _reformulated = 1 / (arg1 ** (-arg2))
                    _new_relaxation_side_map[_reformulated] = relaxation_side_map[node]
                    res = _relax_expr(expr=_reformulated, aux_var_map=aux_var_map, parent_block=parent_block,
                                      relaxation_side_map=_new_relaxation_side_map, counter=counter,
                                      degree_map=degree_map)
                    degree_map[res] = 1
                    return res
            else:
                assert compute_bounds_on_expr(arg1)[0] >= 0
                return _relax_convex_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map,
                                         relaxation_side=relaxation_side_map[node], degree_map=degree_map,
                                         parent_block=parent_block, counter=counter)
    elif degree1 == 0:
        if not is_constant(arg1):
            logger.warning('Found {0} raised to a variable power. However, {0} does not appear to be constant (maybe '
                           'it is or depends on a mutable Param?). Replacing {0} with its value.'.format(str(arg1)))
            arg1 = pe.value(arg1)
        if arg1 < 0:
            raise ValueError('Cannot raise a negative base to a variable exponent: ' + str(arg1**arg2))
        return _relax_convex_pow(arg1=arg1, arg2=arg2, aux_var_map=aux_var_map,
                                 relaxation_side=relaxation_side_map[node], degree_map=degree_map,
                                 parent_block=parent_block, counter=counter, swap=True)
    else:
        if (id(arg1), id(arg2), 'pow') in aux_var_map:
            _aux_var, relaxation = aux_var_map[id(arg1), id(arg2), 'pow']
            if relaxation_side_map[node] != relaxation.relaxation_side:
                relaxation.relaxation_side = RelaxationSide.BOTH
            return _aux_var
        else:
            assert compute_bounds_on_expr(arg1)[0] >= 0
            _new_relaxation_side_map = ComponentMap()
            _reformulated = pe.exp(arg2 * pe.log(arg1))
            _new_relaxation_side_map[_reformulated] = relaxation_side_map[node]
            res = _relax_expr(expr=_reformulated, aux_var_map=aux_var_map, parent_block=parent_block,
                              relaxation_side_map=_new_relaxation_side_map, counter=counter,
                              degree_map=degree_map)
            degree_map[res] = 1
            return res