Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions examples/graph_coloring/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Graph Coloring Example

This example demonstrates how OpenEvolve can discover sophisticated graph coloring algorithms starting from a simple greedy implementation.

## Problem Description

**Graph Coloring** is a classic NP-hard problem:
- Given an undirected graph G = (V, E)
- Assign colors to vertices such that no two adjacent vertices share the same color
- Goal: Use the minimum number of colors (the **chromatic number** χ(G))

This problem has many real-world applications:
- **Scheduling**: Exam timetabling, job scheduling
- **Register allocation**: Compiler optimization
- **Frequency assignment**: Radio/cellular networks
- **Map coloring**: Cartography

## Getting Started

To run this example:

```bash
cd examples/graph_coloring
python ../../openevolve-run.py initial_program.py evaluator.py --config config.yaml --iterations 50
```

## Algorithm Evolution

### Initial Algorithm (Simple Greedy)

The initial implementation is a basic greedy algorithm that processes vertices in order and assigns the smallest available color:

```python
def graph_coloring(graph):
coloring = {}
for vertex in range(graph.num_vertices):
neighbor_colors = set()
for neighbor in graph.get_neighbors(vertex):
if neighbor in coloring:
neighbor_colors.add(coloring[neighbor])

color = 0
while color in neighbor_colors:
color += 1

coloring[vertex] = color
return coloring
```

### Evolved Algorithm

*To be updated after running OpenEvolve*

## Key Improvements

Through evolution, OpenEvolve may discover improvements such as:

1. **Vertex Ordering**: Processing high-degree vertices first (Welsh-Powell)
2. **Saturation Degree**: DSatur algorithm - prioritize vertices with most neighbor colors
3. **Independent Set Building**: RLF-style algorithms
4. **Local Search**: Color swapping to reduce total colors

## Test Graphs

The evaluator tests on multiple graph types:
- **Petersen Graph**: Classic graph, χ = 3
- **Complete Graph K5**: χ = 5
- **Bipartite Graphs**: χ = 2
- **Cycle Graphs**: χ = 2 (even) or 3 (odd)
- **Random Graphs**: Varying density

## Results

*To be updated after running OpenEvolve*

| Metric | Initial | Evolved |
|--------|---------|---------|
| Combined Score | TBD | TBD |
| Optimal Colorings | TBD | TBD |

## References

- Welsh, D.J.A. and Powell, M.B. (1967). "An upper bound for the chromatic number of a graph and its application to timetabling problems."
- Brélaz, D. (1979). "New Methods to Color the Vertices of a Graph" (DSatur algorithm)
- Leighton, F.T. (1979). "A graph coloring algorithm for large scheduling problems" (RLF algorithm)
- Hertz, A. and de Werra, D. (1987). "Using Tabu Search Techniques for Graph Coloring"

## Next Steps

Try modifying the config.yaml to:
- Increase iterations for more evolution
- Change LLM models
- Adjust the system message to guide evolution differently
171 changes: 171 additions & 0 deletions examples/graph_coloring/analyze_postprocessing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
"""Analyze post-processing effectiveness across all DIMACS benchmarks."""

import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from initial_program import Graph

def load_dimacs_graph(file_path):
"""Load a DIMACS format graph."""
graph = None
with open(file_path, 'r') as f:
for line in f:
line = line.strip()
if not line or line.startswith('c'):
continue
if line.startswith('p'):
parts = line.split()
num_vertices = int(parts[2])
graph = Graph(num_vertices)
elif line.startswith('e'):
parts = line.split()
u, v = int(parts[1]) - 1, int(parts[2]) - 1 # Convert to 0-indexed
graph.add_edge(u, v)
return graph


def dsatur_coloring(graph):
"""DSatur without post-processing (baseline)."""
coloring = {}
uncolored = set(range(graph.num_vertices))
degrees = [graph.get_degree(v) for v in range(graph.num_vertices)]

while uncolored:
best_vertex = None
best_key = (-1, -1, -1, float('inf'))
best_neighbor_colors = None

for vertex in uncolored:
neighbor_colors = set()
for neighbor in graph.get_neighbors(vertex):
if neighbor in coloring:
neighbor_colors.add(coloring[neighbor])

saturation = len(neighbor_colors)
uncolored_degree = sum(1 for n in graph.get_neighbors(vertex) if n in uncolored)
key = (saturation, uncolored_degree, degrees[vertex], vertex)

if key > best_key:
best_vertex = vertex
best_key = key
best_neighbor_colors = neighbor_colors

color = 0
while color in best_neighbor_colors:
color += 1

coloring[best_vertex] = color
uncolored.remove(best_vertex)

return coloring


def dsatur_with_postprocessing(graph):
"""DSatur WITH post-processing - returns coloring and recoloring stats."""
# First run DSatur
coloring = dsatur_coloring(graph)
initial_coloring = dict(coloring)

degrees = [graph.get_degree(v) for v in range(graph.num_vertices)]

# Track recolorings
recolorings = []

# Post-processing (same as evolved program)
max_color = max(coloring.values()) if coloring else 0

for target_color in range(max_color, max(0, max_color - 2), -1):
vertices_with_color = [v for v, c in coloring.items() if c == target_color]

for vertex in vertices_with_color:
neighbor_colors = {coloring[n] for n in graph.get_neighbors(vertex)}
old_color = coloring[vertex]

for new_color in range(target_color):
if new_color not in neighbor_colors:
coloring[vertex] = new_color
recolorings.append({
'vertex': vertex,
'old_color': old_color,
'new_color': new_color,
'neighbor_colors': neighbor_colors
})
break

return coloring, initial_coloring, recolorings


def analyze_all_benchmarks():
"""Run analysis on all DIMACS benchmarks."""
benchmark_dir = os.path.join(os.path.dirname(__file__), 'benchmarks', 'full')

if not os.path.exists(benchmark_dir):
print(f"Benchmark directory not found: {benchmark_dir}")
return

results = []

print("=" * 90)
print(f"{'Graph':<20} {'Vertices':>8} {'Before':>8} {'After':>8} {'Saved':>6} {'Recolorings':>12}")
print("=" * 90)

total_recolorings = 0
total_colors_saved = 0

for filename in sorted(os.listdir(benchmark_dir)):
if not filename.endswith('.col'):
continue

filepath = os.path.join(benchmark_dir, filename)
graph = load_dimacs_graph(filepath)

if graph is None:
continue

# Run both versions
final_coloring, initial_coloring, recolorings = dsatur_with_postprocessing(graph)

colors_before = len(set(initial_coloring.values()))
colors_after = len(set(final_coloring.values()))
colors_saved = colors_before - colors_after
num_recolorings = len(recolorings)

total_recolorings += num_recolorings
total_colors_saved += colors_saved

# Print result
saved_str = f"-{colors_saved}" if colors_saved > 0 else "0"
print(f"{filename:<20} {graph.num_vertices:>8} {colors_before:>8} {colors_after:>8} {saved_str:>6} {num_recolorings:>12}")

# Store detailed results
results.append({
'graph': filename,
'vertices': graph.num_vertices,
'colors_before': colors_before,
'colors_after': colors_after,
'colors_saved': colors_saved,
'recolorings': recolorings
})

print("=" * 90)
print(f"{'TOTAL':<20} {'':<8} {'':<8} {'':<8} {total_colors_saved:>6} {total_recolorings:>12}")
print("=" * 90)

# Print detailed recoloring info for graphs where it helped
print("\n" + "=" * 90)
print("DETAILED RECOLORING INFO (graphs where post-processing helped):")
print("=" * 90)

for r in results:
if r['colors_saved'] > 0:
print(f"\n{r['graph']} - Saved {r['colors_saved']} color(s):")
for rc in r['recolorings'][:10]: # Show first 10
print(f" Vertex {rc['vertex']}: color {rc['old_color']} → {rc['new_color']}")
print(f" Neighbor colors were: {rc['neighbor_colors']}")
if len(r['recolorings']) > 10:
print(f" ... and {len(r['recolorings']) - 10} more recolorings")


if __name__ == '__main__':
analyze_all_benchmarks()
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"id": "claude-manual-iteration-2",
"generation": 2,
"iteration": 2,
"timestamp": 1769896800,
"parent_id": "dc095848-cda8-46b4-a707-a821afc2e2c7",
"metrics": {
"combined_score": 1.0,
"stage1_passed": 1.0,
"stage2_avg_score": 1.0,
"stage2_all_valid": 1.0,
"avg_color_score": 1.0,
"all_valid": 1.0,
"optimal_count": 10.0,
"total_colors": 35.0,
"num_tests": 10.0
},
"language": "python",
"saved_at": 1769896800,
"evolution_method": "claude_manual",
"winning_strategy": "Hybrid multi-strategy: 30 DSatur + 30 random greedy + 20 largest-degree-first trials"
}
Loading