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