Skip to content

Commit 775dabf

Browse files
committed
Merge branch 'develop'
2 parents e7ecd5f + cb9abc9 commit 775dabf

File tree

8 files changed

+69
-108
lines changed

8 files changed

+69
-108
lines changed

crates/radiate-gp/src/collections/graphs/aggregate.rs

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,12 @@ impl<'a, T: Clone> GraphAggregate<'a, T> {
136136
let node = self.nodes[node_id];
137137

138138
trans.push((index, node.node_type(), node.value().clone(), node.arity()));
139-
id_index_map.insert(node_id, index);
139+
id_index_map.insert(*node_id, index);
140140
}
141141

142142
for rel in self.relationships.iter() {
143-
let source_idx = id_index_map[&rel.source_id];
144-
let target_idx = id_index_map[&rel.target_id];
143+
let source_idx = id_index_map[rel.source_id];
144+
let target_idx = id_index_map[rel.target_id];
145145

146146
trans.attach(source_idx, target_idx);
147147
}
@@ -256,50 +256,42 @@ impl<'a, T: Clone> GraphAggregate<'a, T> {
256256

257257
fn attach(&mut self, group: &AggregateInsertValue<'a, T>) -> Vec<&'a GraphNodeId> {
258258
match group {
259-
AggregateInsertValue::Single(node) => self.attach_single(node),
260-
AggregateInsertValue::Many(nodes) => self.attach_many(nodes),
261-
}
262-
}
263-
264-
fn attach_single(&mut self, node: &'a GraphNode<T>) -> Vec<&'a GraphNodeId> {
265-
if !self.nodes.contains_key(node.id()) {
266-
let ordinal = self.node_order.len();
267-
self.nodes.insert(node.id(), node);
268-
self.node_order.insert(ordinal, node.id());
269-
270-
return vec![node.id()];
271-
}
272-
273-
vec![node.id()]
274-
}
275-
276-
fn attach_many(&mut self, nodes: &'a [GraphNode<T>]) -> Vec<&'a GraphNodeId> {
277-
let mut indexes = Vec::with_capacity(nodes.len());
278-
for node in nodes.iter() {
279-
let node_id = node.id();
280-
281-
if !self.nodes.contains_key(node_id) {
282-
let ordinal = self.node_order.len();
283-
284-
self.nodes.insert(node_id, node);
285-
self.node_order.insert(ordinal, node_id);
286-
indexes.push(node_id);
287-
288-
nodes
289-
.iter()
290-
.filter(|item| node.outgoing().contains(&item.index()))
291-
.for_each(|item| {
292-
self.relationships.push(Relationship {
293-
source_id: node.id(),
294-
target_id: item.id(),
295-
});
296-
});
297-
} else {
298-
indexes.push(node_id);
259+
AggregateInsertValue::Single(node) => {
260+
if !self.nodes.contains_key(node.id()) {
261+
let ordinal = self.node_order.len();
262+
self.nodes.insert(node.id(), node);
263+
self.node_order.insert(ordinal, node.id());
264+
}
265+
266+
vec![node.id()]
267+
}
268+
AggregateInsertValue::Many(nodes) => {
269+
let mut indexes = Vec::with_capacity(nodes.len());
270+
for node in nodes.iter() {
271+
let node_id = node.id();
272+
indexes.push(node_id);
273+
274+
if !self.nodes.contains_key(node_id) {
275+
let ordinal = self.node_order.len();
276+
277+
self.nodes.insert(node_id, node);
278+
self.node_order.insert(ordinal, node_id);
279+
280+
nodes
281+
.iter()
282+
.filter(|item| node.outgoing().contains(&item.index()))
283+
.for_each(|item| {
284+
self.relationships.push(Relationship {
285+
source_id: node.id(),
286+
target_id: item.id(),
287+
});
288+
});
289+
}
290+
}
291+
292+
indexes
299293
}
300294
}
301-
302-
indexes
303295
}
304296

305297
fn one_to_one_connect(&mut self, one: &[&'a GraphNodeId], two: &[&'a GraphNodeId]) {
@@ -856,10 +848,12 @@ mod tests {
856848
for i in 0..ins.len() {
857849
assert_eq!(g[i].outgoing().len(), outs.len());
858850
}
851+
859852
// Each output receives from all inputs
860853
for j in 0..outs.len() {
861854
assert_eq!(g[ins.len() + j].incoming().len(), ins.len());
862855
}
856+
863857
assert!(g.is_valid());
864858
}
865859

crates/radiate-gp/src/collections/graphs/diversity.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,6 @@ impl NeatDistance {
2828
}
2929
}
3030

31-
impl Diversity<GraphChromosome<Op<f32>>> for NeatDistance {
32-
fn measure(
33-
&self,
34-
one: &Genotype<GraphChromosome<Op<f32>>>,
35-
two: &Genotype<GraphChromosome<Op<f32>>>,
36-
) -> f32 {
37-
one.iter()
38-
.zip(two.iter())
39-
.map(|(a, b)| self.graph_distance_iter(a, b))
40-
.sum()
41-
}
42-
}
43-
4431
impl NeatDistance {
4532
#[inline]
4633
fn graph_distance_iter(
@@ -126,3 +113,16 @@ impl NeatDistance {
126113
+ (self.op_mismatch_penalty * op_mismatch_penalty)
127114
}
128115
}
116+
117+
impl Diversity<GraphChromosome<Op<f32>>> for NeatDistance {
118+
fn measure(
119+
&self,
120+
one: &Genotype<GraphChromosome<Op<f32>>>,
121+
two: &Genotype<GraphChromosome<Op<f32>>>,
122+
) -> f32 {
123+
one.iter()
124+
.zip(two.iter())
125+
.map(|(a, b)| self.graph_distance_iter(a, b))
126+
.sum()
127+
}
128+
}

crates/radiate-gp/src/ops/math.rs

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,14 @@ pub(super) const fn clamp(value: f32) -> f32 {
3030
/// common cases - vals with a len <= 5.
3131
#[inline]
3232
pub(super) fn aggregate(vals: &[f32]) -> f32 {
33-
let len = vals.len();
34-
if len == 0 {
35-
return ZERO;
36-
} else if len == 1 {
37-
return vals[0];
38-
} else if len == 2 {
39-
return vals[0] + vals[1];
40-
} else if len == 3 {
41-
return vals[0] + vals[1] + vals[2];
42-
} else if len == 4 {
43-
return vals[0] + vals[1] + vals[2] + vals[3];
44-
} else if len == 5 {
45-
return vals[0] + vals[1] + vals[2] + vals[3] + vals[4];
46-
} else if len == 6 {
47-
return vals[0] + vals[1] + vals[2] + vals[3] + vals[4] + vals[5];
48-
} else if len == 7 {
49-
return vals[0] + vals[1] + vals[2] + vals[3] + vals[4] + vals[5] + vals[6];
50-
}
51-
52-
vals.iter().cloned().sum::<f32>()
33+
match vals {
34+
[] => ZERO,
35+
[a] => *a,
36+
[a, b] => a + b,
37+
[a, b, c] => a + b + c,
38+
[a, b, c, d] => a + b + c + d,
39+
_ => vals.iter().copied().sum(),
40+
}
5341
}
5442

5543
#[inline]

crates/radiate-gp/src/ops/param.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,3 @@ where
7575
self.data.fmt(f)
7676
}
7777
}
78-
79-
// impl<T> Factory<T, Param<T>> for Param<T> {
80-
// fn new_instance(&self, mut val: T) -> Param<T> {
81-
// (self.modifier)(&mut val);
82-
// Param {
83-
// data: val,
84-
// supplier: self.supplier,
85-
// modifier: self.modifier,
86-
// }
87-
// }
88-
// }

crates/radiate-utils/src/buff/window.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,6 @@ mod tests {
110110
let mut buffer = WindowBuffer::with_window(5);
111111
for i in 0..20 {
112112
buffer.push(i);
113-
println!(
114-
"Added value {}, buffer len: {:?} -> {:?} ",
115-
i,
116-
buffer.len(),
117-
buffer.values(),
118-
);
119113
}
120114
}
121115
}

docs/source/events.md

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
# Events and Subscriptions
22

3-
!!! warning ":construction: Under Construction :construction:"
4-
5-
As of `12/15/2025`: These docs are a work in progress and may not be complete or fully accurate. Please check back later for updates.
6-
73
Radiate provides an event system that allows you to monitor and react to the evolution process in real-time. This is great for:
84

95
- Tracking the progress of evolution
106
- Collecting metrics and statistics
11-
- Implementing custom logging
7+
- Implementing custom logging or logic based on the state of evolution
128
- Visualizing the evolution process
139

1410
## Overview
@@ -19,7 +15,7 @@ The `GeneticEngine` trys it's best to off-load almost the entire compute workloa
1915

2016
!!! note "Threading Behavior"
2117

22-
Currently, the rust implementation is multi-threaded, meaning if you have multiple subscribers, there is no guarantee of the order in which they will be called. For python, regardless of if you are using a free-threaded interpreter (3.13t/3.14t, ect) or not, the events will be dispatched on a single thread in the order they were added.
18+
Currently, the rust implementation is multi-threaded (if multi-threaded executors are used), meaning if you have multiple subscribers, there is no guarantee of the order in which they will be called. For python, regardless of if you are using a free-threaded interpreter (3.13t/3.14t, ect) or not, the events will be dispatched on a single thread in the order they were added.
2319

2420
---
2521
## Event Types
@@ -66,7 +62,7 @@ Below there is a brief description of each event type with its representative da
6662
{
6763
'event_type': 'stop_event',
6864
'index': 0, // Current generation number
69-
// This will be a dictionary of metrics collected, see Engine's metrics docs for more info
65+
// This will be a MetricSet (or dictionary in python) of metrics collected, see Engine's metrics docs for more info
7066
'metrics': ...,
7167
// This will be the decoded best individual found so far. So, if you are
7268
// evolving a vector of FloatGenes, this will be a list of floats
@@ -164,7 +160,7 @@ The simplest way to subscribe to events is by providing a callback function:
164160
use radiate::*;
165161

166162
let mut engine = GeneticEngine::builder()
167-
.codec(your_codec)
163+
.codec(FloatCodec::vector(6, -5.0..5.0))
168164
.fitness_fn(your_fitness_fn)
169165
.subscribe(|event: &EngineEvent<Vec<f32>>| {
170166
if let EngineEvent::EpochComplete(index, best, metrics, score, objective) = event {
@@ -215,7 +211,7 @@ For more complex event handling, you can create a custom event handler class:
215211
engine.run(rd.GenerationsLimit(100))
216212
```
217213

218-
Its also completely possible to create more advanced forms of visualization or logging through this method. For example, below we will collect the score distrubution from each epoch using polars then plot it with matplotlib.
214+
Its also completely possible to create more advanced forms of visualization or logging through this method. For example, below we will collect the scores from each epoch then use polars to create a DataFrame and finally plot it with matplotlib.
219215

220216
```python
221217
class ScorePlotterHandler(rd.EventHandler):
@@ -278,7 +274,7 @@ For more complex event handling, you can create a custom event handler class:
278274

279275
// Create and configure the engine
280276
let mut engine = GeneticEngine::builder()
281-
.codec(your_codec)
277+
.codec(FloatCodec::vector(6, -5.0..5.0))
282278
.subscribe(MyHandler) // Add your handler here
283279
.fitness_fn(your_fitness_fn)
284280
// ... other parameters ...

docs/source/fitness.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Simple fitness functions are the most common type - they take a phenotype and re
2727

2828
!!! note ":fontawesome-brands-python: Python `numba`"
2929

30-
For python, In come cases (primarily if you are decoding to a `np.array`) it is possible to compile your fitness function down to native C using [numba](https://numba.pydata.org).
30+
For python, In some cases it is possible to compile your fitness function down to machine code using [numba](https://numba.pydata.org).
3131
In most cases with this, this will result in your engine running as fast or almost as fast as rust. Check the examples page for an example using this method.
3232

3333
=== ":fontawesome-brands-python: Python"

examples/graphs/regression-graph/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ fn main() {
2525
))
2626
.build();
2727

28-
engine
28+
radiate::ui(engine)
2929
.iter()
30-
.logging()
30+
// .logging()
3131
.until_score(MIN_SCORE)
3232
.last()
3333
.inspect(display);

0 commit comments

Comments
 (0)