Source code for andrey.core.seeding

"""Unified seeding for reproducible stochastic runs, across every backend.

Some algorithms draw randomness from more than one place -- stdlib ``random`` (the permutation
searches BOSS / GRaSP), numpy's global RNG, and torch (CALM's optimizer). :func:`seed_all` seeds all
of them from one number, torch-style: one call, everything reproducible on a fixed machine. The
effective seed comes from :func:`resolve_seed` -- an explicit ``seed`` wins, else the global
``ANDREY_SEED`` (default ``0``) -- so ``ANDREY_SEED=42`` makes every stochastic adapter reproducible
without threading ``seed=`` through each call. See ``docs/docs/configuration.md``.

torch is touched only through :mod:`andrey.core.backend` (behind the ``[torch]`` extra), so
importing this module -- and Andrey -- stays torch-free; torch is seeded only when installed.
"""

from __future__ import annotations

import random

import numpy as np

from . import backend, env

_DEFAULT_SEED = 0


def resolve_seed(seed: int | None = None) -> int:
    """The effective seed: an explicit ``seed`` if given, else ``ANDREY_SEED`` (default ``0``).

    Adapters whose engine takes its own ``random_state`` call this to pick the number to pass down;
    adapters that rely on a global RNG call :func:`seed_all` (which resolves through here).
    """
    if seed is not None:
        return int(seed)
    env_seed = env.SEED.read()
    return _DEFAULT_SEED if env_seed is None else env_seed


[docs] def seed_all(seed: int | None = None) -> int: """Seed Python's ``random``, NumPy's global generator, and torch from one number. Use it to make random draws outside Andrey repeatable, like data simulated with ``np.random``. Andrey's methods with a ``seed`` or ``random_state`` argument seed their own generator from it (else ``ANDREY_SEED``, else ``0``), so ``seed_all`` does not change their results. torch is seeded only when installed, on the CPU and on every CUDA device. Parameters ---------- seed : int or None, default=None The seed. ``None`` reads ``ANDREY_SEED``, and uses ``0`` when it is unset. Any integer is accepted; NumPy receives its lowest 32 bits. Returns ------- int The seed used. Raises ------ ValueError If ``seed`` is ``None`` and ``ANDREY_SEED`` is not an integer from ``0`` to ``2**32 - 1``. """ s = resolve_seed(seed) random.seed(s) np.random.seed(s & 0xFFFFFFFF) torch = backend.torch() if torch is not None: torch.manual_seed(s) if torch.cuda.is_available(): torch.cuda.manual_seed_all(s) return s