Skip to main content

radiate_gp/collections/graphs/
chromosome.rs

1use crate::{Factory, GraphNode, NodeStore, node::Node};
2use radiate_core::{Chromosome, Gene, Valid};
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5use std::{fmt::Debug, hash::Hash};
6
7/// A chromosome type that represents a directed graph structure for genetic programming.
8/// This chromosome is essentially just a graph, the only difference is the name of the struct.
9/// The graph and the [GraphChromosome] are interchangeable, the only difference is who holds
10/// the vector of nodes. For instance, to create a graph from the [GraphChromosome], just take
11/// the vector of nodes and give them to a new graph instance - boom, you have a graph.
12///
13/// [GraphChromosome] is a specialized chromosome type that maintains a collection of graph nodes
14/// and their connections. It's designed for genetic programming applications where the solution
15/// space can be represented as a directed graph.
16///
17/// # Type Parameters
18/// * `T` - The type of value stored in each node. Must implement `Clone` and `PartialEq`.
19///
20/// # Structure
21/// The chromosome consists of:
22/// * A vector of [`GraphNode<T>`] instances representing the graph structure
23/// * An optional [`NodeStore<T>`] for managing node creation and validation. This makes
24///   the creation of new nodes easier and more uniform across the genetic algorithm.
25///
26/// # Features
27/// * Maintains graph connectivity through node connections
28/// * Provides factory methods for creating new instances
29/// * Implements serialization when the "serde" feature is enabled
30/// * Allows the graph nodes, essentially the graph, to be evolved through the genetic algorithm
31///
32/// # Examples
33/// ```
34/// use radiate_gp::collections::graphs::{GraphChromosome, GraphNode, Graph};
35/// use radiate_gp::{NodeStore, node_store, NodeType};
36///
37/// // Create a new chromosome with some nodes
38///let store = node_store! {
39///     Input => vec![1, 2, 3],
40///     Output => vec![4, 5, 6],
41///     Edge => vec![7, 8, 9],
42///     Vertex => vec![10, 11, 12]
43/// };
44///
45/// let graph = Graph::directed(1, 1, store.clone());
46///
47/// let chromosome = GraphChromosome::from((graph, store));
48/// ```
49///
50/// # Genetic Operations
51/// The chromosome supports several genetic operations:
52/// * Crossover through `GraphCrossover`
53/// * Mutation through `GraphMutator`
54/// * Replacement through `GraphReplacement`
55///
56/// # Serialization
57/// When the "serde" feature is enabled, the chromosome can be serialized and deserialized.
58/// The serialization preserves the graph structure and node values, but not the node store.
59///
60/// # Performance
61/// * Node access is O(1) through vector indexing
62/// * Graph operations (adding/removing nodes/edges) are O(log n) due to BTreeSet usage
63/// * Memory usage is O(V + E) where V is the number of nodes and E is the number of edges
64#[derive(Clone)]
65#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
66pub struct GraphChromosome<T> {
67    nodes: Vec<GraphNode<T>>,
68    store: Option<NodeStore<T>>,
69    max_nodes: Option<usize>,
70}
71
72impl<T> GraphChromosome<T> {
73    pub fn new(nodes: Vec<GraphNode<T>>, factory: NodeStore<T>) -> Self {
74        GraphChromosome {
75            nodes,
76            store: Some(factory),
77            max_nodes: None,
78        }
79    }
80
81    pub fn with_max_nodes(mut self, max_nodes: usize) -> Self {
82        self.max_nodes = Some(max_nodes + self.nodes.len());
83        self
84    }
85
86    pub fn take_nodes(&mut self) -> Vec<GraphNode<T>> {
87        std::mem::take(&mut self.nodes)
88    }
89
90    pub fn set_nodes(&mut self, nodes: Vec<GraphNode<T>>) {
91        self.nodes = nodes;
92    }
93
94    pub fn store(&self) -> Option<&NodeStore<T>> {
95        self.store.as_ref()
96    }
97
98    pub fn max_nodes(&self) -> Option<usize> {
99        self.max_nodes
100    }
101}
102
103impl<T> Factory<Option<NodeStore<T>>, GraphChromosome<T>> for GraphChromosome<T>
104where
105    T: Clone + PartialEq + Default,
106{
107    fn new_instance(&self, input: Option<NodeStore<T>>) -> GraphChromosome<T> {
108        input
109            .or_else(|| self.store.clone())
110            .map(|store| GraphChromosome {
111                nodes: self
112                    .iter()
113                    .enumerate()
114                    .filter_map(|(index, node)| {
115                        store
116                            .new_instance((index, node.node_type()))
117                            .map(|new_node| {
118                                let mut new_node = if new_node.arity() == node.arity() {
119                                    node.with_allele(new_node.allele())
120                                } else {
121                                    node.clone()
122                                };
123
124                                new_node.set_innovation(node.innovation());
125                                new_node
126                            })
127                    })
128                    .collect(),
129                store: Some(store),
130                max_nodes: self.max_nodes,
131            })
132            .map(|chromosome| {
133                if chromosome.len() != self.len() {
134                    self.clone()
135                } else {
136                    chromosome
137                }
138            })
139            .unwrap_or_else(|| self.clone())
140    }
141}
142
143impl<T> Chromosome for GraphChromosome<T>
144where
145    T: Clone + PartialEq,
146{
147    type Gene = GraphNode<T>;
148
149    fn as_slice(&self) -> &[GraphNode<T>] {
150        &self.nodes
151    }
152
153    fn as_mut_slice(&mut self) -> &mut [GraphNode<T>] {
154        &mut self.nodes
155    }
156}
157
158impl<T> Valid for GraphChromosome<T> {
159    #[inline]
160    fn is_valid(&self) -> bool {
161        self.nodes.iter().all(|gene| gene.is_valid())
162    }
163}
164
165impl<T> AsRef<[GraphNode<T>]> for GraphChromosome<T> {
166    fn as_ref(&self) -> &[GraphNode<T>] {
167        &self.nodes
168    }
169}
170
171impl<T> AsMut<[GraphNode<T>]> for GraphChromosome<T> {
172    fn as_mut(&mut self) -> &mut [GraphNode<T>] {
173        &mut self.nodes
174    }
175}
176
177impl<T: PartialEq> PartialEq for GraphChromosome<T> {
178    fn eq(&self, other: &Self) -> bool {
179        self.nodes == other.nodes
180    }
181}
182
183impl<T: Hash> Hash for GraphChromosome<T> {
184    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
185        for node in self.as_ref() {
186            node.hash(state);
187        }
188    }
189}
190
191impl<T> From<Vec<GraphNode<T>>> for GraphChromosome<T> {
192    fn from(nodes: Vec<GraphNode<T>>) -> Self {
193        GraphChromosome {
194            nodes,
195            store: None,
196            max_nodes: None,
197        }
198    }
199}
200
201impl<T, I> From<(I, NodeStore<T>)> for GraphChromosome<T>
202where
203    I: IntoIterator<Item = GraphNode<T>>,
204{
205    fn from((iter, store): (I, NodeStore<T>)) -> Self {
206        GraphChromosome {
207            nodes: iter.into_iter().collect(),
208            store: Some(store),
209            max_nodes: None,
210        }
211    }
212}
213
214impl<T> FromIterator<GraphNode<T>> for GraphChromosome<T> {
215    fn from_iter<I: IntoIterator<Item = GraphNode<T>>>(iter: I) -> Self {
216        GraphChromosome {
217            nodes: iter.into_iter().collect(),
218            store: None,
219            max_nodes: None,
220        }
221    }
222}
223
224impl<T> IntoIterator for GraphChromosome<T> {
225    type Item = GraphNode<T>;
226    type IntoIter = std::vec::IntoIter<GraphNode<T>>;
227
228    fn into_iter(self) -> Self::IntoIter {
229        self.nodes.into_iter()
230    }
231}
232
233impl<T: Debug> Debug for GraphChromosome<T> {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        writeln!(f, "Graph {{")?;
236        for node in self.as_ref() {
237            writeln!(f, "  {:?},", node)?;
238        }
239        write!(f, "}}")
240    }
241}