Exemplo n.º 1
0
    def _get_expr_from_pyomo_repn(self, repn, max_degree=2):
        referenced_vars = ComponentSet()

        degree = repn.polynomial_degree()
        if (degree is None) or (degree > max_degree):
            raise DegreeError(
                'GurobiDirect does not support expressions of degree {0}.'.
                format(degree))

        if len(repn.linear_vars) > 0:
            referenced_vars.update(repn.linear_vars)
            new_expr = self._gurobipy.LinExpr(repn.linear_coefs, [
                self._pyomo_var_to_solver_var_map[i] for i in repn.linear_vars
            ])
        else:
            new_expr = 0.0

        for i, v in enumerate(repn.quadratic_vars):
            x, y = v
            new_expr += repn.quadratic_coefs[
                i] * self._pyomo_var_to_solver_var_map[
                    x] * self._pyomo_var_to_solver_var_map[y]
            referenced_vars.add(x)
            referenced_vars.add(y)

        new_expr += repn.constant

        return new_expr, referenced_vars
Exemplo n.º 2
0
def _process_container(blk, config):
    if not hasattr(blk, '_induced_linearity_info'):
        blk._induced_linearity_info = Block()
    else:
        assert blk._induced_linearity_info.type() == Block
    eff_discr_vars = detect_effectively_discrete_vars(
        blk, config.equality_tolerance)
    # TODO will need to go through this for each disjunct, since it does
    # not (should not) descend into Disjuncts.

    # Determine the valid values for the effectively discrete variables
    possible_var_values = determine_valid_values(blk, eff_discr_vars, config)

    # Collect find bilinear expressions that can be reformulated using
    # knowledge of effectively discrete variables
    bilinear_map = _bilinear_expressions(blk)

    # Relevant constraints are those with bilinear terms that involve
    # effectively_discrete_vars
    processed_pairs = ComponentSet()
    for v1, var_values in possible_var_values.items():
        v1_pairs = bilinear_map.get(v1, ())
        for v2, bilinear_constrs in v1_pairs.items():
            if (v1, v2) in processed_pairs:
                continue
            _process_bilinear_constraints(blk, v1, v2, var_values,
                                          bilinear_constrs)
            processed_pairs.add((v2, v1))
Exemplo n.º 3
0
    def _get_expr_from_pyomo_repn(self, repn, max_degree=2):
        referenced_vars = ComponentSet()

        degree = repn.polynomial_degree()
        if (degree is None) or (degree > max_degree):
            raise DegreeError(
                'Mosek does not support expressions of degree {0}.'.format(
                    degree))

        # if len(repn.linear_vars) > 0:
        referenced_vars.update(repn.linear_vars)

        indexes = []
        [
            indexes.append(self._pyomo_var_to_solver_var_map[i])
            for i in repn.linear_vars
        ]

        new_expr = [list(repn.linear_coefs), indexes, repn.constant]

        qsubi = []
        qsubj = []
        qval = []
        for i, v in enumerate(repn.quadratic_vars):
            x, y = v
            qsubj.append(self._pyomo_var_to_solver_var_map[x])
            qsubi.append(self._pyomo_var_to_solver_var_map[y])
            qval.append(repn.quadratic_coefs[i] * ((qsubi == qsubj) + 1))
            referenced_vars.add(x)
            referenced_vars.add(y)
        new_expr.extend([qval, qsubi, qsubj])

        return new_expr, referenced_vars
Exemplo n.º 4
0
    def _get_expr_from_pyomo_repn(self, repn, max_degree=2):
        referenced_vars = ComponentSet()

        degree = repn.polynomial_degree()
        if (degree is None) or (degree > max_degree):
            raise DegreeError(
                'CPLEXDirect does not support expressions of degree {0}.'.
                format(degree))

        new_expr = _CplexExpr()
        if len(repn.linear_vars) > 0:
            referenced_vars.update(repn.linear_vars)
            new_expr.variables.extend(self._pyomo_var_to_ndx_map[i]
                                      for i in repn.linear_vars)
            new_expr.coefficients.extend(repn.linear_coefs)

        for i, v in enumerate(repn.quadratic_vars):
            x, y = v
            new_expr.q_coefficients.append(repn.quadratic_coefs[i])
            new_expr.q_variables1.append(self._pyomo_var_to_ndx_map[x])
            new_expr.q_variables2.append(self._pyomo_var_to_ndx_map[y])
            referenced_vars.add(x)
            referenced_vars.add(y)

        new_expr.offset = repn.constant

        return new_expr, referenced_vars
Exemplo n.º 5
0
def _process_container(blk, config):
    if not hasattr(blk, '_induced_linearity_info'):
        blk._induced_linearity_info = Block()
    else:
        assert blk._induced_linearity_info.type() == Block
    eff_discr_vars = detect_effectively_discrete_vars(
        blk, config.equality_tolerance)
    # TODO will need to go through this for each disjunct, since it does
    # not (should not) descend into Disjuncts.

    # Determine the valid values for the effectively discrete variables
    possible_var_values = determine_valid_values(blk, eff_discr_vars, config)

    # Collect find bilinear expressions that can be reformulated using
    # knowledge of effectively discrete variables
    bilinear_map = _bilinear_expressions(blk)

    # Relevant constraints are those with bilinear terms that involve
    # effectively_discrete_vars
    processed_pairs = ComponentSet()
    for v1, var_values in possible_var_values.items():
        v1_pairs = bilinear_map.get(v1, ())
        for v2, bilinear_constrs in v1_pairs.items():
            if (v1, v2) in processed_pairs:
                continue
            _process_bilinear_constraints(
                blk, v1, v2, var_values, bilinear_constrs)
            processed_pairs.add((v2, v1))
Exemplo n.º 6
0
    def _get_expr_from_pyomo_repn(self, repn, max_degree=2):
        degree = repn.polynomial_degree()
        if degree is None or degree > max_degree:
            raise DegreeError(
                "CPLEXDirect does not support expressions of degree {0}.".
                format(degree))

        referenced_vars = ComponentSet(repn.linear_vars)

        q_coefficients = []
        q_variables1 = []
        q_variables2 = []
        for i, v in enumerate(repn.quadratic_vars):
            x, y = v
            q_coefficients.append(repn.quadratic_coefs[i])
            q_variables1.append(self._pyomo_var_to_ndx_map[x])
            q_variables2.append(self._pyomo_var_to_ndx_map[y])
            referenced_vars.add(x)
            referenced_vars.add(y)

        return (
            _CplexExpr(
                variables=[
                    self._pyomo_var_to_ndx_map[var] for var in repn.linear_vars
                ],
                coefficients=repn.linear_coefs,
                offset=repn.constant,
                q_variables1=q_variables1,
                q_variables2=q_variables2,
                q_coefficients=q_coefficients,
            ),
            referenced_vars,
        )
Exemplo n.º 7
0
    def _get_expr_from_pyomo_repn(self, repn, max_degree=2):
        referenced_vars = ComponentSet()

        degree = repn.polynomial_degree()
        if (degree is None) or (degree > max_degree):
            raise DegreeError(
                'XpressDirect does not support expressions of degree {0}.'.
                format(degree))

        # NOTE: xpress's python interface only allows for expresions
        #       with native numeric types. Others, like numpy.float64,
        #       will cause an exception when constructing expressions
        if len(repn.linear_vars) > 0:
            referenced_vars.update(repn.linear_vars)
            new_expr = self._xpress.Sum(
                float(coef) * self._pyomo_var_to_solver_var_map[var]
                for coef, var in zip(repn.linear_coefs, repn.linear_vars))
        else:
            new_expr = 0.0

        for coef, (x, y) in zip(repn.quadratic_coefs, repn.quadratic_vars):
            new_expr += float(coef) * self._pyomo_var_to_solver_var_map[
                x] * self._pyomo_var_to_solver_var_map[y]
            referenced_vars.add(x)
            referenced_vars.add(y)

        new_expr += repn.constant

        return new_expr, referenced_vars
Exemplo n.º 8
0
    def run_order(self, G, order, function, ignore=None, use_guesses=False):
        """
        Run computations in the order provided by calling the function

        Arguments
        ---------
            G
                A networkx graph corresponding to order
            order
                The order in which to run each node in the graph
            function
                The function to be called on each block/node
            ignore
                Edge indexes to ignore when passing values
            use_guesses
                If True, will check the guesses dict when fixing
                free variables before calling function
        """
        fixed_inputs = self.fixed_inputs()
        fixed_outputs = ComponentSet()
        edge_map = self.edge_to_idx(G)
        guesses = self.options["guesses"]
        default = self.options["default_guess"]
        for lev in order:
            for unit in lev:
                if unit not in fixed_inputs:
                    fixed_inputs[unit] = ComponentSet()
                fixed_ins = fixed_inputs[unit]

                # make sure all inputs are fixed
                for port in unit.component_data_objects(Port):
                    if not len(port.sources()):
                        continue
                    if use_guesses and port in guesses:
                        self.load_guesses(guesses, port, fixed_ins)
                    self.load_values(port, default, fixed_ins, use_guesses)

                function(unit)

                # free the inputs that were not already fixed
                for var in fixed_ins:
                    var.free()
                fixed_ins.clear()

                # pass the values downstream for all outlet ports
                for port in unit.component_data_objects(Port):
                    dests = port.dests()
                    if not len(dests):
                        continue
                    for var in port.iter_vars(expr_vars=True, fixed=False):
                        fixed_outputs.add(var)
                        var.fix()
                    for arc in dests:
                        arc_map = self.arc_to_edge(G)
                        if edge_map[arc_map[arc]] not in ignore:
                            self.pass_values(arc, fixed_inputs)
                    for var in fixed_outputs:
                        var.free()
                    fixed_outputs.clear()
Exemplo n.º 9
0
    def run_order(self, G, order, function, ignore=None, use_guesses=False):
        """
        Run computations in the order provided by calling the function

        Arguments
        ---------
            G
                A networkx graph corresponding to order
            order
                The order in which to run each node in the graph
            function
                The function to be called on each block/node
            ignore
                Edge indexes to ignore when passing values
            use_guesses
                If True, will check the guesses dict when fixing
                free variables before calling function
        """
        fixed_inputs = self.fixed_inputs()
        fixed_outputs = ComponentSet()
        edge_map = self.edge_to_idx(G)
        guesses = self.options["guesses"]
        default = self.options["default_guess"]
        for lev in order:
            for unit in lev:
                if unit not in fixed_inputs:
                    fixed_inputs[unit] = ComponentSet()
                fixed_ins = fixed_inputs[unit]

                # make sure all inputs are fixed
                for port in unit.component_data_objects(Port):
                    if not len(port.sources()):
                        continue
                    if use_guesses and port in guesses:
                        self.load_guesses(guesses, port, fixed_ins)
                    self.load_values(port, default, fixed_ins, use_guesses)

                function(unit)

                # free the inputs that were not already fixed
                for var in fixed_ins:
                    var.free()
                fixed_ins.clear()

                # pass the values downstream for all outlet ports
                for port in unit.component_data_objects(Port):
                    dests = port.dests()
                    if not len(dests):
                        continue
                    for var in port.iter_vars(expr_vars=True, fixed=False):
                        fixed_outputs.add(var)
                        var.fix()
                    for arc in dests:
                        arc_map = self.arc_to_edge(G)
                        if edge_map[arc_map[arc]] not in ignore:
                            self.pass_values(arc, fixed_inputs)
                    for var in fixed_outputs:
                        var.free()
                    fixed_outputs.clear()
Exemplo n.º 10
0
def free_variables_in_active_equalities_set(blk):
    """
    Return a set of variables that are contined in active equalities.
    """
    vin = ComponentSet()
    for c in active_equalities(blk):
        for v in identify_variables(c.body):
            if not v.fixed: vin.add(v)
    return vin
Exemplo n.º 11
0
def free_variables_in_active_equalities_set(blk):
    """
    Return a set of variables that are contined in active equalities.
    """
    vin = ComponentSet()
    for c in active_equalities(blk):
        for v in identify_variables(c.body):
            if not v.fixed: vin.add(v)
    return vin
Exemplo n.º 12
0
def add_integer_cut(var_values, target_model, solve_data, config, feasible=False):
    """Add an integer cut to the target GDP model."""
    with time_code(solve_data.timing, 'integer cut generation'):
        m = target_model
        GDPopt = m.GDPopt_utils
        var_value_is_one = ComponentSet()
        var_value_is_zero = ComponentSet()
        for var, val in zip(GDPopt.variable_list, var_values):
            if not var.is_binary():
                continue
            if var.fixed:
                if val is not None and var.value != val:
                    # val needs to be None or match var.value. Otherwise, we have a
                    # contradiction.
                    raise ValueError(
                        "Fixed variable %s has value %s != "
                        "provided value of %s." % (var.name, var.value, val))
                val = var.value

            if not config.force_subproblem_nlp:
                # Skip indicator variables
                # TODO we should implement this as a check among Disjuncts instead
                if not (var.local_name == 'indicator_var' and var.parent_block().type() == Disjunct):
                    continue

            if fabs(val - 1) <= config.integer_tolerance:
                var_value_is_one.add(var)
            elif fabs(val) <= config.integer_tolerance:
                var_value_is_zero.add(var)
            else:
                raise ValueError(
                    'Binary %s = %s is not 0 or 1' % (var.name, val))

        if not (var_value_is_one or var_value_is_zero):
            # if no remaining binary variables, then terminate algorithm.
            config.logger.info(
                'Adding integer cut to a model without discrete variables. '
                'Model is now infeasible.')
            if solve_data.objective_sense == minimize:
                solve_data.LB = float('inf')
            else:
                solve_data.UB = float('-inf')
            return False

        int_cut = (sum(1 - v for v in var_value_is_one) +
                   sum(v for v in var_value_is_zero)) >= 1

        if not feasible:
            config.logger.info('Adding integer cut')
            GDPopt.integer_cuts.add(expr=int_cut)
        else:
            backtracking_enabled = (
                "disabled" if GDPopt.no_backtracking.active else "allowed")
            config.logger.info(
                'Registering explored configuration. '
                'Backtracking is currently %s.' % backtracking_enabled)
            GDPopt.no_backtracking.add(expr=int_cut)
Exemplo n.º 13
0
def add_integer_cut(var_values, target_model, solve_data, config, feasible=False):
    """Add an integer cut to the target GDP model."""
    with time_code(solve_data.timing, 'integer cut generation'):
        m = target_model
        GDPopt = m.GDPopt_utils
        var_value_is_one = ComponentSet()
        var_value_is_zero = ComponentSet()
        for var, val in zip(GDPopt.variable_list, var_values):
            if not var.is_binary():
                continue
            if var.fixed:
                if val is not None and var.value != val:
                    # val needs to be None or match var.value. Otherwise, we have a
                    # contradiction.
                    raise ValueError(
                        "Fixed variable %s has value %s != "
                        "provided value of %s." % (var.name, var.value, val))
                val = var.value

            if not config.force_subproblem_nlp:
                # Skip indicator variables
                # TODO we should implement this as a check among Disjuncts instead
                if not (var.local_name == 'indicator_var' and var.parent_block().type() == Disjunct):
                    continue

            if fabs(val - 1) <= config.integer_tolerance:
                var_value_is_one.add(var)
            elif fabs(val) <= config.integer_tolerance:
                var_value_is_zero.add(var)
            else:
                raise ValueError(
                    'Binary %s = %s is not 0 or 1' % (var.name, val))

        if not (var_value_is_one or var_value_is_zero):
            # if no remaining binary variables, then terminate algorithm.
            config.logger.info(
                'Adding integer cut to a model without discrete variables. '
                'Model is now infeasible.')
            if solve_data.objective_sense == minimize:
                solve_data.LB = float('inf')
            else:
                solve_data.UB = float('-inf')
            return False

        int_cut = (sum(1 - v for v in var_value_is_one) +
                   sum(v for v in var_value_is_zero)) >= 1

        if not feasible:
            config.logger.info('Adding integer cut')
            GDPopt.integer_cuts.add(expr=int_cut)
        else:
            backtracking_enabled = (
                "disabled" if GDPopt.no_backtracking.active else "allowed")
            config.logger.info(
                'Registering explored configuration. '
                'Backtracking is currently %s.' % backtracking_enabled)
            GDPopt.no_backtracking.add(expr=int_cut)
Exemplo n.º 14
0
def active_equality_set(blk):
    """
    Generator returning active equality constraints in a model.

    Args:
        blk: a Pyomo block in which to look for variables.
    """
    ac = ComponentSet()
    for c in active_equalities(blk):
        ac.add(c)
    return ac
Exemplo n.º 15
0
def active_equality_set(blk):
    """
    Generator returning active equality constraints in a model.

    Args:
        blk: a Pyomo block in which to look for variables.
    """
    ac = ComponentSet()
    for c in active_equalities(blk):
        ac.add(c)
    return ac
Exemplo n.º 16
0
 def test_isdisjoint(self):
     cset1 = ComponentSet()
     cset2 = ComponentSet()
     self.assertTrue(cset1.isdisjoint(cset2))
     self.assertTrue(cset2.isdisjoint(cset1))
     v = variable()
     cset1.add(v)
     self.assertTrue(cset1.isdisjoint(cset2))
     self.assertTrue(cset2.isdisjoint(cset1))
     cset2.add(v)
     self.assertFalse(cset1.isdisjoint(cset2))
     self.assertFalse(cset2.isdisjoint(cset1))
Exemplo n.º 17
0
 def pass_edges(self, G, edges):
     """Call pass values for a list of edge indexes"""
     fixed_outputs = ComponentSet()
     edge_list = self.idx_to_edge(G)
     for ei in edges:
         arc = G.edges[edge_list[ei]]["arc"]
         for var in arc.src.iter_vars(expr_vars=True, fixed=False):
             fixed_outputs.add(var)
             var.fix()
         self.pass_values(arc, self.fixed_inputs())
         for var in fixed_outputs:
             var.free()
         fixed_outputs.clear()
Exemplo n.º 18
0
 def test_pop(self):
     cset = ComponentSet()
     self.assertEqual(len(cset), 0)
     with self.assertRaises(KeyError):
         cset.pop()
     v = variable()
     cset.add(v)
     self.assertTrue(v in cset)
     self.assertEqual(len(cset), 1)
     v_ = cset.pop()
     self.assertIs(v, v_)
     self.assertTrue(v not in cset)
     self.assertEqual(len(cset), 0)
Exemplo n.º 19
0
 def pass_edges(self, G, edges):
     """Call pass values for a list of edge indexes"""
     fixed_outputs = ComponentSet()
     edge_list = self.idx_to_edge(G)
     for ei in edges:
         arc = G.edges[edge_list[ei]]["arc"]
         for var in arc.src.iter_vars(expr_vars=True, fixed=False):
             fixed_outputs.add(var)
             var.fix()
         self.pass_values(arc, self.fixed_inputs())
         for var in fixed_outputs:
             var.free()
         fixed_outputs.clear()
Exemplo n.º 20
0
def add_integer_cut(var_values, solve_data, config, feasible=False):
    """Add an integer cut to the linear GDP model."""
    m = solve_data.linear_GDP
    GDPopt = m.GDPopt_utils
    var_value_is_one = ComponentSet()
    var_value_is_zero = ComponentSet()
    for var, val in zip(GDPopt.working_var_list, var_values):
        if not var.is_binary():
            continue
        if var.fixed:
            if val is not None and var.value != val:
                # val needs to be None or match var.value. Otherwise, we have a
                # contradiction.
                raise ValueError(
                    "Fixed variable %s has value %s != "
                    "provided value of %s." % (var.name, var.value, val))
            val = var.value
        # TODO we can also add a check to skip binary variables that are not an
        # indicator_var on disjuncts.
        if fabs(val - 1) <= config.integer_tolerance:
            var_value_is_one.add(var)
        elif fabs(val) <= config.integer_tolerance:
            var_value_is_zero.add(var)
        else:
            raise ValueError(
                'Binary %s = %s is not 0 or 1' % (var.name, val))

    if not (var_value_is_one or var_value_is_zero):
        # if no remaining binary variables, then terminate algorithm.
        config.logger.info(
            'Adding integer cut to a model without binary variables. '
            'Model is now infeasible.')
        if solve_data.objective_sense == minimize:
            solve_data.LB = float('inf')
        else:
            solve_data.UB = float('-inf')
        return False

    int_cut = (sum(1 - v for v in var_value_is_one) +
               sum(v for v in var_value_is_zero)) >= 1

    if not feasible:
        config.logger.info('Adding integer cut')
        GDPopt.integer_cuts.add(expr=int_cut)
    else:
        backtracking_enabled = (
            "disabled" if GDPopt.no_backtracking.active else "allowed")
        config.logger.info(
            'Registering explored configuration. '
            'Backtracking is currently %s.' % backtracking_enabled)
        GDPopt.no_backtracking.add(expr=int_cut)
Exemplo n.º 21
0
 def visit(self, node, values):
     if node.__class__ is not EXPR.ExternalFunctionExpression:
         return node
     if id(node._fcn) not in self.efSet:
         return node
     # At this point we know this is an ExternalFunctionExpression
     # node that we want to replace with an auliliary variable (y)
     new_args = []
     seen = ComponentSet()
     # TODO: support more than PythonCallbackFunctions
     assert isinstance(node._fcn, PythonCallbackFunction)
     #
     # Note: the first argument to PythonCallbackFunction is the
     # function ID.  Since we are going to complain about constant
     # parameters, we need to skip the first argument when processing
     # the argument list.  This is really not good: we should allow
     # for constant arguments to the functions, and we should relax
     # the restriction that the external functions implement the
     # PythonCallbackFunction API (that restriction leads unfortunate
     # things later; i.e., accessing the private _fcn attribute
     # below).
     for arg in list(values)[1:]:
         if type(arg) in nonpyomo_leaf_types or arg.is_fixed():
             # We currently do not allow constants or parameters for
             # the external functions.
             raise RuntimeError(
                 "TrustRegion does not support black boxes with "
                 "constant or parameter inputs\n\tExpression: %s"
                 % (node,) )
         if arg.is_expression_type():
             # All expressions (including simple linear expressions)
             # are replaced with a single auxiliary variable (and
             # eventually an additional constraint equating the
             # auxiliary variable to the original expression)
             _x = self.trf.x.add()
             _x.set_value( value(arg) )
             self.trf.conset.add(_x == arg)
             new_args.append(_x)
         else:
             # The only thing left is bare variables: check for duplicates.
             if arg in seen:
                 raise RuntimeError(
                     "TrustRegion does not support black boxes with "
                     "duplicate input arguments\n\tExpression: %s"
                     % (node,) )
             seen.add(arg)
             new_args.append(arg)
     _y = self.trf.y.add()
     self.trf.external_fcns.append(node)
     self.trf.exfn_xvars.append(new_args)
     return _y
Exemplo n.º 22
0
 def visit(self, node, values):
     if node.__class__ is not EXPR.ExternalFunctionExpression:
         return node
     if id(node._fcn) not in self.efSet:
         return node
     # At this point we know this is an ExternalFunctionExpression
     # node that we want to replace with an auliliary variable (y)
     new_args = []
     seen = ComponentSet()
     # TODO: support more than PythonCallbackFunctions
     assert isinstance(node._fcn, PythonCallbackFunction)
     #
     # Note: the first argument to PythonCallbackFunction is the
     # function ID.  Since we are going to complain about constant
     # parameters, we need to skip the first argument when processing
     # the argument list.  This is really not good: we should allow
     # for constant arguments to the functions, and we should relax
     # the restriction that the external functions implement the
     # PythonCallbackFunction API (that restriction leads unfortunate
     # things later; i.e., accessing the private _fcn attribute
     # below).
     for arg in list(values)[1:]:
         if type(arg) in nonpyomo_leaf_types or arg.is_fixed():
             # We currently do not allow constants or parameters for
             # the external functions.
             raise RuntimeError(
                 "TrustRegion does not support black boxes with "
                 "constant or parameter inputs\n\tExpression: %s" %
                 (node, ))
         if arg.is_expression_type():
             # All expressions (including simple linear expressions)
             # are replaced with a single auxiliary variable (and
             # eventually an additional constraint equating the
             # auxiliary variable to the original expression)
             _x = self.trf.x.add()
             _x.set_value(value(arg))
             self.trf.conset.add(_x == arg)
             new_args.append(_x)
         else:
             # The only thing left is bare variables: check for duplicates.
             if arg in seen:
                 raise RuntimeError(
                     "TrustRegion does not support black boxes with "
                     "duplicate input arguments\n\tExpression: %s" %
                     (node, ))
             seen.add(arg)
             new_args.append(arg)
     _y = self.trf.y.add()
     self.trf.external_fcns.append(node)
     self.trf.exfn_xvars.append(new_args)
     return _y
Exemplo n.º 23
0
 def set_add(self):
     cset = ComponentSet()
     self.assertEqual(len(cset), 0)
     for i, c in enumerate(self._components):
         self.assertTrue(c not in cset)
         cset.add(c)
         self.assertTrue(c in cset)
         self.assertEqual(len(cset), i + 1)
     self.assertEqual(len(cset), len(self._components))
     for c in self._components:
         self.assertTrue(c in cset)
         cset.add(c)
         self.assertTrue(c in cset)
         self.assertEqual(len(cset), len(self._components))
Exemplo n.º 24
0
def filter_variables_from_solution(candidate_variables_at_relaxation_solution, tolerance=1e-6):
    """
    This function takes a set of candidate variables for OBBT and filters out 
    the variables that are at their bounds in the provided solution to the 
    relaxation. See 

    Gleixner, Ambros M., et al. "Three enhancements for
    optimization-based bound tightening." Journal of Global
    Optimization 67.4 (2017): 731-757.

    for details on why this works. The basic idea is that if x = xl is
    feasible for the relaxation that will be used for OBBT, then
    minimizing x subject to that relaxation is guaranteed to result in
    an optimal solution of x* = xl.

    This function simply loops through
    candidate_variables_at_relaxation_solution and specifies which
    variables should be minimized and which variables should be
    maximized with OBBT.

    Parameters
    ----------
    candidate_variables_at_relaxation_solution: iterable of _GeneralVarData
        This should be an iterable of the variables which are candidates 
        for OBBT. The values of the variables should be feasible for the 
        relaxation that would be used to perform OBBT on the variables.
    tolerance: float
        A float greater than or equal to zero. If the value of the variable
        is within tolerance of its lower bound, then that variable is filtered
        from the set of variables that should be minimized for OBBT. The same
        is true for upper bounds and variables that should be maximized.

    Returns
    -------
    vars_to_minimize: ComponentSet of _GeneralVarData
        variables that should be considered for minimization
    vars_to_maximize: ComponentSet of _GeneralVarData
        variables that should be considered for maximization
    """
    candidate_vars = ComponentSet(candidate_variables_at_relaxation_solution)
    vars_to_minimize = ComponentSet()
    vars_to_maximize = ComponentSet()

    for v in candidate_vars:
        if (not v.has_lb()) or (v.value - v.lb > tolerance):
            vars_to_minimize.add(v)
        if (not v.has_ub()) or (v.ub - v.value > tolerance):
            vars_to_maximize.add(v)

    return vars_to_minimize, vars_to_maximize
Exemplo n.º 25
0
def constraints_in_True_disjuncts(model, config):
    """Yield constraints in disjuncts where the indicator value is set or fixed to True."""
    for constr in model.component_data_objects(Constraint):
        yield constr
    observed_disjuncts = ComponentSet()
    for disjctn in model.component_data_objects(Disjunction):
        # get all the disjuncts in the disjunction. Check which ones are True.
        for disj in disjctn.disjuncts:
            if disj in observed_disjuncts:
                continue
            observed_disjuncts.add(disj)
            if fabs(disj.indicator_var.value - 1) <= config.integer_tolerance:
                for constr in disj.component_data_objects(Constraint):
                    yield constr
Exemplo n.º 26
0
def constraints_in_True_disjuncts(model, config):
    """Yield constraints in disjuncts where the indicator value is set or fixed to True."""
    for constr in model.component_data_objects(Constraint):
        yield constr
    observed_disjuncts = ComponentSet()
    for disjctn in model.component_data_objects(Disjunction):
        # get all the disjuncts in the disjunction. Check which ones are True.
        for disj in disjctn.disjuncts:
            if disj in observed_disjuncts:
                continue
            observed_disjuncts.add(disj)
            if fabs(disj.indicator_var.value - 1) <= config.integer_tolerance:
                for constr in disj.component_data_objects(Constraint):
                    yield constr
Exemplo n.º 27
0
    def pass_tear_direct(self, G, tears):
        """Pass values across all tears in the given tear set"""
        fixed_outputs = ComponentSet()
        edge_list = self.idx_to_edge(G)

        for tear in tears:
            # fix everything then call pass values
            arc = G.edges[edge_list[tear]]["arc"]
            for var in arc.src.iter_vars(expr_vars=True, fixed=False):
                fixed_outputs.add(var)
                var.fix()
            self.pass_values(arc, fixed_inputs=self.fixed_inputs())
            for var in fixed_outputs:
                var.free()
            fixed_outputs.clear()
Exemplo n.º 28
0
    def pass_tear_direct(self, G, tears):
        """Pass values across all tears in the given tear set"""
        fixed_outputs = ComponentSet()
        edge_list = self.idx_to_edge(G)

        for tear in tears:
            # fix everything then call pass values
            arc = G.edges[edge_list[tear]]["arc"]
            for var in arc.src.iter_vars(expr_vars=True, fixed=False):
                fixed_outputs.add(var)
                var.fix()
            self.pass_values(arc, fixed_inputs=self.fixed_inputs())
            for var in fixed_outputs:
                var.free()
            fixed_outputs.clear()
Exemplo n.º 29
0
def _detect_fixed_variables(m):
    """Detect fixed variables due to constraints of form var = const."""
    new_fixed_vars = ComponentSet()
    for constr in m.component_data_objects(ctype=Constraint,
                                           active=True,
                                           descend_into=True):
        if constr.equality and constr.body.polynomial_degree() == 1:
            repn = generate_canonical_repn(constr.body)
            if len(repn.variables) == 1 and repn.linear[0]:
                var = repn.variables[0]
                coef = float(repn.linear[0])
                const = repn.constant if repn.constant is not None else 0
                var_val = (value(constr.lower) - value(const)) / coef
                var.fix(var_val)
                new_fixed_vars.add(var)
    return new_fixed_vars
Exemplo n.º 30
0
def vars_to_eliminate_list(x):
    if isinstance(x, (Var, _VarData)):
        if not x.is_indexed():
            return ComponentSet([x])
        ans = ComponentSet()
        for j in x.index_set():
            ans.add(x[j])
        return ans
    elif hasattr(x, '__iter__'):
        ans = ComponentSet()
        for i in x:
            ans.update(vars_to_eliminate_list(i))
        return ans
    else:
        raise ValueError("Expected Var or list of Vars."
                         "\n\tRecieved %s" % type(x))
Exemplo n.º 31
0
def total_blocks_set(block):
    """
    Method to return a ComponentSet of all Block components in a model.

    Args:
        block : model to be studied

    Returns:
        A ComponentSet including all Block components in block (including block
        itself)
    """
    total_blocks_set = ComponentSet(
            block.component_data_objects(
                    ctype=Block, active=None, descend_into=True))
    total_blocks_set.add(block)
    return total_blocks_set
Exemplo n.º 32
0
def _detect_fixed_variables(m):
    """Detect fixed variables due to constraints of form var = const."""
    new_fixed_vars = ComponentSet()
    for constr in m.component_data_objects(ctype=Constraint,
                                           active=True,
                                           descend_into=True):
        if constr.equality and constr.body.polynomial_degree() == 1:
            repn = generate_standard_repn(constr.body)
            if len(repn.linear_vars) == 1 and repn.linear_coefs[0]:
                var = repn.linear_vars[0]
                coef = float(repn.linear_coefs[0])
                const = repn.constant
                var_val = (value(constr.lower) - value(const)) / coef
                var.fix(var_val)
                new_fixed_vars.add(var)
    return new_fixed_vars
Exemplo n.º 33
0
    def _get_expr_from_pyomo_repn(self, repn, max_degree=2):
        referenced_vars = ComponentSet()

        degree = canonical_degree(repn)
        if (degree is None) or (degree > max_degree):
            raise DegreeError(
                'CPLEXDirect does not support expressions of degree {0}.'.
                format(degree))

        if isinstance(repn, LinearCanonicalRepn):
            new_expr = _CplexExpr()

            if repn.constant is not None:
                new_expr.offset = repn.constant

            if (repn.linear is not None) and (len(repn.linear) > 0):
                list(map(referenced_vars.add, repn.variables))
                new_expr.variables.extend(self._pyomo_var_to_ndx_map[var]
                                          for var in repn.variables)
                new_expr.coefficients.extend(coeff for coeff in repn.linear)

        else:
            new_expr = _CplexExpr()
            if 0 in repn:
                new_expr.offset = repn[0][None]

            if 1 in repn:
                for ndx, coeff in repn[1].items():
                    new_expr.coefficients.append(coeff)
                    var = repn[-1][ndx]
                    new_expr.variables.append(self._pyomo_var_to_ndx_map[var])
                    referenced_vars.add(var)

            if 2 in repn:
                for key, coeff in repn[2].items():
                    new_expr.q_coefficients.append(coeff)
                    indices = list(key.keys())
                    if len(indices) == 1:
                        ndx = indices[0]
                        var = repn[-1][ndx]
                        referenced_vars.add(var)
                        cplex_var = self._pyomo_var_to_ndx_map[var]
                        new_expr.q_variables1.append(cplex_var)
                        new_expr.q_variables2.append(cplex_var)
                    else:
                        ndx = indices[0]
                        var = repn[-1][ndx]
                        referenced_vars.add(var)
                        cplex_var = self._pyomo_var_to_ndx_map[var]
                        new_expr.q_variables1.append(cplex_var)
                        ndx = indices[1]
                        var = repn[-1][ndx]
                        referenced_vars.add(var)
                        cplex_var = self._pyomo_var_to_ndx_map[var]
                        new_expr.q_variables2.append(cplex_var)

        return new_expr, referenced_vars
Exemplo n.º 34
0
def fixed_unused_variables_set(block):
    """
    Method to return a ComponentSet of all fixed Var components which do not
    appear within any activated Constraint in a model.

    Args:
        block : model to be studied

    Returns:
        A ComponentSet including all fixed Var components which do not appear
        within any Constraints in block
    """
    var_set = ComponentSet()
    for v in unused_variables_set(block):
        if v.fixed:
            var_set.add(v)
    return var_set
Exemplo n.º 35
0
def fixed_variables_only_in_inequalities(block):
    """
    Method to return a ComponentSet of all fixed Var components which appear
    only within activated inequality Constraints in a model.

    Args:
        block : model to be studied

    Returns:
        A ComponentSet including all fixed Var components which appear only
        within activated inequality Constraints in block
    """
    var_set = ComponentSet()
    for v in variables_only_in_inequalities(block):
        if v.fixed:
            var_set.add(v)
    return var_set
Exemplo n.º 36
0
def variables_in_activated_inequalities_set(block):
    """
    Method to return a ComponentSet of all Var components which appear within
    an inequality Constraint in a model.

    Args:
        block : model to be studied

    Returns:
        A ComponentSet including all Var components which appear within
        activated inequality Constraints in block
    """
    var_set = ComponentSet()
    for c in activated_inequalities_generator(block):
        for v in identify_variables(c.body):
            var_set.add(v)
    return var_set
Exemplo n.º 37
0
    def test_pickle(self):
        c = ComponentSet()
        self.assertEqual(len(c), 0)
        cup = pickle.loads(pickle.dumps(c))
        self.assertIsNot(cup, c)
        self.assertEqual(len(cup), 0)

        v = variable()
        c.add(v)
        self.assertEqual(len(c), 1)
        self.assertTrue(v in c)
        cup = pickle.loads(pickle.dumps(c))
        vup = cup.pop()
        cup.add(vup)
        self.assertIsNot(cup, c)
        self.assertIsNot(vup, v)
        self.assertEqual(len(cup), 1)
        self.assertTrue(vup in cup)
        self.assertEqual(vup.parent, None)

        b = block()
        V = b.V = variable_list()
        b.V.append(v)
        b.c = c
        self.assertEqual(len(c), 1)
        self.assertTrue(v in c)
        self.assertIs(v.parent, b.V)
        self.assertIs(V.parent, b)
        self.assertIs(b.parent, None)
        bup = pickle.loads(pickle.dumps(b))
        Vup = bup.V
        vup = Vup[0]
        cup = bup.c
        self.assertIsNot(cup, c)
        self.assertIsNot(vup, v)
        self.assertIsNot(Vup, V)
        self.assertIsNot(bup, b)
        self.assertEqual(len(cup), 1)
        self.assertTrue(vup in cup)
        self.assertIs(vup.parent, Vup)
        self.assertIs(Vup.parent, bup)
        self.assertIs(bup.parent, None)

        self.assertEqual(len(c), 1)
        self.assertTrue(v in c)
Exemplo n.º 38
0
def active_variables_in_deactivated_blocks_set(block):
    """
    Method to return a ComponentSet of any Var components which appear within
    an active Constraint but belong to a deacitvated Block in a model.

    Args:
        block : model to be studied

    Returns:
        A ComponentSet including any Var components which belong to a
        deacitvated Block but appear in an activate Constraint in block
    """
    var_set = ComponentSet()
    block_set = activated_blocks_set(block)
    for v in variables_in_activated_constraints_set(block):
        if v.parent_block() not in block_set:
            var_set.add(v)
    return var_set
Exemplo n.º 39
0
def variables_in_activated_constraints_set(block):
    """
    Method to return a ComponentSet of all Var components which appear within a
    Constraint in a model.

    Args:
        block : model to be studied

    Returns:
        A ComponentSet including all Var components which appear within
        activated Constraints in block
    """
    var_set = ComponentSet()
    for c in block.component_data_objects(
            ctype=Constraint, active=True, descend_into=True):
        for v in identify_variables(c.body):
            var_set.add(v)
    return var_set
Exemplo n.º 40
0
    def _get_expr_from_pyomo_repn(self, repn, max_degree=2):
        referenced_vars = ComponentSet()

        degree = canonical_degree(repn)
        if (degree is None) or (degree > max_degree):
            raise DegreeError(
                'GurobiDirect does not support expressions of degree {0}.'.
                format(degree))

        if isinstance(repn, LinearCanonicalRepn):
            if (repn.linear is not None) and (len(repn.linear) > 0):
                list(map(referenced_vars.add, repn.variables))
                new_expr = self._gurobipy.LinExpr(repn.linear, [
                    self._pyomo_var_to_solver_var_map[i]
                    for i in repn.variables
                ])
            else:
                new_expr = 0

            if repn.constant is not None:
                new_expr += repn.constant

        else:
            new_expr = 0
            if 0 in repn:
                new_expr += repn[0][None]

            if 1 in repn:
                for ndx, coeff in repn[1].items():
                    new_expr += coeff * self._pyomo_var_to_solver_var_map[
                        repn[-1][ndx]]
                    referenced_vars.add(repn[-1][ndx])

            if 2 in repn:
                for key, coeff in repn[2].items():
                    tmp_expr = coeff
                    for ndx, power in key.items():
                        referenced_vars.add(repn[-1][ndx])
                        for i in range(power):
                            tmp_expr *= self._pyomo_var_to_solver_var_map[
                                repn[-1][ndx]]
                    new_expr += tmp_expr

        return new_expr, referenced_vars
Exemplo n.º 41
0
    def _get_expr_from_pyomo_repn(self, repn, max_degree=2):
        referenced_vars = ComponentSet()

        degree = repn.polynomial_degree()
        if (degree is None) or (degree > max_degree):
            raise DegreeError('GurobiDirect does not support expressions of degree {0}.'.format(degree))

        if len(repn.linear_vars) > 0:
            referenced_vars.update(repn.linear_vars)
            new_expr = self._gurobipy.LinExpr(repn.linear_coefs, [self._pyomo_var_to_solver_var_map[i] for i in repn.linear_vars])
        else:
            new_expr = 0.0

        for i,v in enumerate(repn.quadratic_vars):
            x,y = v
            new_expr += repn.quadratic_coefs[i] * self._pyomo_var_to_solver_var_map[x] * self._pyomo_var_to_solver_var_map[y]
            referenced_vars.add(x)
            referenced_vars.add(y)

        new_expr += repn.constant

        return new_expr, referenced_vars
Exemplo n.º 42
0
    def _get_expr_from_pyomo_repn(self, repn, max_degree=2):
        referenced_vars = ComponentSet()

        degree = repn.polynomial_degree()
        if (degree is None) or (degree > max_degree):
            raise DegreeError('CPLEXDirect does not support expressions of degree {0}.'.format(degree))

        new_expr = _CplexExpr()
        if len(repn.linear_vars) > 0:
            referenced_vars.update(repn.linear_vars)
            new_expr.variables.extend(self._pyomo_var_to_ndx_map[i] for i in repn.linear_vars)
            new_expr.coefficients.extend(repn.linear_coefs)

        for i, v in enumerate(repn.quadratic_vars):
            x, y = v
            new_expr.q_coefficients.append(repn.quadratic_coefs[i])
            new_expr.q_variables1.append(self._pyomo_var_to_ndx_map[x])
            new_expr.q_variables2.append(self._pyomo_var_to_ndx_map[y])
            referenced_vars.add(x)
            referenced_vars.add(y)

        new_expr.offset = repn.constant

        return new_expr, referenced_vars
Exemplo n.º 43
0
def build_ordered_component_lists(model, solve_data):
    """Define lists used for future data transfer.

    Also attaches ordered lists of the variables, constraints, disjuncts, and
    disjunctions to the model so that they can be used for mapping back and
    forth.

    """
    util_blk = getattr(model, solve_data.util_block_name)
    var_set = ComponentSet()
    setattr(
        util_blk, 'constraint_list', list(
            model.component_data_objects(
                ctype=Constraint, active=True,
                descend_into=(Block, Disjunct))))
    setattr(
        util_blk, 'disjunct_list', list(
            model.component_data_objects(
                ctype=Disjunct, descend_into=(Block, Disjunct))))
    setattr(
        util_blk, 'disjunction_list', list(
            model.component_data_objects(
                ctype=Disjunction, active=True,
                descend_into=(Disjunct, Block))))

    # Identify the non-fixed variables in (potentially) active constraints and
    # objective functions
    for constr in getattr(util_blk, 'constraint_list'):
        for v in identify_variables(constr.body, include_fixed=False):
            var_set.add(v)
    for obj in model.component_data_objects(ctype=Objective, active=True):
        for v in identify_variables(obj.expr, include_fixed=False):
            var_set.add(v)
    # Disjunct indicator variables might not appear in active constraints. In
    # fact, if we consider them Logical variables, they should not appear in
    # active algebraic constraints. For now, they need to be added to the
    # variable set.
    for disj in getattr(util_blk, 'disjunct_list'):
        var_set.add(disj.indicator_var)

    # We use component_data_objects rather than list(var_set) in order to
    # preserve a deterministic ordering.
    var_list = list(
        v for v in model.component_data_objects(
            ctype=Var, descend_into=(Block, Disjunct))
        if v in var_set)
    setattr(util_blk, 'variable_list', var_list)
Exemplo n.º 44
0
    def _apply_to(self, instance, **kwds):
        if __debug__ and logger.isEnabledFor(logging.DEBUG):   #pragma:nocover
            logger.debug("Calling ConnectorExpander")

        connectorsFound = False
        for c in instance.component_data_objects(Connector):
            connectorsFound = True
            break
        if not connectorsFound:
            return

        if __debug__ and logger.isEnabledFor(logging.DEBUG):   #pragma:nocover
            logger.debug("   Connectors found!")

        self._name_buffer = {}

        #
        # At this point, there are connectors in the model, so we must
        # look for constraints that involve connectors and expand them.
        #
        # List of the connectors in the order in which we found them
        # (this should be deterministic, provided that the user's model
        # is deterministic)
        connector_list = []
        # list of constraints with connectors: tuple(constraint, connector_set)
        # (this should be deterministic, provided that the user's model
        # is deterministic)
        constraint_list = []
        # ID of the next connector group (set of matched connectors)
        groupID = 0
        # connector_groups stars out as a dict of {id(set): (groupID, set)}
        # If you sort by the groupID, then this will be deterministic.
        connector_groups = dict()
        # map of connector to the set of connectors that must match it
        matched_connectors = ComponentMap()
        # The set of connectors found in the current constraint
        found = ComponentSet()

        connector_types = set([SimpleConnector, _ConnectorData])
        for constraint in instance.component_data_objects(
                Constraint, sort=SortComponents.deterministic):
            ref = None
            for c in EXPR.identify_components(constraint.body, connector_types):
                found.add(c)
                if c in matched_connectors:
                    if ref is None:
                        # The first connector in this constraint has
                        # already been seen.  We will use that Set as
                        # the reference
                        ref = matched_connectors[c]
                    elif ref is not matched_connectors[c]:
                        # We already have a reference group; merge this
                        # new group into it.
                        #
                        # Optimization: this merge is linear in the size
                        # of the src set.  If the reference set is
                        # smaller, save time by switching to a new
                        # reference set.
                        src = matched_connectors[c]
                        if len(ref) < len(src):
                            ref, src = src, ref
                        ref.update(src)
                        for _ in src:
                            matched_connectors[_] = ref
                        del connector_groups[id(src)]
                    # else: pass
                    #   The new group *is* the reference group;
                    #   there is nothing to do.
                else:
                    # The connector has not been seen before.
                    connector_list.append(c)
                    if ref is None:
                        # This is the first connector in the constraint:
                        # start a new reference set.
                        ref = ComponentSet()
                        connector_groups[id(ref)] = (groupID, ref)
                        groupID += 1
                    # This connector hasn't been seen.  Record it.
                    ref.add(c)
                    matched_connectors[c] = ref
            if ref is not None:
                constraint_list.append((constraint, found))
                found = ComponentSet()

        # Validate all connector sets and expand the empty ones
        known_conn_sets = {}
        for groupID, conn_set in sorted(itervalues(connector_groups)):
            known_conn_sets[id(conn_set)] \
                = self._validate_and_expand_connector_set(conn_set)

        # Expand each constraint
        for constraint, conn_set in constraint_list:
            cList = ConstraintList()
            constraint.parent_block().add_component(
                '%s.expanded' % ( constraint.getname(
                    fully_qualified=False, name_buffer=self._name_buffer), ),
                cList )
            connId = next(iter(conn_set))
            ref = known_conn_sets[id(matched_connectors[connId])]
            for k,v in sorted(iteritems(ref)):
                if v[1] >= 0:
                    _iter = v[0]
                else:
                    _iter = (v[0],)
                for idx in _iter:
                    substitution = {}
                    for c in conn_set:
                        if v[1] >= 0:
                            new_v = c.vars[k][idx]
                        elif k in c.aggregators:
                            new_v = c.vars[k].add()
                        else:
                            new_v = c.vars[k]
                        substitution[id(c)] = new_v
                    cList.add((
                        constraint.lower,
                        EXPR.clone_expression( constraint.body, substitution ),
                        constraint.upper ))
            constraint.deactivate()

        # Now, go back and implement VarList aggregators
        for conn in connector_list:
            block = conn.parent_block()
            for var, aggregator in iteritems(conn.aggregators):
                c = Constraint(expr=aggregator(block, conn.vars[var]))
                block.add_component(
                    '%s.%s.aggregate' % (
                        conn.getname(
                            fully_qualified=True,
                            name_buffer=self._name_buffer),
                        var), c )
Exemplo n.º 45
0
Arquivo: chull.py Projeto: Pyomo/pyomo
    def _transformDisjunctionData(self, obj, transBlock, index):
        # Convex hull doesn't work if this is an or constraint. So if
        # xor is false, give up
        if not obj.xor:
            raise GDP_Error("Cannot do convex hull transformation for "
                            "disjunction %s with or constraint. Must be an xor!"
                            % obj.name)

        parent_component = obj.parent_component()
        transBlock.disjContainers.add(parent_component)
        orConstraint, disaggregationConstraint \
            = self._getDisjunctionConstraints(parent_component)

        # We first go through and collect all the variables that we
        # are going to disaggregate.
        varOrder_set = ComponentSet()
        varOrder = []
        varsByDisjunct = ComponentMap()
        for disjunct in obj.disjuncts:
            # This is crazy, but if the disjunct has been previously
            # relaxed, the disjunct *could* be deactivated.
            not_active = not disjunct.active
            if not_active:
                disjunct._activate_without_unfixing_indicator()
            try:
                disjunctVars = varsByDisjunct[disjunct] = ComponentSet()
                for cons in disjunct.component_data_objects(
                        Constraint,
                        active = True,
                        sort=SortComponents.deterministic,
                        descend_into=Block):
                    # we aren't going to disaggregate fixed
                    # variables. This means there is trouble if they are
                    # unfixed later...
                    for var in EXPR.identify_variables(
                            cons.body, include_fixed=False):
                        # Note the use of a list so that we will
                        # eventually disaggregate the vars in a
                        # deterministic order (the order that we found
                        # them)
                        disjunctVars.add(var)
                        if var not in varOrder_set:
                            varOrder.append(var)
                            varOrder_set.add(var)
            finally:
                if not_active:
                    disjunct._deactivate_without_fixing_indicator()

        # We will only disaggregate variables that
        #  1) appear in multiple disjuncts, or
        #  2) are not contained in this disjunct, or
        #  3) are not themselves disaggregated variables
        varSet = []
        localVars = ComponentMap((d,[]) for d in obj.disjuncts)
        for var in varOrder:
            disjuncts = [d for d in varsByDisjunct if var in varsByDisjunct[d]]
            if len(disjuncts) > 1:
                varSet.append(var)
            elif self._contained_in(var, disjuncts[0]):
                localVars[disjuncts[0]].append(var)
            elif self._contained_in(var, transBlock):
                # There is nothing to do here: these are already
                # disaggregated vars that can/will be forced to 0 when
                # their disjunct is not active.
                pass
            else:
                varSet.append(var)

        # Now that we know who we need to disaggregate, we will do it
        # while we also transform the disjuncts.
        or_expr = 0
        for disjunct in obj.disjuncts:
            or_expr += disjunct.indicator_var
            self._transform_disjunct(disjunct, transBlock, varSet,
                                     localVars[disjunct])
        orConstraint.add(index, (or_expr, 1))

        for i, var in enumerate(varSet):
            disaggregatedExpr = 0
            for disjunct in obj.disjuncts:
                if 'chull' not in disjunct._gdp_transformation_info:
                    if not disjunct.indicator_var.is_fixed() \
                            or value(disjunct.indicator_var) != 0:
                        raise RuntimeError(
                            "GDP chull: disjunct was not relaxed, but "
                            "does not appear to be correctly deactivated.")
                    continue
                disaggregatedVar = disjunct._gdp_transformation_info['chull'][
                    'disaggregatedVars'][var]
                disaggregatedExpr += disaggregatedVar
            if type(index) is tuple:
                consIdx = index + (i,)
            elif parent_component.is_indexed():
                consIdx = (index,) + (i,)
            else:
                consIdx = i

            disaggregationConstraint.add(
                consIdx,
                var == disaggregatedExpr)
Exemplo n.º 46
0
    def _collect_ports(self, instance):
        self._name_buffer = {}
        # List of the ports in the order in which we found them
        # (this should be deterministic, provided that the user's model
        # is deterministic)
        port_list = []
        # ID of the next port group (set of matched ports)
        groupID = 0
        # port_groups stars out as a dict of {id(set): (groupID, set)}
        # If you sort by the groupID, then this will be deterministic.
        port_groups = dict()
        # map of port to the set of ports that must match it
        matched_ports = ComponentMap()

        for arc in instance.component_data_objects(**obj_iter_kwds):
            ports = ComponentSet(arc.ports)
            ref = None

            for p in arc.ports:
                if p in matched_ports:
                    if ref is None:
                        # The first port in this arc has
                        # already been seen. We will use that Set as
                        # the reference
                        ref = matched_ports[p]
                    elif ref is not matched_ports[p]:
                        # We already have a reference group; merge this
                        # new group into it.

                        # Optimization: this merge is linear in the size
                        # of the src set. If the reference set is
                        # smaller, save time by switching to a new
                        # reference set.
                        src = matched_ports[p]
                        if len(ref) < len(src):
                            ref, src = src, ref
                        ref.update(src)
                        for i in src:
                            matched_ports[i] = ref
                        del port_groups[id(src)]
                    # else: pass
                    #   The new group *is* the reference group;
                    #   there is nothing to do.
                else:
                    # The port has not been seen before.
                    port_list.append(p)
                    if ref is None:
                        # This is the first port in the arc:
                        # start a new reference set.
                        ref = ComponentSet()
                        port_groups[id(ref)] = (groupID, ref)
                        groupID += 1
                    # This port hasn't been seen. Record it.
                    ref.add(p)
                    matched_ports[p] = ref

        # Validate all port sets and expand the empty ones
        known_port_sets = {}
        for groupID, port_set in sorted(itervalues(port_groups)):
            known_port_sets[id(port_set)] \
                = self._validate_and_expand_port_set(port_set)

        return port_list, known_port_sets, matched_ports
Exemplo n.º 47
0
    def solve(self, model, **kwds):
        config = self.CONFIG(kwds.pop('options', {}))
        config.set_value(kwds)

        # Validate model to be used with gdpbb
        self.validate_model(model)
        # Set solver as an MINLP
        solve_data = GDPbbSolveData()
        solve_data.timing = Container()
        solve_data.original_model = model
        solve_data.results = SolverResults()

        old_logger_level = config.logger.getEffectiveLevel()
        with time_code(solve_data.timing, 'total', is_main_timer=True), \
                restore_logger_level(config.logger), \
                create_utility_block(model, 'GDPbb_utils', solve_data):
            if config.tee and old_logger_level > logging.INFO:
                # If the logger does not already include INFO, include it.
                config.logger.setLevel(logging.INFO)
            config.logger.info(
                "Starting GDPbb version %s using %s as subsolver"
                % (".".join(map(str, self.version())), config.solver)
            )

            # Setup results
            solve_data.results.solver.name = 'GDPbb - %s' % (str(config.solver))
            setup_results_object(solve_data, config)

            # clone original model for root node of branch and bound
            root = solve_data.working_model = solve_data.original_model.clone()

            # get objective sense
            process_objective(solve_data, config)
            objectives = solve_data.original_model.component_data_objects(Objective, active=True)
            obj = next(objectives, None)
            obj_sign = 1 if obj.sense == minimize else -1
            solve_data.results.problem.sense = obj.sense

            # set up lists to keep track of which disjunctions have been covered.

            # this list keeps track of the relaxed disjunctions
            root.GDPbb_utils.unenforced_disjunctions = list(
                disjunction for disjunction in root.GDPbb_utils.disjunction_list if disjunction.active
            )

            root.GDPbb_utils.deactivated_constraints = ComponentSet([
                constr for disjunction in root.GDPbb_utils.unenforced_disjunctions
                for disjunct in disjunction.disjuncts
                for constr in disjunct.component_data_objects(ctype=Constraint, active=True)
                if constr.body.polynomial_degree() not in (1, 0)
            ])
            # Deactivate nonlinear constraints in unenforced disjunctions
            for constr in root.GDPbb_utils.deactivated_constraints:
                constr.deactivate()

            # Add the BigM suffix if it does not already exist. Used later during nonlinear constraint activation.
            if not hasattr(root, 'BigM'):
                root.BigM = Suffix()

            # Pre-screen that none of the disjunctions are already predetermined due to the disjuncts being fixed
            # to True/False values.
            # TODO this should also be done within the loop, but we aren't handling it right now.
            # Should affect efficiency, but not correctness.
            root.GDPbb_utils.disjuncts_fixed_True = ComponentSet()
            # Only find top-level (non-nested) disjunctions
            for disjunction in root.component_data_objects(Disjunction, active=True):
                fixed_true_disjuncts = [disjunct for disjunct in disjunction.disjuncts
                                        if disjunct.indicator_var.fixed
                                        and disjunct.indicator_var.value == 1]
                fixed_false_disjuncts = [disjunct for disjunct in disjunction.disjuncts
                                         if disjunct.indicator_var.fixed
                                         and disjunct.indicator_var.value == 0]
                for disjunct in fixed_false_disjuncts:
                    disjunct.deactivate()
                if len(fixed_false_disjuncts) == len(disjunction.disjuncts) - 1:
                    # all but one disjunct in the disjunction is fixed to False. Remaining one must be true.
                    if not fixed_true_disjuncts:
                        fixed_true_disjuncts = [disjunct for disjunct in disjunction.disjuncts
                                                if disjunct not in fixed_false_disjuncts]
                # Reactivate the fixed-true disjuncts
                for disjunct in fixed_true_disjuncts:
                    newly_activated = ComponentSet()
                    for constr in disjunct.component_data_objects(Constraint):
                        if constr in root.GDPbb_utils.deactivated_constraints:
                            newly_activated.add(constr)
                            constr.activate()
                            # Set the big M value for the constraint
                            root.BigM[constr] = 1
                            # Note: we use a default big M value of 1
                            # because all non-selected disjuncts should be deactivated.
                            # Therefore, none of the big M transformed nonlinear constraints will need to be relaxed.
                            # The default M value should therefore be irrelevant.
                    root.GDPbb_utils.deactivated_constraints -= newly_activated
                    root.GDPbb_utils.disjuncts_fixed_True.add(disjunct)

                if fixed_true_disjuncts:
                    assert disjunction.xor, "GDPbb only handles disjunctions in which one term can be selected. " \
                        "%s violates this assumption." % (disjunction.name, )
                    root.GDPbb_utils.unenforced_disjunctions.remove(disjunction)

            # Check satisfiability
            if config.check_sat and satisfiable(root, config.logger) is False:
                # Problem is not satisfiable. Problem is infeasible.
                obj_value = obj_sign * float('inf')
            else:
                # solve the root node
                config.logger.info("Solving the root node.")
                obj_value, result, var_values = self.subproblem_solve(root, config)

            if obj_sign * obj_value == float('inf'):
                config.logger.info("Model was found to be infeasible at the root node. Elapsed %.2f seconds."
                                   % get_main_elapsed_time(solve_data.timing))
                if solve_data.results.problem.sense == minimize:
                    solve_data.results.problem.lower_bound = float('inf')
                    solve_data.results.problem.upper_bound = None
                else:
                    solve_data.results.problem.lower_bound = None
                    solve_data.results.problem.upper_bound = float('-inf')
                solve_data.results.solver.timing = solve_data.timing
                solve_data.results.solver.iterations = 0
                solve_data.results.solver.termination_condition = tc.infeasible
                return solve_data.results

            # initialize minheap for Branch and Bound algorithm
            # Heap structure: (ordering tuple, model)
            # Ordering tuple: (objective value, disjunctions_left, -total_nodes_counter)
            #  - select solutions with lower objective value,
            #    then fewer disjunctions left to explore (depth first),
            #    then more recently encountered (tiebreaker)
            heap = []
            total_nodes_counter = 0
            disjunctions_left = len(root.GDPbb_utils.unenforced_disjunctions)
            heapq.heappush(
                heap, (
                    (obj_sign * obj_value, disjunctions_left, -total_nodes_counter),
                    root, result, var_values))

            # loop to branch through the tree
            while len(heap) > 0:
                # pop best model off of heap
                sort_tuple, incumbent_model, incumbent_results, incumbent_var_values = heapq.heappop(heap)
                incumbent_obj_value, disjunctions_left, _ = sort_tuple

                config.logger.info("Exploring node with LB %.10g and %s inactive disjunctions." % (
                    incumbent_obj_value, disjunctions_left
                ))

                # if all the originally active disjunctions are active, solve and
                # return solution
                if disjunctions_left == 0:
                    config.logger.info("Model solved.")
                    # Model is solved. Copy over solution values.
                    original_model = solve_data.original_model
                    for orig_var, val in zip(original_model.GDPbb_utils.variable_list, incumbent_var_values):
                        orig_var.value = val

                    solve_data.results.problem.lower_bound = incumbent_results.problem.lower_bound
                    solve_data.results.problem.upper_bound = incumbent_results.problem.upper_bound
                    solve_data.results.solver.timing = solve_data.timing
                    solve_data.results.solver.iterations = total_nodes_counter
                    solve_data.results.solver.termination_condition = incumbent_results.solver.termination_condition
                    return solve_data.results

                # Pick the next disjunction to branch on
                next_disjunction = incumbent_model.GDPbb_utils.unenforced_disjunctions[0]
                config.logger.info("Branching on disjunction %s" % next_disjunction.name)
                assert next_disjunction.xor, "GDPbb only handles disjunctions in which one term can be selected. " \
                    "%s violates this assumption." % (next_disjunction.name, )

                new_nodes_counter = 0

                for i, disjunct in enumerate(next_disjunction.disjuncts):
                    # Create one branch for each of the disjuncts on the disjunction

                    if any(disj.indicator_var.fixed and disj.indicator_var.value == 1
                           for disj in next_disjunction.disjuncts if disj is not disjunct):
                        # If any other disjunct is fixed to 1 and an xor relationship applies,
                        # then this disjunct cannot be activated.
                        continue

                    # Check time limit
                    if get_main_elapsed_time(solve_data.timing) >= config.time_limit:
                        if solve_data.results.problem.sense == minimize:
                            solve_data.results.problem.lower_bound = incumbent_obj_value
                            solve_data.results.problem.upper_bound = float('inf')
                        else:
                            solve_data.results.problem.lower_bound = float('-inf')
                            solve_data.results.problem.upper_bound = incumbent_obj_value
                        config.logger.info(
                            'GDPopt unable to converge bounds '
                            'before time limit of {} seconds. '
                            'Elapsed: {} seconds'
                            .format(config.time_limit, get_main_elapsed_time(solve_data.timing)))
                        config.logger.info(
                            'Final bound values: LB: {}  UB: {}'.
                            format(solve_data.results.problem.lower_bound, solve_data.results.problem.upper_bound))
                        solve_data.results.solver.timing = solve_data.timing
                        solve_data.results.solver.iterations = total_nodes_counter
                        solve_data.results.solver.termination_condition = tc.maxTimeLimit
                        return solve_data.results

                    # Branch on the disjunct
                    child = incumbent_model.clone()
                    # TODO I am leaving the old branching system in place, but there should be
                    # something better, ideally that deals with nested disjunctions as well.
                    disjunction_to_branch = child.GDPbb_utils.unenforced_disjunctions.pop(0)
                    child_disjunct = disjunction_to_branch.disjuncts[i]
                    child_disjunct.indicator_var.fix(1)
                    # Deactivate (and fix to 0) other disjuncts on the disjunction
                    for disj in disjunction_to_branch.disjuncts:
                        if disj is not child_disjunct:
                            disj.deactivate()
                    # Activate nonlinear constraints on the newly fixed child disjunct
                    newly_activated = ComponentSet()
                    for constr in child_disjunct.component_data_objects(Constraint):
                        if constr in child.GDPbb_utils.deactivated_constraints:
                            newly_activated.add(constr)
                            constr.activate()
                            # Set the big M value for the constraint
                            child.BigM[constr] = 1
                            # Note: we use a default big M value of 1
                            # because all non-selected disjuncts should be deactivated.
                            # Therefore, none of the big M transformed nonlinear constraints will need to be relaxed.
                            # The default M value should therefore be irrelevant.
                    child.GDPbb_utils.deactivated_constraints -= newly_activated
                    child.GDPbb_utils.disjuncts_fixed_True.add(child_disjunct)

                    if disjunct in incumbent_model.GDPbb_utils.disjuncts_fixed_True:
                        # If the disjunct was already branched to True from a parent disjunct branching, just pass
                        # through the incumbent value without resolving. The solution should be the same as the parent.
                        total_nodes_counter += 1
                        ordering_tuple = (obj_sign * incumbent_obj_value, disjunctions_left - 1, -total_nodes_counter)
                        heapq.heappush(heap, (ordering_tuple, child, result, incumbent_var_values))
                        new_nodes_counter += 1
                        continue

                    if config.check_sat and satisfiable(child, config.logger) is False:
                        # Problem is not satisfiable. Skip this disjunct.
                        continue

                    obj_value, result, var_values = self.subproblem_solve(child, config)
                    total_nodes_counter += 1
                    ordering_tuple = (obj_sign * obj_value, disjunctions_left - 1, -total_nodes_counter)
                    heapq.heappush(heap, (ordering_tuple, child, result, var_values))
                    new_nodes_counter += 1

                config.logger.info("Added %s new nodes with %s relaxed disjunctions to the heap. Size now %s." % (
                    new_nodes_counter, disjunctions_left - 1, len(heap)))
Exemplo n.º 48
0
def fbbt_block(m, tol=1e-4, deactivate_satisfied_constraints=False, integer_tol=1e-5, infeasible_tol=1e-8):
    """
    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
    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.
    """
    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)

    for _v in m.component_data_objects(ctype=Var, active=True, descend_into=True, sort=True):
        if _v.is_fixed():
            _v.setlb(_v.value)
            _v.setub(_v.value)
            new_var_bounds[_v] = (_v.value, _v.value)

    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,
                                   integer_tol=integer_tol, infeasible_tol=infeasible_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,
                                       integer_tol=integer_tol, infeasible_tol=infeasible_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