Coverage for moptipyapps / tsp / ea1p1_revn.py: 79%
57 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) EA for the TSP using the reversal move.
4A (1+1) EA is basically a local search :mod:`~moptipy.algorithms.so.rls`
5algorithm that starts with a random solution as the current solution `x`. In
6each iteration, it samples one new solution from the neighborhood of `x` and
7accepts it (as the new `x`), if it is better or equally good.
8The algorithm here applies the reversal move, i.e., a two-opt move, as the
9unary search operator used for sampling the neighborhood of `x`. The
10interesting thing about this operator, when applied to the TSP, is that we
11can compute the tour length of the new solution in `O(1)` from the tour length
12of the current solution. This means we can very quickly decide whether the
13move would be acceptable - and only apply it (in `O(n)`) if its.
15The algorithm implemented here is the same as the basic (1+1) EA with `rev`
16operator in the paper [1].
18The original version of this code has been contributed by Mr. Tianyu LIANG
19(梁天宇), <liangty@stu.hfuu.edu.cn> a Master's student at the Institute of
20Applied Optimization (应用优化研究所) of the School
21of Artificial Intelligence and Big Data (人工智能与大数据学院) at Hefei
22University (合肥大学) in Hefei, Anhui, China (中国安徽省合肥市) under the
23supervision of Prof. Dr. Thomas Weise (汤卫思教授).
251. Tianyu Liang, Zhize Wu, Jörg Lässig, Daan van den Berg, and Thomas Weise.
26 Solving the Traveling Salesperson Problem using Frequency Fitness
27 Assignment. In Hisao Ishibuchi, Chee-Keong Kwoh, Ah-Hwee Tan, Dipti
28 Srinivasan, Chunyan Miao, Anupam Trivedi, and Keeley A. Crockett, editors,
29 *Proceedings of the IEEE Symposium on Foundations of Computational
30 Intelligence (IEEE FOCI'22)*, part of the *IEEE Symposium Series on
31 Computational Intelligence (SSCI 2022)*. December 4-7, 2022, Singapore,
32 pages 360-367. IEEE. https://doi.org/10.1109/SSCI51031.2022.10022296.
332. Pedro Larrañaga, Cindy M. H. Kuijpers, Roberto H. Murga, Iñaki Inza, and
34 S. Dizdarevic. Genetic Algorithms for the Travelling Salesman Problem: A
35 Review of Representations and Operators. *Artificial Intelligence Review,*
36 13(2):129-170, April 1999. Kluwer Academic Publishers, The Netherlands.
37 https://doi.org/10.1023/A:1006529012972.
38"""
40from typing import Callable, Final, cast
42import numba # type: ignore
43import numpy as np
44from moptipy.api.algorithm import Algorithm
45from moptipy.api.process import Process
46from moptipy.utils.logger import KeyValueLogSection
47from numpy.random import Generator
48from pycommons.types import type_error
50from moptipyapps.tsp.instance import Instance
51from moptipyapps.utils.shared import SCOPE_INSTANCE
54@numba.njit(nogil=True, cache=True, inline="always", fastmath=False,
55 boundscheck=False)
56def rev_if_not_worse(i: int, j: int, n_cities: int, dist: np.ndarray,
57 x: np.ndarray, y: int) -> int:
58 """
59 Check a reversal move, apply it if it is better, return new tour length.
61 :param i: the first, smaller index
62 :param j: the second, larger index
63 :param n_cities: the number of cities
64 :param dist: the problem instance
65 :param x: the candidate solution
66 :param y: the tour length
67 """
68 xi: Final[int] = x[i] # the value of x at index i
69 xim1: Final[int] = x[i - 1] # the value of x at index i-1, wraps at 0
70 xj: Final[int] = x[j] # the value of x at index j
71 xjp1: Final[int] = x[(j + 1) % n_cities] # x[j + 1] with index wrap
73 # compute the difference in tour length if we would apply the move
74 dy: Final[int] = (dist[xim1, xj] + dist[xi, xjp1]
75 - dist[xim1, xi] - dist[xj, xjp1])
76 if dy <= 0: # this is not a worsening improving move? ... so apply it
77 # reverse the sequence from i to j in the solution
78 if i == 0: # deal with the special case that i==0
79 x[0:j + 1:1] = x[j::-1]
80 else: # the normal case that i > 0
81 x[i:j + 1:1] = x[j:i - 1:-1]
82 return int(y + dy) # return new tour length
83 return y # return old tour length
86class TSPEA1p1revn(Algorithm):
87 """A (1+1) EA using the reversal operator for the TSP."""
89 def __init__(self, instance: Instance) -> None:
90 """
91 Initialize the RLS algorithm for the TSP with reversal move.
93 :param instance: the problem instance
94 """
95 super().__init__()
96 if not isinstance(instance, Instance):
97 raise type_error(instance, "instance", Instance)
98 self.instance: Final[Instance] = instance
100 def solve(self, process: Process) -> None:
101 """
102 Apply an RLS = (1+1) EA optimization process with reversing operator.
104 :param process: the process instance which provides random numbers,
105 functions for creating, copying, and evaluating solutions, as well
106 as the termination criterion
107 """
108 random: Final[Generator] = process.get_random()
109 # set up the fast calls
110 register: Final[Callable[[np.ndarray, int], int]] =\
111 cast("Callable[[np.ndarray, int], int]", process.register)
112 should_terminate: Final[Callable[[], bool]] = process.should_terminate
113 ri: Final[Callable[[int], int]] = random.integers
115 instance: Final[Instance] = self.instance # get the instance
116 n: Final[int] = instance.n_cities # get the number of cities
117 x: Final[np.ndarray] = process.create() # create the solution
118 x[:] = range(n) # fill array with 0..n
119 random.shuffle(x) # randomly generate an initial solution
121 y: int = cast("int", process.evaluate(x)) # get length of first tour
122 nm1: Final[int] = n - 1 # need n-1 in the loop for the random numbers
123 nm2: Final[int] = n - 2 # we need this to check the move indices
124 while not should_terminate():
125 i = ri(nm1) # get the first index
126 j = ri(nm1) # get the second index
127 if i > j: # ensure that i <= j
128 i, j = j, i # swap indices if i > j
129 if (i == j) or ((i == 0) and (j == nm2)):
130 continue # either a nop or a complete reversal
131 y = rev_if_not_worse(i, j, n, instance, x, y) # apply the move
132 register(x, y) # register the objective value
134 def __str__(self):
135 """
136 Get the name of this algorithm.
138 This name is then used in the directory path and file name of the
139 log files.
141 :returns: "tsp_ea1p1_revn"
142 :retval "tsp_ea1p1_revn": always
143 """
144 return "tsp_ea1p1_revn"
146 def log_parameters_to(self, logger: KeyValueLogSection) -> None:
147 """
148 Log the parameters of the algorithm to the given logger.
150 :param logger: the logger for the parameters
151 """
152 super().log_parameters_to(logger)
153 with logger.scope(SCOPE_INSTANCE) as kv:
154 self.instance.log_parameters_to(kv)