I am solving integer programming problem leveraging CP-SAT solver (ortools). Looking at the logs, I notice that the solver finds the solution that actually is optimal (proven later), but spends a lot of time in proving optimality. Essentially, the gap between best incumbent solution and the best bound closes down slowly.
I tried outputting the feasible solution if the MIP gap % : (abs(objective - best_bound) / objective) < 0.05, but it did not help. Since the convergence is slow.
from ortools.sat.python import cp_model as cp
import time
class PrinterClass(cp.CpSolverSolutionCallback):
def __init__(self):
cp.CpSolverSolutionCallback.__init__(self)
self.__solution_count = 0
self.__start_time = time.time()
def on_solution_callback(self):
current_time = time.time()
objective = self.ObjectiveValue()
best_bound = self.BestObjectiveBound()
print("Solution %i, time = %f s, objective = %i, best_bound = %i" %
(self.__solution_count, current_time - self.__start_time,
objective, best_bound))
self.__solution_count += 1
if (abs(objective - best_bound) / objective) < 0.05:
print('Stop search')
self.StopSearch()
def total_num_solutions(self):
return self.__solution_count
What I want to try is : if the best incumbent solution does not change for 10 seconds (say), then I want to output this particular feasible solution and stop the search. Instead of solver spending 200 seconds (say) trying to close the gap between the incumbent solution and the best bound.
E.g. if you look at the log below, the solver finds a feasible solution (that is actually optimal, proven later), solution #13 at 2.75 secs,
Solution 13, time = 2.745947 s, objective = 2032, best_bound = 1479
and then spends ~25 secs more to prove that its actually optimal, can I stop the search and get the solution when the solver spends additional 10 secs and is not able to improve upon the current incumbent solution. At below point
#Bound 12.14s best:2032 next:[1532,2030] pseudo_costs
I just want to try this out, at least for my learning, can somebody help with this ?
Below is the log generated :
Starting Search at 0.28s with 8 workers.
6 full subsolvers: [default_lp, no_lp, max_lp, reduced_costs, pseudo_costs, quick_restart]
Interleaved subsolvers: [feasibility_pump, rnd_var_lns_default, rnd_cst_lns_default, graph_var_lns_default, graph_cst_lns_default, routing_random_lns_default, routing_path_lns_default, routing_full_path_lns_default, rins_lns_default, rens_lns_default]
#1 0.44s best:4884 next:[0,4883] no_lp fixed_bools:1/931
Solution 0, time = 0.435596 s, objective = 4884, best_bound = 0
#Bound 0.45s best:4884 next:[0,4882] no_lp
#2 0.46s best:4336 next:[0,4335] no_lp fixed_bools:1/935
Solution 1, time = 0.466841 s, objective = 4336, best_bound = 0
#Bound 0.47s best:4336 next:[0,4334] no_lp
#Bound 0.48s best:4336 next:[1278,4334] max_lp initial_propagation
#3 0.54s best:3972 next:[1278,3971] no_lp fixed_bools:1/939
Solution 2, time = 0.544969 s, objective = 3972, best_bound = 1278
#Bound 0.55s best:3972 next:[1278,3970] no_lp
#4 0.55s best:3492 next:[1278,3491] routing_random_lns_default(d=0.50 s=12 t=0.10 p=0.00)
Solution 3, time = 0.560597 s, objective = 3492, best_bound = 1278
#Bound 0.56s best:3492 next:[1278,3490] no_lp
#5 0.57s best:2968 next:[1278,2967] no_lp fixed_bools:1/944
Solution 4, time = 0.576220 s, objective = 2968, best_bound = 1278
#Bound 0.58s best:2968 next:[1278,2966] no_lp
#6 0.62s best:2944 next:[1278,2943] no_lp fixed_bools:1/949
Solution 5, time = 0.632293 s, objective = 2944, best_bound = 1278
#Bound 0.64s best:2944 next:[1278,2942] no_lp
#Bound 0.65s best:2944 next:[1352,2942] max_lp
#7 0.65s best:2876 next:[1352,2875] no_lp fixed_bools:1/950
Solution 6, time = 0.663560 s, objective = 2876, best_bound = 1352
#Bound 0.66s best:2876 next:[1352,2874] no_lp
#8 0.67s best:2648 next:[1352,2647] no_lp fixed_bools:1/954
Solution 7, time = 0.679179 s, objective = 2648, best_bound = 1352
#Bound 0.68s best:2648 next:[1352,2646] no_lp
#9 0.70s best:2420 next:[1352,2419] no_lp fixed_bools:1/957
Solution 8, time = 0.710431 s, objective = 2420, best_bound = 1352
#Bound 0.71s best:2420 next:[1352,2418] no_lp
#10 0.72s best:2396 next:[1352,2395] no_lp fixed_bools:1/958
Solution 9, time = 0.726056 s, objective = 2396, best_bound = 1352
#Bound 0.73s best:2396 next:[1352,2394] no_lp
#Bound 0.91s best:2396 next:[1356,2394] pseudo_costs
#11 0.94s best:2260 next:[1356,2259] no_lp fixed_bools:1/960
Solution 10, time = 0.960437 s, objective = 2260, best_bound = 1356
#Bound 0.96s best:2260 next:[1399,2259] max_lp
#Bound 0.97s best:2260 next:[1399,2258] no_lp
#12 0.98s best:2192 next:[1399,2191] no_lp fixed_bools:1/960
Solution 11, time = 0.991691 s, objective = 2192, best_bound = 1399
#Bound 0.99s best:2192 next:[1399,2190] no_lp
#Bound 1.46s best:2192 next:[1450,2190] reduced_costs
#Bound 1.87s best:2192 next:[1461,2190] reduced_costs
#13 2.18s best:2168 next:[1461,2167] default_lp fixed_bools:1/931
Solution 12, time = 2.183427 s, objective = 2168, best_bound = 1461
#Bound 2.19s best:2168 next:[1461,2166] no_lp
#Bound 2.24s best:2168 next:[1473,2166] pseudo_costs
#Bound 2.50s best:2168 next:[1479,2166] pseudo_costs
#14 2.74s best:2032 next:[1479,2031] no_lp fixed_bools:2/968
Solution 13, time = 2.745947 s, objective = 2032, best_bound = 1479
#Bound 2.75s best:2032 next:[1479,2030] no_lp
#Bound 2.83s best:2032 next:[1484,2030] reduced_costs
#Bound 3.23s best:2032 next:[1491,2030] pseudo_costs
#Bound 3.50s best:2032 next:[1493,2030] pseudo_costs
#Bound 3.73s best:2032 next:[1494,2030] pseudo_costs
#Bound 4.89s best:2032 next:[1498,2030] pseudo_costs
#Bound 5.36s best:2032 next:[1500,2030] pseudo_costs
#Bound 8.63s best:2032 next:[1503,2030] pseudo_costs
#Bound 8.85s best:2032 next:[1508,2030] pseudo_costs
#Bound 9.93s best:2032 next:[1514,2030] pseudo_costs
#Bound 10.18s best:2032 next:[1518,2030] pseudo_costs
#Bound 10.80s best:2032 next:[1523,2030] pseudo_costs
#Bound 11.05s best:2032 next:[1527,2030] pseudo_costs
#Bound 11.25s best:2032 next:[1531,2030] pseudo_costs
#Bound 12.14s best:2032 next:[1532,2030] pseudo_costs
......
......
......
#Bound 26.09s best:2032 next:[1647,2030] pseudo_costs
#Bound 26.60s best:2032 next:[1719,2030] pseudo_costs
#Bound 26.76s best:2032 next:[1720,2030] pseudo_costs
#Bound 26.94s best:2032 next:[1724,2030] pseudo_costs
#Done 26.94s pseudo_costs
#Done 26.96s default_lp
You should read this thread.
In particular, the callback code from stradivari96@
from threading import Timer
from ortools.sat.python import cp_model
class ObjectiveEarlyStopping(cp_model.CpSolverSolutionCallback):
def __init__(self, timer_limit: int):
super(ObjectiveEarlyStopping, self).__init__()
self._timer_limit = timer_limit
self._timer = None
self._reset_timer()
def on_solution_callback(self):
self._reset_timer()
def _reset_timer(self):
if self._timer:
self._timer.cancel()
self._timer = Timer(self._timer_limit, self.StopSearch)
self._timer.start()
def StopSearch(self):
print(f"{self._timer_limit} seconds without improvement")
super().StopSearch()