secs_dsp/random.rs
1//! NumPy 2.5.3 legacy `RandomState` draws used by SciPy differential evolution.
2//!
3//! Port of numpy/random/{mtrand.pyx,_mt19937.pyx,src/mt19937/mt19937.{c,h},
4//! src/distributions/distributions.c} at <https://github.com/numpy/numpy/tree/v2.5.3>.
5//! NumPy Developers, BSD-3-Clause; MT19937 carries the notices below.
6//! This is integer-seeded RandomState, not NumPy's SeedSequence-based MT19937.
7//! It is reproducible, not cryptographically secure.
8/*
9Copyright (c) 2003-2005, Jean-Sebastien Roy (js@jeannot.org)
10
11The rk_random and rk_seed functions algorithms and the original design of
12 the Mersenne Twister RNG:
13Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
14All rights reserved.
15
16Redistribution and use in source and binary forms, with or without
17modification, are permitted provided that the following conditions are met:
181. Redistributions of source code must retain the above copyright notice,
19 this list of conditions and the following disclaimer.
202. Redistributions in binary form must reproduce the above copyright notice,
21 this list of conditions and the following disclaimer in the documentation
22 and/or other materials provided with the distribution.
233. The names of its contributors may not be used to endorse or promote products
24 derived from this software without specific prior written permission.
25
26THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
28WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
31BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
33LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
34OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
35OF THE POSSIBILITY OF SUCH DAMAGE.
36
37Original algorithm for the implementation of rk_interval function from Richard
38J. Wagner's implementation of the Mersenne Twister RNG, optimised by Magnus Jonsson.
39Constants used in the rk_double implementation by Isaku Wada.
40
41Permission is hereby granted, free of charge, to any person obtaining a copy of
42this software and associated documentation files (the "Software"), to deal in
43 the Software without restriction, including without limitation the rights to
44use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
45 the Software, and to permit persons to whom the Software is furnished to do so,
46subject to the following conditions:
47The above copyright notice and this permission notice shall be included in all
48copies or substantial portions of the Software.
49THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
50INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
51PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
52HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
53OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
54SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
55*/
56
57use crate::{DspError, invalid};
58
59/// Legacy MT19937 with NumPy's integer-seed initialization and draw order.
60#[derive(Debug, Clone)]
61pub struct RandomState {
62 key: [u32; 624],
63 pos: usize,
64}
65
66impl RandomState {
67 /// `RandomState(seed)` for an integer in `0..=2**32-1`.
68 pub fn new(seed: u32) -> Self {
69 let mut key = [0; 624];
70 key[0] = seed;
71 for i in 1..624 {
72 key[i] = 1_812_433_253_u32
73 .wrapping_mul(key[i - 1] ^ (key[i - 1] >> 30))
74 .wrapping_add(i as u32);
75 }
76 Self { key, pos: 624 }
77 }
78
79 /// One tempered 32-bit MT19937 output (also the full-width uint32 randint).
80 pub fn next_u32(&mut self) -> u32 {
81 if self.pos == 624 {
82 // In-place twist: the second part uses already-updated state words.
83 for i in 0..624 {
84 let y = (self.key[i] & 0x8000_0000) | (self.key[(i + 1) % 624] & 0x7fff_ffff);
85 self.key[i] = self.key[(i + 397) % 624]
86 ^ (y >> 1)
87 ^ (0_u32.wrapping_sub(y & 1) & 0x9908_b0df);
88 }
89 self.pos = 0;
90 }
91 let mut y = self.key[self.pos];
92 self.pos += 1;
93 y ^= y >> 11;
94 y ^= (y << 7) & 0x9d2c_5680;
95 y ^= (y << 15) & 0xefc6_0000;
96 y ^= y >> 18;
97 y
98 }
99
100 /// A 53-bit double in `[0, 1)`, consuming two 32-bit outputs.
101 pub fn random_sample(&mut self) -> f64 {
102 let a = self.next_u32() >> 5;
103 let b = self.next_u32() >> 6;
104 (f64::from(a) * 67_108_864.0 + f64::from(b)) * (1.0 / 9_007_199_254_740_992.0)
105 }
106
107 /// Scalar NumPy `uniform(low, high)`, including a draw when `low == high`.
108 /// Bounds must be finite and ordered, with a finite difference.
109 pub fn uniform(&mut self, low: f64, high: f64) -> Result<f64, DspError> {
110 let range = high - low;
111 if !low.is_finite() || !high.is_finite() || !range.is_finite() || range < 0.0 {
112 return Err(invalid("uniform bounds must be finite and ordered"));
113 }
114 Ok(low + range * self.random_sample())
115 }
116
117 /// NumPy's legacy `randint(high)` for the nonnegative index domain used by DE.
118 /// Unlike modern Generator integers this uses masked rejection, not Lemire.
119 /// `high == 1` returns zero without advancing the generator.
120 pub fn randint(&mut self, high: usize) -> Result<usize, DspError> {
121 if high == 0 {
122 return Err(invalid("high <= 0"));
123 }
124 Ok(self.interval(high - 1))
125 }
126
127 fn interval(&mut self, max: usize) -> usize {
128 if max == 0 {
129 return 0;
130 }
131 let mask = usize::MAX >> max.leading_zeros();
132 loop {
133 let value = if max <= u32::MAX as usize {
134 self.next_u32() as usize
135 } else {
136 // mt19937_next64: high word first, low word second.
137 let high = u64::from(self.next_u32());
138 ((high << 32) | u64::from(self.next_u32())) as usize
139 } & mask;
140 if value <= max {
141 return value;
142 }
143 }
144 }
145
146 /// In-place legacy shuffle of a one-dimensional sequence, last index first.
147 pub fn shuffle<T>(&mut self, values: &mut [T]) {
148 for i in (1..values.len()).rev() {
149 let j = self.interval(i);
150 values.swap(i, j);
151 }
152 }
153
154 /// NumPy `permutation(range(n))` (the same stream as `permutation(n)`).
155 pub fn permutation(&mut self, n: usize) -> Vec<usize> {
156 let mut values: Vec<_> = (0..n).collect();
157 self.shuffle(&mut values);
158 values
159 }
160}