| """ |
| Valence Engine — Vitalis FSI |
| |
| Computes an affective score from a hypervector. |
| Score in [-1, 1]: |
| -1 = strong negative (failure, frustration) |
| 0 = neutral / unknown |
| +1 = strong positive (success, curiosity) |
| |
| Learned online from outcomes. No pretrained weights. |
| """ |
| import numpy as np |
| import os |
| from pathlib import Path |
|
|
|
|
| class ValenceEngine: |
| LEARNING_RATE = 0.01 |
| BUFFER_MAX = 50 |
| EPISODIC_BIAS = 0.3 |
|
|
| def __init__(self, dim: int = 10_000): |
| self.dim = dim |
| self.path = Path.home() / ".vitalis_workspace" / "valence_weights.npy" |
| self.w = self._load_weights() |
| self.buffer = [] |
|
|
| def _load_weights(self) -> np.ndarray: |
| if self.path.exists(): |
| return np.load(self.path) |
| return np.random.randn(self.dim) * 0.001 |
|
|
| def _save_weights(self): |
| self.path.parent.mkdir(parents=True, exist_ok=True) |
| np.save(self.path, self.w) |
|
|
| def reinforce(self, hv: np.ndarray, reward: float): |
| """ |
| Called after every outcome. |
| reward: +1.0 for success, -1.0 for failure, 0.0 for neutral. |
| """ |
| hv = hv.astype(np.float32) |
| pred = np.dot(self.w, hv) / self.dim |
| err = reward - pred |
| self.w += self.LEARNING_RATE * err * hv |
| self.buffer.append((hv.copy(), reward)) |
| if len(self.buffer) > self.BUFFER_MAX: |
| self.buffer.pop(0) |
| self._save_weights() |
|
|
| def evaluate(self, hv: np.ndarray) -> tuple: |
| """ |
| Returns (valence, confidence). |
| confidence = |valence| — how certain the system is. |
| """ |
| hv_f = hv.astype(np.float32) |
|
|
| |
| raw = float(np.tanh(np.dot(self.w, hv_f) / self.dim)) |
|
|
| |
| bias = 0.0 |
| if self.buffer: |
| sims = [float(np.mean(hv_f == bh)) for bh, _ in self.buffer] |
| weights = [bv for _, bv in self.buffer] |
| denom = sum(sims) + 1e-9 |
| bias = float(np.dot(sims, weights) / denom) |
|
|
| valence = float(np.clip(0.7 * raw + self.EPISODIC_BIAS * bias, -1.0, 1.0)) |
| confidence = abs(valence) |
| return valence, confidence |
|
|
| def report(self) -> dict: |
| return { |
| "buffer_size": len(self.buffer), |
| "avg_recent_reward": round(float(np.mean([r for _, r in self.buffer])), 4) |
| if self.buffer else 0.0, |
| "weight_norm": round(float(np.linalg.norm(self.w)), 4), |
| } |
|
|