VSA Operations¶
VSA operations define how hypervectors are combined and manipulated. VSAX provides three operation sets, each corresponding to a representation type.
Overview¶
All operation sets implement the AbstractOpSet interface with four core operations:
| Operation | Purpose | Example |
|---|---|---|
bind(a, b) |
Combine/associate two vectors | Role-filler binding |
bundle(*vecs) |
Superposition of multiple vectors | Create composite representations |
inverse(a) |
Compute inverse for unbinding | Retrieve bound information |
permute(a, shift) |
Circular shift/rotation | Sequential encoding |
FHRROperations¶
Operations for complex-valued hypervectors using FFT-based circular convolution.
Binding (Circular Convolution)¶
Binds two complex vectors using circular convolution implemented via FFT.
from vsax import FHRROperations
import jax.numpy as jnp
ops = FHRROperations()
# Create unit-magnitude complex vectors
a = jnp.exp(1j * jnp.array([0.1, 0.5, 1.0, 1.5]))
b = jnp.exp(1j * jnp.array([0.2, 0.6, 1.1, 1.6]))
# Bind via circular convolution
bound = ops.bind(a, b)
# Result is also complex
assert jnp.iscomplexobj(bound)
Properties:
- Commutative: bind(a, b) = bind(b, a)
- Associative: bind(a, bind(b, c)) = bind(bind(a, b), c)
- Invertible: Can recover a from bind(a, b) using inverse(b)
Bundling (Sum and Normalize)¶
Bundles multiple vectors by summing and normalizing to unit magnitude.
# Bundle three vectors
bundled = ops.bundle(a, b, c)
# All elements have unit magnitude
assert jnp.allclose(jnp.abs(bundled), 1.0)
Properties: - Similarity preserving: Bundled vector is similar to constituents - Approximate: Some information loss occurs - Commutative: Order doesn't matter
Inverse (Frequency-Domain Conjugate)¶
For FHRR circular convolution, the inverse uses frequency-domain conjugate:
inverse(b) = ifft(conj(fft(b)))
This ensures high-accuracy unbinding (>99% with proper FHRR vectors).
# Method 1: Explicit unbind (RECOMMENDED)
recovered = ops.unbind(bound, b)
# Method 2: Using inverse (equivalent)
inv_b = ops.inverse(b)
recovered = ops.bind(bound, inv_b)
# With proper FHRR vectors (sample_fhrr_random):
# recovered ≈ a with >99% similarity!
Note: For optimal unbinding accuracy, use sample_fhrr_random() which generates
vectors with conjugate symmetry. General complex phasors achieve ~70% accuracy,
while proper FHRR vectors achieve >99% accuracy.
Example: Role-Filler Binding¶
# Represent "The dog chased the cat"
subject = jnp.exp(1j * jax.random.uniform(key1, (512,)))
verb = jnp.exp(1j * jax.random.uniform(key2, (512,)))
object_ = jnp.exp(1j * jax.random.uniform(key3, (512,)))
dog = jnp.exp(1j * jax.random.uniform(key4, (512,)))
chase = jnp.exp(1j * jax.random.uniform(key5, (512,)))
cat = jnp.exp(1j * jax.random.uniform(key6, (512,)))
# Create sentence representation
sentence = ops.bundle(
ops.bind(subject, dog),
ops.bind(verb, chase),
ops.bind(object_, cat)
)
# Query: What was the subject?
query = ops.bind(sentence, ops.inverse(subject))
# query ≈ dog (high similarity)
MAPOperations¶
Operations for real-valued hypervectors using element-wise operations.
Binding (Element-wise Multiplication)¶
Simplest binding operation - just multiply element-wise.
from vsax import MAPOperations
ops = MAPOperations()
# Real vectors
a = jax.random.normal(key1, (512,))
b = jax.random.normal(key2, (512,))
# Bind via multiplication
bound = ops.bind(a, b)
assert bound.shape == a.shape
assert jnp.array_equal(bound, a * b)
Properties:
- Commutative: bind(a, b) = bind(b, a)
- Associative: bind(a, bind(b, c)) = bind(bind(a, b), c)
- Approximate unbinding: Cannot perfectly recover original
Bundling (Element-wise Mean)¶
Average of all input vectors.
# Bundle three vectors
bundled = ops.bundle(a, b, c)
# Result is the mean
assert jnp.allclose(bundled, (a + b + c) / 3)
Properties: - Order-independent - Lossy: Individual vectors cannot be perfectly recovered - Preserves similarity: Bundled vector similar to constituents
Inverse (Approximate)¶
MAP uses an approximate inverse based on normalization.
# Method 1: Explicit unbind (RECOMMENDED)
recovered = ops.unbind(bound, b)
# Method 2: Using inverse (equivalent)
inv_b = ops.inverse(b)
recovered = ops.bind(bound, inv_b)
# recovered ≈ a (but not exact, typically ~30% similarity)
Note: MAP unbinding is approximate - use for applications where exact recovery isn't critical. Typical unbinding similarity is ~30-40%, sufficient for many pattern matching and classification tasks.
Example: Feature Binding¶
# Represent a data point: {age: 25, income: 50000, city: "SF"}
age_role = jax.random.normal(key1, (512,))
income_role = jax.random.normal(key2, (512,))
city_role = jax.random.normal(key3, (512,))
age_25 = jax.random.normal(key4, (512,))
income_50k = jax.random.normal(key5, (512,))
sf = jax.random.normal(key6, (512,))
# Create record
record = ops.bundle(
ops.bind(age_role, age_25),
ops.bind(income_role, income_50k),
ops.bind(city_role, sf)
)
BinaryOperations¶
Operations for binary hypervectors using XOR and majority voting.
Binding (XOR)¶
In bipolar {-1, +1} representation, XOR is implemented as multiplication.
from vsax import BinaryOperations
ops = BinaryOperations()
# Bipolar vectors
a = jnp.array([1, -1, 1, -1, 1, 1, -1, -1])
b = jnp.array([1, 1, -1, -1, 1, -1, 1, -1])
# Bind via XOR (multiplication in bipolar)
bound = ops.bind(a, b)
# Result: element-wise multiplication
# Same values → +1, different values → -1
Properties:
- Commutative: bind(a, b) = bind(b, a)
- Associative: bind(a, bind(b, c)) = bind(bind(a, b), c)
- Self-inverse: bind(bind(a, b), b) = a (exact unbinding!)
Bundling (Majority Vote)¶
Each position in the bundled vector is determined by majority vote.
a = jnp.array([1, -1, 1, -1])
b = jnp.array([1, 1, -1, -1])
c = jnp.array([1, 1, 1, 1])
bundled = ops.bundle(a, b, c)
# Position 0: [1, 1, 1] → majority 1
# Position 1: [-1, 1, 1] → majority 1
# Position 2: [1, -1, 1] → majority 1
# Position 3: [-1, -1, 1] → majority -1
# Result: [1, 1, 1, -1]
Tie Breaking: For even numbers of vectors, ties default to +1.
Inverse (Self-Inverse Property)¶
XOR is its own inverse, so inverse(a) = a. This means unbinding is
identical to binding for Binary VSA!
# Method 1: Explicit unbind (RECOMMENDED - clearer intent)
recovered = ops.unbind(bound, b)
assert jnp.array_equal(recovered, a) # Exact recovery!
# Method 2: Using inverse (equivalent due to self-inverse)
inv_b = ops.inverse(b) # inv_b == b
recovered = ops.bind(bound, inv_b)
assert jnp.array_equal(recovered, a) # Same result!
# Method 3: Direct binding (works because XOR is self-inverse)
recovered = ops.bind(bound, b)
assert jnp.array_equal(recovered, a) # Also works!
Key insight: For Binary VSA, unbind(a, b) = bind(a, b) = a XOR b
due to XOR's self-inverse property.
Example: Symbolic Reasoning¶
# Encode facts: "Alice likes Bob", "Bob likes Charlie"
alice = jax.random.choice(key1, jnp.array([-1, 1]), (512,))
bob = jax.random.choice(key2, jnp.array([-1, 1]), (512,))
charlie = jax.random.choice(key3, jnp.array([-1, 1]), (512,))
likes = jax.random.choice(key4, jnp.array([-1, 1]), (512,))
# Create knowledge base
fact1 = ops.bind(ops.bind(alice, likes), bob)
fact2 = ops.bind(ops.bind(bob, likes), charlie)
kb = ops.bundle(fact1, fact2)
# Query: Who does Alice like?
query = ops.bind(ops.bind(kb, alice), likes)
# High similarity to bob
Permutation¶
All operation sets support circular permutation (rotation).
vec = jnp.array([1, 2, 3, 4, 5])
# Rotate right by 2
shifted = ops.permute(vec, 2)
# Result: [4, 5, 1, 2, 3]
# Rotate left by 2
shifted = ops.permute(vec, -2)
# Result: [3, 4, 5, 1, 2]
Use cases: - Sequence encoding - Temporal ordering - Positional information
Comparison¶
| Feature | FHRR | MAP | Binary |
|---|---|---|---|
| Binding | FFT convolution | Element-wise × | XOR |
| Unbinding | Exact (conjugate) | Approximate | Exact (self-inverse) |
| Bundling | Sum + normalize | Mean | Majority vote |
| Complexity | O(n log n) | O(n) | O(n) |
| Memory | 2x (complex) | 1x | 1/32x |
Best Practices¶
- Normalize inputs: Ensure vectors are properly normalized before operations
- Consistent types: Don't mix operation sets with wrong representations
- Batch operations: Use JAX's
vmapfor processing multiple vectors - Numerical stability: Be aware of numerical precision, especially with FHRR