genetic_algorithm/
allele.rs

1//! The possible values for a single gene
2use impl_trait_for_tuples::impl_for_tuples;
3use rand::distributions::uniform::SampleUniform;
4use std::hash::{Hash, Hasher};
5use std::ops::{Add, AddAssign, Sub, SubAssign};
6
7/// Standard Allele, suitable for [crate::genotype::Genotype]. Implemented for a set of primitives by default
8pub trait Allele: Clone + Copy + Send + Sync + std::fmt::Debug {
9    /// Hash a slice of alleles. This method allows type-specific hashing behavior.
10    /// For most types, this uses the standard Hash trait.
11    /// For float types (f32, f64), this hashes the bytes for deterministic results.
12    fn hash_slice(slice: &[Self], hasher: &mut impl Hasher)
13    where
14        Self: Sized;
15}
16
17/// Macro for implementing Allele with default hash_slice
18/// Use this for any type that implements Hash and needs the standard hashing behavior
19#[macro_export]
20macro_rules! impl_allele{
21    ($($t:ty),*) => {
22        $(
23            impl $crate::allele::Allele for $t {
24                fn hash_slice(slice: &[Self], hasher: &mut impl ::std::hash::Hasher) {
25                    ::std::hash::Hash::hash(slice, hasher);
26                }
27            }
28        )*
29    }
30}
31
32impl_allele!(bool, char, i128, i16, i32, i64, i8, isize, u128, u16, u32, u64, u8, usize);
33impl Allele for f32 {
34    fn hash_slice(slice: &[Self], hasher: &mut impl Hasher) {
35        let bytes: &[u8] = bytemuck::cast_slice(slice);
36        bytes.hash(hasher);
37    }
38}
39impl Allele for f64 {
40    fn hash_slice(slice: &[Self], hasher: &mut impl Hasher) {
41        let bytes: &[u8] = bytemuck::cast_slice(slice);
42        bytes.hash(hasher);
43    }
44}
45
46// Tuple implementations using impl_for_tuples macro
47#[impl_for_tuples(0, 12)]
48impl Allele for Tuple {
49    for_tuples!( where #( Tuple: Allele + Hash ),* );
50
51    fn hash_slice(slice: &[Self], hasher: &mut impl Hasher) {
52        slice.hash(hasher);
53    }
54}
55
56/// Special Allele subtrait, used for [crate::genotype::RangeGenotype],
57/// [crate::genotype::MultiRangeGenotype]
58pub trait RangeAllele:
59    Allele
60    + Add<Output = Self>
61    + Sub<Output = Self>
62    + AddAssign
63    + SubAssign
64    // + Mul<Output = Self>
65    // + Into<f64>
66    + std::cmp::PartialOrd
67    + Default
68    + bytemuck::NoUninit
69    + SampleUniform
70{
71    /// Used to build a start exclusive range, by adding the increment to the start
72    fn smallest_increment() -> Self;
73
74    /// Returns value 0 for iteration/counting
75    fn zero() -> Self;
76
77    /// Returns value 1 for iteration/counting
78    fn one() -> Self;
79
80    /// Floors to nearest integer (identity for integer types)
81    fn floor(&self) -> Self;
82
83    /// Needed as f32 and f64 don't implement saturating_sub and saturating_add
84    fn clamped_add(current_value: Self, delta: Self, max_value: Self) -> Self;
85    fn clamped_sub(current_value: Self, delta: Self, min_value: Self) -> Self;
86    fn min(a: Self, b: Self) -> Self {
87        if a < b { a } else { b }
88    }
89}
90
91impl RangeAllele for f32 {
92    fn smallest_increment() -> Self {
93        f32::EPSILON
94    }
95    fn zero() -> Self {
96        0.0
97    }
98    fn one() -> Self {
99        1.0
100    }
101    fn floor(&self) -> Self {
102        f32::floor(*self)
103    }
104    // ignore f32::MAX, not realistic for use case
105    fn clamped_add(current_value: Self, delta: Self, max_value: Self) -> Self {
106        let new_value = current_value + delta;
107        if new_value > max_value {
108            max_value
109        } else {
110            new_value
111        }
112    }
113    // ignore f32::MIN, not realistic for use case
114    fn clamped_sub(current_value: Self, delta: Self, min_value: Self) -> Self {
115        let new_value = current_value - delta;
116        if new_value < min_value {
117            min_value
118        } else {
119            new_value
120        }
121    }
122}
123impl RangeAllele for f64 {
124    fn smallest_increment() -> Self {
125        f64::EPSILON
126    }
127    fn zero() -> Self {
128        0.0
129    }
130    fn one() -> Self {
131        1.0
132    }
133    fn floor(&self) -> Self {
134        f64::floor(*self)
135    }
136    // ignore f64::MAX, not realistic for use case
137    fn clamped_add(current_value: Self, delta: Self, max_value: Self) -> Self {
138        let new_value = current_value + delta;
139        if new_value > max_value {
140            max_value
141        } else {
142            new_value
143        }
144    }
145    // ignore f64::MIN, not realistic for use case
146    fn clamped_sub(current_value: Self, delta: Self, min_value: Self) -> Self {
147        let new_value = current_value - delta;
148        if new_value < min_value {
149            min_value
150        } else {
151            new_value
152        }
153    }
154}
155impl RangeAllele for i8 {
156    fn smallest_increment() -> Self {
157        1
158    }
159    fn zero() -> Self {
160        0
161    }
162    fn one() -> Self {
163        1
164    }
165    fn floor(&self) -> Self {
166        *self
167    }
168    fn clamped_add(current_value: Self, delta: Self, max_value: Self) -> Self {
169        let new_value = current_value.saturating_add(delta);
170        if new_value > max_value {
171            max_value
172        } else {
173            new_value
174        }
175    }
176    fn clamped_sub(current_value: Self, delta: Self, min_value: Self) -> Self {
177        let new_value = current_value.saturating_sub(delta);
178        if new_value < min_value {
179            min_value
180        } else {
181            new_value
182        }
183    }
184}
185impl RangeAllele for i16 {
186    fn smallest_increment() -> Self {
187        1
188    }
189    fn zero() -> Self {
190        0
191    }
192    fn one() -> Self {
193        1
194    }
195    fn floor(&self) -> Self {
196        *self
197    }
198    fn clamped_add(current_value: Self, delta: Self, max_value: Self) -> Self {
199        let new_value = current_value.saturating_add(delta);
200        if new_value > max_value {
201            max_value
202        } else {
203            new_value
204        }
205    }
206    fn clamped_sub(current_value: Self, delta: Self, min_value: Self) -> Self {
207        let new_value = current_value.saturating_sub(delta);
208        if new_value < min_value {
209            min_value
210        } else {
211            new_value
212        }
213    }
214}
215impl RangeAllele for i32 {
216    fn smallest_increment() -> Self {
217        1
218    }
219    fn zero() -> Self {
220        0
221    }
222    fn one() -> Self {
223        1
224    }
225    fn floor(&self) -> Self {
226        *self
227    }
228    fn clamped_add(current_value: Self, delta: Self, max_value: Self) -> Self {
229        let new_value = current_value.saturating_add(delta);
230        if new_value > max_value {
231            max_value
232        } else {
233            new_value
234        }
235    }
236    fn clamped_sub(current_value: Self, delta: Self, min_value: Self) -> Self {
237        let new_value = current_value.saturating_sub(delta);
238        if new_value < min_value {
239            min_value
240        } else {
241            new_value
242        }
243    }
244}
245impl RangeAllele for u8 {
246    fn smallest_increment() -> Self {
247        1
248    }
249    fn zero() -> Self {
250        0
251    }
252    fn one() -> Self {
253        1
254    }
255    fn floor(&self) -> Self {
256        *self
257    }
258    fn clamped_add(current_value: Self, delta: Self, max_value: Self) -> Self {
259        let new_value = current_value.saturating_add(delta);
260        if new_value > max_value {
261            max_value
262        } else {
263            new_value
264        }
265    }
266    fn clamped_sub(current_value: Self, delta: Self, min_value: Self) -> Self {
267        let new_value = current_value.saturating_sub(delta);
268        if new_value < min_value {
269            min_value
270        } else {
271            new_value
272        }
273    }
274}
275impl RangeAllele for u16 {
276    fn smallest_increment() -> Self {
277        1
278    }
279    fn zero() -> Self {
280        0
281    }
282    fn one() -> Self {
283        1
284    }
285    fn floor(&self) -> Self {
286        *self
287    }
288    fn clamped_add(current_value: Self, delta: Self, max_value: Self) -> Self {
289        let new_value = current_value.saturating_add(delta);
290        if new_value > max_value {
291            max_value
292        } else {
293            new_value
294        }
295    }
296    fn clamped_sub(current_value: Self, delta: Self, min_value: Self) -> Self {
297        let new_value = current_value.saturating_sub(delta);
298        if new_value < min_value {
299            min_value
300        } else {
301            new_value
302        }
303    }
304}
305impl RangeAllele for u32 {
306    fn smallest_increment() -> Self {
307        1
308    }
309    fn zero() -> Self {
310        0
311    }
312    fn one() -> Self {
313        1
314    }
315    fn floor(&self) -> Self {
316        *self
317    }
318    fn clamped_add(current_value: Self, delta: Self, max_value: Self) -> Self {
319        let new_value = current_value.saturating_add(delta);
320        if new_value > max_value {
321            max_value
322        } else {
323            new_value
324        }
325    }
326    fn clamped_sub(current_value: Self, delta: Self, min_value: Self) -> Self {
327        let new_value = current_value.saturating_sub(delta);
328        if new_value < min_value {
329            min_value
330        } else {
331            new_value
332        }
333    }
334}