radiate_core/
diversity.rs

1use crate::{
2    Chromosome, Gene, Genotype,
3    chromosomes::{NumericAllele, gene::NumericGene},
4    fitness::Novelty,
5    math::distance,
6};
7use std::sync::Arc;
8
9pub trait Distance<T>: Send + Sync {
10    fn distance(&self, one: &T, two: &T) -> f32;
11}
12
13pub struct DistanceDiversityAdapter<C: Chromosome> {
14    diversity: Arc<dyn Diversity<C>>,
15}
16
17impl<C: Chromosome> DistanceDiversityAdapter<C> {
18    pub fn new(diversity: Arc<dyn Diversity<C>>) -> Self {
19        Self { diversity }
20    }
21}
22
23impl<C: Chromosome> Distance<Genotype<C>> for DistanceDiversityAdapter<C> {
24    fn distance(&self, one: &Genotype<C>, two: &Genotype<C>) -> f32 {
25        self.diversity.measure(one, two)
26    }
27}
28
29/// Trait for measuring diversity between two [Genotype]s.
30/// Within radiate this is mostly used for speciation and determining how genetically
31/// similar two individuals are. Through this, the engine can determine
32/// whether two individuals belong to the same [Species](super::genome::species::Species) or not.
33pub trait Diversity<C: Chromosome>: Send + Sync {
34    fn measure(&self, geno_one: &Genotype<C>, geno_two: &Genotype<C>) -> f32;
35}
36
37impl<C: Chromosome, F> Diversity<C> for F
38where
39    F: Fn(&Genotype<C>, &Genotype<C>) -> f32 + Send + Sync,
40{
41    fn measure(&self, geno_one: &Genotype<C>, geno_two: &Genotype<C>) -> f32 {
42        (self)(geno_one, geno_two)
43    }
44}
45
46/// A concrete implementation of the [Diversity] trait that calculates the Hamming distance
47/// between two [Genotype]s. The Hamming distance is the number of positions at which the
48/// corresponding genes are different normalized by the total number of genes.
49#[derive(Clone)]
50pub struct HammingDistance;
51
52impl<G, C> Diversity<C> for HammingDistance
53where
54    C: Chromosome<Gene = G>,
55    G: Gene,
56    G::Allele: PartialEq,
57{
58    fn measure(&self, geno_one: &Genotype<C>, geno_two: &Genotype<C>) -> f32 {
59        let mut distance = 0.0;
60        let mut total_genes = 0.0;
61        for (chrom_one, chrom_two) in geno_one.iter().zip(geno_two.iter()) {
62            for (gene_one, gene_two) in chrom_one.iter().zip(chrom_two.iter()) {
63                total_genes += 1.0;
64                if gene_one.allele() != gene_two.allele() {
65                    distance += 1.0;
66                }
67            }
68        }
69
70        distance / total_genes
71    }
72}
73
74impl<P: AsRef<[f32]>> Distance<P> for HammingDistance {
75    fn distance(&self, one: &P, two: &P) -> f32 {
76        let vec_one = one.as_ref();
77        let vec_two = two.as_ref();
78
79        distance::hamming(vec_one, vec_two)
80    }
81}
82
83impl Novelty<Vec<f32>> for HammingDistance {
84    fn description(&self, phenotype: &Vec<f32>) -> Vec<f32> {
85        phenotype.clone()
86    }
87}
88
89/// Implementation of the [Diversity] trait that calculates the Euclidean distance
90/// between two [Genotype]s. The Euclidean distance is the square root of the sum of the
91/// squared differences between the corresponding genes' alleles, normalized by the number of genes.
92#[derive(Clone)]
93pub struct EuclideanDistance;
94
95impl<G, C> Diversity<C> for EuclideanDistance
96where
97    C: Chromosome<Gene = G>,
98    G: NumericGene,
99    G::Allele: NumericAllele,
100{
101    fn measure(&self, geno_one: &Genotype<C>, geno_two: &Genotype<C>) -> f32 {
102        let mut distance = 0.0;
103        let mut total_genes = 0.0;
104        for (chrom_one, chrom_two) in geno_one.iter().zip(geno_two.iter()) {
105            for (gene_one, gene_two) in chrom_one.iter().zip(chrom_two.iter()) {
106                let one_as_f32 = gene_one.allele_as_f32();
107                let two_as_f32 = gene_two.allele_as_f32();
108
109                if let Some((one, two)) = one_as_f32.zip(two_as_f32) {
110                    if one.is_nan() || two.is_nan() {
111                        continue;
112                    }
113
114                    let diff = one - two;
115                    distance += diff * diff;
116                    total_genes += 1.0;
117                }
118            }
119        }
120
121        if total_genes == 0.0 {
122            return 0.0;
123        }
124
125        (distance / total_genes).sqrt()
126    }
127}
128
129impl<P: AsRef<[f32]>> Distance<P> for EuclideanDistance {
130    fn distance(&self, one: &P, two: &P) -> f32 {
131        let vec_one = one.as_ref();
132        let vec_two = two.as_ref();
133
134        distance::euclidean(vec_one, vec_two)
135    }
136}
137
138impl Novelty<Vec<f32>> for EuclideanDistance {
139    fn description(&self, phenotype: &Vec<f32>) -> Vec<f32> {
140        phenotype.clone()
141    }
142}
143
144#[derive(Clone)]
145pub struct CosineDistance;
146
147impl<G, C> Diversity<C> for CosineDistance
148where
149    C: Chromosome<Gene = G>,
150    G: NumericGene,
151    G::Allele: NumericAllele,
152{
153    fn measure(&self, geno_one: &Genotype<C>, geno_two: &Genotype<C>) -> f32 {
154        let mut dot_product = 0.0;
155        let mut norm_one = 0.0;
156        let mut norm_two = 0.0;
157
158        for (chrom_one, chrom_two) in geno_one.iter().zip(geno_two.iter()) {
159            for (gene_one, gene_two) in chrom_one.iter().zip(chrom_two.iter()) {
160                let one_as_f32 = gene_one.allele_as_f32();
161                let two_as_f32 = gene_two.allele_as_f32();
162
163                if let Some((one, two)) = one_as_f32.zip(two_as_f32) {
164                    if one.is_nan() || two.is_nan() {
165                        continue;
166                    }
167
168                    dot_product += one * two;
169                    norm_one += one * one;
170                    norm_two += two * two;
171                }
172            }
173        }
174
175        if norm_one == 0.0 || norm_two == 0.0 {
176            return 1.0;
177        }
178
179        1.0 - (dot_product / (norm_one.sqrt() * norm_two.sqrt()))
180    }
181}
182
183impl<P: AsRef<[f32]>> Distance<P> for CosineDistance {
184    fn distance(&self, one: &P, two: &P) -> f32 {
185        let vec_one = one.as_ref();
186        let vec_two = two.as_ref();
187
188        distance::cosine(vec_one, vec_two)
189    }
190}
191
192impl Novelty<Vec<f32>> for CosineDistance {
193    fn description(&self, phenotype: &Vec<f32>) -> Vec<f32> {
194        phenotype.clone()
195    }
196}