1use burn::tensor::{backend::Backend, Distribution, Int, Tensor};
9
10#[must_use]
14pub fn gaussian_mutation<B: Backend>(
15 population: Tensor<B, 2>,
16 sigma: f32,
17 device: &B::Device,
18) -> Tensor<B, 2> {
19 let shape = population.shape();
20 let noise = Tensor::<B, 2>::random(shape, Distribution::Normal(0.0, 1.0), device);
21 population + noise.mul_scalar(sigma)
22}
23
24#[must_use]
35pub fn gaussian_mutation_per_row<B: Backend>(
36 population: Tensor<B, 2>,
37 sigmas: Tensor<B, 1>,
38 device: &B::Device,
39) -> Tensor<B, 2> {
40 let shape = population.shape();
41 let n = shape.dims[0];
42 let d = shape.dims[1];
43 let noise = Tensor::<B, 2>::random(shape, Distribution::Normal(0.0, 1.0), device);
44 let sigmas_2d = sigmas.reshape([n, 1]).expand([n, d]);
45 population + noise * sigmas_2d
46}
47
48#[must_use]
53pub fn uniform_reset<B: Backend>(
54 population: Tensor<B, 2>,
55 lo: f32,
56 hi: f32,
57 p: f32,
58 device: &B::Device,
59) -> Tensor<B, 2> {
60 let shape = population.shape();
61 let noise =
62 Tensor::<B, 2>::random(shape.clone(), Distribution::Uniform(f64::from(lo), f64::from(hi)), device);
63 let coin = Tensor::<B, 2>::random(shape, Distribution::Uniform(0.0, 1.0), device);
64 let reset = coin.lower_elem(p);
65 population.mask_where(reset, noise)
66}
67
68#[must_use]
75pub fn bit_flip_mutation<B: Backend>(
76 population: Tensor<B, 2, Int>,
77 p: f32,
78 device: &B::Device,
79) -> Tensor<B, 2, Int> {
80 let shape = population.shape();
81 let coin = Tensor::<B, 2>::random(shape.clone(), Distribution::Uniform(0.0, 1.0), device);
82 let flip = coin.lower_elem(p);
83 let ones = Tensor::<B, 2, Int>::ones(shape, device);
85 let flipped = ones - population.clone();
86 population.mask_where(flip, flipped)
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use burn::backend::NdArray;
93 use burn::backend::ndarray::NdArrayDevice;
94 #[allow(unused_imports)]
95 use burn::tensor::backend::Backend as _;
96 use burn::tensor::TensorData;
97
98 type TestBackend = NdArray;
99
100 #[test]
101 fn gaussian_with_zero_sigma_is_identity() {
102 let device: NdArrayDevice = Default::default();
103 TestBackend::seed(&device, 3);
104 let input = Tensor::<TestBackend, 2>::from_data(
105 TensorData::new(vec![1.0_f32, 2.0, 3.0, 4.0], [2, 2]),
106 &device,
107 );
108 let out = gaussian_mutation(input.clone(), 0.0, &device);
109 let before = input.into_data().into_vec::<f32>().unwrap();
110 let after = out.into_data().into_vec::<f32>().unwrap();
111 for (a, b) in before.iter().zip(after.iter()) {
112 approx::assert_relative_eq!(a, b, epsilon = 1e-6);
113 }
114 }
115
116 #[test]
117 fn gaussian_preserves_shape() {
118 let device: NdArrayDevice = Default::default();
119 TestBackend::seed(&device, 3);
120 let input = Tensor::<TestBackend, 2>::from_data(
121 TensorData::new(vec![0.0_f32; 12], [3, 4]),
122 &device,
123 );
124 let out = gaussian_mutation(input, 1.0, &device);
125 assert_eq!(out.shape().dims, vec![3, 4]);
126 }
127
128 #[test]
129 fn per_row_applies_distinct_sigmas() {
130 let device: NdArrayDevice = Default::default();
131 TestBackend::seed(&device, 4);
132 let input = Tensor::<TestBackend, 2>::from_data(
133 TensorData::new(vec![0.0_f32; 4], [2, 2]),
134 &device,
135 );
136 let sigmas = Tensor::<TestBackend, 1>::from_data(
137 TensorData::new(vec![0.0_f32, 0.0], [2]),
138 &device,
139 );
140 let out = gaussian_mutation_per_row(input, sigmas, &device);
141 let values = out.into_data().into_vec::<f32>().unwrap();
142 for v in values {
143 approx::assert_relative_eq!(v, 0.0, epsilon = 1e-6);
144 }
145 }
146
147 #[test]
148 fn uniform_reset_with_p_zero_is_identity() {
149 let device: NdArrayDevice = Default::default();
150 TestBackend::seed(&device, 9);
151 let input = Tensor::<TestBackend, 2>::from_data(
152 TensorData::new(vec![3.0_f32, 4.0, 5.0, 6.0], [2, 2]),
153 &device,
154 );
155 let out = uniform_reset(input.clone(), -10.0, 10.0, 0.0, &device);
156 let before = input.into_data().into_vec::<f32>().unwrap();
157 let after = out.into_data().into_vec::<f32>().unwrap();
158 for (a, b) in before.iter().zip(after.iter()) {
159 approx::assert_relative_eq!(a, b, epsilon = 1e-6);
160 }
161 }
162}