Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ We can find an optimal path using a Dynamic Programming method with:

.. code:: python

import numpy as np
from python_tsp.exact import solve_tsp_dynamic_programming

distance_matrix = np.array([
distance_matrix = [
[0, 5, 4, 10],
[5, 0, 8, 5],
[4, 8, 0, 3],
Expand Down
15 changes: 7 additions & 8 deletions README_pypi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,19 @@ Installation
Examples
========

Given a distance matrix as a numpy array, it is easy to compute a Hamiltonian
Given a distance matrix as a list of lists, it is easy to compute a Hamiltonian
path with least cost. For instance, to use a Dynamic Programming method:

.. code:: python

import numpy as np
from python_tsp.exact import solve_tsp_dynamic_programming

distance_matrix = np.array([
distance_matrix = [
[0, 5, 4, 10],
[5, 0, 8, 5],
[4, 8, 0, 3],
[10, 5, 3, 0]
])
]
permutation, distance = solve_tsp_dynamic_programming(distance_matrix)

The solution will be ``[0, 1, 3, 2]``, with total distance 17. Notice it is
Expand All @@ -54,7 +53,8 @@ zero:

.. code:: python

distance_matrix[:, 0] = 0
for row in distance_matrix:
row[0] = 0
permutation, distance = solve_tsp_dynamic_programming(distance_matrix)

and in this case we obtain ``[0, 2, 3, 1]``, with distance 12. Notice that in
Expand All @@ -71,15 +71,14 @@ of a point,

.. code:: python

import numpy as np
from python_tsp.distances import great_circle_distance_matrix

sources = np.array([
sources = [
[ 40.73024833, -73.79440675],
[ 41.47362495, -73.92783272],
[ 41.26591 , -73.21026228],
[ 41.3249908 , -73.507788 ]
])
]
distance_matrix = great_circle_distance_matrix(sources)

See the `project's repository <https://github.com/fillipe-gsm/python-tsp>`_
Expand Down
34 changes: 14 additions & 20 deletions docs/distances.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ Computes the regular Euclidean distance between points.

.. code:: python

import numpy as np

from python_tsp.distances import euclidean_distance_matrix


sources = np.array([[0, 0], [1, 1]])
destinations = np.array([[2, 2], [3, 3], [4, 4]])
sources = [[0, 0], [1, 1]]
destinations = [[2, 2], [3, 3], [4, 4]]

distance_matrix = euclidean_distance_matrix(sources, destinations)
# outputs a 2 x 3 numpy array
# outputs a 2 x 3 matrix (list of lists)


The returned distance matrix has in the ``i``-th row the distance from the ``i``-th source to each destination. This API is similar to all distance functions here.
Expand All @@ -33,29 +31,27 @@ Notice that, in general, the distance matrix is non-square. While this is by des
# same as
# distance_matrix = euclidean_distance_matrix(sources, sources)

If your problem requires a matrix of integers, just manipulate it like any Numpy array; e.g., ``distance_matrix.astype(int)``.
If your problem requires a matrix of integers, just convert the values; e.g., ``[[int(v) for v in row] for row in distance_matrix]``.


Great Circle Distance
=====================

In case the nodes represent coordinates in a sphere (such as the planet Earth), a more appropriate distance can be the `Great Circle Distance <https://en.wikipedia.org/wiki/Great-circle_distance>`_.

For example, if you have an array where each row has the latitude and longitude of a point,
For example, if you have a list where each row has the latitude and longitude of a point,

.. code:: python

import numpy as np

from python_tsp.distances import great_circle_distance_matrix


sources = np.array([
sources = [
[ 40.73024833, -73.79440675], # latitude, longitude
[ 41.47362495, -73.92783272],
[ 41.26591 , -73.21026228],
[ 41.3249908 , -73.507788 ]
])
]
distance_matrix = great_circle_distance_matrix(sources)


Expand All @@ -70,17 +66,15 @@ Again in case you have coordinates but would like to take a city's geography int

.. code:: python

import numpy as np

from python_tsp.distances import osrm_distance_matrix


sources = np.array([
sources = [
[ 40.73024833, -73.79440675], # latitude, longitude
[ 41.47362495, -73.92783272],
[ 41.26591 , -73.21026228],
[ 41.3249908 , -73.507788 ]
])
]
distance_matrix = osrm_distance_matrix(
sources, osrm_server_address="http://localhost:5000"
)
Expand Down Expand Up @@ -127,14 +121,14 @@ Finally, remember you can also compute the distance between different sources an

.. code:: python

sources = np.array([
sources = [
[ 40.73024833, -73.79440675], # latitude, longitude
[ 41.47362495, -73.92783272],
[ 41.26591 , -73.21026228],
])
destinations = np.array([
]
destinations = [
[ 41.3249908 , -73.507788 ]
])
]

distance_matrix = osrm_distance_matrix(
sources,
Expand Down Expand Up @@ -164,4 +158,4 @@ Finally, this module also has support for many TSPLIB-type files of ``TSP`` and

tsplib_file = "tests/tsplib_data/br17.atsp" # replace with the path to your TSPLIB file
distance_matrix = tsplib_distance_matrix(tsplib_file)
# outputs a 17 x 17 array
# outputs a 17 x 17 matrix (list of lists)
10 changes: 5 additions & 5 deletions docs/solvers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Solvers

This library has currently two classes of solvers: exact solvers and heuristics.

All solvers require at least a ``distance_matrix`` as input, which is an ``n x n`` numpy array containing the distance matrix for a problem with ``n`` nodes. This matrix can contain integers or floats and does not need to be symmetric. Other properties specific to each solver will be detailed below.
All solvers require at least a ``distance_matrix`` as input, which is an ``n x n`` matrix (list of lists) containing the distance matrix for a problem with ``n`` nodes. This matrix can contain integers or floats and does not need to be symmetric. Other properties specific to each solver will be detailed below.

All of them also return a permutation of integers from ``0`` to ``n`` containing the best route found, plus a number indicating the route cost.

Expand Down Expand Up @@ -87,7 +87,7 @@ Notice this local optimum may be different for distinct perturbation schemes and
from python_tsp.heuristics import solve_tsp_local_search

xopt, fopt = solve_tsp_local_search(
distance_matrix: np.ndarray,
distance_matrix: list[list[float]],
x0: Optional[List[int]] = None,
perturbation_scheme: str = "two_opt",
max_processing_time: Optional[float] = None,
Expand Down Expand Up @@ -133,7 +133,7 @@ An implementation of the `Simulated Annealing <https://en.wikipedia.org/wiki/Sim


xopt, fopt = solve_tsp_simulated_annealing(
distance_matrix: np.ndarray,
distance_matrix: list[list[float]],
x0: Optional[List[int]] = None,
perturbation_scheme: str = "two_opt",
alpha: float = 0.9,
Expand Down Expand Up @@ -192,7 +192,7 @@ A basic Lin and Kernighan implementation is provided. It can be said that the qu


xopt, fopt = solve_tsp_lin_kernighan(
distance_matrix: np.ndarray,
distance_matrix: list[list[float]],
x0: Optional[List[int]] = None,
log_file: Optional[str] = None,
verbose: bool = False,
Expand Down Expand Up @@ -230,7 +230,7 @@ Depending on the ``max_iterations`` parameter set, very high quality solutions c


xopt, fopt = solve_tsp_record_to_record(
distance_matrix: np.ndarray,
distance_matrix: list[list[float]],
x0: Optional[List[int]] = None,
max_iterations: Optional[int] = None,
log_file: Optional[str] = None,
Expand Down
2 changes: 0 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ requires-python = ">=3.9"
dependencies = [
"tsplib95>=0.7.1,<0.8.0",
"requests>=2.28.0,<3.0.0",
"numpy>=2.0.0,<3.0.0",
]

[project.urls]
Expand Down Expand Up @@ -61,5 +60,4 @@ select = [
"SIM",
# isort
"I",
"NPY201", # numpy2-deprecation
]
27 changes: 16 additions & 11 deletions python_tsp/distances/data_processing.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
"""Common data processing tasks between all distances"""

from typing import Optional

import numpy as np
from __future__ import annotations


def process_input(
sources: np.ndarray, destinations: Optional[np.ndarray] = None
) -> tuple[np.ndarray, np.ndarray]:
sources: list[list[float]] | list[float],
destinations: list[list[float]] | list[float] | None = None,
) -> tuple[list[list[float]], list[list[float]]]:
"""Pre-process input
This function ensures ``sources`` and ``destinations`` have at least two
dimensions, and if ``destinations`` is `None`, set it equal to ``sources``.
This function ensures ``sources`` and ``destinations`` are two-dimensional
lists, and if ``destinations`` is `None`, set it equal to ``sources``.
"""
if destinations is None:
destinations = sources

sources = np.atleast_2d(sources)
destinations = np.atleast_2d(destinations)
sources = _ensure_2d(sources)
destinations = _ensure_2d(destinations)

return sources, destinations


def _ensure_2d(points: list) -> list[list[float]]:
if not points:
return []
if isinstance(points[0], (int, float)):
return [points]
return points
30 changes: 17 additions & 13 deletions python_tsp/distances/euclidean_distance.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
"""Euclidean distance"""
from __future__ import annotations

from typing import Optional

import numpy as np
import math

from .data_processing import process_input


def euclidean_distance_matrix(
sources: np.ndarray, destinations: Optional[np.ndarray] = None
) -> np.ndarray:
sources: list[list[float]] | list[float],
destinations: list[list[float]] | list[float] | None = None,
) -> list[list[float]]:
"""Distance matrix using the Euclidean distance

Parameters
----------
sources, destinations
Arrays with each row containing the coordinates of a point. If
Lists with each row containing the coordinates of a point. If
``destinations`` is None, compute the distance between each source in
``sources`` and outputs a square distance matrix.

Returns
-------
distance_matrix
Array with the (i, j) entry indicating the Euclidean distance between
list with the (i, j) entry indicating the Euclidean distance between
the i-th row in ``sources`` and the j-th row in ``destinations``.

Notes
Expand All @@ -32,10 +31,15 @@ def euclidean_distance_matrix(

sqrt((y1 - x1)**2 + (y2 - x2)**2 + ... + (yn - xn)**2)

If the user requires the distance between each point in a single array,
call this this function with ``destinations`` set to `None`.
If the user requires the distance between each point in a single list,
call this function with ``destinations`` set to `None`.
"""
sources, destinations = process_input(sources, destinations)
return np.sqrt(
((sources[:, :, None] - destinations[:, :, None].T) ** 2).sum(axis=1)
)
result = []
for src in sources:
row = []
for dst in destinations:
dist = math.sqrt(sum((a - b) ** 2 for a, b in zip(src, dst)))
row.append(dist)
result.append(row)
return result
Loading
Loading