Travelling Salesman Problem (TSP) Example
The Travelling Salesman Problem (TSP) asks for the shortest round-trip through a set of cities, visiting each exactly once and returning to the start. It is NP-hard.
import getpass
import os
import matplotlib.pyplot as plt
import numpy as np
from dotenv import load_dotenv
from luna_quantum.algorithms import SCIP
from luna_usecases.traveling_salesman_problem import TspCircleCollection, TspData, TspFormulation, TspInstance
load_dotenv()
if "LUNA_API_KEY" not in os.environ:
os.environ["LUNA_API_KEY"] = getpass.getpass("Enter your Luna API key: ")
Create Data
Define distances between 4 German cities as a symmetric distance matrix.
dist_matrix = np.array(
[
[0.0, 58.00553679823033, 152.52000388133354, 248.84855373209675],
[58.00553679823033, 0.0, 190.1970628748754, 212.16684065507212],
[152.52000388133354, 190.1970628748754, 0.0, 261.6296330832349],
[248.84855373209675, 212.16684065507212, 261.6296330832349, 0.0],
]
)
city_names = ["Cologne", "Essen", "Frankfurt", "Hanover"]
data = TspData.from_distance_matrix(
distance_matrix=dist_matrix,
city_names=city_names,
start_city=city_names[0],
)
print(data.to_string())
Travelling Salesman Data:
Cities: 4
City names: ['Cologne', 'Essen', 'Frankfurt', 'Hanover']
Start city: Cologne
Distance matrix shape: (4, 4)
Plot Data
Visualize the cities and their connections as a NetworkX graph.

Create Formulation
Set up binary decision variables assigning each city to a position in the tour.
Travelling Salesman Problem Formulation:
Cities: 4
Start city: Cologne
Decision Variables:
x[i,p] in {0,1} for i = 0, ..., 3, p = 0, ..., 3
x[i,p] = 1 if city i is at position p in the tour
Total: 16 binary variables
Objective:
minimize sum_{i,j,p} d[i,j] * x[i,p] * x[j,(p+1) mod 4] for i != j
Constraints:
1. Each city at exactly one position (4 constraints):
sum_p x[i,p] == 1 for all i = 0, ..., 3
2. Each position has exactly one city (4 constraints):
sum_i x[i,p] == 1 for all p = 0, ..., 3
Create Instance
Combine data and formulation into a solvable instance.
Data:Travelling Salesman Data:
Cities: 4
City names: ['Cologne', 'Essen', 'Frankfurt', 'Hanover']
Start city: Cologne
Distance matrix shape: (4, 4)
Formulation:Travelling Salesman Problem Formulation:
Cities: 4
Start city: Cologne
Decision Variables:
x[i,p] in {0,1} for i = 0, ..., 3, p = 0, ..., 3
x[i,p] = 1 if city i is at position p in the tour
Total: 16 binary variables
Objective:
minimize sum_{i,j,p} d[i,j] * x[i,p] * x[j,(p+1) mod 4] for i != j
Constraints:
1. Each city at exactly one position (4 constraints):
sum_p x[i,p] == 1 for all i = 0, ..., 3
2. Each position has exactly one city (4 constraints):
sum_i x[i,p] == 1 for all p = 0, ..., 3
Formulate Model
Translate the instance into a mathematical optimization model.
Solve and Interpret
Solve the model with SCIP and interpret the raw result into a use-case-specific solution.
scip = SCIP()
job = scip.run(model)
sol = job.result()
uc_solution = instance.interpret(sol)
print(uc_solution.to_string())
/Users/maximilianjanetschek/PycharmProjects/luna-usecases/.venv/lib/python3.13/site-packages/rich/live.py:260:
UserWarning: install "ipywidgets" for Jupyter support
warnings.warn('install "ipywidgets" for Jupyter support')
Travelling Salesman Solution:
Tour: Cologne -> Essen -> Hanover -> Frankfurt -> Cologne
Total distance: 684.32
Valid: True
Plot Solution
Visualize the optimal solution.

Collections
Generate a benchmark collection of random instances for batch processing.
collection = TspCircleCollection.generate(min_num_cities=2, max_num_cities=8, radius=1)
model = collection.instances[0].formulate()
print(model)
Model: traveling_salesman_problem<traveling_salesman_problem>
Minimize
4 * x_City_2_0_0_0 * x_City_2_0_1_1 + 4 * x_City_2_0_0_1 * x_City_2_0_1_0
Subject To
city_one_position_0: x_City_2_0_0_0 + x_City_2_0_0_1 == 1
city_one_position_1: x_City_2_0_1_0 + x_City_2_0_1_1 == 1
position_one_city_0: x_City_2_0_0_0 + x_City_2_0_1_0 == 1
position_one_city_1: x_City_2_0_0_1 + x_City_2_0_1_1 == 1
Binary
x_City_2_0_0_0 x_City_2_0_0_1 x_City_2_0_1_0 x_City_2_0_1_1