Filling out documentation comments
This commit is contained in:
parent
ec68d46261
commit
38d9ea7bce
5 changed files with 364 additions and 61 deletions
|
@ -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<S> {
|
||||
/// 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<Semaphore>,
|
||||
/// pub visible_simulations: Arc<Semaphore>,
|
||||
/// }
|
||||
/// ```
|
||||
/// 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<i64>,
|
||||
/// max_generations: u64,
|
||||
/// }
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl GeneticNode for TestState {
|
||||
/// type Context = ();
|
||||
/// async fn initialize(_context: GeneticNodeContext<Self::Context>) -> Result<Box<Self>, 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<Self::Context>) -> Result<Box<Self>, Error>;
|
||||
|
||||
async fn simulate(&mut self, context: GeneticNodeContext<Self::Context>)
|
||||
-> Result<bool, Error>;
|
||||
/// 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<i64>,
|
||||
/// max_generations: u64,
|
||||
/// }
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl GeneticNode for TestState {
|
||||
/// type Context = ();
|
||||
/// async fn simulate(&mut self, context: GeneticNodeContext<Self::Context>) -> Result<bool, gemla::error::Error> {
|
||||
/// 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<Self::Context>) -> Result<bool, Error>;
|
||||
|
||||
/// 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<i64>,
|
||||
/// max_generations: u64,
|
||||
/// }
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl GeneticNode for TestState {
|
||||
/// type Context = ();
|
||||
/// async fn mutate(&mut self, _context: GeneticNodeContext<Self::Context>) -> 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<Self::Context>) -> 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<i64>,
|
||||
/// 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<Box<TestState>, 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<Box<Self>, 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<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
/// The wrapped node instance, if initialized.
|
||||
node: Option<T>,
|
||||
|
||||
/// 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::<T> {
|
||||
..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<T> {
|
||||
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<GeneticState, Error> {
|
||||
let context = GeneticNodeContext {
|
||||
generation: self.generation,
|
||||
|
|
|
@ -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<T> = Box<Tree<GeneticNodeWrapper<T>>>;
|
||||
|
||||
/// 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<Box<TestState>, Error> {
|
||||
/// Ok(Box::new(TestState { score: 0.0 }))
|
||||
/// }
|
||||
///
|
||||
/// fn merge(left: &TestState, right: &TestState) -> Result<Box<TestState>, 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::<MyNodeType>::new(path, config, DataFormat::Json).await?;
|
||||
/// gemla.simulate(5).await?;
|
||||
/// ```
|
||||
///
|
||||
/// [`GeneticNode`]: crate::core::genetic_node::GeneticNode
|
||||
pub struct Gemla<T>
|
||||
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<SimulationTree<T>>, GemlaConfig, T::Context)>,
|
||||
threads: HashMap<Uuid, JoinHandle<Result<GeneticNodeWrapper<T>, 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<RwLock<(Option<SimulationTree<T>>, 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
|
||||
|
|
|
@ -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<file_linked::error::Error> for Error {
|
||||
fn from(error: file_linked::error::Error) -> Error {
|
||||
match error {
|
||||
|
@ -20,12 +34,22 @@ impl From<file_linked::error::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
/// Converts a standard I/O error into a unified [`Error`].
|
||||
impl From<std::io::Error> 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<T>(result: Result<T, Error>) -> Result<T, Error> {
|
||||
result.map_err(|e| {
|
||||
error!("{}", e);
|
||||
|
|
|
@ -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::<MyNode>::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;
|
||||
|
|
|
@ -38,8 +38,13 @@ use std::cmp::max;
|
|||
/// ```
|
||||
#[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
|
||||
pub struct Tree<T> {
|
||||
/// The value stored in this node
|
||||
pub val: T,
|
||||
|
||||
/// The left child node, if any
|
||||
pub left: Option<Box<Tree<T>>>,
|
||||
|
||||
/// The right child node, if any
|
||||
pub right: Option<Box<Tree<T>>>,
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue