def test_update_root(self): tree = BabTree(MockNodeStorage(create_problem()), KSectionBranchingStrategy(), MockSelectionStrategy()) sol = create_solution(5.0, 10.0) tree.update_root(sol) assert np.isclose(5.0, tree.lower_bound) assert np.isclose(10.0, tree.upper_bound)
def tree(): """Create a BaBTree like: 0 +----+----+ | | | 1 2 3 +-+-+ | | 4 5 with coordinates: 0 : [0] 1 : [0, 0] 2 : [0, 1] 3 : [0, 2] 4 : [0, 2, 0] 5 : [0, 2, 1] """ problem = create_problem() storage = MockNodeStorage(problem) t = BabTree(storage, KSectionBranchingStrategy(), MockSelectionStrategy()) t.update_root(create_solution(-30.0, 0.0)) root = t.root bp = BranchingPoint(problem.variable(0), [0.0, 0.5]) root.branch_at_point(bp) c = root.children[2] bp = BranchingPoint(problem.variable(1), [0.0]) c.branch_at_point(bp) return t
def test_ksection(self, problem, solution): ksection_strat = KSectionBranchingStrategy(7) tree = BabTree( MockNodeStorage(problem), ksection_strat, FakeSelectionStrategy(), ) node = tree.root for i in range(5): node.update(create_solution(0.0, 1.0)) children, _ = node.branch(ksection_strat) assert len(children) == 7 for child in children: assert child.variable.idx == i node = children[0]
def _bab_loop(self, problem, run_id, **kwargs): known_optimal_objective = kwargs.get('known_optimal_objective', None) if known_optimal_objective is not None: if not problem.objective.original_sense.is_minimization(): known_optimal_objective = -known_optimal_objective self._bac_telemetry.start_timing( known_optimal_objective, elapsed_time(), ) branching_strategy = self._algo.branching_strategy node_selection_strategy = self._algo.node_selection_strategy bab_iteration = 0 root_node_storage = RootNodeStorage(problem) tree = BabTree(root_node_storage, branching_strategy, node_selection_strategy) self._tree = tree logger.info(run_id, 'Finding initial feasible solution') initial_solution = self._algo.find_initial_solution( run_id, problem, tree, tree.root) if initial_solution is not None: tree.add_initial_solution(initial_solution) self._bac_telemetry.update_at_end_of_iteration( tree, elapsed_time()) self._telemetry.log_at_end_of_iteration(run_id, bab_iteration) if self._algo.should_terminate(tree.state): return logger.info(run_id, 'Solving root problem') root_solution = self._algo.solve_problem_at_root( run_id, problem, tree, tree.root) tree.update_root(root_solution) self._bac_telemetry.update_at_end_of_iteration( tree, elapsed_time(), update_nodes_visited=False) self._telemetry.log_at_end_of_iteration(run_id, bab_iteration) bab_iteration += 1 logger.info(run_id, 'Root problem solved, tree state {}', tree.state) logger.log_add_bab_node( run_id, coordinate=[0], lower_bound=root_solution.lower_bound, upper_bound=root_solution.upper_bound, ) while not self._algo.should_terminate(tree.state): logger.info(run_id, 'Tree state at beginning of iteration: {}', tree.state) if not tree.has_nodes(): logger.info(run_id, 'No more nodes to visit.') break current_node = tree.next_node() if current_node.parent is None: # This is the root node. node_children, branching_point = tree.branch_at_node( current_node) logger.info(run_id, 'Branched at point {}', branching_point) continue else: current_node_problem = current_node.storage.problem var_view = \ current_node_problem.variable_view(current_node.variable) logger.info( run_id, 'Visiting node {}: parent state={}', current_node.coordinate, current_node.parent.state, ) node_can_not_improve_solution = is_close( current_node.parent.lower_bound, tree.upper_bound, atol=self._algo.tolerance, rtol=self._algo.relative_tolerance, ) or current_node.parent.lower_bound > tree.upper_bound if node_can_not_improve_solution: logger.info( run_id, "Fathom node because it won't improve bound: node.lower_bound={}, tree.upper_bound={}", current_node.parent.lower_bound, tree.upper_bound, ) logger.log_prune_bab_node(run_id, current_node.coordinate) tree.fathom_node(current_node, update_nodes_visited=True) self._bac_telemetry.update_at_end_of_iteration( tree, elapsed_time()) self._telemetry.log_at_end_of_iteration(run_id, bab_iteration) bab_iteration += 1 continue solution = self._algo.solve_problem_at_node( run_id, current_node.storage.problem, tree, current_node) tree.update_node(current_node, solution) logger.log_add_bab_node( run_id, coordinate=current_node.coordinate, lower_bound=solution.lower_bound, upper_bound=solution.upper_bound, ) current_node_converged = is_close( solution.lower_bound, solution.upper_bound, atol=self._algo.tolerance, rtol=self._algo.relative_tolerance, ) if not current_node_converged and solution.upper_bound_solution is not None: node_children, branching_point = tree.branch_at_node( current_node) logger.info(run_id, 'Branched at point {}', branching_point) else: # We won't explore this part of the tree anymore. # Add to fathomed nodes. logger.info( run_id, 'Fathom node {}, converged? {}, upper_bound_solution {}', current_node.coordinate, current_node_converged, solution.upper_bound_solution) logger.log_prune_bab_node(run_id, current_node.coordinate) tree.fathom_node(current_node, update_nodes_visited=False) self._log_problem_information_at_node(run_id, current_node.storage.problem, solution, current_node) logger.info(run_id, 'New tree state at {}: {}', current_node.coordinate, tree.state) logger.update_variable(run_id, 'z_l', tree.nodes_visited, tree.lower_bound) logger.update_variable(run_id, 'z_u', tree.nodes_visited, tree.upper_bound) logger.info( run_id, 'Child {} has solutions: LB={} UB={}', current_node.coordinate, solution.lower_bound_solution, solution.upper_bound_solution, ) self._bac_telemetry.update_at_end_of_iteration( tree, elapsed_time()) self._telemetry.log_at_end_of_iteration(run_id, bab_iteration) bab_iteration += 1 logger.info(run_id, 'Branch & Bound Finished: {}', tree.state) logger.info(run_id, 'Branch & Bound Converged?: {}', self._algo._has_converged(tree.state)) logger.info(run_id, 'Branch & Bound Timeout?: {}', self._algo._timeout()) logger.info(run_id, 'Branch & Bound Node Limit Exceeded?: {}', self._algo._node_limit_exceeded(tree.state))
def test_update_with_new_lower_bound(self): tree = BabTree(MockNodeStorage(create_problem()), KSectionBranchingStrategy(), MockSelectionStrategy()) sol = create_solution(5.0, 10.0) tree.update_root(sol) root_children, _ = tree.branch_at_node(tree.root) a, b = root_children tree.update_node(a, create_solution(7.0, 11.0)) tree.branch_at_node(a) assert np.isclose(5.0, tree.lower_bound) assert np.isclose(10.0, tree.upper_bound) tree.update_node(b, create_solution(6.0, 10.0)) children, _ = tree.branch_at_node(b) assert np.isclose(6.0, tree.lower_bound) assert np.isclose(10.0, tree.upper_bound) a, b = children tree.update_node(a, create_solution(7.0, 10.0)) tree.branch_at_node(a) assert np.isclose(6.0, tree.lower_bound) assert np.isclose(10.0, tree.upper_bound)
def _bab_loop(self, model, **kwargs): known_optimal_objective = kwargs.get('known_optimal_objective', None) if known_optimal_objective is not None: if not model._objective.is_originally_minimizing: known_optimal_objective = -known_optimal_objective branching_strategy = self.branching_strategy node_selection_strategy = self.node_selection_strategy root_node_storage = self.init_node_storage(model) tree = BabTree(root_node_storage, branching_strategy, node_selection_strategy) self._tree = tree self.logger.info('Finding initial feasible solution') with self._telemetry.timespan( 'branch_and_bound.find_initial_solution'): initial_solution = self.find_initial_solution( model, tree, tree.root) prev_elapsed_time = None if initial_solution is not None: tree.add_initial_solution(initial_solution, self.galini.mc) if self.should_terminate(tree.state): delta_t, prev_elapsed_time = _compute_delta_t( self.galini, prev_elapsed_time) update_at_end_of_iteration(self.galini, tree, delta_t) return True self.logger.info('Solving root problem') with self._telemetry.timespan( 'branch_and_bound.solve_problem_at_root'): root_solution = self.solve_problem_at_root(tree, tree.root) tree.update_root(root_solution) delta_t, prev_elapsed_time = _compute_delta_t(self.galini, prev_elapsed_time) update_at_end_of_iteration(self.galini, tree, delta_t) self.logger.info('Root problem solved, tree state {}', tree.state) self.logger.info('Root problem solved, root solution {}', root_solution) self.logger.log_add_bab_node( coordinate=[0], lower_bound=root_solution.lower_bound, upper_bound=root_solution.upper_bound, ) if not root_solution.lower_bound_success: if not root_solution.upper_bound_success: return False mc = self.galini.mc while not self.should_terminate(tree.state): self.logger.info('Tree state at beginning of iteration: {}', tree.state) if not tree.has_nodes(): self.logger.info('No more nodes to visit.') break current_node = tree.next_node() if current_node.parent is None: # This is the root node. node_children, branching_point = tree.branch_at_node( current_node, mc) self.logger.info('Branched at point {}', branching_point) continue self.logger.info( 'Visiting node {}: parent state={}', current_node.coordinate, current_node.parent.state, ) node_can_not_improve_solution = is_close( current_node.parent.lower_bound, tree.upper_bound, atol=self.bab_config['absolute_gap'], rtol=self.bab_config['relative_gap'], ) or current_node.parent.lower_bound > tree.upper_bound if node_can_not_improve_solution: self.logger.info( "Fathom node because it won't improve bound: node.lower_bound={}, tree.upper_bound={}", current_node.parent.lower_bound, tree.upper_bound, ) self.logger.log_prune_bab_node(current_node.coordinate) tree.fathom_node(current_node, update_nodes_visited=True) delta_t, prev_elapsed_time = _compute_delta_t( self.galini, prev_elapsed_time) update_at_end_of_iteration(self.galini, tree, delta_t) continue with self._telemetry.timespan( 'branch_and_bound.solve_problem_at_node'): solution = self.solve_problem_at_node(tree, current_node) assert solution is not None tree.update_node(current_node, solution) self.logger.info('Node {} solution: {}', current_node.coordinate, solution) self.logger.log_add_bab_node( coordinate=current_node.coordinate, lower_bound=solution.lower_bound, upper_bound=solution.upper_bound, ) current_node_converged = is_close( solution.lower_bound, tree.upper_bound, atol=self.bab_config['absolute_gap'], rtol=self.bab_config['relative_gap'], ) or solution.lower_bound > tree.upper_bound node_relaxation_is_feasible_or_unbounded = ( solution.lower_bound_solution is not None and (solution.lower_bound_solution.status.is_success() or solution.lower_bound_solution.status.is_unbounded())) if not current_node_converged and node_relaxation_is_feasible_or_unbounded: node_children, branching_point = tree.branch_at_node( current_node, mc) self.logger.info('Branched at point {}', branching_point) else: # We won't explore this part of the tree anymore. # Add to fathomed nodes. self.logger.info( 'Fathom node {}, lower_bound_solution: {}, tree upper bound: {}', current_node.coordinate, solution.lower_bound, tree.upper_bound) self.logger.log_prune_bab_node(current_node.coordinate) tree.fathom_node(current_node, update_nodes_visited=False) self.logger.info('New tree state at {}: {}', current_node.coordinate, tree.state) self.logger.update_variable('z_l', tree.nodes_visited, tree.lower_bound) self.logger.update_variable('z_u', tree.nodes_visited, tree.upper_bound) self.logger.info( 'Child {} has solutions: LB={} UB={}', current_node.coordinate, solution.lower_bound_solution, solution.upper_bound_solution, ) delta_t, prev_elapsed_time = _compute_delta_t( self.galini, prev_elapsed_time) update_at_end_of_iteration(self.galini, tree, delta_t) self.logger.info('Branch & Bound Finished: {}', tree.state) self.logger.info('Branch & Bound Converged?: {}', self.has_converged(tree.state)) self.logger.info('Branch & Bound Timeout?: {}', self.galini.timelimit.timeout()) self.logger.info('Branch & Bound Node Limit Exceeded?: {}', self.node_limit_exceeded(tree.state)) return True