Skip to main content

so_utils/
random.rs

1//! Random number generation utilities
2
3#![allow(non_snake_case)] // Allow mathematical notation (A, D, etc.)
4
5use ndarray::{Array1, Array2};
6use rand::rngs::StdRng;
7use rand::{Rng, SeedableRng};
8
9/// Generate random array from uniform distribution
10pub fn random_uniform_array(n: usize, low: f64, high: f64) -> Array1<f64> {
11    let mut rng = rand::rng();
12
13    Array1::from_iter((0..n).map(|_| rng.random_range(low..high)))
14}
15
16/// Generate random array from normal distribution
17pub fn random_normal_array(n: usize, mean: f64, std_dev: f64) -> Array1<f64> {
18    let mut rng = rand::rng();
19
20    // Generate standard normal using Box-Muller transform
21    Array1::from_iter((0..n).map(|_| {
22        let u1: f64 = rng.random::<f64>();
23        let u2: f64 = rng.random::<f64>();
24        let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
25        mean + std_dev * z
26    }))
27}
28
29/// Generate random 2D array from uniform distribution
30pub fn random_uniform_matrix(rows: usize, cols: usize, low: f64, high: f64) -> Array2<f64> {
31    let mut rng = rand::rng();
32
33    Array2::from_shape_fn((rows, cols), |_| rng.random_range(low..high))
34}
35
36/// Generate random 2D array from normal distribution
37pub fn random_normal_matrix(rows: usize, cols: usize, mean: f64, std_dev: f64) -> Array2<f64> {
38    let mut rng = rand::rng();
39
40    Array2::from_shape_fn((rows, cols), |_| {
41        let u1: f64 = rng.random::<f64>();
42        let u2: f64 = rng.random::<f64>();
43        let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
44        mean + std_dev * z
45    })
46}
47
48/// Generate random permutation of indices
49pub fn random_permutation(n: usize) -> Vec<usize> {
50    let mut rng = rand::rng();
51    let mut indices: Vec<usize> = (0..n).collect();
52
53    for i in 0..n {
54        let j = rng.random_range(i..n);
55        indices.swap(i, j);
56    }
57
58    indices
59}
60
61/// Randomly shuffle an array in place
62pub fn shuffle_array<T>(arr: &mut [T]) {
63    let mut rng = rand::rng();
64
65    for i in 0..arr.len() {
66        let j = rng.random_range(i..arr.len());
67        arr.swap(i, j);
68    }
69}
70
71/// Randomly shuffle rows of a 2D array
72pub fn shuffle_rows(matrix: &mut Array2<f64>) {
73    let mut rng = rand::rng();
74    let n_rows = matrix.nrows();
75
76    for i in 0..n_rows {
77        let j = rng.random_range(i..n_rows);
78        if i != j {
79            let row_i = matrix.row(i).to_owned();
80            let row_j = matrix.row(j).to_owned();
81            matrix.row_mut(i).assign(&row_j);
82            matrix.row_mut(j).assign(&row_i);
83        }
84    }
85}
86
87/// Randomly sample rows from a 2D array (with replacement)
88pub fn bootstrap_sample(data: &Array2<f64>, n_samples: usize) -> Array2<f64> {
89    let mut rng = rand::rng();
90    let n_rows = data.nrows();
91    let n_cols = data.ncols();
92
93    let mut sample = Array2::zeros((n_samples, n_cols));
94
95    for i in 0..n_samples {
96        let idx = rng.random_range(0..n_rows);
97        sample.row_mut(i).assign(&data.row(idx));
98    }
99
100    sample
101}
102
103/// Randomly split data into train and test sets
104pub fn train_test_split(
105    data: &Array2<f64>,
106    labels: &Array1<f64>,
107    test_size: f64,
108    shuffle: bool,
109) -> (Array2<f64>, Array2<f64>, Array1<f64>, Array1<f64>) {
110    let n_samples = data.nrows();
111    let n_cols = data.ncols();
112
113    let mut data_indices: Vec<usize> = (0..n_samples).collect();
114    let mut label_indices: Vec<usize> = (0..labels.len()).collect();
115
116    if shuffle {
117        shuffle_array(&mut data_indices);
118        shuffle_array(&mut label_indices);
119    }
120
121    let split_idx = ((n_samples as f64) * (1.0 - test_size)).round() as usize;
122
123    let train_data =
124        Array2::from_shape_fn((split_idx, n_cols), |(i, j)| data[(data_indices[i], j)]);
125
126    let test_data = Array2::from_shape_fn((n_samples - split_idx, n_cols), |(i, j)| {
127        data[(data_indices[split_idx + i], j)]
128    });
129
130    let train_labels = Array1::from_iter((0..split_idx).map(|i| labels[label_indices[i]]));
131    let test_labels = Array1::from_iter((split_idx..n_samples).map(|i| labels[label_indices[i]]));
132
133    (train_data, test_data, train_labels, test_labels)
134}
135
136/// Generate deterministic random numbers with a seed
137pub struct SeededRng {
138    rng: StdRng,
139}
140
141impl SeededRng {
142    /// Create a new seeded RNG
143    pub fn new(seed: u64) -> Self {
144        Self {
145            rng: StdRng::seed_from_u64(seed),
146        }
147    }
148
149    /// Generate random uniform array
150    pub fn uniform_array(&mut self, n: usize, low: f64, high: f64) -> Array1<f64> {
151        Array1::from_iter((0..n).map(|_| self.rng.random_range(low..high)))
152    }
153
154    /// Generate random normal array
155    pub fn normal_array(&mut self, n: usize, mean: f64, std_dev: f64) -> Array1<f64> {
156        Array1::from_iter((0..n).map(|_| {
157            let u1: f64 = self.rng.random::<f64>();
158            let u2: f64 = self.rng.random::<f64>();
159            let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
160            mean + std_dev * z
161        }))
162    }
163}
164
165/// Create a correlation matrix with specified eigenvalue structure
166pub fn random_correlation_matrix(n: usize, eigenvalues: &[f64]) -> Option<Array2<f64>> {
167    if eigenvalues.len() != n {
168        return None;
169    }
170
171    // Generate random orthogonal matrix
172    let mut rng = rand::rng();
173    let mut A = Array2::zeros((n, n));
174    for i in 0..n {
175        for j in 0..n {
176            A[(i, j)] = rng.random::<f64>() - 0.5;
177        }
178    }
179
180    // QR decomposition to get orthogonal matrix
181    // Note: This is a simplified implementation
182    // For production, use a proper QR decomposition
183
184    // Create diagonal matrix with eigenvalues
185    let mut D = Array2::zeros((n, n));
186    for i in 0..n {
187        D[(i, i)] = eigenvalues[i].sqrt();
188    }
189
190    // Compute correlation matrix: A D D^T A^T
191    let AD = A.dot(&D);
192    let correlation = AD.dot(&AD.t());
193
194    // Normalize to correlation matrix (diagonal = 1)
195    let mut result = Array2::zeros((n, n));
196    for i in 0..n {
197        for j in 0..n {
198            result[(i, j)] =
199                correlation[(i, j)] / (correlation[(i, i)] * correlation[(j, j)]).sqrt();
200        }
201    }
202
203    Some(result)
204}