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
23 changes: 9 additions & 14 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,18 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --user pipx
python -m pipx ensurepath
pipx install poetry
poetry install
- name: Lint with flake8
curl -LsSf https://astral.sh/uv/install.sh | sh
uv sync
- name: Lint with ruff
run: |
# stop the build if there are Python syntax errors or undefined names
poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --ignore=E203,W503
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Check black formatter was run
uv run ruff check .
- name: Check ruff formatter was run
run: |
poetry run black .
- name: Check type hints with mypy
uv run ruff format . --check
- name: Check type hints with ty
run: |
poetry run mypy --ignore-missing-imports .
uv run ty check .
- name: Run tests and check coverage
run: |
# Fail if total coverage is below 95%
poetry run pytest --cov=. --cov-fail-under=95 tests/
uv run pytest --cov=. --cov-fail-under=95 tests/
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,8 @@ venv.bak/
# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# ty
.ty_cache/

# Pyre type checker
.pyre/
Expand Down
14 changes: 6 additions & 8 deletions .scripts/checkup_scripts.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
# Run autopep8 to fix code
poetry run black .
# Run formatter
uv run ruff format .

# Check other requirements of PEP8
poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --ignore=E203,W503
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=79 --statistics
# Check linting rules
uv run ruff check .

# Check mypy
poetry run mypy --ignore-missing-imports .
# Check ty
uv run ty check .
14 changes: 7 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Installation

pip install python-tsp
poetry add python-tsp # if using Poetry in the project
uv add python-tsp # if using `uv` in the project


Quickstart
Expand Down Expand Up @@ -158,13 +159,12 @@ Here are the detailed steps that should be followed before making a pull request

.. code:: bash

# Black and flake8 to be conformant with PEP8
poetry run black .
poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --ignore=E203,W503
poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=79 --statistics
# Ruff formatter and linter to be conformant with PEP8
uv run ruff format .
uv run ruff check .

# Mypy for proper type hints
poetry run mypy --ignore-missing-imports .
# Ty for proper type hints
uv run ty check .

You can also run all of these steps at once with the check-up bash script:

Expand All @@ -177,7 +177,7 @@ Finally (and of course), make sure all tests pass and you get at least 95% of co

.. code:: bash

poetry run pytest --cov=. --cov-report=term-missing --cov-fail-under=95 tests/
uv run pytest --cov=. --cov-report=term-missing --cov-fail-under=95 tests/


Python version support
Expand Down
1,116 changes: 0 additions & 1,116 deletions poetry.lock

This file was deleted.

70 changes: 44 additions & 26 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,47 +1,65 @@
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.poetry]
[project]
name = "python_tsp"
version = "0.5.0"
description = "Library to solve the Traveling Salesperson Problem in pure Python."
readme = "README_pypi.rst"
authors = ["Fillipe Goulart <fillipe.gsm@tutanota.com>", "Luan Leonardo <>"]
repository = "https://github.com/fillipe-gsm/python-tsp"
license = "MIT"
authors = [
{name = "Fillipe Goulart", email = "fillipe.gsm@tutanota.com"},
{name = "Luan Leonardo"},
]
license = {text = "MIT"}
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",
]

[tool.poetry.dependencies]
python = "^3.9"
tsplib95 = "^0.7.1"
requests = "^2.28.0"
numpy = "^2.0.0"
[project.urls]
repository = "https://github.com/fillipe-gsm/python-tsp"

[tool.poetry.group.dev.dependencies]
mypy = "^0.991"
types-requests = "^2.28.11.5"
types-mock = "^4.0.15.2"
flake8 = "^6.0.0"
black = ">=24.4.2,<25.0"
pytest = "^7.4.0"
ipdb = "^0.13.13"
pytest-cov = "^4.1.0"
mock = "^5.1.0"
[dependency-groups]
dev = [
"types-requests>=2.28.11.5,<3.0",
"types-mock>=4.0.15.2,<5.0",
"pytest>=7.4.0,<8.0",
"ipdb>=0.13.13,<0.14",
"pytest-cov>=4.1.0,<5.0",
"mock>=5.1.0,<6.0",
"ruff>=0.15.21",
"ty>=0.0.59",
]

[tool.codespell]
ignore-words-list = 'astroid,asend'

[tool.black]
line-length = 79

[tool.ruff]
line-length = 79
fix = true

include = ["*.py", "*.pyi", "**/pyproject.toml"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
docstring-code-format = true

[tool.ruff.lint]
select = [
# pycodestyle
"E",
# Pyflakes
"F",
# pyupgrade
"UP",
# ruff-plugin-bugbear
"B",
# ruff-plugin-simplify
"SIM",
# isort
"I",
"NPY201", # numpy2-deprecation
]

6 changes: 3 additions & 3 deletions python_tsp/distances/data_processing.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""Common data processing tasks between all distances"""

from typing import Optional, Tuple
from typing import Optional

import numpy as np


def process_input(
sources: np.ndarray, destinations: Optional[np.ndarray] = None
) -> Tuple[np.ndarray, np.ndarray]:
) -> tuple[np.ndarray, np.ndarray]:
"""Pre-process input
This function ensures ``sources`` and ``destinations`` have at least two
dimensions, and if ``destinations`` is `None`, set it equal to ``sources``.
Expand All @@ -18,4 +18,4 @@ def process_input(
sources = np.atleast_2d(sources)
destinations = np.atleast_2d(destinations)

return sources, destinations # type: ignore
return sources, destinations
1 change: 0 additions & 1 deletion python_tsp/distances/great_circle_distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from .data_processing import process_input


EARTH_RADIUS_METERS = 6371000


Expand Down
5 changes: 2 additions & 3 deletions python_tsp/exact/branch_and_bound/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from dataclasses import dataclass
from math import inf
from typing import List, Tuple

import numpy as np

Expand Down Expand Up @@ -37,12 +36,12 @@ class Node:

level: int
index: int
path: List[int]
path: list[int]
cost: float
cost_matrix: np.ndarray

@staticmethod
def compute_reduced_matrix(matrix: np.ndarray) -> Tuple[np.ndarray, float]:
def compute_reduced_matrix(matrix: np.ndarray) -> tuple[np.ndarray, float]:
"""
Compute the reduced matrix and the cost of reducing it.

Expand Down
3 changes: 1 addition & 2 deletions python_tsp/exact/branch_and_bound/priority_queue.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from dataclasses import dataclass, field
from heapq import heappop, heappush
from typing import List

from python_tsp.exact.branch_and_bound import Node

Expand All @@ -26,7 +25,7 @@ class PriorityQueue:
Pop the item with the highest priority from the priority queue.
"""

_container: List[Node] = field(default_factory=list)
_container: list[Node] = field(default_factory=list)

@property
def empty(self) -> bool:
Expand Down
3 changes: 1 addition & 2 deletions python_tsp/exact/branch_and_bound/solver.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from math import inf
from typing import List, Tuple

import numpy as np

Expand All @@ -8,7 +7,7 @@

def solve_tsp_branch_and_bound(
distance_matrix: np.ndarray,
) -> Tuple[List[int], float]:
) -> tuple[list[int], float]:
"""
Solve the Traveling Salesperson Problem (TSP) using the
Branch and Bound algorithm.
Expand Down
4 changes: 2 additions & 2 deletions python_tsp/exact/brute_force.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Module with a brute force TSP solver"""

from itertools import permutations
from typing import Any, List, Optional, Tuple
from typing import Any, Optional

import numpy as np

Expand All @@ -10,7 +10,7 @@

def solve_tsp_brute_force(
distance_matrix: np.ndarray,
) -> Tuple[Optional[List], Any]:
) -> tuple[Optional[list], Any]:
"""Solve TSP to optimality with a brute force approach

Parameters
Expand Down
6 changes: 3 additions & 3 deletions python_tsp/exact/dynamic_programming.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from functools import lru_cache
from typing import Dict, List, Optional, Tuple
from typing import Optional

import numpy as np


def solve_tsp_dynamic_programming(
distance_matrix: np.ndarray,
maxsize: Optional[int] = None,
) -> Tuple[List, float]:
) -> tuple[list, float]:
"""
Solve TSP to optimality with dynamic programming

Expand Down Expand Up @@ -92,7 +92,7 @@ def solve_tsp_dynamic_programming(
# Get initial set {1, 2, ..., tsp_size} as a frozenset because @lru_cache
# requires a hashable type
N = frozenset(range(1, distance_matrix.shape[0]))
memo: Dict[Tuple, int] = {}
memo: dict[tuple, int] = {}

# Step 1: get minimum distance
@lru_cache(maxsize=maxsize)
Expand Down
2 changes: 1 addition & 1 deletion python_tsp/heuristics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@

from .lin_kernighan import solve_tsp_lin_kernighan # noqa: F401
from .local_search import solve_tsp_local_search # noqa: F401
from .record_to_record import solve_tsp_record_to_record # noqa:
from .record_to_record import solve_tsp_record_to_record # noqa: F401
from .simulated_annealing import solve_tsp_simulated_annealing # noqa: F401
Loading
Loading