rand_sequence/
builder.rs

1use num_traits::{AsPrimitive, PrimInt, WrappingAdd, WrappingSub};
2
3use crate::sequence::RandomSequence;
4
5/// The configuration for [RandomSequence], a random unique sequence generator.
6///
7/// These variables define the entire sequence and should not be modified with the exception of
8/// `seed_base` and `seed_offset` during initialisation.
9///
10/// The builder defines the internal properties of the sequence, and serialization includes all
11/// of the properties to preserve the sequence between crate versions which may change the fixed
12/// values between minor versions.
13///
14/// Crate versioning will bump:
15/// - _Minor version_: when the hard coded parameters are updated in favour of better ones. It is
16///    safe to serialize the [RandomSequenceBuilder] between minor versions.
17/// - _Major version_: when the sequence generation logic fundamentally changes the sequence,
18///    meaning it would be potentially unsafe to serialize the [RandomSequenceBuilder] between
19///    major crate version changes.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct RandomSequenceBuilder<T>
23where
24    T: QuadraticResidue
25{
26    /// The seed for the the start index.
27    pub seed_base: T,
28
29    /// The seed for the offsetting addition.
30    pub seed_offset: T,
31
32    /// A value used as an xor during initialisation for `start_index = f(seed_base, init_base)` to
33    /// deterministically pseudo-randomise it.
34    pub init_base: T,
35
36    /// A value used as an xor during initialisation for `offset = f(seed_offset, init_offset)` to
37    /// deterministically pseudo-randomise it.
38    pub init_offset: T,
39
40    /// Should be the largest prime number that fits in type `T` and satisfied `prime = 3 mod 4`.
41    pub prime: T,
42
43    /// A value that provides some noise from the xor to generate a pseudo-uniform distribution.
44    pub intermediate_xor: T,
45}
46
47impl<T> RandomSequenceBuilder<T>
48where
49    T: QuadraticResidue
50{
51    /// Initialise a config from stored settings. Not recommended unless you know what you're doing,
52    /// or these values have been taken from an already serialized RandomSequenceBuilder.
53    ///
54    /// Prefer [RandomSequenceBuilderInit::new] instead.
55    pub unsafe fn from_spec(
56        seed_base: T,
57        seed_offset: T,
58        init_base: T,
59        init_offset: T,
60        prime: T,
61        intermediate_xor: T,
62    ) -> Self {
63        Self {
64            seed_base,
65            seed_offset,
66            init_base,
67            init_offset,
68            prime,
69            intermediate_xor,
70        }
71    }
72
73    /// Intermediary function to compute the quadratic prime residue.
74    #[inline]
75    pub(crate) fn permute_qpr(&self, x: T) -> T {
76        // The small set of integers out of range are mapped to themselves.
77        if x >= self.prime {
78            return x;
79        }
80
81        // (x * x) % prime; but done safely to avoid integer overflow on x * x
82        // let xm = MontgomeryInt::new(x, &self.prime);
83        // let residue = (xm * xm).residue();
84
85        let residue = x.residue(self.prime);
86
87        // Op: `self.prime / 2` the bit shift is used to get around rust types
88        if x <= self.prime >> 1 {
89            residue
90        } else {
91            self.prime - residue
92        }
93    }
94}
95
96impl<T> IntoIterator for RandomSequenceBuilder<T>
97where
98    T: QuadraticResidue,
99    RandomSequence<T>: Iterator<Item = T>,
100{
101    type Item = T;
102    type IntoIter = RandomSequence<T>;
103
104    /// Build a [RandomSequence] iterator from this config.
105    fn into_iter(self) -> Self::IntoIter {
106        let start_index = self.permute_qpr(self.permute_qpr(self.seed_base).wrapping_add(&self.init_base));
107        let intermediate_offset = self.permute_qpr(self.permute_qpr(self.seed_offset).wrapping_add(&self.init_offset));
108
109        RandomSequence {
110            config: self,
111            start_index,
112            current_index: T::zero(),
113            intermediate_offset,
114            ended: false,
115        }
116    }
117}
118
119impl RandomSequenceBuilder<u8> {
120    /// Initialise a [RandomSequenceBuilder] from a specific seed pair.
121    pub fn new(seed_base: u8, seed_offset: u8) -> Self {
122        Self {
123            seed_base,
124            seed_offset,
125            init_base: 167,
126            init_offset: 181,
127            prime: 251,
128            intermediate_xor: 137,
129        }
130    }
131}
132
133impl RandomSequenceBuilder<u16> {
134    /// Initialise a [RandomSequenceBuilder] from a specific seed pair.
135    pub fn new(seed_base: u16, seed_offset: u16) -> Self {
136        Self {
137            seed_base,
138            seed_offset,
139            init_base: 0x682f,
140            init_offset: 0x4679,
141            prime: 65519,
142            intermediate_xor: 0x5bf0,
143        }
144    }
145}
146
147impl RandomSequenceBuilder<u32> {
148    /// Initialise a [RandomSequenceBuilder] from a specific seed pair.
149    pub fn new(seed_base: u32, seed_offset: u32) -> Self {
150        Self {
151            seed_base,
152            seed_offset,
153            init_base: 0x682f0161,
154            init_offset: 0x46790905,
155            prime: 4294967291,
156            intermediate_xor: 0x5bf03635,
157        }
158    }
159}
160
161impl RandomSequenceBuilder<u64> {
162    /// Initialise a [RandomSequenceBuilder] from a specific seed pair.
163    pub fn new(seed_base: u64, seed_offset: u64) -> Self {
164        Self {
165            seed_base,
166            seed_offset,
167            init_base: 0x682f01615bf03635,
168            init_offset: 0x46790905682f0161,
169            prime: 18446744073709551427,
170            intermediate_xor: 0x5bf0363546790905,
171        }
172    }
173}
174
175impl RandomSequenceBuilder<usize> {
176    /// Initialise a [RandomSequenceBuilder] from a specific seed pair.
177    pub fn new(seed_base: usize, seed_offset: usize) -> Self {
178        Self {
179            seed_base,
180            seed_offset,
181            init_base: 0x682f01615bf03635,
182            init_offset: 0x46790905682f0161,
183            prime: 18446744073709551427,
184            intermediate_xor: 0x5bf0363546790905,
185        }
186    }
187}
188
189pub trait QuadraticResidue
190where
191    Self: PrimInt + AsPrimitive<usize> + WrappingAdd + WrappingSub
192{
193    /// Compute the quadratic residue of this number against a prime.
194    fn residue(self, prime: Self) -> Self;
195}
196
197macro_rules! impl_residue {
198    ($base_type:ident, $larger_type:ident) => {
199        impl QuadraticResidue for $base_type {
200            fn residue(self, prime: Self) -> Self {
201                ((self as $larger_type * self as $larger_type) % prime as $larger_type) as Self
202            }
203        }
204    };
205}
206
207impl_residue!(u8, u16);
208impl_residue!(u16, u32);
209impl_residue!(u32, u64);
210impl_residue!(u64, u128);
211#[cfg(target_pointer_width = "64")]
212impl_residue!(usize, u128);
213#[cfg(target_pointer_width = "32")]
214impl_residue!(usize, u64);
215#[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))]
216compile_error!("Unsupported pointer width.");
217
218#[cfg(test)]
219mod tests {
220    use std::collections::hash_map::Entry;
221    use std::collections::HashMap;
222    use std::string::ToString;
223
224    use super::*;
225
226    /// Check the prime satisfies `p = 3 mod 4`.
227    fn is_3_mod_4(n: u64) -> bool {
228        n % 4 == 3
229    }
230
231    fn is_send<T: Send>() {}
232    fn is_sync<T: Sync>() {}
233
234    macro_rules! test_config {
235        ($name:ident, $type:ident, $check:literal) => {
236            #[test]
237            fn $name() {
238                let config = RandomSequenceBuilder::<$type>::new(0, 0);
239                let config_orig = config.clone();
240
241                // check the configured prime number satisfies the requirements
242                // is_prime crate makes this very quick for u64
243                let is_prime_res = is_prime::is_prime(&config.prime.to_string());
244                let is_3_mod_4_res = is_3_mod_4(config.prime as u64);
245
246                let mut found_number = config.prime;
247                if !is_prime_res || !is_3_mod_4_res {
248                    if found_number % 2 == 0 {
249                        found_number -= 1;
250                    }
251
252                    // suggest a suitable prime number (slow, but only run when there's a bad prime)
253                    let mut found_next_prime = false;
254                    let mut found_next_3_mod_4 = false;
255
256                    while !found_next_prime || !found_next_3_mod_4 {
257                        found_number -= 2;
258                        found_next_prime = is_prime::is_prime(&found_number.to_string());
259                        found_next_3_mod_4 = is_3_mod_4(found_number as u64);
260                    }
261                }
262
263                assert!(is_prime_res, "{} is not prime, suggested prime: {}", config.prime, found_number);
264                assert!(is_3_mod_4_res, "{} = 3 mod 4 doesn't hold, suggested prime: {}", config.prime, found_number);
265
266                // check config can be cloned and equality tested
267                let sequence = config.into_iter();
268                assert_eq!(sequence.config, config_orig);
269
270                // check permute_qpr for uniqueness
271                const CHECK: usize = $check;
272                let mut nums = HashMap::<$type, usize>::new();
273                for i in 0..CHECK {
274                    let num = config.permute_qpr(i as $type);
275                    match nums.entry(num) {
276                        Entry::Vacant(v) => {
277                            v.insert(i);
278                        }
279                        Entry::Occupied(o) => {
280                            panic!("Duplicate number {} at index {} and {}", num, o.get(), i);
281                        }
282                    }
283                }
284                assert_eq!(nums.len(), (0..CHECK).len());
285
286                // check builder is send and sync
287                is_send::<RandomSequenceBuilder<$type>>();
288                is_sync::<RandomSequenceBuilder<$type>>();
289            }
290        };
291    }
292
293    test_config!(test_u8_config, u8, 256);
294    test_config!(test_u16_config, u16, 65536);
295    test_config!(test_u32_config, u32, 100_000);
296    test_config!(test_u64_config, u64, 100_000);
297}