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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//! Crate-wide control of pseudo-random number generation for reproducibility
//!
//! Most randomized components in the crate draw their RNG through `make_rng` (or its sibling
//! `make_rng_opt`, for callers that stay deterministic unless explicitly seeded), so a single
//! [`set_global_seed`] call can make them reproducible together. This routes the
//! neural-network components (weight initialization, dropout/noise masks, and the
//! [`Sequential`](crate::neural_network::sequential::Sequential) minibatch shuffle), the
//! machine-learning estimators (k-means, SVC/LinearSVC, MeanShift, Isolation Forest, etc.), and the
//! utilities (`train_test_split`, t-SNE) all through this one entry point
//!
//! # Seed resolution
//!
//! `make_rng` resolves a per-consumer `random_state: Option<u64>` against the process-global
//! (thread-local) seed as follows:
//!
//! - `Some(seed)` - use that seed; the global is **ignored and left untouched**
//! - `None` + a global seed is set - derive an independent sub-seed from the global stream
//! - `None` + no global seed - seed from entropy (non-reproducible)
//!
//! Because an explicit `Some` seed never consumes the global stream, adding or removing an
//! explicitly-seeded component does not perturb the seeds handed to the unseeded ones. Unseeded
//! components, by contrast, draw from the shared stream in construction order, so their
//! reproducibility is order-sensitive (this matches Keras' global-seed behavior)
//!
//! # Threading
//!
//! The global seed is **thread-local**: [`set_global_seed`] only affects the thread that calls
//! it, so set the seed on the same thread that constructs your models. This is lock-free, and
//! because the default test harness spawns a fresh thread per test, each test starts unseeded
//! Under `--test-threads=1`, however, all tests share one thread, so a test that sets a global
//! seed should [`clear_global_seed`] afterwards (ideally panic-safely, e.g. via a drop guard)
//! to avoid leaking it into a later test that relies on unseeded behavior
//!
//! # Intentional exclusions
//!
//! Not every pseudo-random draw in the crate is routed here. A draw is worth seeding through this
//! module only when it has a real, persistent effect on the result. The `utils` dimensionality
//! reducers (`pca`, `kernel_pca`) are deliberately left out, for two reasons:
//!
//! - Their **iterative eigensolvers** (power iteration and Lanczos: `pca`'s `PowerIteration` solver
//! and `kernel_pca`'s `Lanczos` / `PowerIteration` solvers) seed a random *starting vector* with a
//! fixed constant. Such methods converge to the same eigenvectors regardless of the (generic)
//! starting vector, so the seed is observationally inert: it only pins otherwise-arbitrary
//! eigenvector signs, and exists purely to make the solver deterministic. Routing it through the
//! global seed would make that sign choice depend on ambient global state (*less* reproducible)
//! for no observable benefit
//! - **Randomized SVD** (`pca`'s `SVDSolver::Randomized(u64)`) takes its seed as part of the public
//! solver variant, so the caller always supplies it explicitly: there is no unseeded (`None`) path
//! for the global to fill, and the result is already reproducible by construction
//!
//! The general rule: route a draw through this module only when it makes a pseudo-random choice that
//! changes the result; a draw whose result converges (or whose seed the user already pins) stays out
use ;
use RefCell;
thread_local!
/// Sets the thread-local global seed
///
/// After this call, every component constructed **on this thread** with `random_state == None`
/// becomes reproducible (it derives its RNG from the global stream). Call this before
/// constructing the models/estimators whose randomness you want to fix
///
/// # Parameters
///
/// - `seed` - The seed for the thread-local global RNG stream
/// Clears the thread-local global seed, restoring entropy-based behavior for unseeded components
///
/// Useful to isolate tests that may share a thread (e.g. under `--test-threads=1`)
/// Resolves a `random_state` into an RNG **only when a seed is in effect**, returning `None`
/// when there is none (`random_state` is `None` AND no global seed is set)
///
/// This is for callers that should stay deterministic unless randomness is explicitly requested,
/// e.g. a decision tree that breaks split ties randomly only when seeded. `Some(seed)` uses that
/// seed (ignoring the global); `None` derives a sub-seed from the thread-local global if one is set,
/// otherwise returns `None` (the signal: "no randomization requested")
///
/// # Parameters
///
/// - `random_state` - The per-consumer seed, or `None` to defer to the global
///
/// # Returns
///
/// - `Option<StdRng>` - A seeded RNG if a local or global seed is active, else `None`
pub
/// Resolves a `random_state` into a concrete RNG (see the [module docs](self) for the rules)
///
/// This is the single entry point for all randomness in the crate: `Some` uses the given seed,
/// `None` consults the thread-local global (deriving a sub-seed from it if set, else entropy)
///
/// # Parameters
///
/// - `random_state` - The per-consumer seed, or `None` to defer to the global/entropy
///
/// # Returns
///
/// - `StdRng` - A freshly seeded RNG for the caller to own and advance
pub