Пример #1
0
    def _crossover_ox(self, parent1, parent2):
        """Performs order crossover to create a child path from two given
        parent paths.

        :param Path parent1: First parent path.
        :param Path parent2: Second parent path.
        :return: Child path.
        :rtype: Path
        """

        # Initial child path
        child = Path(len(parent1))

        # Copy random subpath from parent 1 to child
        start, end = self._rand_subpath()
        subpath = parent1.path[start:end + 1]
        tmp = parent2.path

        # Rotate tmp with pivot in the end + 1
        tmp = tmp[end + 1:] + tmp[:end + 1]
        # Remove cities found in subpath from parent 2
        tmp = list(filter(lambda x: x not in subpath, tmp))

        # Join subpath and tmp to form a child
        child.path = subpath + tmp

        # Rotate the path so it always starts at 0
        last_zero_idx = len(child) - child[::-1].index(0) - 1
        child.path = child[last_zero_idx:] + child[:last_zero_idx]

        child.distance = self.tsp.path_dist(child)
        return child
Пример #2
0
 def _test_crossover(self, fun, expected, mock_rand_subpath, mock_dist):
     for (p1, p2, subpath), exp in zip(self.data, expected):
         with self.subTest(p1=p1, p2=p2, subpath=subpath):
             mock_rand_subpath.return_value = subpath
             self.gasolver.tsp.specification['DIMENSION'] = len(p1) - 1
             parent1 = Path(path=p1)
             parent2 = Path(path=p2)
             child = fun(parent1, parent2)
             self.assertListEqual(child.path, exp)
Пример #3
0
    def test_set_path(self):
        data = [[1, 2, 3, 4, 5, 6], [9, 20, 1, 5, 55, 7], [0, 0, 0, 0, 0, 0]]

        path = Path(6)

        for p in data:
            with self.subTest(path=p):
                path.path = p
                self.assertListEqual(path._path, p)
Пример #4
0
    def test_set_path_exception(self):
        data = [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6, 7], [1], []]

        path = Path(6)

        for p in data:
            with self.subTest(path=p):
                with self.assertRaises(ValueError):
                    path.path = p
Пример #5
0
    def test_invert(self):
        data = [(1, 3, [1, 4, 3, 2, 5, 6]), (0, 4, [5, 2, 3, 4, 1, 6]),
                (2, 5, [5, 2, 6, 1, 4, 3]), (5, 0, [3, 4, 1, 6, 2, 5]),
                (2, 3, [3, 4, 6, 1, 2, 5]), (4, 4, [3, 4, 6, 1, 2, 5])]

        path = Path(path=[1, 2, 3, 4, 5, 6])

        for i, j, expected in data:
            with self.subTest(i=i, j=j):
                path.invert(i, j)
                self.assertListEqual(path.path, expected)
Пример #6
0
    def test_move(self):
        data = [(Neighbourhood.SWAP, 1, 4, [1, 5, 3, 4, 2, 6]),
                (Neighbourhood.INSERT, 1, 4, [1, 3, 4, 2, 5, 6]),
                (Neighbourhood.INVERT, 1, 4, [1, 5, 2, 4, 3, 6])]

        path = Path(path=[1, 2, 3, 4, 5, 6])

        for neigh, i, j, expected in data:
            with self.subTest(neigh=neigh, i=i, j=j):
                path.move(neigh, i, j)
                self.assertListEqual(path.path, expected)
Пример #7
0
    def test_in_path(self):
        data = [(4, None, True), (2, None, True), (7, None, True),
                (3, None, False), (7, 6, True), (4, 1, True), (8, 3, True),
                (4, 0, False), (7, 5, False), (10, 3, False)]

        path = Path(path=[4, 1, 8, 2, 9, 7])

        for city, limit, expected in data:
            with self.subTest(city=city, limit=limit):
                result = path.in_path(city, limit)
                self.assertEqual(result, expected)
Пример #8
0
    def test_shuffle(self):
        data = [([0, 1, 2, 3, 4, 5, 6, 7], 2, 5),
                ([0, 1, 2, 3, 3, 5, 6, 0], 1, -1), ([5, 4, 3, 2, 1], 0, 4),
                ([1, 2, 3], 0, 2), ([], 0, 0)]

        for p, i, j in data:
            with self.subTest(path=p, i=i, j=j):
                path = Path(path=deepcopy(p))
                path.shuffle(i, j)

                # Make sure no elements are lost or added
                for n in p:
                    self.assertEqual(path.path.count(n), p.count(n))

                # Compare slices that shouldn't be shuffled
                self.assertListEqual(path[0:i], p[0:i])
                self.assertListEqual(path[j:-1], p[j:-1])
Пример #9
0
    def test_get_stop(self):
        data = [2, 7, 4, 3, 5, 1]

        path = Path(path=data)

        for index, value in enumerate(data):
            with self.subTest(index=index):
                result = path[index]
                self.assertEqual(result, value)
Пример #10
0
    def test_get_stop_exception(self):
        data = [6, 7, 20, -7]

        path = Path(6)

        for index in data:
            with self.subTest(index=index, length=len(path)):
                with self.assertRaises(IndexError):
                    path[index]
Пример #11
0
    def _min_neighbour(self, path):
        """Finds shortest neighbour of the given path.

        :param Path path: Path whose neighbourhood will be searched.
        """

        min_neigh = Path(self.tsp.dimension + 1)
        min_neigh.distance = inf
        best_move = ()

        # Iterate through all possible 2-city moves
        for i, j in product(range(1, self.tsp.dimension), repeat=2):
            # Skip redundant moves
            if self.neighbourhood == Neighbourhood.SWAP or \
                    self.neighbourhood == Neighbourhood.INVERT:
                if j <= i:
                    continue
            if self.neighbourhood == Neighbourhood.INSERT:
                if abs(i - j) == 1 and i > j:
                    continue

            # Skip tabu moves
            if self._tabu[i][j]:
                continue

            # Perform the move
            cur_neigh = deepcopy(path)
            cur_neigh.move(self.neighbourhood, i, j)
            cur_neigh.distance = self.tsp.path_dist(cur_neigh)

            # If resulting path is better than current minimum keep its
            # length and move indexed
            if cur_neigh.distance < min_neigh.distance:
                min_neigh, best_move = cur_neigh, (i, j)

        # Tabu found move
        if best_move:
            self._tabu[best_move[0]][best_move[1]] = self.cadence
            self._tabu[best_move[1]][best_move[0]] = self.cadence

        # In small instances it can happen all neighbours are already on tabu
        # list, if that happens we cannot return an empty path
        return min_neigh if min_neigh.distance != inf else path
Пример #12
0
    def _crossover_nwox(self, parent1, parent2):
        """Performs non wrapping order crossover to create a child path from
        two given parents paths.

        :param Path parent1: First parent path.
        :param Path parent2: Second parent path.
        :return: Child path.
        :rtype: Path
        """

        # Initial child path
        child = Path(self.tsp.dimension + 1)

        # Copy random subpath from parent 1 to child
        start, end = self._rand_subpath()
        child[start:end + 1] = parent1[start:end + 1]

        # Fill in child's empty slots with cities from parent 2 in order
        parent_pos = child_pos = 0
        while parent_pos < self.tsp.dimension + 1:
            # Skip already filled subpath
            if start <= child_pos <= end:
                child_pos = end + 1
                continue

            # Get city from parent path
            city = parent2[parent_pos]

            if child.in_path(city):
                # If this city is already in child path then go to next one
                parent_pos += 1
                continue
            else:
                # Otherwise add it to child path and go to next
                child[child_pos] = city
                child_pos += 1
                parent_pos += 1

        # Add return to 0 if last stop is empty
        child[-1] = child[-1] if child[-1] != -1 else 0

        child.distance = self.tsp.path_dist(child)
        return child
Пример #13
0
    def test_from_path(self):
        data = [([0, 1, 2, 3, 4, 5, 6, 0], [1, 2, 3, 4, 5, 6, 7]),
                ([0, 1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6, 7]),
                ([9, 8, 7, 6, 5, 4], [10, 9, 8, 7, 6, 5]),
                ([3, 2, 1, 0, 6, 5, 4, 3], [4, 3, 2, 1, 7, 6, 5])]

        for path, expected in data:
            with self.subTest(path=path):
                p = Path(path=path)
                result = TSPLibTour.from_path(p).tour
                self.assertListEqual(result, expected)
Пример #14
0
    def test_from_tour(self):
        data = [([1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 0]),
                ([1, 2, 3, 4, 5, 6, 1], [0, 1, 2, 3, 4, 5, 0]),
                ([5, 1, 3, 4, 2], [4, 0, 2, 3, 1, 4])]

        for tour, expected in data:
            with self.subTest(tour=tour):
                tsplibtour = TSPLibTour()
                tsplibtour.tour = tour
                path = Path.from_tour(tsplibtour)
                self.assertListEqual(path.path, expected)
Пример #15
0
    def _crossover_pmx(self, parent1, parent2):
        """Performs partially matched crossover to create a child path from two
        given parent paths.

        :param Path parent1: First parent path.
        :param Path parent2: Second parent path.
        :return: Child path.
        :rtype: Path
        """

        # Starting path
        child = Path(len(parent1))

        # Copy random subpath from parent 1 to child and create mapping
        start, end = self._rand_subpath()
        child[start:end + 1] = parent1[start:end + 1]

        # Create mapping
        mapping = dict(zip(parent1[start:end + 1], parent2[start:end + 1]))

        # Copy stops from parent 2 to child using mapping if necessary
        child_pos = 0
        while child_pos < self.tsp.dimension + 1:
            # Skip already filled subpath
            if start <= child_pos <= end:
                child_pos = end + 1
                continue

            # Get city at current stop in parent 2
            city = parent2[child_pos]

            # Trace mapping if it exists
            while city in mapping:
                city = mapping[city]

            # Set stop in the child path
            child[child_pos] = city
            child_pos += 1

        child.distance = self.tsp.path_dist(child)
        return child
Пример #16
0
    def test_path_dist(self):
        data = [([0, 1, 2, 3], 2 + 7 + 12), ([2, 0, 1, 3], 9 + 2 + 8),
                ([0, 3, 1, 0], 4 + 14 + 5), ([1, 1, 1, 1], 0),
                ([-1, -1, -1, -1], 0)]

        self.tsp.distances = self.distances

        for p, expected in data:
            with self.subTest(p=p):
                path = Path(path=p)
                result = self.tsp.path_dist(path)
                self.assertEqual(result, expected)
Пример #17
0
    def test_path_dist(self):
        data = [([0, 1, 2, 3, 4, 5], 22), ([5, 4, 3, 2, 1, 0], 22),
                ([5, 2, 0, 1, 4, 2], 34), ([0, 1, 2, 3, 4, 0], 27),
                ([5, 4, 3], 12), ([0, 0, 0, 0, 0, 0], 0),
                ([-1, -1, -1, -1, -1, -1], 0)]

        self.tsp.load('tests/fixtures/test6.tsp')

        for p, expected in data:
            with self.subTest(path=p):
                path = Path(path=p)
                result = self.tsp.path_dist(path)
                self.assertEqual(result, expected)
Пример #18
0
    def test_set_stop(self):
        data = [(1, 5, [-1, 5, -1, -1, -1, -1]), (0, 0, [0, 5, -1, -1, -1,
                                                         -1]),
                (5, 9, [0, 5, -1, -1, -1, 9]), (2, 3, [0, 5, 3, -1, -1, 9]),
                (3, 7, [0, 5, 3, 7, -1, 9]), (4, 6, [0, 5, 3, 7, 6, 9]),
                (0, 4, [4, 5, 3, 7, 6, 9]), (-1, 8, [4, 5, 3, 7, 6, 8])]

        path = Path(6)

        for index, city, expected in data:
            with self.subTest(index=index, city=city):
                path[index] = city
                self.assertListEqual(path._path, expected)
Пример #19
0
    def solve(self, tsp, steps=True):
        # Make sure given argument is of correct type
        if not isinstance(tsp, TSP):
            raise TypeError('solve() argument has to be of type \'TSP\'')
        self.tsp = tsp

        # Path will always start and end in 0
        path = Path(self.tsp.dimension + 1)
        path[0] = path[-1] = 0

        # Start timer
        self._start_timer()

        # For each stop except the first and last one
        for i in range(1, len(path) - 1):
            prev = path[i - 1]
            min_dist = inf

            # Check all connections to different cities
            for j in range(self.tsp.dimension):
                # Skip cities that already are in path
                if path.in_path(j, i):
                    continue

                # Keep the new distance if it's lower than current minimum
                new_dist = self.tsp.dist(prev, j)
                if new_dist < min_dist:
                    min_dist = new_dist
                    path[i] = j

            if steps:
                progress = i / (len(path) - 1)
                yield SolverState(self._time(), progress, deepcopy(path), None)

        path.distance = self.tsp.path_dist(path)
        yield SolverState(self._time(), 1, None, deepcopy(path), True)
Пример #20
0
    def _init_population(self):
        """Initializes population by creating specified number of random paths.
        """

        self._population.clear()
        for _ in range(self.population_size):
            path = Path(self.tsp.dimension + 1)
            path.path = list(range(self.tsp.dimension)) + [0]
            path.shuffle(1, -1)
            path.distance = self.tsp.path_dist(path)
            self._population.append(path)

        self._population.sort(key=lambda p: p.distance)
Пример #21
0
    def solve(self, tsp, steps=True):
        # Make sure given argument is of correct type
        if not isinstance(tsp, TSP):
            raise TypeError('solve() argument has to be of type \'TSP\'')
        self.tsp = tsp

        # Create starting path: 0, 1, 2, ..., 0, this path will be permuted
        path = Path(self.tsp.dimension + 1)
        path.path = list(range(len(path) - 1)) + [0]
        path.distance = self.tsp.path_dist(path)
        # Best path
        min_path = deepcopy(path)
        # Create permutations skipping the last stop (return to 0)
        perms = permutations(path.path[1:-1])

        if steps:
            total = factorial(self.tsp.dimension - 1)

        # Start the timer
        self._start_timer()

        # Loop through all permutations to find the shortest path
        for i, perm in enumerate(perms):
            path.path = [0] + list(perm) + [0]
            path.distance = self.tsp.path_dist(path)

            if path.distance < min_path.distance:
                min_path = deepcopy(path)

            if steps:
                # Need to use deepcopies because object could change before the
                # reference will be used
                yield SolverState(self._time(), i / total, deepcopy(path),
                                  deepcopy(min_path))

        yield SolverState(self._time(), 1, None, min_path, True)
Пример #22
0
 def test_init(self):
     for i in range(10):
         result = Path(i)
         self.assertListEqual(result._path, [-1] * i)
         self.assertEqual(len(result), i)
         self.assertEqual(result.distance, -1)
Пример #23
0
    def solve(self, tsp, steps=True):
        # Make sure given argument is of correct type
        if not isinstance(tsp, TSP):
            raise TypeError('solve() argument has to be of type \'TSP\'')
        self.tsp = tsp

        # Total number of iterations or time for calculating progress
        if steps:
            current = 0
            iters = log(self.end_temp / self.init_temp, 1 - self.cooling_rate)
            total = self.run_time if self.run_time else iters

        # Start with random path
        cur_path = Path(self.tsp.dimension + 1)
        cur_path.path = list(range(len(cur_path) - 1)) + [0]
        cur_path.shuffle(1, -1)
        cur_path.distance = self.tsp.path_dist(cur_path)

        # And set it as current minimum
        min_path = deepcopy(cur_path)

        # Start the timer
        self._start_timer()

        # Init temperature
        temp = self.init_temp
        # Repeat as long as system temperature is higher than minimum
        while True:
            # Update iteration counter ro time counterif running in step mode
            if steps:
                current = self._time_ms() if self.run_time else current + 1

            # Get random neighbour of current path
            new_path = self._rand_neigh(cur_path)

            # Difference between current and new path
            delta_dist = new_path.distance - cur_path.distance

            # If it's shorter or equal
            if delta_dist <= 0:
                # If it's shorter set it as current minimum
                if new_path.distance < min_path.distance:
                    min_path = deepcopy(new_path)
                # Set new path as current path
                cur_path = deepcopy(new_path)
            elif exp(-delta_dist / temp) > random():
                # If path is longer accept it with random probability
                cur_path = deepcopy(new_path)

            # Cooling down
            temp *= 1 - self.cooling_rate

            # Terminate search after reaching end temperature
            if not self.run_time and temp < self.end_temp:
                break

            # Terminate search after exceeding specified runtime
            # We use `total` to not have to convert to nanoseconds every time
            if self.run_time and self._time_ms() >= self.run_time:
                break

            # Report current solver state
            if steps:
                yield SolverState(self._time(), current / total,
                                  deepcopy(new_path), deepcopy(min_path))

        yield SolverState(self._time(), 1, None, deepcopy(min_path), True)
Пример #24
0
    def solve(self, tsp, steps=True):
        # Make sure given argument is of correct type
        if not isinstance(tsp, TSP):
            raise TypeError('solve() argument has to be of type \'TSP\'')
        self.tsp = tsp

        # Total and current number of steps for calculating progress
        if steps:
            total = factorial(self.tsp.dimension - 1) * 2
            current = 0

        # Working path
        path = Path(self.tsp.dimension + 1)
        path[-1] = 0
        # Minimal path and distance
        min_path = Path(self.tsp.dimension + 1)
        min_path.distance = inf
        # Nodes list (used as a stack)
        stack = []

        # Add starting city (0) to the stack
        stack.append((0, 0, 0))

        # Start the timer
        self._start_timer()

        while len(stack) > 0:
            # Increment step counter
            if steps:
                current += 1

            # Get node from the top of the stack
            cur_node = stack.pop()
            city, dist, level = cur_node
            # Update current path with this node
            path[level] = city
            # This is the level of all children of this node
            next_level = level + 1

            # If it's the last level of the tree
            if level == self.tsp.dimension - 1:
                path.distance = dist + self.tsp.dist(city, 0)
                # Yield the current state
                if steps:
                    yield SolverState(self._time(), current / total,
                                      deepcopy(path), deepcopy(min_path))
                # Distance of full path with return to 0
                # Keep it if it's better than the current minimum
                if path.distance < min_path.distance:
                    min_path = deepcopy(path)
                else:
                    continue

            # Iterate through all cities
            for i in range(self.tsp.dimension):
                # Skip current city itself, its predecessors and starting city
                if i == city or path.in_path(i, next_level) or i == 0:
                    continue

                # Skip this node if its distance is greater than min path
                next_dist = dist + self.tsp.dist(city, i)
                if next_dist >= min_path.distance:
                    continue

                # If it's valid node push it onto stack
                stack.append((i, next_dist, next_level))

        yield SolverState(self._time(), 1, None, deepcopy(min_path), True)