chip-brain 1.0.0__py3-none-any.whl
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.
- amygdala/__init__.py +7 -0
- amygdala/affective_forecast.py +154 -0
- amygdala/arousal_modulator.py +92 -0
- amygdala/emotional_core.py +199 -0
- amygdala/emotional_memory.py +147 -0
- amygdala/fear_assessment.py +123 -0
- amygdala/habituation.py +152 -0
- brain.py +983 -0
- brainstem/__init__.py +8 -0
- brainstem/circadian.py +168 -0
- brainstem/cryostasis.py +268 -0
- brainstem/device.py +112 -0
- brainstem/forgetting_prevention.py +185 -0
- brainstem/gradient_clipper.py +102 -0
- brainstem/health_monitor.py +156 -0
- brainstem/online_trainer.py +358 -0
- brainstem/running_stats.py +78 -0
- brainstem/scheduler.py +140 -0
- cerebellum/__init__.py +7 -0
- cerebellum/action_smoother.py +111 -0
- cerebellum/emotional_contagion.py +235 -0
- cerebellum/skill_library.py +147 -0
- cerebellum/swarm_coordinator.py +150 -0
- cerebrum/__init__.py +7 -0
- cerebrum/attention_query.py +145 -0
- cerebrum/causal_engine.py +445 -0
- cerebrum/chip_policy.py +240 -0
- cerebrum/concept_grounding.py +138 -0
- cerebrum/goal_generator.py +153 -0
- cerebrum/goal_stack.py +292 -0
- cerebrum/inner_speech.py +326 -0
- cerebrum/meta_cognition.py +262 -0
- cerebrum/narrative_self.py +314 -0
- cerebrum/personality.py +130 -0
- cerebrum/planner.py +125 -0
- cerebrum/reasoning.py +193 -0
- cerebrum/self_consistency.py +268 -0
- cerebrum/theory_of_mind.py +195 -0
- cerebrum/working_memory.py +164 -0
- cerebrum/world_model.py +172 -0
- chip_brain-1.0.0.dist-info/METADATA +248 -0
- chip_brain-1.0.0.dist-info/RECORD +87 -0
- chip_brain-1.0.0.dist-info/WHEEL +5 -0
- chip_brain-1.0.0.dist-info/entry_points.txt +2 -0
- chip_brain-1.0.0.dist-info/licenses/LICENSE +21 -0
- chip_brain-1.0.0.dist-info/top_level.txt +13 -0
- core/chip_policy.py +15 -0
- core/device.py +7 -0
- core/emotional_core.py +7 -0
- core/episodic_memory.py +7 -0
- core/interfaces.py +21 -0
- core/latent_alignment.py +7 -0
- core/merge_strategies.py +7 -0
- core/online_trainer.py +7 -0
- core/running_stats.py +7 -0
- core/transformer_architecture.py +7 -0
- hippocampus/__init__.py +7 -0
- hippocampus/active_dreaming.py +294 -0
- hippocampus/boundary_detector.py +177 -0
- hippocampus/dream_cycle.py +123 -0
- hippocampus/episodic_memory.py +228 -0
- hippocampus/episodic_recall.py +178 -0
- hippocampus/memory_consolidation.py +96 -0
- hippocampus/spatial_map.py +167 -0
- hippocampus/temporal_abstraction.py +115 -0
- hypothalamus/__init__.py +7 -0
- hypothalamus/curiosity_drive.py +114 -0
- hypothalamus/drive_arbitrator.py +106 -0
- hypothalamus/energy_manager.py +131 -0
- hypothalamus/entropy_temperature.py +136 -0
- hypothalamus/homeostasis.py +93 -0
- interfaces/__init__.py +28 -0
- interfaces/base.py +231 -0
- interfaces/plugins.py +183 -0
- interfaces/signals.py +235 -0
- locomotion/ModelMovementAndLocomotion.py +532 -0
- locomotion/__init__.py +1 -0
- parasite/ModelWeightParasiticExtraction.py +257 -0
- parasite/__init__.py +1 -0
- run.py +133 -0
- thalamus/__init__.py +13 -0
- thalamus/attention_bottleneck.py +152 -0
- thalamus/granite_embedder.py +372 -0
- thalamus/latent_alignment.py +153 -0
- thalamus/merge_strategies.py +117 -0
- thalamus/sensory_encoder.py +177 -0
- thalamus/transformer_backbone.py +206 -0
amygdala/__init__.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
amygdala/affective_forecast.py — Predict future valence of a trajectory.
|
|
3
|
+
|
|
4
|
+
The world model predicts future *states*. This module predicts future
|
|
5
|
+
*feelings* — how a trajectory will make the agent feel over time. This
|
|
6
|
+
lets the cerebrum pick actions not just by predicted reward, but by
|
|
7
|
+
anticipated emotional outcome.
|
|
8
|
+
|
|
9
|
+
predicted_valence_trajectory = forecast(z_trajectory)
|
|
10
|
+
|
|
11
|
+
Use cases:
|
|
12
|
+
- Avoid actions that lead to sustained negative valence (learned anxiety)
|
|
13
|
+
- Prefer actions that lead to engagement (learned motivation)
|
|
14
|
+
- Detect "emotional dead ends" (trajectories that flatten to neutral)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Optional, Tuple
|
|
20
|
+
|
|
21
|
+
import torch
|
|
22
|
+
import torch.nn as nn
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AffectiveForecaster(nn.Module):
|
|
26
|
+
"""
|
|
27
|
+
Predicts the valence trajectory for a sequence of latent states.
|
|
28
|
+
|
|
29
|
+
Architecture: small GRU over the trajectory, outputs per-step valence
|
|
30
|
+
predictions in [-1, 1].
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
latent_dim: Dimensionality of latent states.
|
|
34
|
+
hidden_dim: GRU hidden size.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, latent_dim: int = 512, hidden_dim: int = 128) -> None:
|
|
38
|
+
super().__init__()
|
|
39
|
+
self.gru = nn.GRU(latent_dim, hidden_dim, batch_first=True)
|
|
40
|
+
self.head = nn.Sequential(
|
|
41
|
+
nn.Linear(hidden_dim, 64),
|
|
42
|
+
nn.GELU(),
|
|
43
|
+
nn.Linear(64, 1),
|
|
44
|
+
nn.Tanh(),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def forward(self, trajectory: torch.Tensor) -> torch.Tensor:
|
|
48
|
+
"""
|
|
49
|
+
Predict valence at each step of a trajectory.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
trajectory: (B, T, D) latent state sequence.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
(B, T, 1) predicted valence at each timestep.
|
|
56
|
+
"""
|
|
57
|
+
h, _ = self.gru(trajectory) # (B, T, hidden)
|
|
58
|
+
return self.head(h) # (B, T, 1)
|
|
59
|
+
|
|
60
|
+
def forecast_mean(self, trajectory: torch.Tensor) -> torch.Tensor:
|
|
61
|
+
"""
|
|
62
|
+
Predict the average valence over a trajectory.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
(B, 1) mean predicted valence.
|
|
66
|
+
"""
|
|
67
|
+
per_step = self.forward(trajectory)
|
|
68
|
+
return per_step.mean(dim=1)
|
|
69
|
+
|
|
70
|
+
def forecast_final(self, trajectory: torch.Tensor) -> torch.Tensor:
|
|
71
|
+
"""
|
|
72
|
+
Predict the valence at the END of the trajectory.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
(B, 1) final-step valence prediction.
|
|
76
|
+
"""
|
|
77
|
+
per_step = self.forward(trajectory)
|
|
78
|
+
return per_step[:, -1, :]
|
|
79
|
+
|
|
80
|
+
def compare_trajectories(
|
|
81
|
+
self,
|
|
82
|
+
traj_a: torch.Tensor,
|
|
83
|
+
traj_b: torch.Tensor,
|
|
84
|
+
) -> Tuple[float, float]:
|
|
85
|
+
"""
|
|
86
|
+
Compare two trajectories by predicted emotional outcome.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
(mean_valence_a, mean_valence_b) — higher is emotionally preferred.
|
|
90
|
+
"""
|
|
91
|
+
va = float(self.forecast_mean(traj_a).mean().item())
|
|
92
|
+
vb = float(self.forecast_mean(traj_b).mean().item())
|
|
93
|
+
return va, vb
|
|
94
|
+
|
|
95
|
+
def emotional_dead_end(
|
|
96
|
+
self,
|
|
97
|
+
trajectory: torch.Tensor,
|
|
98
|
+
threshold: float = 0.05,
|
|
99
|
+
) -> bool:
|
|
100
|
+
"""
|
|
101
|
+
Detect if a trajectory leads to emotional flatness (stagnation).
|
|
102
|
+
|
|
103
|
+
A dead end is where predicted valence variance across time is
|
|
104
|
+
near zero AND mean valence is near zero. The agent isn't feeling
|
|
105
|
+
anything — neither good nor bad. Often worse than negative valence
|
|
106
|
+
(which at least indicates engagement).
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
True if the trajectory is an emotional dead end.
|
|
110
|
+
"""
|
|
111
|
+
per_step = self.forward(trajectory) # (B, T, 1)
|
|
112
|
+
var = per_step.var(dim=1).mean().item()
|
|
113
|
+
mean = per_step.mean().abs().item()
|
|
114
|
+
return var < threshold and mean < threshold
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class AffectiveForecasterTrainer:
|
|
118
|
+
"""
|
|
119
|
+
Trains the forecaster on observed (trajectory, actual_valence) pairs.
|
|
120
|
+
|
|
121
|
+
Call `update()` with real episodes and their actual valences from the
|
|
122
|
+
emotional core. The forecaster learns to predict future affect.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def __init__(self, forecaster: AffectiveForecaster, lr: float = 1e-4) -> None:
|
|
126
|
+
self.forecaster = forecaster
|
|
127
|
+
self.optimizer = torch.optim.Adam(forecaster.parameters(), lr=lr)
|
|
128
|
+
self._step = 0
|
|
129
|
+
|
|
130
|
+
def update(
|
|
131
|
+
self,
|
|
132
|
+
trajectory: torch.Tensor,
|
|
133
|
+
actual_valences: torch.Tensor,
|
|
134
|
+
) -> float:
|
|
135
|
+
"""
|
|
136
|
+
One training step.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
trajectory: (B, T, D) latent state sequences.
|
|
140
|
+
actual_valences: (B, T, 1) actual valences observed.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Scalar MSE loss.
|
|
144
|
+
"""
|
|
145
|
+
self.optimizer.zero_grad()
|
|
146
|
+
predicted = self.forecaster(trajectory)
|
|
147
|
+
loss = nn.functional.mse_loss(predicted, actual_valences.detach())
|
|
148
|
+
loss.backward()
|
|
149
|
+
self.optimizer.step()
|
|
150
|
+
self._step += 1
|
|
151
|
+
return loss.item()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
__all__ = ["AffectiveForecaster", "AffectiveForecasterTrainer"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
amygdala/arousal_modulator.py — Attention gain control.
|
|
3
|
+
|
|
4
|
+
High arousal (fear, excitement) narrows attention to the most salient
|
|
5
|
+
features. Low arousal (calm, fatigue) broadens attention. The amygdala
|
|
6
|
+
modulates this gain signal and sends it to the thalamus to adjust
|
|
7
|
+
attention sensitivity in the transformer backbone.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import torch
|
|
13
|
+
import torch.nn as nn
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ArousalModulator(nn.Module):
|
|
17
|
+
"""
|
|
18
|
+
Computes an attention gain signal from the current arousal level.
|
|
19
|
+
|
|
20
|
+
The gain is applied as a multiplicative bias to the transformer's
|
|
21
|
+
attention scores in the thalamus:
|
|
22
|
+
scores ← scores * gain_scale
|
|
23
|
+
|
|
24
|
+
High arousal → gain > 1.0 (sharper, more focused attention)
|
|
25
|
+
Low arousal → gain < 1.0 (softer, more diffuse attention)
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
d_model: Latent dimensionality.
|
|
29
|
+
arousal_dim: Dimensionality of the arousal input vector.
|
|
30
|
+
Defaults to 1 (scalar arousal from homeostasis).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, d_model: int, arousal_dim: int = 1) -> None:
|
|
34
|
+
super().__init__()
|
|
35
|
+
self.d_model = d_model
|
|
36
|
+
|
|
37
|
+
# Maps arousal level → per-head gain scale
|
|
38
|
+
self.gain_net = nn.Sequential(
|
|
39
|
+
nn.Linear(arousal_dim, 32),
|
|
40
|
+
nn.GELU(),
|
|
41
|
+
nn.Linear(32, 1),
|
|
42
|
+
)
|
|
43
|
+
# Initialise to output 0 so gain starts at sigmoid(0) = 0.5 → scale = 1.0
|
|
44
|
+
nn.init.zeros_(self.gain_net[-1].weight)
|
|
45
|
+
nn.init.zeros_(self.gain_net[-1].bias)
|
|
46
|
+
|
|
47
|
+
def forward(self, arousal: torch.Tensor) -> torch.Tensor:
|
|
48
|
+
"""
|
|
49
|
+
Compute attention gain scale from arousal level.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
arousal: (B, 1) or scalar arousal value in [0, 1].
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
(B, 1) gain scale. Values > 1.0 sharpen attention,
|
|
56
|
+
values < 1.0 soften it.
|
|
57
|
+
"""
|
|
58
|
+
if arousal.dim() == 0:
|
|
59
|
+
arousal = arousal.unsqueeze(0).unsqueeze(0)
|
|
60
|
+
elif arousal.dim() == 1:
|
|
61
|
+
arousal = arousal.unsqueeze(-1)
|
|
62
|
+
|
|
63
|
+
# Map [0,1] arousal to gain: 0.5 arousal → gain 1.0 (neutral)
|
|
64
|
+
raw = self.gain_net(arousal)
|
|
65
|
+
# Sigmoid centred at 0 → range (0, 1), then scale to (0.5, 2.0)
|
|
66
|
+
gain = 0.5 + 1.5 * torch.sigmoid(raw)
|
|
67
|
+
return gain
|
|
68
|
+
|
|
69
|
+
def get_valence_bias(
|
|
70
|
+
self,
|
|
71
|
+
arousal: torch.Tensor,
|
|
72
|
+
valence: torch.Tensor,
|
|
73
|
+
) -> torch.Tensor:
|
|
74
|
+
"""
|
|
75
|
+
Compute the combined arousal+valence attention bias for the thalamus.
|
|
76
|
+
|
|
77
|
+
This is the signal sent via "arousal_gain" on the SignalBus.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
arousal: (B, 1) arousal level.
|
|
81
|
+
valence: (B, 1) emotional valence.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
(B, 1) combined bias for transformer attention scores.
|
|
85
|
+
"""
|
|
86
|
+
gain = self.forward(arousal)
|
|
87
|
+
# Valence shifts the bias direction: positive valence → attend to
|
|
88
|
+
# rewarding features; negative valence → attend to threat features
|
|
89
|
+
return gain * valence
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
__all__ = ["ArousalModulator"]
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""
|
|
2
|
+
amygdala/emotional_core.py — Emotion processing centre.
|
|
3
|
+
|
|
4
|
+
The amygdala processes emotions, especially fear and threat responses.
|
|
5
|
+
It tags memories with emotional significance and modulates attention
|
|
6
|
+
based on arousal state. This module is the computational equivalent:
|
|
7
|
+
mood tracking, homeostatic drives, and the valence network that maps
|
|
8
|
+
latent states to emotional tone.
|
|
9
|
+
|
|
10
|
+
Moved from: core/emotional_core.py
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
from typing import Tuple
|
|
15
|
+
import torch
|
|
16
|
+
import torch.nn as nn
|
|
17
|
+
from thalamus.latent_alignment import LatentAligner
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MoodState:
|
|
21
|
+
"""
|
|
22
|
+
Tracks a single named emotion and the reason for the most recent change.
|
|
23
|
+
Performs no tensor operations — pure bookkeeping.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, initial_mood: str, valid_vocab: dict) -> None:
|
|
27
|
+
if initial_mood not in valid_vocab:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
f"Initial mood '{initial_mood}' not in vocabulary: "
|
|
30
|
+
f"{list(valid_vocab.keys())}"
|
|
31
|
+
)
|
|
32
|
+
self._mood: str = initial_mood
|
|
33
|
+
self._reason: str = "Initialization"
|
|
34
|
+
self._vocab: dict = valid_vocab
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def name(self) -> str:
|
|
38
|
+
return self._mood
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def reason(self) -> str:
|
|
42
|
+
return self._reason
|
|
43
|
+
|
|
44
|
+
def transition(self, new_mood: str, reason: str) -> None:
|
|
45
|
+
if new_mood not in self._vocab:
|
|
46
|
+
print(f"[MoodState] Unknown mood '{new_mood}'; transition ignored.")
|
|
47
|
+
return
|
|
48
|
+
self._mood = new_mood
|
|
49
|
+
self._reason = reason
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class HomeostasisState:
|
|
53
|
+
"""
|
|
54
|
+
4-D homeostatic drive vector: [arousal, energy, safety, engagement].
|
|
55
|
+
Clamped to [0,1]. strain() = L2 distance from target setpoint.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
initial: Tuple[float, float, float, float] = (0.5, 0.8, 1.0, 0.5),
|
|
61
|
+
target: Tuple[float, float, float, float] = (0.8, 1.0, 0.7, 0.6),
|
|
62
|
+
) -> None:
|
|
63
|
+
self._state = nn.Parameter(torch.tensor(list(initial)), requires_grad=False)
|
|
64
|
+
self._target = torch.tensor(list(target))
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def vector(self) -> torch.Tensor:
|
|
68
|
+
return self._state
|
|
69
|
+
|
|
70
|
+
def update(
|
|
71
|
+
self,
|
|
72
|
+
action_impact: float,
|
|
73
|
+
environment_surprise: float,
|
|
74
|
+
policy_violation: float = 0.0,
|
|
75
|
+
tool_failure_rate: float = 0.0,
|
|
76
|
+
task_success: float = 0.0,
|
|
77
|
+
) -> None:
|
|
78
|
+
self._state[0] += 0.05 * environment_surprise
|
|
79
|
+
self._state[1] -= 0.02 * action_impact
|
|
80
|
+
self._state[2] -= 0.10 * float(policy_violation)
|
|
81
|
+
self._state[3] -= 0.05 * float(tool_failure_rate)
|
|
82
|
+
self._state[3] += 0.03 * float(task_success)
|
|
83
|
+
self._state.clamp_(0, 1)
|
|
84
|
+
|
|
85
|
+
def strain(self) -> torch.Tensor:
|
|
86
|
+
target = self._target.to(self._state.device)
|
|
87
|
+
return torch.norm(self._state - target)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class EmotionalCore(nn.Module):
|
|
91
|
+
"""
|
|
92
|
+
Amygdala: orchestrates mood + homeostasis + valence network.
|
|
93
|
+
|
|
94
|
+
Responsibilities:
|
|
95
|
+
1. Discrete mood bookkeeping (MoodState).
|
|
96
|
+
2. Homeostatic drive updates (HomeostasisState).
|
|
97
|
+
3. Learned valence network: (latent_state ‖ internal_state) → scalar ∈ (−1, +1).
|
|
98
|
+
|
|
99
|
+
Publishes to SignalBus:
|
|
100
|
+
"valence_update" → cerebrum (current emotional valence)
|
|
101
|
+
"arousal_gain" → thalamus (attention sensitivity scale)
|
|
102
|
+
"homeostasis_update" → hypothalamus (internal state vector)
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
latent_aligner: LatentAligner,
|
|
108
|
+
initial_mood: str = "Calm",
|
|
109
|
+
hidden_dim: int = 512,
|
|
110
|
+
) -> None:
|
|
111
|
+
super().__init__()
|
|
112
|
+
self._aligner = latent_aligner
|
|
113
|
+
self._mood = MoodState(initial_mood, latent_aligner.emotion_vocab)
|
|
114
|
+
self._homeostasis = HomeostasisState()
|
|
115
|
+
self.internal_state = self._homeostasis._state
|
|
116
|
+
self.valence_network = nn.Sequential(
|
|
117
|
+
nn.Linear(hidden_dim + 4, 128),
|
|
118
|
+
nn.ReLU(),
|
|
119
|
+
nn.Linear(128, 1),
|
|
120
|
+
nn.Tanh(),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def current_mood(self) -> Tuple[str, torch.Tensor]:
|
|
124
|
+
mood_id = self._aligner.emotion_vocab[self._mood.name]
|
|
125
|
+
idx = torch.tensor(
|
|
126
|
+
[mood_id], dtype=torch.long,
|
|
127
|
+
device=self._aligner.emotion_embeddings.weight.device,
|
|
128
|
+
)
|
|
129
|
+
vector = self._aligner.emotion_embeddings(idx).squeeze(0).detach()
|
|
130
|
+
return self._mood.name, vector
|
|
131
|
+
|
|
132
|
+
def mood_swing(self, new_mood: str, reason: str) -> None:
|
|
133
|
+
self._mood.transition(new_mood, reason)
|
|
134
|
+
|
|
135
|
+
def reason_for_mood_change(self) -> str:
|
|
136
|
+
return self._mood.reason
|
|
137
|
+
|
|
138
|
+
def set_mood_angry(self, reason: str) -> None:
|
|
139
|
+
self.mood_swing("Angry", reason)
|
|
140
|
+
|
|
141
|
+
def set_mood_sad(self, reason: str) -> None:
|
|
142
|
+
self.mood_swing("Sad", reason)
|
|
143
|
+
|
|
144
|
+
def set_mood_happy(self, reason: str) -> None:
|
|
145
|
+
self.mood_swing("Happy", reason)
|
|
146
|
+
|
|
147
|
+
def set_mood_calm(self, reason: str) -> None:
|
|
148
|
+
self.mood_swing("Calm", reason)
|
|
149
|
+
|
|
150
|
+
def auto_transition_mood(
|
|
151
|
+
self, valence: float, arousal: float, reason: str = "auto_circumplex",
|
|
152
|
+
) -> str:
|
|
153
|
+
v = float(max(-1.0, min(1.0, valence)))
|
|
154
|
+
a = float(max(0.0, min(1.0, arousal)))
|
|
155
|
+
v_pos = v > 0.10
|
|
156
|
+
v_neg = v < -0.10
|
|
157
|
+
a_high = a > 0.60
|
|
158
|
+
a_low = a < 0.40
|
|
159
|
+
|
|
160
|
+
if a_high and v_pos:
|
|
161
|
+
new_mood = "Happy"
|
|
162
|
+
elif a_high and v_neg:
|
|
163
|
+
new_mood = "Angry"
|
|
164
|
+
elif a_low and v_neg:
|
|
165
|
+
new_mood = "Sad"
|
|
166
|
+
elif a_low and v_pos:
|
|
167
|
+
new_mood = "Calm"
|
|
168
|
+
else:
|
|
169
|
+
return self._mood.name
|
|
170
|
+
|
|
171
|
+
if new_mood != self._mood.name:
|
|
172
|
+
self._mood.transition(new_mood, reason)
|
|
173
|
+
return new_mood
|
|
174
|
+
|
|
175
|
+
def update_homeostasis(
|
|
176
|
+
self,
|
|
177
|
+
action_impact: float,
|
|
178
|
+
environment_surprise: float,
|
|
179
|
+
policy_violation: float = 0.0,
|
|
180
|
+
tool_failure_rate: float = 0.0,
|
|
181
|
+
task_success: float = 0.0,
|
|
182
|
+
) -> None:
|
|
183
|
+
self._homeostasis.update(
|
|
184
|
+
action_impact=action_impact,
|
|
185
|
+
environment_surprise=environment_surprise,
|
|
186
|
+
policy_violation=policy_violation,
|
|
187
|
+
tool_failure_rate=tool_failure_rate,
|
|
188
|
+
task_success=task_success,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def calculate_strain(self) -> torch.Tensor:
|
|
192
|
+
return self._homeostasis.strain()
|
|
193
|
+
|
|
194
|
+
def get_valence(self, latent_state: torch.Tensor) -> torch.Tensor:
|
|
195
|
+
s = self._homeostasis.vector
|
|
196
|
+
if latent_state.dim() > 1:
|
|
197
|
+
s = s.unsqueeze(0).expand(latent_state.size(0), -1)
|
|
198
|
+
combined = torch.cat([latent_state, s], dim=-1)
|
|
199
|
+
return self.valence_network(combined)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
amygdala/emotional_memory.py — Emotion-tagged memory encoding.
|
|
3
|
+
|
|
4
|
+
The amygdala tags memories with emotional significance at encoding time.
|
|
5
|
+
Traumatic or highly rewarding experiences are remembered more vividly
|
|
6
|
+
than neutral ones. This module biases hippocampal memory retrieval
|
|
7
|
+
toward emotionally significant episodes.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Dict, List, Optional, Tuple
|
|
13
|
+
|
|
14
|
+
import torch
|
|
15
|
+
import torch.nn as nn
|
|
16
|
+
import torch.nn.functional as F
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EmotionalMemoryTagger(nn.Module):
|
|
20
|
+
"""
|
|
21
|
+
Tags latent states with emotional significance scores at encoding time.
|
|
22
|
+
|
|
23
|
+
The significance score is used by the hippocampus to weight replay
|
|
24
|
+
sampling — high-significance memories are replayed more often.
|
|
25
|
+
|
|
26
|
+
Significance formula:
|
|
27
|
+
σ = α · |valence| + β · arousal + γ · surprise
|
|
28
|
+
|
|
29
|
+
Where surprise = ||z_predicted - z_actual||₂ (from world model).
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
alpha: Weight on valence magnitude.
|
|
33
|
+
beta: Weight on arousal level.
|
|
34
|
+
gamma: Weight on prediction surprise.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
alpha: float = 0.4,
|
|
40
|
+
beta: float = 0.3,
|
|
41
|
+
gamma: float = 0.3,
|
|
42
|
+
) -> None:
|
|
43
|
+
super().__init__()
|
|
44
|
+
self.alpha = alpha
|
|
45
|
+
self.beta = beta
|
|
46
|
+
self.gamma = gamma
|
|
47
|
+
|
|
48
|
+
def compute_significance(
|
|
49
|
+
self,
|
|
50
|
+
valence: torch.Tensor,
|
|
51
|
+
arousal: torch.Tensor,
|
|
52
|
+
surprise: Optional[torch.Tensor] = None,
|
|
53
|
+
) -> torch.Tensor:
|
|
54
|
+
"""
|
|
55
|
+
Compute emotional significance score for a batch of experiences.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
valence: (B, 1) emotional valence in [-1, 1].
|
|
59
|
+
arousal: (B, 1) arousal level in [0, 1].
|
|
60
|
+
surprise: (B, 1) optional prediction error from world model.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
(B, 1) significance score in [0, 1].
|
|
64
|
+
"""
|
|
65
|
+
sig = self.alpha * valence.abs() + self.beta * arousal
|
|
66
|
+
if surprise is not None:
|
|
67
|
+
sig = sig + self.gamma * surprise
|
|
68
|
+
return torch.clamp(sig, 0.0, 1.0)
|
|
69
|
+
|
|
70
|
+
def tag_episode(
|
|
71
|
+
self,
|
|
72
|
+
states: torch.Tensor,
|
|
73
|
+
valences: torch.Tensor,
|
|
74
|
+
arousal: float,
|
|
75
|
+
surprise: Optional[float] = None,
|
|
76
|
+
) -> Dict:
|
|
77
|
+
"""
|
|
78
|
+
Create an emotion-tagged episode dict for the hippocampus.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
states: (T, D) state sequence.
|
|
82
|
+
valences: (T, 1) valence sequence.
|
|
83
|
+
arousal: Scalar arousal level.
|
|
84
|
+
surprise: Optional scalar prediction surprise.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Dict with 'states', 'significance', 'emotional_tag'.
|
|
88
|
+
"""
|
|
89
|
+
arousal_t = torch.tensor([[arousal]])
|
|
90
|
+
surprise_t = torch.tensor([[surprise]]) if surprise is not None else None
|
|
91
|
+
|
|
92
|
+
mean_valence = valences.mean().unsqueeze(0).unsqueeze(0)
|
|
93
|
+
sig = self.compute_significance(mean_valence, arousal_t, surprise_t)
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
"states": states,
|
|
97
|
+
"significance": float(sig.item()),
|
|
98
|
+
"emotional_tag": {
|
|
99
|
+
"mean_valence": float(mean_valence.item()),
|
|
100
|
+
"arousal": arousal,
|
|
101
|
+
"surprise": surprise or 0.0,
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class EmotionalRetrievalBias(nn.Module):
|
|
107
|
+
"""
|
|
108
|
+
Biases memory retrieval queries toward emotionally congruent memories.
|
|
109
|
+
|
|
110
|
+
Mood-congruent memory: humans recall memories that match their current
|
|
111
|
+
mood more easily. This module implements the same bias: when the agent
|
|
112
|
+
is in a high-arousal state, it preferentially retrieves high-arousal
|
|
113
|
+
memories.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
d_model: Latent dimensionality.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def __init__(self, d_model: int) -> None:
|
|
120
|
+
super().__init__()
|
|
121
|
+
# Projects emotional state into query space
|
|
122
|
+
self.emotion_to_query = nn.Linear(4, d_model) # 4 = homeostasis dims
|
|
123
|
+
|
|
124
|
+
def bias_query(
|
|
125
|
+
self,
|
|
126
|
+
base_query: torch.Tensor,
|
|
127
|
+
homeostasis_vector: torch.Tensor,
|
|
128
|
+
strength: float = 0.3,
|
|
129
|
+
) -> torch.Tensor:
|
|
130
|
+
"""
|
|
131
|
+
Add an emotional bias to a memory retrieval query.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
base_query: (B, D) base retrieval query.
|
|
135
|
+
homeostasis_vector: (4,) current homeostatic state.
|
|
136
|
+
strength: How strongly to bias the query.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
(B, D) emotionally-biased query.
|
|
140
|
+
"""
|
|
141
|
+
if homeostasis_vector.dim() == 1:
|
|
142
|
+
homeostasis_vector = homeostasis_vector.unsqueeze(0)
|
|
143
|
+
emotion_bias = self.emotion_to_query(homeostasis_vector) # (1, D)
|
|
144
|
+
return base_query + strength * emotion_bias
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
__all__ = ["EmotionalMemoryTagger", "EmotionalRetrievalBias"]
|