Source code for pymixef.random
"""Deterministic counter-based random streams for parallel scientific workflows."""
from __future__ import annotations
import hashlib
from collections.abc import Iterable
from dataclasses import dataclass
import numpy as np
def _label_words(label: str) -> tuple[int, ...]:
digest = hashlib.blake2b(label.encode("utf-8"), digest_size=16).digest()
return tuple(
int.from_bytes(digest[position : position + 4], "little")
for position in range(0, len(digest), 4)
)
[docs]
@dataclass(frozen=True, slots=True)
class RandomStreamManager:
"""Create order-independent NumPy Philox streams from a recorded root seed."""
seed: int
namespace: str = "pymixef"
[docs]
def generator(
self,
component: str,
*,
replicate: int = 0,
chain: int = 0,
) -> np.random.Generator:
if replicate < 0 or chain < 0:
raise ValueError("replicate and chain identifiers must be nonnegative.")
spawn_key = (
*_label_words(self.namespace),
*_label_words(component),
int(replicate),
int(chain),
)
sequence = np.random.SeedSequence(int(self.seed), spawn_key=spawn_key)
return np.random.Generator(np.random.Philox(sequence))
[docs]
def replicates(self, component: str, count: int) -> Iterable[np.random.Generator]:
if count < 0:
raise ValueError("count must be nonnegative.")
for replicate in range(count):
yield self.generator(component, replicate=replicate)
[docs]
def to_dict(self) -> dict[str, object]:
return {
"algorithm": "numpy.Philox",
"seed": int(self.seed),
"namespace": self.namespace,
"stream_derivation": "SeedSequence spawn key from BLAKE2b labels",
}
[docs]
def random_streams(seed: int, *, namespace: str = "pymixef") -> RandomStreamManager:
"""Construct a named counter-based stream manager."""
return RandomStreamManager(int(seed), namespace)