feat(genome): uniform crossover for hypothesis genomes
Aggiunge `uniform_crossover` che eredita ogni campo da p1 o p2 con probabilita' 0.5, popola `parent_ids=[p1.id, p2.id]` e incrementa la generazione. Deterministico dato lo stesso `random.Random` seed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from typing import Any
|
||||
|
||||
from .hypothesis import HypothesisAgentGenome, ModelTier
|
||||
|
||||
|
||||
def uniform_crossover(
|
||||
p1: HypothesisAgentGenome,
|
||||
p2: HypothesisAgentGenome,
|
||||
rng: random.Random,
|
||||
) -> HypothesisAgentGenome:
|
||||
"""Per ogni campo, eredita da p1 (prob 0.5) o p2."""
|
||||
|
||||
def pick(field: str) -> Any:
|
||||
return getattr(p1 if rng.random() < 0.5 else p2, field)
|
||||
|
||||
tier_pick: ModelTier = pick("model_tier")
|
||||
feature_pick: list[str] = pick("feature_access")
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
"system_prompt": pick("system_prompt"),
|
||||
"feature_access": list(feature_pick),
|
||||
"temperature": pick("temperature"),
|
||||
"top_p": pick("top_p"),
|
||||
"model_tier": tier_pick.value,
|
||||
"lookback_window": pick("lookback_window"),
|
||||
"cognitive_style": pick("cognitive_style"),
|
||||
"parent_ids": [p1.id, p2.id],
|
||||
"generation": max(p1.generation, p2.generation) + 1,
|
||||
}
|
||||
return HypothesisAgentGenome.from_dict(payload)
|
||||
@@ -0,0 +1,44 @@
|
||||
import random
|
||||
|
||||
from multi_swarm.genome.crossover import uniform_crossover
|
||||
from multi_swarm.genome.hypothesis import HypothesisAgentGenome, ModelTier
|
||||
|
||||
|
||||
def make(name: str) -> HypothesisAgentGenome:
|
||||
return HypothesisAgentGenome(
|
||||
system_prompt=f"prompt-{name}",
|
||||
feature_access=["close"] if name == "A" else ["close", "volume"],
|
||||
temperature=0.7 if name == "A" else 1.1,
|
||||
top_p=0.9,
|
||||
model_tier=ModelTier.C,
|
||||
lookback_window=100 if name == "A" else 300,
|
||||
cognitive_style="physicist" if name == "A" else "biologist",
|
||||
)
|
||||
|
||||
|
||||
def test_crossover_lineage() -> None:
|
||||
p1 = make("A")
|
||||
p2 = make("B")
|
||||
rng = random.Random(0)
|
||||
child = uniform_crossover(p1, p2, rng)
|
||||
assert sorted(child.parent_ids[-2:]) == sorted([p1.id, p2.id])
|
||||
assert child.generation == max(p1.generation, p2.generation) + 1
|
||||
|
||||
|
||||
def test_crossover_inherits_each_field_from_one_parent() -> None:
|
||||
p1 = make("A")
|
||||
p2 = make("B")
|
||||
rng = random.Random(0)
|
||||
child = uniform_crossover(p1, p2, rng)
|
||||
assert child.system_prompt in (p1.system_prompt, p2.system_prompt)
|
||||
assert child.temperature in (p1.temperature, p2.temperature)
|
||||
assert child.lookback_window in (p1.lookback_window, p2.lookback_window)
|
||||
assert child.cognitive_style in (p1.cognitive_style, p2.cognitive_style)
|
||||
|
||||
|
||||
def test_crossover_deterministic_with_same_seed() -> None:
|
||||
p1 = make("A")
|
||||
p2 = make("B")
|
||||
c1 = uniform_crossover(p1, p2, random.Random(42))
|
||||
c2 = uniform_crossover(p1, p2, random.Random(42))
|
||||
assert c1.to_dict() == c2.to_dict()
|
||||
Reference in New Issue
Block a user