Ejemplo n.º 1
0
def _execute_single_test(problem, baseline, solver=None, comm=None, **kwds):
    if solver is None:
        solver = Solver(comm=comm)
    else:
        assert comm is None
    if solver.comm is not None:
        if solver.comm.rank == 0:
            pass
        elif solver.comm.rank == 1:
            pass
        elif solver.comm.rank == 3:
            pass
    orig = pybnb.node.Node()
    problem.save_state(orig)
    results = solver.solve(problem, **kwds)

    current = pybnb.node.Node()
    problem.save_state(current)
    assert len(current.state) == len(orig.state)
    for i in range(len(current.state)):
        assert current.state[i] == orig.state[i]
    assert len(vars(results)) > 0
    assert len(vars(results)) == len(vars(baseline))
    for name in sorted(sorted(list(vars(results).keys()))):
        if getattr(baseline, name) is _ignore_value_:
            continue
        if (name == 'nodes') and \
           (solver.comm is not None) and \
           (solver.comm.size > 2):
            pass
        else:
            assert getattr(results, name) == getattr(baseline, name), \
                ("value for '"+str(name)+"' ("+
                 str(getattr(results, name))+") does "
                 "not match baseline ("+
                 str(getattr(baseline, name))+")")
    if solver.is_dispatcher:
        q = solver.save_dispatcher_queue()
        assert len(q) == 2
        assert q.next_tree_id >= 0
        assert len(q.nodes) == solver._disp.queue.size()
Ejemplo n.º 2
0
class NestedSolver(_ProblemWithSolveInfoCollection):
    """A class for creating a nested branch-and-bound solve
    strategy. An instance of this class (wrapped around a
    standard problem) can be passed to the solver as the
    problem argument.

    Parameters
    ----------
    problem : :class:`pybnb.Problem <pybnb.problem.Problem>`
        An object defining a branch-and-bound problem.
    node_limit : int, optional
        The same as the standard solver option, but applied
        to the nested solver to limit the number of nodes to
        explore when processing a work item. (default: None)
    time_limit : float, optional
        The same as the standard solver option, but applied
        to the nested solver to limit the amount of time
        spent processing a work item. (default: 5)
    queue_limit : int, optional
        The same as the standard solver option, but applied
        to the nested solver to limit the size of the queue.
        (default: None)
    track_bound : bool, optional
        The same as the standard solver option, but applied
        to the nested solver control bound tracking.
        (default: True)
    queue_strategy : :class:`QueueStrategy <pybnb.common.QueueStrategy>` or tuple
        The same as the standard solver option, but applied
        to the nested solver to control the queue strategy
        used when processing a work item. (default: 'depth')
    """
    def __init__(self,
                 problem,
                 node_limit=None,
                 time_limit=5,
                 queue_limit=None,
                 track_bound=True,
                 queue_strategy="depth"):
        # we import from pybnb.solver here to avoid a
        # circular import reference
        from pybnb.solver import Solver
        self._problem = problem
        self._node_limit = node_limit
        self._time_limit = time_limit
        self._queue_limit = queue_limit
        self._track_bound = track_bound
        self._queue_strategy = queue_strategy
        self._solver = Solver(comm=None)
        self._log = logging.Logger(None, level=logging.WARNING)
        self._convergence_checker = None
        self._best_objective = None
        self._best_node = None
        self._current_node = None
        self._results = None
        self._queue = None
        super(NestedSolver, self).__init__()

    def _initialize(self, dispatcher, best_objective, disable_objective_call):
        assert best_objective is not None
        self._best_objective = best_objective
        self._disable_objective_call = disable_objective_call
        self._log.addHandler(_RedirectHandler(dispatcher))

    def _solve(self):
        bound_stop = None
        if not math.isinf(self._best_objective):
            bound_stop = self._best_objective
        # shallow copy
        root = copy.copy(self._current_node)
        init_queue = DispatcherQueueData(nodes=[root],
                                         worst_terminal_bound=None,
                                         sense=self._convergence_checker.sense)
        self._results = self._solver.solve(
            self._problem,
            best_objective=self._best_objective,
            best_node=self._best_node,
            bound_stop=bound_stop,
            initialize_queue=init_queue,
            log=self._log,
            absolute_gap=\
                self._convergence_checker.absolute_gap,
            relative_gap=\
                self._convergence_checker.relative_gap,
            scale_function=\
                self._convergence_checker.scale_function,
            queue_tolerance=\
                self._convergence_checker.queue_tolerance,
            branch_tolerance=\
                self._convergence_checker.branch_tolerance,
            comparison_tolerance=\
                self._convergence_checker.comparison_tolerance,
            objective_stop=\
                self._convergence_checker.objective_stop,
            disable_objective_call=self._disable_objective_call,
            node_limit=self._node_limit,
            time_limit=self._time_limit,
            queue_limit=self._queue_limit,
            track_bound=self._track_bound,
            queue_strategy=self._queue_strategy,
            disable_signal_handlers=True)
        self._queue = self._solver.save_dispatcher_queue()
        self._solve_info.add_from(self._solver._global_solve_info)

    #
    # Ducktype a partial Problem interface
    #

    def objective(self):  #pragma:nocover
        """The solver does not call this method when it sees the
        problem implements a nested solve."""
        raise NotImplementedError()

    def bound(self):  #pragma:nocover
        """The solver does not call this method when it sees the
        problem implements a nested solve."""
        raise NotImplementedError()

    def branch(self):  #pragma:nocover
        """The solver does not call this method when it sees the
        problem implements a nested solve."""
        raise NotImplementedError()

    def sense(self):
        """Calls the sense() method on the user-provided
        problem."""
        return self._problem.sense()

    def save_state(self, node):
        """Calls the save_state() method on the user-provided
        problem."""
        self._problem.save_state(node)

    def load_state(self, node):
        """Calls the load_state() method on the user-provided
        problem and prepares for a nested solve."""
        self._problem.load_state(node)
        self._results = None
        self._children = None
        self._current_node = node
        self._solve_found_best_node = False

    def notify_solve_begins(self, comm, worker_comm, convergence_checker):
        """Calls the notify_solve_begins() method on the
        user-provided problem and prepares for a solve."""
        self._best_objective = None
        self._best_node = None
        self._convergence_checker = convergence_checker
        self._current_node = None
        self._results = None
        self._queue = None
        self._problem.notify_solve_begins(comm, worker_comm,
                                          convergence_checker)

    def notify_new_best_node(self, node, current):
        """Calls the notify_new_best_node() method on the
        user-provided problem and stores the best node for
        use in the next nested solve."""
        self._best_objective = self._convergence_checker.\
            best_objective(self._best_objective,
                           node.objective)
        self._best_node = node
        self._problem.notify_new_best_node(node, current)

    def notify_solve_finished(self, comm, worker_comm, results):
        """Calls the notify_solve_finished() method on the
        user-provided problem and does some final
        cleanup."""
        while len(self._log.handlers) > 0:
            self._log.removeHandler(self._log.handlers[0])
        self._problem.notify_solve_finished(comm, worker_comm, results)
Ejemplo n.º 3
0
def _test_initialize_queue(comm):
    solver = Solver(comm=comm)

    # no initial queue
    for sense in (minimize, maximize):
        problem = DummyProblem(sense)
        results = solver.solve(problem, queue_limit=0)
        assert results.solution_status == "unknown"
        assert results.termination_condition == "queue_limit"
        assert results.objective == (inf if (sense == minimize) else -inf)
        assert results.bound == (-inf if (sense == minimize) else inf)
        assert results.absolute_gap is None
        assert results.relative_gap is None
        assert results.nodes == 0
        assert results.wall_time is not None
        assert results.best_node is None
        assert problem._notify_new_best_node_call_count == 0
        assert solver._local_solve_info.explored_nodes_count == 0
        problem = DummyProblem(sense)
        results = solver.solve(problem)
        assert results.solution_status == "optimal"
        assert results.termination_condition == "optimality"
        assert results.objective == 0
        assert results.bound == 0
        assert results.absolute_gap == 0
        assert results.relative_gap == 0
        assert results.nodes == 1
        assert results.wall_time is not None
        assert results.best_node is not None
        assert results.best_node.objective == results.objective
        assert results.best_node.tree_depth == 0
        if solver.is_worker:
            assert problem._notify_new_best_node_call_count == 1
            assert problem._notify_new_best_node_args[0] is results.best_node
            if solver._local_solve_info.explored_nodes_count == 1:
                assert problem._notify_new_best_node_args[1]
            else:
                assert solver._local_solve_info.explored_nodes_count == 0
                assert not problem._notify_new_best_node_args[1]
        else:
            assert problem._notify_new_best_node_call_count == 0
        problem = DummyProblem(sense)
        results = solver.solve(
            problem, best_objective=(1 if (sense == minimize) else -1)
        )
        assert results.solution_status == "optimal"
        assert results.termination_condition == "optimality"
        assert results.objective == 0
        assert results.bound == 0
        assert results.absolute_gap == 0
        assert results.relative_gap == 0
        assert results.nodes == 1
        assert results.wall_time is not None
        assert results.best_node is not None
        assert results.best_node.objective == results.objective
        assert results.best_node.tree_depth == 0
        if solver.is_worker:
            assert problem._notify_new_best_node_call_count == 1
            assert problem._notify_new_best_node_args[0] is results.best_node
            if solver._local_solve_info.explored_nodes_count == 1:
                assert problem._notify_new_best_node_args[1]
            else:
                assert solver._local_solve_info.explored_nodes_count == 0
                assert not problem._notify_new_best_node_args[1]
        else:
            assert problem._notify_new_best_node_call_count == 0
        problem = DummyProblem(sense)
        results = solver.solve(
            problem,
            best_objective=(1 if (sense == minimize) else -1),
            disable_objective_call=True,
        )
        assert results.solution_status == "feasible"
        assert results.termination_condition == "queue_empty"
        assert results.objective == (1 if (sense == minimize) else -1)
        assert results.bound == 0
        assert results.absolute_gap == 1
        assert results.relative_gap == 1
        assert results.nodes == 1
        assert results.wall_time is not None
        assert results.best_node is None
        assert problem._notify_new_best_node_call_count == 0
        best_node_ = Node()
        best_node_.objective = 1 if (sense == minimize) else -1
        best_node_._uuid = "abcd"
        problem = DummyProblem(sense)
        results = solver.solve(
            problem, best_node=best_node_, disable_objective_call=True
        )
        assert results.solution_status == "feasible"
        assert results.termination_condition == "queue_empty"
        assert results.objective == best_node_.objective
        assert results.bound == 0
        assert results.absolute_gap == 1
        assert results.relative_gap == 1
        assert results.nodes == 1
        assert results.best_node._uuid == best_node_._uuid
        if (comm is None) or (comm.size == 1):
            assert results.best_node is best_node_
        assert results.best_node.objective == results.objective
        if solver.is_worker:
            assert problem._notify_new_best_node_call_count == 1
            assert problem._notify_new_best_node_args[0] is results.best_node
            if solver._local_solve_info.explored_nodes_count == 1:
                assert not problem._notify_new_best_node_args[1]
            else:
                assert solver._local_solve_info.explored_nodes_count == 0
                assert not problem._notify_new_best_node_args[1]
        else:
            assert problem._notify_new_best_node_call_count == 0
        best_node_ = Node()
        best_node_.objective = 1 if (sense == minimize) else -1
        best_node_._uuid = "abcd"
        problem = DummyProblem(sense)
        results = solver.solve(problem, best_node=best_node_)
        assert results.solution_status == "optimal"
        assert results.termination_condition == "optimality"
        assert results.objective == 0
        assert results.bound == 0
        assert results.absolute_gap == 0
        assert results.relative_gap == 0
        assert results.nodes == 1
        assert results.wall_time is not None
        assert results.best_node._uuid != best_node_._uuid
        if (comm is None) or (comm.size == 1):
            assert results.best_node is not best_node_
        assert results.best_node.objective == results.objective
        assert results.best_node.tree_depth == 0
        if solver.is_worker:
            assert problem._notify_new_best_node_call_count >= 1
            assert problem._notify_new_best_node_args[0] is results.best_node
            if problem._notify_new_best_node_call_count == 2:
                assert problem._notify_new_best_node_args[1]
            else:
                assert problem._notify_new_best_node_args[0]
        else:
            assert problem._notify_new_best_node_call_count == 0

    # empty initial queue
    queue = DispatcherQueueData(nodes=[], worst_terminal_bound=None, sense=minimize)
    for sense in (minimize, maximize):
        queue.sense = sense
        problem = DummyProblem(sense)
        results = solver.solve(problem, initialize_queue=queue)
        assert results.solution_status == "unknown"
        assert results.termination_condition == "queue_empty"
        assert results.objective == (inf if (sense == minimize) else -inf)
        assert results.bound == (-inf if (sense == minimize) else inf)
        assert results.absolute_gap is None
        assert results.relative_gap is None
        assert results.nodes == 0
        assert results.wall_time is not None
        assert results.best_node is None
        assert problem._notify_new_best_node_call_count == 0
        problem = DummyProblem(sense)
        results = solver.solve(problem, initialize_queue=queue, best_objective=0)
        assert results.solution_status == "feasible"
        assert results.termination_condition == "queue_empty"
        assert results.objective == 0
        assert results.bound == (-inf if (sense == minimize) else inf)
        assert results.absolute_gap == inf
        assert results.relative_gap == inf
        assert results.nodes == 0
        assert results.wall_time is not None
        assert results.best_node is None
        assert problem._notify_new_best_node_call_count == 0
        problem = DummyProblem(sense)
        results = solver.solve(
            problem,
            initialize_queue=queue,
            best_objective=0,
            disable_objective_call=True,
        )
        assert results.solution_status == "feasible"
        assert results.termination_condition == "queue_empty"
        assert results.objective == 0
        assert results.bound == (-inf if (sense == minimize) else inf)
        assert results.absolute_gap == inf
        assert results.relative_gap == inf
        assert results.nodes == 0
        assert results.wall_time is not None
        assert results.best_node is None
        assert problem._notify_new_best_node_call_count == 0
        best_node_ = Node()
        best_node_.objective = 1 if (sense == minimize) else -1
        best_node_._uuid = "abcd"
        problem = DummyProblem(sense)
        results = solver.solve(
            problem, initialize_queue=queue, best_objective=0, best_node=best_node_
        )
        assert results.solution_status == "feasible"
        assert results.termination_condition == "queue_empty"
        assert results.objective == 0
        assert results.bound == (-inf if (sense == minimize) else inf)
        assert results.absolute_gap == inf
        assert results.relative_gap == inf
        assert results.nodes == 0
        assert results.wall_time is not None
        assert results.best_node._uuid == best_node_._uuid
        if (comm is None) or (comm.size == 1):
            assert results.best_node is best_node_
        if solver.is_worker:
            assert problem._notify_new_best_node_call_count == 1
            assert problem._notify_new_best_node_args[0] is results.best_node
            assert solver._local_solve_info.explored_nodes_count == 0
            assert not problem._notify_new_best_node_args[1]
        else:
            assert problem._notify_new_best_node_call_count == 0
        best_node_ = Node()
        best_node_.objective = 1 if (sense == minimize) else -1
        best_node_._uuid = "abcd"
        problem = DummyProblem(sense)
        results = solver.solve(
            problem,
            initialize_queue=queue,
            best_objective=(2 if (sense == minimize) else -2),
            best_node=best_node_,
        )
        assert results.solution_status == "feasible"
        assert results.termination_condition == "queue_empty"
        assert results.objective == (1 if (sense == minimize) else -1)
        assert results.bound == (-inf if (sense == minimize) else inf)
        assert results.absolute_gap == inf
        assert results.relative_gap == inf
        assert results.nodes == 0
        assert results.wall_time is not None
        assert results.best_node._uuid == best_node_._uuid
        if (comm is None) or (comm.size == 1):
            assert results.best_node is best_node_
        if solver.is_worker:
            assert problem._notify_new_best_node_call_count == 1
            assert problem._notify_new_best_node_args[0] is results.best_node
            assert solver._local_solve_info.explored_nodes_count == 0
            assert not problem._notify_new_best_node_args[1]
        else:
            assert problem._notify_new_best_node_call_count == 0

    # non-empty initial queue
    root = Node()
    root._uuid = "abcd"
    root.tree_depth = 0
    root.objective = 0
    root.bound = 0
    queue = DispatcherQueueData(nodes=[root], worst_terminal_bound=None, sense=minimize)
    orig_objective = queue.nodes[0].objective
    for sense in (minimize, maximize):
        queue.sense = sense
        queue.nodes[0].objective = orig_objective
        problem = DummyProblem(sense)
        results = solver.solve(problem, initialize_queue=queue)
        assert results.solution_status == "optimal"
        assert results.termination_condition == "optimality"
        assert results.objective == 0
        assert results.bound == 0
        assert results.absolute_gap == 0
        assert results.relative_gap == 0
        assert results.nodes == 1
        assert results.wall_time is not None
        assert results.best_node._uuid == root._uuid
        if (comm is None) or (comm.size == 1):
            assert results.best_node is root
        if solver.is_worker:
            assert problem._notify_new_best_node_call_count == 1
            assert problem._notify_new_best_node_args[0] is results.best_node
            if solver._local_solve_info.explored_nodes_count == 1:
                assert problem._notify_new_best_node_args[1]
            else:
                assert solver._local_solve_info.explored_nodes_count == 0
                assert not problem._notify_new_best_node_args[1]
        else:
            assert problem._notify_new_best_node_call_count == 0
        queue.nodes[0].objective = orig_objective
        problem = DummyProblem(sense)
        results = solver.solve(
            problem,
            initialize_queue=queue,
            best_objective=(1 if (sense == minimize) else -1),
        )
        assert results.solution_status == "optimal"
        assert results.termination_condition == "optimality"
        assert results.objective == 0
        assert results.bound == 0
        assert results.absolute_gap == 0
        assert results.relative_gap == 0
        assert results.nodes == 1
        assert results.wall_time is not None
        assert results.best_node._uuid == root._uuid
        if (comm is None) or (comm.size == 1):
            assert results.best_node is root
        if solver.is_worker:
            assert problem._notify_new_best_node_call_count == 1
            assert problem._notify_new_best_node_args[0] is results.best_node
            if solver._local_solve_info.explored_nodes_count == 1:
                assert problem._notify_new_best_node_args[1]
            else:
                assert solver._local_solve_info.explored_nodes_count == 0
                assert not problem._notify_new_best_node_args[1]
        else:
            assert problem._notify_new_best_node_call_count == 0
        queue.nodes[0].objective = inf if (sense == minimize) else -inf
        problem = DummyProblem(sense)
        results = solver.solve(
            problem,
            initialize_queue=queue,
            best_objective=(1 if (sense == minimize) else -1),
        )
        assert results.solution_status == "optimal"
        assert results.termination_condition == "optimality"
        assert results.objective == 0
        assert results.bound == 0
        assert results.absolute_gap == 0
        assert results.relative_gap == 0
        assert results.nodes == 1
        assert results.wall_time is not None
        assert results.best_node._uuid == root._uuid
        if (comm is None) or (comm.size == 1):
            assert results.best_node is root
        if solver.is_worker:
            assert problem._notify_new_best_node_call_count == 1
            assert problem._notify_new_best_node_args[0] is results.best_node
            if solver._local_solve_info.explored_nodes_count == 1:
                assert problem._notify_new_best_node_args[1]
            else:
                assert solver._local_solve_info.explored_nodes_count == 0
                assert not problem._notify_new_best_node_args[1]
        else:
            assert problem._notify_new_best_node_call_count == 0
        queue.nodes[0].objective = inf if (sense == minimize) else -inf
        problem = DummyProblem(sense)
        results = solver.solve(
            problem,
            initialize_queue=queue,
            best_objective=(1 if (sense == minimize) else -1),
            disable_objective_call=True,
        )
        assert results.solution_status == "feasible"
        assert results.termination_condition == "queue_empty"
        assert results.objective == (1 if (sense == minimize) else -1)
        assert results.bound == 0
        assert results.absolute_gap == 1
        assert results.relative_gap == 1
        assert results.nodes == 1
        assert results.wall_time is not None
        assert results.best_node is None
        assert problem._notify_new_best_node_call_count == 0
        queue.nodes[0].objective = orig_objective