Skip to content

Commit ebd2838

Browse files
authored
Merge pull request #2 from tubs-alg/fix/hw01-ex03-python-timeout
HW01/ex03: add timeout to Python 2-opt variants
2 parents 422d3f0 + 0b85865 commit ebd2838

5 files changed

Lines changed: 54 additions & 14 deletions

File tree

homework/HW01/exercise03/README.md

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33
This exercise implements the 2-opt heuristic for the Traveling Salesman Problem
44
using several move-selection strategies, in both Python and C++.
55

6+
## Updates
7+
8+
Post-release fixes. Only changes that affect your workflow are listed here;
9+
see `git log` for the full history.
10+
11+
- **2026-04-16** — Python variants (`first_improvement_two_opt`,
12+
`full_scan_two_opt`, `best_improvement_two_opt`) now take an optional
13+
`timeout: float = 10.0` parameter, matching the C++ API. If you already
14+
started: add the parameter to your signature and check a deadline
15+
(`time.perf_counter()`) in your outer loop — coarse-grained checks after
16+
each restart or full sweep are enough. Without this, `benchmark.py` could
17+
run for hours on large `n`. See issue #1.
18+
619
## Project Structure
720

821
```
@@ -84,16 +97,17 @@ and prints a summary table comparing tour length and runtime.
8497

8598
## Function Signatures
8699

87-
All single-run functions take a list of `(x, y)` tuples and an `initial_tour`
88-
(a permutation of `0..n-1`), and return the improved tour. The benchmark
89-
generates the initial tour in Python and passes it to every variant, so all
90-
start from the same permutation and results are directly comparable.
100+
All single-run functions take a list of `(x, y)` tuples, an `initial_tour`
101+
(a permutation of `0..n-1`), and an optional `timeout` in seconds, and
102+
return the improved tour. The benchmark generates the initial tour in
103+
Python and passes it to every variant, so all start from the same
104+
permutation and results are directly comparable.
91105

92106
```python
93-
# Part a: Python variants
94-
first_improvement_two_opt(points, initial_tour) -> list[int]
95-
full_scan_two_opt(points, initial_tour) -> list[int]
96-
best_improvement_two_opt(points, initial_tour) -> list[int]
107+
# Part a: Python variants (all take an optional timeout in seconds)
108+
first_improvement_two_opt(points, initial_tour, timeout=10.0) -> list[int]
109+
full_scan_two_opt(points, initial_tour, timeout=10.0) -> list[int]
110+
best_improvement_two_opt(points, initial_tour, timeout=10.0) -> list[int]
97111

98112
# Part b: C++ variants (all take an optional timeout in seconds)
99113
cpp_first_improvement(points, initial_tour, timeout=10.0) -> list[int]
@@ -104,6 +118,11 @@ cpp_best_improvement(points, initial_tour, timeout=10.0) -> list[int]
104118
parallel_two_opt(points, num_threads=4, base_seed=0, timeout=10.0) -> list[int]
105119
```
106120

121+
When the timeout is reached, return the best tour found so far. Coarse-grained
122+
checks are fine: compare `time.perf_counter()` (Python) or `Clock::now()` (C++)
123+
against a precomputed deadline after each restart or full sweep, not inside
124+
the innermost loop.
125+
107126
## Build Troubleshooting
108127

109128
**C++ changes not taking effect?**

homework/HW01/exercise03/benchmark.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,13 @@ def main():
164164
# Python variants
165165
print(f"\n Python:")
166166
res = run_multi_seed(first_improvement_two_opt, points, initial_tours,
167-
label="first-impr")
167+
label="first-impr", timeout=TIMEOUT)
168168
rows.append(("first-impr (py)", res))
169169
res = run_multi_seed(best_improvement_two_opt, points, initial_tours,
170-
label="best-impr")
170+
label="best-impr", timeout=TIMEOUT)
171171
rows.append(("best-impr (py)", res))
172172
res = run_multi_seed(full_scan_two_opt, points, initial_tours,
173-
label="full-scan")
173+
label="full-scan", timeout=TIMEOUT)
174174
rows.append(("full-scan (py)", res))
175175

176176
if have_cpp:

homework/HW01/exercise03/src/tsp_two_opt/best_improvement.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,24 @@ def _dist(points, a, b):
88

99

1010
def best_improvement_two_opt(
11-
points: list[tuple[float, float]], initial_tour: list[int]
11+
points: list[tuple[float, float]],
12+
initial_tour: list[int],
13+
timeout: float = 10.0,
1214
) -> list[int]:
1315
"""
1416
Best-improvement 2-opt: scan all pairs, apply only the best move per pass.
1517
1618
Args:
1719
points: List of (x, y) coordinate tuples.
1820
initial_tour: Starting tour as a permutation of point indices.
21+
timeout: Maximum runtime in seconds. If exceeded, return the best tour
22+
found so far. Check ``time.perf_counter()`` against a precomputed
23+
deadline after each full sweep (coarse-grained is fine).
1924
2025
Returns:
2126
Tour as a list of point indices.
2227
"""
2328
# TODO: Implement Variant 3 (best-improvement).
29+
# Respect ``timeout``: compute ``deadline = time.perf_counter() + timeout``
30+
# and break out of the outer loop once the deadline is reached.
2431
raise NotImplementedError

homework/HW01/exercise03/src/tsp_two_opt/first_improvement.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,24 @@ def _dist(points, a, b):
88

99

1010
def first_improvement_two_opt(
11-
points: list[tuple[float, float]], initial_tour: list[int]
11+
points: list[tuple[float, float]],
12+
initial_tour: list[int],
13+
timeout: float = 10.0,
1214
) -> list[int]:
1315
"""
1416
First-improvement 2-opt (classical): find first improving move, apply, restart.
1517
1618
Args:
1719
points: List of (x, y) coordinate tuples.
1820
initial_tour: Starting tour as a permutation of point indices.
21+
timeout: Maximum runtime in seconds. If exceeded, return the best tour
22+
found so far. Check ``time.perf_counter()`` against a precomputed
23+
deadline after each restart (coarse-grained is fine).
1924
2025
Returns:
2126
Tour as a list of point indices.
2227
"""
2328
# TODO: Implement Variant 1 (first-improvement with restart).
29+
# Respect ``timeout``: compute ``deadline = time.perf_counter() + timeout``
30+
# and break out of the outer loop once the deadline is reached.
2431
raise NotImplementedError

homework/HW01/exercise03/src/tsp_two_opt/full_scan.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,24 @@ def _dist(points, a, b):
88

99

1010
def full_scan_two_opt(
11-
points: list[tuple[float, float]], initial_tour: list[int]
11+
points: list[tuple[float, float]],
12+
initial_tour: list[int],
13+
timeout: float = 10.0,
1214
) -> list[int]:
1315
"""
1416
Full-scan 2-opt: apply improvements immediately, continue scanning.
1517
1618
Args:
1719
points: List of (x, y) coordinate tuples.
1820
initial_tour: Starting tour as a permutation of point indices.
21+
timeout: Maximum runtime in seconds. If exceeded, return the best tour
22+
found so far. Check ``time.perf_counter()`` against a precomputed
23+
deadline after each full sweep (coarse-grained is fine).
1924
2025
Returns:
2126
Tour as a list of point indices.
2227
"""
2328
# TODO: Implement Variant 2 (full-scan, continue after improvement).
29+
# Respect ``timeout``: compute ``deadline = time.perf_counter() + timeout``
30+
# and break out of the outer loop once the deadline is reached.
2431
raise NotImplementedError

0 commit comments

Comments
 (0)