In [1]:
from ortools.linear_solver import pywraplp as OR
In [2]:
def find_subtour(nodes, edges):
    """Identifies the first closed loop in the list of active edges."""
    unvisited = set(nodes)
    while unvisited:
        start_node = next(iter(unvisited))
        cycle = []
        current = start_node
        while current is not None:
            cycle.append(current)
            if current in unvisited:
                unvisited.remove(current)
            # Find the next node in the path (i -> j)
            next_node = next((j for i, j in edges if i == current), None)
            if next_node == start_node:
                return cycle
            if next_node not in unvisited:
                break
            current = next_node
    return None
In [3]:
def solve_tsp(dist_matrix):
    n = len(dist_matrix)
    nodes = range(n)
    
    # Initialize the SCIP solver
    m = OR.Solver('TSP_Class_Demo', OR.Solver.SCIP_MIXED_INTEGER_PROGRAMMING)
    
    # 1. Variables: x[i,j] is 1 if we travel from city i to city j
    x = {}
    for i in nodes:
        for j in nodes:
            if i != j:
                x[i, j] = m.BoolVar(f'x_{i}_{j}')

    # 2. Objective: Minimize total travel distance
    m.Minimize(sum(dist_matrix[i][j] * x[i, j] for (i, j) in x))

    # 3. Degree Constraints (The Assignment Problem)
    # Every city must have exactly one outgoing and one incoming edge
    for i in nodes:
        m.Add(sum(x[i, j] for j in nodes if i != j) == 1)
        m.Add(sum(x[j, i] for j in nodes if i != j) == 1)

    iteration = 1
    while True:
        status = m.Solve()
        
        if status != OR.Solver.OPTIMAL:
            print("No feasible solution found.")
            break

        # Convert solver output to a list of chosen edges
        active_edges = [k for k, var in x.items() if var.solution_value() > 0.5]
        print(f'{active_edges=}')
        subtour = find_subtour(nodes, active_edges)

        # If the first subtour found includes all nodes, we are done!
        if len(subtour) == n:
            print(f"Optimal TSP Tour Found! Distance: {m.Objective().Value()}")
            return active_edges

        # 4. Subtour Elimination: Add a constraint to break the discovered loop
        print(f"Iteration {iteration}: Subtour {subtour} detected. Breaking it...")
        
        # Logic: The number of edges connecting nodes within this subset 
        # must be less than the number of nodes in the subset.
        m.Add(sum(x[i, j] for i in subtour for j in subtour if i != j) <= len(subtour) - 1)
        
        iteration += 1

Input:

In [4]:
dist_matrix = [
    [0, 10, 8, 9, 7],
    [10, 0, 10, 5, 6],
    [8, 10, 0, 8, 9],
    [9, 5, 8, 0, 6],
    [7, 6, 9, 6, 0]
]

tour = solve_tsp(dist_matrix)
print("Final Tour:", tour)
active_edges=[(0, 2), (1, 4), (2, 0), (3, 1), (4, 3)]
Iteration 1: Subtour [0, 2] detected. Breaking it...
active_edges=[(0, 2), (1, 4), (2, 3), (3, 1), (4, 0)]
Optimal TSP Tour Found! Distance: 34.0
Final Tour: [(0, 2), (1, 4), (2, 3), (3, 1), (4, 0)]