Skip to content

Resonator Networks API

Overview

The resonator module implements resonator networks for VSA factorization.

Given a composite vector s = a ⊙ b ⊙ c, resonator networks iteratively recover the factors a, b, c from known codebooks.

CleanupMemory

vsax.resonator.CleanupMemory

Cleanup memory for projecting vectors onto a codebook.

This class implements codebook projection, which finds the nearest vector from a set of known vectors (codebook) to a query vector.

Parameters:

Name Type Description Default
codebook list[str]

List of named symbols from VSAMemory to use as codebook.

required
memory VSAMemory

VSAMemory containing the basis vectors.

required
threshold float

Optional similarity threshold for cleanup (default: 0.0). If best match is below threshold, returns None.

0.0
Example

model = create_binary_model(dim=10000) memory = VSAMemory(model) memory.add_many(["red", "blue", "green"]) cleanup = CleanupMemory(["red", "blue", "green"], memory) noisy = model.opset.bundle(memory["red"].vec, memory["blue"].vec) result = cleanup.query(noisy) print(result) # Should return "red" or "blue"

Source code in vsax/resonator/cleanup.py
class CleanupMemory:
    """Cleanup memory for projecting vectors onto a codebook.

    This class implements codebook projection, which finds the nearest
    vector from a set of known vectors (codebook) to a query vector.

    Args:
        codebook: List of named symbols from VSAMemory to use as codebook.
        memory: VSAMemory containing the basis vectors.
        threshold: Optional similarity threshold for cleanup (default: 0.0).
                   If best match is below threshold, returns None.

    Example:
        >>> model = create_binary_model(dim=10000)
        >>> memory = VSAMemory(model)
        >>> memory.add_many(["red", "blue", "green"])
        >>> cleanup = CleanupMemory(["red", "blue", "green"], memory)
        >>> noisy = model.opset.bundle(memory["red"].vec, memory["blue"].vec)
        >>> result = cleanup.query(noisy)
        >>> print(result)  # Should return "red" or "blue"
    """

    def __init__(
        self,
        codebook: list[str],
        memory: VSAMemory,
        threshold: float = 0.0,
    ) -> None:
        """Initialize cleanup memory with codebook."""
        self.codebook = codebook
        self.memory = memory
        self.threshold = threshold

        # Validate codebook symbols exist in memory
        for symbol in codebook:
            if symbol not in memory:
                raise ValueError(f"Symbol '{symbol}' not found in memory")

        # Pre-compute codebook matrix for efficient lookup
        self._codebook_vecs = jnp.stack([memory[name].vec for name in codebook])

    def query(
        self,
        vec: Union[jnp.ndarray, AbstractHypervector],
        return_similarity: bool = False,
    ) -> Union[Optional[str], tuple[Optional[str], float]]:
        """Project vector onto codebook and return nearest symbol.

        Args:
            vec: Query vector to cleanup (array or hypervector).
            return_similarity: If True, also return similarity score.

        Returns:
            If return_similarity=False: Symbol name or None if below threshold.
            If return_similarity=True: Tuple of (symbol, similarity) or (None, similarity).

        Example:
            >>> result = cleanup.query(noisy_vec)
            >>> result_with_score = cleanup.query(noisy_vec, return_similarity=True)
            >>> print(result_with_score)  # ("red", 0.95)
        """
        # Coerce to array if hypervector
        if isinstance(vec, AbstractHypervector):
            vec = vec.vec

        # Compute similarities to all codebook vectors
        # For complex vectors, use conjugate dot product (inner product)
        # For real/binary vectors, use direct dot product
        if jnp.iscomplexobj(self._codebook_vecs):
            # Complex case: use conjugate dot product, then take abs for similarity
            similarities = jnp.abs(jnp.dot(self._codebook_vecs.conj(), vec))
        else:
            # Real/binary case: direct dot product
            similarities = jnp.dot(self._codebook_vecs, vec)

        # Find best match
        best_idx = int(jnp.argmax(similarities))
        best_sim = float(similarities[best_idx])

        # Check threshold
        if best_sim < self.threshold:
            return (None, best_sim) if return_similarity else None

        best_symbol = self.codebook[best_idx]
        return (best_symbol, best_sim) if return_similarity else best_symbol

    def query_top_k(
        self,
        vec: Union[jnp.ndarray, AbstractHypervector],
        k: int = 3,
    ) -> list[tuple[str, float]]:
        """Return top-k closest symbols with similarity scores.

        Args:
            vec: Query vector to cleanup.
            k: Number of top matches to return.

        Returns:
            List of (symbol, similarity) tuples sorted by similarity (descending).

        Example:
            >>> top_matches = cleanup.query_top_k(noisy_vec, k=3)
            >>> for symbol, sim in top_matches:
            ...     print(f"{symbol}: {sim:.3f}")
        """
        # Coerce to array if hypervector
        if isinstance(vec, AbstractHypervector):
            vec = vec.vec

        # Compute similarities
        if jnp.iscomplexobj(self._codebook_vecs):
            similarities = jnp.abs(jnp.dot(self._codebook_vecs.conj(), vec))
        else:
            similarities = jnp.dot(self._codebook_vecs, vec)

        # Get top-k indices
        top_k_indices = jnp.argsort(similarities)[-k:][::-1]

        # Build result list
        results = [(self.codebook[int(idx)], float(similarities[idx])) for idx in top_k_indices]

        return results

    def __len__(self) -> int:
        """Return number of vectors in codebook."""
        return len(self.codebook)

    def __repr__(self) -> str:
        """Return string representation."""
        return f"CleanupMemory(codebook_size={len(self.codebook)}, threshold={self.threshold})"

Functions

__init__(codebook, memory, threshold=0.0)

Initialize cleanup memory with codebook.

Source code in vsax/resonator/cleanup.py
def __init__(
    self,
    codebook: list[str],
    memory: VSAMemory,
    threshold: float = 0.0,
) -> None:
    """Initialize cleanup memory with codebook."""
    self.codebook = codebook
    self.memory = memory
    self.threshold = threshold

    # Validate codebook symbols exist in memory
    for symbol in codebook:
        if symbol not in memory:
            raise ValueError(f"Symbol '{symbol}' not found in memory")

    # Pre-compute codebook matrix for efficient lookup
    self._codebook_vecs = jnp.stack([memory[name].vec for name in codebook])
query(vec, return_similarity=False)

Project vector onto codebook and return nearest symbol.

Parameters:

Name Type Description Default
vec Union[ndarray, AbstractHypervector]

Query vector to cleanup (array or hypervector).

required
return_similarity bool

If True, also return similarity score.

False

Returns:

Type Description
Union[Optional[str], tuple[Optional[str], float]]

If return_similarity=False: Symbol name or None if below threshold.

Union[Optional[str], tuple[Optional[str], float]]

If return_similarity=True: Tuple of (symbol, similarity) or (None, similarity).

Example

result = cleanup.query(noisy_vec) result_with_score = cleanup.query(noisy_vec, return_similarity=True) print(result_with_score) # ("red", 0.95)

Source code in vsax/resonator/cleanup.py
def query(
    self,
    vec: Union[jnp.ndarray, AbstractHypervector],
    return_similarity: bool = False,
) -> Union[Optional[str], tuple[Optional[str], float]]:
    """Project vector onto codebook and return nearest symbol.

    Args:
        vec: Query vector to cleanup (array or hypervector).
        return_similarity: If True, also return similarity score.

    Returns:
        If return_similarity=False: Symbol name or None if below threshold.
        If return_similarity=True: Tuple of (symbol, similarity) or (None, similarity).

    Example:
        >>> result = cleanup.query(noisy_vec)
        >>> result_with_score = cleanup.query(noisy_vec, return_similarity=True)
        >>> print(result_with_score)  # ("red", 0.95)
    """
    # Coerce to array if hypervector
    if isinstance(vec, AbstractHypervector):
        vec = vec.vec

    # Compute similarities to all codebook vectors
    # For complex vectors, use conjugate dot product (inner product)
    # For real/binary vectors, use direct dot product
    if jnp.iscomplexobj(self._codebook_vecs):
        # Complex case: use conjugate dot product, then take abs for similarity
        similarities = jnp.abs(jnp.dot(self._codebook_vecs.conj(), vec))
    else:
        # Real/binary case: direct dot product
        similarities = jnp.dot(self._codebook_vecs, vec)

    # Find best match
    best_idx = int(jnp.argmax(similarities))
    best_sim = float(similarities[best_idx])

    # Check threshold
    if best_sim < self.threshold:
        return (None, best_sim) if return_similarity else None

    best_symbol = self.codebook[best_idx]
    return (best_symbol, best_sim) if return_similarity else best_symbol
query_top_k(vec, k=3)

Return top-k closest symbols with similarity scores.

Parameters:

Name Type Description Default
vec Union[ndarray, AbstractHypervector]

Query vector to cleanup.

required
k int

Number of top matches to return.

3

Returns:

Type Description
list[tuple[str, float]]

List of (symbol, similarity) tuples sorted by similarity (descending).

Example

top_matches = cleanup.query_top_k(noisy_vec, k=3) for symbol, sim in top_matches: ... print(f"{symbol}: {sim:.3f}")

Source code in vsax/resonator/cleanup.py
def query_top_k(
    self,
    vec: Union[jnp.ndarray, AbstractHypervector],
    k: int = 3,
) -> list[tuple[str, float]]:
    """Return top-k closest symbols with similarity scores.

    Args:
        vec: Query vector to cleanup.
        k: Number of top matches to return.

    Returns:
        List of (symbol, similarity) tuples sorted by similarity (descending).

    Example:
        >>> top_matches = cleanup.query_top_k(noisy_vec, k=3)
        >>> for symbol, sim in top_matches:
        ...     print(f"{symbol}: {sim:.3f}")
    """
    # Coerce to array if hypervector
    if isinstance(vec, AbstractHypervector):
        vec = vec.vec

    # Compute similarities
    if jnp.iscomplexobj(self._codebook_vecs):
        similarities = jnp.abs(jnp.dot(self._codebook_vecs.conj(), vec))
    else:
        similarities = jnp.dot(self._codebook_vecs, vec)

    # Get top-k indices
    top_k_indices = jnp.argsort(similarities)[-k:][::-1]

    # Build result list
    results = [(self.codebook[int(idx)], float(similarities[idx])) for idx in top_k_indices]

    return results
__len__()

Return number of vectors in codebook.

Source code in vsax/resonator/cleanup.py
def __len__(self) -> int:
    """Return number of vectors in codebook."""
    return len(self.codebook)
__repr__()

Return string representation.

Source code in vsax/resonator/cleanup.py
def __repr__(self) -> str:
    """Return string representation."""
    return f"CleanupMemory(codebook_size={len(self.codebook)}, threshold={self.threshold})"

Example

from vsax import create_binary_model, VSAMemory
from vsax.resonator import CleanupMemory

model = create_binary_model(dim=10000, bipolar=True)
memory = VSAMemory(model)
memory.add_many(["red", "blue", "green"])

# Create cleanup memory
cleanup = CleanupMemory(["red", "blue", "green"], memory)

# Query with noisy vector
result = cleanup.query(noisy_vector)
print(result)  # "red"

# Get top-k matches
top_3 = cleanup.query_top_k(noisy_vector, k=3)
for symbol, similarity in top_3:
    print(f"{symbol}: {similarity:.3f}")

Resonator

vsax.resonator.Resonator

Resonator network for factorizing composite VSA vectors.

Given a composite vector s = a ⊙ b ⊙ c, this class implements an iterative algorithm to find the factors a, b, c from known codebooks.

The algorithm alternates between: 1. Unbinding current estimates of other factors from s 2. Cleaning up the result using codebook projection

Parameters:

Name Type Description Default
codebooks list[CleanupMemory]

List of CleanupMemory objects, one per factor position.

required
opset AbstractOpSet

Operation set defining bind/unbind operations.

required
max_iterations int

Maximum number of iterations (default: 100).

100
convergence_threshold int

Stop if estimates don't change (default: 3).

3
Example

model = create_binary_model(dim=10000) memory = VSAMemory(model) memory.add_many(["red", "blue", "circle", "square"])

Create codebooks for two factor positions

colors = CleanupMemory(["red", "blue"], memory) shapes = CleanupMemory(["circle", "square"], memory)

Create composite: red ⊙ circle

composite = model.opset.bind( ... memory["red"].vec, ... memory["circle"].vec ... )

Factorize

resonator = Resonator([colors, shapes], model.opset) factors = resonator.factorize(composite) print(factors) # ["red", "circle"]

Source code in vsax/resonator/resonator.py
class Resonator:
    """Resonator network for factorizing composite VSA vectors.

    Given a composite vector s = a ⊙ b ⊙ c, this class implements an
    iterative algorithm to find the factors a, b, c from known codebooks.

    The algorithm alternates between:
    1. Unbinding current estimates of other factors from s
    2. Cleaning up the result using codebook projection

    Args:
        codebooks: List of CleanupMemory objects, one per factor position.
        opset: Operation set defining bind/unbind operations.
        max_iterations: Maximum number of iterations (default: 100).
        convergence_threshold: Stop if estimates don't change (default: 3).

    Example:
        >>> model = create_binary_model(dim=10000)
        >>> memory = VSAMemory(model)
        >>> memory.add_many(["red", "blue", "circle", "square"])
        >>>
        >>> # Create codebooks for two factor positions
        >>> colors = CleanupMemory(["red", "blue"], memory)
        >>> shapes = CleanupMemory(["circle", "square"], memory)
        >>>
        >>> # Create composite: red ⊙ circle
        >>> composite = model.opset.bind(
        ...     memory["red"].vec,
        ...     memory["circle"].vec
        ... )
        >>>
        >>> # Factorize
        >>> resonator = Resonator([colors, shapes], model.opset)
        >>> factors = resonator.factorize(composite)
        >>> print(factors)  # ["red", "circle"]
    """

    def __init__(
        self,
        codebooks: list[CleanupMemory],
        opset: AbstractOpSet,
        max_iterations: int = 100,
        convergence_threshold: int = 3,
    ) -> None:
        """Initialize resonator network."""
        if len(codebooks) < 2:
            raise ValueError("Need at least 2 codebooks for factorization")

        self.codebooks = codebooks
        self.opset = opset
        self.max_iterations = max_iterations
        self.convergence_threshold = convergence_threshold
        self.num_factors = len(codebooks)

    def factorize(
        self,
        composite: Union[jnp.ndarray, AbstractHypervector],
        initial_estimates: Optional[list[str]] = None,
        return_history: bool = False,
    ) -> Union[list[Optional[str]], tuple[list[Optional[str]], list[list[Optional[str]]]]]:
        """Factorize a composite vector into its constituent factors.

        Args:
            composite: Composite vector to factorize.
            initial_estimates: Optional initial guesses for factors.
                              If None, uses superposition of all codebook vectors.
            return_history: If True, return iteration history.

        Returns:
            If return_history=False: List of factor names (or None if not converged).
            If return_history=True: Tuple of (factors, history) where history is
                                    a list of factor estimates at each iteration.

        Example:
            >>> factors = resonator.factorize(composite)
            >>> factors, history = resonator.factorize(composite, return_history=True)
        """
        # Coerce to array if hypervector
        if isinstance(composite, AbstractHypervector):
            composite = composite.vec

        # Initialize estimates
        estimates = self._initialize_estimates(initial_estimates)
        history: list[list[Optional[str]]] = [estimates.copy()] if return_history else []

        # Track convergence
        stable_count = 0
        prev_estimates = estimates.copy()

        # Iterative resonance
        for iteration in range(self.max_iterations):
            # Update each factor estimate
            for i in range(self.num_factors):
                estimates[i] = self._update_factor(composite, estimates, i)

            # Track history
            if return_history:
                history.append(estimates.copy())

            # Check convergence
            if estimates == prev_estimates:
                stable_count += 1
                if stable_count >= self.convergence_threshold:
                    break
            else:
                stable_count = 0
                prev_estimates = estimates.copy()

        # Return results
        if return_history:
            return estimates, history
        return estimates

    def _initialize_estimates(
        self,
        initial_estimates: Optional[list[str]] = None,
    ) -> list[Optional[str]]:
        """Initialize factor estimates.

        If initial_estimates provided, validate and use them.
        Otherwise, use None (superposition initialization happens in update).
        """
        if initial_estimates is not None:
            if len(initial_estimates) != self.num_factors:
                raise ValueError(
                    f"Expected {self.num_factors} initial estimates, got {len(initial_estimates)}"
                )
            # Validate estimates exist in codebooks
            for i, est in enumerate(initial_estimates):
                if est not in self.codebooks[i].codebook:
                    raise ValueError(f"Initial estimate '{est}' not in codebook {i}")
            # Cast to list[Optional[str]] for type compatibility
            return cast(list[Optional[str]], initial_estimates.copy())

        # Start with None (will use superposition in first iteration)
        return [None] * self.num_factors

    def _update_factor(
        self,
        composite: jnp.ndarray,
        current_estimates: list[Optional[str]],
        factor_idx: int,
    ) -> Optional[str]:
        """Update estimate for a single factor.

        Implements: x̂(t+1) = g(XX^T(s ⊙ ŷ(t) ⊙ ẑ(t)))

        Args:
            composite: The composite vector s.
            current_estimates: Current estimates for all factors.
            factor_idx: Which factor to update.

        Returns:
            Updated factor name or None.
        """
        # Start with composite vector
        residual = composite

        # Unbind all OTHER factors from composite
        # s ⊙ inverse(ŷ) ⊙ inverse(ẑ) should leave x̂
        for i, estimate_name in enumerate(current_estimates):
            if i == factor_idx:
                continue

            if estimate_name is None:
                # No estimate yet - use superposition of all vectors in codebook
                # This is the initialization from the paper
                codebook_vecs = self.codebooks[i]._codebook_vecs
                superposition = jnp.sum(codebook_vecs, axis=0)
                residual = self.opset.bind(residual, self.opset.inverse(superposition))
            else:
                # Use the current estimate
                factor_vec = self.codebooks[i].memory[estimate_name].vec
                residual = self.opset.bind(residual, self.opset.inverse(factor_vec))

        # Cleanup: project residual onto codebook for this factor
        # This is g(XX^T(...)) from the paper
        # query with return_similarity=False returns Optional[str]
        result: Optional[str] = self.codebooks[factor_idx].query(residual)  # type: ignore[assignment]

        return result

    def factorize_batch(
        self,
        composites: jnp.ndarray,
        initial_estimates: Optional[list[list[str]]] = None,
    ) -> list[list[Optional[str]]]:
        """Factorize multiple composite vectors.

        Args:
            composites: Array of composite vectors, shape (batch_size, dim).
            initial_estimates: Optional initial guesses for each composite.

        Returns:
            List of factor lists, one per composite.

        Example:
            >>> composites = jnp.stack([comp1, comp2, comp3])
            >>> all_factors = resonator.factorize_batch(composites)
        """
        batch_size = composites.shape[0]
        results: list[list[Optional[str]]] = []

        for i in range(batch_size):
            init = initial_estimates[i] if initial_estimates else None
            # factorize returns list[Optional[str]] when return_history=False (default)
            factors = self.factorize(composites[i], initial_estimates=init, return_history=False)
            # Type narrowing: we know it's just the list, not the tuple
            assert isinstance(factors, list), "Expected list without history"
            results.append(factors)

        return results

    def __repr__(self) -> str:
        """Return string representation."""
        return (
            f"Resonator(num_factors={self.num_factors}, "
            f"max_iterations={self.max_iterations}, "
            f"convergence_threshold={self.convergence_threshold})"
        )

Functions

__init__(codebooks, opset, max_iterations=100, convergence_threshold=3)

Initialize resonator network.

Source code in vsax/resonator/resonator.py
def __init__(
    self,
    codebooks: list[CleanupMemory],
    opset: AbstractOpSet,
    max_iterations: int = 100,
    convergence_threshold: int = 3,
) -> None:
    """Initialize resonator network."""
    if len(codebooks) < 2:
        raise ValueError("Need at least 2 codebooks for factorization")

    self.codebooks = codebooks
    self.opset = opset
    self.max_iterations = max_iterations
    self.convergence_threshold = convergence_threshold
    self.num_factors = len(codebooks)
factorize(composite, initial_estimates=None, return_history=False)

Factorize a composite vector into its constituent factors.

Parameters:

Name Type Description Default
composite Union[ndarray, AbstractHypervector]

Composite vector to factorize.

required
initial_estimates Optional[list[str]]

Optional initial guesses for factors. If None, uses superposition of all codebook vectors.

None
return_history bool

If True, return iteration history.

False

Returns:

Type Description
Union[list[Optional[str]], tuple[list[Optional[str]], list[list[Optional[str]]]]]

If return_history=False: List of factor names (or None if not converged).

Union[list[Optional[str]], tuple[list[Optional[str]], list[list[Optional[str]]]]]

If return_history=True: Tuple of (factors, history) where history is a list of factor estimates at each iteration.

Example

factors = resonator.factorize(composite) factors, history = resonator.factorize(composite, return_history=True)

Source code in vsax/resonator/resonator.py
def factorize(
    self,
    composite: Union[jnp.ndarray, AbstractHypervector],
    initial_estimates: Optional[list[str]] = None,
    return_history: bool = False,
) -> Union[list[Optional[str]], tuple[list[Optional[str]], list[list[Optional[str]]]]]:
    """Factorize a composite vector into its constituent factors.

    Args:
        composite: Composite vector to factorize.
        initial_estimates: Optional initial guesses for factors.
                          If None, uses superposition of all codebook vectors.
        return_history: If True, return iteration history.

    Returns:
        If return_history=False: List of factor names (or None if not converged).
        If return_history=True: Tuple of (factors, history) where history is
                                a list of factor estimates at each iteration.

    Example:
        >>> factors = resonator.factorize(composite)
        >>> factors, history = resonator.factorize(composite, return_history=True)
    """
    # Coerce to array if hypervector
    if isinstance(composite, AbstractHypervector):
        composite = composite.vec

    # Initialize estimates
    estimates = self._initialize_estimates(initial_estimates)
    history: list[list[Optional[str]]] = [estimates.copy()] if return_history else []

    # Track convergence
    stable_count = 0
    prev_estimates = estimates.copy()

    # Iterative resonance
    for iteration in range(self.max_iterations):
        # Update each factor estimate
        for i in range(self.num_factors):
            estimates[i] = self._update_factor(composite, estimates, i)

        # Track history
        if return_history:
            history.append(estimates.copy())

        # Check convergence
        if estimates == prev_estimates:
            stable_count += 1
            if stable_count >= self.convergence_threshold:
                break
        else:
            stable_count = 0
            prev_estimates = estimates.copy()

    # Return results
    if return_history:
        return estimates, history
    return estimates
factorize_batch(composites, initial_estimates=None)

Factorize multiple composite vectors.

Parameters:

Name Type Description Default
composites ndarray

Array of composite vectors, shape (batch_size, dim).

required
initial_estimates Optional[list[list[str]]]

Optional initial guesses for each composite.

None

Returns:

Type Description
list[list[Optional[str]]]

List of factor lists, one per composite.

Example

composites = jnp.stack([comp1, comp2, comp3]) all_factors = resonator.factorize_batch(composites)

Source code in vsax/resonator/resonator.py
def factorize_batch(
    self,
    composites: jnp.ndarray,
    initial_estimates: Optional[list[list[str]]] = None,
) -> list[list[Optional[str]]]:
    """Factorize multiple composite vectors.

    Args:
        composites: Array of composite vectors, shape (batch_size, dim).
        initial_estimates: Optional initial guesses for each composite.

    Returns:
        List of factor lists, one per composite.

    Example:
        >>> composites = jnp.stack([comp1, comp2, comp3])
        >>> all_factors = resonator.factorize_batch(composites)
    """
    batch_size = composites.shape[0]
    results: list[list[Optional[str]]] = []

    for i in range(batch_size):
        init = initial_estimates[i] if initial_estimates else None
        # factorize returns list[Optional[str]] when return_history=False (default)
        factors = self.factorize(composites[i], initial_estimates=init, return_history=False)
        # Type narrowing: we know it's just the list, not the tuple
        assert isinstance(factors, list), "Expected list without history"
        results.append(factors)

    return results
__repr__()

Return string representation.

Source code in vsax/resonator/resonator.py
def __repr__(self) -> str:
    """Return string representation."""
    return (
        f"Resonator(num_factors={self.num_factors}, "
        f"max_iterations={self.max_iterations}, "
        f"convergence_threshold={self.convergence_threshold})"
    )

Example

from vsax import create_binary_model, VSAMemory
from vsax.resonator import CleanupMemory, Resonator

# Setup
model = create_binary_model(dim=10000, bipolar=True)
memory = VSAMemory(model)
memory.add_many(["red", "blue", "circle", "square"])

# Create composite
composite = model.opset.bind(
    memory["red"].vec,
    memory["circle"].vec
)

# Create codebooks
colors = CleanupMemory(["red", "blue"], memory)
shapes = CleanupMemory(["circle", "square"], memory)

# Factorize
resonator = Resonator([colors, shapes], model.opset)
factors = resonator.factorize(composite)

print(factors)  # ["red", "circle"]

Algorithm Details

Resonance Equations

For a composite s = a ⊙ b ⊙ c, the update equations are:

x̂(t+1) = g(XX^T(s ⊙ ŷ(t) ⊙ ẑ(t)))
ŷ(t+1) = g(YY^T(s ⊙ x̂(t) ⊙ ẑ(t)))
ẑ(t+1) = g(ZZ^T(s ⊙ x̂(t) ⊙ ŷ(t)))

Where: - x̂, ŷ, ẑ are factor estimates - X, Y, Z are codebook matrices - g(XX^T·) is the cleanup operation (codebook projection) - is the binding operation

Cleanup Operation

The cleanup operation g(XX^T v) projects vector v onto the nearest vector in codebook X.

For binary/bipolar vectors:

similarities = codebook_matrix @ v
best_idx = argmax(similarities)
result = codebook[best_idx]

For complex/real vectors:

similarities = [cosine_similarity(v, c) for c in codebook]
best_idx = argmax(similarities)
result = codebook[best_idx]

Initialization

On the first iteration (no prior estimates), the algorithm uses superposition initialization:

initial_estimate = sum(all_vectors_in_codebook)

This provides information about all possible factors simultaneously.

Convergence

The algorithm stops when:

  1. Stable convergence: Estimates unchanged for convergence_threshold iterations (default: 3)
  2. Max iterations: Reached max_iterations (default: 100)

Binary VSA typically converges in < 10 iterations due to exact unbinding.

Performance Characteristics

Time Complexity

Per iteration for N factors with codebook size M and dimension D: - Unbinding: O(N × D) - binding operations - Cleanup: O(M × D) - dot products with codebook - Total: O(N × M × D) per iteration

Typical iterations to convergence: 5-20

Space Complexity

  • Codebooks: O(M × D) per codebook
  • Estimates: O(N × D)
  • Total: O((N + M) × D)

Recommendations

Dimensionality: - Binary VSA: ≥10,000 dimensions - FHRR: ≥512 dimensions - MAP: ≥512 dimensions

Codebook Size: - Works well with codebooks of 2-100 items - Larger codebooks may require more iterations

Number of Factors: - Tested with 2-3 factors - Can handle more but convergence may slow

Common Patterns

Two-Factor Factorization

# Encode
composite = bind(a, b)

# Setup codebooks
codebook_a = CleanupMemory(["a1", "a2", "a3"], memory)
codebook_b = CleanupMemory(["b1", "b2", "b3"], memory)

# Factorize
resonator = Resonator([codebook_a, codebook_b], opset)
factors = resonator.factorize(composite)

Three-Factor Factorization

# Encode
composite = bind(bind(a, b), c)

# Setup
codebook_a = CleanupMemory([...], memory)
codebook_b = CleanupMemory([...], memory)
codebook_c = CleanupMemory([...], memory)

# Factorize
resonator = Resonator([codebook_a, codebook_b, codebook_c], opset)
factors = resonator.factorize(composite)

Batch Processing

import jax.numpy as jnp

# Create multiple composites
composites = jnp.stack([comp1, comp2, comp3, comp4])

# Batch factorize
results = resonator.factorize_batch(composites)
# results[i] contains factors for composites[i]

Monitoring Convergence

factors, history = resonator.factorize(
    composite,
    return_history=True
)

print(f"Converged in {len(history)} iterations")
for i, step in enumerate(history):
    print(f"Iteration {i}: {step}")

Error Handling

Invalid Codebook

# Raises ValueError if symbol not in memory
cleanup = CleanupMemory(["missing_symbol"], memory)
# ValueError: Symbol 'missing_symbol' not found in memory

Wrong Number of Estimates

# Raises ValueError if initial estimates don't match codebook count
resonator = Resonator([codebook1, codebook2], opset)
factors = resonator.factorize(composite, initial_estimates=["a"])
# ValueError: Expected 2 initial estimates, got 1

Invalid Initial Estimate

# Raises ValueError if estimate not in corresponding codebook
factors = resonator.factorize(
    composite,
    initial_estimates=["valid", "not_in_codebook"]
)
# ValueError: Initial estimate 'not_in_codebook' not in codebook 1

See Also