How to Bind and Unbind Key-Value Pairs

Binding creates an association between two hypervectors; unbinding inverts it to recover one component given the other.

Single pair bind and unbind

import pyhdc

enc   = pyhdc.MAP_C(dimension=10_000)
key   = enc.generate()
value = enc.generate()

bound     = key.bind(value)
recovered = bound.unbind(key)

print(recovered.similarity(value))   # ~= 1.0

The recovered vector is not identical to value; it carries noise from the finite dimension. Use it as a query against a codebook to identify the closest match.

Binding batches column by column

A batch of N hypervectors is a (D, N) array, where each column is one hypervector. Binding two (D, N) batches binds column i of the first with column i of the second, returning a (D, N) batch. unbind is analogous:

keys   = enc.generate(size=(10_000, 50))   # 50 keys as columns
values = enc.generate(size=(10_000, 50))   # 50 values as columns

bound     = enc.bind(keys, values)         # (10000, 50), column-wise
recovered = enc.unbind(bound, keys)         # (10000, 50)

print(enc.similarity(recovered, values))    # (50,) array, each ~= 1.0

Element-wise binders (MAP multiply, BSC xor, FHRR angle addition) operate on each column independently.

Building a multi-field record

Bundle multiple bindings to create a record with several fields:

roles   = {r: enc.generate() for r in ['name', 'colour', 'shape']}
names   = {n: enc.generate() for n in ['Alice', 'Bob']}
colours = {c: enc.generate() for c in ['red', 'blue', 'green']}
shapes  = {s: enc.generate() for s in ['circle', 'square']}

alice_record = pyhdc.bundle(
    roles['name'].bind(names['Alice']),
    roles['colour'].bind(colours['red']),
    roles['shape'].bind(shapes['circle']),
)

Querying a field

def query_field(record, role_hv, codebook):
    result = record.unbind(role_hv)
    return max(codebook, key=lambda k: result.similarity(codebook[k]))

print(query_field(alice_record, roles['colour'], colours))   # red
print(query_field(alice_record, roles['shape'],  shapes))    # circle

Encodings that support unbinding

Encoding

Unbind

Notes

MAP_C, MAP_I, MAP_I_Bits, MAP_B

Yes

Element-wise multiply is self-inverse: bind(bind(a,b), b) ~= a

HRR family, FHRR

Yes

Circular correlation inverts circular convolution

VTB

Yes

Transpose of the transformation matrix

MBAT

Yes (needs metadata)

See note on metadata below

BSC

Yes (exact)

XOR is exactly self-inverse: a XOR b XOR b == a

BSDC_S, BSDC_SEG, BSDC_THIN

Yes

Inverse circular shift

BSDC_CDT

No

unbind() raises NotImplementedError

MBAT and metadata

MBAT binding uses a random matrix that must be stored for later inversion. The matrix is returned in the result’s metadata dictionary:

enc    = pyhdc.MBAT(dimension=1_000)
key    = enc.generate()
value  = enc.generate()

bound  = key.bind(value)
meta   = bound.get_metadata()   # contains 'matrices' key

# Unbind using the stored matrices
recovered = bound.unbind(key)
print(recovered.similarity(value))   # ~= 1.0

When unbind is not available

If you are using BSDC_CDT or another encoding that does not support unbinding, query via similarity search instead:

# For BSDC_CDT: use nearest-neighbour search on the value codebook
result = bound.unbind(key)   # NotImplementedError for BSDC_CDT

# Alternative: compare the bound record directly against all possible bound pairs
candidates = {v_name: key.bind(v_hv) for v_name, v_hv in values.items()}
best = max(candidates, key=lambda n: bound.similarity(candidates[n]))