Creating TSP Qubots

This example shows how to create qubots for the Traveling Salesman Problem (TSP) - finding the shortest route visiting all cities exactly once. You’ll learn to build both problem and optimizer qubots, connect them, run locally, and upload to Rastion.

Problem Overview

The TSP asks: given cities and distances, find the shortest tour visiting each city once and returning to start.

Applications: Vehicle routing, manufacturing optimization, DNA sequencing, circuit design.

Creating the TSP Problem Qubot

Step 1: Problem Implementation

Create qubot.py for the TSP problem:

# qubot.py
import numpy as np
from qubots import BaseProblem, ProblemMetadata, ProblemType, ObjectiveType

class TSPProblem(BaseProblem):
    def __init__(self, n_cities=10, seed=42):
        # Generate random city coordinates
        np.random.seed(seed)
        self.n_cities = n_cities
        self.coordinates = np.random.rand(n_cities, 2) * 100

        # Calculate distance matrix
        self.distances = self._calculate_distances()

        # Set metadata
        metadata = ProblemMetadata(
            name="TSP Problem",
            description=f"Find shortest tour visiting {n_cities} cities",
            problem_type=ProblemType.COMBINATORIAL,
            objective_type=ObjectiveType.MINIMIZE,
            dimension=n_cities,
            variable_bounds=[(0, n_cities-1)] * n_cities
        )
        super().__init__(metadata)

    def _calculate_distances(self):
        """Calculate Euclidean distance matrix between cities."""
        distances = np.zeros((self.n_cities, self.n_cities))
        for i in range(self.n_cities):
            for j in range(self.n_cities):
                if i != j:
                    dx = self.coordinates[i][0] - self.coordinates[j][0]
                    dy = self.coordinates[i][1] - self.coordinates[j][1]
                    distances[i][j] = np.sqrt(dx*dx + dy*dy)
        return distances

    def evaluate_solution(self, solution):
        """Calculate total tour distance."""
        if not self.is_valid_solution(solution):
            return float('inf')

        total_distance = 0
        for i in range(len(solution)):
            from_city = solution[i]
            to_city = solution[(i + 1) % len(solution)]
            total_distance += self.distances[from_city][to_city]

        return total_distance

    def is_valid_solution(self, solution):
        """Check if solution is a valid tour."""
        if len(solution) != self.n_cities:
            return False
        if set(solution) != set(range(self.n_cities)):
            return False
        return True

    def get_random_solution(self):
        """Generate random valid tour."""
        tour = list(range(self.n_cities))
        np.random.shuffle(tour)
        return tour

Step 2: Problem Configuration

Create config.json for the TSP problem:

{
  "type": "problem",
  "class_name": "TSPProblem",
  "default_params": {
    "n_cities": 10,
    "seed": 42
  },
  "parameters": {
    "n_cities": {
      "type": "integer",
      "default": 10,
      "min": 3,
      "max": 1000,
      "description": "Number of cities to visit"
    },
    "seed": {
      "type": "integer",
      "default": 42,
      "description": "Random seed for city generation"
    }
  },
  "metadata": {
    "name": "TSP Problem",
    "description": "Traveling Salesman Problem with random cities",
    "domain": "routing",
    "tags": ["tsp", "routing", "combinatorial"]
  }
}

Creating the TSP Optimizer Qubot

Step 3: Optimizer Implementation

Create qubot.py for a genetic algorithm optimizer:

# qubot.py
import numpy as np
import random
from qubots import BaseOptimizer, OptimizationResult, OptimizerMetadata, OptimizerType

class GeneticTSPOptimizer(BaseOptimizer):
    def __init__(self, population_size=50, generations=100, mutation_rate=0.1):
        self.population_size = population_size
        self.generations = generations
        self.mutation_rate = mutation_rate

        # Set metadata
        metadata = OptimizerMetadata(
            name="Genetic TSP Optimizer",
            description="Genetic algorithm for TSP with order crossover",
            optimizer_type=OptimizerType.METAHEURISTIC,
            required_parameters=["population_size", "generations"],
            optional_parameters=["mutation_rate"],
            parameter_ranges={
                "population_size": (10, 500),
                "generations": (10, 2000),
                "mutation_rate": (0.001, 1.0)
            }
        )
        super().__init__(metadata)

    def optimize(self, problem, progress_callback=None, log_callback=None):
        """Solve TSP using genetic algorithm."""
        n_cities = problem.n_cities

        # Initialize population
        population = [problem.get_random_solution() for _ in range(self.population_size)]
        best_solution = None
        best_value = float('inf')

        for generation in range(self.generations):
            # Evaluate population
            fitness_scores = []
            for individual in population:
                fitness = problem.evaluate_solution(individual)
                fitness_scores.append(fitness)

                if fitness < best_value:
                    best_value = fitness
                    best_solution = individual.copy()

            # Log progress
            if log_callback:
                log_callback('info', f'Generation {generation}: Best = {best_value:.2f}', 'optimizer')

            # Selection and reproduction
            new_population = []
            for _ in range(self.population_size):
                # Tournament selection
                parent1 = self._tournament_selection(population, fitness_scores)
                parent2 = self._tournament_selection(population, fitness_scores)

                # Order crossover
                child = self._order_crossover(parent1, parent2)

                # Mutation
                if random.random() < self.mutation_rate:
                    child = self._mutate(child)

                new_population.append(child)

            population = new_population

        return OptimizationResult(
            best_solution=best_solution,
            best_value=best_value,
            is_feasible=True,
            iterations=self.generations,
            termination_reason="max_generations_reached"
        )

    def _tournament_selection(self, population, fitness_scores, tournament_size=3):
        """Select individual using tournament selection."""
        tournament_indices = random.sample(range(len(population)), tournament_size)
        best_idx = min(tournament_indices, key=lambda i: fitness_scores[i])
        return population[best_idx].copy()

    def _order_crossover(self, parent1, parent2):
        """Order crossover for TSP."""
        size = len(parent1)
        start, end = sorted(random.sample(range(size), 2))

        child = [-1] * size
        child[start:end] = parent1[start:end]

        pointer = end
        for city in parent2[end:] + parent2[:end]:
            if city not in child:
                child[pointer % size] = city
                pointer += 1

        return child

    def _mutate(self, individual):
        """Swap mutation for TSP."""
        mutated = individual.copy()
        i, j = random.sample(range(len(individual)), 2)
        mutated[i], mutated[j] = mutated[j], mutated[i]
        return mutated

Step 4: Optimizer Configuration

Create config.json for the genetic algorithm:

{
  "type": "optimizer",
  "class_name": "GeneticTSPOptimizer",
  "default_params": {
    "population_size": 50,
    "generations": 100,
    "mutation_rate": 0.1
  },
  "parameters": {
    "population_size": {
      "type": "integer",
      "default": 50,
      "min": 10,
      "max": 500,
      "description": "Number of individuals in population"
    },
    "generations": {
      "type": "integer",
      "default": 100,
      "min": 10,
      "max": 2000,
      "description": "Number of generations to evolve"
    },
    "mutation_rate": {
      "type": "number",
      "default": 0.1,
      "min": 0.001,
      "max": 1.0,
      "description": "Probability of mutation"
    }
  },
  "metadata": {
    "name": "Genetic TSP Optimizer",
    "description": "Genetic algorithm with order crossover for TSP",
    "optimizer_type": "metaheuristic",
    "tags": ["genetic", "evolutionary", "tsp"]
  }
}

Connecting Problem and Optimizer

Step 5: Local Testing

Test your qubots locally before uploading:

# test_local.py
from qubot import TSPProblem, GeneticTSPOptimizer

# Create problem instance
problem = TSPProblem(n_cities=15, seed=42)
print(f"Created TSP with {problem.n_cities} cities")

# Create optimizer instance
optimizer = GeneticTSPOptimizer(
    population_size=30,
    generations=50,
    mutation_rate=0.15
)
print(f"Created genetic optimizer")

# Solve the problem
print("Solving TSP...")
result = optimizer.optimize(problem)

print(f"Best tour distance: {result.best_value:.2f}")
print(f"Best tour: {result.best_solution}")
print(f"Valid solution: {problem.is_valid_solution(result.best_solution)}")
print(f"Generations: {result.iterations}")

Uploading to Rastion

Step 6: Upload Problem Qubot

Upload your TSP problem to Rastion:

# upload_problem.py
import qubots.rastion as rastion

# Authenticate with Rastion
rastion.authenticate("your_api_token")

# Upload the TSP problem
problem_result = rastion.upload_qubots_model(
    model_path="./tsp_problem",  # Directory containing qubot.py and config.json
    model_name="tsp_problem",
    description="TSP problem with configurable city count",
    tags=["tsp", "routing", "combinatorial"],
    public=True
)

print(f"Problem uploaded: {problem_result['repository_url']}")

Step 7: Upload Optimizer Qubot

Upload your genetic algorithm optimizer:

# upload_optimizer.py
import qubots.rastion as rastion

# Upload the genetic optimizer
optimizer_result = rastion.upload_qubots_model(
    model_path="./genetic_tsp_optimizer",  # Directory with qubot.py and config.json
    model_name="genetic_tsp_optimizer",
    description="Genetic algorithm for TSP with order crossover",
    tags=["genetic", "evolutionary", "tsp", "metaheuristic"],
    public=True
)

print(f"Optimizer uploaded: {optimizer_result['repository_url']}")

Using AutoLoading Functions

Step 8: Load and Use Your Qubots

Once uploaded, use AutoProblem and AutoOptimizer to load your qubots:

from qubots import AutoProblem, AutoOptimizer

# Load your uploaded problem
problem = AutoProblem.from_repo("your_username/tsp_problem", override_params={
    "n_cities": 20,
    "seed": 123
})

# Load your uploaded optimizer
optimizer = AutoOptimizer.from_repo("your_username/genetic_tsp_optimizer", override_params={
    "population_size": 100,
    "generations": 200,
    "mutation_rate": 0.08
})

# Solve the problem
result = optimizer.optimize(problem)

print(f"Solution found!")
print(f"Tour distance: {result.best_value:.2f}")
print(f"Runtime: {result.runtime_seconds:.3f}s")
print(f"Valid tour: {problem.is_valid_solution(result.best_solution)}")

Step 9: Load Others’ Qubots

Use qubots created by the community:

# Load community TSP problems
berlin52 = AutoProblem.from_repo("benchmarks/tsp_berlin52")
att48 = AutoProblem.from_repo("benchmarks/tsp_att48")

# Load community optimizers
ortools_solver = AutoOptimizer.from_repo("algorithms/ortools_tsp")
sa_optimizer = AutoOptimizer.from_repo("algorithms/simulated_annealing_tsp")

# Compare your optimizer against others
problems = [berlin52, att48]
optimizers = [optimizer, ortools_solver, sa_optimizer]

for prob in problems:
    print(f"\nSolving {prob.metadata.name}:")
    for opt in optimizers:
        result = opt.optimize(prob)
        print(f"  {opt.metadata.name}: {result.best_value:.2f}")

Summary

You’ve learned how to create TSP qubots from scratch:

  1. Problem Qubot: Implemented TSPProblem class with distance calculation and solution evaluation
  2. Optimizer Qubot: Created GeneticTSPOptimizer with order crossover and mutation
  3. Configuration: Set up config.json files for both qubots
  4. Local Testing: Connected problem and optimizer to verify functionality
  5. Upload to Rastion: Made qubots available to the community
  6. AutoLoading: Used AutoProblem and AutoOptimizer to load and run qubots

Your qubots are now ready for benchmarking, cloud execution, and sharing with the community!

Next Steps