Skip to main content

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}