Source code for pyhdc.components.binding.vtb

from math import sqrt

import numpy as np

# Optional PyTorch support
try:
    import torch

    TORCH_AVAILABLE = True
except ImportError:
    TORCH_AVAILABLE = False
    torch = None

from pyhdc.components.input_formatting import _normalize_binding

# Type aliases
from pyhdc.types import ArrayLike

# ============================================================================
# Vector-Derived Transformation Binding (VTB)
# ============================================================================


def _vtb_get_y_prime(y: ArrayLike, is_torch: bool) -> ArrayLike:
    """
    Compute the V_y matrix for Vector-Derived Transformation Binding.

    Calculates the block diagonal matrix V_y used in VTB:

    V_y' = d^(1/4) * | y_1       y_2       ...  y_d'  |
                     | y_d'+1    y_d'+2    ...  y_2d' |
                     |  :          :       ...   :    |
                     | y_d-d'+1  y_d-d'+2  ...  y_d   |

    Args:
        y: Input hypervector
        is_torch: Whether using PyTorch backend

    Returns:
        V_y matrix
    """
    size = y.shape[0] if hasattr(y, "shape") else len(y)
    d_prime = int(sqrt(size))
    d_quart = pow(size, 0.25)

    if is_torch:
        # Reshape into square matrix
        V_prime = y.reshape(d_prime, d_prime)
        # Create block diagonal
        Z_prime = torch.zeros((d_prime, d_prime), dtype=y.dtype, device=y.device)
        blocks = [
            [V_prime if i == j else Z_prime for i in range(d_prime)]
            for j in range(d_prime)
        ]
        # Flatten and concatenate blocks
        V_y = torch.block_diag(*[V_prime for _ in range(d_prime)])
        V_y = d_quart * V_y
    else:
        # NumPy implementation
        V_prime = y.reshape(d_prime, d_prime)
        Z_prime = np.zeros([d_prime, d_prime])
        V_y = d_quart * np.block(
            [
                [V_prime if i == j else Z_prime for i in range(d_prime)]
                for j in range(d_prime)
            ]
        )

    return V_y


[docs] def VectorDerivedTransformation(*hypervectors: ArrayLike) -> ArrayLike: """ Vector-Derived Transformation Binding. Binds vectors using the equation: B_v(y, x) = V_y * x where V_y is a matrix derived from vector y. For multiple vectors [x1, x2, x3, ..., xn]: B_v(x1, x2, x3, ..., xn) = V_xn * V_x(n-1) * ... * V_x2 * x1 Args: *hypervectors: Variable number of hypervectors to bind, or single 2D batch Returns: Bound hypervector Note: Requires hypervector dimension to be a perfect fourth power (d = k^4) """ hvs, is_torch, _ = _normalize_binding(*hypervectors) # Compute V_y matrices for all but the first hypervector V_y_list = [_vtb_get_y_prime(hvs[i], is_torch) for i in range(1, len(hvs))] V_y_list.reverse() # Reverse for proper multiplication order # Multiply matrices together if is_torch: result = V_y_list[0] for i in range(1, len(V_y_list)): result = torch.matmul(result, V_y_list[i]) # Apply to first vector x = hvs[0].unsqueeze(1) # Column vector result = torch.matmul(result, x).squeeze() else: result = V_y_list[0] for i in range(1, len(V_y_list)): result = np.dot(result, V_y_list[i]) # Apply to first vector x = hvs[0].reshape(-1, 1) # Column vector result = (result @ x).squeeze() return result
[docs] def TransposeVectorDerivedTransformation(*hypervectors: ArrayLike) -> ArrayLike: """ Pseudo-inverse for Vector-Derived Transformation (unbinding). Unbinds vectors using: B+_v(y, x) = V_y^T * x where V_y^T is the transpose of the V_y matrix. Args: *hypervectors: Variable number of hypervectors, or single 2D batch Returns: Unbound hypervector """ hvs, is_torch, _ = _normalize_binding(*hypervectors) # Compute transposed V_y matrices V_y_list = [_vtb_get_y_prime(hvs[i], is_torch).T for i in range(1, len(hvs))] V_y_list.reverse() # Multiply matrices together if is_torch: result = V_y_list[0] for i in range(1, len(V_y_list)): result = torch.matmul(result, V_y_list[i]) # Apply to first vector x = hvs[0].unsqueeze(1) result = torch.matmul(result, x).squeeze() else: result = V_y_list[0] for i in range(1, len(V_y_list)): result = np.dot(result, V_y_list[i]) # Apply to first vector x = hvs[0].reshape(-1, 1) result = (result @ x).squeeze() return result