1use burn::tensor::{backend::Backend, Int, Tensor, TensorData};
13use rand::{Rng, RngExt};
14use rand_distr::{Distribution as _, Normal};
15
16fn standard_normal_rows(n: usize, d: usize, rng: &mut dyn Rng) -> Vec<f32> {
19 let normal = Normal::new(0.0f32, 1.0).expect("unit normal is well-defined");
20 let mut rows = Vec::with_capacity(n * d);
21 for _ in 0..n * d {
22 rows.push(normal.sample(rng));
23 }
24 rows
25}
26
27#[must_use]
43pub fn gaussian_mutation<B: Backend>(
44 population: Tensor<B, 2>,
45 sigma: f32,
46 rng: &mut dyn Rng,
47 device: &<B as burn::tensor::backend::BackendTypes>::Device,
48) -> Tensor<B, 2> {
49 let [n, d] = population.dims();
50 let noise =
51 Tensor::<B, 2>::from_data(TensorData::new(standard_normal_rows(n, d, rng), [n, d]), device);
52 population + noise.mul_scalar(sigma)
53}
54
55#[must_use]
75pub fn gaussian_mutation_per_row<B: Backend>(
76 population: Tensor<B, 2>,
77 sigmas: Tensor<B, 1>,
78 rng: &mut dyn Rng,
79 device: &<B as burn::tensor::backend::BackendTypes>::Device,
80) -> Tensor<B, 2> {
81 let [n, d] = population.dims();
82 let noise =
83 Tensor::<B, 2>::from_data(TensorData::new(standard_normal_rows(n, d, rng), [n, d]), device);
84 let sigmas_2d = sigmas.reshape([n, 1]).expand([n, d]);
85 population + noise * sigmas_2d
86}
87
88#[must_use]
107pub fn uniform_reset<B: Backend>(
108 population: Tensor<B, 2>,
109 lo: f32,
110 hi: f32,
111 p: f32,
112 rng: &mut dyn Rng,
113 device: &<B as burn::tensor::backend::BackendTypes>::Device,
114) -> Tensor<B, 2> {
115 let [n, d] = population.dims();
116 let mut noise_rows = Vec::with_capacity(n * d);
117 let mut coin_rows = Vec::with_capacity(n * d);
118 for _ in 0..n * d {
119 noise_rows.push(lo + (hi - lo) * rng.random::<f32>());
120 coin_rows.push(rng.random::<f32>());
121 }
122 let noise = Tensor::<B, 2>::from_data(TensorData::new(noise_rows, [n, d]), device);
123 let coin = Tensor::<B, 2>::from_data(TensorData::new(coin_rows, [n, d]), device);
124 let reset = coin.lower_elem(p);
125 population.mask_where(reset, noise)
126}
127
128#[must_use]
146pub fn bit_flip_mutation<B: Backend>(
147 population: Tensor<B, 2, Int>,
148 p: f32,
149 rng: &mut dyn Rng,
150 device: &<B as burn::tensor::backend::BackendTypes>::Device,
151) -> Tensor<B, 2, Int> {
152 let shape = population.shape();
153 let [n, d] = population.dims();
154 let mut coin_rows = Vec::with_capacity(n * d);
155 for _ in 0..n * d {
156 coin_rows.push(rng.random::<f32>());
157 }
158 let coin = Tensor::<B, 2>::from_data(TensorData::new(coin_rows, [n, d]), device);
159 let flip = coin.lower_elem(p);
160 let ones = Tensor::<B, 2, Int>::ones(shape, device);
162 let flipped = ones - population.clone();
163 population.mask_where(flip, flipped)
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use burn::backend::Flex;
170 use burn::backend::flex::FlexDevice;
171 #[allow(unused_imports)]
172 use burn::tensor::backend::Backend as _;
173 use rand::SeedableRng;
174 use rand::rngs::StdRng;
175
176 type TestBackend = Flex;
177
178 #[test]
179 fn gaussian_with_zero_sigma_is_identity() {
180 let device: FlexDevice = Default::default();
181 let mut rng = StdRng::seed_from_u64(3);
182 let input = Tensor::<TestBackend, 2>::from_data(
183 TensorData::new(vec![1.0_f32, 2.0, 3.0, 4.0], [2, 2]),
184 &device,
185 );
186 let out = gaussian_mutation(input.clone(), 0.0, &mut rng, &device);
187 let before = input.into_data().into_vec::<f32>().unwrap();
188 let after = out.into_data().into_vec::<f32>().unwrap();
189 for (a, b) in before.iter().zip(after.iter()) {
190 approx::assert_relative_eq!(a, b, epsilon = 1e-6);
191 }
192 }
193
194 #[test]
195 fn gaussian_preserves_shape() {
196 let device: FlexDevice = Default::default();
197 let mut rng = StdRng::seed_from_u64(3);
198 let input = Tensor::<TestBackend, 2>::from_data(
199 TensorData::new(vec![0.0_f32; 12], [3, 4]),
200 &device,
201 );
202 let out = gaussian_mutation(input, 1.0, &mut rng, &device);
203 assert_eq!(out.dims(), [3, 4]);
204 }
205
206 #[test]
207 fn per_row_applies_distinct_sigmas() {
208 let device: FlexDevice = Default::default();
209 let mut rng = StdRng::seed_from_u64(4);
210 let input = Tensor::<TestBackend, 2>::from_data(
211 TensorData::new(vec![0.0_f32; 4], [2, 2]),
212 &device,
213 );
214 let sigmas = Tensor::<TestBackend, 1>::from_data(
215 TensorData::new(vec![0.0_f32, 0.0], [2]),
216 &device,
217 );
218 let out = gaussian_mutation_per_row(input, sigmas, &mut rng, &device);
219 let values = out.into_data().into_vec::<f32>().unwrap();
220 for v in values {
221 approx::assert_relative_eq!(v, 0.0, epsilon = 1e-6);
222 }
223 }
224
225 #[test]
226 fn uniform_reset_with_p_zero_is_identity() {
227 let device: FlexDevice = Default::default();
228 let mut rng = StdRng::seed_from_u64(9);
229 let input = Tensor::<TestBackend, 2>::from_data(
230 TensorData::new(vec![3.0_f32, 4.0, 5.0, 6.0], [2, 2]),
231 &device,
232 );
233 let out = uniform_reset(input.clone(), -10.0, 10.0, 0.0, &mut rng, &device);
234 let before = input.into_data().into_vec::<f32>().unwrap();
235 let after = out.into_data().into_vec::<f32>().unwrap();
236 for (a, b) in before.iter().zip(after.iter()) {
237 approx::assert_relative_eq!(a, b, epsilon = 1e-6);
238 }
239 }
240}