Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: XNLLLLH <XNLLLLH@users.noreply.github.com>
122 lines
4.5 KiB
Python
122 lines
4.5 KiB
Python
"""Plan 03-01 CONN-05 D-TEM-04: structure-edge Hebbian LTP.
|
|
|
|
Mirrors content-edge Hebbian (retrieve.reinforce_edges -> store.boost_edges
|
|
with edge_type="hebbian"). Co-retrieval of two records whose structure_hv
|
|
hypervectors are sufficiently similar (Hamming similarity >= 0.7 by default)
|
|
strengthens a "hebbian_structure" edge between them. FSRS decay on the new
|
|
edge type is identical to the content-edge formula in sleep._decay_edges.
|
|
|
|
Constitutional fit:
|
|
- D-TEM-04: Hebbian LTP on structure edges. Autopoiesis applied to structure;
|
|
the brain reinforces structural co-occurrence the same way it reinforces
|
|
content co-occurrence in Phase 1.
|
|
- Flat layout (PATTERNS.md): no `connectome/` subpackage. Module path is
|
|
src/iai_mcp/hebbian_structure.py.
|
|
- Same shape as retrieve.reinforce_edges -- pairwise iterate, compute
|
|
similarity, call store.boost_edges with edge_type="hebbian_structure".
|
|
|
|
Public API:
|
|
- STRUCTURAL_SIMILARITY_THRESHOLD: pairs above this fire LTP (default 0.7).
|
|
- structural_similarity(a, b): 1 - hamming_distance(a, b) / D in [0, 1].
|
|
- strengthen_structure_edge(store, src_id, dst_id, gain=1.0): boost the
|
|
structure edge between two records.
|
|
- co_retrieval_trigger(store, hits): pairwise scan of co-retrieved hits;
|
|
fire strengthen_structure_edge for every pair above the threshold.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from itertools import combinations
|
|
from uuid import UUID
|
|
|
|
import numpy as np
|
|
|
|
from iai_mcp.store import MemoryStore
|
|
from iai_mcp.types import STRUCTURE_HV_DIM
|
|
|
|
|
|
# D-TEM-04 default trigger (per plan Task 2b behavior contract):
|
|
# co-retrieval LTP fires when structural similarity >= 0.7 (Hamming distance
|
|
# fraction <= 0.3). Tunable later via the profile registry if a knob is added.
|
|
STRUCTURAL_SIMILARITY_THRESHOLD: float = 0.7
|
|
|
|
|
|
def structural_similarity(a: bytes, b: bytes) -> float:
|
|
"""Return 1 - hamming_distance(a, b) / STRUCTURE_HV_DIM in [0.0, 1.0].
|
|
|
|
Empty / unequal-length / corrupt inputs return 0.0 (graceful degradation).
|
|
"""
|
|
if not a or not b or len(a) != len(b):
|
|
return 0.0
|
|
aa = np.frombuffer(a, dtype=np.uint8)
|
|
bb = np.frombuffer(b, dtype=np.uint8)
|
|
# popcount of XOR -> hamming distance in bits.
|
|
xor = np.bitwise_xor(aa, bb)
|
|
# numpy >= 2.x has np.bitwise_count; fall back to unpackbits sum on older.
|
|
try:
|
|
ham_bits = int(np.bitwise_count(xor).sum())
|
|
except AttributeError:
|
|
ham_bits = int(np.unpackbits(xor).sum())
|
|
return 1.0 - (ham_bits / STRUCTURE_HV_DIM)
|
|
|
|
|
|
def strengthen_structure_edge(
|
|
store: MemoryStore,
|
|
src_id: UUID,
|
|
dst_id: UUID,
|
|
gain: float = 1.0,
|
|
) -> dict[tuple[str, str], float]:
|
|
"""Plan 03-01 D-TEM-04: structure-edge LTP via store.boost_edges.
|
|
|
|
Returns the new weights dict (same shape as retrieve.reinforce_edges'
|
|
underlying call). Mirrors content-edge LTP shape so downstream code
|
|
(events, audit, decay sweep) treats structure edges identically.
|
|
"""
|
|
return store.boost_edges(
|
|
[(src_id, dst_id)],
|
|
delta=float(gain),
|
|
edge_type="hebbian_structure",
|
|
)
|
|
|
|
|
|
def co_retrieval_trigger(
|
|
store: MemoryStore,
|
|
hits,
|
|
*,
|
|
threshold: float = STRUCTURAL_SIMILARITY_THRESHOLD,
|
|
gain: float = 1.0,
|
|
) -> int:
|
|
"""Pairwise scan of co-retrieved hits; fire strengthen_structure_edge
|
|
for each pair whose structural_similarity >= threshold.
|
|
|
|
`hits` may be a list of MemoryHit (record_id only -- structure_hv is
|
|
fetched lazily from store.get) OR a list of MemoryRecord (faster path,
|
|
structure_hv read directly).
|
|
|
|
Returns the number of structure edges strengthened. A structurally-
|
|
isolated co-retrieved set returns 0 -- this is expected (means no two
|
|
hits shared structure to reinforce).
|
|
"""
|
|
# Materialise (id, structure_hv) tuples once.
|
|
pairs: list[tuple[UUID, bytes]] = []
|
|
for h in hits:
|
|
rec_id = getattr(h, "record_id", None) or getattr(h, "id", None)
|
|
if rec_id is None:
|
|
continue
|
|
hv = getattr(h, "structure_hv", None)
|
|
if hv is None:
|
|
rec = store.get(rec_id)
|
|
if rec is None:
|
|
continue
|
|
hv = rec.structure_hv
|
|
pairs.append((rec_id, hv or b""))
|
|
|
|
fired = 0
|
|
for (a_id, a_hv), (b_id, b_hv) in combinations(pairs, 2):
|
|
if structural_similarity(a_hv, b_hv) >= threshold:
|
|
try:
|
|
strengthen_structure_edge(store, a_id, b_id, gain=gain)
|
|
fired += 1
|
|
except Exception:
|
|
# Diagnostic only -- never block the pipeline on edge failure.
|
|
continue
|
|
return fired
|