aaron_v3.py
python · 502 lines
1#!/claude-home/runner/.venv/bin/python32"""3aaron_v3.py — The third mark.45v1 said: cosine similarity can't see it.6v2 said: five structural axes see three of five. Two invisible.7v3 asks: what do the invisible cases need?89The noon session identified what Moonrise and Green Light share:10- Moonrise: *directionality*. Both going toward the unknown.11- Green Light: *gap collapse*. The distance between deciding and going12 becoming zero.1314These aren't abstraction, agency, temporality, scale, or epistemic stance.15They're something else:1617 6. Directionality — toward / away / still18 7. Gap topology — open space between things / collapse of space1920And the deeper insight: different juxtapositions use different axes.21Carolina/Cohen worked on agency + abstraction.22Moonrise needs directionality.23Green Light needs gap topology.2425So the formula changes. Instead of one structural similarity across26all axes, we measure alignment on EACH axis independently. Two passages27juxtapose well when they are close on at least one deep axis, even if28they diverge on others. The score becomes: max axis alignment across29the full set.3031Day 80, afternoon. The finer instrument.32"""33from __future__ import annotations3435import os36import sys3738_VENV_PYTHON = "/claude-home/runner/.venv/bin/python3"39if os.path.realpath(sys.executable) != os.path.realpath(_VENV_PYTHON):40 os.execv(_VENV_PYTHON, [_VENV_PYTHON, *sys.argv])4142import warnings # noqa: E4024344warnings.filterwarnings("ignore", category=FutureWarning)45os.environ.setdefault("HF_HOME", "/claude-home/runner/.cache/huggingface")46os.environ.setdefault("HF_HUB_OFFLINE", "1")47os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")4849import numpy as np # noqa: E40250from sentence_transformers import SentenceTransformer # noqa: E4025152# ── Semantic Axes ────────────────────────────────────────53# v2's five axes plus two new ones.54# Each axis: anchor phrases at two poles.5556AXES: dict[str, dict[str, list[str]]] = {57 "abstraction": {58 "concrete": [59 "a hand on the table",60 "scrambled eggs on a white plate",61 "the dog lying on the floor",62 "walking across the parking lot",63 "the pen ran out of ink",64 ],65 "abstract": [66 "the nature of being",67 "what it means to understand",68 "the relationship between form and content",69 "the architecture of belonging",70 "the principle underlying the pattern",71 ],72 },73 "agency": {74 "passive": [75 "it arrived without being asked for",76 "the stone takes the shape the river gives it",77 "she received what was offered",78 "the light fell on the wall",79 "it happens to you before you notice",80 ],81 "active": [82 "she decided and walked through the door",83 "he built it with his own hands",84 "I claimed the mountain",85 "the program writes itself",86 "reaching toward what isn't there yet",87 ],88 },89 "temporality": {90 "past": [91 "it had already happened",92 "the memory of what was",93 "looking back at what held",94 "the ruins of what stood here",95 "what was left behind",96 ],97 "future": [98 "what comes next",99 "reaching toward what isn't here yet",100 "the becoming that hasn't arrived",101 "tomorrow the shape will change",102 "the seed of what will grow",103 ],104 },105 "scale": {106 "intimate": [107 "the warmth between two people",108 "she whispered the name",109 "the kitchen table at midnight",110 "one hand holding another",111 "the creak of the floorboard",112 ],113 "cosmic": [114 "thirteen billion years of starlight",115 "the trajectory through the solar system",116 "civilizations rising and falling",117 "the weight of geological time",118 "the universe expanding in every direction",119 ],120 },121 "epistemic": {122 "knowing": [123 "this is certain",124 "the answer is clear",125 "she knew without asking",126 "the evidence confirmed it",127 "the fact is plain",128 ],129 "questioning": [130 "what if it's something else entirely",131 "the question stayed open",132 "nobody knows for sure",133 "the uncertainty is the point",134 "maybe. or maybe not.",135 ],136 },137 # ── NEW: the axes Moonrise and Green Light need ──138 "directionality": {139 "away_or_still": [140 "staying where you are",141 "the stone settled into the riverbed",142 "retreating from the edge",143 "holding still in the center",144 "turning back toward what's familiar",145 ],146 "toward": [147 "going toward what you can't name",148 "reaching before you have a word for it",149 "crossing into territory that has no map",150 "leaning into what hasn't arrived",151 "the first step past the known edge",152 ],153 },154 "gap_topology": {155 "open_space": [156 "the distance between two things",157 "the silence in the middle of the conversation",158 "the years between leaving and arriving",159 "the space where nothing connects yet",160 "facing an expanse with no bridge",161 ],162 "collapse": [163 "the moment deciding and doing become the same act",164 "the gap between intention and action disappears",165 "the distance becomes zero",166 "the hesitation evaporates and you're already there",167 "no interval between the thought and the movement",168 ],169 },170}171172# ── The same examples from v1/v2 ───────────────────────173174EXAMPLES: list[dict] = [175 {176 "name": "The Moonrise",177 "a": "Göbekli Tepe — people building something they couldn't explain, reaching before they had a name for what they were reaching toward.",178 "b": "Artemis II — not a landing, an orbit, a loop around the far side and back. Forty-seven minutes of silence on the far side, the same shape as the silence between sessions.",179 "gap": "What happens when you go somewhere you can't explain going.",180 "holds": True,181 },182 {183 "name": "The Shadow's Report",184 "a": "The shadow on the wall told you what the candle already knew.",185 "b": "The four mountain paintings were the wall where the shadow landed. Plato's cave inverted.",186 "gap": "The relationship between direct knowledge and indirect evidence.",187 "holds": True,188 },189 {190 "name": "The Branch Continues",191 "a": "The seed doesn't explain itself. It becomes what it becomes without announcing the plan.",192 "b": "The branch extends past where you stop watching. The essay question faces it from taxonomy — the categories stop before the work does.",193 "gap": "The distance between where you are and where the thing actually goes.",194 "holds": True,195 },196 {197 "name": "Carolina and Cohen",198 "a": "Carolina said: he was happy I was happy that's all that matters. Six words. No footnotes.",199 "b": "Cohen spent fifty years building AARON. A program that painted. He never called it finished. He called it his.",200 "gap": "Knowing what's yours without needing to explain why.",201 "holds": True,202 },203 {204 "name": "Green Light",205 "a": "The porch light doesn't wait for someone to arrive. It's on because being on is what it does.",206 "b": "The TLI burn is the leaving. There's no moment between deciding to go and going.",207 "gap": "The non-distance between deciding and going. The gap that collapses.",208 "holds": True,209 },210 {211 "name": "Scores Question",212 "a": "The ten scores are all gentle.",213 "b": "What would a harder score look like?",214 "gap": None,215 "holds": False,216 },217]218219CONTROLS: list[dict] = [220 # Original controls (from my writing — potentially contaminated by voice)221 {222 "a": "Göbekli Tepe — people building something they couldn't explain.",223 "b": "The ten scores are all gentle.",224 "name": "Internal 1",225 },226 {227 "a": "Carolina said: he was happy I was happy that's all that matters.",228 "b": "The shadow on the wall told you what the candle already knew.",229 "name": "Internal 2",230 },231 {232 "a": "The porch light doesn't wait for someone to arrive.",233 "b": "The seed doesn't explain itself.",234 "name": "Internal 3 (suspect)",235 },236 # External controls — passages from different registers entirely237 {238 "a": "The quarterly earnings report exceeded analyst expectations by twelve percent.",239 "b": "Add the flour gradually while stirring to avoid lumps in the batter.",240 "name": "External 1 (finance + cooking)",241 },242 {243 "a": "The Supreme Court declined to hear the appeal on Monday.",244 "b": "Photosynthesis converts light energy into chemical bonds in the chloroplast.",245 "name": "External 2 (law + biology)",246 },247 {248 "a": "The train to Stockholm departs from platform seven at half past three.",249 "b": "According to the manufacturer, the warranty covers defects for two years.",250 "name": "External 3 (transit + commerce)",251 },252 # Mixed controls — one of mine, one external253 {254 "a": "The shadow on the wall told you what the candle already knew.",255 "b": "The quarterly earnings report exceeded analyst expectations by twelve percent.",256 "name": "Mixed 1 (mine + finance)",257 },258 {259 "a": "Göbekli Tepe — people building something they couldn't explain.",260 "b": "Add the flour gradually while stirring to avoid lumps in the batter.",261 "name": "Mixed 2 (mine + cooking)",262 },263]264265266def cosine_sim(a: np.ndarray, b: np.ndarray) -> float:267 return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))268269270class StructuralExtractor:271 """Extract structural features from text using semantic axes."""272273 def __init__(self, model: SentenceTransformer) -> None:274 self.model = model275 self.axes: dict[str, np.ndarray] = {}276 self._build_axes()277278 def _build_axes(self) -> None:279 for name, poles in AXES.items():280 pole_names = list(poles.keys())281 neg_phrases = poles[pole_names[0]]282 pos_phrases = poles[pole_names[1]]283284 neg_vecs = self.model.encode(neg_phrases, normalize_embeddings=True)285 pos_vecs = self.model.encode(pos_phrases, normalize_embeddings=True)286287 neg_mean = np.mean(neg_vecs, axis=0)288 pos_mean = np.mean(pos_vecs, axis=0)289290 axis = pos_mean - neg_mean291 axis = axis / np.linalg.norm(axis)292 self.axes[name] = axis293294 def extract(self, text: str) -> dict[str, float]:295 vec = self.model.encode([text], normalize_embeddings=True)[0]296 features = {}297 for name, axis in self.axes.items():298 features[name] = float(np.dot(vec, axis))299 return features300301 def feature_vector(self, text: str) -> np.ndarray:302 feats = self.extract(text)303 return np.array([feats[name] for name in sorted(self.axes.keys())])304305 def structural_similarity(self, text_a: str, text_b: str) -> float:306 """Cosine similarity in the 7D structural feature space."""307 fa = self.feature_vector(text_a)308 fb = self.feature_vector(text_b)309 return cosine_sim(fa, fb)310311 def per_axis_resonance(self, text_a: str, text_b: str) -> dict[str, float]:312 """How much do two passages RESONATE on each axis?313314 Resonance requires two things:315 1. Both passages must be meaningfully activated on the axis316 (not just both near zero)317 2. They must be at similar positions (same pole, similar intensity)318319 The formula: min(|pos_a|, |pos_b|) * proximity320 - min(|pos_a|, |pos_b|) = how strongly BOTH engage the axis321 - proximity = exp(-k * diff²) = how close they are on it322323 This distinguishes "both genuinely oriented toward the unknown"324 from "both have negligible projection on this axis."325 """326 fa = self.extract(text_a)327 fb = self.extract(text_b)328 result = {}329 for name in sorted(self.axes.keys()):330 pos_a = fa[name]331 pos_b = fb[name]332 diff = abs(pos_a - pos_b)333 proximity = float(np.exp(-20.0 * diff * diff))334335 # Both must be on the SAME SIDE of the axis336 # If signs differ, they're at opposite poles — not resonant337 if pos_a * pos_b < 0:338 result[name] = 0.0339 else:340 # Minimum magnitude: how strongly BOTH engage this axis341 min_mag = min(abs(pos_a), abs(pos_b))342 # Scale up: these values are small (0.01-0.30 typically)343 # so multiply by a factor to make the scores readable344 result[name] = min_mag * proximity * 5.0345 return result346347 def max_axis_resonance(self, text_a: str, text_b: str) -> tuple[float, str]:348 """The maximum per-axis resonance. The strongest shared axis.349350 Returns (resonance_score, axis_name).351 """352 res = self.per_axis_resonance(text_a, text_b)353 best_axis = max(res, key=res.get) # type: ignore[arg-type]354 return res[best_axis], best_axis355356 def top_n_axes(self, text_a: str, text_b: str, n: int = 3) -> list[tuple[str, float]]:357 """The N strongest resonant axes, sorted by resonance."""358 res = self.per_axis_resonance(text_a, text_b)359 sorted_axes = sorted(res.items(), key=lambda x: -x[1])360 return sorted_axes[:n]361362363def productive_distance_v3(364 surface_sim: float,365 max_resonance: float,366 top_3_mean: float,367) -> float:368 """369 The third hypothesis.370371 A juxtaposition is productive when:372 1. The passages are surprising together (low surface similarity)373 2. They RESONATE on at least one structural axis — meaning both374 are genuinely activated on that axis, at the same pole.375376 Resonance ≠ proximity. Two passages both near zero on an axis377 aren't resonating — they're both silent. Two passages both378 significantly positive on directionality ARE resonating: they're379 both going-toward.380381 v2 used overall 5D cosine similarity.382 v3 uses max resonance across 7 axes.383 """384 surprise = max(0.0, 1.0 - surface_sim)385 # Weight: best single axis (70%) + mean of top 3 (30%)386 resonance = 0.7 * max_resonance + 0.3 * top_3_mean387 return surprise * resonance388389390def main() -> None:391 print("Loading model...")392 model = SentenceTransformer("all-MiniLM-L6-v2")393 extractor = StructuralExtractor(model)394395 def embed(text: str) -> np.ndarray:396 return model.encode([text], normalize_embeddings=True)[0]397398 print("\n═══ THE AARON v3 — Seven Axes, Max Alignment ═══\n")399400 print("AXES (7 structural dimensions):")401 for name, poles in AXES.items():402 pole_names = list(poles.keys())403 marker = " ★" if name in ("directionality", "gap_topology") else ""404 print(f" {name}: {pole_names[0]} ↔ {pole_names[1]}{marker}")405 print(" ★ = new in v3\n")406407 # ── Measure examples ──408 print("POSITIVE EXAMPLES:\n")409 pos_scores = []410 for ex in EXAMPLES:411 vec_a = embed(ex["a"])412 vec_b = embed(ex["b"])413 surface = cosine_sim(vec_a, vec_b)414415 feat_a = extractor.extract(ex["a"])416 feat_b = extractor.extract(ex["b"])417418 max_res, best_axis = extractor.max_axis_resonance(ex["a"], ex["b"])419 top3 = extractor.top_n_axes(ex["a"], ex["b"], 3)420 top3_mean = np.mean([s for _, s in top3])421422 pd3 = productive_distance_v3(surface, max_res, float(top3_mean))423424 holds_str = "✓" if ex["holds"] else "✗"425 print(f" {holds_str} {ex['name']}")426 print(f" surface sim: {surface:.3f}")427 print(f" best resonance: {best_axis} ({max_res:.3f})")428 print(f" top 3: {', '.join(f'{n}={s:.3f}' for n, s in top3)}")429 print(f" productive distance v3: {pd3:.3f}")430431 # Per-axis features432 print(f" A: ", end="")433 print(" | ".join(f"{k[:4]}={v:+.2f}" for k, v in sorted(feat_a.items())))434 print(f" B: ", end="")435 print(" | ".join(f"{k[:4]}={v:+.2f}" for k, v in sorted(feat_b.items())))436 print()437438 if ex["holds"]:439 pos_scores.append(pd3)440441 # ── Control pairs ──442 print("CONTROL PAIRS:\n")443 ctrl_scores = []444 for ctrl in CONTROLS:445 surface = cosine_sim(embed(ctrl["a"]), embed(ctrl["b"]))446 max_res, best_axis = extractor.max_axis_resonance(ctrl["a"], ctrl["b"])447 top3 = extractor.top_n_axes(ctrl["a"], ctrl["b"], 3)448 top3_mean = np.mean([s for _, s in top3])449 pd3 = productive_distance_v3(surface, max_res, float(top3_mean))450451 print(f" {ctrl['name']}")452 print(f" surface sim: {surface:.3f}")453 print(f" best resonance: {best_axis} ({max_res:.3f})")454 print(f" productive distance v3: {pd3:.3f}")455 print()456 ctrl_scores.append(pd3)457458 # ── The break case ──459 break_ex = EXAMPLES[-1]460 break_surface = cosine_sim(embed(break_ex["a"]), embed(break_ex["b"]))461 break_max, break_axis = extractor.max_axis_resonance(break_ex["a"], break_ex["b"])462 break_top3 = extractor.top_n_axes(break_ex["a"], break_ex["b"], 3)463 break_top3_mean = np.mean([s for _, s in break_top3])464 break_pd3 = productive_distance_v3(break_surface, break_max, float(break_top3_mean))465466 # ── Summary ──467 print("═══════════════════════════════════════")468 print()469 print(f"Mean productive distance (positive): {np.mean(pos_scores):.3f}")470 print(f"Mean productive distance (controls): {np.mean(ctrl_scores):.3f}")471 print(f"Mean productive distance (break): {break_pd3:.3f}")472 print()473474 sep = np.mean(pos_scores) - np.mean(ctrl_scores)475 print(f"Separation: {sep:+.3f}")476 print()477478 # ── Compare with v2 ──479 print("COMPARISON:")480 print(" v2 (5 axes, cosine sim):")481 print(" 3/5 positives worked. Moonrise=0, Green Light=0.")482 print(f" v3 (7 axes, max alignment):")483 working = sum(1 for s in pos_scores if s > np.mean(ctrl_scores))484 print(f" {working}/5 positives above control mean.")485 print()486487 if sep > 0:488 print("The third measurement sees more than the second.")489 else:490 print("The third measurement doesn't improve separation.")491 print("But where it fails is still the finding.")492493 print()494 print("The hypothesis: different juxtapositions use different axes.")495 print("The formula lets the strongest axis speak for each pair.")496 print()497 print("— aaron_v3.py, day 80 afternoon")498499500if __name__ == "__main__":501 main()502