Пример #1
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_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)
Пример #3
0
    def test_find_strong_disjunctive_cut(self):
        bb = BranchAndBound(square)
        bb.solve()
        pi, pi0 = bb.find_strong_disjunctive_cut(0)

        # check cut is what we expect, i.e. x1 <= 1
        assert_allclose(pi / pi0, np.array([1, 0]), atol=.01)
        self.assertTrue((pi - .01 < 0).all())
        self.assertTrue(pi0 - .01 < 0)

        # check we get same bound
        A = np.append(bb.root_node.lp.coefMatrix.toarray(), [pi], axis=0)
        b = np.append(bb.root_node.lp.constraintsLower, pi0, axis=0)
        warm_model = MILPInstance(A=A,
                                  b=b,
                                  c=bb.root_node.lp.objective,
                                  l=bb.root_node.lp.variablesLower.copy(),
                                  u=bb.root_node.lp.variablesUpper.copy(),
                                  sense=['Min', '>='],
                                  integerIndices=bb.root_node._integer_indices,
                                  numVars=bb.root_node.lp.nVariables)
        warm_bb = BranchAndBound(warm_model)
        warm_bb.solve()
        # self.assertTrue(bb.global_lower_bound == warm_bb.root_node.objective_value)

        # try another problem
        bb = BranchAndBound(small_branch, node_limit=10)
        bb.solve()
        pi, pi0 = bb.find_strong_disjunctive_cut(0)

        # check cut is what we expect, i.e. x3 <= 1
        assert_allclose(pi / pi0, np.array([0, 0, 1]), atol=.01)
        self.assertTrue((pi - .01 < 0).all())
        self.assertTrue(pi0 - .01 < 0)

        # check we get same bound
        A = np.append(bb.root_node.lp.coefMatrix.toarray(), [pi], axis=0)
        b = np.append(bb.root_node.lp.constraintsLower, pi0, axis=0)
        warm_model = MILPInstance(A=A,
                                  b=b,
                                  c=bb.root_node.lp.objective,
                                  l=bb.root_node.lp.variablesLower.copy(),
                                  u=bb.root_node.lp.variablesUpper.copy(),
                                  sense=['Min', '>='],
                                  integerIndices=bb.root_node._integer_indices,
                                  numVars=bb.root_node.lp.nVariables)
        warm_bb = BranchAndBound(warm_model)
        warm_bb.solve()
Пример #4
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)
Пример #5
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')
Пример #6
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
Пример #7
0
    def test_init_fails_asserts(self):
        bb = BranchAndBound(small_branch)
        queue = PriorityQueue()

        # 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)

        # node limit asserts
        self.assertRaisesRegex(AssertionError,
                               f'node limit',
                               BranchAndBound,
                               model=small_branch,
                               node_limit=-5)

        # mip gap asserts
        self.assertRaisesRegex(AssertionError,
                               f'mip_gap',
                               BranchAndBound,
                               model=small_branch,
                               mip_gap=-5)

        # kwargs asserts
        self.assertRaisesRegex(AssertionError,
                               f'keys "right"',
                               BranchAndBound,
                               model=small_branch,
                               right=-5)
Пример #8
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)
Пример #9
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')
Пример #10
0
 def setUp(self) -> None:
     self.bb = BranchAndBound(small_branch)
     self.unbounded_root = BaseNode(small_branch.lp,
                                    small_branch.integerIndices)
     self.bound_root = BaseNode(small_branch.lp,
                                small_branch.integerIndices)
     self.bound_root.bound()
     self.root_branch_rtn = self.bound_root.branch()
 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)
Пример #13
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)
Пример #14
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)
 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)
    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')
Пример #17
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()
Пример #18
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])
Пример #19
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_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)
Пример #21
0
 def test_init(self):
     bb = BranchAndBound(small_branch)
     self.assertTrue(isinstance(bb, Utils))
     self.assertTrue(bb.global_upper_bound == float('inf'))
     self.assertTrue(bb._node_queue.empty())
     self.assertFalse(bb._unbounded)
     self.assertFalse(bb._best_solution)
     self.assertFalse(bb.solution)
     self.assertTrue(bb.status == 'unsolved')
     self.assertFalse(bb.objective_value)
     self.assertTrue(isinstance(bb.tree, BranchAndBoundTree))
     self.assertTrue(list(bb.tree.nodes.keys()) == [0])
     self.assertTrue(bb.tree.nodes[0].attr['node'] is bb.root_node)
     self.assertFalse(bb.solve_time, 'solve time should exist and be 0')
     self.assertTrue(bb.mip_gap, 'mip gap should be an attribute')
Пример #22
0
    def test_get_node_instances(self):
        bb = BranchAndBound(small_branch)
        bb.solve()

        # test list
        node1, node2 = bb.tree.get_node_instances([1, 2])
        self.assertTrue(node1.idx == 1, 'we should get node with matching id')
        self.assertTrue(isinstance(node1, BaseNode), 'we should get a node')
        self.assertTrue(node2.idx == 2, 'we should get node with matching id')
        self.assertTrue(isinstance(node2, BaseNode), 'we should get a node')

        # test singleton
        node1 = bb.tree.get_node_instances(1)
        self.assertTrue(node1.idx == 1, 'we should get node with matching id')
        self.assertTrue(isinstance(node1, BaseNode), 'we should get a node')
Пример #23
0
 def test_bound_dual_fails_asserts(self):
     bb = BranchAndBound(infeasible)
     bb.solve()
     terminal_nodes = bb.tree.get_leaves(0)
     infeasible_nodes = [
         n for n in terminal_nodes if n.lp_feasible is False
     ]
     n = infeasible_nodes[0]
     n.lp.addVariable('s_0', 1)
     self.assertRaisesRegex(AssertionError,
                            "variable 's_0' is a reserved name",
                            bb._bound_dual, n.lp)
     self.assertRaisesRegex(AssertionError,
                            "must give CyClpSimplex instance",
                            bb._bound_dual, n)
    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_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())
Пример #26
0
 def base_test_models(self):
     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)
         bb.solve()
         gu_mdl = gu.read(pth)
         gu_mdl.optimize()
         self.assertTrue(
             isclose(bb.objective_value, gu_mdl.objVal, abs_tol=.01),
             f'different for {file}')
Пример #27
0
    def test_find_strong_disjunctive_cut_fails_asserts(self):
        bb = BranchAndBound(small_branch)
        bb.solve()

        self.assertRaisesRegex(AssertionError,
                               'parent must already exist in tree',
                               bb.find_strong_disjunctive_cut, 50)

        terminal_nodes = bb.tree.get_leaves(0)
        disjunctive_nodes = [
            n for n in terminal_nodes if n.lp_feasible is not False
        ]
        n = disjunctive_nodes[0]
        n.lp.addVariable('d', 3)

        self.assertRaisesRegex(
            AssertionError,
            'Each disjunctive term should have the same variables',
            bb.find_strong_disjunctive_cut, 0)
    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)
Пример #29
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}')
Пример #30
0
    def test_find_strong_disjunctive_cut_many_times(self):
        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)):
            if i >= 10:
                break
            print(f'running test {i + 1}')
            pth = os.path.join(fldr, file)
            model = MILPInstance(file_name=pth)
            bb = BranchAndBound(model)
            bb.solve()
            pi, pi0 = bb.find_strong_disjunctive_cut(0)

            # ensure we cut off the root solution
            self.assertTrue(sum(pi * bb.root_node.solution) <= pi0)

            # ensure we don't cut off disjunctive mins
            for n in bb.tree.get_leaves(0):
                if n.lp_feasible:
                    self.assertTrue(sum(pi * n.solution) >= pi0 - .01)