psyche_core/
offspring_builder.rs

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