def test_sense(self): # min p = ConvergenceChecker(minimize) assert p.sense == minimize # max p = ConvergenceChecker(maximize) assert p.sense == maximize
def test_infeasible_objective(self): # min p = ConvergenceChecker(minimize) assert p.infeasible_objective == inf # max p = ConvergenceChecker(maximize) assert p.infeasible_objective == -inf
def test_unbounded_objective(self): # min p = ConvergenceChecker(minimize) assert p.unbounded_objective == -inf # max p = ConvergenceChecker(maximize) assert p.unbounded_objective == inf
def test_initialize_queue(self): node_limit = None time_limit = None log = get_simple_logger() log_interval_seconds = inf log_new_incumbent = True convergence_checker = ConvergenceChecker(minimize) root = Node(size=0) Node._insert_tree_id(root._data, 0) root.bound = convergence_checker.unbounded_objective root.objective = convergence_checker.infeasible_objective queue = DispatcherQueueData(nodes=[root], next_tree_id=1) disp = DispatcherLocal() disp.initialize(0, queue, 'bound', convergence_checker, node_limit, time_limit, log, log_interval_seconds, log_new_incumbent) assert disp.best_objective == 0 disp.initialize(1, queue, 'bound', convergence_checker, node_limit, time_limit, log, log_interval_seconds, log_new_incumbent) assert disp.best_objective == 1 root.objective = -1 disp.initialize(1, queue, 'bound', convergence_checker, node_limit, time_limit, log, log_interval_seconds, log_new_incumbent) assert disp.best_objective == -1
def test_best_bound(self): # min p = ConvergenceChecker(minimize) assert p.best_bound(-1, 0, 1) == 1 assert p.best_bound([-1, 0, 1]) == 1 # max p = ConvergenceChecker(maximize) assert p.best_bound(-1, 0, 1) == -1 assert p.best_bound([-1, 0, 1]) == -1
def test_best_objective(self): # min p = ConvergenceChecker(minimize) assert p.best_objective(-1, 0, 1) == -1 assert p.best_objective([-1, 0, 1]) == -1 # max p = ConvergenceChecker(maximize) assert p.best_objective(-1, 0, 1) == 1 assert p.best_objective([-1, 0, 1]) == 1
def _logging_redirect_check(comm): opt = Solver(comm=comm) p = DummyProblem() log = logging.Logger(None, level=logging.WARNING) log.addHandler(_RedirectHandler(opt._disp)) if opt.is_dispatcher: assert (comm is None) or (comm.rank == 0) root = Node() p.save_state(root) root.objective = p.infeasible_objective() root.bound = p.unbounded_objective() initialize_queue = DispatcherQueueData(nodes=[root], worst_terminal_bound=None, sense=p.sense()) out = StringIO() formatter = logging.Formatter("[%(levelname)s] %(message)s") opt._disp.initialize( p.infeasible_objective(), None, initialize_queue, "bound", ConvergenceChecker(p.sense()), None, None, None, True, get_simple_logger(console=True, stream=out, level=logging.DEBUG, formatter=formatter), 0.0, True, ) log.debug("0: debug") log.info("0: info") log.warning("0: warning") log.error("0: error") log.critical("0: critical") if (comm is not None) and (comm.size > 1): opt._disp.serve() else: assert comm is not None if comm.size > 1: for i in range(1, comm.size): if comm.rank == i: log.debug(str(comm.rank) + ": debug") log.info(str(comm.rank) + ": info") log.warning(str(comm.rank) + ": warning") log.error(str(comm.rank) + ": error") log.critical(str(comm.rank) + ": critical") opt.worker_comm.Barrier() if opt.worker_comm.rank == 0: opt._disp.stop_listen() if comm is not None: comm.Barrier() if opt.is_dispatcher: assert ("\n".join( out.getvalue().splitlines()[7:])) == _get_logging_baseline( comm.size if comm is not None else 1)
def test_compute_relative_gap(self): for sense in [minimize, maximize]: p = ConvergenceChecker(sense) for bound, objective in itertools.product([-inf, inf, 0.0], [-inf, inf, 0.0]): if (not math.isinf(bound)) or \ (not math.isinf(objective)): continue if bound == objective: assert p.compute_relative_gap(bound, objective) == 0 elif p.sense == minimize: if (bound == -inf) or \ (objective == inf): assert p.compute_relative_gap(bound,objective) == \ inf else: assert p.compute_relative_gap(bound,objective) == \ -inf else: assert p.sense == maximize if (bound == inf) or \ (objective == -inf): assert p.compute_relative_gap(bound,objective) == \ inf else: assert p.compute_relative_gap(bound,objective) == \ -inf
def test_objective_is_optimal(self): for sense in [minimize, maximize]: p = ConvergenceChecker(sense) for bound, objective in itertools.product([-inf, inf, 0.0], [-inf, inf, 0.0]): if math.isinf(bound) and math.isinf(objective): if bound != p.infeasible_objective: assert not p.objective_is_optimal(objective, bound) elif objective == bound: assert p.objective_is_optimal(objective, bound) elif bound != p.infeasible_objective: assert not p.objective_is_optimal(objective, bound) p = ConvergenceChecker(sense, absolute_gap=0, relative_gap=0) assert p.objective_is_optimal(0, 0) assert p.objective_is_optimal(1, 1) assert p.objective_is_optimal(-1, -1)
def test_queue_strategy(self): node_limit = None time_limit = None log = get_simple_logger() log_interval_seconds = inf log_new_incumbent = True convergence_checker = ConvergenceChecker(minimize) root = Node(size=0) Node._insert_tree_id(root._data, 0) root.bound = convergence_checker.unbounded_objective root.objective = convergence_checker.infeasible_objective queue = DispatcherQueueData(nodes=[root], next_tree_id=1) disp = DispatcherLocal() disp.initialize(inf, queue, 'bound', convergence_checker, node_limit, time_limit, log, log_interval_seconds, log_new_incumbent) assert type(disp.queue) is WorstBoundFirstPriorityQueue disp.initialize(inf, queue, 'custom', convergence_checker, node_limit, time_limit, log, log_interval_seconds, log_new_incumbent) assert type(disp.queue) is CustomPriorityQueue disp.initialize(inf, queue, 'objective', convergence_checker, node_limit, time_limit, log, log_interval_seconds, log_new_incumbent) assert type(disp.queue) is BestObjectiveFirstPriorityQueue disp.initialize(inf, queue, 'breadth', convergence_checker, node_limit, time_limit, log, log_interval_seconds, log_new_incumbent) assert type(disp.queue) is BreadthFirstPriorityQueue disp.initialize(inf, queue, 'depth', convergence_checker, node_limit, time_limit, log, log_interval_seconds, log_new_incumbent) assert type(disp.queue) is DepthFirstPriorityQueue disp.initialize(inf, queue, 'fifo', convergence_checker, node_limit, time_limit, log, log_interval_seconds, log_new_incumbent) assert type(disp.queue) is FIFOQueue disp.initialize(inf, queue, 'random', convergence_checker, node_limit, time_limit, log, log_interval_seconds, log_new_incumbent) assert type(disp.queue) is RandomPriorityQueue disp.initialize(inf, queue, 'local_gap', convergence_checker, node_limit, time_limit, log, log_interval_seconds, log_new_incumbent) assert type(disp.queue) is LocalGapPriorityQueue
def _logging_check(comm): opt = Solver(comm=comm) p = DummyProblem() if opt.is_dispatcher: assert (comm is None) or (comm.rank == 0) root = Node() p.save_state(root) root.objective = p.infeasible_objective() root.bound = p.unbounded_objective() assert root.tree_id is None Node._insert_tree_id(root._data, 0) initialize_queue = DispatcherQueueData(nodes=[root], next_tree_id=1) out = StringIO() formatter = logging.Formatter("[%(levelname)s] %(message)s") opt._disp.initialize( p.infeasible_objective(), initialize_queue, "bound", ConvergenceChecker(p.sense()), None, None, get_simple_logger(console=True, stream=out, level=logging.DEBUG, formatter=formatter), 0.0, True) opt._disp.log_debug("0: debug") opt._disp.log_info("0: info") opt._disp.log_warning("0: warning") opt._disp.log_error("0: error") if (comm is not None) and (comm.size > 1): opt._disp.serve() else: assert comm is not None if comm.size > 1: for i in range(1, comm.size): if comm.rank == i: opt._disp.log_debug(str(comm.rank) + ": debug") opt._disp.log_info(str(comm.rank) + ": info") opt._disp.log_warning(str(comm.rank) + ": warning") opt._disp.log_error(str(comm.rank) + ": error") opt.worker_comm.Barrier() if opt.worker_comm.rank == 0: opt._disp.stop_listen() if comm is not None: comm.Barrier() if opt.is_dispatcher: assert ('\n'.join(out.getvalue().splitlines()[8:])) == \ _get_logging_baseline(comm.size if comm is not None else 1)
def test_queue_strategy(self): node_limit = None time_limit = None queue_limit = None track_bound = True log = get_simple_logger() log_interval_seconds = inf log_new_incumbent = True convergence_checker = ConvergenceChecker(minimize) root = Node() root.tree_depth = 0 root.bound = convergence_checker.unbounded_objective root.objective = convergence_checker.infeasible_objective queue = DispatcherQueueData([root], None, minimize) disp = DispatcherLocal() disp.initialize( convergence_checker.infeasible_objective, None, queue, "bound", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert type(disp.queue) is WorstBoundFirstPriorityQueue disp.initialize( convergence_checker.infeasible_objective, None, queue, "custom", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert type(disp.queue) is CustomPriorityQueue disp.initialize( convergence_checker.infeasible_objective, None, queue, "objective", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert type(disp.queue) is BestObjectiveFirstPriorityQueue disp.initialize( convergence_checker.infeasible_objective, None, queue, "breadth", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert type(disp.queue) is BreadthFirstPriorityQueue disp.initialize( convergence_checker.infeasible_objective, None, queue, "depth", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert type(disp.queue) is DepthFirstPriorityQueue disp.initialize( convergence_checker.infeasible_objective, None, queue, "fifo", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert type(disp.queue) is FIFOQueue disp.initialize( convergence_checker.infeasible_objective, None, queue, "lifo", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert type(disp.queue) is LIFOQueue disp.initialize( convergence_checker.infeasible_objective, None, queue, "random", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert type(disp.queue) is RandomPriorityQueue disp.initialize( convergence_checker.infeasible_objective, None, queue, "local_gap", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert type(disp.queue) is LocalGapPriorityQueue disp.initialize( convergence_checker.infeasible_objective, None, queue, ("bound", "local_gap"), convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert type(disp.queue) is LexicographicPriorityQueue
def test_initialize_queue(self): node_limit = None time_limit = None queue_limit = None track_bound = True log = get_simple_logger() log_interval_seconds = inf log_new_incumbent = True convergence_checker = ConvergenceChecker(minimize) root = Node() root.tree_depth = 0 root.bound = convergence_checker.unbounded_objective root.objective = convergence_checker.infeasible_objective queue = DispatcherQueueData([root], None, minimize) best_node_ = Node() best_node_.objective = 0 best_node_._generate_uuid() disp = DispatcherLocal() disp.initialize( convergence_checker.infeasible_objective, best_node_, queue, "bound", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert disp.best_objective == 0 assert disp.best_node.objective == 0 assert disp.best_node is best_node_ disp.initialize( -1, best_node_, queue, "bound", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert disp.best_objective == -1 assert disp.best_node.objective == 0 assert disp.best_node is best_node_ best_node_.objective = 1 disp.initialize( 2, best_node_, queue, "bound", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert disp.best_objective == 1 assert disp.best_node.objective == 1 assert disp.best_node is best_node_ best_node_.objective = 1 root.objective = -1 disp.initialize( 2, best_node_, queue, "bound", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert disp.best_objective == 1 assert disp.best_node.objective == 1 assert disp.best_node is best_node_ best_node_.objective = 1 root.objective = -1 disp.initialize( -2, best_node_, queue, "bound", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, ) assert disp.best_objective == -2 assert disp.best_node.objective == 1 assert disp.best_node is best_node_ # bad objective sense queue = DispatcherQueueData([root], None, maximize) with pytest.raises(ValueError): disp.initialize( convergence_checker.infeasible_objective, best_node_, queue, "bound", convergence_checker, node_limit, time_limit, queue_limit, track_bound, log, log_interval_seconds, log_new_incumbent, )
def test_eligible_for_queue(self): # min p = ConvergenceChecker(minimize) assert not p.eligible_for_queue(-inf, -inf) assert p.eligible_for_queue(-inf, inf) assert p.eligible_for_queue(-inf, 0.0) assert not p.eligible_for_queue(inf, -inf) assert not p.eligible_for_queue(inf, inf) assert not p.eligible_for_queue(inf, 0.0) assert not p.eligible_for_queue(0.0, -inf) assert p.eligible_for_queue(0.0, inf) assert p.eligible_for_queue(-1e-16, 0.0) assert not p.eligible_for_queue(0.0, 0.0) p = ConvergenceChecker(minimize, queue_tolerance=0.1) assert not p.eligible_for_queue(-0.1, 0.0) assert p.eligible_for_queue(-0.11, 0.0) p = ConvergenceChecker(minimize, queue_tolerance=None) assert p.eligible_for_queue(0.0, 0.0) # max p = ConvergenceChecker(maximize) assert not p.eligible_for_queue(-inf, -inf) assert not p.eligible_for_queue(-inf, inf) assert not p.eligible_for_queue(-inf, 0.0) assert p.eligible_for_queue(inf, -inf) assert not p.eligible_for_queue(inf, inf) assert p.eligible_for_queue(inf, 0.0) assert p.eligible_for_queue(0.0, -inf) assert not p.eligible_for_queue(0.0, inf) assert p.eligible_for_queue(1e-16, 0.0) assert not p.eligible_for_queue(0.0, 0.0) p = ConvergenceChecker(maximize, queue_tolerance=0.1) assert not p.eligible_for_queue(0.1, 0.0) assert p.eligible_for_queue(0.11, 0.0) p = ConvergenceChecker(maximize, queue_tolerance=None) assert p.eligible_for_queue(0.0, 0.0)
def test_eligible_to_branch(self): # min p = ConvergenceChecker(minimize) assert not p.eligible_to_branch(-inf, -inf) assert p.eligible_to_branch(-inf, inf) assert p.eligible_to_branch(-inf, 0.0) assert not p.eligible_to_branch(inf, -inf) assert not p.eligible_to_branch(inf, inf) assert not p.eligible_to_branch(inf, 0.0) assert not p.eligible_to_branch(0.0, -inf) assert p.eligible_to_branch(0.0, inf) assert not p.eligible_to_branch(0.0, 0.0) p = ConvergenceChecker(minimize, branch_tolerance=0.1) assert not p.eligible_to_branch(-0.1, 0.0) assert p.eligible_to_branch(-0.11, 0.0) p = ConvergenceChecker(minimize, branch_tolerance=None) assert p.eligible_to_branch(0.0, 0.0) # max p = ConvergenceChecker(maximize) assert not p.eligible_to_branch(-inf, -inf) assert not p.eligible_to_branch(-inf, inf) assert not p.eligible_to_branch(-inf, 0.0) assert p.eligible_to_branch(inf, -inf) assert not p.eligible_to_branch(inf, inf) assert p.eligible_to_branch(inf, 0.0) assert p.eligible_to_branch(0.0, -inf) assert not p.eligible_to_branch(0.0, inf) assert not p.eligible_to_branch(0.0, 0.0) p = ConvergenceChecker(maximize, branch_tolerance=0.1) assert not p.eligible_to_branch(0.1, 0.0) assert p.eligible_to_branch(0.11, 0.0) p = ConvergenceChecker(maximize, branch_tolerance=None) assert p.eligible_to_branch(0.0, 0.0)
def test_check_termination_criteria(self): # min p = ConvergenceChecker(minimize) assert p.check_termination_criteria(inf, None) is \ TerminationCondition.optimality assert p.check_termination_criteria(0, 0) is \ TerminationCondition.optimality assert p.check_termination_criteria(0, 1) is None p = ConvergenceChecker(minimize, objective_stop=1) assert p.check_termination_criteria(0, 1.1) is None assert p.check_termination_criteria(0, 1) is \ TerminationCondition.objective_limit p = ConvergenceChecker(minimize, objective_stop=inf) assert p.check_termination_criteria(-inf, inf) is None assert p.check_termination_criteria(-inf, 1000000) is \ TerminationCondition.objective_limit p = ConvergenceChecker(minimize, bound_stop=0) assert p.check_termination_criteria(-0.1, 1) is None assert p.check_termination_criteria(0, 1) is \ TerminationCondition.objective_limit p = ConvergenceChecker(minimize, bound_stop=-inf) assert p.check_termination_criteria(-inf, inf) is None assert p.check_termination_criteria(-10000000, inf) is \ TerminationCondition.objective_limit # max p = ConvergenceChecker(maximize) assert p.check_termination_criteria(-inf, None) is \ TerminationCondition.optimality assert p.check_termination_criteria(0, 0) is \ TerminationCondition.optimality assert p.check_termination_criteria(0, -1) is None p = ConvergenceChecker(maximize, objective_stop=-1) assert p.check_termination_criteria(0, -1.1) is None assert p.check_termination_criteria(0, -1) is \ TerminationCondition.objective_limit p = ConvergenceChecker(maximize, objective_stop=-inf) assert p.check_termination_criteria(inf, -inf) is None assert p.check_termination_criteria(inf, -1000000) is \ TerminationCondition.objective_limit p = ConvergenceChecker(maximize, bound_stop=0) assert p.check_termination_criteria(0.1, -1) is None assert p.check_termination_criteria(0, -1) is \ TerminationCondition.objective_limit p = ConvergenceChecker(maximize, bound_stop=inf) assert p.check_termination_criteria(inf, -inf) is None assert p.check_termination_criteria(1000000, -inf) is \ TerminationCondition.objective_limit
def solve(self, problem, best_objective=None, disable_objective_call=False, absolute_gap=1e-8, relative_gap=1e-4, scale_function=_default_scale, queue_tolerance=0, branch_tolerance=0, comparison_tolerance=0, objective_stop=None, bound_stop=None, node_limit=None, time_limit=None, initialize_queue=None, queue_strategy="bound", log_interval_seconds=1.0, log_new_incumbent=True, log=_notset): """Solve a problem using branch-and-bound. Note ---- Parameters for this function are treated differently depending on whether the process is a worker or dispatcher. For the serial case (no MPI), the single process is both a worker and a dispatcher. For the parallel case, exactly one process is a dispatcher and all processes are workers. A **(W)** in the parameter description indicates that it is only used by worker processes (ignored otherwise). A **(D)** in the parameter description indicates that it is only used by the dispatcher process (ignored otherwise). An **(A)** indicates that it is used by all processes, and it is assumed the same value is provided for each process; otherwise, the behavior is undefined. Parameters ---------- problem : :class:`pybnb.Problem <pybnb.problem.Problem>` An object defining a branch-and-bound problem. best_objective : float, optional Initializes the solve with an assumed best objective. This is the only option used by both worker and dispatcher processes that can be set to a different value on each process. The dispatcher will collect all values and use the best. (default: None) disable_objective_call : bool, optional **(W)** Disables requests for an objective value from subproblems. (default: False) absolute_gap : float, optional **(A)** The maximum absolute difference between the global bound and best objective for the problem to be considered solved to optimality. Setting to `None` will disable this optimality check. (default: 1e-8) relative_gap : float, optional **(A)** The maximum relative difference (absolute difference scaled by `max{1.0,|objective|}`) between the global bound and best objective for the problem to be considered solved to optimality. Setting to `None` will disable this optimality check. (default: 1e-4) scale_function : function, optional **(A)** A function with signature `f(bound, objective) -> float` that returns a positive scale factor used to convert the absolute difference between the bound and objective into a relative difference. The relative difference is compared with the `relative_gap` convergence tolerance to determine if the solver should terminate. The default is equivalent to `max{1.0,|objective|}`. Other examples one could use are `max{|bound|,|objective|}`, `(|bound|+|objective|)/2`, etc. queue_tolerance : float, optional **(A)** The absolute tolerance used when deciding if a node is eligible to enter the queue. The difference between the node bound and the incumbent objective must be greater than this value. The default setting of zero means that nodes whose bound is equal to the incumbent objective are not eligible to enter the queue. Setting this to larger values can be used to control the queue size, but it should be kept small enough to allow absolute and relative optimality tolerances to be met. This option can also be set to `None` to allow nodes with a bound equal to (but not greater than) the incumbent objective to enter the queue. (default: 0) branch_tolerance : float, optional **(A)** The absolute tolerance used when deciding if the computed objective and bound for a node are sufficiently different to branch into the node. The default value of zero means that branching will occur if the bound is not exactly equal to the objective. This option can be set to `None` to enable branching for nodes with a bound and objective that are exactly equal. (default: 0) comparison_tolerance : float, optional **(A)** The absolute tolerance used when deciding if two objective or bound values are sufficiently different to be considered improved or worsened. This tolerance controls when the solver considers a new incumbent objective to be found. It also controls when warnings are output about bounds becoming worse on child nodes. Setting this to larger values can be used to avoid the above solver actions due to insignificant numerical differences, but it is better to deal with these numerical issues by rounding numbers to a reliable precision before returning them from the problem methods. (default: 0) objective_stop : float, optional **(A)** If provided, the solve will terminate when a feasible objective is found that is at least as good as the specified value, and the termination_condition flag on the results object will be set to "objective_limit". If this value is infinite, the solve will terminate as soon as a finite objective is found. (default: None) bound_stop : float, optional **(A)** If provided, the solve will terminate when the global bound on the objective is at least as good as the specified value, and the termination_condition flag on the results object will be set to "objective_limit". If this value is infinite, the solve will terminate as soon as a finite bound is found. (default: None) node_limit : int, optional **(D)** If provided, the solve will begin to terminate once this many nodes have been served from the dispatcher queue, and the termination_condition flag on the results object will be set to "node_limit". (default: None) time_limit : float, optional **(D)** If provided, the solve will begin to terminate once this amount of time has passed, and the termination_condition flag on the results object will be set to "time_limit". Note that the solve may run for an arbitrarily longer amount of time, depending how long worker processes spend completing their final task. (default: None) initialize_queue : :class:`pybnb.dispatcher.DispatcherQueueData`, optional **(D)** Initializes the dispatcher queue with that remaining from a previous solve (obtained by calling :func:`Solver.save_dispatcher_queue` after the solve). If left as None, the queue will be initialized with a single root node created by calling :func:`problem.save_state <pybnb.problem.Problem.save_state>`. (default: None) queue_strategy : :class:`QueueStrategy <pybnb.common.QueueStrategy>` **(D)** Sets the strategy for prioritizing nodes in the central dispatcher queue. See the :class:`QueueStrategy <pybnb.common.QueueStrategy>` enum for the list of acceptable values. This keyword can be assigned one of the enumeration attributes or an equivalent string name. (default: "bound") log_interval_seconds : float, optional **(D)** The approximate time (in seconds) between solver log updates. More time may pass between log updates if no updates have been received from worker processes, and less time may pass if a new incumbent objective is found. (default: 1.0) log_new_incumbent : bool **(D)** Controls whether updates to the best objective are logged immediately (overriding the log interval). Setting this to false can be useful when frequent updates to the incumbent are expected and the additional logging slows down the dispatcher. (default: True) log : logging.Logger, optional **(D)** A log object where solver output should be sent. The default value causes all output to be streamed to the console. Setting to None disables all output. Returns ------- results : :class:`SolverResults` An object storing information about the solve. """ self._reset_local_solve_stats() self._solve_start = self._time() assert (initialize_queue is None) or \ (self.is_dispatcher) if best_objective is None: best_objective = problem.infeasible_objective() results = SolverResults() convergence_checker = ConvergenceChecker( problem.sense(), absolute_gap=absolute_gap, relative_gap=relative_gap, scale_function=scale_function, queue_tolerance=queue_tolerance, branch_tolerance=branch_tolerance, comparison_tolerance=comparison_tolerance, objective_stop=objective_stop, bound_stop=bound_stop) problem.notify_solve_begins(self.comm, self.worker_comm, convergence_checker) root = Node() root.queue_priority = 0 problem.save_state(root) try: if self.is_dispatcher: if initialize_queue is None: root.bound = problem.unbounded_objective() root.objective = best_objective assert root.tree_id is None Node._insert_tree_id(root._data, 0) initialize_queue = DispatcherQueueData( nodes=[Node(data_=root._data.copy())], next_tree_id=1) if log is _notset: log = get_simple_logger() if type(queue_strategy) is \ QueueStrategy: queue_strategy = \ queue_strategy.value self._disp.initialize( best_objective, initialize_queue, queue_strategy, convergence_checker, node_limit, time_limit, log, log_interval_seconds, log_new_incumbent) if not self.is_worker: def handler(signum, frame): #pragma:nocover self._disp.log_warning( "Solve interrupted by user. " "Waiting for current worker " "jobs to complete before " "terminating the solve.") self._disp.termination_condition = \ TerminationCondition.interrupted with MPI_InterruptHandler(handler): tmp = self._disp.serve() else: def handler(signum, frame): #pragma:nocover if self.is_dispatcher: self._disp.log_warning( "Solve interrupted by user. " "Waiting for current worker " "jobs to complete before " "terminating the solve.") self._disp.termination_condition = \ TerminationCondition.interrupted with MPI_InterruptHandler(handler): tmp = self._solve(problem, best_objective, disable_objective_call, convergence_checker, results) if not self.is_dispatcher: self._disp.clear_cache() (results.objective, results.bound, results.termination_condition, self._global_solve_info) = tmp results.nodes = self._global_solve_info.explored_nodes_count self._fill_results(results, convergence_checker) except: #pragma:nocover sys.stderr.write("Exception caught: "+str(sys.exc_info()[1])+"\n") sys.stderr.write("Attempting to shut down, but this may hang.\n") sys.stderr.flush() raise finally: problem.load_state(root) self._wall_time = self._time() - self._solve_start results.wall_time = self._wall_time assert results.solution_status in SolutionStatus,\ str(results) assert results.termination_condition in TerminationCondition,\ str(results) problem.notify_solve_finished(self.comm, self.worker_comm, results) if self.is_dispatcher and \ (log is not None) and \ (not log.disabled): self._disp.log_info("") if results.solution_status in ("feasible", "optimal"): agap = convergence_checker.compute_absolute_gap( results.bound, results.objective) rgap = convergence_checker.compute_relative_gap( results.bound, results.objective) if results.solution_status == "feasible": self._disp.log_info("Feasible solution found") else: if (convergence_checker.absolute_gap is not None) and \ agap <= convergence_checker.absolute_gap: self._disp.log_info("Absolute optimality tolerance met") if (convergence_checker.relative_gap is not None) and \ rgap <= convergence_checker.relative_gap: self._disp.log_info("Relative optimality tolerance met") assert results.solution_status == "optimal" self._disp.log_info("Optimal solution found!") elif results.solution_status == "infeasible": self._disp.log_info("Problem is infeasible") elif results.solution_status == "unbounded": self._disp.log_info("Problem is unbounded") elif results.solution_status == "invalid": #pragma:nocover self._disp.log_info("Problem is invalid") else: assert results.solution_status == "unknown" self._disp.log_info("Status unknown") self._disp.log_info("") self._disp.log_info(str(results)) return results
def test_bound_worsened(self): # min p = ConvergenceChecker(minimize) assert not p.bound_worsened(-inf, -inf) assert p.bound_worsened(-inf, inf) assert p.bound_worsened(-inf, 0.0) assert not p.bound_worsened(inf, -inf) assert not p.bound_worsened(inf, inf) assert not p.bound_worsened(inf, 0.0) assert not p.bound_worsened(0.0, -inf) assert p.bound_worsened(0.0, inf) assert not p.bound_worsened(0.0, 0.0) p = ConvergenceChecker(minimize, comparison_tolerance=0.1) assert not p.bound_worsened(-0.1, 0.0) assert p.bound_worsened(-0.11, 0.0) # max p = ConvergenceChecker(maximize) assert not p.bound_worsened(-inf, -inf) assert not p.bound_worsened(-inf, inf) assert not p.bound_worsened(-inf, 0.0) assert p.bound_worsened(inf, -inf) assert not p.bound_worsened(inf, inf) assert p.bound_worsened(inf, 0.0) assert p.bound_worsened(0.0, -inf) assert not p.bound_worsened(0.0, inf) assert not p.bound_worsened(0.0, 0.0) p = ConvergenceChecker(maximize, comparison_tolerance=0.1) assert not p.bound_worsened(0.1, 0.0) assert p.bound_worsened(0.11, 0.0)
def test_objective_improved(self): # min p = ConvergenceChecker(minimize) assert not p.objective_improved(-inf, -inf) assert p.objective_improved(-inf, inf) assert p.objective_improved(-inf, 0.0) assert not p.objective_improved(inf, -inf) assert not p.objective_improved(inf, inf) assert not p.objective_improved(inf, 0.0) assert not p.objective_improved(0.0, -inf) assert p.objective_improved(0.0, inf) assert not p.objective_improved(0.0, 0.0) p = ConvergenceChecker(minimize, comparison_tolerance=0.1) assert not p.objective_improved(-0.1, 0.0) assert p.objective_improved(-0.11, 0.0) # max p = ConvergenceChecker(maximize) assert not p.objective_improved(-inf, -inf) assert not p.objective_improved(-inf, inf) assert not p.objective_improved(-inf, 0.0) assert p.objective_improved(inf, -inf) assert not p.objective_improved(inf, inf) assert p.objective_improved(inf, 0.0) assert p.objective_improved(0.0, -inf) assert not p.objective_improved(0.0, inf) assert not p.objective_improved(0.0, 0.0) p = ConvergenceChecker(maximize, comparison_tolerance=0.1) assert not p.objective_improved(0.1, 0.0) assert p.objective_improved(0.11, 0.0)