Skip to main content

radiate_core/
diversity.rs

1use crate::{
2    Chromosome, Gene, Phenotype,
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<Phenotype<C>> for DistanceDiversityAdapter<C> {
24    fn distance(&self, one: &Phenotype<C>, two: &Phenotype<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: &Phenotype<C>, geno_two: &Phenotype<C>) -> f32;
35}
36
37impl<C: Chromosome, F> Diversity<C> for F
38where
39    F: Fn(&Phenotype<C>, &Phenotype<C>) -> f32 + Send + Sync,
40{
41    fn measure(&self, geno_one: &Phenotype<C>, geno_two: &Phenotype<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: &Phenotype<C>, geno_two: &Phenotype<C>) -> f32 {
59        let geno_one = geno_one.genotype();
60        let geno_two = geno_two.genotype();
61
62        let mut distance = 0.0;
63        let mut total_genes = 0.0;
64        for (chrom_one, chrom_two) in geno_one.iter().zip(geno_two.iter()) {
65            for (gene_one, gene_two) in chrom_one.iter().zip(chrom_two.iter()) {
66                total_genes += 1.0;
67                if gene_one.allele() != gene_two.allele() {
68                    distance += 1.0;
69                }
70            }
71        }
72
73        distance / total_genes
74    }
75}
76
77impl<P: AsRef<[f32]>> Distance<P> for HammingDistance {
78    fn distance(&self, one: &P, two: &P) -> f32 {
79        let vec_one = one.as_ref();
80        let vec_two = two.as_ref();
81
82        distance::hamming(vec_one, vec_two)
83    }
84}
85
86impl Novelty<Vec<f32>> for HammingDistance {
87    fn description(&self, phenotype: &Vec<f32>) -> Vec<f32> {
88        phenotype.clone()
89    }
90}
91
92/// Implementation of the [Diversity] trait that calculates the Euclidean distance
93/// between two [Genotype]s. The Euclidean distance is the square root of the sum of the
94/// squared differences between the corresponding genes' alleles, normalized by the number of genes.
95#[derive(Clone)]
96pub struct EuclideanDistance;
97
98impl<G, C> Diversity<C> for EuclideanDistance
99where
100    C: Chromosome<Gene = G>,
101    G: NumericGene,
102    G::Allele: NumericAllele,
103{
104    fn measure(&self, geno_one: &Phenotype<C>, geno_two: &Phenotype<C>) -> f32 {
105        let geno_one = geno_one.genotype();
106        let geno_two = geno_two.genotype();
107
108        let mut distance = 0.0;
109        let mut total_genes = 0.0;
110        for (chrom_one, chrom_two) in geno_one.iter().zip(geno_two.iter()) {
111            for (gene_one, gene_two) in chrom_one.iter().zip(chrom_two.iter()) {
112                let one_as_f64 = gene_one.allele().extract::<f64>();
113                let two_as_f64 = gene_two.allele().extract::<f64>();
114
115                if let Some((one, two)) = one_as_f64.zip(two_as_f64) {
116                    if one.is_nan() || two.is_nan() {
117                        continue;
118                    }
119
120                    let diff = one - two;
121                    distance += diff * diff;
122                    total_genes += 1.0;
123                }
124            }
125        }
126
127        if total_genes == 0.0 {
128            return 0.0;
129        }
130
131        (distance / total_genes).sqrt() as f32
132    }
133}
134
135impl<P: AsRef<[f32]>> Distance<P> for EuclideanDistance {
136    fn distance(&self, one: &P, two: &P) -> f32 {
137        let vec_one = one.as_ref();
138        let vec_two = two.as_ref();
139
140        distance::euclidean(vec_one, vec_two)
141    }
142}
143
144impl Novelty<Vec<f32>> for EuclideanDistance {
145    fn description(&self, phenotype: &Vec<f32>) -> Vec<f32> {
146        phenotype.clone()
147    }
148}
149
150#[derive(Clone)]
151pub struct CosineDistance;
152
153impl<G, C> Diversity<C> for CosineDistance
154where
155    C: Chromosome<Gene = G>,
156    G: NumericGene,
157    G::Allele: NumericAllele,
158{
159    fn measure(&self, geno_one: &Phenotype<C>, geno_two: &Phenotype<C>) -> f32 {
160        let geno_one = geno_one.genotype();
161        let geno_two = geno_two.genotype();
162
163        let mut dot_product = 0.0;
164        let mut norm_one = 0.0;
165        let mut norm_two = 0.0;
166
167        for (chrom_one, chrom_two) in geno_one.iter().zip(geno_two.iter()) {
168            for (gene_one, gene_two) in chrom_one.iter().zip(chrom_two.iter()) {
169                let one_as_f64 = gene_one.allele().extract::<f64>();
170                let two_as_f64 = gene_two.allele().extract::<f64>();
171
172                if let Some((one, two)) = one_as_f64.zip(two_as_f64) {
173                    if one.is_nan() || two.is_nan() {
174                        continue;
175                    }
176
177                    dot_product += one * two;
178                    norm_one += one * one;
179                    norm_two += two * two;
180                }
181            }
182        }
183
184        if norm_one == 0.0 || norm_two == 0.0 {
185            return 1.0;
186        }
187
188        1.0 - (dot_product / (norm_one.sqrt() * norm_two.sqrt())) as f32
189    }
190}
191
192impl<P: AsRef<[f32]>> Distance<P> for CosineDistance {
193    fn distance(&self, one: &P, two: &P) -> f32 {
194        let vec_one = one.as_ref();
195        let vec_two = two.as_ref();
196
197        distance::cosine(vec_one, vec_two)
198    }
199}
200
201impl Novelty<Vec<f32>> for CosineDistance {
202    fn description(&self, phenotype: &Vec<f32>) -> Vec<f32> {
203        phenotype.clone()
204    }
205}