Esempio n. 1
0
 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)
Esempio n. 2
0
 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
Esempio n. 3
0
 def copy_from(self, other: 'SubsetVectorSolution'):
     self.sel = other.sel
     if self.unselected_elems_in_x():
         super().copy_from(other)
     else:
         self.x[:self.sel] = other.x[:self.sel]
         Solution.copy_from(self, other)
Esempio n. 4
0
    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
Esempio n. 5
0
 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
Esempio n. 6
0
 def alns(self, sol: Solution):
     """Perform adaptive large neighborhood search (ALNS) on given solution."""
     self.next_segment = self.iteration + self.own_settings.mh_alns_segment_size
     sol_incumbent = sol.copy()
     sol_new = sol.copy()
     while True:
         destroy, repair = self.select_method_pair()
         res = self.perform_method_pair(destroy, repair, sol_new)
         self.update_after_destroy_and_repair_performed(
             destroy, repair, sol_new, sol_incumbent, sol)
         if res.terminate:
             sol.copy_from(sol_incumbent)
             return
         self.update_operator_weights()
Esempio n. 7
0
    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
Esempio n. 8
0
    def update_tabu_list(self, sol: Solution, sol_old: Solution):
        ll = self.tabu_list.generate_list_length(self.iteration) # generate list length for current iteration

        self.step_logger.info(f'LL: {ll}')
        
        if self.incumbent_iteration == self.iteration and self.incumbent.is_tabu(self.tabu_list):
            # a new best solution was found, but it was tabu (aspiration criterion)
            # get the violated tabu attribute and delete it from the list
            tabu_violated = sol_old.get_tabu_attribute(self.incumbent)
            for t in tabu_violated:
                self.tabu_list.delete_attribute({t})

            self.step_logger.info(f'TA_DEL: {tabu_violated}')

        self.tabu_list.update_list() # updates lifespan of each tabu attribute and deletes expired attributes
        self.tabu_list.add_attribute(sol.get_tabu_attribute(sol_old), self.tabu_list.current_ll)
Esempio n. 9
0
    def __new__(cls,
                sol: Solution,
                meths_ch: List[Method],
                own_settings: dict = None):
        """Create population of mh_pop_size solutions using the list of construction heuristics if given.

        If sol is None or no constructors are given, the population is initialized empty.
        sol itself is just used as template for obtaining further solutions.
        """
        own_settings = OwnSettings(own_settings) if own_settings else settings
        size = own_settings.mh_pop_size
        obj = super(Population, cls).__new__(cls, size, Solution)
        obj.own_settings = own_settings
        if sol is not None and meths_ch:
            # cycle through construction heuristics to generate population
            # perform all construction heuristics, take best solution
            meths_cycle = cycle(meths_ch)
            idx = 0
            while idx < size:
                m = next(meths_cycle)
                sol = sol.copy()
                res = Result()
                m.func(sol, m.par, res)
                if own_settings.mh_pop_dupelim and obj.duplicates_of(
                        sol) != []:
                    continue  # do not add this duplicate
                obj[idx] = sol
                if res.terminate:
                    break
                idx += 1
        return obj
Esempio n. 10
0
    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
Esempio n. 11
0
    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
Esempio n. 12
0
 def sa_iteration(sol: Solution, _par, result):
     neighborhood_move, delta_obj = self.random_move_delta_eval(sol)
     acceptance = self.metropolis_criterion(sol, delta_obj)
     if acceptance:
         self.apply_neighborhood_move(sol, neighborhood_move)
         sol.obj_val = sol.obj_val + delta_obj
         result.changed = True
     if self.iter_cb is not None:
         self.iter_cb(self.iteration, sol, self.temperature, acceptance)
Esempio n. 13
0
    def vnd(self, sol: Solution) -> bool:
        """Perform variable neighborhood descent (VND) on given solution.

        :returns: true if a global termination condition is fulfilled, else False.
        """
        sol2 = sol.copy()
        while True:
            for m in self.next_method(self.meths_li):
                res = self.perform_method(m, sol2)
                if sol2.is_better(sol):
                    sol.copy_from(sol2)
                    if res.terminate:
                        return True
                    break
                if res.terminate:
                    return True
                if res.changed:
                    sol2.copy_from(sol)
            else:  # local optimum reached
                return False
Esempio n. 14
0
 def alns(self, sol: Solution):
     """Perform adaptive large neighborhood search (ALNS) on given solution."""
     self.next_segment = self.iteration + self.own_settings.mh_alns_segment_size
     sol_incumbent = sol.copy()
     sol_new = sol.copy()
     operators = self.operators_generator(sol_new)
     worker_seed = 0 if settings.mh_workers > 1 else settings.seed
     with mp.Pool(processes=settings.mh_workers,
                  initializer=self.process_init,
                  initargs=(settings, worker_seed)) as worker_pool:
         result_iter = worker_pool.imap_unordered(
             self.perform_method_pair_in_worker, operators)
         for result in result_iter:
             # print("Result:", result)
             destroy, repair, sol_result, res, obj_old, t_destroy, t_repair = result
             sol_new.copy_from(sol_result)
             self.update_stats_for_method_pair(destroy, repair, sol, res,
                                               obj_old, t_destroy, t_repair)
             self.update_after_destroy_and_repair_performed(
                 destroy, repair, sol_new, sol_incumbent, sol)
             if res.terminate:
                 sol.copy_from(sol_incumbent)
                 return
             self.update_operator_weights()
Esempio n. 15
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()
        ##### 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
Esempio n. 16
0
    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
Esempio n. 17
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
Esempio n. 18
0
    def update_after_destroy_and_repair_performed(self, destroy: Method,
                                                  repair: Method,
                                                  sol_new: Solution,
                                                  sol_incumbent: Solution,
                                                  sol: Solution):
        """Update current solution, incumbent, and all operator score data according to performed destroy+repair.

        :param destroy: applied destroy method
        :param repair: applied repair method
        :param sol_new: obtained new solution
        :param sol_incumbent: current incumbent solution
        :param sol: current (last accepted) solution
        """
        destroy_data = self.score_data[destroy.name]
        repair_data = self.score_data[repair.name]
        destroy_data.applied += 1
        repair_data.applied += 1
        score = 0
        if sol_new.is_better(sol_incumbent):
            score = self.own_settings.mh_alns_sigma1
            # print('better than incumbent')
            sol_incumbent.copy_from(sol_new)
            sol.copy_from(sol_new)
        elif sol_new.is_better(sol):
            score = self.own_settings.mh_alns_sigma2
            # print('better than current')
            sol.copy_from(sol_new)
        elif sol.is_better(sol_new) and self.metropolis_criterion(
                sol_new, sol):
            score = self.own_settings.mh_alns_sigma3
            # print('accepted although worse')
            sol.copy_from(sol_new)
        elif sol_new != sol:
            sol_new.copy_from(sol)
        destroy_data.score += score
        repair_data.score += score