Create optimizer.py
Browse files- services/optimizer.py +68 -0
services/optimizer.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
from typing import List
|
| 3 |
+
from ortools.constraint_solver import pywrapcp, routing_enums_pb2
|
| 4 |
+
|
| 5 |
+
def solve_tsp(time_matrix: List[List[float]]) -> List[int]:
|
| 6 |
+
"""
|
| 7 |
+
OSRMのduration行列(秒)を入力に、0番ノード(Start)発→全訪問→Start戻りの巡回順序を返す。
|
| 8 |
+
OR-Toolsで解けない場合は貪欲法でフォールバックします。
|
| 9 |
+
返り値例: [0, 3, 2, 1, 0]
|
| 10 |
+
"""
|
| 11 |
+
n = len(time_matrix) if time_matrix else 0
|
| 12 |
+
if n <= 1:
|
| 13 |
+
return list(range(n))
|
| 14 |
+
|
| 15 |
+
# 行列のサニタイズ(Noneや欠損を大値に、対角は0、int秒へ)
|
| 16 |
+
BIG = 10**9
|
| 17 |
+
matrix: List[List[int]] = []
|
| 18 |
+
for i in range(n):
|
| 19 |
+
row_i = []
|
| 20 |
+
for j in range(n):
|
| 21 |
+
try:
|
| 22 |
+
v = time_matrix[i][j]
|
| 23 |
+
v = float(v) if v is not None else BIG
|
| 24 |
+
except Exception:
|
| 25 |
+
v = BIG
|
| 26 |
+
if i == j:
|
| 27 |
+
v = 0.0
|
| 28 |
+
row_i.append(int(max(0, round(v))))
|
| 29 |
+
matrix.append(row_i)
|
| 30 |
+
|
| 31 |
+
# OR-Tools セットアップ
|
| 32 |
+
manager = pywrapcp.RoutingIndexManager(n, 1, 0) # depot=0
|
| 33 |
+
routing = pywrapcp.RoutingModel(manager)
|
| 34 |
+
|
| 35 |
+
def transit_cb(from_index, to_index):
|
| 36 |
+
i = manager.IndexToNode(from_index)
|
| 37 |
+
j = manager.IndexToNode(to_index)
|
| 38 |
+
return matrix[i][j]
|
| 39 |
+
|
| 40 |
+
transit_idx = routing.RegisterTransitCallback(transit_cb)
|
| 41 |
+
routing.SetArcCostEvaluatorOfAllVehicles(transit_idx)
|
| 42 |
+
|
| 43 |
+
params = pywrapcp.DefaultRoutingSearchParameters()
|
| 44 |
+
params.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
|
| 45 |
+
params.local_search_metaheuristic = routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH
|
| 46 |
+
params.time_limit.FromSeconds(1) # 1秒で切り上げ(HF Spaces向けに軽量)
|
| 47 |
+
|
| 48 |
+
solution = routing.SolveWithParameters(params)
|
| 49 |
+
if solution:
|
| 50 |
+
index = routing.Start(0)
|
| 51 |
+
order: List[int] = []
|
| 52 |
+
while not routing.IsEnd(index):
|
| 53 |
+
order.append(manager.IndexToNode(index))
|
| 54 |
+
index = solution.Value(routing.NextVar(index))
|
| 55 |
+
order.append(manager.IndexToNode(index)) # 最後にdepot(0)へ戻る
|
| 56 |
+
return order
|
| 57 |
+
|
| 58 |
+
# フォールバック:近傍貪欲
|
| 59 |
+
unvisited = set(range(1, n))
|
| 60 |
+
order = [0]
|
| 61 |
+
cur = 0
|
| 62 |
+
while unvisited:
|
| 63 |
+
nxt = min(unvisited, key=lambda j: matrix[cur][j])
|
| 64 |
+
order.append(nxt)
|
| 65 |
+
unvisited.remove(nxt)
|
| 66 |
+
cur = nxt
|
| 67 |
+
order.append(0)
|
| 68 |
+
return order
|