Skip to content

Instantly share code, notes, and snippets.

@guiyomh
Created August 31, 2023 09:16
Show Gist options
  • Save guiyomh/059582f441c9574335916964a18bc9a9 to your computer and use it in GitHub Desktop.
Save guiyomh/059582f441c9574335916964a18bc9a9 to your computer and use it in GitHub Desktop.
rust genetic algo
use rand::prelude::*;
use crate::{ELITISM, GENERATIONS_COUNT, GENOME_SIZE, LOG, POPULATION_SIZE, SELECTION_RATIO};
static mut UNIFORM_RATE: f64 = 0.5;
static mut MUTATION_RATE: f64 = 0.06;
#[derive(Debug, Clone)]
pub struct Gene {
random: f64,
}
impl Gene {
pub fn new() -> Self {
Self {
random: rand::thread_rng().gen::<f64>(),
}
}
pub fn as_int(&self, max: i32) -> i32 {
(self.random * (max + 1) as f64) as i32
}
}
#[derive(Debug, Clone)]
pub struct GenomeAndResult<T>
where
T: Clone,
{
genome: Vec<Gene>,
pub result: T,
}
impl<T> GenomeAndResult<T>
where
T: Clone,
{
fn new<C>(genome: Vec<Gene>, create: C) -> Self
where
C: Fn(Vec<Gene>) -> T,
{
Self {
genome: genome.clone(),
result: create(genome),
}
}
}
pub fn find_best_chimp<T, F, C>(create: C, fitness: F) -> GenomeAndResult<T>
where
F: Fn(&T) -> f64,
C: Fn(Vec<Gene>) -> T,
T: Clone,
{
fn sort_by_fitness<T, F>(population: &mut Vec<GenomeAndResult<T>>, fitness: F)
where
F: Fn(&T) -> f64,
T: Clone,
{
population.sort_by(|a, b| fitness(&b.result).partial_cmp(&fitness(&a.result)).unwrap());
if LOG {
let fitness_values: Vec<i32> = population
.iter()
.map(|g| fitness(&g.result) as i32)
.collect();
eprintln!(
"{}",
fitness_values
.iter()
.map(|f| format!("{:5}", f))
.collect::<Vec<String>>()
.join(" ")
);
}
}
let population: Vec<GenomeAndResult<T>> = (0..POPULATION_SIZE)
.map(|_| GenomeAndResult::new(build_genome(GENOME_SIZE), &create))
.collect();
let mut chimps_few_generations_later = population.clone();
sort_by_fitness(&mut chimps_few_generations_later, &fitness);
for _ in 0..GENERATIONS_COUNT {
chimps_few_generations_later =
build_next_generation(&chimps_few_generations_later, &create, &fitness);
sort_by_fitness(&mut chimps_few_generations_later, &fitness);
}
chimps_few_generations_later.first().unwrap().clone()
}
fn build_genome(size: usize) -> Vec<Gene> {
(0..size).map(|_| Gene::new()).collect()
}
fn build_next_generation<T, F>(
population: &[GenomeAndResult<T>],
create: F,
_fitness: &impl Fn(&T) -> f64,
) -> Vec<GenomeAndResult<T>>
where
F: Fn(Vec<Gene>) -> T,
T: Clone,
{
let mut new_pop: Vec<GenomeAndResult<T>> = population.to_vec();
let elitism_offset = if ELITISM { 1 } else { 0 };
for i in elitism_offset..population.len() - 1 {
let genome1 = select(&population).genome;
let genome2 = select(&population).genome;
let mut genome = crossover(&genome1, &genome2);
mutate(&mut genome);
new_pop[i] = GenomeAndResult::new(genome, &create);
}
new_pop
}
fn select<T>(population: &[GenomeAndResult<T>]) -> GenomeAndResult<T>
where
T: Clone,
{
let mut rng = rand::thread_rng();
for (i, chimp) in population.iter().enumerate() {
if rng.gen::<f64>()
<= SELECTION_RATIO * (population.len() - i) as f64 / population.len() as f64
{
return chimp.clone();
}
}
population[0].clone()
}
fn crossover(genome1: &[Gene], genome2: &[Gene]) -> Vec<Gene> {
genome1
.iter()
.enumerate()
.map(|(i, gene1)| {
if random::<f64>() <= unsafe { UNIFORM_RATE } {
gene1.clone()
} else {
genome2[i].clone()
}
})
.collect()
}
fn mutate(genome: &mut [Gene]) {
for gene in genome.iter_mut() {
if random::<f64>() <= unsafe { MUTATION_RATE } {
*gene = Gene::new()
}
}
}
// for exemple
const ELITISM: bool = true;
const GENERATIONS_COUNT: usize = 100;
const POPULATION_SIZE: usize = 20;
const GENOME_SIZE: usize = 1200;
const SELECTION_RATIO: f64 = 0.4;
const LOG: bool = false;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment