def update_stats_for_method_pair(self, destroy: Method, repair: Method, sol: Solution, res: Result, obj_old: TObj, t_destroy: float, t_repair: float): """Update statistics, incumbent and check termination condition after having performed a destroy+repair.""" if __debug__ and self.own_settings.mh_checkit: sol.check() ms_destroy = self.method_stats[destroy.name] ms_destroy.applications += 1 ms_destroy.netto_time += t_destroy ms_destroy.brutto_time += t_destroy ms_repair = self.method_stats[repair.name] ms_repair.applications += 1 ms_repair.netto_time += t_repair ms_repair.brutto_time += t_repair obj_new = sol.obj() if sol.is_better_obj(sol.obj(), obj_old): ms_destroy.successes += 1 ms_destroy.obj_gain += obj_new - obj_old ms_repair.successes += 1 ms_repair.obj_gain += obj_new - obj_old self.iteration += 1 new_incumbent = self.update_incumbent(sol, time.process_time() - self.time_start) terminate = self.check_termination() self.log_iteration(destroy.name+'+'+repair.name, obj_old, sol, new_incumbent, terminate, res.log_info) if terminate: self.run_time = time.process_time() - self.time_start res.terminate = True
def metropolis_criterion(self, sol_new: Solution, sol_current: Solution) -> bool: """Apply Metropolis criterion as acceptance decision, return True when sol_new should be accepted.""" if sol_new.is_better(sol_current): return True return np.random.random_sample() <= exp( -abs(sol_new.obj() - sol_current.obj()) / self.temperature)
def perform_method(self, method: Method, sol: Solution, delayed_success=False) -> Result: """Perform method on given solution and returns Results object. Also updates incumbent, iteration and the method's statistics in method_stats. Furthermore checks the termination condition and eventually sets terminate in the returned Results object. :param method: method to be performed :param sol: solution to which the method is applied :param delayed_success: if set the success is not immediately determined and updated but at some later call of delayed_success_update() :returns: Results object """ res = Result() obj_old = sol.obj() ##### logging for visualisation if self.step_logger.hasHandlers(): sol_str, inc_str = f'{sol}'.replace( '\n', ' '), f'{self.incumbent}'.replace('\n', ' ') step_info = f'START\nSOL: {sol_str}\nOBJ: {obj_old}\nM: {method.name}\n' + \ f'PAR: {method.par}\nINC: {inc_str}\nBEST: {self.incumbent.obj()}' self.step_logger.info(step_info) ################# t_start = time.process_time() method.func(sol, method.par, res) t_end = time.process_time() if __debug__ and self.own_settings.mh_checkit: sol.check() ms = self.method_stats[method.name] ms.applications += 1 ms.netto_time += t_end - t_start obj_new = sol.obj() if not delayed_success: ms.brutto_time += t_end - t_start if sol.is_better_obj(sol.obj(), obj_old): ms.successes += 1 ms.obj_gain += obj_new - obj_old self.iteration += 1 new_incumbent = self.update_incumbent(sol, t_end - self.time_start) ##### logging for visualisation if self.step_logger.hasHandlers(): sol_str, inc_str = f'{sol}'.replace( '\n', ' '), f'{self.incumbent}'.replace('\n', ' ') step_info = f'END\nSOL: {sol_str}\nOBJ: {sol.obj()}\nM: {method.name}\n' + \ f'PAR: {method.par}\nINC: {inc_str}\nBEST: {self.incumbent.obj()}\nBETTER: {new_incumbent}' self.step_logger.info(step_info) ################# terminate = self.check_termination() self.log_iteration(method.name, obj_old, sol, new_incumbent, terminate, res.log_info) if terminate: self.run_time = time.process_time() - self.time_start res.terminate = True return res
def delayed_success_update(self, method: Method, obj_old: TObj, t_start: float, sol: Solution): """Update an earlier performed method's success information in method_stats. :param method: earlier performed method :param obj_old: objective value of solution with which to compare to determine success :param t_start: time when the application of method had started :param sol: current solution considered the final result of the method """ t_end = time.process_time() ms = self.method_stats[method.name] ms.brutto_time += t_end - t_start obj_new = sol.obj() if sol.is_better_obj(sol.obj(), obj_old): ms.successes += 1 ms.obj_gain += obj_new - obj_old
def gvns(self, sol: Solution): """Perform general variable neighborhood search (GVNS) to given solution.""" sol2 = sol.copy() if self.vnd(sol2) or not self.meths_sh: return use_vnd = bool(self.meths_li) while True: for m in self.next_method(self.meths_sh, repeat=True): t_start = time.process_time() res = self.perform_method(m, sol2, delayed_success=use_vnd) terminate = res.terminate if not terminate and use_vnd: terminate = self.vnd(sol2) self.delayed_success_update(m, sol.obj(), t_start, sol2) if sol2.is_better(sol): sol.copy_from(sol2) if terminate or res.terminate: return break else: if terminate or res.terminate: return sol2.copy_from(sol) else: break
def perform_methods(self, methods: List[Method], sol: Solution) -> Result: """Performs all methods on given solution and returns Results object. Also updates incumbent, iteration and the method's statistics in method_stats. Furthermore checks the termination condition and eventually sets terminate in the returned Results object. :param methods: list of methods to perform :param sol: solution to which the method is applied :returns: Results object """ res = Result() obj_old = sol.obj() method_name = "" for method in methods: if method_name != "": method_name += "+" method_name += method.name method.func(sol, method.par, res) if res.terminate: break t_end = time.process_time() self.iteration += 1 new_incumbent = self.update_incumbent(sol, t_end - self.time_start) terminate = self.check_termination() self.log_iteration(method_name, obj_old, sol, new_incumbent, terminate, res.log_info) if terminate: self.run_time = time.process_time() - self.time_start res.terminate = True return res
def ts(self, sol: Solution): while True: # use of multiple different methods for restricted neighborhood search is possible, # but usually only one is used for m in self.next_method(self.meths_rli, repeat=True): sol_old = sol.copy() def ts_iteration(sol: Solution, _par, result): for ta in self.tabu_list.tabu_list: self.step_logger.info(f'TA: {ta}') m.func(sol, m.par, None, self.tabu_list, self.incumbent) ts_method = Method(m.name, ts_iteration, m.par) t_start = time.process_time() res = self.perform_method(ts_method, sol, delayed_success=True) self.update_tabu_list(sol, sol_old) self.delayed_success_update(m, sol.obj(), t_start, sol_old) for ta in self.tabu_list.tabu_list: self.step_logger.info(f'TA: {ta}') if res.terminate: return
def __init__(self, sol: Solution, meths_ch: List[Method], meths_destroy: List[Method], meths_repair: List[Method], own_settings: dict = None, consider_initial_sol=False): """Initialization. :param sol: solution to be improved :param meths_ch: list of construction heuristic methods :param meths_destroy: list of destroy methods :param meths_repair: list of repair methods :param own_settings: optional dictionary with specific settings :param consider_initial_sol: if true consider sol as valid solution that should be improved upon; otherwise sol is considered just a possibly uninitialized of invalid solution template """ super().__init__(sol, meths_ch + meths_destroy + meths_repair, own_settings, consider_initial_sol) self.meths_ch = meths_ch assert meths_destroy and meths_repair self.meths_destroy = meths_destroy self.meths_repair = meths_repair self.score_data = { m.name: ScoreData() for m in chain(self.meths_destroy, self.meths_repair) } self.temperature = sol.obj( ) * self.own_settings.mh_alns_init_temp_factor + 0.000000001 self.next_segment = 0
def perform_method(self, method: Method, sol: Solution, delayed_success=False) -> Result: """Perform method on given solution and returns Results object. Also updates incumbent, iteration and the method's statistics in method_stats. Furthermore checks the termination condition and eventually sets terminate in the returned Results object. :param method: method to be performed :param sol: solution to which the method is applied :param delayed_success: if set the success is not immediately determined and updated but at some later call of delayed_success_update() :returns: Results object """ res = Result() obj_old = sol.obj() t_start = time.process_time() method.func(sol, method.par, res) t_end = time.process_time() if __debug__ and self.own_settings.mh_checkit: sol.check() ms = self.method_stats[method.name] ms.applications += 1 ms.netto_time += t_end - t_start obj_new = sol.obj() if not delayed_success: ms.brutto_time += t_end - t_start if sol.is_better_obj(sol.obj(), obj_old): ms.successes += 1 ms.obj_gain += obj_new - obj_old self.iteration += 1 new_incumbent = self.update_incumbent(sol, t_end - self.time_start) terminate = self.check_termination() self.log_iteration(method.name, obj_old, sol, new_incumbent, terminate, res.log_info) if terminate: self.run_time = time.process_time() - self.time_start res.terminate = True return res
def perform_method_pair(self, destroy: Method, repair: Method, sol: Solution) -> Result: """Performs a destroy/repair method pair on given solution and returns Results object. Also updates incumbent, iteration and the method's statistics in method_stats. Furthermore checks the termination condition and eventually sets terminate in the returned Results object. :param destroy: destroy destroy method to be performed :param repair: repair destroy method to be performed :param sol: solution to which the method is applied :returns: Results object """ res = Result() obj_old = sol.obj() t_start = time.process_time() destroy.func(sol, destroy.par, res) t_destroyed = time.process_time() repair.func(sol, repair.par, res) t_end = time.process_time() self.update_stats_for_method_pair(destroy, repair, sol, res, obj_old, t_destroyed - t_start, t_end - t_destroyed) return res