Source code for mrinufft.trajectories.maths.tsp_solver
"""Solver for the Travelling Salesman Problem."""
import numpy as np
from numpy.typing import NDArray
[docs]
def solve_tsp_with_2opt(
    locations: NDArray, improvement_threshold: float = 1e-8
) -> NDArray:
    """Solve approximately the TSP using a 2-opt approach.
    A sub-optimal solution to the Travelling Salesman Problem (TSP)
    is provided using the 2-opt approach in O(n²) time and memory,
    where chunks of an arbitrary initial route are reversed,
    and selected if the total distance is reduced. A notable
    result in 2D is that the path is guaranteed to never cross
    itself.
    This implementation solves the TSP for a one-way path, not a
    looping cycle.
    Parameters
    ----------
    locations : NDArray
        An array of N points with shape (N, D) where D is the space dimension.
    improvement_threshold : float, optional
        Threshold used as progress criterion to stop the optimization process.
        The default is 1e-8.
    Returns
    -------
    NDArray
        The new positions order of shape (N,).
    """
    route = np.arange(locations.shape[0])
    distances = np.linalg.norm(locations[None] - locations[:, None], axis=-1)
    route_length = np.sum(distances[route[:-1], route[1:]])
    improvement_factor = 1
    while improvement_factor >= improvement_threshold:
        old_route_length = route_length
        for i in range(1, len(route) - 2):
            # Check new distance by reversing chunks between i and j
            for j in range(i + 1, len(route) - 1):
                # Compute new route distance variation
                delta_length = (
                    distances[route[i - 1], route[j]]
                    + distances[route[i], route[j + 1]]
                    - distances[route[i - 1], route[i]]
                    - distances[route[j], route[j + 1]]
                )
                if delta_length < 0:
                    # Reverse route chunk
                    route = np.concatenate(
                        [route[:i], route[i : j + 1][::-1], route[j + 1 :]]
                    )
                    route_length += delta_length
        improvement_factor = 1 - route_length / old_route_length
    return route