Download revision_memory.py from zeechimp/revision-memory: direct link, hf CLI and curl.
- Browser
- Download file 10.3 kB
-
https://huggingface.co/zeechimp/revision-memory/resolve/main/revision_memory.py
- Command line
-
hf download hf://zeechimp/revision-memory/revision_memory.py
-
curl -L -o revision_memory.py https://huggingface.co/zeechimp/revision-memory/resolve/main/revision_memory.py
10.3 kB
| """ | |
| revision_memory.py — belief revision with derivation-tracked cascade. | |
| Fixes dream-cascade loose ends: | |
| - Derived edges have weight = min over all derivation paths | |
| - Removing a source triggers full re-derivation | |
| - Every derived edge knows its parents (explainability) | |
| - Chain quality = min substrate sim along the walk | |
| Two views of the same memory: | |
| - The substrate trace (what the phasor memory can answer on its own) | |
| - The revision state (asserted + derived edges with parents and weights) | |
| """ | |
| import numpy as np, hashlib, time | |
| from pathlib import Path | |
| def _seed(s): return int.from_bytes(hashlib.blake2b(s.encode(), digest_size=4).digest(), "little") | |
| def phasor(n, D): | |
| rng = np.random.default_rng(_seed(n)) | |
| return np.exp(1j*rng.uniform(-np.pi, np.pi, D).astype(np.float32)).astype(np.complex64) | |
| def bind_dir(a, b): return (a*np.roll(b, 1)).astype(np.complex64) | |
| def unbind_dir(a, c): return np.roll(c*np.conj(a), -1).astype(np.complex64) | |
| def sim(a, b): | |
| return float(np.real(np.vdot(a, b))/(np.linalg.norm(a)*np.linalg.norm(b)+1e-12)) | |
| def hdr(t): print(f"\n{'='*72}\n{t}\n{'='*72}") | |
| class RevisionMemory: | |
| def __init__(self, D=1024): | |
| self.D = D | |
| self._items = {} | |
| self.asserted = {} # (a, b) -> weight | |
| self.derived = {} # (a, b) -> {"weight": float, "parents": [(a,b),(b,c)]} | |
| self.trace = np.zeros(D, dtype=np.complex64) | |
| def _node(self, name): | |
| v = self._items.get(name) | |
| if v is None: | |
| v = phasor(f"node::{name}", self.D) | |
| self._items[name] = v | |
| return v | |
| def _binding(self, a, b): | |
| return bind_dir(self._node(a), self._node(b)) | |
| def _rebuild_trace(self): | |
| t = np.zeros(self.D, dtype=np.complex64) | |
| for (a, b), w in self.asserted.items(): | |
| t = t + w * self._binding(a, b) | |
| for (a, b), e in self.derived.items(): | |
| t = t + e["weight"] * self._binding(a, b) | |
| self.trace = t | |
| # ---------------- assertions ---------------- | |
| def assert_edge(self, a, b, weight=1.0): | |
| self._node(a); self._node(b) | |
| self.asserted[(a, b)] = weight | |
| self._rebuild_trace() | |
| return self | |
| def forget(self, a, b, rederive=True): | |
| if (a, b) not in self.asserted: | |
| return {"ok": False} | |
| del self.asserted[(a, b)] | |
| self.derived = {} | |
| self._rebuild_trace() | |
| if rederive: | |
| self.derive() | |
| return {"ok": True, "asserted_remaining": len(self.asserted), | |
| "derived_after": len(self.derived)} | |
| # ---------------- derivation ---------------- | |
| def derive(self, max_rounds=30): | |
| """Iterate forward chaining to fixpoint. Weight = min(parent weights).""" | |
| for _ in range(max_rounds): | |
| all_edges = dict(self.asserted) | |
| for k, v in self.derived.items(): | |
| all_edges[k] = v["weight"] | |
| new = {} | |
| for (a, b), w_ab in all_edges.items(): | |
| for (b2, c), w_bc in all_edges.items(): | |
| if b2 != b or a == c: continue | |
| if (a, c) in all_edges: continue | |
| w = min(w_ab, w_bc) | |
| if (a, c) not in new or new[(a, c)]["weight"] < w: | |
| new[(a, c)] = {"weight": w, "parents": [(a, b), (b, c)]} | |
| if not new: break | |
| for k, v in new.items(): | |
| if k not in self.derived or self.derived[k]["weight"] < v["weight"]: | |
| self.derived[k] = v | |
| self._rebuild_trace() | |
| return len(self.derived) | |
| # ---------------- substrate queries ---------------- | |
| def query(self, a, b): | |
| u = unbind_dir(self._node(a), self.trace) | |
| return sim(u, self._node(b)) | |
| def _outgoing(self, a): | |
| return sorted({b for (x, b) in list(self.asserted) + list(self.derived) if x == a}) | |
| def chain(self, start, n=10): | |
| path = [start]; cur = start; seen = {start} | |
| for _ in range(n): | |
| outs = self._outgoing(cur) | |
| if not outs: break | |
| scored = [(x, self.query(cur, x)) for x in outs] | |
| scored.sort(key=lambda t: -t[1]) | |
| cur = scored[0][0] | |
| if cur in seen: break | |
| seen.add(cur); path.append(cur) | |
| return path | |
| def chain_quality(self, path): | |
| if len(path) < 2: return 1.0 | |
| return min(self.query(path[i], path[i+1]) for i in range(len(path)-1)) | |
| # ---------------- explainability ---------------- | |
| def explain(self, a, b, depth=0): | |
| """Return a derivation tree.""" | |
| if depth > 12: | |
| return {"edge": (a, b), "kind": "depth_limit"} | |
| if (a, b) in self.asserted: | |
| return {"edge": (a, b), "kind": "asserted", | |
| "weight": self.asserted[(a, b)]} | |
| if (a, b) not in self.derived: | |
| return {"edge": (a, b), "kind": "unknown"} | |
| e = self.derived[(a, b)] | |
| (p1a, p1b), (p2a, p2b) = e["parents"] | |
| return { | |
| "edge": (a, b), | |
| "kind": "derived", | |
| "weight": e["weight"], | |
| "via": [self.explain(p1a, p1b, depth+1), | |
| self.explain(p2a, p2b, depth+1)], | |
| } | |
| # ---------------- introspection ---------------- | |
| def edges(self): | |
| return sorted(set(self.asserted) | set(self.derived)) | |
| def stats(self): | |
| return { | |
| "nodes": len(self._items), | |
| "asserted": len(self.asserted), | |
| "derived": len(self.derived), | |
| } | |
| def __repr__(self): | |
| s = self.stats() | |
| return (f"RevisionMemory(D={self.D}, nodes={s['nodes']}, " | |
| f"asserted={s['asserted']}, derived={s['derived']})") | |
| # ============================================================ | |
| def hdr(t): print(f"\n{'='*72}\n{t}\n{'='*72}") | |
| def test_1_basic(): | |
| hdr("T1. Basic derivation on a chain") | |
| mem = RevisionMemory(D=1024) | |
| for a, b in [("a","b"), ("b","c"), ("c","d"), ("d","e")]: | |
| mem.assert_edge(a, b) | |
| print(f" Asserted: {sorted(mem.asserted.keys())}") | |
| mem.derive() | |
| print(f" After derive: {sorted(mem.derived.keys())}") | |
| print(f" Query a→c: sim={mem.query('a','c'):+.4f}") | |
| print(f" Query a→e: sim={mem.query('a','e'):+.4f}") | |
| print(f" Chain a: {' → '.join(mem.chain('a'))}") | |
| def test_2_multipath(): | |
| hdr("T2. Multi-path derivation — alternate route survives removal") | |
| mem = RevisionMemory(D=1024) | |
| for a, b in [("a","b"), ("b","c"), ("a","x"), ("x","c")]: | |
| mem.assert_edge(a, b) | |
| mem.derive() | |
| print(f" Asserted: {sorted(mem.asserted.keys())}") | |
| print(f" Derived: {sorted(mem.derived.keys())}") | |
| print(f" (a,c) weight: {mem.derived.get(('a','c'), {}).get('weight', '?')}") | |
| print(f"\n forget a→b:") | |
| mem.forget("a", "b") | |
| print(f" Asserted: {sorted(mem.asserted.keys())}") | |
| print(f" Derived: {sorted(mem.derived.keys())}") | |
| print(f" (a,c) still derived? {('a','c') in mem.derived}") | |
| if ('a','c') in mem.derived: | |
| print(f" (a,c) parents: {mem.derived[('a','c')]['parents']}") | |
| print(f"\n forget a→x:") | |
| mem.forget("a", "x") | |
| print(f" Asserted: {sorted(mem.asserted.keys())}") | |
| print(f" Derived: {sorted(mem.derived.keys())}") | |
| print(f" (a,c) still derived? {('a','c') in mem.derived}") | |
| def test_3_chain_quality(): | |
| hdr("T3. Chain quality along walk") | |
| mem = RevisionMemory(D=2048) | |
| # chain of length 8 | |
| nodes = list("abcdefghi") | |
| for i in range(len(nodes)-1): | |
| mem.assert_edge(nodes[i], nodes[i+1]) | |
| mem.derive() | |
| path = mem.chain("a", n=10) | |
| print(f" Path: {' → '.join(path)}") | |
| print(f" Chain quality (min sim along walk): {mem.chain_quality(path):+.4f}") | |
| # now add noise by scattering many unrelated edges | |
| rng = np.random.default_rng(0) | |
| for _ in range(100): | |
| x, y = f"x{rng.integers(50)}", f"y{rng.integers(50)}" | |
| mem.assert_edge(x, y) | |
| mem.derive() | |
| path2 = mem.chain("a", n=10) | |
| print(f" After 100 noise edges:") | |
| print(f" Path: {' → '.join(path2)}") | |
| print(f" Chain quality: {mem.chain_quality(path2):+.4f}") | |
| def test_4_explain(): | |
| hdr("T4. Explainability — why is (a,e)?") | |
| mem = RevisionMemory(D=1024) | |
| for a, b in [("a","b"), ("b","c"), ("c","d"), ("d","e")]: | |
| mem.assert_edge(a, b) | |
| mem.derive() | |
| tree = mem.explain("a", "e") | |
| def show(t, indent=0): | |
| pad = " " * indent | |
| kind = t["kind"] | |
| e = t.get("edge") | |
| w = t.get("weight") | |
| print(f"{pad}{e} [{kind}]" + (f" w={w:.2f}" if w is not None else "")) | |
| if kind == "derived": | |
| for child in t["via"]: | |
| show(child, indent+1) | |
| show(tree) | |
| def test_5_scaling(): | |
| hdr("T5. Derivation cost vs chain length") | |
| print(f" {'depth':>6} {'asserted':>10} {'derived':>9} {'closure':>9} " | |
| f"{'chain_q':>9}") | |
| for depth in [3, 5, 8, 12, 18]: | |
| mem = RevisionMemory(D=2048) | |
| nodes = [f"n{i}" for i in range(depth)] | |
| for i in range(depth-1): | |
| mem.assert_edge(nodes[i], nodes[i+1]) | |
| mem.derive() | |
| closure_expected = depth * (depth-1) // 2 | |
| path = mem.chain(nodes[0], n=depth+2) | |
| q = mem.chain_quality(path) | |
| print(f" {depth:>6} {len(mem.asserted):>10} " | |
| f"{len(mem.derived):>9} {closure_expected:>9} {q:>+9.4f}") | |
| def test_6_weighted(): | |
| hdr("T6. Weighted assertions — weak links degrade derivation") | |
| mem = RevisionMemory(D=1024) | |
| mem.assert_edge("a", "b", weight=1.0) | |
| mem.assert_edge("b", "c", weight=0.5) | |
| mem.assert_edge("c", "d", weight=0.2) | |
| mem.derive() | |
| print(f" Asserted weights: " | |
| f"a→b={mem.asserted[('a','b')]:.2f} " | |
| f"b→c={mem.asserted[('b','c')]:.2f} " | |
| f"c→d={mem.asserted[('c','d')]:.2f}") | |
| for edge in [("a","c"), ("b","d"), ("a","d")]: | |
| if edge in mem.derived: | |
| print(f" Derived {edge}: weight={mem.derived[edge]['weight']:.2f} " | |
| f"parents={mem.derived[edge]['parents']}") | |
| if __name__ == "__main__": | |
| t0 = time.time() | |
| test_1_basic() | |
| test_2_multipath() | |
| test_3_chain_quality() | |
| test_4_explain() | |
| test_5_scaling() | |
| test_6_weighted() | |
| print(f"\nRuntime: {time.time()-t0:.2f} s") |