HDC Symbolic Query Demo — "Capital of France?"¶
SC-NeuroCore v3.10 — Hyper-Dimensional Computing (HDC/VSA) Kernel
This notebook demonstrates how SC-NeuroCore's SIMD-accelerated BitStreamTensor enables
symbolic reasoning via Hyper-Dimensional Computing on 10,000-bit binary vectors.
Key operations:
- Bind (
*/ XOR) — associates two concepts (self-inverse) - Bundle (
+/ majority vote) — combines multiple items into a set - Permute (cyclic rotation) — encodes position/sequence
- Similarity (1 − normalised Hamming distance) — measures relatedness
© 1998–2026 Miroslav Šotek. All rights reserved.
License: GNU AFFERO GENERAL PUBLIC LICENSE v3 | Commercial Licensing Available
Contact: www.anulum.li protoscience@anulum.li
from sc_neurocore.hdc import HDCEncoder, AssociativeMemory
import numpy as np
DIM = 10_000 # 10,000-bit hypervectors
enc = HDCEncoder(dim=DIM, seed=0)
def sim(a, b):
"""Normalised Hamming similarity between two binary hypervectors."""
return float(np.mean(a == b))
print(f"SC-NeuroCore HDC Symbolic Query Demo (D={DIM})")
SC-NeuroCore HDC Symbolic Query Demo (D=10000)
Step 1: Create Atomic Symbols¶
Each concept (country, capital, role) gets a random 10,000-bit vector. Random high-dimensional vectors are quasi-orthogonal: pairwise similarity ≈ 0.50.
# Role vectors
role_country = enc.generate_random_vector()
role_capital = enc.generate_random_vector()
# Country atoms
france = enc.generate_random_vector()
germany = enc.generate_random_vector()
japan = enc.generate_random_vector()
# Capital atoms
paris = enc.generate_random_vector()
berlin = enc.generate_random_vector()
tokyo = enc.generate_random_vector()
atoms = {
"France": france, "Germany": germany, "Japan": japan,
"Paris": paris, "Berlin": berlin, "Tokyo": tokyo,
}
print("Pairwise similarity (should be ~0.50):")
print(f" France vs Germany: {sim(france, germany):.3f}")
print(f" Paris vs Berlin: {sim(paris, berlin):.3f}")
Pairwise similarity (should be ~0.50): France vs Germany: 0.493 Paris vs Berlin: 0.496
Step 2: Encode Records¶
Each country–capital pair is encoded as:
$$\text{record} = (\text{role\_country} \oplus \text{country}) + (\text{role\_capital} \oplus \text{capital})$$
where $\oplus$ is XOR-bind and $+$ is majority-vote bundle.
rec_france = enc.bundle([enc.bind(role_country, france), enc.bind(role_capital, paris)])
rec_germany = enc.bundle([enc.bind(role_country, germany), enc.bind(role_capital, berlin)])
rec_japan = enc.bundle([enc.bind(role_country, japan), enc.bind(role_capital, tokyo)])
# Bundle all records into memory
memory = enc.bundle([rec_france, rec_germany, rec_japan])
print("Records encoded and bundled into memory.")
Records encoded and bundled into memory.
Step 3: Query — "Which countries are in memory?"¶
Unbind the capital role from memory to reveal country associations:
# Unbind the "capital" role from the whole memory -> a superposition of all capitals.
probe = enc.bind(memory, role_capital)
print("Which capitals are stored? (unbind role_capital from memory)")
for name, atom in [("Paris", paris), ("Berlin", berlin), ("Tokyo", tokyo),
("France", france), ("Germany", germany)]:
tag = " <- capital" if name in ("Paris", "Berlin", "Tokyo") else ""
print(f" sim(probe, {name:>8s}) = {sim(probe, atom):.4f}{tag}")
Which capitals are stored? (unbind role_capital from memory) sim(probe, Paris) = 0.5925 <- capital sim(probe, Berlin) = 0.5875 <- capital sim(probe, Tokyo) = 0.5935 <- capital sim(probe, France) = 0.4856 sim(probe, Germany) = 0.4950
Step 4: Query — "Capital of France?"¶
Two-step unbinding:
- Unbind France from memory:
hat = memory * france - Unbind the capital role:
answer = hat * role_capital
The result should be most similar to Paris.
# "Capital of France?" -> unbind the capital role from the France record, then clean up.
answer = enc.bind(rec_france, role_capital)
am = AssociativeMemory()
for name, atom in atoms.items():
am.store(name, atom)
print("Query: 'Capital of France?' (unbind role_capital from the France record)")
best_name, best_sim = "", -1.0
for name, atom in atoms.items():
s = sim(answer, atom)
if s > best_sim:
best_sim, best_name = s, name
print(f" sim(answer, {name:>8s}) = {s:.4f}")
print(f"\nItem-memory cleanup -> {am.query(answer)}")
print(f"Best match: {best_name} (similarity {best_sim:.4f})")
Query: 'Capital of France?' (unbind role_capital from the France record) sim(answer, France) = 0.4905 sim(answer, Germany) = 0.4951 sim(answer, Japan) = 0.5052 sim(answer, Paris) = 0.7428 sim(answer, Berlin) = 0.5010 sim(answer, Tokyo) = 0.4960 Item-memory cleanup -> Paris Best match: Paris (similarity 0.7428)
Step 5: Verify Bind-Inverse Property¶
XOR is self-inverse: $(a \oplus b) \oplus b = a$. This is the mathematical foundation that makes unbinding work.
a = enc.generate_random_vector()
b = enc.generate_random_vector()
recovered = enc.bind(enc.bind(a, b), b)
print(f"Bind-inverse: sim(a, (a*b)*b) = {sim(recovered, a):.4f} (should be ~1.0)")
Bind-inverse: sim(a, (a*b)*b) = 1.0000 (should be ~1.0)
Step 6: Permute for Sequence Encoding¶
Cyclic rotation produces quasi-orthogonal vectors, enabling position encoding:
$$\text{sim}(v, \pi^k(v)) \approx 0.50 \quad \text{for } k \neq 0$$
v = enc.generate_random_vector()
p1 = enc.permute(v, 1)
p2 = enc.permute(v, 2)
print("Permute orthogonality:")
print(f" sim(v, permute(v,1)) = {sim(v, p1):.4f}")
print(f" sim(v, permute(v,2)) = {sim(v, p2):.4f}")
Permute orthogonality: sim(v, permute(v,1)) = 0.4940 sim(v, permute(v,2)) = 0.5028