1use burn::tensor::{Int, Tensor, TensorData, backend::Backend};
13use rand::{Rng, RngExt};
14use rlevo_core::probability::Probability;
15use rlevo_core::rate::NonNegativeRate;
16
17fn standard_normal_rows(n: usize, d: usize, rng: &mut dyn Rng) -> Vec<f32> {
20 let mut rows = Vec::with_capacity(n * d);
21 for _ in 0..n * d {
22 rows.push(crate::sampling::standard_normal(rng));
23 }
24 rows
25}
26
27#[must_use]
43pub fn gaussian_mutation<B: Backend>(
44 population: Tensor<B, 2>,
45 sigma: NonNegativeRate,
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 = Tensor::<B, 2>::from_data(
51 TensorData::new(standard_normal_rows(n, d, rng), [n, d]),
52 device,
53 );
54 population + noise.mul_scalar(sigma.get())
55}
56
57#[must_use]
77pub fn gaussian_mutation_per_row<B: Backend>(
78 population: Tensor<B, 2>,
79 sigmas: Tensor<B, 1>,
80 rng: &mut dyn Rng,
81 device: &<B as burn::tensor::backend::BackendTypes>::Device,
82) -> Tensor<B, 2> {
83 let [n, d] = population.dims();
84 let noise = Tensor::<B, 2>::from_data(
85 TensorData::new(standard_normal_rows(n, d, rng), [n, d]),
86 device,
87 );
88 let sigmas_2d = sigmas.reshape([n, 1]).expand([n, d]);
89 population + noise * sigmas_2d
90}
91
92#[must_use]
111pub fn uniform_reset<B: Backend>(
112 population: Tensor<B, 2>,
113 lo: f32,
114 hi: f32,
115 p: Probability,
116 rng: &mut dyn Rng,
117 device: &<B as burn::tensor::backend::BackendTypes>::Device,
118) -> Tensor<B, 2> {
119 let [n, d] = population.dims();
120 let mut noise_rows = Vec::with_capacity(n * d);
121 let mut coin_rows = Vec::with_capacity(n * d);
122 for _ in 0..n * d {
123 noise_rows.push(lo + (hi - lo) * rng.random::<f32>());
124 coin_rows.push(rng.random::<f32>());
125 }
126 let noise = Tensor::<B, 2>::from_data(TensorData::new(noise_rows, [n, d]), device);
127 let coin = Tensor::<B, 2>::from_data(TensorData::new(coin_rows, [n, d]), device);
128 let reset = coin.lower_elem(p.get());
129 population.mask_where(reset, noise)
130}
131
132#[must_use]
150pub fn bit_flip_mutation<B: Backend>(
151 population: Tensor<B, 2, Int>,
152 p: Probability,
153 rng: &mut dyn Rng,
154 device: &<B as burn::tensor::backend::BackendTypes>::Device,
155) -> Tensor<B, 2, Int> {
156 let shape = population.shape();
157 let [n, d] = population.dims();
158 let mut coin_rows = Vec::with_capacity(n * d);
159 for _ in 0..n * d {
160 coin_rows.push(rng.random::<f32>());
161 }
162 let coin = Tensor::<B, 2>::from_data(TensorData::new(coin_rows, [n, d]), device);
163 let flip = coin.lower_elem(p.get());
164 let ones = Tensor::<B, 2, Int>::ones(shape, device);
166 let flipped = ones - population.clone();
167 population.mask_where(flip, flipped)
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use burn::backend::Flex;
174 use burn::backend::flex::FlexDevice;
175 #[allow(unused_imports)]
176 use burn::tensor::backend::Backend as _;
177 use rand::SeedableRng;
178 use rand::rngs::StdRng;
179
180 type TestBackend = Flex;
181
182 #[test]
183 fn gaussian_with_zero_sigma_is_identity() {
184 let device: FlexDevice = Default::default();
185 let mut rng = StdRng::seed_from_u64(3);
186 let input = Tensor::<TestBackend, 2>::from_data(
187 TensorData::new(vec![1.0_f32, 2.0, 3.0, 4.0], [2, 2]),
188 &device,
189 );
190 let out = gaussian_mutation(input.clone(), NonNegativeRate::new(0.0), &mut rng, &device);
191 let before = input
192 .into_data()
193 .into_vec::<f32>()
194 .expect("genome host-read of a tensor this test just built");
195 let after = out
196 .into_data()
197 .into_vec::<f32>()
198 .expect("genome host-read of a tensor this test just built");
199 for (a, b) in before.iter().zip(after.iter()) {
200 approx::assert_relative_eq!(a, b, epsilon = 1e-6);
201 }
202 }
203
204 #[test]
205 fn gaussian_preserves_shape() {
206 let device: FlexDevice = Default::default();
207 let mut rng = StdRng::seed_from_u64(3);
208 let input = Tensor::<TestBackend, 2>::from_data(
209 TensorData::new(vec![0.0_f32; 12], [3, 4]),
210 &device,
211 );
212 let out = gaussian_mutation(input, NonNegativeRate::new(1.0), &mut rng, &device);
213 assert_eq!(out.dims(), [3, 4]);
214 }
215
216 #[test]
217 fn per_row_applies_distinct_sigmas() {
218 let device: FlexDevice = Default::default();
219 let mut rng = StdRng::seed_from_u64(4);
220 let input =
221 Tensor::<TestBackend, 2>::from_data(TensorData::new(vec![0.0_f32; 4], [2, 2]), &device);
222 let sigmas =
223 Tensor::<TestBackend, 1>::from_data(TensorData::new(vec![0.0_f32, 0.0], [2]), &device);
224 let out = gaussian_mutation_per_row(input, sigmas, &mut rng, &device);
225 let values = out
226 .into_data()
227 .into_vec::<f32>()
228 .expect("genome host-read of a tensor this test just built");
229 for v in values {
230 approx::assert_relative_eq!(v, 0.0, epsilon = 1e-6);
231 }
232 }
233
234 #[test]
235 fn uniform_reset_with_p_zero_is_identity() {
236 let device: FlexDevice = Default::default();
237 let mut rng = StdRng::seed_from_u64(9);
238 let input = Tensor::<TestBackend, 2>::from_data(
239 TensorData::new(vec![3.0_f32, 4.0, 5.0, 6.0], [2, 2]),
240 &device,
241 );
242 let out = uniform_reset(
243 input.clone(),
244 -10.0,
245 10.0,
246 Probability::new(0.0),
247 &mut rng,
248 &device,
249 );
250 let before = input
251 .into_data()
252 .into_vec::<f32>()
253 .expect("genome host-read of a tensor this test just built");
254 let after = out
255 .into_data()
256 .into_vec::<f32>()
257 .expect("genome host-read of a tensor this test just built");
258 for (a, b) in before.iter().zip(after.iter()) {
259 approx::assert_relative_eq!(a, b, epsilon = 1e-6);
260 }
261 }
262}