import warnings
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional
import numpy as np
[docs]
class HDCGenerator(ABC):
"""
Abstract base class for HDC-compatible random number generators.
Generators can produce bits, words, or floats, and are used to generate
hypervectors with specific properties (e.g., using LFSRs, LCGs, etc.).
"""
def __init__(self, seed: Optional[int] = None) -> None:
"""
Initialize the generator.
Args:
seed: Optional seed for reproducibility
"""
self._seed = seed
self._state: Any = None
self._configure_internal()
@abstractmethod
def _configure_internal(self) -> None:
"""
Internal configuration called during initialization.
Should set up the generator state based on parameters.
"""
@abstractmethod
def _next_bit(self) -> int:
"""
Generate the next bit (0 or 1).
Returns:
The next bit (0 or 1)
Raises:
NotImplementedError: If the generator doesn't support bit generation
"""
@abstractmethod
def _next_word(self, word_size: int = 32) -> int:
"""
Generate the next word of specified bit width.
Args:
word_size: Number of bits in the word
Returns:
The next word as an integer
Raises:
NotImplementedError: If the generator doesn't support word generation
"""
[docs]
def generate_bits(self, length: int) -> List[int]:
"""
Generate a sequence of bits.
Args:
length: Number of bits to generate
Returns:
List of bits (0s and 1s)
Raises:
NotImplementedError: If the generator doesn't support bit generation
ValueError: If length is not positive
"""
if not isinstance(length, int) or length <= 0:
raise ValueError("Length must be a positive integer.")
try:
return [self._next_bit() for _ in range(length)]
except NotImplementedError:
raise NotImplementedError(
f"{self.__class__.__name__} does not support bit generation. "
f"Use a generator that implements _next_bit()."
)
[docs]
def generate_words(self, length: int, word_size: int = 32) -> List[int]:
"""
Generate a sequence of words.
Args:
length: Number of words to generate
word_size: Size of each word in bits
Returns:
List of words (integers)
Raises:
NotImplementedError: If the generator doesn't support word generation
ValueError: If length is not positive
"""
if not isinstance(length, int) or length <= 0:
raise ValueError("Length must be a positive integer.")
try:
return [self._next_word(word_size) for _ in range(length)]
except NotImplementedError:
raise NotImplementedError(
f"{self.__class__.__name__} does not support word generation. "
f"Use a generator that implements _next_word()."
)
[docs]
def generate_floats(
self, length: int, min_val: float = -1.0, max_val: float = 1.0
) -> List[float]:
"""
Generate a sequence of floating-point values.
Args:
length: Number of floats to generate
min_val: Minimum value (inclusive)
max_val: Maximum value (inclusive)
Returns:
List of floats in [min_val, max_val]
Raises:
NotImplementedError: If the generator doesn't support float generation
ValueError: If length is not positive
"""
if not isinstance(length, int) or length <= 0:
raise ValueError("Length must be a positive integer.")
# Default implementation uses words to generate floats
try:
word_size = 32
words = self.generate_words(length, word_size)
max_word = (1 << word_size) - 1
# Normalize to [0, 1] then scale to [min_val, max_val]
return [min_val + (w / max_word) * (max_val - min_val) for w in words]
except NotImplementedError:
raise NotImplementedError(
f"{self.__class__.__name__} does not support float generation. "
f"Implement either _next_word() or override generate_floats()."
)
[docs]
@abstractmethod
def set_parameters(self, **kwargs) -> None:
"""
Set generator parameters.
Args:
**kwargs: Generator-specific parameters
Raises:
ValueError: If parameters are invalid
"""
[docs]
@abstractmethod
def get_parameters(self) -> Dict[str, Any]:
"""
Get current generator parameters.
Returns:
Dictionary of parameter names and values
"""
[docs]
def set_seed(self, seed: int) -> None:
"""
Set the generator seed.
Args:
seed: The seed value
"""
self._seed = seed
self._configure_internal()
self.reset()
[docs]
def get_seed(self) -> Optional[int]:
"""Get the current seed."""
return self._seed
[docs]
@abstractmethod
def reset(self) -> None:
"""Reset the generator to its initial state."""
[docs]
@abstractmethod
def get_state(self) -> Any:
"""
Get the current generator state.
Returns:
The current state (generator-specific format)
"""
[docs]
def supports_bits(self) -> bool:
"""Check if generator supports bit generation."""
try:
# Try to generate a single bit
self._next_bit()
return True
except NotImplementedError:
return False
finally:
self.reset()
[docs]
def supports_words(self) -> bool:
"""Check if generator supports word generation."""
try:
# Try to generate a single word
self._next_word()
return True
except NotImplementedError:
return False
finally:
self.reset()
[docs]
def supports_floats(self) -> bool:
"""Check if generator supports float generation."""
try:
# Try to generate a single float
self.generate_floats(1)
return True
except NotImplementedError:
return False
finally:
self.reset()
[docs]
class DefaultGenerator(HDCGenerator):
"""
Default generator using NumPy's random number generator.
Supports all output types.
"""
def __init__(self, seed: Optional[int] = None) -> None:
"""Initialize with optional seed."""
super().__init__(seed)
def _configure_internal(self) -> None:
"""Configure NumPy RNG."""
if self._seed is not None:
self._rng = np.random.RandomState(self._seed)
else:
self._rng = np.random.RandomState()
self._state = self._rng.get_state()
def _next_bit(self) -> int:
"""Generate next bit."""
return int(self._rng.randint(0, 2))
def _next_word(self, word_size: int = 32) -> int:
"""Generate next word."""
if word_size < 64:
# 1 << word_size fits in int64 for word_size up to 63
return int(self._rng.randint(0, 1 << word_size, dtype=np.int64))
else:
# 64-bit: combine two 32-bit halves to avoid int64 overflow
lo = int(self._rng.randint(0, 1 << 32, dtype=np.int64))
hi = int(self._rng.randint(0, 1 << 32, dtype=np.int64))
return (hi << 32) | lo
[docs]
def generate_floats(
self, length: int, min_val: float = -1.0, max_val: float = 1.0
) -> List[float]:
"""Generate floats efficiently using NumPy."""
return list(self._rng.uniform(min_val, max_val, length))
[docs]
def set_parameters(self, **kwargs) -> None:
"""Default generator has no additional parameters."""
if kwargs:
warnings.warn(f"DefaultGenerator ignores parameters: {list(kwargs.keys())}")
[docs]
def get_parameters(self) -> Dict[str, Any]:
"""Get parameters."""
return {"seed": self._seed}
[docs]
def reset(self) -> None:
"""Reset to initial seed state."""
self._configure_internal()
[docs]
def get_state(self) -> Any:
"""Get NumPy RNG state."""
return self._rng.get_state()