psyche-core 0.2.19

Core module for Psyche AI Toolset
Documentation
use crate::brain::Brain;
use crate::neuron::{NeuronID, Position};
use crate::Scalar;
use rand::{thread_rng, Rng};
use serde::{Deserialize, Serialize};
use std::f64::consts::PI;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OffspringBuilder {
    new_neurons: usize,
    new_connections: usize,
    radius: Scalar,
    min_neurogenesis_range: Scalar,
    max_neurogenesis_range: Scalar,
    new_sensors: usize,
    new_effectors: usize,
    no_loop_connections: bool,
    max_connecting_tries: usize,
}

impl Default for OffspringBuilder {
    fn default() -> Self {
        Self {
            new_neurons: 1,
            new_connections: 1,
            radius: 10.0,
            min_neurogenesis_range: 0.1,
            max_neurogenesis_range: 1.0,
            new_sensors: 1,
            new_effectors: 1,
            no_loop_connections: true,
            max_connecting_tries: 10,
        }
    }
}

impl OffspringBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn new_neurons(mut self, value: usize) -> Self {
        self.new_neurons = value;
        self
    }

    pub fn new_connections(mut self, value: usize) -> Self {
        self.new_connections = value;
        self
    }

    pub fn radius(mut self, value: Scalar) -> Self {
        self.radius = value;
        self
    }

    pub fn min_neurogenesis_range(mut self, value: Scalar) -> Self {
        self.min_neurogenesis_range = value;
        self
    }

    pub fn max_neurogenesis_range(mut self, value: Scalar) -> Self {
        self.max_neurogenesis_range = value;
        self
    }

    pub fn new_sensors(mut self, value: usize) -> Self {
        self.new_sensors = value;
        self
    }

    pub fn new_effectors(mut self, value: usize) -> Self {
        self.new_effectors = value;
        self
    }

    pub fn no_loop_connections(mut self, value: bool) -> Self {
        self.no_loop_connections = value;
        self
    }

    pub fn max_connecting_tries(mut self, value: usize) -> Self {
        self.max_connecting_tries = value;
        self
    }

    pub fn build_mutated(mut self, source: &Brain) -> Brain {
        let mut brain = source.duplicate();
        let mut rng = thread_rng();

        let mut neurons = brain.get_neurons();
        for _ in 0..self.new_neurons {
            neurons.push(self.make_neighbor_neuron(&neurons, &mut brain, &mut rng));
        }

        let neuron_positions = neurons
            .iter()
            .map(|id| (*id, brain.neuron(*id).unwrap().position()))
            .collect::<Vec<_>>();
        for _ in 0..self.new_sensors {
            let mut tries = self.max_connecting_tries + 1;
            while tries > 0 && !self.make_peripheral_sensor(&neuron_positions, &mut brain, &mut rng)
            {
                tries -= 1;
            }
        }
        for _ in 0..self.new_effectors {
            let mut tries = self.max_connecting_tries + 1;
            while tries > 0
                && !self.make_peripheral_effector(&neuron_positions, &mut brain, &mut rng)
            {
                tries -= 1;
            }
        }
        for _ in 0..self.new_connections {
            let mut tries = self.max_connecting_tries + 1;
            while tries > 0
                && self.connect_neighbor_neurons(&neuron_positions, &mut brain, &mut rng)
            {
                tries -= 1;
            }
        }
        for id in brain.get_neurons() {
            if !brain.does_neuron_has_connections(id) {
                drop(brain.kill_neuron(id));
            }
        }

        brain
    }

    pub fn build_merged(mut self, source_a: &Brain, source_b: &Brain) -> Brain {
        let mut brain = source_a.merge(source_b);
        let mut rng = thread_rng();

        let mut neurons = brain.get_neurons();
        for _ in 0..self.new_neurons {
            neurons.push(self.make_neighbor_neuron(&neurons, &mut brain, &mut rng));
        }

        self.new_sensors += (source_a.get_sensors().len() + source_b.get_sensors().len()) / 2
            - brain.get_sensors().len();
        self.new_effectors += (source_a.get_effectors().len() + source_b.get_effectors().len()) / 2
            - brain.get_effectors().len();
        let neuron_positions = neurons
            .iter()
            .map(|id| (*id, brain.neuron(*id).unwrap().position()))
            .collect::<Vec<_>>();
        for _ in 0..self.new_sensors {
            let mut tries = self.max_connecting_tries + 1;
            while tries > 0 && !self.make_peripheral_sensor(&neuron_positions, &mut brain, &mut rng)
            {
                tries -= 1;
            }
        }
        for _ in 0..self.new_effectors {
            let mut tries = self.max_connecting_tries + 1;
            while tries > 0
                && !self.make_peripheral_effector(&neuron_positions, &mut brain, &mut rng)
            {
                tries -= 1;
            }
        }
        for _ in 0..self.new_connections {
            let mut tries = self.max_connecting_tries + 1;
            while tries > 0
                && !self.connect_neighbor_neurons(&neuron_positions, &mut brain, &mut rng)
            {
                tries -= 1;
            }
        }

        brain
    }

    fn make_peripheral_sensor<R>(
        &self,
        neuron_positions: &[(NeuronID, Position)],
        brain: &mut Brain,
        rng: &mut R,
    ) -> bool
    where
        R: Rng,
    {
        let pos = self.make_new_peripheral_position(rng);
        let index = neuron_positions
            .iter()
            .map(|(_, p)| p.distance_sqr(pos))
            .enumerate()
            .min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
            .unwrap()
            .0;
        brain.create_sensor(neuron_positions[index].0).is_ok()
    }

    fn make_peripheral_effector<R>(
        &self,
        neuron_positions: &[(NeuronID, Position)],
        brain: &mut Brain,
        rng: &mut R,
    ) -> bool
    where
        R: Rng,
    {
        let pos = self.make_new_peripheral_position(rng);
        let index = neuron_positions
            .iter()
            .map(|(_, p)| p.distance_sqr(pos))
            .enumerate()
            .min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
            .unwrap()
            .0;
        brain.create_effector(neuron_positions[index].0).is_ok()
    }

    fn make_neighbor_neuron<R>(
        &mut self,
        neurons: &[NeuronID],
        brain: &mut Brain,
        rng: &mut R,
    ) -> NeuronID
    where
        R: Rng,
    {
        let distance = rng.gen_range(self.min_neurogenesis_range, self.max_neurogenesis_range);
        let origin = neurons[rng.gen_range(0, neurons.len()) % neurons.len()];
        let origin_pos = brain.neuron(origin).unwrap().position();
        let new_position = self.make_new_position(origin_pos, distance, rng);
        brain.create_neuron(new_position)
    }

    fn connect_neighbor_neurons<R>(
        &mut self,
        neuron_positions: &[(NeuronID, Position)],
        brain: &mut Brain,
        rng: &mut R,
    ) -> bool
    where
        R: Rng,
    {
        let origin =
            neuron_positions[rng.gen_range(0, neuron_positions.len()) % neuron_positions.len()];
        let filtered = neuron_positions
            .iter()
            .filter_map(|(id, p)| {
                if p.distance(origin.1) <= self.max_neurogenesis_range {
                    Some(id)
                } else {
                    None
                }
            })
            .collect::<Vec<_>>();
        let target = *filtered[rng.gen_range(0, filtered.len()) % filtered.len()];
        origin.0 != target
            && (!self.no_loop_connections
                || (!brain.are_neurons_connected(origin.0, target)
                    && !brain.are_neurons_connected(target, origin.0)))
            && brain.bind_neurons(origin.0, target).is_ok()
    }

    fn make_new_position<R>(&self, pos: Position, scale: Scalar, rng: &mut R) -> Position
    where
        R: Rng,
    {
        let phi = rng.gen_range(0.0, PI * 2.0);
        let theta = rng.gen_range(-PI, PI);
        let pos = Position {
            x: pos.x + theta.cos() * phi.cos() * scale,
            y: pos.y + theta.cos() * phi.sin() * scale,
            z: pos.z + theta.sin() * scale,
        };
        let magnitude = pos.magnitude();
        if magnitude > self.radius {
            Position {
                x: self.radius * pos.x / magnitude,
                y: self.radius * pos.y / magnitude,
                z: self.radius * pos.z / magnitude,
            }
        } else {
            pos
        }
    }

    fn make_new_peripheral_position<R>(&self, rng: &mut R) -> Position
    where
        R: Rng,
    {
        let phi = rng.gen_range(0.0, PI * 2.0);
        let theta = rng.gen_range(-PI, PI);
        Position {
            x: theta.cos() * phi.cos() * self.radius,
            y: theta.cos() * phi.sin() * self.radius,
            z: theta.sin() * self.radius,
        }
    }
}