Skip to main content

graphos_adapters/plugins/algorithms/
traits.rs

1//! Core traits and types for graph algorithms.
2//!
3//! This module provides the fundamental building blocks for implementing
4//! high-performance graph algorithms, inspired by rustworkx patterns.
5
6use graphos_common::types::{EdgeId, NodeId, Value};
7use graphos_common::utils::error::Result;
8use graphos_core::graph::lpg::LpgStore;
9use std::cmp::Ordering;
10use std::collections::HashMap;
11
12use super::super::{AlgorithmResult, ParameterDef, Parameters};
13
14// ============================================================================
15// Control Flow
16// ============================================================================
17
18/// Control flow for algorithm visitors.
19///
20/// Returned by visitor callbacks to control traversal behavior.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Control<B = ()> {
23    /// Continue the algorithm normally.
24    Continue,
25    /// Stop the algorithm and return the break value.
26    Break(B),
27    /// Skip the current subtree (for traversals) but continue the algorithm.
28    Prune,
29}
30
31impl<B> Control<B> {
32    /// Returns `true` if this is `Continue`.
33    #[inline]
34    pub fn is_continue(&self) -> bool {
35        matches!(self, Control::Continue)
36    }
37
38    /// Returns `true` if this is `Break`.
39    #[inline]
40    pub fn is_break(&self) -> bool {
41        matches!(self, Control::Break(_))
42    }
43
44    /// Returns `true` if this is `Prune`.
45    #[inline]
46    pub fn is_prune(&self) -> bool {
47        matches!(self, Control::Prune)
48    }
49
50    /// Converts to an option containing the break value.
51    pub fn break_value(self) -> Option<B> {
52        match self {
53            Control::Break(b) => Some(b),
54            _ => None,
55        }
56    }
57}
58
59impl<B> Default for Control<B> {
60    fn default() -> Self {
61        Control::Continue
62    }
63}
64
65// ============================================================================
66// Traversal Events
67// ============================================================================
68
69/// Events emitted during graph traversal (BFS/DFS).
70///
71/// These events follow the visitor pattern, allowing algorithms to
72/// react to different stages of the traversal.
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum TraversalEvent {
75    /// A node is discovered for the first time.
76    Discover(NodeId),
77
78    /// A tree edge is traversed (edge to an undiscovered node).
79    TreeEdge {
80        /// The source node of the edge.
81        source: NodeId,
82        /// The target node of the edge.
83        target: NodeId,
84        /// The edge ID.
85        edge: EdgeId,
86    },
87
88    /// A non-tree edge is traversed (edge to an already-discovered node).
89    /// In BFS, this is a cross edge. In DFS, this could be a back edge or cross edge.
90    NonTreeEdge {
91        /// The source node of the edge.
92        source: NodeId,
93        /// The target node of the edge.
94        target: NodeId,
95        /// The edge ID.
96        edge: EdgeId,
97    },
98
99    /// A back edge is traversed (DFS only - edge to an ancestor).
100    BackEdge {
101        /// The source node of the edge.
102        source: NodeId,
103        /// The target node of the edge.
104        target: NodeId,
105        /// The edge ID.
106        edge: EdgeId,
107    },
108
109    /// Processing of a node is complete.
110    Finish(NodeId),
111}
112
113impl TraversalEvent {
114    /// Returns the source node of this event, if applicable.
115    pub fn source(&self) -> Option<NodeId> {
116        match self {
117            TraversalEvent::Discover(n) | TraversalEvent::Finish(n) => Some(*n),
118            TraversalEvent::TreeEdge { source, .. }
119            | TraversalEvent::NonTreeEdge { source, .. }
120            | TraversalEvent::BackEdge { source, .. } => Some(*source),
121        }
122    }
123
124    /// Returns the target node of this event, if applicable.
125    pub fn target(&self) -> Option<NodeId> {
126        match self {
127            TraversalEvent::Discover(n) | TraversalEvent::Finish(n) => Some(*n),
128            TraversalEvent::TreeEdge { target, .. }
129            | TraversalEvent::NonTreeEdge { target, .. }
130            | TraversalEvent::BackEdge { target, .. } => Some(*target),
131        }
132    }
133}
134
135// ============================================================================
136// Priority Queue Support
137// ============================================================================
138
139/// Wrapper for priority queue ordering (min-heap behavior).
140///
141/// Rust's `BinaryHeap` is a max-heap, so we reverse the ordering
142/// to get min-heap behavior for Dijkstra and A*.
143#[derive(Clone, Copy, Debug)]
144pub struct MinScored<K, T>(pub K, pub T);
145
146impl<K, T> MinScored<K, T> {
147    /// Creates a new `MinScored` with the given score and value.
148    pub fn new(score: K, value: T) -> Self {
149        MinScored(score, value)
150    }
151
152    /// Returns the score.
153    pub fn score(&self) -> &K {
154        &self.0
155    }
156
157    /// Returns the value.
158    pub fn value(&self) -> &T {
159        &self.1
160    }
161
162    /// Consumes self and returns the value.
163    pub fn into_value(self) -> T {
164        self.1
165    }
166}
167
168impl<K: PartialOrd, T> PartialEq for MinScored<K, T> {
169    fn eq(&self, other: &Self) -> bool {
170        self.0 == other.0
171    }
172}
173
174impl<K: PartialOrd, T> Eq for MinScored<K, T> {}
175
176impl<K: PartialOrd, T> PartialOrd for MinScored<K, T> {
177    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
178        // Reverse ordering for min-heap
179        other.0.partial_cmp(&self.0)
180    }
181}
182
183impl<K: PartialOrd, T> Ord for MinScored<K, T> {
184    fn cmp(&self, other: &Self) -> Ordering {
185        self.partial_cmp(other).unwrap_or(Ordering::Equal)
186    }
187}
188
189// ============================================================================
190// Graph Algorithm Traits
191// ============================================================================
192
193/// A graph algorithm that can be executed on an LPG store.
194///
195/// This trait extends the base `Algorithm` trait with graph-specific
196/// functionality, providing direct access to the graph store.
197pub trait GraphAlgorithm: Send + Sync {
198    /// Returns the name of the algorithm.
199    fn name(&self) -> &str;
200
201    /// Returns a description of the algorithm.
202    fn description(&self) -> &str;
203
204    /// Returns the parameter definitions for this algorithm.
205    fn parameters(&self) -> &[ParameterDef];
206
207    /// Executes the algorithm on the given graph store.
208    fn execute(&self, store: &LpgStore, params: &Parameters) -> Result<AlgorithmResult>;
209}
210
211/// A graph algorithm that supports parallel execution.
212///
213/// Algorithms implementing this trait can automatically switch between
214/// sequential and parallel execution based on graph size.
215pub trait ParallelGraphAlgorithm: GraphAlgorithm {
216    /// Minimum node count to trigger parallelization.
217    ///
218    /// Below this threshold, sequential execution is used to avoid
219    /// parallelization overhead.
220    fn parallel_threshold(&self) -> usize {
221        50
222    }
223
224    /// Executes the algorithm with explicit parallelism control.
225    fn execute_parallel(
226        &self,
227        store: &LpgStore,
228        params: &Parameters,
229        num_threads: usize,
230    ) -> Result<AlgorithmResult>;
231}
232
233// ============================================================================
234// Distance Map
235// ============================================================================
236
237/// A map from nodes to distances/costs.
238///
239/// This trait abstracts over different distance map implementations,
240/// allowing algorithms to work with various storage strategies.
241pub trait DistanceMap<K> {
242    /// Gets the distance to a node, if known.
243    fn get(&self, node: NodeId) -> Option<&K>;
244
245    /// Sets the distance to a node.
246    fn insert(&mut self, node: NodeId, dist: K);
247
248    /// Returns `true` if the node has a recorded distance.
249    fn contains(&self, node: NodeId) -> bool {
250        self.get(node).is_some()
251    }
252}
253
254impl<K> DistanceMap<K> for HashMap<NodeId, K> {
255    fn get(&self, node: NodeId) -> Option<&K> {
256        HashMap::get(self, &node)
257    }
258
259    fn insert(&mut self, node: NodeId, dist: K) {
260        HashMap::insert(self, node, dist);
261    }
262}
263
264// ============================================================================
265// Result Builders
266// ============================================================================
267
268/// Helper for building algorithm results with node ID and value columns.
269pub struct NodeValueResultBuilder {
270    node_ids: Vec<u64>,
271    values: Vec<Value>,
272    value_column_name: String,
273}
274
275impl NodeValueResultBuilder {
276    /// Creates a new builder with the given value column name.
277    #[allow(dead_code)]
278    pub fn new(value_column_name: impl Into<String>) -> Self {
279        Self {
280            node_ids: Vec::new(),
281            values: Vec::new(),
282            value_column_name: value_column_name.into(),
283        }
284    }
285
286    /// Creates a new builder with pre-allocated capacity.
287    pub fn with_capacity(value_column_name: impl Into<String>, capacity: usize) -> Self {
288        Self {
289            node_ids: Vec::with_capacity(capacity),
290            values: Vec::with_capacity(capacity),
291            value_column_name: value_column_name.into(),
292        }
293    }
294
295    /// Adds a node ID and value pair.
296    pub fn push(&mut self, node: NodeId, value: impl Into<Value>) {
297        self.node_ids.push(node.0);
298        self.values.push(value.into());
299    }
300
301    /// Builds the algorithm result.
302    pub fn build(self) -> AlgorithmResult {
303        let mut result = AlgorithmResult::new(vec!["node_id".to_string(), self.value_column_name]);
304        for (node_id, value) in self.node_ids.into_iter().zip(self.values) {
305            result.add_row(vec![Value::Int64(node_id as i64), value]);
306        }
307        result
308    }
309}
310
311/// Helper for building algorithm results with component information.
312pub struct ComponentResultBuilder {
313    node_ids: Vec<u64>,
314    component_ids: Vec<u64>,
315}
316
317impl ComponentResultBuilder {
318    /// Creates a new builder.
319    pub fn new() -> Self {
320        Self {
321            node_ids: Vec::new(),
322            component_ids: Vec::new(),
323        }
324    }
325
326    /// Creates a new builder with pre-allocated capacity.
327    pub fn with_capacity(capacity: usize) -> Self {
328        Self {
329            node_ids: Vec::with_capacity(capacity),
330            component_ids: Vec::with_capacity(capacity),
331        }
332    }
333
334    /// Adds a node ID and component ID pair.
335    pub fn push(&mut self, node: NodeId, component: u64) {
336        self.node_ids.push(node.0);
337        self.component_ids.push(component);
338    }
339
340    /// Builds the algorithm result.
341    pub fn build(self) -> AlgorithmResult {
342        let mut result =
343            AlgorithmResult::new(vec!["node_id".to_string(), "component_id".to_string()]);
344        for (node_id, component_id) in self.node_ids.into_iter().zip(self.component_ids) {
345            result.add_row(vec![
346                Value::Int64(node_id as i64),
347                Value::Int64(component_id as i64),
348            ]);
349        }
350        result
351    }
352}
353
354impl Default for ComponentResultBuilder {
355    fn default() -> Self {
356        Self::new()
357    }
358}