bounded_graph 0.3.0

A thin newtype wrapper for `petgraph` to assist in the creation of graphs with restrictions on their edges
Documentation
//! A bounded graph implementation that enforces edge count constraints on nodes.
//!
//! This crate provides a wrapper around [`petgraph::Graph`] and [`petgraph::stable_graph::StableGraph`] that ensures nodes cannot exceed
//! their defined edge limits when adding connections. All edge addition operations check the
//! bounds before modifying the graph.
//!
//! # Features
//!
//! - **Flexible constraint system**: Define custom edge constraints via the [`BoundedNode`] trait
//! - **Simple edge bounds**: Use [`EdgeBounds`] + [`SimpleEdgeBounds`] for basic numeric limits
//! - **Convenience types**: Use [`FixedEdgeCount`] or [`SymmetricFixedEdgeCount`] for quick node creation with fixed limits
//! - **Petgraph compatible**: Implements most petgraph traits for seamless integration
//! - **Acyclic graphs**: Specialized [`BoundedAcyclicGraph`] variants that combines edge bounds enforcement from this crate with acyclicity enforcement built into petgraph
//! - **Type-safe error handling**: Returns [`BoundedGraphError`] or [`BoundedAcyclicGraphError`] instead of panicking
//!
//! # Quick Start Example
//!
//! [`SymmetricFixedEdgeCount`] provides a simple way to create nodes with the same maximum
//! number of incoming and outgoing edges:
//! ```
//! use bounded_graph::{BoundedGraph, SymmetricFixedEdgeCount};
//!
//! // Create a graph where each node can have at most 2 edges (both incoming and outgoing)
//! let mut graph = BoundedGraph::<SymmetricFixedEdgeCount<2>, ()>::new();
//! let n1 = graph.add_node(SymmetricFixedEdgeCount::empty());
//! let n2 = graph.add_node(SymmetricFixedEdgeCount::empty());
//! let n3 = graph.add_node(SymmetricFixedEdgeCount::empty());
//!
//! // These succeed
//! assert!(graph.add_edge(n1, n2, ()).is_ok());
//! assert!(graph.add_edge(n1, n3, ()).is_ok());
//!
//! // This fails - n1 already has 2 outgoing edges
//! let n4 = graph.add_node(SymmetricFixedEdgeCount::empty());
//! assert!(graph.add_edge(n1, n4, ()).is_err());
//! ```
//!
//! # Asymmetric Edge Limits
//!
//! [`FixedEdgeCount`] supports different limits for incoming and outgoing edges:
//!
//! ```
//! use bounded_graph::{BoundedGraph, FixedEdgeCount};
//!
//! // Create nodes with 2 max incoming, 5 max outgoing edges
//! let mut graph = BoundedGraph::<FixedEdgeCount<2, 5>, ()>::new();
//! let hub = graph.add_node(FixedEdgeCount::empty());
//! let spoke1 = graph.add_node(FixedEdgeCount::empty());
//! let spoke2 = graph.add_node(FixedEdgeCount::empty());
//!
//! // Hub can send to many spokes
//! assert!(graph.add_edge(hub, spoke1, ()).is_ok());
//! assert!(graph.add_edge(hub, spoke2, ()).is_ok());
//! // ... up to 5 outgoing edges
//! ```
//!
//! # Custom Edge Bounds
//!
//! For more control, implement [`EdgeBounds`] directly:
//!
//! ```
//! use bounded_graph::{BoundedGraph, EdgeBounds, SimpleEdgeBounds};
//! use petgraph::graph::DefaultIx;
//!
//! #[derive(Default)]
//! struct TwiceLimitedNode {
//!     max_in: usize,
//!     max_out: usize,
//! }
//!
//! impl EdgeBounds for TwiceLimitedNode {
//!     fn max_incoming_edges(&self) -> DefaultIx {
//!         2 * self.max_in as DefaultIx
//!     }
//!
//!     fn max_outgoing_edges(&self) -> DefaultIx {
//!         2 * self.max_out as DefaultIx
//!     }
//! }
//!
//! impl SimpleEdgeBounds for TwiceLimitedNode {}
//!
//! let mut graph = BoundedGraph::<TwiceLimitedNode, ()>::new();
//! let n1 = graph.add_node(TwiceLimitedNode { max_in: 2, max_out: 2 });
//! let n2 = graph.add_node(TwiceLimitedNode { max_in: 2, max_out: 2 });
//!
//! // This will succeed
//! assert!(graph.add_edge(n1, n2, ()).is_ok());
//! ```
//!
//! For complete control with custom logic, implement [`BoundedNode`] directly:
//!
//! ```
//! use bounded_graph::{BoundedGraph, BoundedNode};
//! use petgraph::Direction;
//!
//! #[derive(Clone, PartialEq)]
//! enum Color { Red, Blue, Green }
//!
//! struct ColoredNode {
//!     color: Color,
//! }
//!
//! impl BoundedNode for ColoredNode {
//!     fn can_add_edge(&self, _dir: Direction, _count: usize, other: &Self) -> bool {
//!         // Only allow edges between nodes of the same color
//!         self.color == other.color
//!     }
//! }
//!
//! let mut graph = BoundedGraph::<ColoredNode, ()>::new();
//! let red1 = graph.add_node(ColoredNode { color: Color::Red });
//! let red2 = graph.add_node(ColoredNode { color: Color::Red });
//! let blue = graph.add_node(ColoredNode { color: Color::Blue });
//!
//! // Same color - succeeds
//! assert!(graph.add_edge(red1, red2, ()).is_ok());
//! // Different colors - fails
//! assert!(graph.add_edge(red1, blue, ()).is_err());
//! ```

mod acyclic;
mod error;
mod graph;
mod impls;
mod nodes;
mod traits;

#[cfg(test)]
mod tests;

// Re-export public API
pub use acyclic::{BoundedAcyclicDiGraph, BoundedAcyclicGraph, BoundedAcyclicStableDiGraph};
pub use error::{BoundedAcyclicGraphError, BoundedGraphError};
pub use graph::{
    BoundedDiGraph, BoundedGraph, BoundedStableDiGraph, BoundedStableUnGraph, BoundedUnGraph,
};
pub use nodes::{FixedEdgeCount, SymmetricFixedEdgeCount};
pub use traits::{BoundedNode, EdgeBounds, ImmutableEdgeBounds, SimpleEdgeBounds};

// Re-export commonly used petgraph types for ergonomics
pub use petgraph::graph::{DefaultIx, EdgeIndex, NodeIndex};
pub use petgraph::{Directed, Direction, Undirected};