Exemplo n.º 1
0
    def _optimize_cut(self: G,
                      pi: np.ndarray,
                      cut_optimization_node_limit: int = 10,
                      **kwargs: Any) -> float:
        """ Given the valid inequality pi >= pi0, try to find a smaller RHS such
        that pi >= smaller RHS is still a valid inequality

        :param pi: coefficients of the vector to optimize
        :param cut_optimization_node_limit: maximimum number of nodes to evaluate
        before terminating
        :param kwargs: catch all for unused passed kwargs
        :return: the objective value of the best milp feasible solution found
        """
        A = self.lp.coefMatrix.toarray()
        assert A.shape[1] == pi.shape[
            0], 'number of columns of A and length of c should match'

        # make new model where we minimize the cut
        model = MILPInstance(A=A,
                             b=self.lp.constraintsLower.copy(),
                             c=pi,
                             sense=['Min', '>='],
                             integerIndices=self._integer_indices,
                             numVars=pi.shape[0])

        # find a tighter bound with branch and bound
        bb = BranchAndBound(model=model,
                            Node=BaseNode,
                            node_limit=cut_optimization_node_limit,
                            pseudo_costs={})
        bb.solve()
        return bb.objective_value if bb.status == 'optimal' else bb.global_lower_bound
Exemplo n.º 2
0
 def test_solve_past_node_limit(self):
     bb = BranchAndBound(unbounded, node_limit=10)
     # check we quit even if node_queue nonempty
     with patch.object(bb, '_evaluate_node') as en:
         bb.evaluated_nodes = 10
         bb.solve()
         self.assertFalse(en.called, 'were past the node limit')
 def test_solve_infeasible(self):
     # check and make sure we're good with both nodes
     for Node in [BaseNode, PCBDFSNode]:
         bb = BranchAndBound(infeasible, Node=Node)
         bb.solve()
         self.assertTrue(bb.status == 'infeasible')
         self.assertFalse(bb.solution)
         self.assertTrue(bb.objective_value == float('inf'))
 def test_solve_optimal(self):
     # check and make sure we're good with both nodes
     for Node in [BaseNode, PCBDFSNode]:
         bb = BranchAndBound(small_branch, Node=Node)
         bb.solve()
         self.assertTrue(bb.status == 'optimal')
         self.assertTrue(all(s.is_integer for s in bb.solution))
         self.assertTrue(bb.objective_value == -2)
Exemplo n.º 5
0
    def test_evaluate_node_unbounded(self):
        bb = BranchAndBound(unbounded)
        bb._evaluate_node(bb.root_node)

        # check attributes
        self.assertTrue(bb._unbounded)
        self.assertTrue(bb.evaluated_nodes == 1,
                        'only one node should be evaluated')
Exemplo n.º 6
0
 def test_get_leaves(self):
     bb = BranchAndBound(small_branch)
     bb.solve()
     leaves = bb.tree.get_leaves(0)
     for node_id, node in bb.tree.nodes.items():
         if node_id in [n.idx for n in leaves]:
             self.assertFalse(bb.tree.get_children(node_id))
         else:
             self.assertTrue(len(bb.tree.get_children(node_id)) == 2)
Exemplo n.º 7
0
 def test_solve_stopped_on_iterations(self):
     # check and make sure we're good with both nodes
     for Node in [BaseNode, PCBDFSNode]:
         bb = BranchAndBound(small_branch,
                             Node=Node,
                             node_limit=1,
                             pseudo_costs={})
         bb.solve()
         self.assertTrue(bb.status == 'stopped on iterations')
         self.assertTrue(bb.solve_time)
Exemplo n.º 8
0
 def test_current_gap(self):
     bb = BranchAndBound(small_branch, node_limit=1)
     bb.solve()
     self.assertTrue(bb.current_gap is None)
     bb.node_limit = 10
     bb.solve()
     self.assertTrue(bb.current_gap == .125)
     bb.node_limit = float('inf')
     bb.solve()
     self.assertTrue(bb.current_gap == 0)
     print()
Exemplo n.º 9
0
 def test_get_node_instances_fails_asserts(self):
     bb = BranchAndBound(small_branch)
     bb.solve()
     self.assertRaisesRegex(AssertionError,
                            'must be an integer or iterable',
                            bb.tree.get_node_instances, '1')
     self.assertRaisesRegex(AssertionError, 'node_ids are not in the tree',
                            bb.tree.get_node_instances, [20])
     del bb.tree.nodes[0].attr['node']
     self.assertRaisesRegex(AssertionError,
                            'must have an attribute for a node instance',
                            bb.tree.get_node_instances, [0])
Exemplo n.º 10
0
    def test_process_branch_rtn(self):
        bb = BranchAndBound(small_branch)
        node = BaseNode(small_branch.lp, small_branch.integerIndices, idx=0)
        node.bound()
        rtn = node.branch(next_node_idx=1)
        left_node = rtn['left']
        right_node = rtn['right']
        bb._process_branch_rtn(node.idx, rtn)

        # check attributes
        self.assertTrue(isinstance(bb._node_queue.get(), BaseNode))
        self.assertTrue(isinstance(bb._node_queue.get(), BaseNode))
        self.assertTrue(bb._node_queue.empty())
        children = bb.tree.get_children(node.idx)
        self.assertTrue(len(children) == 2, 'there should be two kids created')
        for child in children:
            self.assertFalse(bb.tree.get_children(child),
                             'children shouldnt have kids')

        self.assertTrue(bb.tree.get_node(1).attr['node'] is left_node)
        self.assertTrue(bb.tree.get_node(2).attr['node'] is right_node)

        # check function calls
        bb = BranchAndBound(small_branch)
        node = BaseNode(small_branch.lp, small_branch.integerIndices, 0)
        node.bound()
        rtn = node.branch()
        with patch.object(bb, '_process_rtn') as pr, \
                patch.object(bb.tree, 'add_left_child') as alc, \
                patch.object(bb.tree, 'add_right_child') as arc:
            bb._process_branch_rtn(0, rtn)
            self.assertTrue(pr.call_count == 1, 'should call process rtn')
            self.assertTrue(alc.call_count == 1, 'should call add left child')
            self.assertTrue(arc.call_count == 1, 'should call add right child')
 def test_evaluate_node_properly_prunes(self):
     bb = BranchAndBound(no_branch)
     bb._global_upper_bound = -2
     called_node = BaseNode(bb.model.lp, bb.model.integerIndices, -4)
     pruned_node = BaseNode(bb.model.lp, bb.model.integerIndices, 0)
     with patch.object(called_node, 'bound') as cnb, \
             patch.object(pruned_node, 'bound') as pnb:
         cnb.return_value = {}
         pnb.return_value = {}
         bb._node_queue.put(called_node)
         bb._node_queue.put(pruned_node)
         bb._evaluate_node(bb._node_queue.get())
         bb._evaluate_node(bb._node_queue.get())
         self.assertTrue(cnb.call_count == 1, 'first node should run')
         self.assertFalse(pnb.call_count, 'second node should get pruned')
         self.assertTrue(bb._node_queue.empty())
    def test_solve_unbounded(self):
        # check and make sure we're good with both nodes
        for Node in [BaseNode, PCBDFSNode]:
            bb = BranchAndBound(unbounded, Node=Node)
            bb.solve()
            self.assertTrue(bb.status == 'unbounded')

        # check we quit even if node_queue nonempty
        with patch.object(bb, '_evaluate_node') as en:
            bb = BranchAndBound(unbounded)
            bb._unbounded = True
            bb.solve()
            self.assertFalse(en.called)
 def test_process_branch_rtn_fails_asserts(self):
     bb = BranchAndBound(small_branch)
     self.assertRaisesRegex(AssertionError, 'rtn must be a dictionary',
                            bb._process_rtn, 'fish')
     rtn = {'up': 5, 'down': 5}
     self.assertRaisesRegex(AssertionError, 'value must be type',
                            bb._process_branch_rtn, rtn)
     del rtn['up']
     self.assertRaisesRegex(AssertionError, 'must be in the returned',
                            bb._process_branch_rtn, rtn)
Exemplo n.º 14
0
 def base_test_models(self, standardize_model=False):
     self.assertTrue(gu, 'gurobipy needed for this test')
     fldr = os.path.join(
         os.path.dirname(os.path.abspath(inspect.getfile(generate_random_variety))),
         'example_models'
     )
     for i, file in enumerate(os.listdir(fldr)):
         print(f'running test {i + 1}')
         pth = os.path.join(fldr, file)
         model = MILPInstance(file_name=pth)
         bb = BranchAndBound(model, self.Node, pseudo_costs={})
         bb.solve()
         gu_mdl = gu.read(pth)
         gu_mdl.setParam(gu.GRB.Param.LogToConsole, 0)
         gu_mdl.optimize()
         if not isclose(bb.objective_value, gu_mdl.objVal, abs_tol=.01):
             print(f'different for {file}')
             print(f'mine: {bb.objective_value}')
             print(f'gurobi: {gu_mdl.objVal}')
         self.assertTrue(isclose(bb.objective_value, gu_mdl.objVal, abs_tol=.01),
                         f'different for {file}')
Exemplo n.º 15
0
    def test_evaluate_node_fractional(self):
        bb = BranchAndBound(small_branch,
                            Node=PCBDFSNode,
                            pseudo_costs={},
                            strong_branch_iters=5)
        bb._evaluate_node(bb.root_node)

        # check attributes
        self.assertFalse(bb._best_solution, 'best solution should not change')
        self.assertTrue(bb.global_upper_bound == float('inf'),
                        'shouldnt change')
        self.assertTrue(bb.global_lower_bound > -float('inf'), 'should change')
        self.assertTrue(bb._node_queue.qsize() == 2,
                        'should branch and add two nodes')
        self.assertTrue(bb._kwargs['pseudo_costs'], 'something should be set')
        self.assertTrue(bb._kwargs['strong_branch_iters'],
                        'something should be set')
        self.assertTrue(bb.evaluated_nodes == 1,
                        'only one node should be evaluated')

        # check function calls - recycle object since it has attrs already set
        with patch.object(bb, '_process_rtn') as pr, \
                patch.object(bb, '_process_branch_rtn') as pbr, \
                patch.object(bb.root_node, 'bound') as bd, \
                patch.object(bb.root_node, 'branch') as bh:
            bb._evaluate_node(bb.root_node)
            self.assertTrue(pr.call_count == 1)  # direct calls
            self.assertTrue(pbr.call_count == 1)
            self.assertTrue(0 == pbr.call_args.args[0],
                            'root node id should be first call arg')
            self.assertTrue(bd.call_count == 1)
            self.assertTrue(bh.call_count == 1)
Exemplo n.º 16
0
    def test_evaluate_node_infeasible(self):
        bb = BranchAndBound(infeasible)
        bb._evaluate_node(bb.root_node)

        # check attributes
        self.assertTrue(bb._node_queue.empty(),
                        'inf model should create no nodes')
        self.assertFalse(bb._best_solution, 'best solution should not change')
        self.assertTrue(bb.global_upper_bound == float('inf'),
                        'shouldnt change')
        self.assertTrue(bb.global_lower_bound == float('inf'),
                        'shouldnt change')
        self.assertTrue(bb.evaluated_nodes == 1,
                        'only one node should be evaluated')

        # check function calls - recycle object since it has attrs already set
        with patch.object(bb, '_process_rtn') as pr, \
                patch.object(bb, '_process_branch_rtn') as pbr, \
                patch.object(bb.root_node, 'bound') as bd, \
                patch.object(bb.root_node, 'branch') as bh:
            bb._evaluate_node(bb.root_node)
            self.assertTrue(pr.call_count == 1)
            self.assertTrue(pbr.call_count == 0)
            self.assertTrue(bd.call_count == 1)
            self.assertTrue(bh.call_count == 0)
 def test_init(self):
     bb = BranchAndBound(small_branch)
     self.assertTrue(bb._global_upper_bound == float('inf'))
     self.assertTrue(bb._node_queue.empty())
     self.assertTrue(inspect.isclass(bb._Node))
     self.assertTrue(bb._root_node)
     self.assertTrue(bb.model)
     self.assertFalse(bb._unbounded)
     self.assertFalse(bb._best_solution)
     self.assertFalse(bb.solution)
     self.assertTrue(bb.status == 'unsolved')
     self.assertFalse(bb.objective_value)
     self.assertFalse(bb._pseudo_costs, 'should exist but be empty')
     self.assertTrue(bb._strong_branch_iters == 5)
    def test_init_fails_asserts(self):
        lp = CyClpSimplex()
        bb = BranchAndBound(small_branch)
        queue = PriorityQueue()

        # model asserts
        self.assertRaisesRegex(AssertionError,
                               'model must be cuppy MILPInstance',
                               BranchAndBound, lp)

        # Node asserts
        self.assertRaisesRegex(AssertionError, 'Node must be a class',
                               BranchAndBound, small_branch, 'Node')
        for attribute in bb._node_attributes:

            class BadNode(BaseNode):
                def __init__(self, **kwargs):
                    super().__init__(**kwargs)
                    delattr(self, attribute)

            self.assertRaisesRegex(AssertionError, f'Node needs a {attribute}',
                                   BranchAndBound, small_branch, BadNode)

        for func in bb._node_funcs:

            class BadNode(BaseNode):
                def __init__(self, **kwargs):
                    super().__init__(**kwargs)
                    self.__dict__[func] = 5

            self.assertRaisesRegex(AssertionError, f'Node needs a {func}',
                                   BranchAndBound, small_branch, BadNode)

        # node_queue asserts
        for func in reversed(bb._queue_funcs):
            queue.__dict__[func] = 5
            self.assertRaisesRegex(AssertionError,
                                   f'node_queue needs a {func} function',
                                   BranchAndBound, small_branch, BaseNode,
                                   queue)

        # strong branch iters asserts
        self.assertRaisesRegex(
            AssertionError,
            'strong branching iterations must be positive integer',
            BranchAndBound,
            small_branch,
            strong_branch_iters=-1)
    def test_process_branch_rtn(self):
        bb = BranchAndBound(small_branch)
        node = BaseNode(small_branch.lp, small_branch.integerIndices)
        node.bound()
        rtn = node.branch()
        bb._process_branch_rtn(rtn)

        # check attributes
        self.assertTrue(isinstance(bb._node_queue.get(), BaseNode))
        self.assertTrue(isinstance(bb._node_queue.get(), BaseNode))
        self.assertTrue(bb._node_queue.empty())

        # check function calls
        bb = BranchAndBound(small_branch)
        node = BaseNode(small_branch.lp, small_branch.integerIndices)
        node.bound()
        rtn = node.branch()
        with patch.object(bb, '_process_rtn') as pr:
            bb._process_branch_rtn(rtn)
            self.assertTrue(pr.call_count == 1, 'should call rtn')
Exemplo n.º 20
0
    def test_dual_bound_fails_asserts(self):
        bb = BranchAndBound(small_branch)
        self.assertRaisesRegex(AssertionError,
                               'must solve this instance before',
                               bb.dual_bound, CyLPArray([2.5, 4.5]))
        bb.solve()
        self.assertRaisesRegex(AssertionError, 'only works with CyLP arrays',
                               bb.dual_bound, np.array([2.5, 4.5]))
        self.assertRaisesRegex(AssertionError,
                               'shape of the RHS being added should match',
                               bb.dual_bound, CyLPArray([4.5]))

        bb = BranchAndBound(infeasible2)
        bb.root_node.lp += np.matrix(
            [[0, -1, -1]]) * bb.root_node.lp.getVarByName('x') >= CyLPArray(
                [-2.5])
        bb.solve()
        self.assertRaisesRegex(
            AssertionError,
            'feature expects the root node to have a single constraint object',
            bb.dual_bound, CyLPArray([2.5, 4.5]))
    def test_evaluate_node_integer(self):
        bb = BranchAndBound(no_branch)
        bb._evaluate_node(bb._root_node)

        # check attributes
        self.assertTrue(all(bb._best_solution == [1, 1, 0]))
        self.assertTrue(bb._global_upper_bound == -2)
        self.assertTrue(bb._node_queue.empty(),
                        'immediately optimal model should create no nodes')

        # check function calls - recycle object since it has attrs already set
        with patch.object(bb, '_process_rtn') as pr, \
                patch.object(bb, '_process_branch_rtn') as pbr, \
                patch.object(bb._root_node, 'bound') as bd, \
                patch.object(bb._root_node, 'branch') as bh:
            bb._evaluate_node(bb._root_node)
            self.assertTrue(pr.call_count == 1)
            self.assertTrue(pbr.call_count == 0)
            self.assertTrue(bd.call_count == 1)
            self.assertTrue(bh.call_count == 0)