Skip to main content

grafeo_adapters/plugins/algorithms/
traversal.rs

1//! Graph traversal algorithms: BFS and DFS.
2//!
3//! These algorithms use the visitor pattern to allow flexible customization
4//! of traversal behavior, including early termination and edge filtering.
5
6use std::collections::VecDeque;
7use std::sync::OnceLock;
8
9use grafeo_common::types::{NodeId, Value};
10use grafeo_common::utils::error::Result;
11use grafeo_common::utils::hash::{FxHashMap, FxHashSet};
12use grafeo_core::graph::Direction;
13use grafeo_core::graph::lpg::LpgStore;
14
15use super::super::{AlgorithmResult, ParameterDef, ParameterType, Parameters};
16use super::traits::{Control, GraphAlgorithm, NodeValueResultBuilder, TraversalEvent};
17
18// ============================================================================
19// BFS Implementation
20// ============================================================================
21
22/// Performs breadth-first search from a starting node.
23///
24/// Returns the set of visited nodes in BFS order.
25///
26/// # Arguments
27///
28/// * `store` - The graph store to traverse
29/// * `start` - The starting node ID
30///
31/// # Returns
32///
33/// A vector of node IDs in the order they were discovered.
34pub fn bfs(store: &LpgStore, start: NodeId) -> Vec<NodeId> {
35    let mut visited = Vec::new();
36    bfs_with_visitor(store, start, |event| -> Control<()> {
37        if let TraversalEvent::Discover(node) = event {
38            visited.push(node);
39        }
40        Control::Continue
41    });
42    visited
43}
44
45/// Performs breadth-first search with a visitor callback.
46///
47/// The visitor is called for each traversal event, allowing custom
48/// behavior such as early termination or path recording.
49///
50/// # Arguments
51///
52/// * `store` - The graph store to traverse
53/// * `start` - The starting node ID
54/// * `visitor` - Callback function receiving traversal events
55///
56/// # Returns
57///
58/// `Some(B)` if the visitor returned `Control::Break(B)`, otherwise `None`.
59pub fn bfs_with_visitor<B, F>(store: &LpgStore, start: NodeId, mut visitor: F) -> Option<B>
60where
61    F: FnMut(TraversalEvent) -> Control<B>,
62{
63    let mut discovered: FxHashSet<NodeId> = FxHashSet::default();
64    let mut queue: VecDeque<NodeId> = VecDeque::new();
65
66    // Check if start node exists
67    store.get_node(start)?;
68
69    // Discover the start node
70    discovered.insert(start);
71    queue.push_back(start);
72
73    match visitor(TraversalEvent::Discover(start)) {
74        Control::Break(b) => return Some(b),
75        Control::Prune => {
76            // Prune means don't explore neighbors, but we still finish the node
77            match visitor(TraversalEvent::Finish(start)) {
78                Control::Break(b) => return Some(b),
79                _ => return None,
80            }
81        }
82        Control::Continue => {}
83    }
84
85    while let Some(node) = queue.pop_front() {
86        // Iterate over outgoing edges
87        for (neighbor, edge_id) in store.edges_from(node, Direction::Outgoing) {
88            if discovered.insert(neighbor) {
89                // Tree edge - neighbor not yet discovered
90                match visitor(TraversalEvent::TreeEdge {
91                    source: node,
92                    target: neighbor,
93                    edge: edge_id,
94                }) {
95                    Control::Break(b) => return Some(b),
96                    Control::Prune => continue, // Don't add to queue
97                    Control::Continue => {}
98                }
99
100                match visitor(TraversalEvent::Discover(neighbor)) {
101                    Control::Break(b) => return Some(b),
102                    Control::Prune => continue, // Don't explore neighbors
103                    Control::Continue => {}
104                }
105
106                queue.push_back(neighbor);
107            } else {
108                // Non-tree edge - neighbor already discovered
109                match visitor(TraversalEvent::NonTreeEdge {
110                    source: node,
111                    target: neighbor,
112                    edge: edge_id,
113                }) {
114                    Control::Break(b) => return Some(b),
115                    _ => {}
116                }
117            }
118        }
119
120        // Node processing complete
121        match visitor(TraversalEvent::Finish(node)) {
122            Control::Break(b) => return Some(b),
123            _ => {}
124        }
125    }
126
127    None
128}
129
130/// BFS layers - returns nodes grouped by their distance from the start.
131///
132/// # Arguments
133///
134/// * `store` - The graph store to traverse
135/// * `start` - The starting node ID
136///
137/// # Returns
138///
139/// A vector of vectors, where `result[i]` contains all nodes at distance `i` from start.
140pub fn bfs_layers(store: &LpgStore, start: NodeId) -> Vec<Vec<NodeId>> {
141    let mut layers: Vec<Vec<NodeId>> = Vec::new();
142    let mut discovered: FxHashSet<NodeId> = FxHashSet::default();
143    let mut current_layer: Vec<NodeId> = Vec::new();
144    let mut next_layer: Vec<NodeId> = Vec::new();
145
146    if store.get_node(start).is_none() {
147        return layers;
148    }
149
150    discovered.insert(start);
151    current_layer.push(start);
152
153    while !current_layer.is_empty() {
154        layers.push(current_layer.clone());
155
156        for &node in &current_layer {
157            for (neighbor, _) in store.edges_from(node, Direction::Outgoing) {
158                if discovered.insert(neighbor) {
159                    next_layer.push(neighbor);
160                }
161            }
162        }
163
164        current_layer.clear();
165        std::mem::swap(&mut current_layer, &mut next_layer);
166    }
167
168    layers
169}
170
171// ============================================================================
172// DFS Implementation
173// ============================================================================
174
175/// Node state during DFS traversal.
176#[derive(Clone, Copy, PartialEq, Eq)]
177enum NodeColor {
178    /// Not yet discovered
179    White,
180    /// Discovered but not finished (on stack)
181    Gray,
182    /// Finished processing
183    Black,
184}
185
186/// Performs depth-first search from a starting node.
187///
188/// Returns nodes in the order they were finished (post-order).
189///
190/// # Arguments
191///
192/// * `store` - The graph store to traverse
193/// * `start` - The starting node ID
194///
195/// # Returns
196///
197/// A vector of node IDs in post-order (finished order).
198pub fn dfs(store: &LpgStore, start: NodeId) -> Vec<NodeId> {
199    let mut finished = Vec::new();
200    dfs_with_visitor(store, start, |event| -> Control<()> {
201        if let TraversalEvent::Finish(node) = event {
202            finished.push(node);
203        }
204        Control::Continue
205    });
206    finished
207}
208
209/// Performs depth-first search with a visitor callback.
210///
211/// Uses an explicit stack to avoid stack overflow on deep graphs.
212///
213/// # Arguments
214///
215/// * `store` - The graph store to traverse
216/// * `start` - The starting node ID
217/// * `visitor` - Callback function receiving traversal events
218///
219/// # Returns
220///
221/// `Some(B)` if the visitor returned `Control::Break(B)`, otherwise `None`.
222pub fn dfs_with_visitor<B, F>(store: &LpgStore, start: NodeId, mut visitor: F) -> Option<B>
223where
224    F: FnMut(TraversalEvent) -> Control<B>,
225{
226    let mut color: FxHashMap<NodeId, NodeColor> = FxHashMap::default();
227
228    // Stack entries: (node, edge_iterator_index, is_first_visit)
229    // We use indices to track progress through neighbors
230    let mut stack: Vec<(NodeId, Vec<(NodeId, grafeo_common::types::EdgeId)>, usize)> = Vec::new();
231
232    // Check if start node exists
233    store.get_node(start)?;
234
235    // Discover start node
236    color.insert(start, NodeColor::Gray);
237    match visitor(TraversalEvent::Discover(start)) {
238        Control::Break(b) => return Some(b),
239        Control::Prune => {
240            color.insert(start, NodeColor::Black);
241            match visitor(TraversalEvent::Finish(start)) {
242                Control::Break(b) => return Some(b),
243                _ => return None,
244            }
245        }
246        Control::Continue => {}
247    }
248
249    let neighbors: Vec<_> = store.edges_from(start, Direction::Outgoing).collect();
250    stack.push((start, neighbors, 0));
251
252    while let Some((node, neighbors, idx)) = stack.last_mut() {
253        if *idx >= neighbors.len() {
254            // All neighbors processed, finish this node
255            let node = *node;
256            stack.pop();
257            color.insert(node, NodeColor::Black);
258            match visitor(TraversalEvent::Finish(node)) {
259                Control::Break(b) => return Some(b),
260                _ => {}
261            }
262            continue;
263        }
264
265        let (neighbor, edge_id) = neighbors[*idx];
266        *idx += 1;
267
268        match color.get(&neighbor).copied().unwrap_or(NodeColor::White) {
269            NodeColor::White => {
270                // Tree edge - undiscovered node
271                match visitor(TraversalEvent::TreeEdge {
272                    source: *node,
273                    target: neighbor,
274                    edge: edge_id,
275                }) {
276                    Control::Break(b) => return Some(b),
277                    Control::Prune => continue,
278                    Control::Continue => {}
279                }
280
281                color.insert(neighbor, NodeColor::Gray);
282                match visitor(TraversalEvent::Discover(neighbor)) {
283                    Control::Break(b) => return Some(b),
284                    Control::Prune => {
285                        color.insert(neighbor, NodeColor::Black);
286                        match visitor(TraversalEvent::Finish(neighbor)) {
287                            Control::Break(b) => return Some(b),
288                            _ => {}
289                        }
290                        continue;
291                    }
292                    Control::Continue => {}
293                }
294
295                let neighbor_neighbors: Vec<_> =
296                    store.edges_from(neighbor, Direction::Outgoing).collect();
297                stack.push((neighbor, neighbor_neighbors, 0));
298            }
299            NodeColor::Gray => {
300                // Back edge - node is on the stack (ancestor)
301                match visitor(TraversalEvent::BackEdge {
302                    source: *node,
303                    target: neighbor,
304                    edge: edge_id,
305                }) {
306                    Control::Break(b) => return Some(b),
307                    _ => {}
308                }
309            }
310            NodeColor::Black => {
311                // Non-tree edge (cross/forward) - already finished
312                match visitor(TraversalEvent::NonTreeEdge {
313                    source: *node,
314                    target: neighbor,
315                    edge: edge_id,
316                }) {
317                    Control::Break(b) => return Some(b),
318                    _ => {}
319                }
320            }
321        }
322    }
323
324    None
325}
326
327/// Performs DFS on all nodes, visiting each connected component.
328///
329/// Returns nodes in reverse post-order (useful for topological sort).
330pub fn dfs_all(store: &LpgStore) -> Vec<NodeId> {
331    let mut finished = Vec::new();
332    let mut visited: FxHashSet<NodeId> = FxHashSet::default();
333
334    for node_id in store.node_ids() {
335        if visited.contains(&node_id) {
336            continue;
337        }
338
339        dfs_with_visitor(store, node_id, |event| -> Control<()> {
340            match event {
341                TraversalEvent::Discover(n) => {
342                    visited.insert(n);
343                }
344                TraversalEvent::Finish(n) => {
345                    finished.push(n);
346                }
347                _ => {}
348            }
349            Control::Continue
350        });
351    }
352
353    finished
354}
355
356// ============================================================================
357// Algorithm Wrappers for Plugin Registry
358// ============================================================================
359
360/// Static parameter definitions for BFS algorithm.
361static BFS_PARAMS: OnceLock<Vec<ParameterDef>> = OnceLock::new();
362
363fn bfs_params() -> &'static [ParameterDef] {
364    BFS_PARAMS.get_or_init(|| {
365        vec![ParameterDef {
366            name: "start".to_string(),
367            description: "Starting node ID".to_string(),
368            param_type: ParameterType::NodeId,
369            required: true,
370            default: None,
371        }]
372    })
373}
374
375/// BFS algorithm wrapper for the plugin registry.
376pub struct BfsAlgorithm;
377
378impl GraphAlgorithm for BfsAlgorithm {
379    fn name(&self) -> &str {
380        "bfs"
381    }
382
383    fn description(&self) -> &str {
384        "Breadth-first search traversal from a starting node"
385    }
386
387    fn parameters(&self) -> &[ParameterDef] {
388        bfs_params()
389    }
390
391    fn execute(&self, store: &LpgStore, params: &Parameters) -> Result<AlgorithmResult> {
392        let start_id = params.get_int("start").ok_or_else(|| {
393            grafeo_common::utils::error::Error::InvalidValue("start parameter required".to_string())
394        })?;
395
396        let start = NodeId::new(start_id as u64);
397        let layers = bfs_layers(store, start);
398
399        let mut result = AlgorithmResult::new(vec!["node_id".to_string(), "distance".to_string()]);
400
401        for (distance, layer) in layers.iter().enumerate() {
402            for &node in layer {
403                result.add_row(vec![
404                    Value::Int64(node.0 as i64),
405                    Value::Int64(distance as i64),
406                ]);
407            }
408        }
409
410        Ok(result)
411    }
412}
413
414/// Static parameter definitions for DFS algorithm.
415static DFS_PARAMS: OnceLock<Vec<ParameterDef>> = OnceLock::new();
416
417fn dfs_params() -> &'static [ParameterDef] {
418    DFS_PARAMS.get_or_init(|| {
419        vec![ParameterDef {
420            name: "start".to_string(),
421            description: "Starting node ID".to_string(),
422            param_type: ParameterType::NodeId,
423            required: true,
424            default: None,
425        }]
426    })
427}
428
429/// DFS algorithm wrapper for the plugin registry.
430pub struct DfsAlgorithm;
431
432impl GraphAlgorithm for DfsAlgorithm {
433    fn name(&self) -> &str {
434        "dfs"
435    }
436
437    fn description(&self) -> &str {
438        "Depth-first search traversal from a starting node"
439    }
440
441    fn parameters(&self) -> &[ParameterDef] {
442        dfs_params()
443    }
444
445    fn execute(&self, store: &LpgStore, params: &Parameters) -> Result<AlgorithmResult> {
446        let start_id = params.get_int("start").ok_or_else(|| {
447            grafeo_common::utils::error::Error::InvalidValue("start parameter required".to_string())
448        })?;
449
450        let start = NodeId::new(start_id as u64);
451        let finished = dfs(store, start);
452
453        let mut builder = NodeValueResultBuilder::with_capacity("finish_order", finished.len());
454        for (order, node) in finished.iter().enumerate() {
455            builder.push(*node, Value::Int64(order as i64));
456        }
457
458        Ok(builder.build())
459    }
460}
461
462#[cfg(test)]
463mod tests {
464    use super::*;
465
466    fn create_test_graph() -> LpgStore {
467        let store = LpgStore::new();
468
469        // Create a simple graph:
470        //   0 -> 1 -> 2
471        //   |    |
472        //   v    v
473        //   3 -> 4
474        let n0 = store.create_node(&["Node"]);
475        let n1 = store.create_node(&["Node"]);
476        let n2 = store.create_node(&["Node"]);
477        let n3 = store.create_node(&["Node"]);
478        let n4 = store.create_node(&["Node"]);
479
480        store.create_edge(n0, n1, "EDGE");
481        store.create_edge(n0, n3, "EDGE");
482        store.create_edge(n1, n2, "EDGE");
483        store.create_edge(n1, n4, "EDGE");
484        store.create_edge(n3, n4, "EDGE");
485
486        store
487    }
488
489    #[test]
490    fn test_bfs_simple() {
491        let store = create_test_graph();
492        let visited = bfs(&store, NodeId::new(0));
493
494        assert!(!visited.is_empty());
495        assert_eq!(visited[0], NodeId::new(0));
496        // Node 0 should be first
497    }
498
499    #[test]
500    fn test_bfs_layers() {
501        let store = create_test_graph();
502        let layers = bfs_layers(&store, NodeId::new(0));
503
504        assert!(!layers.is_empty());
505        assert_eq!(layers[0], vec![NodeId::new(0)]);
506        // Distance 0: just the start node
507    }
508
509    #[test]
510    fn test_dfs_simple() {
511        let store = create_test_graph();
512        let finished = dfs(&store, NodeId::new(0));
513
514        assert!(!finished.is_empty());
515        // Post-order means leaves are finished first
516    }
517
518    #[test]
519    fn test_bfs_nonexistent_start() {
520        let store = LpgStore::new();
521        let visited = bfs(&store, NodeId::new(999));
522        assert!(visited.is_empty());
523    }
524
525    #[test]
526    fn test_dfs_nonexistent_start() {
527        let store = LpgStore::new();
528        let finished = dfs(&store, NodeId::new(999));
529        assert!(finished.is_empty());
530    }
531
532    #[test]
533    fn test_bfs_early_termination() {
534        let store = create_test_graph();
535        let target = NodeId::new(2);
536
537        let found = bfs_with_visitor(&store, NodeId::new(0), |event| {
538            if let TraversalEvent::Discover(node) = event
539                && node == target
540            {
541                return Control::Break(true);
542            }
543            Control::Continue
544        });
545
546        assert_eq!(found, Some(true));
547    }
548
549    #[test]
550    fn test_bfs_visits_all_reachable() {
551        let store = create_test_graph();
552        let visited = bfs(&store, NodeId::new(0));
553        // All 5 nodes are reachable from node 0
554        assert_eq!(visited.len(), 5);
555    }
556
557    #[test]
558    fn test_bfs_layers_distances() {
559        let store = create_test_graph();
560        let layers = bfs_layers(&store, NodeId::new(0));
561
562        // Layer 0: node 0
563        // Layer 1: nodes 1, 3 (direct neighbors)
564        // Layer 2: nodes 2, 4 (distance 2)
565        assert_eq!(layers.len(), 3);
566        assert_eq!(layers[0].len(), 1);
567        assert_eq!(layers[1].len(), 2);
568        assert_eq!(layers[2].len(), 2);
569    }
570
571    #[test]
572    fn test_bfs_layers_empty_graph() {
573        let store = LpgStore::new();
574        let layers = bfs_layers(&store, NodeId::new(0));
575        assert!(layers.is_empty());
576    }
577
578    #[test]
579    fn test_bfs_single_node() {
580        let store = LpgStore::new();
581        let n0 = store.create_node(&["Node"]);
582        let visited = bfs(&store, n0);
583        assert_eq!(visited, vec![n0]);
584    }
585
586    #[test]
587    fn test_bfs_layers_single_node() {
588        let store = LpgStore::new();
589        let n0 = store.create_node(&["Node"]);
590        let layers = bfs_layers(&store, n0);
591        assert_eq!(layers.len(), 1);
592        assert_eq!(layers[0], vec![n0]);
593    }
594
595    #[test]
596    fn test_bfs_with_visitor_prune_on_start() {
597        let store = create_test_graph();
598        let result: Option<()> = bfs_with_visitor(&store, NodeId::new(0), |event| {
599            if let TraversalEvent::Discover(node) = event
600                && node == NodeId::new(0)
601            {
602                return Control::Prune;
603            }
604            Control::Continue
605        });
606        // Pruning start node means no further traversal
607        assert!(result.is_none());
608    }
609
610    #[test]
611    fn test_bfs_with_visitor_collects_tree_edges() {
612        let store = create_test_graph();
613        let mut tree_edges = Vec::new();
614
615        bfs_with_visitor(&store, NodeId::new(0), |event| -> Control<()> {
616            if let TraversalEvent::TreeEdge { source, target, .. } = event {
617                tree_edges.push((source, target));
618            }
619            Control::Continue
620        });
621
622        // BFS tree from node 0 has 4 tree edges (one per non-start node)
623        assert_eq!(tree_edges.len(), 4);
624    }
625
626    #[test]
627    fn test_bfs_with_visitor_detects_non_tree_edges() {
628        let store = create_test_graph();
629        let mut non_tree_edges = Vec::new();
630
631        bfs_with_visitor(&store, NodeId::new(0), |event| -> Control<()> {
632            if let TraversalEvent::NonTreeEdge { source, target, .. } = event {
633                non_tree_edges.push((source, target));
634            }
635            Control::Continue
636        });
637
638        // There's at least one non-tree edge (3->4 or 1->4)
639        assert!(!non_tree_edges.is_empty());
640    }
641
642    #[test]
643    fn test_dfs_visits_all_reachable() {
644        let store = create_test_graph();
645        let finished = dfs(&store, NodeId::new(0));
646        assert_eq!(finished.len(), 5);
647    }
648
649    #[test]
650    fn test_dfs_post_order() {
651        let store = create_test_graph();
652        let finished = dfs(&store, NodeId::new(0));
653        // In post-order, the start node is finished last
654        assert_eq!(*finished.last().unwrap(), NodeId::new(0));
655    }
656
657    #[test]
658    fn test_dfs_with_visitor_early_termination() {
659        let store = create_test_graph();
660        let found = dfs_with_visitor(&store, NodeId::new(0), |event| {
661            if let TraversalEvent::Discover(node) = event
662                && node == NodeId::new(4)
663            {
664                return Control::Break(node);
665            }
666            Control::Continue
667        });
668        assert_eq!(found, Some(NodeId::new(4)));
669    }
670
671    #[test]
672    fn test_dfs_with_visitor_prune() {
673        let store = create_test_graph();
674        let mut discovered = Vec::new();
675
676        dfs_with_visitor(&store, NodeId::new(0), |event| -> Control<()> {
677            if let TraversalEvent::Discover(node) = event {
678                discovered.push(node);
679                if node == NodeId::new(1) {
680                    return Control::Prune; // Don't explore node 1's children
681                }
682            }
683            Control::Continue
684        });
685
686        // Node 1 is discovered but its children (2, 4) may not all be
687        assert!(discovered.contains(&NodeId::new(0)));
688        assert!(discovered.contains(&NodeId::new(1)));
689        // Node 2 should not be discovered since we pruned node 1
690        assert!(!discovered.contains(&NodeId::new(2)));
691    }
692
693    #[test]
694    fn test_dfs_with_visitor_back_edge() {
695        // Create a cycle: 0 -> 1 -> 2 -> 0
696        let store = LpgStore::new();
697        let n0 = store.create_node(&["Node"]);
698        let n1 = store.create_node(&["Node"]);
699        let n2 = store.create_node(&["Node"]);
700        store.create_edge(n0, n1, "EDGE");
701        store.create_edge(n1, n2, "EDGE");
702        store.create_edge(n2, n0, "EDGE");
703
704        let mut back_edges = Vec::new();
705        dfs_with_visitor(&store, n0, |event| -> Control<()> {
706            if let TraversalEvent::BackEdge { source, target, .. } = event {
707                back_edges.push((source, target));
708            }
709            Control::Continue
710        });
711
712        // Edge 2->0 is a back edge (0 is ancestor of 2)
713        assert_eq!(back_edges.len(), 1);
714        assert_eq!(back_edges[0], (n2, n0));
715    }
716
717    #[test]
718    fn test_dfs_single_node() {
719        let store = LpgStore::new();
720        let n0 = store.create_node(&["Node"]);
721        let finished = dfs(&store, n0);
722        assert_eq!(finished, vec![n0]);
723    }
724
725    #[test]
726    fn test_dfs_all_visits_all_components() {
727        let store = LpgStore::new();
728        // Component 1: 0 -> 1
729        let n0 = store.create_node(&["Node"]);
730        let n1 = store.create_node(&["Node"]);
731        store.create_edge(n0, n1, "EDGE");
732
733        // Component 2: 2 -> 3
734        let n2 = store.create_node(&["Node"]);
735        let n3 = store.create_node(&["Node"]);
736        store.create_edge(n2, n3, "EDGE");
737
738        let finished = dfs_all(&store);
739        assert_eq!(finished.len(), 4);
740    }
741
742    #[test]
743    fn test_dfs_all_empty_graph() {
744        let store = LpgStore::new();
745        let finished = dfs_all(&store);
746        assert!(finished.is_empty());
747    }
748
749    #[test]
750    fn test_bfs_prune_tree_edge() {
751        let store = create_test_graph();
752        let mut discovered = Vec::new();
753
754        bfs_with_visitor(&store, NodeId::new(0), |event| -> Control<()> {
755            match event {
756                TraversalEvent::TreeEdge { target, .. } => {
757                    if target == NodeId::new(1) {
758                        return Control::Prune; // Skip node 1
759                    }
760                    Control::Continue
761                }
762                TraversalEvent::Discover(node) => {
763                    discovered.push(node);
764                    Control::Continue
765                }
766                _ => Control::Continue,
767            }
768        });
769
770        // Node 1 should not be discovered due to pruned tree edge
771        assert!(discovered.contains(&NodeId::new(0)));
772        assert!(!discovered.contains(&NodeId::new(1)));
773    }
774
775    #[test]
776    fn test_dfs_with_visitor_prune_start() {
777        let store = create_test_graph();
778        let result: Option<()> = dfs_with_visitor(&store, NodeId::new(0), |event| {
779            if let TraversalEvent::Discover(node) = event
780                && node == NodeId::new(0)
781            {
782                return Control::Prune;
783            }
784            Control::Continue
785        });
786        assert!(result.is_none());
787    }
788}