"""
Unary HDC operations: permute, inverse, negative, normalize.
Each operates on a single hypervector (or batch) of raw array data, dimension
first (axis 0 is always the hypervector dimension ``D``), and works on both the
numpy and torch backends.
"""
from math import pi
import numpy as np
# Optional PyTorch support
try:
import torch
TORCH_AVAILABLE = True
except ImportError:
TORCH_AVAILABLE = False
torch = None
from pyhdc.types import ArrayLike
def _is_torch(data: ArrayLike) -> bool:
return TORCH_AVAILABLE and torch is not None and torch.is_tensor(data)
[docs]
def CyclicShift(data: ArrayLike, shift: int = 1) -> ArrayLike:
"""
Cyclic-shift permutation along axis 0 (the dimension); broadcasts over any
trailing batch axes. Encoding-agnostic, so it is the default ``permute``.
"""
amount = int(shift)
if _is_torch(data):
return torch.roll(data, shifts=amount, dims=0)
return np.roll(data, amount, axis=0)
[docs]
def IdentityInverse(data: ArrayLike) -> ArrayLike:
"""
Inverse for self-inverse binding (MAP bipolar multiply, BSC XOR): the element
is its own inverse. Exact for bipolar/binary values.
"""
return data
[docs]
def ReverseInverse(data: ArrayLike) -> ArrayLike:
"""
Exact involution inverse of circular convolution (HRR): keep index 0 and
reverse the remaining coordinates along axis 0.
"""
if _is_torch(data):
return torch.cat([data[:1], torch.flip(data[1:], dims=[0])], dim=0)
return np.concatenate([data[:1], np.flip(data[1:], axis=0)], axis=0)
[docs]
def PhaseNegate(data: ArrayLike) -> ArrayLike:
"""Inverse for FHRR angle binding: negate the phase (mod 2*pi)."""
if _is_torch(data):
return torch.remainder(-data, 2 * pi)
return np.mod(-data, 2 * pi)
[docs]
def Negate(data: ArrayLike) -> ArrayLike:
"""Additive (bundling) inverse: element-wise negation."""
return -data
[docs]
def L2Normalize(data: ArrayLike) -> ArrayLike:
"""Normalize each hypervector to unit L2 length along axis 0."""
if _is_torch(data):
return data / torch.norm(data, dim=0, keepdim=True)
return data / np.linalg.norm(data, axis=0, keepdims=True)
[docs]
def WrapPhase(data: ArrayLike) -> ArrayLike:
"""Normalize FHRR phases to the canonical range [-pi, pi)."""
if _is_torch(data):
return torch.remainder(data + pi, 2 * pi) - pi
return np.mod(data + pi, 2 * pi) - pi
[docs]
def SignNormalize(data: ArrayLike) -> ArrayLike:
"""Normalize MAP hypervectors back to bipolar {-1, 0, +1} by sign."""
if _is_torch(data):
return torch.sign(data)
return np.sign(data)