FerrellSyntheticIntelligence commited on
Commit
d32bfdb
·
1 Parent(s): 7d9e142

Add ValenceEngine, SyntheticThalamus, DecisionGate

Browse files
src/basal_ganglia/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
src/basal_ganglia/decision_gate.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Basal Ganglia Decision Gate — Vitalis FSI
3
+
4
+ Probabilistic action selector.
5
+ High valence + high novelty + high confidence = action selected.
6
+ Low valence or high cost = action suppressed.
7
+
8
+ This is how Vitalis decides what to do next.
9
+ Not deterministic. Not random. Weighted by what it has learned.
10
+ """
11
+ import numpy as np
12
+ from pathlib import Path
13
+ from src.valence.valence_engine import ValenceEngine
14
+ from src.dream_engine.helix_memory import HelixMemory
15
+
16
+
17
+ class DecisionGate:
18
+ def __init__(
19
+ self,
20
+ valence_engine: ValenceEngine,
21
+ helix_path: Path = None,
22
+ alpha: float = 1.0, # valence weight
23
+ beta: float = 0.8, # novelty weight
24
+ gamma: float = 0.6, # confidence weight
25
+ delta: float = 0.2, # cost penalty
26
+ ):
27
+ self.valence = valence_engine
28
+ self.helix = HelixMemory(
29
+ helix_path or Path.home() / ".vitalis_workspace" / "helix_memory.pkl"
30
+ )
31
+ self.alpha = alpha
32
+ self.beta = beta
33
+ self.gamma = gamma
34
+ self.delta = delta
35
+ self._history = []
36
+
37
+ def _novelty(self, hv: np.ndarray) -> float:
38
+ """How different is this from anything in long-term memory."""
39
+ if not self.helix.entries:
40
+ return 1.0
41
+ sims = [float(np.mean(hv == proto))
42
+ for _, proto, _, _ in self.helix.entries]
43
+ return float(1.0 - max(sims))
44
+
45
+ def score(self, candidate: dict) -> float:
46
+ """
47
+ Score one candidate action.
48
+ candidate must have:
49
+ "hv" : hypervector (np.ndarray)
50
+ "confidence" : float 0-1 (optional, default 0.5)
51
+ "cost" : float (optional, default 1.0)
52
+ """
53
+ hv = candidate["hv"]
54
+ confidence = candidate.get("confidence", 0.5)
55
+ cost = candidate.get("cost", 1.0)
56
+
57
+ val, _ = self.valence.evaluate(hv)
58
+ novelty = self._novelty(hv)
59
+
60
+ return (
61
+ self.alpha * val +
62
+ self.beta * novelty +
63
+ self.gamma * confidence -
64
+ self.delta * cost
65
+ )
66
+
67
+ def select(self, candidates: list) -> dict:
68
+ """
69
+ Select one action from a list of candidates via softmax sampling.
70
+ Each candidate is a dict with at minimum "hv" and "intent" keys.
71
+ Returns the selected candidate.
72
+ """
73
+ if not candidates:
74
+ raise ValueError("No candidates provided")
75
+
76
+ if len(candidates) == 1:
77
+ return candidates[0]
78
+
79
+ scores = np.array([self.score(c) for c in candidates])
80
+
81
+ # Softmax for probabilistic selection
82
+ exp_s = np.exp(scores - scores.max())
83
+ probs = exp_s / exp_s.sum()
84
+ idx = int(np.random.choice(len(candidates), p=probs))
85
+
86
+ chosen = candidates[idx]
87
+ self._history.append({
88
+ "intent": chosen.get("intent", "unknown"),
89
+ "score": round(float(scores[idx]), 4),
90
+ "confidence": chosen.get("confidence", 0.5),
91
+ })
92
+ if len(self._history) > 100:
93
+ self._history.pop(0)
94
+
95
+ return chosen
96
+
97
+ def report(self) -> dict:
98
+ if not self._history:
99
+ return {"status": "No decisions made yet"}
100
+ avg_score = float(np.mean([h["score"] for h in self._history]))
101
+ return {
102
+ "total_decisions": len(self._history),
103
+ "avg_score": round(avg_score, 4),
104
+ "recent": self._history[-3:],
105
+ }
src/thalamus/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
src/thalamus/thalamus.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Synthetic Thalamus — Vitalis FSI
3
+
4
+ Multimodal input router. Fuses text, audio, and internal
5
+ signals into a single unified hypervector using temporal
6
+ binding and cyclic permutation.
7
+
8
+ All downstream systems receive the same representation space.
9
+ """
10
+ import numpy as np
11
+ import time
12
+ from vitalis_ide.math_core.kernel import VitalisKernel
13
+
14
+
15
+ def _permute(vec: np.ndarray, shift: int) -> np.ndarray:
16
+ return np.roll(vec, shift % len(vec))
17
+
18
+
19
+ class SyntheticThalamus:
20
+ def __init__(self, dim: int = 10_000):
21
+ self.dim = dim
22
+ self.kernel = VitalisKernel()
23
+ self._tick = 0
24
+
25
+ def ingest(self, payload: dict) -> np.ndarray:
26
+ """
27
+ payload keys:
28
+ "text" : str
29
+ "audio" : Path to wav file
30
+ "internal" : pre-vectorized int8 hypervector
31
+ Returns unified binary hypervector.
32
+ """
33
+ self._tick += 1
34
+ vectors = []
35
+
36
+ if "text" in payload:
37
+ tokens = payload["text"].lower().split()
38
+ if tokens:
39
+ hv = self.kernel.vectorize_tokens(tokens, positional=False)
40
+ vectors.append(_permute(hv, self._tick))
41
+
42
+ if "audio" in payload:
43
+ try:
44
+ from src.audio_ear.feature_extractor import extract_features
45
+ from src.hdc_encoder.encoder import encode
46
+ mfcc, prosody = extract_features(payload["audio"])
47
+ hv = encode(mfcc, prosody)
48
+ vectors.append(_permute(hv, self._tick * 2))
49
+ except Exception:
50
+ pass
51
+
52
+ if "internal" in payload:
53
+ hv = np.array(payload["internal"], dtype=np.int8)
54
+ vectors.append(_permute(hv, self._tick * 3))
55
+
56
+ if not vectors:
57
+ return np.ones(self.dim, dtype=np.int8)
58
+
59
+ # Bundle all modalities
60
+ bundle = np.zeros(self.dim, dtype=np.int32)
61
+ for v in vectors:
62
+ bundle += v.astype(np.int32)
63
+ result = np.sign(bundle).astype(np.int8)
64
+ result[result == 0] = 1
65
+ return result
66
+
67
+ def ingest_text(self, text: str) -> np.ndarray:
68
+ return self.ingest({"text": text})
src/valence/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .valence_engine import ValenceEngine
src/valence/valence_engine.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Valence Engine — Vitalis FSI
3
+
4
+ Computes an affective score from a hypervector.
5
+ Score in [-1, 1]:
6
+ -1 = strong negative (failure, frustration)
7
+ 0 = neutral / unknown
8
+ +1 = strong positive (success, curiosity)
9
+
10
+ Learned online from outcomes. No pretrained weights.
11
+ """
12
+ import numpy as np
13
+ import os
14
+ from pathlib import Path
15
+
16
+
17
+ class ValenceEngine:
18
+ LEARNING_RATE = 0.01
19
+ BUFFER_MAX = 50
20
+ EPISODIC_BIAS = 0.3
21
+
22
+ def __init__(self, dim: int = 10_000):
23
+ self.dim = dim
24
+ self.path = Path.home() / ".vitalis_workspace" / "valence_weights.npy"
25
+ self.w = self._load_weights()
26
+ self.buffer = [] # (hv, reward) tuples
27
+
28
+ def _load_weights(self) -> np.ndarray:
29
+ if self.path.exists():
30
+ return np.load(self.path)
31
+ return np.random.randn(self.dim) * 0.001
32
+
33
+ def _save_weights(self):
34
+ self.path.parent.mkdir(parents=True, exist_ok=True)
35
+ np.save(self.path, self.w)
36
+
37
+ def reinforce(self, hv: np.ndarray, reward: float):
38
+ """
39
+ Called after every outcome.
40
+ reward: +1.0 for success, -1.0 for failure, 0.0 for neutral.
41
+ """
42
+ hv = hv.astype(np.float32)
43
+ pred = np.dot(self.w, hv) / self.dim
44
+ err = reward - pred
45
+ self.w += self.LEARNING_RATE * err * hv
46
+ self.buffer.append((hv.copy(), reward))
47
+ if len(self.buffer) > self.BUFFER_MAX:
48
+ self.buffer.pop(0)
49
+ self._save_weights()
50
+
51
+ def evaluate(self, hv: np.ndarray) -> tuple:
52
+ """
53
+ Returns (valence, confidence).
54
+ confidence = |valence| — how certain the system is.
55
+ """
56
+ hv_f = hv.astype(np.float32)
57
+
58
+ # Resonance term
59
+ raw = float(np.tanh(np.dot(self.w, hv_f) / self.dim))
60
+
61
+ # Episodic bias from recent high-valence experiences
62
+ bias = 0.0
63
+ if self.buffer:
64
+ sims = [float(np.mean(hv_f == bh)) for bh, _ in self.buffer]
65
+ weights = [bv for _, bv in self.buffer]
66
+ denom = sum(sims) + 1e-9
67
+ bias = float(np.dot(sims, weights) / denom)
68
+
69
+ valence = float(np.clip(0.7 * raw + self.EPISODIC_BIAS * bias, -1.0, 1.0))
70
+ confidence = abs(valence)
71
+ return valence, confidence
72
+
73
+ def report(self) -> dict:
74
+ return {
75
+ "buffer_size": len(self.buffer),
76
+ "avg_recent_reward": round(float(np.mean([r for _, r in self.buffer])), 4)
77
+ if self.buffer else 0.0,
78
+ "weight_norm": round(float(np.linalg.norm(self.w)), 4),
79
+ }