Source code for pyhdc.encodings.map

#!/usr/bin/env python
"""
Multiply-Add-Permute Encodings for HDC

HDC-compatible wrapper for MAP encodings.
"""

from functools import partial
from typing import Any, Callable, Optional

import numpy as np

from pyhdc.components.binding import ElementMultiplication
from pyhdc.components.bundling import (
    ElementAddition,
    ElementAdditionBipolarThreshold,
    ElementAdditionBits,
    ElementAdditionCut,
)
from pyhdc.components.elements import BernoulliBipolar, UniformBipolar
from pyhdc.components.similarity import CosineSimilarity
from pyhdc.components.thinning import NoThin
from pyhdc.components.unary import IdentityInverse, Negate, SignNormalize
from pyhdc.encodings.base import Encoding
from pyhdc.generation.base import HDCGenerator
from pyhdc.hypervector import EncodingSpec
from pyhdc.types import Backend, Device

# ============================================================================
# Multiply-Add-Permute Encodings
# ============================================================================


[docs] class MAP_C(Encoding): r""" Multiply-Add-Permute encoding with continuous values. Uses bipolar values with element-wise multiplication for binding and cosine similarity for comparison. Args: random_choice_range: Optional float (rho). When set, coordinates whose \|pre-aggregate sum\| <= rho * sqrt(N/3) are replaced by independent Uniform[-1,1] draws during bundling (band randomization). """ def __init__( self, dimension: int = 10_000, backend: Optional[Backend] = None, device: Optional[Device] = None, dtype: Optional[Any] = None, mask: Optional[int] = None, generator: Optional[HDCGenerator] = None, similarity_remap: Optional[Callable] = None, random_choice_range: Optional[float] = None, ) -> None: self._random_choice_range = random_choice_range super().__init__( dimension, backend, device, dtype, mask, generator, similarity_remap ) def _get_encoding_spec(self) -> EncodingSpec: if self._random_choice_range is not None: bundling_fn = partial( ElementAdditionCut, random_choice_range=self._random_choice_range ) else: bundling_fn = ElementAdditionCut return EncodingSpec( dtype=np.float32, element_generator=UniformBipolar, similarity_fn=CosineSimilarity, bundling_fn=bundling_fn, thinning_fn=NoThin, binding_fn=ElementMultiplication, unbinding_fn=ElementMultiplication, generator_output_type="floats", normalize_fn=SignNormalize, negative_fn=Negate, )
[docs] class MAP_I(Encoding): r""" Multiply-Add-Permute encoding with integer values. Uses bipolar integer values with element-wise multiplication for binding and cosine similarity for comparison. Args: random_choice_range: Optional float (rho). When set, coordinates whose \|pre-aggregate sum\| <= rho * sqrt(N) are replaced by independent {-1, +1} draws during bundling (band randomization). """ def __init__( self, dimension: int = 10_000, backend: Optional[Backend] = None, device: Optional[Device] = None, dtype: Optional[Any] = None, mask: Optional[int] = None, generator: Optional[HDCGenerator] = None, similarity_remap: Optional[Callable] = None, random_choice_range: Optional[float] = None, ) -> None: self._random_choice_range = random_choice_range super().__init__( dimension, backend, device, dtype, mask, generator, similarity_remap ) def _get_encoding_spec(self) -> EncodingSpec: if self._random_choice_range is not None: bundling_fn = partial( ElementAddition, random_choice_range=self._random_choice_range ) else: bundling_fn = ElementAddition return EncodingSpec( dtype=np.int32, element_generator=BernoulliBipolar, similarity_fn=CosineSimilarity, bundling_fn=bundling_fn, thinning_fn=NoThin, binding_fn=ElementMultiplication, unbinding_fn=ElementMultiplication, generator_output_type="bits", inverse_fn=IdentityInverse, normalize_fn=SignNormalize, negative_fn=Negate, )
[docs] class MAP_I_Bits(Encoding): """ Multiply-Add-Permute encoding with bit-limited integer values. Similar to MAP_I but with configurable bit limits via mask parameter. """ def __init__( self, dimension: int = 10_000, backend: Optional[Backend] = None, device: Optional[Device] = None, dtype: Optional[Any] = None, mask: int = (2**32) - 1, generator: Optional[HDCGenerator] = None, similarity_remap: Optional[Callable] = None, ) -> None: self._mask = mask super().__init__( dimension, backend, device, dtype, mask, generator, similarity_remap ) def _get_encoding_spec(self) -> EncodingSpec: bundling_fn = partial( ElementAdditionBits, min_val=np.iinfo(np.int32).min, max_val=np.iinfo(np.int32).max, ) return EncodingSpec( dtype=np.int32, element_generator=BernoulliBipolar, similarity_fn=CosineSimilarity, bundling_fn=bundling_fn, thinning_fn=NoThin, binding_fn=ElementMultiplication, unbinding_fn=ElementMultiplication, mask=self._mask, generator_output_type="words", inverse_fn=IdentityInverse, normalize_fn=SignNormalize, negative_fn=Negate, )
[docs] class MAP_B(Encoding): r""" Multiply-Add-Permute with bipolar thresholding. Uses bipolar values with thresholding during bundling. Args: random_choice_range: Optional float (rho). When set, coordinates whose \|bipolar sum\| <= rho * sqrt(N) are replaced by independent {-1, +1} draws during bundling (band randomization). """ def __init__( self, dimension: int = 10_000, backend: Optional[Backend] = None, device: Optional[Device] = None, dtype: Optional[Any] = None, mask: Optional[int] = None, generator: Optional[HDCGenerator] = None, similarity_remap: Optional[Callable] = None, random_choice_range: Optional[float] = None, ) -> None: self._random_choice_range = random_choice_range super().__init__( dimension, backend, device, dtype, mask, generator, similarity_remap ) def _get_encoding_spec(self) -> EncodingSpec: if self._random_choice_range is not None: bundling_fn = partial( ElementAdditionBipolarThreshold, random_choice_range=self._random_choice_range, ) else: bundling_fn = ElementAdditionBipolarThreshold return EncodingSpec( dtype=np.int8, element_generator=BernoulliBipolar, similarity_fn=CosineSimilarity, bundling_fn=bundling_fn, thinning_fn=NoThin, binding_fn=ElementMultiplication, unbinding_fn=ElementMultiplication, generator_output_type="bits", inverse_fn=IdentityInverse, normalize_fn=SignNormalize, negative_fn=Negate, )