#!/usr/bin/env python
"""
Linear Cellular Automata for HDC
HDC-compatible wrapper for LCA random number generation.
"""
import random
from typing import Any, Dict, List, Optional
from pyhdc.generation.base import HDCGenerator
class LCAGenerator(HDCGenerator):
"""
Linear Cellular Automata for hypervector generation.
Uses cellular automata evolution to generate pseudorandom sequences.
"""
def __init__(
self,
width: int = 32,
rule: int = 30,
boundary: str = "periodic",
seed: Optional[int] = None,
extraction_method: str = "lsb",
) -> None:
"""
Initialize LCA generator.
Args:
width: The width of the cellular automaton in cells
rule: The rule number defining the local update function
boundary: Boundary condition ("periodic", "fixed", "null")
seed: Optional seed for reproducibility
extraction_method: Method to extract bits ("lsb", "msb", "center", "parity")
"""
self._width = width
self._rule = rule
self._boundary = boundary
self._extraction_method = extraction_method
self._validate_parameters()
super().__init__(seed)
def _validate_parameters(self) -> None:
"""Validate LCA parameters."""
if not isinstance(self._width, int) or self._width <= 0:
raise ValueError("Width must be a positive integer")
if not isinstance(self._rule, int) or self._rule < 0:
raise ValueError("Rule must be a non-negative integer")
if self._boundary not in ["periodic", "fixed", "null"]:
raise ValueError("Boundary must be one of: periodic, fixed, null")
if self._extraction_method not in ["lsb", "msb", "center", "parity"]:
raise ValueError(
"Extraction method must be one of: lsb, msb, center, parity"
)
def _configure_internal(self) -> None:
"""Configure the LCA state."""
self._max_value = (1 << self._width) - 1
self._rule_table = self._build_rule_table(self._rule)
if self._seed is None:
self._state = random.randint(1, self._max_value)
else:
if (
not isinstance(self._seed, int)
or self._seed < 0
or self._seed > self._max_value
):
raise ValueError(f"Seed must be between 0 and {self._max_value}")
self._state = self._seed
self._initial_state = self._state
def _build_rule_table(self, rule: int) -> List[int]:
"""Build lookup table for the cellular automaton rule."""
table = []
for i in range(8):
table.append((rule >> i) & 1)
return table
def _get_cell(self, position: int) -> int:
"""Get the value of a cell at given position with boundary handling."""
if 0 <= position < self._width:
return (self._state >> position) & 1
if self._boundary == "periodic":
return (self._state >> (position % self._width)) & 1
else: # fixed or null boundary
return 0
def _get_neighborhood(self, position: int) -> int:
"""Get the neighborhood value for a cell position."""
left = self._get_cell(position - 1)
center = self._get_cell(position)
right = self._get_cell(position + 1)
return (left << 2) | (center << 1) | right
def _next_generation(self) -> int:
"""Compute the next generation of the cellular automaton."""
new_state = 0
for i in range(self._width):
neighborhood = self._get_neighborhood(i)
new_bit = self._rule_table[neighborhood]
if new_bit:
new_state |= 1 << i
self._state = new_state
return self._state
def _extract_bit(self, state: int) -> int:
"""Extract a bit from state using the configured method."""
if self._extraction_method == "lsb":
return state & 1
elif self._extraction_method == "msb":
return (state >> (self._width - 1)) & 1
elif self._extraction_method == "center":
return (state >> (self._width // 2)) & 1
else: # parity
return bin(state).count("1") % 2
def _next_bit(self) -> int:
"""Generate next bit."""
state = self._next_generation()
return self._extract_bit(state)
def _next_word(self, word_size: int = 32) -> int:
"""Generate next word by evolving CA and extracting bits."""
word = 0
for i in range(word_size):
word |= self._next_bit() << i
return word
def set_parameters(
self,
width: Optional[int] = None,
rule: Optional[int] = None,
boundary: Optional[str] = None,
extraction_method: Optional[str] = None,
seed: Optional[int] = None,
) -> None:
"""
Set LCA parameters.
Args:
width: The width of the cellular automaton
rule: The rule number
boundary: Boundary condition
extraction_method: Bit extraction method
seed: The seed value
"""
if width is not None:
self._width = width
if rule is not None:
self._rule = rule
if boundary is not None:
self._boundary = boundary
if extraction_method is not None:
self._extraction_method = extraction_method
if seed is not None:
self._seed = seed
self._validate_parameters()
self._configure_internal()
def set_width(self, width: int) -> None:
"""Set the width parameter."""
self.set_parameters(width=width)
def set_rule(self, rule: int) -> None:
"""Set the rule number."""
self.set_parameters(rule=rule)
def set_boundary(self, boundary: str) -> None:
"""Set the boundary condition."""
self.set_parameters(boundary=boundary)
def set_extraction_method(self, extraction_method: str) -> None:
"""Set the bit extraction method."""
self.set_parameters(extraction_method=extraction_method)
def get_parameters(self) -> Dict[str, Any]:
"""Get current LCA parameters."""
return {
"width": self._width,
"rule": self._rule,
"boundary": self._boundary,
"extraction_method": self._extraction_method,
"seed": self._seed,
}
def get_width(self) -> int:
"""Get the width parameter."""
return self._width
def get_rule(self) -> int:
"""Get the rule number."""
return self._rule
def get_boundary(self) -> str:
"""Get the boundary condition."""
return self._boundary
def get_extraction_method(self) -> str:
"""Get the extraction method."""
return self._extraction_method
def reset(self) -> None:
"""Reset to initial state."""
self._state = self._initial_state
def get_state(self) -> int:
"""Get current state."""
return self._state
def get_state_as_array(self) -> List[int]:
"""Get current state as an array of cell values."""
return [(self._state >> i) & 1 for i in range(self._width)]
[docs]
class ElementaryLCAGenerator(LCAGenerator):
"""Elementary LCA generator using standard elementary CA rules."""
def __init__(
self,
width: int = 32,
rule: int = 30,
boundary: str = "periodic",
seed: Optional[int] = None,
) -> None:
"""Initialize elementary LCA."""
super().__init__(width, rule, boundary, seed, "lsb")
[docs]
class TotalisticLCAGenerator(LCAGenerator):
"""
Totalistic LCA generator.
Uses totalistic rules where next state depends only on the sum
of neighborhood values.
"""
def __init__(
self,
width: int = 32,
rule: int = 14,
boundary: str = "periodic",
seed: Optional[int] = None,
) -> None:
"""Initialize totalistic LCA."""
super().__init__(width, rule, boundary, seed, "lsb")
def _build_rule_table(self, rule: int) -> List[int]:
"""Build lookup table for totalistic CA rules."""
# For 3-cell totalistic neighborhood, sums can be 0, 1, 2, 3
table = []
for i in range(4):
table.append((rule >> i) & 1)
return table
def _get_neighborhood_sum(self, position: int) -> int:
"""Get the sum of the neighborhood for a cell position."""
left = self._get_cell(position - 1)
center = self._get_cell(position)
right = self._get_cell(position + 1)
return left + center + right
def _next_generation(self) -> int:
"""Compute next generation using totalistic rules."""
new_state = 0
for i in range(self._width):
neighborhood_sum = self._get_neighborhood_sum(i)
new_bit = self._rule_table[neighborhood_sum]
if new_bit:
new_state |= 1 << i
self._state = new_state
return self._state
# Predefined LCA configurations
[docs]
class CommonLCAGenerators:
"""Factory for common LCA configurations."""
@staticmethod
def rule_30(width: int = 64, seed: Optional[int] = None) -> ElementaryLCAGenerator:
"""Rule 30 - chaotic behavior, used in Mathematica's RandomInteger."""
return ElementaryLCAGenerator(
width=width, rule=30, boundary="periodic", seed=seed
)
@staticmethod
def rule_90(width: int = 64, seed: Optional[int] = None) -> ElementaryLCAGenerator:
"""Rule 90 - simple fractal patterns, XOR rule."""
return ElementaryLCAGenerator(
width=width, rule=90, boundary="periodic", seed=seed
)
@staticmethod
def rule_110(width: int = 64, seed: Optional[int] = None) -> ElementaryLCAGenerator:
"""Rule 110 - universal computation."""
return ElementaryLCAGenerator(
width=width, rule=110, boundary="periodic", seed=seed
)
@staticmethod
def rule_150(width: int = 64, seed: Optional[int] = None) -> ElementaryLCAGenerator:
"""Rule 150 - another XOR rule with good randomness."""
return ElementaryLCAGenerator(
width=width, rule=150, boundary="periodic", seed=seed
)
@staticmethod
def majority_totalistic(
width: int = 64, seed: Optional[int] = None
) -> TotalisticLCAGenerator:
"""Totalistic majority rule."""
return TotalisticLCAGenerator(
width=width,
rule=14, # Binary: 1110 - majority rule
boundary="periodic",
seed=seed,
)
# Legacy alias for backward compatibility
LCA = ElementaryLCAGenerator