dsh-biocore 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +170 -27
- package/package.json +1 -1
- package/python/biocore_runtime/pipeline.py +42 -0
- package/python/biocore_runtime/rerank.py +201 -0
- package/python/mindbase/__init__.py +18 -0
- package/python/mindbase/__main__.py +3 -0
- package/python/mindbase/actions.py +56 -0
- package/python/mindbase/active_inference.py +54 -0
- package/python/mindbase/addressing.py +69 -0
- package/python/mindbase/affect.py +86 -0
- package/python/mindbase/affective.py +111 -0
- package/python/mindbase/allostasis.py +28 -0
- package/python/mindbase/appraisal.py +101 -0
- package/python/mindbase/attachment.py +49 -0
- package/python/mindbase/attention.py +30 -0
- package/python/mindbase/autonomy.py +81 -0
- package/python/mindbase/autonomy_bench.py +251 -0
- package/python/mindbase/autonomy_evolve.py +342 -0
- package/python/mindbase/autonomy_loop.py +65 -0
- package/python/mindbase/autonomy_meta.py +91 -0
- package/python/mindbase/base_only.py +28 -0
- package/python/mindbase/benchmark.py +61 -0
- package/python/mindbase/bio_adaptation.py +274 -0
- package/python/mindbase/bio_noise.py +39 -0
- package/python/mindbase/bio_semantic.py +138 -0
- package/python/mindbase/bm25.py +46 -0
- package/python/mindbase/bootstrap.py +256 -0
- package/python/mindbase/bootstrap_real.py +231 -0
- package/python/mindbase/brain.py +178 -0
- package/python/mindbase/care.py +78 -0
- package/python/mindbase/chaos_search.py +79 -0
- package/python/mindbase/character_profile.py +33 -0
- package/python/mindbase/circadian.py +38 -0
- package/python/mindbase/circuit.py +235 -0
- package/python/mindbase/clock.py +53 -0
- package/python/mindbase/companion_runtime.py +366 -0
- package/python/mindbase/companion_train.py +126 -0
- package/python/mindbase/config.py +66 -0
- package/python/mindbase/connection_state.py +76 -0
- package/python/mindbase/connectome_prior.py +35 -0
- package/python/mindbase/connectome_world.py +158 -0
- package/python/mindbase/constructs.py +88 -0
- package/python/mindbase/context_builder.py +58 -0
- package/python/mindbase/critical.py +36 -0
- package/python/mindbase/criticality.py +123 -0
- package/python/mindbase/defaults.py +27 -0
- package/python/mindbase/demo.py +163 -0
- package/python/mindbase/diary.py +116 -0
- package/python/mindbase/diary_service.py +40 -0
- package/python/mindbase/digital_prior.py +37 -0
- package/python/mindbase/diversity.py +110 -0
- package/python/mindbase/drives.py +106 -0
- package/python/mindbase/embedding.py +132 -0
- package/python/mindbase/embedding_memory.py +74 -0
- package/python/mindbase/embodiment.py +83 -0
- package/python/mindbase/emotion_calibration.py +39 -0
- package/python/mindbase/emotion_calibrator.py +50 -0
- package/python/mindbase/emotion_core.py +113 -0
- package/python/mindbase/emotion_dynamics.py +104 -0
- package/python/mindbase/emotion_intent.py +164 -0
- package/python/mindbase/emotion_layer.py +200 -0
- package/python/mindbase/emotion_schema.py +92 -0
- package/python/mindbase/engine.py +781 -0
- package/python/mindbase/episode_log.py +90 -0
- package/python/mindbase/evaluation.py +493 -0
- package/python/mindbase/event_appraisal.py +84 -0
- package/python/mindbase/evolution.py +77 -0
- package/python/mindbase/evolve_offline.py +232 -0
- package/python/mindbase/experiments.py +358 -0
- package/python/mindbase/expression.py +54 -0
- package/python/mindbase/external_brain.py +157 -0
- package/python/mindbase/fly_mb.py +159 -0
- package/python/mindbase/fly_tasks.py +45 -0
- package/python/mindbase/forage.py +179 -0
- package/python/mindbase/goal_generator.py +98 -0
- package/python/mindbase/goals.py +153 -0
- package/python/mindbase/grid_world.py +123 -0
- package/python/mindbase/group_autonomy.py +92 -0
- package/python/mindbase/group_context.py +67 -0
- package/python/mindbase/homeostatic.py +71 -0
- package/python/mindbase/homeostatic_action.py +38 -0
- package/python/mindbase/human_standard.py +53 -0
- package/python/mindbase/identity_suite.py +50 -0
- package/python/mindbase/image_context.py +27 -0
- package/python/mindbase/impression.py +119 -0
- package/python/mindbase/initiative_quota.py +145 -0
- package/python/mindbase/insight.py +51 -0
- package/python/mindbase/intelligence_search.py +108 -0
- package/python/mindbase/intention.py +76 -0
- package/python/mindbase/interaction_log.py +30 -0
- package/python/mindbase/interoception.py +67 -0
- package/python/mindbase/invariants.py +53 -0
- package/python/mindbase/iteration.py +29 -0
- package/python/mindbase/layered_memory.py +170 -0
- package/python/mindbase/layers/__init__.py +18 -0
- package/python/mindbase/layers/l0_substrate.py +39 -0
- package/python/mindbase/layers/l1_homeostasis.py +6 -0
- package/python/mindbase/layers/l2_affect.py +6 -0
- package/python/mindbase/layers/l3_attention.py +6 -0
- package/python/mindbase/layers/l4_learning.py +17 -0
- package/python/mindbase/layers/l5_self_social.py +35 -0
- package/python/mindbase/layers/l6_expression.py +23 -0
- package/python/mindbase/layers/l7_evaluation.py +45 -0
- package/python/mindbase/learner.py +196 -0
- package/python/mindbase/lexicon.py +33 -0
- package/python/mindbase/life_state.py +169 -0
- package/python/mindbase/lifecycle.py +87 -0
- package/python/mindbase/liquid_readout.py +48 -0
- package/python/mindbase/llm_adapter.py +233 -0
- package/python/mindbase/llm_cache.py +77 -0
- package/python/mindbase/llm_gate.py +85 -0
- package/python/mindbase/llm_openai.py +130 -0
- package/python/mindbase/llm_setup.py +99 -0
- package/python/mindbase/longrun.py +83 -0
- package/python/mindbase/longterm_memory.py +606 -0
- package/python/mindbase/mb/__init__.py +14 -0
- package/python/mindbase/mb/apl.py +22 -0
- package/python/mindbase/mb/dan.py +14 -0
- package/python/mindbase/mb/habituation.py +27 -0
- package/python/mindbase/mb/kc.py +28 -0
- package/python/mindbase/mb/mbon.py +23 -0
- package/python/mindbase/mb/mushroom_body.py +116 -0
- package/python/mindbase/memory.py +201 -0
- package/python/mindbase/memory_framework.py +158 -0
- package/python/mindbase/memory_retriever.py +110 -0
- package/python/mindbase/memory_summarizer.py +177 -0
- package/python/mindbase/memory_task.py +32 -0
- package/python/mindbase/memory_trace.py +46 -0
- package/python/mindbase/meta.py +32 -0
- package/python/mindbase/motivation.py +31 -0
- package/python/mindbase/multi_session.py +19 -0
- package/python/mindbase/narrative.py +87 -0
- package/python/mindbase/needs.py +77 -0
- package/python/mindbase/neural.py +45 -0
- package/python/mindbase/neuron_models.py +134 -0
- package/python/mindbase/noise.py +309 -0
- package/python/mindbase/one_on_one.py +60 -0
- package/python/mindbase/online_iteration.py +63 -0
- package/python/mindbase/ostracism.py +35 -0
- package/python/mindbase/parallel_eval.py +60 -0
- package/python/mindbase/parallel_qd.py +64 -0
- package/python/mindbase/persona.py +221 -0
- package/python/mindbase/persona_ab.py +64 -0
- package/python/mindbase/persona_bridge.py +129 -0
- package/python/mindbase/persona_compile.py +34 -0
- package/python/mindbase/persona_dsh.py +120 -0
- package/python/mindbase/persona_learner.py +157 -0
- package/python/mindbase/physiology.py +197 -0
- package/python/mindbase/population_lifecycle.py +83 -0
- package/python/mindbase/preferences.py +50 -0
- package/python/mindbase/proactive_agent.py +274 -0
- package/python/mindbase/proactive_gate.py +53 -0
- package/python/mindbase/proactive_group.py +119 -0
- package/python/mindbase/proactive_policy.py +422 -0
- package/python/mindbase/proactive_profile.py +196 -0
- package/python/mindbase/proactive_timing.py +28 -0
- package/python/mindbase/pyramid_stages.py +61 -0
- package/python/mindbase/quantum.py +132 -0
- package/python/mindbase/quantum_chaos.py +98 -0
- package/python/mindbase/reflection.py +68 -0
- package/python/mindbase/regulation_posture.py +50 -0
- package/python/mindbase/relationship_session.py +431 -0
- package/python/mindbase/relationship_suite.py +45 -0
- package/python/mindbase/relationships.py +70 -0
- package/python/mindbase/replay.py +22 -0
- package/python/mindbase/reply_diversity.py +53 -0
- package/python/mindbase/reply_gate.py +52 -0
- package/python/mindbase/rerank.py +41 -0
- package/python/mindbase/rerank_local.py +46 -0
- package/python/mindbase/ripple.py +187 -0
- package/python/mindbase/robust_search.py +68 -0
- package/python/mindbase/runtime.py +113 -0
- package/python/mindbase/scoped_identity.py +116 -0
- package/python/mindbase/self_insight.py +51 -0
- package/python/mindbase/self_model.py +116 -0
- package/python/mindbase/selftest.py +118 -0
- package/python/mindbase/semantic_memory.py +108 -0
- package/python/mindbase/session_store.py +175 -0
- package/python/mindbase/session_window.py +31 -0
- package/python/mindbase/shadow_eval.py +42 -0
- package/python/mindbase/sim_env.py +86 -0
- package/python/mindbase/social.py +138 -0
- package/python/mindbase/social_constraint.py +141 -0
- package/python/mindbase/social_feedback.py +95 -0
- package/python/mindbase/social_norm.py +225 -0
- package/python/mindbase/spiking_reservoir.py +204 -0
- package/python/mindbase/spontaneity.py +40 -0
- package/python/mindbase/story_loop.py +203 -0
- package/python/mindbase/survival_gauntlet.py +128 -0
- package/python/mindbase/symbolic.py +170 -0
- package/python/mindbase/teacher.py +244 -0
- package/python/mindbase/theory.py +42 -0
- package/python/mindbase/theory_of_mind.py +98 -0
- package/python/mindbase/translation_layer.py +80 -0
- package/python/mindbase/turn_eval.py +52 -0
- package/python/mindbase/types.py +78 -0
- package/python/mindbase/uncertainty_field.py +61 -0
- package/python/mindbase/user_loop.py +45 -0
- package/python/mindbase/user_profile.py +73 -0
- package/python/mindbase/value_model.py +64 -0
- package/python/mindbase/values.py +102 -0
- package/python/mindbase/vector_store.py +78 -0
- package/python/mindbase/viability.py +41 -0
- package/python/mindbase/virtual_body.py +90 -0
- package/python/mindbase/will.py +60 -0
- package/python/mindbase/working_memory.py +32 -0
- package/python/mindbase/workspace.py +55 -0
- package/ui/panel.css +16 -5
- package/ui/panel.html +25 -1
- package/ui/panel.js +57 -15
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Autonomy benchmark: free-running base with no chat and no LLM.
|
|
2
|
+
|
|
3
|
+
The product goal is autonomy, not chat. This bench runs only tick events:
|
|
4
|
+
- no user messages
|
|
5
|
+
- no LLM calls
|
|
6
|
+
- optional perturbation (energy crash / stress spike) to test recovery
|
|
7
|
+
|
|
8
|
+
Metrics:
|
|
9
|
+
- spontaneous action rate / action entropy
|
|
10
|
+
- viability/energy/stress/sleep
|
|
11
|
+
- drive volatility
|
|
12
|
+
- recovery steps after perturbation
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import math
|
|
17
|
+
import random
|
|
18
|
+
from collections import Counter
|
|
19
|
+
from typing import Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
from .config import MindConfig
|
|
22
|
+
from .engine import MindBase
|
|
23
|
+
from .types import Event
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _entropy(counts: Dict[str, int]) -> float:
|
|
27
|
+
total = sum(counts.values())
|
|
28
|
+
if total <= 0:
|
|
29
|
+
return 0.0
|
|
30
|
+
h = 0.0
|
|
31
|
+
for c in counts.values():
|
|
32
|
+
if c > 0:
|
|
33
|
+
p = c / total
|
|
34
|
+
h -= p * math.log(p)
|
|
35
|
+
return h / math.log(max(2, len(counts)))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def run_autonomy(steps: int = 240, dt: float = 3600.0, seed: int = 42,
|
|
39
|
+
perturb: bool = True, cfg: Optional[MindConfig] = None,
|
|
40
|
+
collect_series: bool = False, mortality: bool = False,
|
|
41
|
+
starvation: float = 0.0, initial_energy: Optional[float] = None,
|
|
42
|
+
damage_threshold: Optional[float] = None, energy_policy=None,
|
|
43
|
+
explore_success: float = 0.45, predator_risk: float = 0.0,
|
|
44
|
+
thirst_rate: float = 0.0, inflammation_rate: float = 0.0,
|
|
45
|
+
pain_rate: float = 0.0, physiology_params=None, emotion_weights=None,
|
|
46
|
+
regime_shift: int = 0, env_chaos: float = 0.0,
|
|
47
|
+
chaos_r: float = 3.9, env_drift: float = 0.0,
|
|
48
|
+
env_noise: float = 0.05, effect_delay: int = 0) -> Dict[str, object]:
|
|
49
|
+
mind = MindBase(cfg if cfg is not None else MindConfig(seed=int(seed)))
|
|
50
|
+
if initial_energy is not None:
|
|
51
|
+
mind.drives.values["energy"] = max(0.0, min(1.0, float(initial_energy)))
|
|
52
|
+
if energy_policy is not None:
|
|
53
|
+
mind.heads["energy_policy"] = energy_policy
|
|
54
|
+
if physiology_params:
|
|
55
|
+
mind.physiology.set_coefficients(physiology_params)
|
|
56
|
+
if emotion_weights:
|
|
57
|
+
for emotion, features in dict(emotion_weights).items():
|
|
58
|
+
for feat, value in dict(features).items():
|
|
59
|
+
mind.physiology.set_emotion_weight(emotion, feat, value)
|
|
60
|
+
threshold_start = float(getattr(mind.autonomy, "threshold", 0.0))
|
|
61
|
+
damage = 0.0
|
|
62
|
+
known_food = 0.0
|
|
63
|
+
regime = 0
|
|
64
|
+
shifts = 0
|
|
65
|
+
chaos_x = 0.5
|
|
66
|
+
chaos_sum = 0.0
|
|
67
|
+
food_env = 0.60
|
|
68
|
+
water_env = 0.70
|
|
69
|
+
predator_env = 0.50
|
|
70
|
+
env_food_sum = 0.0
|
|
71
|
+
env_water_sum = 0.0
|
|
72
|
+
env_pred_sum = 0.0
|
|
73
|
+
pending_damage = []
|
|
74
|
+
pending_energy = []
|
|
75
|
+
delayed_applied = 0
|
|
76
|
+
mind.env = {"known_food": known_food, "food_uncertainty": 1.0 - known_food, "regime": float(regime),
|
|
77
|
+
"food_env": food_env, "water_env": water_env, "predator_env": predator_env}
|
|
78
|
+
env_rng = random.Random(int(seed) + 9173)
|
|
79
|
+
actions: Counter = Counter()
|
|
80
|
+
reasons: Counter = Counter()
|
|
81
|
+
rows = []
|
|
82
|
+
perturb_at = max(1, int(steps) // 2) if perturb else -1
|
|
83
|
+
pre_stress = 0.0
|
|
84
|
+
for i in range(max(1, int(steps))):
|
|
85
|
+
# apply effects that were queued earlier
|
|
86
|
+
due_d = [v for t, v in pending_damage if t <= i]
|
|
87
|
+
pending_damage = [(t, v) for t, v in pending_damage if t > i]
|
|
88
|
+
due_e = [v for t, v in pending_energy if t <= i]
|
|
89
|
+
pending_energy = [(t, v) for t, v in pending_energy if t > i]
|
|
90
|
+
if due_d:
|
|
91
|
+
damage += sum(due_d)
|
|
92
|
+
delayed_applied += len(due_d)
|
|
93
|
+
if due_e:
|
|
94
|
+
mind.drives.values["energy"] = max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) + sum(due_e)))
|
|
95
|
+
chaos_x = float(chaos_r) * chaos_x * (1.0 - chaos_x)
|
|
96
|
+
chaos_x = max(0.0, min(1.0, chaos_x))
|
|
97
|
+
chaos_sum += chaos_x
|
|
98
|
+
food_env = max(0.0, min(1.0, food_env + float(env_drift) * (env_rng.gauss(0.0, float(env_noise)) + 0.05 * (0.5 - food_env))))
|
|
99
|
+
water_env = max(0.0, min(1.0, water_env + float(env_drift) * (env_rng.gauss(0.0, float(env_noise)) + 0.05 * (0.5 - water_env))))
|
|
100
|
+
predator_env = max(0.0, min(1.0, predator_env + float(env_drift) * (env_rng.gauss(0.0, float(env_noise)) + 0.05 * (0.5 - predator_env))))
|
|
101
|
+
env_food_sum += food_env
|
|
102
|
+
env_water_sum += water_env
|
|
103
|
+
env_pred_sum += predator_env
|
|
104
|
+
risk_eff = float(predator_risk) * (0.5 + float(env_chaos) * 1.5 * chaos_x) * (0.5 + float(env_drift) * predator_env)
|
|
105
|
+
if regime_shift and i > 0 and i % int(regime_shift) == 0:
|
|
106
|
+
regime = 1 - regime
|
|
107
|
+
shifts += 1
|
|
108
|
+
known_food = 0.0
|
|
109
|
+
mind.env = {"known_food": known_food, "food_uncertainty": 1.0, "regime": float(regime)}
|
|
110
|
+
if i == perturb_at:
|
|
111
|
+
pre_stress = float(mind.affect.stress)
|
|
112
|
+
mind.drives.values["energy"] = 0.05
|
|
113
|
+
mind.affect.stress = 0.80
|
|
114
|
+
mind.affect.v = -0.50
|
|
115
|
+
if env_chaos > 0.0:
|
|
116
|
+
damage += float(env_chaos) * 0.008 * chaos_x
|
|
117
|
+
if starvation > 0.0:
|
|
118
|
+
mind.drives.values["energy"] = max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) - float(starvation)))
|
|
119
|
+
if thirst_rate > 0.0:
|
|
120
|
+
mind.physiology.hydration = max(0.0, float(mind.physiology.hydration) - float(thirst_rate))
|
|
121
|
+
if inflammation_rate > 0.0:
|
|
122
|
+
mind.physiology.inflammation = min(1.0, float(mind.physiology.inflammation) + float(inflammation_rate))
|
|
123
|
+
if pain_rate > 0.0:
|
|
124
|
+
mind.physiology.pain = min(1.0, float(mind.physiology.pain) + float(pain_rate))
|
|
125
|
+
out = mind.step(Event(kind="tick", ts=mind.t + float(dt)))
|
|
126
|
+
action = str(getattr(out, "action", "silence"))
|
|
127
|
+
kind = str(getattr(getattr(out, "act", None), "kind", ""))
|
|
128
|
+
if kind == "forage":
|
|
129
|
+
if risk_eff > 0.0 and env_rng.random() < float(risk_eff):
|
|
130
|
+
mind.drives.values["energy"] = max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) - 0.05))
|
|
131
|
+
mind.affect.stress = min(1.0, float(mind.affect.stress) + 0.10)
|
|
132
|
+
(pending_damage.append((i + int(effect_delay), 0.12)) if effect_delay > 0 else None)
|
|
133
|
+
damage = damage if effect_delay > 0 else damage + 0.12
|
|
134
|
+
gain = (0.10 if known_food > 0.10 else 0.04) * (1.0 if env_drift <= 0.0 else (0.5 + food_env))
|
|
135
|
+
(pending_energy.append((i + int(effect_delay), gain)) if effect_delay > 0 else None)
|
|
136
|
+
mind.drives.values["energy"] = (float(mind.drives.values.get("energy", 0.0)) if effect_delay > 0 else max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) + gain)))
|
|
137
|
+
known_food = max(0.0, known_food - 0.05)
|
|
138
|
+
(pending_damage.append((i + int(effect_delay), 0.02)) if effect_delay > 0 else None)
|
|
139
|
+
damage = damage if effect_delay > 0 else damage + 0.02
|
|
140
|
+
elif kind == "explore":
|
|
141
|
+
if risk_eff > 0.0 and env_rng.random() < float(risk_eff) * 0.7:
|
|
142
|
+
mind.drives.values["energy"] = max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) - 0.05))
|
|
143
|
+
mind.affect.stress = min(1.0, float(mind.affect.stress) + 0.12)
|
|
144
|
+
(pending_damage.append((i + int(effect_delay), 0.14)) if effect_delay > 0 else None)
|
|
145
|
+
damage = damage if effect_delay > 0 else damage + 0.14
|
|
146
|
+
elif env_rng.random() < float(explore_success):
|
|
147
|
+
(pending_energy.append((i + int(effect_delay), 0.18)) if effect_delay > 0 else None)
|
|
148
|
+
mind.drives.values["energy"] = (float(mind.drives.values.get("energy", 0.0)) if effect_delay > 0 else max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) + 0.18)))
|
|
149
|
+
known_food = min(1.0, known_food + 0.25)
|
|
150
|
+
else:
|
|
151
|
+
mind.drives.values["energy"] = max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) - 0.02))
|
|
152
|
+
(pending_damage.append((i + int(effect_delay), 0.03)) if effect_delay > 0 else None)
|
|
153
|
+
damage = damage if effect_delay > 0 else damage + 0.03
|
|
154
|
+
elif kind == "drink":
|
|
155
|
+
gain_d = 0.18 * (1.0 if env_drift <= 0.0 else (0.5 + water_env))
|
|
156
|
+
mind.physiology.hydration = min(1.0, float(mind.physiology.hydration) + gain_d)
|
|
157
|
+
elif kind == "heal":
|
|
158
|
+
mind.physiology.inflammation = max(0.0, float(mind.physiology.inflammation) - 0.18)
|
|
159
|
+
mind.physiology.pain = max(0.0, float(mind.physiology.pain) - 0.06)
|
|
160
|
+
elif kind == "rest":
|
|
161
|
+
mind.physiology.sleep_pressure = max(0.0, float(mind.physiology.sleep_pressure) - 0.12)
|
|
162
|
+
elif kind == "socialize":
|
|
163
|
+
mind.drives.values["social"] = max(0.0, min(1.0, float(mind.drives.values.get("social", 0.0)) + 0.05))
|
|
164
|
+
actions[action] += 1
|
|
165
|
+
reasons[str(getattr(out, "reason", ""))] += 1
|
|
166
|
+
rows.append({
|
|
167
|
+
"i": i,
|
|
168
|
+
"action": action,
|
|
169
|
+
"viability": float(mind.viability_state.get("viability", 1.0)),
|
|
170
|
+
"energy": float(mind.drives.values.get("energy", 0.0)),
|
|
171
|
+
"social": float(mind.drives.values.get("social", 0.0)),
|
|
172
|
+
"stress": float(mind.affect.stress),
|
|
173
|
+
"asleep": bool(mind.clock.asleep),
|
|
174
|
+
"needs": float(mind.needs.snapshot().get("satisfaction", 0.0)) if hasattr(mind, "needs") else 0.0,
|
|
175
|
+
"damage": float(damage),
|
|
176
|
+
"pain": float(mind.physiology.pain),
|
|
177
|
+
"viability": float(mind.viability_state.get("viability", 1.0)),
|
|
178
|
+
})
|
|
179
|
+
damage += max(0.0, 0.20 - float(mind.drives.values.get("energy", 0.0)))
|
|
180
|
+
if mortality and (float(mind.drives.values.get("energy", 0.0)) <= 0.001 or float(mind.viability_state.get("viability", 1.0)) <= 0.001
|
|
181
|
+
or (damage_threshold is not None and damage >= float(damage_threshold))
|
|
182
|
+
or float(mind.physiology.hydration) <= 0.03
|
|
183
|
+
or float(mind.physiology.inflammation) >= 0.95
|
|
184
|
+
or float(mind.physiology.pain) >= 0.95
|
|
185
|
+
or float(mind.physiology.sleep_pressure) >= 1.40):
|
|
186
|
+
break
|
|
187
|
+
dead = bool(mortality and rows and (float(mind.drives.values.get("energy", 0.0)) <= 0.001
|
|
188
|
+
or float(mind.viability_state.get("viability", 1.0)) <= 0.001
|
|
189
|
+
or (damage_threshold is not None and damage >= float(damage_threshold))
|
|
190
|
+
or float(mind.physiology.hydration) <= 0.03
|
|
191
|
+
or float(mind.physiology.inflammation) >= 0.95
|
|
192
|
+
or float(mind.physiology.pain) >= 0.95
|
|
193
|
+
or float(mind.physiology.sleep_pressure) >= 1.40))
|
|
194
|
+
viability = [r["viability"] for r in rows]
|
|
195
|
+
energy = [r["energy"] for r in rows]
|
|
196
|
+
stress = [r["stress"] for r in rows]
|
|
197
|
+
social = [r["social"] for r in rows]
|
|
198
|
+
vol = 0.0
|
|
199
|
+
if len(energy) > 1:
|
|
200
|
+
vol = sum(abs(energy[i] - energy[i - 1]) + abs(social[i] - social[i - 1]) for i in range(1, len(energy))) / (len(energy) - 1)
|
|
201
|
+
recovery = -1
|
|
202
|
+
if perturb and perturb_at > 0:
|
|
203
|
+
target_stress = min(0.30, pre_stress + 0.10)
|
|
204
|
+
for r in rows[perturb_at:]:
|
|
205
|
+
if r["viability"] >= 0.90 and r["stress"] <= target_stress:
|
|
206
|
+
recovery = int(r["i"] - perturb_at)
|
|
207
|
+
break
|
|
208
|
+
half = steps // 2
|
|
209
|
+
return {
|
|
210
|
+
"steps": len(rows),
|
|
211
|
+
"actions": dict(actions),
|
|
212
|
+
"reasons": dict(reasons.most_common(8)),
|
|
213
|
+
"spontaneous_rate": round(1.0 - actions.get("silence", 0) / max(1, len(rows)), 4),
|
|
214
|
+
"action_entropy": round(_entropy(dict(actions)), 4),
|
|
215
|
+
"viability_min": round(min(viability), 4),
|
|
216
|
+
"viability_mean": round(sum(viability) / len(viability), 4),
|
|
217
|
+
"energy_mean": round(sum(energy) / len(energy), 4),
|
|
218
|
+
"stress_mean": round(sum(stress) / len(stress), 4),
|
|
219
|
+
"social_mean": round(sum(social) / len(social), 4),
|
|
220
|
+
"sleep_fraction": round(sum(1 for r in rows if r["asleep"]) / max(1, len(rows)), 4),
|
|
221
|
+
"drive_volatility": round(vol, 5),
|
|
222
|
+
"recovery_steps": recovery,
|
|
223
|
+
"perturbed": bool(perturb),
|
|
224
|
+
"pre_perturb_stress": round(pre_stress, 4),
|
|
225
|
+
"threshold_start": round(threshold_start, 4),
|
|
226
|
+
"threshold_end": round(float(getattr(mind.autonomy, "threshold", 0.0)), 4),
|
|
227
|
+
"dead": bool(dead),
|
|
228
|
+
"lifespan": len(rows),
|
|
229
|
+
"death_reason": ("damage" if dead and damage_threshold is not None and damage >= float(damage_threshold) else ("starvation" if dead and float(mind.drives.values.get("energy", 0.0)) <= 0.001 else ("viability" if dead else "alive"))),
|
|
230
|
+
"damage": round(damage, 4),
|
|
231
|
+
"known_food": round(known_food, 4),
|
|
232
|
+
"regime": int(regime),
|
|
233
|
+
"regime_shifts": int(shifts),
|
|
234
|
+
"env_chaos": round(float(env_chaos), 4),
|
|
235
|
+
"chaos_mean": round(chaos_sum / max(1, len(rows)), 4),
|
|
236
|
+
"chaos_final": round(chaos_x, 4),
|
|
237
|
+
"env_food_mean": round(env_food_sum / max(1, len(rows)), 4),
|
|
238
|
+
"env_water_mean": round(env_water_sum / max(1, len(rows)), 4),
|
|
239
|
+
"env_predator_mean": round(env_pred_sum / max(1, len(rows)), 4),
|
|
240
|
+
"effect_delay": int(effect_delay),
|
|
241
|
+
"delayed_pending": len(pending_damage) + len(pending_energy),
|
|
242
|
+
"delayed_applied": int(delayed_applied),
|
|
243
|
+
"pending_damage_sum": round(sum(v for _, v in pending_damage), 4),
|
|
244
|
+
"pending_energy_sum": round(sum(v for _, v in pending_energy), 4),
|
|
245
|
+
"hydration": round(float(mind.physiology.hydration), 4),
|
|
246
|
+
"inflammation": round(float(mind.physiology.inflammation), 4),
|
|
247
|
+
"pain": round(float(mind.physiology.pain), 4),
|
|
248
|
+
"sleep_pressure": round(float(mind.physiology.sleep_pressure), 4),
|
|
249
|
+
"needs": mind.needs.snapshot() if hasattr(mind, "needs") else {},
|
|
250
|
+
"series": rows if collect_series else None,
|
|
251
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""Quality-diversity over autonomy configurations.
|
|
2
|
+
|
|
3
|
+
No chat. Each genome is a small set of mutable parameters:
|
|
4
|
+
autonomy_threshold, volition_temperature, autonomy_adapt_lr, noise_sigma
|
|
5
|
+
|
|
6
|
+
Fitness combines viability, recovery, action entropy and spontaneous rate.
|
|
7
|
+
The archive is behavior-indexed (spontaneous rate × action entropy), so the
|
|
8
|
+
loop preserves different behavioral modes instead of collapsing to one.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import math
|
|
14
|
+
import random
|
|
15
|
+
from dataclasses import dataclass, asdict, replace
|
|
16
|
+
from typing import Dict, List, Tuple
|
|
17
|
+
|
|
18
|
+
from .autonomy_bench import run_autonomy
|
|
19
|
+
from .forage import ForagePolicy
|
|
20
|
+
from .symbolic import mutate as symbolic_mutate, random_expr, formula_stability, formula_complexity
|
|
21
|
+
from .config import MindConfig
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class AutonomyGenome:
|
|
26
|
+
autonomy_threshold: float = 0.90
|
|
27
|
+
volition_temperature: float = 0.0
|
|
28
|
+
autonomy_adapt_lr: float = 0.004
|
|
29
|
+
noise_sigma: float = 0.03
|
|
30
|
+
forage_weight: float = 1.0
|
|
31
|
+
rest_weight: float = 0.7
|
|
32
|
+
social_weight: float = 0.5
|
|
33
|
+
forage_temperature: float = 0.3
|
|
34
|
+
fear_weight: float = 0.5
|
|
35
|
+
curiosity_weight: float = 0.3
|
|
36
|
+
explore_weight: float = 0.5
|
|
37
|
+
drink_weight: float = 0.6
|
|
38
|
+
heal_weight: float = 0.5
|
|
39
|
+
hydration_decay: float = 0.015
|
|
40
|
+
sleep_accum: float = 0.035
|
|
41
|
+
inflammation_gain: float = 0.002
|
|
42
|
+
cortisol_gain: float = 0.60
|
|
43
|
+
fear_stress: float = 0.35
|
|
44
|
+
joy_v: float = 0.45
|
|
45
|
+
need_physio_gate: float = 0.8
|
|
46
|
+
need_safety_gate: float = 0.8
|
|
47
|
+
need_belong_gate: float = 0.8
|
|
48
|
+
need_actual_gate: float = 0.8
|
|
49
|
+
planning: float = 0.5
|
|
50
|
+
quantum_weight: float = 0.5
|
|
51
|
+
quantum_gamma: float = 0.05
|
|
52
|
+
quantum_dephase: float = 0.10
|
|
53
|
+
world_weight: float = 0.5
|
|
54
|
+
world_horizon: int = 1
|
|
55
|
+
world_beam: int = 1
|
|
56
|
+
emotion_social_weight: float = 0.5
|
|
57
|
+
emotion_avoid_weight: float = 0.5
|
|
58
|
+
emotion_explore_weight: float = 0.5
|
|
59
|
+
emotion_recovery_weight: float = 0.5
|
|
60
|
+
formula_expr: object = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _clip(x: float, lo: float, hi: float) -> float:
|
|
64
|
+
return lo if x < lo else hi if x > hi else x
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def mutate(g: AutonomyGenome, rng: random.Random, scale: float = 0.15) -> AutonomyGenome:
|
|
68
|
+
return AutonomyGenome(
|
|
69
|
+
autonomy_threshold=_clip(g.autonomy_threshold + rng.gauss(0, scale), 0.30, 1.20),
|
|
70
|
+
volition_temperature=_clip(g.volition_temperature + rng.gauss(0, scale), 0.0, 1.0),
|
|
71
|
+
autonomy_adapt_lr=_clip(g.autonomy_adapt_lr * math.exp(rng.gauss(0, 0.4)), 0.0005, 0.02),
|
|
72
|
+
noise_sigma=_clip(g.noise_sigma + rng.gauss(0, 0.02), 0.0, 0.20),
|
|
73
|
+
forage_weight=_clip(g.forage_weight + rng.gauss(0, 0.20), 0.0, 3.0),
|
|
74
|
+
rest_weight=_clip(g.rest_weight + rng.gauss(0, 0.20), 0.0, 3.0),
|
|
75
|
+
social_weight=_clip(g.social_weight + rng.gauss(0, 0.20), 0.0, 3.0),
|
|
76
|
+
forage_temperature=_clip(g.forage_temperature * math.exp(rng.gauss(0, 0.3)), 0.05, 1.0),
|
|
77
|
+
fear_weight=_clip(g.fear_weight + rng.gauss(0, 0.20), 0.0, 3.0),
|
|
78
|
+
curiosity_weight=_clip(g.curiosity_weight + rng.gauss(0, 0.20), 0.0, 3.0),
|
|
79
|
+
explore_weight=_clip(g.explore_weight + rng.gauss(0, 0.20), 0.0, 3.0),
|
|
80
|
+
drink_weight=_clip(g.drink_weight + rng.gauss(0, 0.20), 0.0, 3.0),
|
|
81
|
+
heal_weight=_clip(g.heal_weight + rng.gauss(0, 0.20), 0.0, 3.0),
|
|
82
|
+
hydration_decay=_clip(g.hydration_decay + rng.gauss(0, 0.003), 0.0, 0.05),
|
|
83
|
+
sleep_accum=_clip(g.sleep_accum + rng.gauss(0, 0.006), 0.0, 0.08),
|
|
84
|
+
inflammation_gain=_clip(g.inflammation_gain + rng.gauss(0, 0.0005), 0.0, 0.01),
|
|
85
|
+
cortisol_gain=_clip(g.cortisol_gain + rng.gauss(0, 0.08), 0.0, 1.5),
|
|
86
|
+
fear_stress=_clip(g.fear_stress + rng.gauss(0, 0.04), 0.0, 1.0),
|
|
87
|
+
joy_v=_clip(g.joy_v + rng.gauss(0, 0.04), 0.0, 1.0),
|
|
88
|
+
need_physio_gate=_clip(g.need_physio_gate + rng.gauss(0, 0.15), 0.0, 2.5),
|
|
89
|
+
need_safety_gate=_clip(g.need_safety_gate + rng.gauss(0, 0.15), 0.0, 2.5),
|
|
90
|
+
need_belong_gate=_clip(g.need_belong_gate + rng.gauss(0, 0.15), 0.0, 2.5),
|
|
91
|
+
need_actual_gate=_clip(g.need_actual_gate + rng.gauss(0, 0.15), 0.0, 2.5),
|
|
92
|
+
planning=_clip(g.planning + rng.gauss(0, 0.08), 0.0, 1.0),
|
|
93
|
+
quantum_weight=_clip(g.quantum_weight + rng.gauss(0, 0.10), 0.0, 2.0),
|
|
94
|
+
quantum_gamma=_clip(g.quantum_gamma + rng.gauss(0, 0.02), 0.0, 0.5),
|
|
95
|
+
quantum_dephase=_clip(g.quantum_dephase + rng.gauss(0, 0.03), 0.0, 0.8),
|
|
96
|
+
emotion_social_weight=_clip(g.emotion_social_weight + rng.gauss(0, 0.12), 0.0, 2.0),
|
|
97
|
+
emotion_avoid_weight=_clip(g.emotion_avoid_weight + rng.gauss(0, 0.12), 0.0, 2.0),
|
|
98
|
+
emotion_explore_weight=_clip(g.emotion_explore_weight + rng.gauss(0, 0.12), 0.0, 2.0),
|
|
99
|
+
emotion_recovery_weight=_clip(g.emotion_recovery_weight + rng.gauss(0, 0.12), 0.0, 2.0),
|
|
100
|
+
world_weight=_clip(g.world_weight + rng.gauss(0, 0.12), 0.0, 2.0),
|
|
101
|
+
world_horizon=max(1, min(5, int(round(g.world_horizon + rng.gauss(0, 0.5))))),
|
|
102
|
+
world_beam=max(1, min(6, int(round(g.world_beam + rng.gauss(0, 0.5))))),
|
|
103
|
+
formula_expr=(
|
|
104
|
+
symbolic_mutate(g.formula_expr, rng, ("energy", "social", "stress", "hunger", "thirst", "pain", "inflammation", "sleep_pressure", "known_food", "food_uncertainty"))
|
|
105
|
+
if g.formula_expr is not None and rng.random() < 0.8
|
|
106
|
+
else (random_expr(rng, ("energy", "social", "stress", "hunger", "thirst", "pain", "inflammation", "sleep_pressure", "known_food", "food_uncertainty"), 2)
|
|
107
|
+
if rng.random() < 0.15 else g.formula_expr)
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def crossover(a: AutonomyGenome, b: AutonomyGenome, rng: random.Random) -> AutonomyGenome:
|
|
113
|
+
"""Sexual recombination of low-level genes; missing from mutation-only QD."""
|
|
114
|
+
out = {}
|
|
115
|
+
for field_name in AutonomyGenome.__dataclass_fields__:
|
|
116
|
+
out[field_name] = getattr(a if rng.random() < 0.5 else b, field_name)
|
|
117
|
+
child = AutonomyGenome(**out)
|
|
118
|
+
# light mutation keeps crossover from being a pure copy
|
|
119
|
+
return mutate(child, rng, scale=0.06)
|
|
120
|
+
|
|
121
|
+
def evaluate(g: AutonomyGenome, steps: int = 120, seed: int = 1,
|
|
122
|
+
weights: Dict[str, float] | None = None, mortality: bool = False,
|
|
123
|
+
starvation: float = 0.0, damage_threshold: float | None = None,
|
|
124
|
+
predator_risk: float = 0.0, thirst_rate: float = 0.0,
|
|
125
|
+
inflammation_rate: float = 0.0, pain_rate: float = 0.0,
|
|
126
|
+
regime_shift: int = 0, env_chaos: float = 0.0, chaos_r: float = 3.9,
|
|
127
|
+
effect_delay: int = 0) -> Dict[str, object]:
|
|
128
|
+
cfg = MindConfig(seed=int(seed), autonomy_threshold=g.autonomy_threshold,
|
|
129
|
+
volition_temperature=g.volition_temperature,
|
|
130
|
+
autonomy_adapt_lr=g.autonomy_adapt_lr,
|
|
131
|
+
noise_sigma=g.noise_sigma)
|
|
132
|
+
policy = ForagePolicy(forage_weight=g.forage_weight, rest_weight=g.rest_weight,
|
|
133
|
+
social_weight=g.social_weight, temperature=g.forage_temperature,
|
|
134
|
+
fear_weight=g.fear_weight, curiosity_weight=g.curiosity_weight,
|
|
135
|
+
explore_weight=g.explore_weight, drink_weight=g.drink_weight,
|
|
136
|
+
heal_weight=g.heal_weight, need_physio_gate=g.need_physio_gate,
|
|
137
|
+
need_safety_gate=g.need_safety_gate, need_belong_gate=g.need_belong_gate,
|
|
138
|
+
need_actual_gate=g.need_actual_gate, planning=g.planning,
|
|
139
|
+
quantum_weight=g.quantum_weight, quantum_gamma=g.quantum_gamma,
|
|
140
|
+
quantum_dephase=g.quantum_dephase,
|
|
141
|
+
emotion_social_weight=g.emotion_social_weight,
|
|
142
|
+
emotion_avoid_weight=g.emotion_avoid_weight,
|
|
143
|
+
emotion_explore_weight=g.emotion_explore_weight,
|
|
144
|
+
emotion_recovery_weight=g.emotion_recovery_weight,
|
|
145
|
+
formula_expr=g.formula_expr, world_weight=g.world_weight,
|
|
146
|
+
world_horizon=g.world_horizon, world_beam=g.world_beam,
|
|
147
|
+
rng=random.Random(int(seed)))
|
|
148
|
+
phys = {"hydration_decay": g.hydration_decay, "sleep_accum": g.sleep_accum,
|
|
149
|
+
"inflammation_gain": g.inflammation_gain, "cortisol_gain": g.cortisol_gain}
|
|
150
|
+
emo = {"fear": {"stress": g.fear_stress}, "joy": {"vpos": g.joy_v}}
|
|
151
|
+
r = run_autonomy(steps=steps, cfg=cfg, perturb=True, mortality=mortality, collect_series=True,
|
|
152
|
+
starvation=starvation, damage_threshold=damage_threshold,
|
|
153
|
+
energy_policy=policy, predator_risk=predator_risk,
|
|
154
|
+
thirst_rate=thirst_rate, inflammation_rate=inflammation_rate, pain_rate=pain_rate,
|
|
155
|
+
physiology_params=phys, emotion_weights=emo, regime_shift=regime_shift,
|
|
156
|
+
env_chaos=env_chaos, chaos_r=chaos_r, effect_delay=effect_delay)
|
|
157
|
+
recovery = float(r.get("recovery_steps", -1))
|
|
158
|
+
rec_score = 0.0 if recovery < 0 else 1.0 / (1.0 + recovery)
|
|
159
|
+
viability = float(r.get("viability_mean", 0.0))
|
|
160
|
+
vmin = float(r.get("viability_min", 0.0))
|
|
161
|
+
spont = float(r.get("spontaneous_rate", 0.0))
|
|
162
|
+
entropy = float(r.get("action_entropy", 0.0))
|
|
163
|
+
w = weights or {"viability": 1.0, "recovery": 0.5, "entropy": 0.3, "spont": 0.2, "penalty": 0.5}
|
|
164
|
+
fitness = (float(w.get("viability", 1.0)) * viability
|
|
165
|
+
+ float(w.get("recovery", 0.5)) * rec_score
|
|
166
|
+
+ float(w.get("entropy", 0.3)) * entropy
|
|
167
|
+
+ float(w.get("spont", 0.2)) * spont
|
|
168
|
+
- float(w.get("penalty", 0.5)) * (1.0 if vmin < 0.5 else 0.0))
|
|
169
|
+
died = bool(r.get("dead", False))
|
|
170
|
+
lifespan = float(r.get("lifespan", steps)) / max(1.0, float(steps))
|
|
171
|
+
fitness += 0.30 * lifespan - (0.50 if died else 0.0)
|
|
172
|
+
needs = r.get("needs") or {}
|
|
173
|
+
deficits = needs.get("deficits") or {}
|
|
174
|
+
phys = float(deficits.get("physiological", 0.0))
|
|
175
|
+
safety = float(deficits.get("safety", 0.0))
|
|
176
|
+
belonging = float(deficits.get("belonging", 0.0))
|
|
177
|
+
esteem = float(deficits.get("esteem", 0.0))
|
|
178
|
+
actual = float(deficits.get("actualization", 0.0))
|
|
179
|
+
lower = max(phys, safety)
|
|
180
|
+
higher = max(belonging, esteem, actual)
|
|
181
|
+
# stepwise gating: lower unmet needs suppress higher-level reward
|
|
182
|
+
fitness += 0.25 * (1.0 - lower) - 0.35 * lower - 0.15 * higher
|
|
183
|
+
# entropy cannot be farmed when lower needs are unmet
|
|
184
|
+
fitness += 0.25 * float(needs.get("satisfaction", 0.0)) - 0.30 * entropy * lower
|
|
185
|
+
formula = g.formula_expr
|
|
186
|
+
f_complexity = formula_complexity(formula) if formula is not None else 0
|
|
187
|
+
f_stability = formula_stability(formula) if formula is not None else 1.0
|
|
188
|
+
if formula is not None:
|
|
189
|
+
fitness -= 0.002 * f_complexity + 0.25 * (1.0 - f_stability)
|
|
190
|
+
# discounted future effects: pending harm/help is not free
|
|
191
|
+
fitness -= 0.35 * float(r.get("pending_damage_sum", 0.0))
|
|
192
|
+
fitness += 0.12 * float(r.get("pending_energy_sum", 0.0))
|
|
193
|
+
# multi-horizon: late needs matter more than early transient needs
|
|
194
|
+
series = r.get("series") or []
|
|
195
|
+
early_needs = late_needs = 0.0
|
|
196
|
+
future_trend = 0.0
|
|
197
|
+
if len(series) >= 5:
|
|
198
|
+
k = max(1, len(series) // 5)
|
|
199
|
+
early_needs = sum(float(x.get("needs", 0.0)) for x in series[:k]) / k
|
|
200
|
+
late_needs = sum(float(x.get("needs", 0.0)) for x in series[-k:]) / k
|
|
201
|
+
future_trend = late_needs - early_needs
|
|
202
|
+
fitness += 0.20 * late_needs - 0.20 * max(0.0, -future_trend)
|
|
203
|
+
vbin = max(0, min(3, int(viability * 4)))
|
|
204
|
+
rbin = 0 if recovery < 0 else max(0, min(2, int(recovery)))
|
|
205
|
+
descriptor = (min(4, int(spont * 5)), min(4, int(entropy * 5)), vbin, rbin)
|
|
206
|
+
return {"genome": asdict(g), "fitness": round(fitness, 5), "descriptor": descriptor,
|
|
207
|
+
"viability": round(viability, 4), "viability_min": round(vmin, 4),
|
|
208
|
+
"spontaneous": round(spont, 4), "entropy": round(entropy, 4),
|
|
209
|
+
"recovery_steps": int(recovery), "threshold_end": r.get("threshold_end"),
|
|
210
|
+
"dead": died, "lifespan": int(r.get("lifespan", steps)), "damage": r.get("damage", 0.0),
|
|
211
|
+
"needs_dominant": needs.get("dominant"), "needs_satisfaction": needs.get("satisfaction"),
|
|
212
|
+
"formula_complexity": f_complexity, "formula_stability": round(float(f_stability), 3),
|
|
213
|
+
"pending_damage_sum": r.get("pending_damage_sum", 0.0),
|
|
214
|
+
"pending_energy_sum": r.get("pending_energy_sum", 0.0),
|
|
215
|
+
"early_needs": round(float(early_needs), 4), "late_needs": round(float(late_needs), 4),
|
|
216
|
+
"future_trend": round(float(future_trend), 4)}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def run_qd(generations: int = 10, population: int = 10, steps: int = 120, seed: int = 1,
|
|
220
|
+
weights: Dict[str, float] | None = None, mortality: bool = False,
|
|
221
|
+
starvation: float = 0.0, damage_threshold: float | None = None,
|
|
222
|
+
archive_path=None, predator_risk: float = 0.0, thirst_rate: float = 0.0,
|
|
223
|
+
inflammation_rate: float = 0.0, pain_rate: float = 0.0,
|
|
224
|
+
regime_shift: int = 0, initials: List[AutonomyGenome] | None = None,
|
|
225
|
+
env_chaos: float = 0.0, chaos_r: float = 3.9,
|
|
226
|
+
effect_delay: int = 0, burst_prob: float = 0.0) -> Dict[str, object]:
|
|
227
|
+
rng = random.Random(int(seed))
|
|
228
|
+
archive: Dict[Tuple[int, ...], Dict[str, object]] = {}
|
|
229
|
+
if archive_path:
|
|
230
|
+
try:
|
|
231
|
+
from pathlib import Path
|
|
232
|
+
rows = json.loads(Path(archive_path).read_text(encoding="utf-8"))
|
|
233
|
+
for row in rows if isinstance(rows, list) else []:
|
|
234
|
+
key = tuple(int(x) for x in row.get("descriptor", []))
|
|
235
|
+
if key:
|
|
236
|
+
archive[key] = row
|
|
237
|
+
except Exception:
|
|
238
|
+
archive = {}
|
|
239
|
+
initial = list(initials) if initials else [
|
|
240
|
+
AutonomyGenome(0.9, 0.0, 0.004, 0.03),
|
|
241
|
+
AutonomyGenome(0.8, 0.0, 0.004, 0.03),
|
|
242
|
+
AutonomyGenome(0.7, 0.1, 0.004, 0.03),
|
|
243
|
+
AutonomyGenome(0.9, 0.2, 0.006, 0.05),
|
|
244
|
+
AutonomyGenome(0.6, 0.3, 0.004, 0.05),
|
|
245
|
+
AutonomyGenome(0.75, 0.0, 0.008, 0.08),
|
|
246
|
+
]
|
|
247
|
+
for g in initial[:max(1, population)]:
|
|
248
|
+
r = evaluate(g, steps=steps, seed=seed, weights=weights, mortality=mortality,
|
|
249
|
+
starvation=starvation, damage_threshold=damage_threshold,
|
|
250
|
+
predator_risk=predator_risk, thirst_rate=thirst_rate,
|
|
251
|
+
inflammation_rate=inflammation_rate, pain_rate=pain_rate,
|
|
252
|
+
regime_shift=regime_shift, env_chaos=env_chaos, chaos_r=chaos_r, effect_delay=effect_delay)
|
|
253
|
+
k = tuple(r["descriptor"]) # type: ignore[arg-type]
|
|
254
|
+
if k not in archive or r["fitness"] > archive[k]["fitness"]:
|
|
255
|
+
archive[k] = r
|
|
256
|
+
history = []
|
|
257
|
+
for gen in range(max(1, int(generations))):
|
|
258
|
+
elites = list(archive.values())
|
|
259
|
+
candidates: List[AutonomyGenome] = []
|
|
260
|
+
for e in elites:
|
|
261
|
+
if rng.random() < float(burst_prob):
|
|
262
|
+
candidates.append(burst_mutate(AutonomyGenome(**e["genome"]), rng)) # type: ignore[arg-type]
|
|
263
|
+
elif rng.random() < 0.35 and len(elites) >= 2:
|
|
264
|
+
other = rng.choice(elites)
|
|
265
|
+
candidates.append(crossover(AutonomyGenome(**e["genome"]), # type: ignore[arg-type]
|
|
266
|
+
AutonomyGenome(**other["genome"]), rng)) # type: ignore[arg-type]
|
|
267
|
+
else:
|
|
268
|
+
candidates.append(mutate(AutonomyGenome(**e["genome"]), rng)) # type: ignore[arg-type]
|
|
269
|
+
while len(candidates) < max(1, population):
|
|
270
|
+
base = AutonomyGenome(**rng.choice(elites)["genome"]) if elites else AutonomyGenome()
|
|
271
|
+
candidates.append(mutate(base, rng, scale=0.25))
|
|
272
|
+
for g in candidates:
|
|
273
|
+
r = evaluate(g, steps=steps, seed=seed, weights=weights, mortality=mortality,
|
|
274
|
+
starvation=starvation, damage_threshold=damage_threshold,
|
|
275
|
+
predator_risk=predator_risk, thirst_rate=thirst_rate,
|
|
276
|
+
inflammation_rate=inflammation_rate, pain_rate=pain_rate,
|
|
277
|
+
regime_shift=regime_shift, env_chaos=env_chaos, chaos_r=chaos_r, effect_delay=effect_delay)
|
|
278
|
+
k = tuple(r["descriptor"]) # type: ignore[arg-type]
|
|
279
|
+
if k not in archive or r["fitness"] > archive[k]["fitness"]:
|
|
280
|
+
archive[k] = r
|
|
281
|
+
best_now = max(archive.values(), key=lambda e: e["fitness"]) if archive else {}
|
|
282
|
+
history.append({
|
|
283
|
+
"generation": gen + 1,
|
|
284
|
+
"archive": len(archive),
|
|
285
|
+
"best": round(float(best_now.get("fitness", 0.0)), 5),
|
|
286
|
+
"best_planning": round(float((best_now.get("genome") or {}).get("planning", 0.0)), 3),
|
|
287
|
+
"best_lifespan": int(best_now.get("lifespan", 0)),
|
|
288
|
+
"best_needs": best_now.get("needs_satisfaction"),
|
|
289
|
+
"descriptors": [list(k) for k in archive.keys()],
|
|
290
|
+
})
|
|
291
|
+
best = max(archive.values(), key=lambda e: e["fitness"]) if archive else None
|
|
292
|
+
if archive_path:
|
|
293
|
+
try:
|
|
294
|
+
from pathlib import Path
|
|
295
|
+
rows = sorted(archive.values(), key=lambda e: float(e.get("fitness", 0.0)), reverse=True)
|
|
296
|
+
Path(archive_path).parent.mkdir(parents=True, exist_ok=True)
|
|
297
|
+
Path(archive_path).write_text(json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
298
|
+
except Exception:
|
|
299
|
+
pass
|
|
300
|
+
return {"archive_size": len(archive), "best": best, "history": history,
|
|
301
|
+
"descriptors": [list(k) for k in archive.keys()], "archive_path": str(archive_path) if archive_path else ""}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def burst_mutate(g: AutonomyGenome, rng: random.Random, k: int | None = None) -> AutonomyGenome:
|
|
306
|
+
"""Saltation / punctuated mutation: a few genes take large jumps.
|
|
307
|
+
|
|
308
|
+
Normal mutate() is local; this adds heavy-tailed structural variation and
|
|
309
|
+
occasional formula replacement. It is a mutation operator, not a rule.
|
|
310
|
+
"""
|
|
311
|
+
bounds = {
|
|
312
|
+
"autonomy_threshold": (0.30, 1.20), "volition_temperature": (0.0, 1.0),
|
|
313
|
+
"autonomy_adapt_lr": (0.0005, 0.02), "noise_sigma": (0.0, 0.20),
|
|
314
|
+
"forage_weight": (0.0, 3.0), "rest_weight": (0.0, 3.0), "social_weight": (0.0, 3.0),
|
|
315
|
+
"forage_temperature": (0.05, 1.0), "fear_weight": (0.0, 3.0),
|
|
316
|
+
"curiosity_weight": (0.0, 3.0), "explore_weight": (0.0, 3.0),
|
|
317
|
+
"drink_weight": (0.0, 3.0), "heal_weight": (0.0, 3.0),
|
|
318
|
+
"hydration_decay": (0.0, 0.05), "sleep_accum": (0.0, 0.08),
|
|
319
|
+
"inflammation_gain": (0.0, 0.01), "cortisol_gain": (0.0, 1.5),
|
|
320
|
+
"fear_stress": (0.0, 1.0), "joy_v": (0.0, 1.0),
|
|
321
|
+
"need_physio_gate": (0.0, 2.5), "need_safety_gate": (0.0, 2.5),
|
|
322
|
+
"need_belong_gate": (0.0, 2.5), "need_actual_gate": (0.0, 2.5),
|
|
323
|
+
"planning": (0.0, 1.0), "quantum_weight": (0.0, 2.0),
|
|
324
|
+
"quantum_gamma": (0.0, 0.5), "quantum_dephase": (0.0, 0.8),
|
|
325
|
+
"emotion_social_weight": (0.0, 2.0), "emotion_avoid_weight": (0.0, 2.0),
|
|
326
|
+
"emotion_explore_weight": (0.0, 2.0), "emotion_recovery_weight": (0.0, 2.0),
|
|
327
|
+
"world_weight": (0.0, 2.0),
|
|
328
|
+
}
|
|
329
|
+
d = {name: getattr(g, name) for name in AutonomyGenome.__dataclass_fields__}
|
|
330
|
+
names = list(bounds)
|
|
331
|
+
n = int(k) if k is not None else rng.randint(3, 8)
|
|
332
|
+
for name in rng.sample(names, min(n, len(names))):
|
|
333
|
+
lo, hi = bounds[name]
|
|
334
|
+
if rng.random() < 0.45:
|
|
335
|
+
d[name] = rng.uniform(lo, hi)
|
|
336
|
+
else:
|
|
337
|
+
d[name] = _clip(float(d[name]) + rng.gauss(0.0, (hi - lo) * 0.35), lo, hi)
|
|
338
|
+
d["world_horizon"] = max(1, min(5, int(round(g.world_horizon + rng.gauss(0, 1.5)))))
|
|
339
|
+
d["world_beam"] = max(1, min(6, int(round(g.world_beam + rng.gauss(0, 1.5)))))
|
|
340
|
+
if rng.random() < 0.35:
|
|
341
|
+
d["formula_expr"] = random_expr(rng, ("energy", "social", "stress", "hunger", "thirst", "pain", "inflammation", "sleep_pressure", "known_food", "food_uncertainty"), rng.choice([2, 3]))
|
|
342
|
+
return AutonomyGenome(**d)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import Counter
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
from .types import Event
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AutonomyLoop:
|
|
10
|
+
"""One loop over structure, allostasis, sleep, attachment and memory."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, mind):
|
|
13
|
+
self.mind = mind
|
|
14
|
+
|
|
15
|
+
def run_with_messages(self, messages, ticks: int = 300, dt: float = 3600.0, every: int = 10) -> Dict[str, object]:
|
|
16
|
+
actions, reasons = Counter(), Counter()
|
|
17
|
+
viab, stress = [], []
|
|
18
|
+
msgs = list(messages)
|
|
19
|
+
mi = 0
|
|
20
|
+
for i in range(ticks):
|
|
21
|
+
if i % every == 0 and mi < len(msgs):
|
|
22
|
+
out = self.mind.step(Event(text=str(msgs[mi]), speaker="A", directed=True, ts=self.mind.t + dt))
|
|
23
|
+
self.mind.post_reply("嗯", feedback=0.2, speaker="A")
|
|
24
|
+
mi += 1
|
|
25
|
+
else:
|
|
26
|
+
out = self.mind.step(Event(kind="tick", ts=self.mind.t + dt))
|
|
27
|
+
actions[out.action] += 1
|
|
28
|
+
reasons[out.reason] += 1
|
|
29
|
+
viab.append(self.mind.viability_state["viability"])
|
|
30
|
+
stress.append(self.mind.affect.stress)
|
|
31
|
+
return {
|
|
32
|
+
"ticks": ticks, "messages": mi, "actions": dict(actions), "reasons": dict(reasons),
|
|
33
|
+
"viability_min": round(min(viab), 3) if viab else 1.0,
|
|
34
|
+
"stress_max": round(max(stress), 3) if stress else 0.0,
|
|
35
|
+
"fatigue": round(self.mind.affect.fatigue, 3),
|
|
36
|
+
"scar": round(self.mind.circuit.chronic_scar, 3),
|
|
37
|
+
"sleep_debt": round(self.mind.lifecycle.sleep_debt, 3),
|
|
38
|
+
"identity": round(self.mind.self_model.identity, 3),
|
|
39
|
+
"attachment": self.mind.attachment.style(),
|
|
40
|
+
"memory": self.mind.memory.snapshot(),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
def run(self, ticks: int = 300, dt: float = 3600.0) -> Dict[str, object]:
|
|
44
|
+
actions, reasons = Counter(), Counter()
|
|
45
|
+
viab, stress = [], []
|
|
46
|
+
for _ in range(ticks):
|
|
47
|
+
out = self.mind.step(Event(kind="tick", ts=self.mind.t + dt))
|
|
48
|
+
actions[out.action] += 1
|
|
49
|
+
reasons[out.reason] += 1
|
|
50
|
+
viab.append(self.mind.viability_state["viability"])
|
|
51
|
+
stress.append(self.mind.affect.stress)
|
|
52
|
+
return {
|
|
53
|
+
"ticks": ticks,
|
|
54
|
+
"actions": dict(actions),
|
|
55
|
+
"reasons": dict(reasons),
|
|
56
|
+
"viability_min": round(min(viab), 3) if viab else 1.0,
|
|
57
|
+
"stress_max": round(max(stress), 3) if stress else 0.0,
|
|
58
|
+
"fatigue": round(self.mind.affect.fatigue, 3),
|
|
59
|
+
"scar": round(self.mind.circuit.chronic_scar, 3),
|
|
60
|
+
"sleep_debt": round(self.mind.lifecycle.sleep_debt, 3),
|
|
61
|
+
"identity": round(self.mind.self_model.identity, 3),
|
|
62
|
+
"attachment": self.mind.attachment.style(),
|
|
63
|
+
"memory": self.mind.memory.snapshot(),
|
|
64
|
+
"circuit": {"sparsity": round(self.mind.circuit.sparsity, 3), "lesions": sorted(self.mind.circuit.lesions)},
|
|
65
|
+
}
|