File size: 1,379 Bytes
2ca80ad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from __future__ import annotations

import hashlib
import math


def _u64(seed: bytes, counter: int) -> int:
    h = hashlib.blake2b(digest_size=8)
    h.update(seed)
    h.update(counter.to_bytes(8, "little", signed=False))
    return int.from_bytes(h.digest(), "little", signed=False)


def uniform01(seed: bytes, counter: int) -> float:
    """
    Deterministic uniform float in (0, 1), derived from (seed, counter).
    """
    # Map 64-bit integer to (0, 1) with clamping away from endpoints
    x = _u64(seed, counter)
    u = (x + 1.0) / (2**64 + 1.0)
    # Defensive clamp (should already be in (0, 1))
    if u <= 0.0:
        return 1.0 / (2**64 + 1.0)
    if u >= 1.0:
        return 1.0 - (1.0 / (2**64 + 1.0))
    return float(u)


def uniform(seed: bytes, counter: int, low: float, high: float) -> float:
    return low + (high - low) * uniform01(seed, counter)


def normal(seed: bytes, counter: int, mean: float = 0.0, std: float = 1.0) -> float:
    """
    Deterministic normal via Box-Muller transform.

    Uses counters (counter, counter+1) internally.
    """
    u1 = uniform01(seed, counter)
    u2 = uniform01(seed, counter + 1)
    r = math.sqrt(-2.0 * math.log(u1))
    theta = 2.0 * math.pi * u2
    z0 = r * math.cos(theta)
    return mean + std * z0


def uint31(seed: bytes, counter: int) -> int:
    return int(_u64(seed, counter) & 0x7FFF_FFFF)