Example #1
0
def calc_jacobians(solve_data, config):
    """
    Generates a map of jacobians for the variables in the model

    This function generates a map of jacobians corresponding to the variables in the model and adds this
    ComponentMap to solve_data

    Parameters
    ----------
    solve_data: MindtPy Data Container
        data container that holds solve-instance data
    config: MindtPy configurations
        contains the specific configurations for the algorithm
    """
    # Map nonlinear_constraint --> Map(
    #     variable --> jacobian of constraint wrt. variable)
    solve_data.jacobians = ComponentMap()
    if config.differentiate_mode == "reverse_symbolic":
        mode = differentiate.Modes.reverse_symbolic
    elif config.differentiate_mode == "sympy":
        mode = differentiate.Modes.sympy
    for c in solve_data.mip.MindtPy_utils.constraint_list:
        if c.body.polynomial_degree() in (1, 0):
            continue  # skip linear constraints
        vars_in_constr = list(EXPR.identify_variables(c.body))
        jac_list = differentiate(c.body, wrt_list=vars_in_constr, mode=mode)
        solve_data.jacobians[c] = ComponentMap(
            (var, jac_wrt_var)
            for var, jac_wrt_var in zip(vars_in_constr, jac_list))
Example #2
0
def calc_jacobians(solve_data, config):
    """Generates a map of jacobians for the variables in the model.

    This function generates a map of jacobians corresponding to the variables in the
    model and adds this ComponentMap to solve_data.

    Parameters
    ----------
    solve_data : MindtPySolveData
        Data container that holds solve-instance data.
    config : ConfigBlock
        The specific configurations for MindtPy.
    """
    # Map nonlinear_constraint --> Map(
    #     variable --> jacobian of constraint wrt. variable)
    solve_data.jacobians = ComponentMap()
    if config.differentiate_mode == 'reverse_symbolic':
        mode = differentiate.Modes.reverse_symbolic
    elif config.differentiate_mode == 'sympy':
        mode = differentiate.Modes.sympy
    for c in solve_data.mip.MindtPy_utils.nonlinear_constraint_list:
        vars_in_constr = list(EXPR.identify_variables(c.body))
        jac_list = differentiate(c.body, wrt_list=vars_in_constr, mode=mode)
        solve_data.jacobians[c] = ComponentMap(
            (var, jac_wrt_var)
            for var, jac_wrt_var in zip(vars_in_constr, jac_list))
Example #3
0
def calc_jacobians(solve_data, config):
    """Generate a map of jacobians."""
    # Map nonlinear_constraint --> Map(
    #     variable --> jacobian of constraint wrt. variable)
    solve_data.jacobians = ComponentMap()
    for c in solve_data.mip.MindtPy_utils.constraint_list:
        if c.body.polynomial_degree() in (1, 0):
            continue  # skip linear constraints
        vars_in_constr = list(EXPR.identify_variables(c.body))
        jac_list = differentiate(c.body,
                                 wrt_list=vars_in_constr,
                                 mode=differentiate.Modes.sympy)
        solve_data.jacobians[c] = ComponentMap(
            (var, jac_wrt_var)
            for var, jac_wrt_var in zip(vars_in_constr, jac_list))
Example #4
0
def add_outer_approximation_cuts(nlp_result, solve_data, config):
    """Add outer approximation cuts to the linear GDP model."""
    with time_code(solve_data.timing, 'OA cut generation'):
        m = solve_data.linear_GDP
        GDPopt = m.GDPopt_utils
        sign_adjust = -1 if solve_data.objective_sense == minimize else 1

        # copy values over
        for var, val in zip(GDPopt.variable_list, nlp_result.var_values):
            if val is not None and not var.fixed:
                var.value = val

        # TODO some kind of special handling if the dual is phenomenally small?
        config.logger.debug('Adding OA cuts.')

        counter = 0
        if not hasattr(GDPopt, 'jacobians'):
            GDPopt.jacobians = ComponentMap()
        for constr, dual_value in zip(GDPopt.constraint_list,
                                      nlp_result.dual_values):
            if dual_value is None or constr.body.polynomial_degree() in (1, 0):
                continue

            # Determine if the user pre-specified that OA cuts should not be
            # generated for the given constraint.
            parent_block = constr.parent_block()
            ignore_set = getattr(parent_block, 'GDPopt_ignore_OA', None)
            config.logger.debug('Ignore_set %s' % ignore_set)
            if (ignore_set and (constr in ignore_set or
                                constr.parent_component() in ignore_set)):
                config.logger.debug(
                    'OA cut addition for %s skipped because it is in '
                    'the ignore set.' % constr.name)
                continue

            config.logger.debug(
                "Adding OA cut for %s with dual value %s"
                % (constr.name, dual_value))

            # Cache jacobians
            jacobians = GDPopt.jacobians.get(constr, None)
            if jacobians is None:
                constr_vars = list(identify_variables(constr.body, include_fixed=False))
                if len(constr_vars) >= 1000:
                    mode = differentiate.Modes.reverse_numeric
                else:
                    mode = differentiate.Modes.sympy

                jac_list = differentiate(
                    constr.body, wrt_list=constr_vars, mode=mode)
                jacobians = ComponentMap(zip(constr_vars, jac_list))
                GDPopt.jacobians[constr] = jacobians

            # Create a block on which to put outer approximation cuts.
            oa_utils = parent_block.component('GDPopt_OA')
            if oa_utils is None:
                oa_utils = parent_block.GDPopt_OA = Block(
                    doc="Block holding outer approximation cuts "
                    "and associated data.")
                oa_utils.GDPopt_OA_cuts = ConstraintList()
                oa_utils.GDPopt_OA_slacks = VarList(
                    bounds=(0, config.max_slack),
                    domain=NonNegativeReals, initialize=0)

            oa_cuts = oa_utils.GDPopt_OA_cuts
            slack_var = oa_utils.GDPopt_OA_slacks.add()
            rhs = value(constr.lower) if constr.has_lb() else value(constr.upper)
            try:
                new_oa_cut = (
                    copysign(1, sign_adjust * dual_value) * (
                        value(constr.body) - rhs + sum(
                            value(jacobians[var]) * (var - value(var))
                            for var in jacobians)) - slack_var <= 0)
                if new_oa_cut.polynomial_degree() not in (1, 0):
                    for var in jacobians:
                        print(var.name, value(jacobians[var]))
                oa_cuts.add(expr=new_oa_cut)
                counter += 1
            except ZeroDivisionError:
                config.logger.warning(
                    "Zero division occured attempting to generate OA cut for constraint %s.\n"
                    "Skipping OA cut generation for this constraint."
                    % (constr.name,)
                )
                # Simply continue on to the next constraint.

        config.logger.info('Added %s OA cuts' % counter)
Example #5
0
def create_cuts_fme(transBlock_rHull, var_info, hull_to_bigm_map,
                    rBigM_linear_constraints, rHull_vars, disaggregated_vars,
                    norm, cut_threshold, zero_tolerance, integer_arithmetic):
    """Returns a cut which removes x* from the relaxed bigm feasible region.

    Finds all the constraints which are tight at xhat (assumed to be the 
    solution currently in instance_rHull), and calculates a composite normal
    vector by summing the vectors normal to each of these constraints. Then
    Fourier-Motzkin elimination is used to project the disaggregated variables
    out of the polyhedron formed by the composite normal and the collection 
    of tight constraints. This results in multiple cuts, of which we select
    one that cuts of x* by the greatest margin, as long as that margin is
    more than cut_threshold. If no cut satisfies the margin specified by 
    cut_threshold, we return None.

    Parameters
    -----------
    transBlock_rHull: transformation blcok on relaxed hull instance
    var_info: List of tuples (rBigM_var, rHull_var, xstar_param)
    hull_to_bigm_map: For expression substition, maps id(hull_var) to 
                      coresponding bigm var
    rBigM_linear_constraints: list of linear constraints in relaxed bigM
    rHull_vars: list of all variables in relaxed hull
    disaggregated_vars: ComponentSet of disaggregated variables in hull 
                        reformulation
    norm: norm used in the separation problem
    cut_threshold: Amount x* needs to be infeasible in generated cut in order
                   to consider the cut for addition to the bigM model.
    zero_tolerance: Tolerance at which a float will be treated as 0 during
                    Fourier-Motzkin elimination
    integer_arithmetic: boolean, whether or not to require Fourier-Motzkin
                        Elimination does integer arithmetic. Only possible 
                        when all data is integer.
    """
    instance_rHull = transBlock_rHull.model()
    # In the first iteration, we will compute a list of constraints that could
    # ever be interesting: Everything that involves at least one disaggregated
    # variable.
    if transBlock_rHull.component("constraints_for_FME") is None:
        _precompute_potentially_useful_constraints( transBlock_rHull,
                                                    disaggregated_vars)

    tight_constraints = Block()
    conslist = tight_constraints.constraints = Constraint(
        NonNegativeIntegers)
    conslist.construct()
    something_interesting = False
    for constraint in transBlock_rHull.constraints_for_FME:
        multipliers = _constraint_tight(instance_rHull, constraint)
        for multiplier in multipliers:
            if multiplier:
                something_interesting = True
                f = constraint.body
                firstDerivs = differentiate(f, wrt_list=rHull_vars)
                normal_vec = [multiplier*value(_) for _ in firstDerivs]
                # check if constraint is linear
                if f.polynomial_degree() == 1:
                    conslist[len(conslist)] = constraint.expr
                else: 
                    # we will use the linear approximation of this constraint at
                    # x_hat
                    conslist[len(conslist)] = _get_linear_approximation_expr(
                        normal_vec, rHull_vars)

    # NOTE: we now have all the tight Constraints (in the pyomo sense of the
    # word "Constraint"), but we are missing some variable bounds. The ones for
    # the disaggregated variables will be added by FME

    # It is possible that the separation problem returned a point in the
    # interior of the convex hull. It is also possible that the only active
    # constraints do not involve the disaggregated variables. In these
    # situations, there are not constraints from which to create a valid cut.
    if not something_interesting:
        return None

    tight_constraints.construct()
    logger.info("Calling FME transformation on %s constraints to eliminate"
                " %s variables" % (len(tight_constraints.constraints),
                                   len(disaggregated_vars)))
    TransformationFactory('contrib.fourier_motzkin_elimination').\
        apply_to(tight_constraints, vars_to_eliminate=disaggregated_vars,
                 zero_tolerance=zero_tolerance,
                 do_integer_arithmetic=integer_arithmetic,
                 projected_constraints_name="fme_constraints")
    fme_results = tight_constraints.fme_constraints
    projected_constraints = [cons for i, cons in iteritems(fme_results)]

    # we created these constraints with the variables from rHull. We
    # actually need constraints for BigM and rBigM now!
    cuts = _get_constraint_exprs(projected_constraints, hull_to_bigm_map)

    # We likely have some cuts that duplicate other constraints now. We will
    # filter them to make sure that they do in fact cut off x*. If that's the
    # case, we know they are not already in the BigM relaxation. Because they
    # came from FME, they are very likely redundant, so we'll keep the best one
    # we find
    best = 0
    best_cut = None
    cuts_to_keep = []
    for i, cut in enumerate(cuts):
        # x* is still in rBigM, so we can just remove this constraint if it
        # is satisfied at x*
        logger.info("FME: Post-processing cut %s" % cut)
        if value(cut):
            logger.info("FME:\t Doesn't cut off x*")
            continue
        # we have found a constraint which cuts of x* by some convincing amount
        # and is not already in rBigM. 
        cuts_to_keep.append(i)
        # We know cut is lb <= expr and that it's violated
        assert len(cut.args) == 2
        cut_off = value(cut.args[0]) - value(cut.args[1])
        if cut_off > cut_threshold and cut_off > best:
            best = cut_off
            best_cut = cut
            logger.info("FME:\t New best cut: Cuts off x* by %s." % best)

    # NOTE: this is not used right now, but it's not hard to imagine a world in
    # which we would want to keep multiple cuts from FME, so leaving it in for
    # now.
    cuts = [cuts[i] for i in cuts_to_keep]

    if best_cut is not None:
        return [best_cut]

    return None
Example #6
0
    def test_split_metis(self):
        m = self.m

        g = Graph()
        v1 = _VarNode(m.v1)
        v2 = _VarNode(m.v2)
        v3 = _VarNode(m.v3)
        v4 = _VarNode(m.v4)
        v5 = _VarNode(m.v5)
        v6 = _VarNode(m.v6)
        c1 = _ConNode(m.c1)
        c2 = _ConNode(m.c2)
        r1 = _RelNode(m.r1)
        r2 = _RelNode(m.r2)

        g.add_edge(v2, r2)
        g.add_edge(v3, r2)
        g.add_edge(v4, r2)
        g.add_edge(v1, c1)
        g.add_edge(v2, c1)
        g.add_edge(v3, c1)
        g.add_edge(v4, r1)
        g.add_edge(v5, r1)
        g.add_edge(v6, r1)
        g.add_edge(v4, c2)
        g.add_edge(v5, c2)
        g.add_edge(v6, c2)

        tree, partitioning_ratio = split_metis(graph=g)
        self.assertAlmostEqual(partitioning_ratio,
                               3 * 12 / (14 * 1 + 6 * 2 + 6 * 2))

        children = list(tree.children)
        self.assertEqual(len(children), 2)
        graph_a = children[0]
        graph_b = children[1]
        if v1 in graph_b.nodes():
            graph_a, graph_b = graph_b, graph_a

        graph_a_nodes = set(graph_a.nodes())
        graph_b_nodes = set(graph_b.nodes())
        self.assertIn(v1, graph_a_nodes)
        self.assertIn(v2, graph_a_nodes)
        self.assertIn(v3, graph_a_nodes)
        self.assertIn(v4, graph_b_nodes)
        self.assertIn(v5, graph_b_nodes)
        self.assertIn(v6, graph_b_nodes)
        self.assertIn(r2, graph_a_nodes)
        self.assertIn(c1, graph_a_nodes)
        self.assertIn(r1, graph_b_nodes)
        self.assertIn(c2, graph_b_nodes)
        self.assertEqual(len(graph_a_nodes), 6)
        self.assertEqual(len(graph_b_nodes), 5)
        v4_hat = list(graph_a_nodes - {v1, v2, v3, c1, r2})[0]

        graph_a_edges = set(graph_a.edges())
        graph_b_edges = set(graph_b.edges())
        self.assertTrue((v2, r2) in graph_a_edges or (r2, v2) in graph_a_edges)
        self.assertTrue((v3, r2) in graph_a_edges or (r2, v3) in graph_a_edges)
        self.assertTrue((v4_hat, r2) in graph_a_edges
                        or (r2, v4_hat) in graph_a_edges)
        self.assertTrue((v1, c1) in graph_a_edges or (c1, v1) in graph_a_edges)
        self.assertTrue((v2, c1) in graph_a_edges or (c1, v2) in graph_a_edges)
        self.assertTrue((v3, c1) in graph_a_edges or (c1, v3) in graph_a_edges)
        self.assertTrue((v4, r1) in graph_b_edges or (r1, v4) in graph_b_edges)
        self.assertTrue((v5, r1) in graph_b_edges or (r1, v5) in graph_b_edges)
        self.assertTrue((v6, r1) in graph_b_edges or (r1, v6) in graph_b_edges)
        self.assertTrue((v4, c2) in graph_b_edges or (c2, v4) in graph_b_edges)
        self.assertTrue((v5, c2) in graph_b_edges or (c2, v5) in graph_b_edges)
        self.assertTrue((v6, c2) in graph_b_edges or (c2, v6) in graph_b_edges)
        self.assertEqual(len(graph_a_edges), 6)
        self.assertEqual(len(graph_b_edges), 6)

        edges_between_children = list(tree.edges_between_children)
        self.assertEqual(len(edges_between_children), 1)
        edge = edges_between_children[0]
        self.assertTrue((v4 is edge.node1 and v4_hat is edge.node2)
                        or (v4 is edge.node2 and v4_hat is edge.node1))

        new_model = TreeBlock(concrete=True)
        component_map = tree.build_pyomo_model(block=new_model)
        new_vars = list(
            coramin.relaxations.nonrelaxation_component_data_objects(
                new_model, ctype=pe.Var, descend_into=True, sort=True))
        new_cons = list(
            coramin.relaxations.nonrelaxation_component_data_objects(
                new_model,
                ctype=pe.Constraint,
                active=True,
                descend_into=True,
                sort=True))
        new_rels = list(
            coramin.relaxations.relaxation_data_objects(new_model,
                                                        descend_into=True,
                                                        active=True,
                                                        sort=True))
        self.assertEqual(len(new_vars), 7)
        self.assertEqual(len(new_cons), 3)
        self.assertEqual(len(new_rels), 2)
        self.assertEqual(len(new_model.children), 2)
        self.assertEqual(len(new_model.linking_constraints), 1)
        self.assertEqual(new_model.num_stages(), 2)

        stage0_vars = list(
            new_model.component_data_objects(pe.Var,
                                             descend_into=False,
                                             sort=True))
        stage0_cons = list(
            new_model.component_data_objects(pe.Constraint,
                                             descend_into=False,
                                             sort=True,
                                             active=True))
        stage0_rels = list(
            coramin.relaxations.relaxation_data_objects(new_model,
                                                        descend_into=False,
                                                        active=True,
                                                        sort=True))
        self.assertEqual(len(stage0_vars), 0)
        self.assertEqual(len(stage0_cons), 1)
        self.assertEqual(len(stage0_rels), 0)

        block_a = new_model.children[0]
        block_b = new_model.children[1]
        block_a_vars = ComponentSet(
            coramin.relaxations.nonrelaxation_component_data_objects(
                block_a, ctype=pe.Var, descend_into=True, sort=True))
        block_b_vars = ComponentSet(
            coramin.relaxations.nonrelaxation_component_data_objects(
                block_b, ctype=pe.Var, descend_into=True, sort=True))
        block_a_cons = ComponentSet(
            coramin.relaxations.nonrelaxation_component_data_objects(
                block_a,
                ctype=pe.Constraint,
                descend_into=True,
                active=True,
                sort=True))
        block_b_cons = ComponentSet(
            coramin.relaxations.nonrelaxation_component_data_objects(
                block_b,
                ctype=pe.Constraint,
                descend_into=True,
                active=True,
                sort=True))
        block_a_rels = ComponentSet(
            coramin.relaxations.relaxation_data_objects(block_a,
                                                        descend_into=True,
                                                        active=True,
                                                        sort=True))
        block_b_rels = ComponentSet(
            coramin.relaxations.relaxation_data_objects(block_b,
                                                        descend_into=True,
                                                        active=True,
                                                        sort=True))
        if component_map[m.v1] not in block_a_vars:
            block_a, block_b = block_b, block_a
            block_a_vars, block_b_vars = block_b_vars, block_a_vars
            block_a_cons, block_b_cons = block_b_cons, block_a_cons
            block_a_rels, block_b_rels = block_b_rels, block_a_rels

        self.assertEqual(len(block_a_vars), 4)
        self.assertEqual(len(block_a_cons), 1)
        self.assertEqual(len(block_a_rels), 1)
        self.assertEqual(len(block_b_vars), 3)
        self.assertEqual(len(block_b_cons), 1)
        self.assertEqual(len(block_b_rels), 1)

        v1 = component_map[m.v1]
        v2 = component_map[m.v2]
        v3 = component_map[m.v3]
        v4_a = block_a.vars['v4']
        v4_b = block_b.vars['v4']
        v5 = component_map[m.v5]
        v6 = component_map[m.v6]

        self.assertIs(v1, block_a.vars['v1'])
        self.assertIs(v2, block_a.vars['v2'])
        self.assertIs(v3, block_a.vars['v3'])
        self.assertIs(v5, block_b.vars['v5'])
        self.assertIs(v6, block_b.vars['v6'])

        self.assertEqual(v2.lb, -1)
        self.assertEqual(v2.ub, 1)
        self.assertEqual(v3.lb, -1)
        self.assertEqual(v3.ub, 1)
        self.assertEqual(v4_a.lb, -1)
        self.assertEqual(v4_a.ub, 1)
        self.assertEqual(v4_b.lb, -1)
        self.assertEqual(v4_b.ub, 1)
        self.assertEqual(v5.lb, -1)
        self.assertEqual(v5.ub, 1)
        self.assertEqual(v1.lb, None)
        self.assertEqual(v1.ub, None)
        self.assertEqual(v6.lb, None)
        self.assertEqual(v6.ub, None)

        linking_con = new_model.linking_constraints[1]
        linking_con_vars = ComponentSet(identify_variables(linking_con.body))
        self.assertEqual(len(linking_con_vars), 2)
        self.assertIn(v4_a, linking_con_vars)
        self.assertIn(v4_b, linking_con_vars)
        derivs = differentiate(expr=linking_con.body,
                               mode=differentiate.Modes.reverse_symbolic)
        self.assertTrue((derivs[v4_a] == 1 and derivs[v4_b] == -1)
                        or (derivs[v4_a] == -1 and derivs[v4_b] == 1))
        self.assertEqual(linking_con.lower, 0)
        self.assertEqual(linking_con.upper, 0)

        c1 = block_a.cons['c1']
        c2 = block_b.cons['c2']
        r1 = block_b.rels.r1
        r2 = block_a.rels.r2
        c1_vars = ComponentSet(identify_variables(c1.body))
        c2_vars = ComponentSet(identify_variables(c2.body))
        self.assertEqual(len(c1_vars), 3)
        self.assertEqual(len(c2_vars), 3)
        self.assertIn(v1, c1_vars)
        self.assertIn(v2, c1_vars)
        self.assertIn(v3, c1_vars)
        self.assertIn(v4_b, c2_vars)
        self.assertIn(v5, c2_vars)
        self.assertIn(v6, c2_vars)
        self.assertIs(r1.get_aux_var(), v6)
        self.assertIs(r2.get_aux_var(), v2)
        r1_rhs_vars = ComponentSet(r1.get_rhs_vars())
        r2_rhs_vars = ComponentSet(r2.get_rhs_vars())
        self.assertIn(v3, r2_rhs_vars)
        self.assertIn(v4_a, r2_rhs_vars)
        self.assertIn(v4_b, r1_rhs_vars)
        self.assertIn(v5, r1_rhs_vars)
        self.assertTrue(
            isinstance(r1, coramin.relaxations.PWMcCormickRelaxationData))
        self.assertTrue(
            isinstance(r2, coramin.relaxations.PWMcCormickRelaxationData))
        c1_derivs = differentiate(c1.body,
                                  mode=differentiate.Modes.reverse_symbolic)
        c2_derivs = differentiate(c2.body,
                                  mode=differentiate.Modes.reverse_symbolic)
        self.assertEqual(c1_derivs[v1], 1)
        self.assertEqual(c1_derivs[v2], -1)
        self.assertEqual(c1_derivs[v3], -1)
        self.assertEqual(c2_derivs[v4_b], -1)
        self.assertEqual(c2_derivs[v5], -1)
        self.assertEqual(c2_derivs[v6], 1)
        self.assertEqual(c1.lower, 0)
        self.assertEqual(c1.upper, 0)
        self.assertEqual(c2.lower, 0)
        self.assertEqual(c2.upper, 0)