1use burn::tensor::{backend::Backend, Distribution, Int, Tensor};
22
23#[must_use]
29pub fn blx_alpha<B: Backend>(
30 parent_a: Tensor<B, 2>,
31 parent_b: Tensor<B, 2>,
32 alpha: f32,
33 device: &B::Device,
34) -> Tensor<B, 2> {
35 assert_eq!(
36 parent_a.shape().dims,
37 parent_b.shape().dims,
38 "BLX-α: parents must have identical shapes"
39 );
40 let shape = parent_a.shape();
41
42 let min = parent_a.clone().min_pair(parent_b.clone());
43 let max = parent_a.max_pair(parent_b);
44 let diff = max.clone() - min.clone();
45 let lo = min - diff.clone().mul_scalar(alpha);
46 let hi = max + diff.mul_scalar(alpha);
47
48 let u = Tensor::<B, 2>::random(shape, Distribution::Uniform(0.0, 1.0), device);
49 lo.clone() + u * (hi - lo)
50}
51
52#[must_use]
60pub fn uniform_crossover<B: Backend>(
61 parent_a: Tensor<B, 2>,
62 parent_b: Tensor<B, 2>,
63 p: f32,
64 device: &B::Device,
65) -> Tensor<B, 2> {
66 assert_eq!(
67 parent_a.shape().dims,
68 parent_b.shape().dims,
69 "uniform crossover: parents must have identical shapes"
70 );
71 let shape = parent_a.shape();
72 let u = Tensor::<B, 2>::random(shape, Distribution::Uniform(0.0, 1.0), device);
73 let keep_a = u.lower_elem(p);
74 parent_a.mask_where(keep_a.bool_not(), parent_b)
75}
76
77#[must_use]
86pub fn binary_uniform_crossover<B: Backend>(
87 parent_a: Tensor<B, 2, Int>,
88 parent_b: Tensor<B, 2, Int>,
89 p: f32,
90 device: &B::Device,
91) -> Tensor<B, 2, Int> {
92 assert_eq!(
93 parent_a.shape().dims,
94 parent_b.shape().dims,
95 "binary uniform crossover: parents must have identical shapes"
96 );
97 let shape = parent_a.shape();
98 let u = Tensor::<B, 2>::random(shape, Distribution::Uniform(0.0, 1.0), device);
99 let keep_a = u.lower_elem(p);
100 parent_a.mask_where(keep_a.bool_not(), parent_b)
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use burn::backend::{ndarray::NdArrayDevice, NdArray};
107 use burn::tensor::TensorData;
108 #[allow(unused_imports)]
109 use burn::tensor::backend::Backend as _;
110 type TestBackend = NdArray;
111
112 #[test]
113 fn blx_alpha_lies_between_bounds() {
114 let device: NdArrayDevice = Default::default();
115 TestBackend::seed(&device, 13);
116 let a = Tensor::<TestBackend, 2>::from_data(
117 TensorData::new(vec![0.0_f32, 0.0, 0.0, 0.0], [2, 2]),
118 &device,
119 );
120 let b = Tensor::<TestBackend, 2>::from_data(
121 TensorData::new(vec![1.0_f32, 1.0, 1.0, 1.0], [2, 2]),
122 &device,
123 );
124 let c = blx_alpha(a, b, 0.0, &device);
125 let values = c.into_data().into_vec::<f32>().unwrap();
126 for v in values {
128 assert!((0.0..=1.0).contains(&v), "value out of bounds: {v}");
129 }
130 }
131
132 #[test]
133 fn uniform_all_from_a_when_p_is_one() {
134 let device: NdArrayDevice = Default::default();
135 TestBackend::seed(&device, 5);
136 let a = Tensor::<TestBackend, 2>::from_data(
137 TensorData::new(vec![7.0_f32, 7.0, 7.0, 7.0], [2, 2]),
138 &device,
139 );
140 let b = Tensor::<TestBackend, 2>::from_data(
141 TensorData::new(vec![-7.0_f32, -7.0, -7.0, -7.0], [2, 2]),
142 &device,
143 );
144 let c = uniform_crossover(a, b, 1.0, &device);
145 let values = c.into_data().into_vec::<f32>().unwrap();
146 for v in values {
147 approx::assert_relative_eq!(v, 7.0, epsilon = 1e-6);
148 }
149 }
150
151 #[test]
152 fn uniform_all_from_b_when_p_is_zero() {
153 let device: NdArrayDevice = Default::default();
154 TestBackend::seed(&device, 5);
155 let a = Tensor::<TestBackend, 2>::from_data(
156 TensorData::new(vec![7.0_f32, 7.0, 7.0, 7.0], [2, 2]),
157 &device,
158 );
159 let b = Tensor::<TestBackend, 2>::from_data(
160 TensorData::new(vec![-7.0_f32, -7.0, -7.0, -7.0], [2, 2]),
161 &device,
162 );
163 let c = uniform_crossover(a, b, 0.0, &device);
164 let values = c.into_data().into_vec::<f32>().unwrap();
165 for v in values {
166 approx::assert_relative_eq!(v, -7.0, epsilon = 1e-6);
167 }
168 }
169}