buddy_up_lib/algorithm/
mod.rs

1pub mod history;
2use crate::People;
3use crate::Person;
4use genetic_algorithm::{chromosome::GenesOwner, strategy::evolve::prelude::*};
5use history::History;
6use serde::Deserialize;
7use serde::Serialize;
8use tracing::{debug, trace};
9
10#[derive(Debug, Serialize, Deserialize)]
11pub struct Pairs(Vec<(Person, Person)>);
12
13impl Pairs {
14    pub fn inner(self) -> Vec<(Person, Person)> {
15        self.0
16    }
17}
18
19/// This function does the pairing magic. Given some [`People`] and a [`History`] of past pairings,
20/// it'll output a new set of [`Pairs`].
21pub fn pair(people: People, last: &History) -> Pairs {
22    let ids = people.as_ids();
23
24    let genotype = UniqueGenotype::builder()
25        .with_allele_list(ids)
26        .build()
27        .unwrap();
28
29    debug!("{genotype}");
30
31    let mut evolve = Evolve::builder()
32        .with_genotype(genotype)
33        .with_target_population_size(50)
34        .with_max_stale_generations(1000)
35        .with_fitness(PairFitness::new(last.clone()))
36        .with_fitness_ordering(FitnessOrdering::Minimize)
37        .with_target_fitness_score(0)
38        //.with_par_fitness(true)
39        .with_replace_on_equal_fitness(true)
40        .with_mutate(MutateSingleGene::new(0.2))
41        .with_crossover(CrossoverClone::new())
42        .with_select(SelectElite::new(0.9))
43        //.with_reporter(EvolveReporterSimple::new(1000))
44        .build()
45        .unwrap();
46
47    evolve.call();
48    let genes = evolve
49        .best_genes()
50        .expect("Something went wrong getting best genes");
51
52    let pairs: Vec<(usize, usize)> = genes.chunks(2).map(|c| (c[0], c[1])).collect();
53    let pairs: Vec<(Person, Person)> = pairs
54        .iter()
55        .map(|(id1, id2)| {
56            (
57                Person::new(*id1, people.name_from_id(*id1).unwrap()),
58                Person::new(*id2, people.name_from_id(*id2).unwrap()),
59            )
60        })
61        .collect();
62    Pairs(pairs)
63}
64
65#[derive(Clone, Debug)]
66struct PairFitness {
67    last: History,
68}
69
70impl PairFitness {
71    fn new(last: History) -> PairFitness {
72        Self { last }
73    }
74}
75impl Fitness for PairFitness {
76    type Genotype = UniqueGenotype<usize>;
77    #[allow(clippy::cast_possible_wrap)]
78    fn calculate_for_chromosome(
79        &mut self,
80        chromosome: &FitnessChromosome<Self>,
81        _genotype: &FitnessGenotype<Self>,
82    ) -> Option<FitnessValue> {
83        let mut score = 0;
84        chromosome.genes().chunks(2).for_each(|chunk| {
85            let (i, j) = (chunk[0], chunk[1]);
86
87            let last = match self.last.get((i, j)) {
88                Some(x) => {
89                    trace!("Found score {x} for pair ({i}, {j}).");
90                    x
91                }
92                None => {
93                    if let Some(x) = self.last.get((j, i)) {
94                        trace!("Found score {x} for pair ({j}, {i}).");
95                        x
96                    } else {
97                        trace!("Found no score for pair ({j}, {i}), using 0");
98                        0
99                    }
100                }
101            };
102            // high score should be bad
103            score += last as isize;
104        });
105        trace!("Score for chromosome {:?}: {score}", chromosome.genes());
106        Some(score)
107    }
108}