# Optiverse

Optiverse is a Python library designed for evolving code and algorithms using Large Language Models (LLMs). Inspired by Deepmind's [AlphaEvolve](https://deepmind.google/discover/blog/alphaevolve-a-gemini-powered-coding-agent-for-designing-advanced-algorithms/), it provides a flexible framework to iteratively improve programs, from code snippets to full files, in any programming language.

With Optiverse, you define a problem and provide an evaluator. The system then generates and evolves candidate solutions over multiple iterations, learning which approaches yield better results.

📖 **Read the announcement post:** [Optiverse: Evolving Code with LLMs](https://mathieularose.com/optiverse-evolving-code-with-llms)

## Table of Contents


- [Why Optiverse?](#why-optiverse)
- [Quick Start](#quick-start)
- [Evolved Solutions](#evolved-solutions)
- [License](#license)

## Why Optiverse?

Optiverse helps developers and researchers automate code improvement by evolving and refining solutions with LLMs. Key capabilities include:

- **Whole-file evolution**: Unlike other implementations limited to isolated code blocks, Optiverse evolves entire source files.
- **Modular architecture**: Swap out search strategies easily to experiment with different optimization approaches.
- **Multi-language support**: As long as you provide an evaluator for your problem, Optiverse can optimize code in any programming language.
- **Flexible LLM integration**: Works with any LLM provider compatible with the OpenAI API standard, such as OpenAI, Google Gemini, NVIDIA, and more.

## Quick Start

Follow these steps to set up Optiverse and run an example.

### 1. Set Up Environment

First, create a virtual environment and install dependencies:

```bash
make init
```

Then, activate the virtual environment:

```bash
source venv/bin/activate
```

### 2. Run the Traveling Salesman Problem example:

This example uses Optiverse to solve the [Traveling Salesman Problem (TSP)](https://en.wikipedia.org/wiki/Travelling_salesman_problem). The code is in the [examples/tsp](examples/tsp) directory.

The example supports multiple LLM providers through environment variables:
- `LLM_API_KEY`: Your API key for the chosen provider
- `LLM_MODEL`: The model name to use
- `LLM_PROVIDER`: The provider name (`openai`, `google`, or `nvidia`)

Example with Google Gemini:

```bash
LLM_API_KEY="your-gemini-api-key" LLM_MODEL="gemini-2.0-flash" LLM_PROVIDER="google" make run.tsp
```

Note: Optiverse uses the OpenAI package under the hood, so it supports any LLM provider that follows the OpenAI API standard.

### Sample Output:

When you run it, you'll see output like this:

```bash
2025-07-17 09:35:47 - optiverse.optimizer - INFO - Evaluating and saving initial solution...
2025-07-17 09:35:47 - examples.tsp.evaluator - INFO - Score 1: 33800.20318013358
2025-07-17 09:35:47 - examples.tsp.evaluator - INFO - Score 2: 32719.233809839046
2025-07-17 09:35:48 - examples.tsp.evaluator - INFO - Score 3: 35557.94658416552
2025-07-17 09:35:48 - optiverse.optimizer - INFO - Initial solution saved with ID: d3c1a4eb294e469593b52ee805d7028b, score: 34025.79452471271
2025-07-17 09:35:48 - optiverse.optimizer - INFO - Starting iteration 1/100
2025-07-17 09:35:50 - httpx - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/openai/chat/completions "HTTP/1.1 200 OK"
2025-07-17 09:35:51 - optiverse.solution_generator - INFO - No code blocks found in LLM response
2025-07-17 09:35:51 - optiverse.optimizer - INFO - No code generated by solution generator
2025-07-17 09:35:51 - optiverse.optimizer - INFO - Starting iteration 2/100
2025-07-17 09:35:52 - httpx - INFO - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/openai/chat/completions "HTTP/1.1 200 OK"
2025-07-17 09:35:58 - optiverse.optimizer - INFO - Evaluating solution
2025-07-17 09:36:09 - examples.tsp.evaluator - INFO - Score 1: 3149.0239046268334
2025-07-17 09:36:21 - examples.tsp.evaluator - INFO - Score 2: 3200.6735649311436
2025-07-17 09:36:30 - examples.tsp.evaluator - INFO - Score 3: 3089.4221942253616
2025-07-17 09:36:30 - optiverse.optimizer - INFO - Evaluation result: 3146.3732212611126
2025-07-17 09:36:30 - optiverse.optimizer - INFO - Saved solution with ID: 564d577d222a42ba8e1defb0dd2870e3

...
```

### Understanding the Results

During optimization, Optiverse saves results in directories named `tmp/YYYYMMDD_HHMM`, indicating the date and time of each run.

#### `solutions.csv`


Inside each run directory, `solutions.csv` provides a high-level overview of all solutions explored:


| id                                 | score           | t_group | t_move      | t_parent_id_1                  | ... |
|------------------------------------|-----------------|---------|-------------|-------------------------------|-----|
| 0d8c5789dde24a94901871c18d6d9854  | 2817.0555492637 | 1       | exploitation| 034afd2da8894ab6838efb17bc28f201 | ... |
| a42574709f27484fade7adc76683b2cf  | 2828.6215589938 | 6       | exploitation| 96c7140054154a20a4b67fd986658dd4 | ... |
| 883f6de275c14b32822feb5cdaaba55d  | 2837.3500311898 | 6       | exploitation| a42574709f27484fade7adc76683b2cf | ... |


#### Individual Solution Directories

Each solution has a dedicated directory named after its ID, containing:

- `prompt.md`: The prompt sent to the LLM.
- `solution.txt`: The code generated by the LLM.
- `description.txt`: The LLM's own explanation of the changes it made.
- `metadata.json`: Metadata including ID and score.
- `*_stdout.txt` and `*_strerr.txt`: Program logs for debugging.


## Evolved Solutions

### Traveling Salesman Problem (TSP)

#### Problem Summary

The Traveling Salesman Problem (TSP) requires finding the shortest possible route visiting each city exactly once and returning to the start. It’s a standard benchmark for combinatorial optimization algorithms.

The full problem description is in [examples/tsp/problem.md](examples/tsp/problem.md).

#### Initial Solution

The initial solution simply generates a random permutation of cities:

```python
from datetime import timedelta
import random
from context import Context


def solve(context: Context) -> None:
    num_cities = len(context.instance)

    while context.remaining_time() > timedelta():
        # Generate a random solution (permutation of city indices)
        solution = random.sample(range(num_cities), num_cities)
        context.report_new_best_solution(solution)
        break  # since it's pointless to continue in this example
```

#### Evolved Algorithm

Through iterative evolution, Optiverse transformed the initial solution into an Iterated Local Search (ILS) variant, including:

- Intensification (improvement phase): 2-opt local search with delta evaluation for efficient improvements.
- Diversification (perturbation phase): Perturbation operators such as ruin-and-recreate, block moves, swaps, and double bridge moves.
- Performance optimizations: Precomputed distance matrix for faster evaluation.

#### Evolution Setup

- Iterations: ~300
- LLM model: Qwen3-235B-A22B


#### Evaluation Results

On a 280-city instance (3 runs of 30 seconds each), the evolved algorithm achieved an average tour length of 2593, within ~0.5% of the known optimal (2579).

#### Full Evolved Code

```python
import random
import math
from datetime import timedelta
from typing import List, Tuple
from context import Context

def compute_tour_length(tour: List[int], dist_matrix: List[List[float]]) -> float:
    length = 0.0
    n = len(tour)
    for i in range(n):
        a, b = tour[i], tour[(i+1)%n]
        length += dist_matrix[a][b]
    return length

def nearest_neighbor(instance: List[Tuple[float, float]], start: int, dist_matrix: List[List[float]], nearest_neighbors: List[List[int]]) -> List[int]:
    n = len(instance)
    visited = [False]*n
    tour, current = [start], start
    visited[start] = True
    while len(tour) < n:
        for city in nearest_neighbors[current]:
            if not visited[city]:
                next_city = city
                break
        tour.append(next_city)
        visited[next_city] = True
        current = next_city
    return tour

def two_opt(tour: List[int], dist_matrix: List[List[float]]) -> List[int]:
    tour = tour.copy()
    n = len(tour)
    improved = True
    while improved:
        improved = False
        for i in range(n-1):
            for j in range(i+2, n):
                a, b = tour[i], tour[i+1]
                c, d = tour[j-1], tour[j]
                if dist_matrix[a][b] + dist_matrix[c][d] > dist_matrix[a][c] + dist_matrix[b][d]:
                    tour[i+1:j] = tour[i+1:j][::-1]
                    improved = True
    return tour

def solve(context: Context) -> None:
    instance = context.instance
    n = len(instance)
    if n <= 1:
        context.report_new_best_solution(list(range(n)))
        return

    dist_matrix = [[math.hypot(x1-x2, y1-y2) for x2, y2 in instance] for x1, y1 in instance]

    nearest_neighbors = []
    for u in range(n):
        sorted_cities = sorted(range(n), key=lambda c: dist_matrix[u][c])
        nearest_neighbors.append(sorted_cities)

    best_solution, best_length = None, float('inf')
    start_points = [0, n//4, n//2, (3*n)//4, n-1]
    if n >= 8:
        start_points += random.sample(range(n), 3)

    for start in start_points:
        if context.remaining_time() <= timedelta(seconds=1):
            break
        tour = nearest_neighbor(instance, start, dist_matrix, nearest_neighbors)
        optimized = two_opt(tour, dist_matrix)
        current_length = compute_tour_length(optimized, dist_matrix)
        if current_length < best_length:
            best_solution, best_length = optimized, current_length
            context.report_new_best_solution(best_solution)

    while context.remaining_time() > timedelta(seconds=0.1) and best_solution:
        new_solution = best_solution.copy()
        move = random.random()
        n_cities = len(best_solution)

        if move < 0.2:  # Ruin and Recreate
            remove_count = max(2, int(n_cities * 0.2))
            removed = random.sample(best_solution, remove_count)
            current_tour = [city for city in best_solution if city not in removed]
            for city in removed:
                best_pos, best_delta = 0, float('inf')
                for i in range(len(current_tour)):
                    a = current_tour[i]
                    b = current_tour[(i+1) % len(current_tour)]
                    delta = dist_matrix[a][city] + dist_matrix[city][b] - dist_matrix[a][b]
                    if delta < best_delta:
                        best_delta, best_pos = delta, i
                current_tour.insert(best_pos + 1, city)
            new_solution = current_tour

        elif move < 0.4:  # Block move
            i = random.randint(0, n_cities-3)
            j = random.randint(i+1, n_cities-1)
            block = new_solution[i+1:j+1]
            if random.random() < 0.5:
                block = block[::-1]
            new_solution = new_solution[:i+1] + new_solution[j+1:]
            k = random.randint(0, len(new_solution)-1)
            new_solution = new_solution[:k+1] + block + new_solution[k+1:]

        elif move < 0.6:  # Swap
            i, j = random.sample(range(n_cities), 2)
            new_solution[i], new_solution[j] = new_solution[j], new_solution[i]

        else:  # Double bridge
            if n_cities < 4:
                i, j = random.sample(range(n_cities), 2)
                new_solution[i], new_solution[j] = new_solution[j], new_solution[i]
            else:
                a, b, c, d = sorted(random.sample(range(n_cities), 4))
                new_solution = (new_solution[:a+1] +
                               new_solution[c+1:d+1] +
                               new_solution[a+1:b+1][::-1] +
                               new_solution[b+1:c+1] +
                               new_solution[d+1:])

        optimized = two_opt(new_solution, dist_matrix)
        new_length = compute_tour_length(optimized, dist_matrix)
        if new_length < best_length:
            best_solution, best_length = optimized, new_length
            context.report_new_best_solution(best_solution)

    if best_solution is None:
        solution = list(range(n))
        random.shuffle(solution)
        context.report_new_best_solution(solution)
    else:
        final_tour = two_opt(best_solution, dist_matrix)
        context.report_new_best_solution(final_tour)
```

## License

Optiverse is open source and licensed under the GNU General Public License v3.0 (GPLv3).
