Spaces:
Paused
Paused
File size: 6,261 Bytes
3d46076 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | """Versioned artificial genome schema (REAL, IMPLEMENTED).
v1.0: 15 body/development/behavior params (compat baseline — legacy snapshots
and canonical replays remain bit-exact and loadable).
v2.0: v1 params + 9 learning-architecture genes (STAGE I). Evolution can now
alter eligibility decay, neuromodulation weights, growth budget,
prediction influence, social-learning bias, sleep duration and
communication tendency — i.e. the learning architecture itself.
Version policy: validate() accepts exactly "1.0" (15 params) or "2.0" (24
params). NO silent migration — a v1 genome stays v1 (consumers apply runtime
defaults for absent genes), so old replays reproduce exactly.
"""
import hashlib
import json
from dataclasses import dataclass, field
from typing import Any, Dict
GENOME_VERSION = "2.0"
LEGACY_VERSION = "1.0"
SUPPORTED_VERSIONS = (LEGACY_VERSION, GENOME_VERSION)
V1_PARAMS: Dict[str, float] = {
"neurogenesis_rate": 0.3,
"differentiation_bias": 0.5,
"migration_rate": 0.4,
"axon_growth_rate": 0.5,
"dendrite_growth_rate": 0.5,
"synaptogenesis_rate": 0.5,
"pruning_threshold": 0.05,
"plasticity_rate": 0.05,
"memory_retention": 0.8,
"metabolism_rate": 0.01,
"exploration": 0.5,
"sociality": 0.5,
"teaching_ability": 0.5,
"reproduction_threshold": 0.6,
"developmental_timing": 0.5,
}
# STAGE I: learning-architecture genes (consumed by autonomy/v2 organisms)
ARCH_PARAMS: Dict[str, float] = {
"eligibility_decay": 0.9, # L2: trace persistence
"neuromod_novelty_weight": 0.3, # L2: novelty drives plasticity
"neuromod_prediction_weight": 0.3, # L2: surprise drives plasticity
"growth_budget_fraction": 0.5, # L6: metabolic allocation to growth
"prediction_gain": 0.5, # L5: attention steering by error
"social_learning_bias": 0.5, # L4: prefer trusted teachers
"sleep_duration": 0.4, # 0..1 -> 1..8 replay episodes
"communication_tendency": 0.4, # L4: produce grounded symbols
"curiosity_drive": 0.5, # L5: exploration scaling
}
DEFAULT_PARAMS: Dict[str, float] = dict(V1_PARAMS)
DEFAULT_PARAMS.update(ARCH_PARAMS)
V1_BOUNDS = {
"neurogenesis_rate": (0.0, 1.0),
"differentiation_bias": (0.0, 1.0),
"migration_rate": (0.0, 1.0),
"axon_growth_rate": (0.0, 1.0),
"dendrite_growth_rate": (0.0, 1.0),
"synaptogenesis_rate": (0.0, 1.0),
"pruning_threshold": (0.0, 0.5),
"plasticity_rate": (0.0, 0.5),
"memory_retention": (0.0, 1.0),
"metabolism_rate": (0.0, 0.1),
"exploration": (0.0, 1.0),
"sociality": (0.0, 1.0),
"teaching_ability": (0.0, 1.0),
"reproduction_threshold": (0.0, 1.0),
"developmental_timing": (0.0, 1.0),
}
ARCH_BOUNDS = {
"eligibility_decay": (0.0, 0.99),
"neuromod_novelty_weight": (0.0, 2.0),
"neuromod_prediction_weight": (0.0, 2.0),
"growth_budget_fraction": (0.0, 1.0),
"prediction_gain": (0.0, 1.0),
"social_learning_bias": (0.0, 1.0),
"sleep_duration": (0.0, 1.0),
"communication_tendency": (0.0, 1.0),
"curiosity_drive": (0.0, 1.0),
}
PARAM_BOUNDS: Dict[str, Any] = dict(V1_BOUNDS)
PARAM_BOUNDS.update(ARCH_BOUNDS)
V1_PARAM_NAMES = frozenset(V1_PARAMS.keys())
@dataclass
class Genome:
params: Dict[str, float] = field(default_factory=lambda: dict(DEFAULT_PARAMS))
version: str = GENOME_VERSION
lineage: Dict[str, Any] = field(default_factory=dict)
def _required_names(self) -> frozenset:
return V1_PARAM_NAMES if self.version == LEGACY_VERSION else \
frozenset(PARAM_BOUNDS.keys())
def validate(self) -> bool:
if self.version not in SUPPORTED_VERSIONS:
raise ValueError(f"Unsupported genome version: {self.version} "
f"(supported: {SUPPORTED_VERSIONS})")
required = self._required_names()
extra = set(self.params) - required
if extra:
raise ValueError(f"Genome version {self.version} does not allow "
f"params: {sorted(extra)}")
for k in required:
if k not in self.params:
raise ValueError(f"Missing genome param: {k}")
for k, v in self.params.items():
lo, hi = PARAM_BOUNDS[k]
v = float(v)
if not (lo <= v <= hi) or v != v: # NaN check via v!=v
raise ValueError(f"Param {k}={v} out of bounds [{lo},{hi}]")
return True
def genome_hash(self) -> str:
self.validate()
canon = json.dumps({"version": self.version,
"params": {k: round(float(self.params[k]), 6) for k in sorted(self.params)}},
sort_keys=True)
return hashlib.sha256(canon.encode()).hexdigest()
def to_dict(self) -> Dict[str, Any]:
return {"version": self.version, "params": dict(self.params), "lineage": dict(self.lineage)}
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "Genome":
g = cls(params=dict(d["params"]), version=d.get("version", GENOME_VERSION),
lineage=dict(d.get("lineage", {})))
g.validate()
return g
@classmethod
def founder(cls, seed: int = 42, legacy: bool = False) -> "Genome":
import numpy as np
rng = np.random.RandomState(seed)
# RNG consumption order MUST match the original implementation
# (dict insertion order: v1 params first) so legacy founders stay
# bit-identical with pre-v2 runs.
ordered = V1_PARAMS if legacy else DEFAULT_PARAMS
params = {}
for k in ordered:
lo, hi = PARAM_BOUNDS[k]
params[k] = float(lo + (hi - lo) * 0.5 + rng.normal(0, 0.02 * (hi - lo)))
params[k] = float(min(hi, max(lo, params[k])))
g = cls(params=params, version=LEGACY_VERSION if legacy else GENOME_VERSION,
lineage={"origin": "founder", "seed": seed})
g.validate()
return g
# Runtime defaults for genes absent in v1 genomes (explicit, not silent):
def get(self, name: str, default: float = 0.0) -> float:
return float(self.params.get(name, ARCH_PARAMS.get(name, default)))
|