Jerlshin's picture
refined progress and defect fix
761d149
Raw
History Blame Contribute Delete
3.4 kB
from __future__ import annotations
import hashlib
from datetime import date
from typing import TYPE_CHECKING, Final
import numpy as np
from redstack.ports.rng import EntropyDisabledError
#: Width (bytes) of the sha256 prefix folded into a numpy seed (64-bit).
_SUBSEED_BYTES: Final[int] = 8
def _derive_subseed(seed: int, label: str) -> int:
"""Deterministically fold ``(seed, label)`` into a stable 64-bit sub-seed."""
digest = hashlib.sha256(f"{seed}:{label}".encode("utf-8")).digest()
return int.from_bytes(digest[:_SUBSEED_BYTES], "big")
class OfflineEntropy:
"""Seeded, labeled, reproducible entropy for the offline pipeline."""
__slots__ = ("_seed", "_as_of")
def __init__(self, seed: int, as_of: date) -> None:
"""Bind the run seed and the fixed reference date.
Args:
seed: The run seed from config.
as_of: The fixed reference date from config (the only clock).
"""
self._seed: Final[int] = seed
self._as_of: Final[date] = as_of
@property
def seed(self) -> int:
"""The run seed."""
return self._seed
def as_of(self) -> date:
"""The fixed reference date from config."""
return self._as_of
def derive(self, label: str) -> int:
"""Return a stable sub-seed deterministically derived from ``(seed, label)``."""
return _derive_subseed(self._seed, label)
def numpy_generator(self, label: str) -> np.random.Generator:
"""Return a seeded PCG64 ``Generator`` for the named substream."""
return np.random.default_rng(self.derive(label))
class OnlineEntropy:
"""RNG-disabled entropy for the online pipeline: ``as_of`` only.
Any attempt to draw randomness raises :class:`EntropyDisabledError`,
enforcing the online RNG-free guarantee (ties break by ``candidate_id``).
"""
__slots__ = ("_seed", "_as_of")
def __init__(self, seed: int, as_of: date) -> None:
"""Bind the recorded seed (provenance only) and the fixed reference date."""
self._seed: Final[int] = seed
self._as_of: Final[date] = as_of
@property
def seed(self) -> int:
"""The recorded run seed (audit/provenance; never used to draw randomness)."""
return self._seed
def as_of(self) -> date:
"""The fixed reference date from config."""
return self._as_of
def derive(self, label: str) -> int:
"""Always raise: online randomness is forbidden.
Raises:
EntropyDisabledError: online RNG is disabled.
"""
raise EntropyDisabledError(
f"online RNG is disabled; derive({label!r}) is not permitted"
)
def numpy_generator(self, label: str) -> np.random.Generator:
"""Always raise: online randomness is forbidden.
Raises:
EntropyDisabledError: online RNG is disabled.
"""
raise EntropyDisabledError(
f"online RNG is disabled; numpy_generator({label!r}) is not permitted"
)
if TYPE_CHECKING:
from redstack.ports.rng import DeterministicEntropyPort
# Compile-time structural conformance to the frozen port surface.
_OFFLINE_CONFORMANCE: type[DeterministicEntropyPort] = OfflineEntropy
_ONLINE_CONFORMANCE: type[DeterministicEntropyPort] = OnlineEntropy
__all__: tuple[str, ...] = ("OfflineEntropy", "OnlineEntropy")