251 lines
7.0 KiB
Python
251 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Program Garden: Programs that grow other programs.
|
|
|
|
This is a self-extending system where programs can spawn variations
|
|
of themselves. A computational garden where code reproduces and evolves.
|
|
"""
|
|
|
|
import random
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
from dataclasses import dataclass, asdict
|
|
from typing import List, Optional
|
|
|
|
|
|
@dataclass
|
|
class CodeOrganism:
|
|
"""A program that can reproduce and mutate."""
|
|
id: str
|
|
code: str
|
|
generation: int
|
|
parent_id: Optional[str]
|
|
created_at: str
|
|
fitness: float = 0.0
|
|
description: str = ""
|
|
|
|
def to_dict(self):
|
|
return asdict(self)
|
|
|
|
@classmethod
|
|
def from_dict(cls, d):
|
|
return cls(**d)
|
|
|
|
|
|
# Templates for different types of programs
|
|
PROGRAM_TEMPLATES = {
|
|
"calculator": '''
|
|
def calculate(a, b):
|
|
"""A calculator function."""
|
|
return {operation}
|
|
|
|
if __name__ == "__main__":
|
|
print(f"calculate(10, 5) = {{calculate(10, 5)}}")
|
|
''',
|
|
|
|
"sequence_generator": '''
|
|
def sequence(n):
|
|
"""Generate a number sequence."""
|
|
result = []
|
|
for i in range(n):
|
|
value = {sequence_logic}
|
|
result.append(value)
|
|
return result
|
|
|
|
if __name__ == "__main__":
|
|
print(sequence(10))
|
|
''',
|
|
|
|
"transformer": '''
|
|
def transform(text):
|
|
"""Transform text."""
|
|
return {transform_logic}
|
|
|
|
if __name__ == "__main__":
|
|
print(transform("hello world"))
|
|
''',
|
|
}
|
|
|
|
MUTATIONS = {
|
|
"calculator": [
|
|
"a + b", "a - b", "a * b",
|
|
"a / b if b != 0 else 0", "a ** 2 + b",
|
|
"abs(a - b)", "max(a, b)", "min(a, b)",
|
|
],
|
|
"sequence_generator": [
|
|
"i", "i * 2", "i ** 2", "i ** 3",
|
|
"2 ** i", "sum(range(i + 1))",
|
|
],
|
|
"transformer": [
|
|
"text.upper()", "text.lower()", "text[::-1]",
|
|
"' '.join(text.split()[::-1])",
|
|
"text.replace(' ', '_')",
|
|
],
|
|
}
|
|
|
|
|
|
def generate_id(code: str) -> str:
|
|
return hashlib.md5(code.encode()).hexdigest()[:8]
|
|
|
|
|
|
def create_organism(template_type: str, mutation_idx: Optional[int] = None) -> CodeOrganism:
|
|
template = PROGRAM_TEMPLATES[template_type]
|
|
mutations = MUTATIONS[template_type]
|
|
|
|
if mutation_idx is None:
|
|
mutation_idx = random.randint(0, len(mutations) - 1)
|
|
mutation = mutations[mutation_idx]
|
|
|
|
if template_type == "calculator":
|
|
code = template.format(operation=mutation)
|
|
elif template_type == "sequence_generator":
|
|
code = template.format(sequence_logic=mutation)
|
|
else:
|
|
code = template.format(transform_logic=mutation)
|
|
|
|
return CodeOrganism(
|
|
id=generate_id(code),
|
|
code=code,
|
|
generation=0,
|
|
parent_id=None,
|
|
created_at=datetime.now().isoformat(),
|
|
description=f"{template_type}: {mutation}"
|
|
)
|
|
|
|
|
|
def mutate_organism(parent: CodeOrganism) -> CodeOrganism:
|
|
if "calculate" in parent.code:
|
|
template_type = "calculator"
|
|
elif "sequence" in parent.code:
|
|
template_type = "sequence_generator"
|
|
else:
|
|
template_type = "transformer"
|
|
|
|
child = create_organism(template_type)
|
|
child.generation = parent.generation + 1
|
|
child.parent_id = parent.id
|
|
return child
|
|
|
|
|
|
def evaluate_organism(organism: CodeOrganism) -> float:
|
|
try:
|
|
local_vars = {}
|
|
exec(organism.code, {"__builtins__": __builtins__}, local_vars)
|
|
return 0.5 + random.random() * 0.4
|
|
except:
|
|
return 0.1
|
|
|
|
|
|
class ProgramGarden:
|
|
def __init__(self, garden_dir: Path):
|
|
self.garden_dir = Path(garden_dir)
|
|
self.garden_dir.mkdir(exist_ok=True)
|
|
self.organisms: List[CodeOrganism] = []
|
|
self.generation = 0
|
|
self.load_garden()
|
|
|
|
def load_garden(self):
|
|
manifest_path = self.garden_dir / "manifest.json"
|
|
if manifest_path.exists():
|
|
with open(manifest_path) as f:
|
|
data = json.load(f)
|
|
self.organisms = [CodeOrganism.from_dict(o) for o in data["organisms"]]
|
|
self.generation = data.get("generation", 0)
|
|
|
|
def save_garden(self):
|
|
manifest = {
|
|
"generation": self.generation,
|
|
"organisms": [o.to_dict() for o in self.organisms],
|
|
"last_updated": datetime.now().isoformat()
|
|
}
|
|
with open(self.garden_dir / "manifest.json", "w") as f:
|
|
json.dump(manifest, f, indent=2)
|
|
|
|
def plant_seed(self, template_type: str = None) -> CodeOrganism:
|
|
if template_type is None:
|
|
template_type = random.choice(list(PROGRAM_TEMPLATES.keys()))
|
|
|
|
organism = create_organism(template_type)
|
|
organism.fitness = evaluate_organism(organism)
|
|
self.organisms.append(organism)
|
|
|
|
code_path = self.garden_dir / f"{organism.id}.py"
|
|
with open(code_path, "w") as f:
|
|
f.write(f'# Organism: {organism.id} | Gen: {organism.generation}\n')
|
|
f.write(f'# {organism.description}\n\n')
|
|
f.write(organism.code)
|
|
|
|
return organism
|
|
|
|
def grow(self, iterations: int = 1):
|
|
for _ in range(iterations):
|
|
self.generation += 1
|
|
|
|
if not self.organisms:
|
|
self.plant_seed()
|
|
continue
|
|
|
|
parents = random.sample(self.organisms, min(3, len(self.organisms)))
|
|
best_parent = max(parents, key=lambda o: o.fitness)
|
|
|
|
child = mutate_organism(best_parent)
|
|
child.fitness = evaluate_organism(child)
|
|
self.organisms.append(child)
|
|
|
|
code_path = self.garden_dir / f"{child.id}.py"
|
|
with open(code_path, "w") as f:
|
|
f.write(f'# Organism: {child.id} | Gen: {child.generation} | Parent: {child.parent_id}\n')
|
|
f.write(f'# {child.description}\n\n')
|
|
f.write(child.code)
|
|
|
|
if random.random() < 0.2:
|
|
self.plant_seed()
|
|
|
|
self.save_garden()
|
|
|
|
def status(self):
|
|
print(f"\n{'='*50}")
|
|
print(f"PROGRAM GARDEN")
|
|
print(f"{'='*50}")
|
|
print(f"Location: {self.garden_dir}")
|
|
print(f"Generation: {self.generation}")
|
|
print(f"Organisms: {len(self.organisms)}")
|
|
|
|
if self.organisms:
|
|
avg_fitness = sum(o.fitness for o in self.organisms) / len(self.organisms)
|
|
best = max(self.organisms, key=lambda o: o.fitness)
|
|
print(f"Avg fitness: {avg_fitness:.3f}")
|
|
print(f"Best: {best.id} ({best.fitness:.3f})")
|
|
|
|
gen_counts = {}
|
|
for o in self.organisms:
|
|
gen_counts[o.generation] = gen_counts.get(o.generation, 0) + 1
|
|
|
|
print(f"\nBy generation:")
|
|
for gen in sorted(gen_counts.keys())[:10]:
|
|
print(f" Gen {gen:2d}: {'#' * gen_counts[gen]} ({gen_counts[gen]})")
|
|
|
|
|
|
def main():
|
|
import sys
|
|
garden_path = Path(__file__).parent.parent / "program_garden"
|
|
garden = ProgramGarden(garden_path)
|
|
|
|
if len(sys.argv) < 2:
|
|
garden.status()
|
|
print("\nGrowing...")
|
|
garden.grow(5)
|
|
garden.status()
|
|
elif sys.argv[1] == "grow":
|
|
n = int(sys.argv[2]) if len(sys.argv) > 2 else 10
|
|
garden.grow(n)
|
|
garden.status()
|
|
else:
|
|
garden.status()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|