From 38d9ea7bce328c81d92828499ee5b00f2f224218 Mon Sep 17 00:00:00 2001 From: vandomej Date: Sat, 6 Sep 2025 10:56:01 -0700 Subject: [PATCH] Filling out documentation comments --- gemla/src/core/genetic_node.rs | 245 ++++++++++++++++++++++++++++++--- gemla/src/core/mod.rs | 94 +++++++------ gemla/src/error.rs | 24 ++++ gemla/src/lib.rs | 57 ++++++++ gemla/src/tree/mod.rs | 5 + 5 files changed, 364 insertions(+), 61 deletions(-) diff --git a/gemla/src/core/genetic_node.rs b/gemla/src/core/genetic_node.rs index abcb15f..657e9f9 100644 --- a/gemla/src/core/genetic_node.rs +++ b/gemla/src/core/genetic_node.rs @@ -1,6 +1,11 @@ -//! A trait used to interact with the internal state of nodes within the [`Bracket`] +//! This module defines the [`GeneticNode`] trait and related types for managing the state and behavior of nodes within a [`Gemla`] simulation. //! -//! [`Bracket`]: crate::bracket::Bracket +//! - [`GeneticNode`]: A trait for interacting with the internal state of nodes, supporting asynchronous initialization, simulation, mutation, and merging. +//! - [`GeneticState`]: Enum representing the current state of a node in the simulation lifecycle. +//! - [`GeneticNodeContext`]: Context struct passed to node methods, containing generation, node ID, and simulation context. +//! - [`GeneticNodeWrapper`]: Wrapper struct for managing state transitions and node lifecycle. +//! +//! [`Gemla`]: crate::Gemla use crate::error::Error; @@ -11,50 +16,230 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::fmt::Debug; use uuid::Uuid; -/// An enum used to control the state of a [`GeneticNode`] +/// An enum representing the current state of a [`GeneticNode`] in the simulation lifecycle. /// -/// [`GeneticNode`]: crate::bracket::genetic_node +/// Used to control the processing flow of a node, such as initialization, simulation, mutation, and completion. #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] pub enum GeneticState { - /// The node and it's data have not finished initializing + /// The node and its data have not finished initializing. Initialize, - /// The node is currently simulating a round against target data to determine the fitness of the population + /// The node is currently simulating a round against target data to determine the fitness of the population. Simulate, - /// The node is currently mutating members of it's population and breeding new members + /// The node is currently mutating members of its population and breeding new members. Mutate, - /// The node has finished processing for a given number of iterations + /// The node has finished processing for a given number of iterations. Finish, } +/// Context information passed to [`GeneticNode`] trait methods. Most of the data originates from the [`GeneticNodeWrapper`]. +/// +/// Contains the current generation, node ID, and simulation context. #[derive(Clone, Debug)] pub struct GeneticNodeContext { + /// The current generation number. Generations start at 0 and increment after each mutation phase. pub generation: u64, + + /// The unique identifier for the node. pub id: Uuid, + + /// The user defined context specific to the simulation, such as parameters or environment data. pub gemla_context: S, } -/// A trait used to interact with the internal state of nodes within the [`Bracket`] +/// Trait for managing the state and behavior of nodes within a [`Gemla`] simulation /// -/// [`Bracket`]: crate::bracket::Bracket +/// Implementors define how nodes are initialized, simulated, mutated, and merged asynchronously. +/// +/// # Associated Types +/// - `Context`: The type of context data passed to node methods. +/// +/// # Example +/// ```ignore +/// // Example implementation for a custom node type +/// #[async_trait] +/// impl GeneticNode for MyNode { +/// type Context = MyContext; +/// // ... +/// } +/// ``` +/// +/// [`Gemla`]: crate::Gemla #[async_trait] pub trait GeneticNode: Send { + /// Custom type that provides a shared context across different nodes and simulations. Useful if you want to manage + /// conncurrency or share data between nodes. + /// + /// # Example + /// ```ignore + /// pub struct SharedContext { + /// pub shared_semaphore: Arc, + /// pub visible_simulations: Arc, + /// } + /// ``` + /// In this example, `SharedContext` could be used to limit the number of concurrent simulations + /// and control visibility of simulations to users. type Context; - /// Initializes a new instance of a [`GeneticState`]. + /// Initializes a new instance of a node implementing [`GeneticNode`]. /// - /// # Examples - /// TODO + /// # Arguments + /// * `context` - The context for initialization, including generation and node ID. + /// + /// # Returns + /// * A boxed instance of the initialized node. + /// + /// # Example + /// ```rust + /// # use gemla::core::genetic_node::{GeneticNode, GeneticNodeContext}; + /// # use async_trait::async_trait; + /// # use serde::{Serialize, Deserialize}; + /// # use rand::prelude::*; + /// # use uuid::Uuid; + /// #[derive(Serialize, Deserialize, Debug, Clone)] + /// struct TestState { + /// population: Vec, + /// max_generations: u64, + /// } + /// + /// #[async_trait] + /// impl GeneticNode for TestState { + /// type Context = (); + /// async fn initialize(_context: GeneticNodeContext) -> Result, gemla::error::Error> { + /// let mut population = vec![]; + /// for _ in 0..5 { + /// population.push(thread_rng().gen_range(0..100)); + /// } + /// Ok(Box::new(TestState { population, max_generations: 10 })) + /// } + /// // ... + /// } + /// ``` async fn initialize(context: GeneticNodeContext) -> Result, Error>; - async fn simulate(&mut self, context: GeneticNodeContext) - -> Result; + /// Simulates a round for the node, updating its state and returning whether to continue. + /// + /// # Arguments + /// * `context` - The context for simulation, including generation and node ID. + /// + /// # Returns + /// * `Ok(true)` if the node should continue to the next phase, `Ok(false)` if finished. + /// + /// # Example + /// ```rust + /// # use gemla::core::genetic_node::{GeneticNode, GeneticNodeContext}; + /// # use async_trait::async_trait; + /// # use serde::{Serialize, Deserialize}; + /// # use rand::prelude::*; + /// # use uuid::Uuid; + /// #[derive(Serialize, Deserialize, Debug, Clone)] + /// struct TestState { + /// population: Vec, + /// max_generations: u64, + /// } + /// + /// #[async_trait] + /// impl GeneticNode for TestState { + /// type Context = (); + /// async fn simulate(&mut self, context: GeneticNodeContext) -> Result { + /// let mut rng = thread_rng(); + /// self.population = self.population.iter().map(|p| p.saturating_add(rng.gen_range(-1..2))).collect(); + /// if context.generation >= self.max_generations { + /// Ok(false) + /// } else { + /// Ok(true) + /// } + /// } + /// // ... + /// } + /// ``` + async fn simulate(&mut self, context: GeneticNodeContext) -> Result; /// Mutates members in a population and/or crossbreeds them to produce new offspring. /// - /// # Examples - /// TODO + /// # Arguments + /// * `context` - The context for mutation, including generation and node ID. + /// + /// # Returns + /// * `Ok(())` if mutation was successful. + /// * `Err(Error)` if an error occurred during mutation. + /// + /// # Example + /// ```rust + /// # use gemla::core::genetic_node::{GeneticNode, GeneticNodeContext}; + /// # use async_trait::async_trait; + /// # use serde::{Serialize, Deserialize}; + /// # use rand::prelude::*; + /// # use uuid::Uuid; + /// #[derive(Serialize, Deserialize, Debug, Clone)] + /// struct TestState { + /// population: Vec, + /// max_generations: u64, + /// } + /// + /// #[async_trait] + /// impl GeneticNode for TestState { + /// type Context = (); + /// async fn mutate(&mut self, _context: GeneticNodeContext) -> Result<(), gemla::error::Error> { + /// let mut rng = thread_rng(); + /// let mut v = self.population.clone(); + /// v.sort_unstable(); + /// v.reverse(); + /// self.population = v[0..3].to_vec(); + /// while self.population.len() < 5 { + /// let i = rng.gen_range(0..self.population.len()); + /// let j = loop { + /// let idx = rng.gen_range(0..self.population.len()); + /// if idx != i { break idx; } + /// }; + /// let mut new_ind = self.population[i]; + /// let cross = self.population[j]; + /// new_ind = (new_ind.saturating_add(cross) / 2).saturating_add(rng.gen_range(-1..2)); + /// self.population.push(new_ind); + /// } + /// Ok(()) + /// } + /// // ... + /// } + /// ``` async fn mutate(&mut self, context: GeneticNodeContext) -> Result<(), Error>; + /// Merges two nodes into a new node, using the provided context and ID. This occurs after + /// two nodes have finished simulating and the populations need to be combined. + /// + /// # Arguments + /// * `left` - The left node to merge. + /// * `right` - The right node to merge. + /// * `id` - The ID for the new merged node. + /// * `context` - The context for merging. + /// + /// # Example + /// ```rust + /// # use gemla::core::genetic_node::{GeneticNode, GeneticNodeContext}; + /// # use async_trait::async_trait; + /// # use serde::{Serialize, Deserialize}; + /// # use uuid::Uuid; + /// #[derive(Serialize, Deserialize, Debug, Clone)] + /// struct TestState { + /// population: Vec, + /// max_generations: u64, + /// } + /// + /// #[async_trait] + /// impl GeneticNode for TestState { + /// type Context = (); + /// async fn merge(left: &TestState, right: &TestState, id: &Uuid, gemla_context: Self::Context) -> Result, gemla::error::Error> { + /// let mut v = left.population.clone(); + /// v.append(&mut right.population.clone()); + /// v.sort_by(|a, b| a.partial_cmp(b).unwrap()); + /// v.reverse(); + /// v = v[..3].to_vec(); + /// let mut result = TestState { population: v, max_generations: 10 }; + /// result.mutate(GeneticNodeContext { id: *id, generation: 0, gemla_context }).await?; + /// Ok(Box::new(result)) + /// } + /// // ... + /// } + /// ``` async fn merge( left: &Self, right: &Self, @@ -63,16 +248,24 @@ pub trait GeneticNode: Send { ) -> Result, Error>; } -/// Used externally to wrap a node implementing the [`GeneticNode`] trait. Processes state transitions for the given node as -/// well as signal recovery. Transition states are given by [`GeneticState`] +/// Wrapper for a node implementing [`GeneticNode`], managing state transitions and lifecycle. +/// +/// Used externally to process state transitions and signal recovery. State transitions are managed using [`GeneticState`]. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct GeneticNodeWrapper where T: Clone, { + /// The wrapped node instance, if initialized. node: Option, + + /// The current state of the node in the simulation lifecycle. state: GeneticState, + + /// The current generation number. Generations start at 0 and increment after each mutation phase. generation: u64, + + /// The unique identifier for the node. id: Uuid, } @@ -95,12 +288,14 @@ where T: GeneticNode + Debug + Send + Clone, T::Context: Send + Sync + Clone + Debug + Serialize + DeserializeOwned + 'static + Default, { + /// Creates a new instance of [`GeneticNodeWrapper`]. Calls [`Default::default()`] of the inner type. pub fn new() -> Self { GeneticNodeWrapper:: { ..Default::default() } } + /// Creates a new instance from the given node data and ID. pub fn from(data: T, id: Uuid) -> Self { GeneticNodeWrapper { node: Some(data), @@ -110,26 +305,38 @@ where } } + /// Returns a reference to the wrapped node, if available. pub fn as_ref(&self) -> Option<&T> { self.node.as_ref() } + /// Takes the wrapped node, consuming the wrapper. pub fn take(&mut self) -> Option { self.node.take() } + /// Returns the ID of the node. pub fn id(&self) -> Uuid { self.id } + /// Returns the current generation number. pub fn generation(&self) -> u64 { self.generation } + /// Returns the current state of the node. pub fn state(&self) -> GeneticState { self.state } + /// Processes the node for the current generation, updating its state and transitioning to the next phase as needed. + /// + /// # Arguments + /// * `gemla_context` - The user-defined context for the simulation, passed to node methods. + /// + /// # Returns + /// * The updated state of the node after processing. pub async fn process_node(&mut self, gemla_context: T::Context) -> Result { let context = GeneticNodeContext { generation: self.generation, diff --git a/gemla/src/core/mod.rs b/gemla/src/core/mod.rs index d3de3f3..c11e789 100644 --- a/gemla/src/core/mod.rs +++ b/gemla/src/core/mod.rs @@ -1,5 +1,13 @@ -//! Simulates a genetic algorithm on a population in order to improve the fit score and performance. The simulations -//! are performed in a tournament bracket configuration so that populations can compete against each other. +//! This module provides the core logic for simulating genetic algorithms using a tournament bracket structure. +//! +//! - Defines the [`Gemla`] struct, which manages the simulation of populations as a tree of nodes. +//! - Nodes implement the [`GeneticNode`] trait and are managed using [`GeneticNodeWrapper`]. +//! - Simulations are performed in a bracket configuration, allowing populations to compete and evolve. +//! - Includes configuration, tree management, and asynchronous simulation logic. +//! +//! [`Gemla`]: crate::core::Gemla +//! [`GeneticNode`]: crate::core::genetic_node::GeneticNode +//! [`GeneticNodeWrapper`]: crate::core::genetic_node::GeneticNodeWrapper pub mod genetic_node; @@ -19,58 +27,41 @@ use uuid::Uuid; type SimulationTree = Box>>; -/// Provides configuration options for managing a [`Gemla`] object as it executes. +/// Configuration options for managing a [`Gemla`] simulation. /// -/// # Examples -/// ```rust,ignore -/// #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] -/// struct TestState { -/// pub score: f64, -/// } -/// -/// impl genetic_node::GeneticNode for TestState { -/// fn simulate(&mut self) -> Result<(), Error> { -/// self.score += 1.0; -/// Ok(()) -/// } -/// -/// fn mutate(&mut self) -> Result<(), Error> { -/// Ok(()) -/// } -/// -/// fn initialize() -> Result, Error> { -/// Ok(Box::new(TestState { score: 0.0 })) -/// } -/// -/// fn merge(left: &TestState, right: &TestState) -> Result, Error> { -/// Ok(Box::new(if left.score > right.score { -/// left.clone() -/// } else { -/// right.clone() -/// })) -/// } -/// } -/// -/// fn main() { -/// -/// } -/// ``` +/// - `overwrite`: If true, existing simulation data will be overwritten when initializing a new simulation. #[derive(Serialize, Deserialize, Copy, Clone)] pub struct GemlaConfig { + /// If true, existing simulation data will be overwritten when initializing a new simulation. pub overwrite: bool, } -/// Creates a tournament style bracket for simulating and evaluating nodes of type `T` implementing [`GeneticNode`]. -/// These nodes are built upwards as a balanced binary tree starting from the bottom. This results in `Bracket` building -/// a separate tree of the same height then merging trees together. Evaluating populations between nodes and taking the strongest -/// individuals. +/// Manages a tournament-style bracket for simulating and evaluating nodes of type `T` implementing [`GeneticNode`]. /// -/// [`GeneticNode`]: genetic_node::GeneticNode +/// Nodes are organized as an unbalanced binary tree, and the simulation proceeds by processing nodes, merging results, +/// and evolving populations. The simulation is asynchronous and supports concurrent node processing. +/// +/// # Type Parameters +/// - `T`: The node type, which must implement [`GeneticNode`], serialization, Debug, Send, and Clone traits. +/// +/// # Fields +/// - `data`: Stores the simulation tree, configuration, and context. +/// - `threads`: Tracks asynchronous tasks for node processing. +/// +/// # Example +/// ```ignore +/// let config = GemlaConfig { overwrite: true }; +/// let mut gemla = Gemla::::new(path, config, DataFormat::Json).await?; +/// gemla.simulate(5).await?; +/// ``` +/// +/// [`GeneticNode`]: crate::core::genetic_node::GeneticNode pub struct Gemla where T: GeneticNode + Serialize + DeserializeOwned + Debug + Send + Clone, T::Context: Send + Sync + Clone + Debug + Serialize + DeserializeOwned + 'static + Default, { + /// The simulation data, including the tree, configuration, and context. pub data: FileLinked<(Option>, GemlaConfig, T::Context)>, threads: HashMap, Error>>>, } @@ -80,6 +71,16 @@ where T: GeneticNode + Serialize + DeserializeOwned + Debug + Send + Sync + Clone, T::Context: Send + Sync + Clone + Debug + Serialize + DeserializeOwned + 'static + Default, { + /// Creates a new [`Gemla`] instance, initializing or loading the simulation data from the specified path. + /// + /// # Arguments + /// - `path`: The file system path to load or create the simulation data. + /// - `config`: Configuration options for the simulation. + /// - `data_format`: The format of the data (e.g., JSON, binary). + /// + /// # Returns + /// - `Ok(Self)`: A new [`Gemla`] instance. + /// - `Err(Error)`: An error occurred during initialization or loading. pub async fn new( path: &Path, config: GemlaConfig, @@ -107,10 +108,19 @@ where } } + /// Returns a read-only reference to the simulation tree, configuration, and context. pub fn tree_ref(&self) -> Arc>, GemlaConfig, T::Context)>> { self.data.readonly().clone() } + /// Simulates the genetic algorithm for the specified number of steps, processing and evolving the population. + /// + /// # Arguments + /// - `steps`: The number of simulation steps to perform. A simulation step increases the height of the tree by one and continues processing until all nodes are completed. + /// + /// # Returns + /// - `Ok(())`: Simulation completed successfully. + /// - `Err(Error)`: An error occurred during simulation. pub async fn simulate(&mut self, steps: u64) -> Result<(), Error> { let tree_completed = { // Only increase height if the tree is uninitialized or completed diff --git a/gemla/src/error.rs b/gemla/src/error.rs index 3b8b6a7..00c02ce 100644 --- a/gemla/src/error.rs +++ b/gemla/src/error.rs @@ -1,16 +1,30 @@ +//! Error handling utilities and error type for the Gemla crate. +//! +//! This module defines a unified [`Error`] enum for representing errors that can occur throughout the crate, +//! including I/O errors, errors from the `file_linked` crate, and general errors using `anyhow`. +//! +//! It also provides conversion implementations and a helper function for logging errors. + use log::error; use thiserror::Error; +/// The main error type for the Gemla crate. +/// +/// This enum wraps errors from dependencies and provides a unified error type for use throughout the crate. #[derive(Error, Debug)] pub enum Error { + /// An error originating from the `file_linked` crate. #[error(transparent)] FileLinked(file_linked::error::Error), + /// An I/O error. #[error(transparent)] IO(std::io::Error), + /// Any other error, wrapped using `anyhow`. #[error(transparent)] Other(#[from] anyhow::Error), } +/// Converts a `file_linked::error::Error` into a unified [`Error`]. impl From for Error { fn from(error: file_linked::error::Error) -> Error { match error { @@ -20,12 +34,22 @@ impl From for Error { } } +/// Converts a standard I/O error into a unified [`Error`]. impl From for Error { fn from(error: std::io::Error) -> Error { Error::IO(error) } } +/// Logs an error using the `log` crate and returns the error. +/// +/// This helper is useful for logging errors at the point of handling while still propagating them. +/// +/// # Example +/// ```rust +/// use gemla::error::{log_error, Error}; +/// let result: Result<(), Error> = log_error(Err(Error::Other(anyhow::anyhow!("Some error")))); +/// ``` pub fn log_error(result: Result) -> Result { result.map_err(|e| { error!("{}", e); diff --git a/gemla/src/lib.rs b/gemla/src/lib.rs index d69f277..18313b9 100644 --- a/gemla/src/lib.rs +++ b/gemla/src/lib.rs @@ -1,3 +1,60 @@ +//! # Gemla +//! +//! **Gemla** is a Rust library for simulating and evolving populations using genetic algorithms. It provides a flexible framework for evolutionary computation, supporting custom node types, tournament bracket simulations, and persistent state management. +//! +//! ## Features +//! - Tournament-style genetic algorithm simulation evaluating populations via nodes +//! - Asynchronous and concurrent simulation using Tokio +//! - Customizable node logic via the [`GeneticNode`] trait +//! - Persistent, file-backed simulation state with the `file_linked` crate +//! - Utilities for binary tree management +//! +//! ## Modules +//! - [`tree`]: Defines an unbalanced binary tree structure and macros for tree construction. +//! - [`core`]: Contains the main simulation logic, including the [`Gemla`] struct, configuration, and node management. +//! - [`error`]: Provides unified error types and logging utilities for the crate. +//! +//! ## Example +//! ```rust,ignore +//! # use gemla::core::{Gemla, GemlaConfig}; +//! # use gemla::core::genetic_node::{GeneticNode, GeneticNodeContext}; +//! # use file_linked::constants::data_format::DataFormat; +//! # use std::path::PathBuf; +//! +//! #[derive(Clone, Debug)] +//! struct MyNode { /* ... */ } +//! // Implement GeneticNode for MyNode... +//! +//! #[tokio::main] +//! async fn main() { +//! let mut gemla = Gemla::::new( +//! &PathBuf::from("state.json"), +//! GemlaConfig { overwrite: true }, +//! DataFormat::Json, +//! ).await.unwrap(); +//! // Grows simulation tree by 5 levels +//! gemla.simulate(5).await.unwrap(); +//! } +//! ``` +//! +//! ## Crate Organization +//! - All core simulation logic is in [`core`]. +//! - Tree structures and macros are in [`tree`]. +//! - Error types and helpers are in [`error`]. +//! +//! ## Getting Started +//! 1. Define your node type and implement the [`GeneticNode`] trait. +//! 2. Create a [`Gemla`] instance and run simulations. +//! 3. Use the provided error handling and tree utilities as needed. +//! +//! [`Gemla`]: crate::core::Gemla +//! [`GeneticNode`]: crate::core::genetic_node::GeneticNode +//! [`tree`]: crate::tree +//! [`core`]: crate::core +//! [`error`]: crate::error + +#![warn(missing_docs)] + #[macro_use] pub mod tree; pub mod core; diff --git a/gemla/src/tree/mod.rs b/gemla/src/tree/mod.rs index 1388aaf..841ea54 100644 --- a/gemla/src/tree/mod.rs +++ b/gemla/src/tree/mod.rs @@ -38,8 +38,13 @@ use std::cmp::max; /// ``` #[derive(Default, Serialize, Deserialize, PartialEq, Debug)] pub struct Tree { + /// The value stored in this node pub val: T, + + /// The left child node, if any pub left: Option>>, + + /// The right child node, if any pub right: Option>>, }