holo-four-axis
A single memory store parameterized on four orthogonal axes.
Every previous tool in this series fixed one behavior. This tool exposes the substrate's design space. Four parameters, four behaviors, one class:
| Axis | Parameter | What it controls |
|---|---|---|
| 1 | d (dimension) |
Capacity β how many items fit |
| 2 | kind (per region) |
Algebra β item, anti, or chain |
| 3 | threshold |
Readout confidence gate |
| 4 | consolidation_rate |
Forgetting behavior |
Regions of different kinds can share one store. The same primitives, the same dimension, three different address shapes.
What it does
from holo_four_axis import FourAxisMemory
mem = FourAxisMemory(d=2048, threshold=0.05, consolidation_rate=0.3)
# Three regions, three algebras
mem.add_region("items", kind="item")
mem.add_region("claims", kind="anti")
mem.add_region("events", kind="chain")
# Item region: presence / absence
mem.store("items", "apple")
mem.query("items", "apple") # YES
mem.query("items", "pear") # UNKNOWN
# Anti region: presence / denial / contradiction
mem.store("claims", "earth_round")
mem.deny("claims", "earth_flat")
mem.store("claims", "water_is_wet")
mem.deny("claims", "water_is_wet")
mem.query("claims", "earth_round") # YES
mem.query("claims", "earth_flat") # NO
mem.query("claims", "water_is_wet") # AMBIGUOUS
# Chain region: ordered traversal
mem.chain("events", ["wake", "coffee", "work", "lunch", "sleep"])
mem.walk("events", "wake")
# ['wake', 'coffee', 'work', 'lunch', 'sleep']
Why four axes
Prior work in this series measured each axis separately. This tool
demonstrates that they are orthogonal. Changing d changes capacity
but not address shape. Changing kind changes address shape but not
capacity. Changing threshold changes the confidence gate but not
what is stored. Changing consolidation_rate changes retention but
not the other three.
The design intent is a memory whose behavior is fully specified by its
profile. A working memory is d=512, threshold=0.10, consolidation_rate=0.8. A long-term store is d=2048, threshold=0.04, consolidation_rate=0.0. A sequence memory is d=2048, threshold=0.08, consolidation_rate=0.3 with a chain region. Same code, different
profile, different behavior.
Installation
pip install numpy # required
pip install matplotlib # optional, for the region plot
Usage
Python
from holo_four_axis import FourAxisMemory
mem = FourAxisMemory(
d=2048,
kind="item",
threshold=0.05,
consolidation_rate=0.3,
anchor_quantile=0.8,
)
mem.add_region("items", kind="item")
mem.add_region("claims", kind="anti")
mem.add_region("events", kind="chain")
# Store with weights
mem.store("items", "fact_a", weight=1.0)
mem.store("items", "fact_b", weight=0.5)
# Consolidate β non-anchors decay, anchors preserved
mem.consolidate(cycles=5)
# Save / load
mem.save_json("state.json")
mem.plot("regions.png")
CLI
python holo_four_axis.py
python holo_four_axis.py --output results/
Runs five demonstrations (one per axis plus a combined profile test) and writes the region plot.
Results
All results at D=2048 unless noted. Self-test verifies the three primitives (bind/unbind, directed bind/unbind, raw projection).
Axis 1 β Dimension
Store K items, query each, count correct.
| D | K=20 | K=50 | K=100 | K=200 |
|---|---|---|---|---|
| 512 | 20/20 | 50/50 | 95/100 | 149/200 |
| 1024 | 20/20 | 50/50 | 99/100 | 167/200 |
| 2048 | 20/20 | 50/50 | 100/100 | 174/200 |
The capacity ceiling scales sublinearly with D, matching the 0.907 exponent measured in earlier work.
Axis 2 β Algebra
Three regions, same D, three different address shapes.
Item region:
| Query | Verdict | Similarity |
|---|---|---|
apple |
YES | +0.573 |
orange |
YES | +0.565 |
banana |
YES | +0.582 |
pear |
UNKNOWN | +0.000 |
Anti region:
| Query | Verdict | raw_yes | raw_no |
|---|---|---|---|
earth_round |
YES | +0.999 | +0.006 |
earth_flat |
NO | +0.012 | +1.005 |
water_is_wet |
AMBIGUOUS | +0.999 | +1.005 |
sun_orbits_earth |
UNKNOWN | +0.000 | +0.000 |
Chain region:
wake -> coffee -> work -> lunch -> afternoon -> dinner -> sleep
Same primitives. Same code path. Different algebra. Three behaviors.
Axis 3 β Threshold
Store 30 items. Query stored keys and unseen keys. Sweep the readout threshold to trade acceptance of known items against rejection of unseen ones.
| Threshold | Known accepted | Unseen rejected |
|---|---|---|
| 0.05 | 30/30 | 30/30 |
| 0.10 | 30/30 | 30/30 |
| 0.20 | 30/30 | 30/30 |
| 0.40 | 30/30 | 30/30 |
| 0.60 | 0/30 | 30/30 |
With raw-projection readout, stored items are read back at their stored weight (~1.0) and unseen items read at near zero. The threshold has a wide plateau where both quantities are perfect. Above the plateau, stored items are rejected. Below, unseen items are accepted. The user picks the operating point.
Axis 4 β Consolidation
Store 50 items with weights drawn uniformly from [0.3, 1.0]. Run 5
consolidation cycles. Anchors (top 20% by weight) are preserved;
non-anchors decay by consolidation_rate per cycle.
| Rate | Magnitude before | Magnitude after | Retrievable |
|---|---|---|---|
| 0.0 | 4.88 | 4.88 | 48/50 |
| 0.2 | 4.88 | 3.25 | 34/50 |
| 0.5 | 4.88 | 2.99 | 10/50 |
| 0.8 | 4.88 | 2.99 | 10/50 |
At rate=0.0 nothing decays. At rate=0.2 the store loses ~30% of retrievable items over 5 cycles. At rate=0.5 and 0.8 the store converges to the anchor set (10 items) β aggressive decay flushes everything below the anchor quantile, and further decay does not remove anchors. The floor at 10 is a feature, not a bug.
Combined β Four profiles
Same code, four parameter profiles:
| Profile | d | threshold | rate | Behavior |
|---|---|---|---|---|
| working | 512 | 0.10 | 0.8 | fast-shallow, most items decay |
| longterm | 2048 | 0.04 | 0.0 | wide-persistent, nothing decays |
| seqmem | 2048 | 0.08 | 0.3 | medium persistence, chain walk works |
| contra | 2048 | 0.05 | 0.1 | contradiction-aware, AMBIGUOUS verdict |
Each profile produces measurably different behavior. The four-axis structure is what makes this possible with one class.
API reference
FourAxisMemory
FourAxisMemory(
d=2048,
kind="item",
threshold=0.05,
consolidation_rate=0.0,
anchor_quantile=0.8,
seed=0,
)
Region management
add_region(name, kind)β kind is"item","anti", or"chain"._region(name) -> Regionβ direct access (private).
Storage
store(region, label, weight=1.0)β add an item (or a chain node).deny(region, label, weight=1.0)β add a denial (anti regions only).store_many(region, labels, weight=1.0)chain(region, labels, weight=1.0)β add a chain of linked nodes.
Query
query(region, label) -> dictβ verdict, similarity, raw_yes, raw_no.query_next(region, label) -> label | Noneβ successor in a chain.query_prev(region, label) -> label | Noneβ predecessor.walk(region, start, max_steps=20) -> [label]β full walk.
Maintenance
consolidate(cycles=1) -> dictβ decay non-anchors.save_json(path)β full state.plot(path)β region plot.stats() -> dictβ per-region diagnostics.
Query result
{
"region": str,
"label": str,
"verdict": "YES" | "NO" | "UNKNOWN" | "AMBIGUOUS",
"similarity": float, # max similarity in the region's readout
"confidence": float, # magnitude of the winning signal
"raw_yes": float, # positive-trace projection
"raw_no": float, # negative-trace projection (anti only)
}
Design notes
Region-based algebra
Each region declares its kind at creation. The store routes operations
accordingly. An item region accepts store and answers query. An
anti region accepts store, deny, and answers with four verdicts.
A chain region accepts chain and answers query_next, query_prev,
and walk. A single FourAxisMemory object can hold all three kinds
simultaneously, sharing the same D.
Raw projection vs cosine
Item and anti regions use project for the readout, not cos. The
difference: cos(w * v, v) = 1.0 for any positive w, while
project(w * v, v) = w. The item region needs to distinguish an item
stored with weight 0.3 from one stored with weight 1.0, so it needs
raw projection. The same is true for the anti region's four-way
verdict.
Anchor preservation
Consolidation decays everything below the anchor_quantile (default
0.8 β top 20% by weight). The anchors do not decay. This produces the
floor at 10 items in the Axis 4 sweep: aggressive decay flushes the
non-anchors, but the top 10 items remain retrievable regardless of
decay rate. The floor is the anchor set.
Limitations
No cross-region queries. A query goes to one region. Asking "is
this item in any region" requires calling query per region.
No provenance. Weights accumulate. The store does not track which call produced which weight.
No automatic re-anchoring. The anchor set is fixed at the first consolidation call. If a non-anchor becomes important later, it will be decayed unless the user re-stores it or adjusts the quantile.
Consolidation is destructive. Decay subtracts from the trace and
multiplies the stored weight by (1 - rate). There is no undo. Save
state before consolidating if you might want to reverse it.
Chain regions have no weights. Chain nodes are stored with
weight_yes[label] = 1.0 regardless of the weight argument to
chain. Weighted chains are not implemented.
No deletion of individual chain edges. Removing one edge requires subtracting it from the trace manually. No helper is provided.
Citation
@misc{holo-four-axis2026,
title = {holo-four-axis: A memory store parameterized on four
orthogonal axes},
author = {zeechimp},
year = {2026},
note = {Item, anti, and chain regions share one substrate.
Capacity, algebra, threshold, and consolidation are
independent parameters.}
}
References
- Plate, T. A. "Holographic Reduced Representations." IEEE Transactions on Neural Networks 6:3 (1995), 623β641.
- Kanerva, P. "Hyperdimensional Computing." Cognitive Computation 1:2 (2009), 139β159.
- Gayler, R. W. "Vector Symbolic Architectures Answer Jackendoff's Challenges." ICCS/ASCS (2003).
License
Apache 2.0