Tasks
Tasks define the optimization problems that Cadence can solve. This document explains how to create custom tasks and work with existing ones.
Task Interface
All tasks inherit from the abstract Task class:
from abc import ABC, abstractmethod
class Task(ABC):
@property
@abstractmethod
def function_name(self) -> str:
"""Name of the function to extract from evolved code."""
@abstractmethod
def generate_inputs(self, seed: int):
"""Generate deterministic input for a given seed."""
@abstractmethod
def evaluate(self, output, input_data) -> float:
"""Evaluate function output, return scalar cost (lower=better)."""
@property
@abstractmethod
def baseline_program(self) -> str:
"""Return default working solution with marked blocks."""
def is_feasible(self, output, *args) -> bool:
"""Check if output is feasible (override if needed)."""
return True
Built-in Tasks
TSP Task
The Traveling Salesman Problem task is included as a reference implementation.
from src.tasks.tsp_task import TSPTask
# Create TSP task with 10 cities
task = TSPTask(n_cities=10)
# Generate test input
cities = task.generate_inputs(seed=42)
# Returns: [(x1, y1), (x2, y2), ..., (x10, y10)]
# Evaluate a solution
tour = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
result = task.evaluate(tour, cities)
# Returns: {"cost": 245.67, "feasible": True}
TSP Features: - Configurable number of cities - Euclidean distance calculation - Feasibility checking (valid permutation) - Deterministic city placement
Creating Custom Tasks
Step 1: Define the Problem
Create a new task class inheriting from Task:
# src/tasks/knapsack_task.py
import random
from src.task import Task
class KnapsackTask(Task):
def __init__(self, n_items=20, capacity=100):
self.n_items = n_items
self.capacity = capacity
@property
def function_name(self):
return "knapsack"
Step 2: Implement Input Generation
Generate deterministic test cases:
def generate_inputs(self, seed: int):
random.seed(seed)
# Generate items: (weight, value) pairs
items = []
for _ in range(self.n_items):
weight = random.randint(1, 20)
value = random.randint(1, 100)
items.append((weight, value))
return {
"items": items,
"capacity": self.capacity
}
Step 3: Implement Evaluation
Define how solutions are scored:
def evaluate(self, output, input_data) -> float:
items = input_data["items"]
capacity = input_data["capacity"]
if not self.is_feasible(output, input_data):
return {"cost": float("inf"), "feasible": False}
# Calculate total weight and value
total_weight = sum(items[i][0] for i in output)
total_value = sum(items[i][1] for i in output)
# Return negative value (since lower cost = better)
return {"cost": -total_value, "feasible": True}
def is_feasible(self, output, input_data):
items = input_data["items"]
capacity = input_data["capacity"]
# Check valid indices
if not all(0 <= i < len(items) for i in output):
return False
# Check capacity constraint
total_weight = sum(items[i][0] for i in output)
return total_weight <= capacity
Step 4: Define Baseline Program
Provide a working template with marked evolution blocks:
@property
def baseline_program(self) -> str:
return '''
def knapsack(items, capacity):
"""Solve knapsack problem."""
### START_BLOCK
# Simple greedy approach: highest value first
n = len(items)
indices = list(range(n))
indices.sort(key=lambda i: items[i][1], reverse=True)
selected = []
current_weight = 0
for i in indices:
weight, value = items[i]
if current_weight + weight <= capacity:
selected.append(i)
current_weight += weight
return selected
### END_BLOCK
'''
Step 5: Register and Use
# In main.py or experiment script
from src.tasks.knapsack_task import KnapsackTask
task = KnapsackTask(n_items=50, capacity=200)
# Use with evolution system...
Task Design Guidelines
Input Generation
Use Deterministic Seeds:
def generate_inputs(self, seed: int):
random.seed(seed) # Ensures reproducible inputs
# Generate test case...
Return Structured Data:
# Good: structured dictionary
return {
"nodes": graph_nodes,
"edges": graph_edges,
"constraints": constraints
}
# Avoid: positional arguments
return graph_nodes, graph_edges, constraints
Evaluation Metrics
Return Detailed Results:
def evaluate(self, output, input_data) -> dict:
return {
"cost": primary_objective,
"feasible": is_valid,
"secondary_metrics": {
"execution_time": time_taken,
"memory_usage": memory_used
}
}
Handle Edge Cases:
def evaluate(self, output, input_data) -> dict:
# Handle None/invalid output
if output is None:
return {"cost": float("inf"), "feasible": False}
# Handle exceptions gracefully
try:
cost = compute_cost(output, input_data)
except Exception as e:
return {"cost": float("inf"), "feasible": False, "error": str(e)}
return {"cost": cost, "feasible": True}
Baseline Programs
Include Evolution Blocks:
@property
def baseline_program(self) -> str:
return '''
def solve_problem(input_data):
"""Problem description."""
### START_BLOCK
# Initial solution approach
# This block will be evolved by LLM
### END_BLOCK
# Helper functions (not evolved)
def helper_function():
pass
### START_BLOCK
# Another evolvable section
### END_BLOCK
'''
Provide Working Solutions: Ensure the baseline program runs without errors:
# Test your baseline
task = YourTask()
baseline = task.baseline_program
inputs = task.generate_inputs(42)
# This should work
exec(baseline)
result = solve_problem(inputs)
evaluation = task.evaluate(result, inputs)
assert evaluation["feasible"] == True
Multi-Objective Tasks
For problems with multiple objectives:
class MultiObjectiveTask(Task):
def evaluate(self, output, input_data) -> dict:
obj1 = compute_objective1(output, input_data)
obj2 = compute_objective2(output, input_data)
# Weighted combination
cost = 0.7 * obj1 + 0.3 * obj2
return {
"cost": cost,
"feasible": self.is_feasible(output, input_data),
"objectives": {
"obj1": obj1,
"obj2": obj2
}
}
Testing Tasks
Create comprehensive tests for your tasks:
# tests/tasks/test_knapsack_task.py
import pytest
from src.tasks.knapsack_task import KnapsackTask
class TestKnapsackTask:
def test_input_generation(self):
task = KnapsackTask(n_items=10)
inputs = task.generate_inputs(42)
assert len(inputs["items"]) == 10
assert inputs["capacity"] == 100
# Test determinism
inputs2 = task.generate_inputs(42)
assert inputs == inputs2
def test_evaluation(self):
task = KnapsackTask(n_items=5, capacity=50)
inputs = task.generate_inputs(1)
# Test valid solution
solution = [0, 2, 4] # Select some items
result = task.evaluate(solution, inputs)
assert result["feasible"] == True
assert isinstance(result["cost"], (int, float))
def test_feasibility(self):
task = KnapsackTask(n_items=5, capacity=10)
inputs = {
"items": [(5, 10), (3, 6), (4, 8), (2, 4), (6, 12)],
"capacity": 10
}
assert task.is_feasible([0, 1], inputs) == True # weight=8, ok
assert task.is_feasible([0, 4], inputs) == False # weight=11, too heavy
assert task.is_feasible([5], inputs) == False # invalid index
Best Practices
- Keep it Simple: Start with basic implementations and iterate
- Test Thoroughly: Ensure baseline programs work correctly
- Document Well: Clear docstrings and comments
- Handle Errors: Graceful handling of invalid outputs
- Use Type Hints: Better code clarity and IDE support
- Benchmark: Compare evolved solutions against known optimal ones