Coverage for moptipyapps / tsp / fea1p1_revn.py: 74%
69 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 04:40 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 04:40 +0000
1"""
2A (1+1) FEA for the TSP using the reversal move.
4A (1+1) FEA is the same as the (1+1) EA with Frequency Fitness Assignment.
5The (1+1) EA using the same search operator as this algorithm here is
6implemented in module :mod:`~moptipyapps.tsp.ea1p1_revn`.
7The algorithm implemented here is the same as the basic (1+1) FEA with `rev`
8operator in the paper [1].
10The original version of this code has been contributed by Mr. Tianyu LIANG
11(梁天宇), <liangty@stu.hfuu.edu.cn> a Master's student at the Institute of
12Applied Optimization (应用优化研究所) of the School
13of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei
14University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the
15supervision of Prof. Dr. Thomas Weise (汤卫思教授).
171. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, and Thomas Weise.
18 Solving the Traveling Salesperson Problem using Frequency Fitness
19 Assignment. In Hisao Ishibuchi, Chee-Keong Kwoh, Ah-Hwee Tan, Dipti
20 Srinivasan, Chunyan Miao, Anupam Trivedi, and Keeley A. Crockett, editors,
21 *Proceedings of the IEEE Symposium on Foundations of Computational
22 Intelligence (IEEE FOCI'22)*, part of the *IEEE Symposium Series on
23 Computational Intelligence (SSCI 2022)*. December 4-7, 2022, Singapore,
24 pages 360-367. IEEE. https://doi.org/10.1109/SSCI51031.2022.10022296.
252. Pedro Larrañaga, Cindy M. H. Kuijpers, Roberto H. Murga, Iñaki Inza, and
26 S. Dizdarevic. Genetic Algorithms for the Travelling Salesman Problem: A
27 Review of Representations and Operators. *Artificial Intelligence Review,*
28 13(2):129-170, April 1999. Kluwer Academic Publishers, The Netherlands.
29 https://doi.org/10.1023/A:1006529012972.
30"""
32from typing import Callable, Final, cast
34import numba # type: ignore
35import numpy as np
36from moptipy.algorithms.so.ffa.ffa_h import log_h
37from moptipy.api.algorithm import Algorithm
38from moptipy.api.process import Process
39from moptipy.utils.logger import KeyValueLogSection
40from moptipy.utils.nputils import DEFAULT_INT
41from numpy.random import Generator
42from pycommons.types import type_error
44from moptipyapps.tsp.instance import Instance
45from moptipyapps.utils.shared import SCOPE_INSTANCE
48@numba.njit(nogil=True, cache=True, inline="always", fastmath=False,
49 boundscheck=False)
50def rev_if_h_not_worse(i: int, j: int, n_cities: int, dist: np.ndarray,
51 h: np.ndarray, x: np.ndarray, y: int) -> int:
52 """
53 Apply a reversal move if its tour length frequency is not worse.
55 :param i: the first, smaller index
56 :param j: the second, larger index
57 :param n_cities: the number of cities
58 :param dist: the problem instance
59 :param h: the frequency table
60 :param x: the candidate solution
61 :param y: the tour length
62 """
63 xi: Final[int] = x[i] # the value of x at index i
64 xim1: Final[int] = x[i - 1] # the value of x at index i-1, wraps at 0
65 xj: Final[int] = x[j] # the value of x at index j
66 xjp1: Final[int] = x[(j + 1) % n_cities] # x[j + 1], but index wrapped
68 # compute the difference in tour length if we would apply the move
69 dy: Final[int] = (dist[xim1, xj] + dist[xi, xjp1]
70 - dist[xim1, xi] - dist[xj, xjp1])
71 y2: Final[int] = int(y + dy) # and compute the new tour length
72 h[y] += 1 # update frequency of the tour length of the current solution
73 h[y2] += 1 # update frequency of the tour length of the new solution
74 if h[y2] <= h[y]: # this move does not make the frequency worse?
75 # so we reverse the sequence from i to j in the solution
76 if i == 0: # deal with the special case that i==0
77 x[0:j + 1:1] = x[j::-1]
78 else: # the normal case that i > 0
79 x[i:j + 1:1] = x[j:i - 1:-1]
80 return y2 # return new tour length
81 return y # return old tour length
84class TSPFEA1p1revn(Algorithm):
85 """A (1+1) FEA using the reversal operator for the TSP."""
87 def __init__(self, instance: Instance,
88 do_log_h: bool = False) -> None:
89 """
90 Initialize the RLS algorithm for the TSP with reversal move.
92 :param instance: the problem instance
93 :param do_log_h: shall we log the `h`-table?
94 """
95 super().__init__()
96 if not isinstance(instance, Instance):
97 raise type_error(instance, "instance", Instance)
98 #: the instance
99 self.instance: Final[Instance] = instance
100 #: shall we log the `h`-table`?
101 self.do_log_h: Final[bool] = do_log_h
103 def solve(self, process: Process) -> None:
104 """
105 Apply a (1+1) FEA optimization process with reversing operator.
107 :param process: the process instance which provides random numbers,
108 functions for creating, copying, and evaluating solutions, as well
109 as the termination criterion
110 """
111 random: Final[Generator] = process.get_random()
112 # set up the fast calls
113 register: Final[Callable[[np.ndarray, int], int]] =\
114 cast("Callable[[np.ndarray, int], int]", process.register)
115 should_terminate: Final[Callable[[], bool]] = process.should_terminate
116 ri: Final[Callable[[int], int]] = random.integers
118 instance: Final[Instance] = self.instance # get the instance
119 h: Final[np.ndarray] = np.zeros( # allocate the frequency table
120 instance.tour_length_upper_bound + 1, DEFAULT_INT)
121 n: Final[int] = instance.n_cities # get the number of cities
122 x: Final[np.ndarray] = process.create() # create the solution
123 x[:] = range(n) # fill array with 0..n
124 random.shuffle(x) # randomly generate an initial solution
126 y: int = cast("int", process.evaluate(x)) # get length of first tour
127 nm1: Final[int] = n - 1 # need n-1 in the loop for the random numbers
128 nm2: Final[int] = n - 2 # we need this to check the move indices
129 while not should_terminate():
130 i = ri(nm1) # get the first index
131 j = ri(nm1) # get the second index
132 if i > j: # ensure that i <= j
133 i, j = j, i # swap indices if i > j
134 if (i == j) or ((i == 0) and (j == nm2)):
135 continue # either a nop or a complete reversal
136 y = rev_if_h_not_worse(i, j, n, instance, h, x, y) # move
137 register(x, y) # register the objective value
139 if self.do_log_h:
140 # we log the frequency table at the very end of the run
141 if h[y] == 0:
142 h[y] = 1
143 log_h(process, h, 0)
145 def __str__(self):
146 """
147 Get the name of this algorithm.
149 This name is then used in the directory path and file name of the
150 log files.
152 :returns: "tsp_fea1p1_revn"
153 :retval "tsp_fea1p1_revn": always
154 """
155 return "tsp_fea1p1_revn"
157 def log_parameters_to(self, logger: KeyValueLogSection) -> None:
158 """
159 Log the parameters of the algorithm to the given logger.
161 :param logger: the logger for the parameters
162 """
163 super().log_parameters_to(logger)
164 logger.key_value("doLogH", self.do_log_h)
165 with logger.scope(SCOPE_INSTANCE) as kv:
166 self.instance.log_parameters_to(kv)