psyche_core/
brain_builder.rs

1use crate::brain::Brain;
2use crate::config::Config;
3use crate::neuron::{NeuronID, Position};
4use crate::Scalar;
5use rand::{thread_rng, Rng};
6use serde::{Deserialize, Serialize};
7use std::f64::consts::PI;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct BrainBuilder {
11    config: Config,
12    neurons: usize,
13    connections: usize,
14    radius: Scalar,
15    min_neurogenesis_range: Scalar,
16    max_neurogenesis_range: Scalar,
17    sensors: usize,
18    effectors: usize,
19    no_loop_connections: bool,
20    max_connecting_tries: usize,
21}
22
23impl Default for BrainBuilder {
24    fn default() -> Self {
25        Self {
26            config: Default::default(),
27            neurons: 100,
28            connections: 0,
29            radius: 10.0,
30            min_neurogenesis_range: 0.1,
31            max_neurogenesis_range: 1.0,
32            sensors: 1,
33            effectors: 1,
34            no_loop_connections: true,
35            max_connecting_tries: 10,
36        }
37    }
38}
39
40impl BrainBuilder {
41    pub fn new() -> Self {
42        Self::default()
43    }
44
45    pub fn config(mut self, config: Config) -> Self {
46        self.config = config;
47        self
48    }
49
50    pub fn neurons(mut self, value: usize) -> Self {
51        self.neurons = value;
52        self
53    }
54
55    pub fn connections(mut self, value: usize) -> Self {
56        self.connections = value;
57        self
58    }
59
60    pub fn radius(mut self, value: Scalar) -> Self {
61        self.radius = value;
62        self
63    }
64
65    pub fn min_neurogenesis_range(mut self, value: Scalar) -> Self {
66        self.min_neurogenesis_range = value;
67        self
68    }
69
70    pub fn max_neurogenesis_range(mut self, value: Scalar) -> Self {
71        self.max_neurogenesis_range = value;
72        self
73    }
74
75    pub fn sensors(mut self, value: usize) -> Self {
76        self.sensors = value;
77        self
78    }
79
80    pub fn effectors(mut self, value: usize) -> Self {
81        self.effectors = value;
82        self
83    }
84
85    pub fn no_loop_connections(mut self, value: bool) -> Self {
86        self.no_loop_connections = value;
87        self
88    }
89
90    pub fn max_connecting_tries(mut self, value: usize) -> Self {
91        self.max_connecting_tries = value;
92        self
93    }
94
95    pub fn build(mut self) -> Brain {
96        let mut brain = Brain::new();
97        brain.set_config(self.config.clone());
98        let mut rng = thread_rng();
99
100        let mut neurons = vec![];
101        neurons.push(brain.create_neuron(Position {
102            x: 0.0,
103            y: 0.0,
104            z: 0.0,
105        }));
106        for _ in 0..self.neurons {
107            neurons.push(self.make_neighbor_neuron(&neurons, &mut brain, &mut rng));
108        }
109
110        let neuron_positions = neurons
111            .iter()
112            .map(|id| (*id, brain.neuron(*id).unwrap().position()))
113            .collect::<Vec<_>>();
114        for _ in 0..self.sensors {
115            let mut tries = self.max_connecting_tries + 1;
116            while tries > 0 && !self.make_peripheral_sensor(&neuron_positions, &mut brain, &mut rng)
117            {
118                tries -= 1;
119            }
120        }
121        for _ in 0..self.effectors {
122            let mut tries = self.max_connecting_tries + 1;
123            while tries > 0
124                && !self.make_peripheral_effector(&neuron_positions, &mut brain, &mut rng)
125            {
126                tries -= 1;
127            }
128        }
129        for _ in 0..self.connections {
130            let mut tries = self.max_connecting_tries + 1;
131            while tries > 0
132                && !self.connect_neighbor_neurons(&neuron_positions, &mut brain, &mut rng)
133            {
134                tries -= 1;
135            }
136        }
137        for id in brain.get_neurons() {
138            if !brain.does_neuron_has_connections(id) {
139                drop(brain.kill_neuron(id));
140            }
141        }
142
143        brain
144    }
145
146    fn make_peripheral_sensor<R>(
147        &self,
148        neuron_positions: &[(NeuronID, Position)],
149        brain: &mut Brain,
150        rng: &mut R,
151    ) -> bool
152    where
153        R: Rng,
154    {
155        let pos = self.make_new_peripheral_position(rng);
156        let index = neuron_positions
157            .iter()
158            .map(|(_, p)| p.distance_sqr(pos))
159            .enumerate()
160            .min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
161            .unwrap()
162            .0;
163        brain.create_sensor(neuron_positions[index].0).is_ok()
164    }
165
166    fn make_peripheral_effector<R>(
167        &self,
168        neuron_positions: &[(NeuronID, Position)],
169        brain: &mut Brain,
170        rng: &mut R,
171    ) -> bool
172    where
173        R: Rng,
174    {
175        let pos = self.make_new_peripheral_position(rng);
176        let index = neuron_positions
177            .iter()
178            .map(|(_, p)| p.distance_sqr(pos))
179            .enumerate()
180            .min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
181            .unwrap()
182            .0;
183        brain.create_effector(neuron_positions[index].0).is_ok()
184    }
185
186    fn make_neighbor_neuron<R>(
187        &mut self,
188        neurons: &[NeuronID],
189        brain: &mut Brain,
190        rng: &mut R,
191    ) -> NeuronID
192    where
193        R: Rng,
194    {
195        let distance = rng.gen_range(self.min_neurogenesis_range, self.max_neurogenesis_range);
196        let origin = neurons[rng.gen_range(0, neurons.len()) % neurons.len()];
197        let origin_pos = brain.neuron(origin).unwrap().position();
198        let new_position = self.make_new_position(origin_pos, distance, rng);
199        brain.create_neuron(new_position)
200    }
201
202    fn connect_neighbor_neurons<R>(
203        &mut self,
204        neuron_positions: &[(NeuronID, Position)],
205        brain: &mut Brain,
206        rng: &mut R,
207    ) -> bool
208    where
209        R: Rng,
210    {
211        let origin =
212            neuron_positions[rng.gen_range(0, neuron_positions.len()) % neuron_positions.len()];
213        let filtered = neuron_positions
214            .iter()
215            .filter_map(|(id, p)| {
216                if p.distance(origin.1) <= self.max_neurogenesis_range {
217                    Some(id)
218                } else {
219                    None
220                }
221            })
222            .collect::<Vec<_>>();
223        let target = *filtered[rng.gen_range(0, filtered.len()) % filtered.len()];
224        origin.0 != target
225            && (!self.no_loop_connections
226                || (!brain.are_neurons_connected(origin.0, target)
227                    && !brain.are_neurons_connected(target, origin.0)))
228            && brain.bind_neurons(origin.0, target).is_ok()
229    }
230
231    fn make_new_position<R>(&self, pos: Position, scale: Scalar, rng: &mut R) -> Position
232    where
233        R: Rng,
234    {
235        let phi = rng.gen_range(0.0, PI * 2.0);
236        let theta = rng.gen_range(-PI, PI);
237        let pos = Position {
238            x: pos.x + theta.cos() * phi.cos() * scale,
239            y: pos.y + theta.cos() * phi.sin() * scale,
240            z: pos.z + theta.sin() * scale,
241        };
242        let magnitude = pos.magnitude();
243        if magnitude > self.radius {
244            Position {
245                x: self.radius * pos.x / magnitude,
246                y: self.radius * pos.y / magnitude,
247                z: self.radius * pos.z / magnitude,
248            }
249        } else {
250            pos
251        }
252    }
253
254    fn make_new_peripheral_position<R>(&self, rng: &mut R) -> Position
255    where
256        R: Rng,
257    {
258        let phi = rng.gen_range(0.0, PI * 2.0);
259        let theta = rng.gen_range(-PI, PI);
260        Position {
261            x: theta.cos() * phi.cos() * self.radius,
262            y: theta.cos() * phi.sin() * self.radius,
263            z: theta.sin() * self.radius,
264        }
265    }
266}