stack_graphs/
stitching.rs

1// -*- coding: utf-8 -*-
2// ------------------------------------------------------------------------------------------------
3// Copyright © 2021, stack-graphs authors.
4// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
5// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6// ------------------------------------------------------------------------------------------------
7
8//! Partial paths can be "stitched together" to produce name-binding paths.
9//!
10//! The "path stitching" algorithm defined in this module is how we take a collection of [partial
11//! paths][] and use them to build up name-binding paths.  Our conjecture is that by building paths
12//! this way, we can precompute a useful amount of work at _index time_ (when we construct the
13//! partial paths), to reduce the amount of work that needs to be done at _query time_ (when those
14//! partial paths are stitched together into paths).
15//!
16//! Complicating this story is that for large codebases (especially those with many upstream and
17//! downstream dependencies), there is a _very_ large set of partial paths available to us.  We
18//! want to be able to load those in _lazily_, during the execution of the path-stitching
19//! algorithm.
20//!
21//! The [`Database`][] and [`PathStitcher`][] types provide this functionality.  `Database`
22//! manages a collection of partial paths that have been loaded into this process from some
23//! external data store.  `PathStitcher` implements the path-stitching algorithm in _phases_.
24//! During each phase, we will process a set of (possibly incomplete) paths, looking in the
25//! `Database` for the set of partial paths that are compatible with those paths.  It is your
26//! responsibility to make sure that the database contains all of possible extensions of the paths
27//! that we're going to process in that phase.  For the first phase, you already know which
28//! paths you're starting the search from, and must make sure that the database starts out
29//! containing the possible extensions of those "seed" paths.  For subsequent phases, you get to
30//! see which paths will be processed in the _next_ phase as part of handling the _current_ phase.
31//! This gives you the opporunity to load additional partial paths into the `Database` before
32//! allowing the next phase to proceed.
33//!
34//! [partial paths]: ../partial/index.html
35//! [`Database`]: struct.Database.html
36//! [`PathStitcher`]: struct.PathStitcher.html
37
38use std::cmp::Ordering;
39use std::collections::HashMap;
40use std::collections::VecDeque;
41#[cfg(feature = "copious-debugging")]
42use std::fmt::Display;
43
44use itertools::izip;
45use itertools::Itertools;
46
47use crate::arena::Arena;
48use crate::arena::Handle;
49use crate::arena::HandleSet;
50use crate::arena::List;
51use crate::arena::ListArena;
52use crate::arena::ListCell;
53use crate::arena::SupplementalArena;
54use crate::cycles::Appendables;
55use crate::cycles::AppendingCycleDetector;
56use crate::cycles::SimilarPathDetector;
57use crate::cycles::SimilarPathStats;
58use crate::graph::Degree;
59use crate::graph::Edge;
60use crate::graph::File;
61use crate::graph::Node;
62use crate::graph::StackGraph;
63use crate::graph::Symbol;
64use crate::partial::Cyclicity;
65use crate::partial::PartialPath;
66use crate::partial::PartialPaths;
67use crate::partial::PartialSymbolStack;
68use crate::paths::Extend;
69use crate::paths::PathResolutionError;
70use crate::stats::FrequencyDistribution;
71use crate::CancellationError;
72use crate::CancellationFlag;
73
74//-------------------------------------------------------------------------------------------------
75// Appendable
76
77/// Something that can be appended to a partial path.
78pub trait Appendable {
79    /// Append this appendable to the given path. Resolving jump nodes and renaming unused_variables
80    /// is part of the responsibility of this method.
81    fn append_to(
82        &self,
83        graph: &StackGraph,
84        partials: &mut PartialPaths,
85        path: &mut PartialPath,
86    ) -> Result<(), PathResolutionError>;
87
88    /// Return the start node.
89    fn start_node(&self) -> Handle<Node>;
90
91    /// Return the end node.
92    fn end_node(&self) -> Handle<Node>;
93
94    /// Return a Display implementation.
95    fn display<'a>(
96        &'a self,
97        graph: &'a StackGraph,
98        partials: &'a mut PartialPaths,
99    ) -> Box<dyn std::fmt::Display + 'a>;
100}
101
102impl Appendable for Edge {
103    fn append_to(
104        &self,
105        graph: &StackGraph,
106        partials: &mut PartialPaths,
107        path: &mut PartialPath,
108    ) -> Result<(), PathResolutionError> {
109        path.resolve_to_node(graph, partials, self.source)?;
110        path.append(graph, partials, *self)
111    }
112
113    fn start_node(&self) -> Handle<Node> {
114        self.source
115    }
116
117    fn end_node(&self) -> Handle<Node> {
118        self.sink
119    }
120
121    fn display<'a>(
122        &'a self,
123        graph: &'a StackGraph,
124        _partials: &'a mut PartialPaths,
125    ) -> Box<dyn std::fmt::Display + 'a> {
126        Box::new(format!(
127            "{} -> {}",
128            self.source.display(graph),
129            self.sink.display(graph)
130        ))
131    }
132}
133
134impl Appendable for PartialPath {
135    fn append_to(
136        &self,
137        graph: &StackGraph,
138        partials: &mut PartialPaths,
139        path: &mut PartialPath,
140    ) -> Result<(), PathResolutionError> {
141        path.resolve_to_node(graph, partials, self.start_node)?;
142        path.ensure_no_overlapping_variables(partials, self);
143        path.concatenate(graph, partials, self)?;
144        Ok(())
145    }
146
147    fn start_node(&self) -> Handle<Node> {
148        self.start_node
149    }
150
151    fn end_node(&self) -> Handle<Node> {
152        self.end_node
153    }
154
155    fn display<'a>(
156        &'a self,
157        graph: &'a StackGraph,
158        partials: &'a mut PartialPaths,
159    ) -> Box<dyn std::fmt::Display + 'a> {
160        Box::new(self.display(graph, partials))
161    }
162}
163
164//-------------------------------------------------------------------------------------------------
165// ToAppendable
166
167/// A trait to be implemented on types such as [`Database`][] that allow converting handles
168/// to appendables.
169///
170/// It is very similar to the [`std::ops::Index`] trait, but returns a reference instead
171/// of a value, such that an efficient identifity implementation is possible, that doesn't
172/// require cloning values.
173pub trait ToAppendable<H, A>
174where
175    A: Appendable,
176{
177    fn get_appendable<'a>(&'a self, handle: &'a H) -> &'a A;
178}
179
180//-------------------------------------------------------------------------------------------------
181// Candidates
182
183/// A trait to support finding candidates for partial path extension. The candidates are represented
184/// by handles `H`, which are mapped to appendables `A` using the database `Db`. Loading errors are
185/// reported as values of the `Err` type.
186pub trait ForwardCandidates<H, A, Db, Err>
187where
188    A: Appendable,
189    Db: ToAppendable<H, A>,
190{
191    /// Load possible forward candidates for the given partial path into this candidates instance.
192    /// Must be called before [`get_forward_candidates`] to allow lazy-loading implementations.
193    fn load_forward_candidates(
194        &mut self,
195        _path: &PartialPath,
196        _cancellation_flag: &dyn CancellationFlag,
197    ) -> Result<(), Err> {
198        Ok(())
199    }
200
201    /// Get forward candidates for extending the given partial path and add them to the provided
202    /// result instance. If this instance loads data lazily, this only considers previously loaded
203    /// data.
204    fn get_forward_candidates<R>(&mut self, path: &PartialPath, result: &mut R)
205    where
206        R: std::iter::Extend<H>;
207
208    /// Get the number of available candidates that share the given path's end node.
209    fn get_joining_candidate_degree(&self, path: &PartialPath) -> Degree;
210
211    /// Get the graph, partial path arena, and database backing this candidates instance.
212    fn get_graph_partials_and_db(&mut self) -> (&StackGraph, &mut PartialPaths, &Db);
213}
214
215//-------------------------------------------------------------------------------------------------
216// FileEdges
217
218/// Acts as a database of the edges in the graph.
219pub struct GraphEdgeCandidates<'a> {
220    graph: &'a StackGraph,
221    partials: &'a mut PartialPaths,
222    file: Option<Handle<File>>,
223    edges: GraphEdges,
224}
225
226impl<'a> GraphEdgeCandidates<'a> {
227    pub fn new(
228        graph: &'a StackGraph,
229        partials: &'a mut PartialPaths,
230        file: Option<Handle<File>>,
231    ) -> Self {
232        Self {
233            graph,
234            partials,
235            file,
236            edges: GraphEdges,
237        }
238    }
239}
240
241impl ForwardCandidates<Edge, Edge, GraphEdges, CancellationError> for GraphEdgeCandidates<'_> {
242    fn get_forward_candidates<R>(&mut self, path: &PartialPath, result: &mut R)
243    where
244        R: std::iter::Extend<Edge>,
245    {
246        result.extend(self.graph.outgoing_edges(path.end_node).filter(|e| {
247            self.file
248                .map_or(true, |file| self.graph[e.sink].is_in_file(file))
249        }));
250    }
251
252    fn get_joining_candidate_degree(&self, path: &PartialPath) -> Degree {
253        self.graph.incoming_edge_degree(path.end_node)
254    }
255
256    fn get_graph_partials_and_db(&mut self) -> (&StackGraph, &mut PartialPaths, &GraphEdges) {
257        (self.graph, self.partials, &self.edges)
258    }
259}
260
261/// A dummy type to act as the "database" for graph edges. Its [`ToAppendable`] implementation
262/// is the identity on edges.
263pub struct GraphEdges;
264
265impl ToAppendable<Edge, Edge> for GraphEdges {
266    fn get_appendable<'a>(&'a self, edge: &'a Edge) -> &'a Edge {
267        edge
268    }
269}
270
271//-------------------------------------------------------------------------------------------------
272// Databases
273
274/// Contains a "database" of partial paths.
275///
276/// This type is meant to be a lazily loaded "view" into a proper storage layer.  During the
277/// path-stitching algorithm, we repeatedly try to extend a currently incomplete path with any
278/// partial paths that are compatible with it.  For large codebases, or projects with a large
279/// number of dependencies, it can be prohibitive to load in _all_ of the partial paths up-front.
280/// We've written the path-stitching algorithm so that you have a chance to only load in the
281/// partial paths that are actually needed, placing them into a `Database` instance as they're
282/// needed.
283pub struct Database {
284    pub(crate) partial_paths: Arena<PartialPath>,
285    pub(crate) local_nodes: HandleSet<Node>,
286    symbol_stack_keys: ListArena<Handle<Symbol>>,
287    symbol_stack_key_cache: HashMap<SymbolStackCacheKey, SymbolStackKeyHandle>,
288    paths_by_start_node: SupplementalArena<Node, Vec<Handle<PartialPath>>>,
289    root_paths_by_precondition_prefix:
290        SupplementalArena<SymbolStackKeyCell, Vec<Handle<PartialPath>>>,
291    root_paths_by_precondition_with_variable:
292        SupplementalArena<SymbolStackKeyCell, Vec<Handle<PartialPath>>>,
293    root_paths_by_precondition_without_variable:
294        SupplementalArena<SymbolStackKeyCell, Vec<Handle<PartialPath>>>,
295    incoming_paths: SupplementalArena<Node, Degree>,
296}
297
298impl Database {
299    /// Creates a new, empty database.
300    pub fn new() -> Database {
301        Database {
302            partial_paths: Arena::new(),
303            local_nodes: HandleSet::new(),
304            symbol_stack_keys: List::new_arena(),
305            symbol_stack_key_cache: HashMap::new(),
306            paths_by_start_node: SupplementalArena::new(),
307            root_paths_by_precondition_prefix: SupplementalArena::new(),
308            root_paths_by_precondition_with_variable: SupplementalArena::new(),
309            root_paths_by_precondition_without_variable: SupplementalArena::new(),
310            incoming_paths: SupplementalArena::new(),
311        }
312    }
313
314    /// Clear the database.  After this, all previous handles into the database are
315    /// invalid.
316    #[cfg_attr(not(feature = "storage"), allow(dead_code))]
317    pub(crate) fn clear(&mut self) {
318        self.partial_paths.clear();
319        self.local_nodes.clear();
320        self.symbol_stack_keys.clear();
321        self.symbol_stack_key_cache.clear();
322        self.paths_by_start_node.clear();
323        self.root_paths_by_precondition_prefix.clear();
324        self.root_paths_by_precondition_with_variable.clear();
325        self.root_paths_by_precondition_without_variable.clear();
326        self.incoming_paths.clear();
327    }
328
329    /// Adds a partial path to this database.  We do not deduplicate partial paths in any way; it's
330    /// your responsibility to only add each partial path once.
331    pub fn add_partial_path(
332        &mut self,
333        graph: &StackGraph,
334        partials: &mut PartialPaths,
335        path: PartialPath,
336    ) -> Handle<PartialPath> {
337        let start_node = path.start_node;
338        let end_node = path.end_node;
339        copious_debugging!(
340            "    Add {} path to database {}",
341            if graph[start_node].is_root() {
342                "root"
343            } else {
344                "node"
345            },
346            path.display(graph, partials)
347        );
348        let symbol_stack_precondition = path.symbol_stack_precondition;
349        let handle = self.partial_paths.add(path);
350
351        // If the partial path starts at the root node, index it by its symbol stack precondition.
352        if graph[start_node].is_root() {
353            // The join node is root, so there's no need to use half-open symbol stacks here, as we
354            // do for [`PartialPath::concatenate`][].
355            let mut key = SymbolStackKey::from_partial_symbol_stack(
356                partials,
357                self,
358                symbol_stack_precondition,
359            );
360            if !key.is_empty() {
361                match symbol_stack_precondition.has_variable() {
362                    true => self.root_paths_by_precondition_with_variable[key.back_handle()]
363                        .push(handle),
364                    false => self.root_paths_by_precondition_without_variable[key.back_handle()]
365                        .push(handle),
366                }
367            }
368            while key.pop_back(self).is_some() && !key.is_empty() {
369                self.root_paths_by_precondition_prefix[key.back_handle()].push(handle);
370            }
371        } else {
372            // Otherwise index it by its source node.
373            self.paths_by_start_node[start_node].push(handle);
374        }
375
376        self.incoming_paths[end_node] += Degree::One;
377        handle
378    }
379
380    /// Find all partial paths in this database that start at the given path's end node.
381    /// If the end node is the root node, returns paths with a symbol stack precondition
382    /// that are compatible with the path's symbol stack post condition.
383    pub fn find_candidate_partial_paths<R>(
384        &mut self,
385        graph: &StackGraph,
386        partials: &mut PartialPaths,
387        path: &PartialPath,
388        result: &mut R,
389    ) where
390        R: std::iter::Extend<Handle<PartialPath>>,
391    {
392        if graph[path.end_node].is_root() {
393            // The join node is root, so there's no need to use half-open symbol stacks here, as we
394            // do for [`PartialPath::concatenate`][].
395            self.find_candidate_partial_paths_from_root(
396                graph,
397                partials,
398                Some(path.symbol_stack_postcondition),
399                result,
400            );
401        } else {
402            self.find_candidate_partial_paths_from_node(graph, partials, path.end_node, result);
403        }
404    }
405
406    /// Find all partial paths in this database that start at the root node, and have a symbol
407    /// stack precondition that is compatible with a given symbol stack.
408    #[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))]
409    pub fn find_candidate_partial_paths_from_root<R>(
410        &mut self,
411        graph: &StackGraph,
412        partials: &mut PartialPaths,
413        symbol_stack: Option<PartialSymbolStack>,
414        result: &mut R,
415    ) where
416        R: std::iter::Extend<Handle<PartialPath>>,
417    {
418        // If the path currently ends at the root node, then we need to look up partial paths whose
419        // symbol stack precondition is compatible with the path.
420        match symbol_stack {
421            Some(symbol_stack) => {
422                let mut key =
423                    SymbolStackKey::from_partial_symbol_stack(partials, self, symbol_stack);
424                copious_debugging!(
425                    "      Search for symbol stack <{}>",
426                    key.display(graph, self)
427                );
428                // paths that have exactly this symbol stack
429                if let Some(paths) = self
430                    .root_paths_by_precondition_without_variable
431                    .get(key.back_handle())
432                {
433                    #[cfg(feature = "copious-debugging")]
434                    {
435                        for path in paths {
436                            copious_debugging!(
437                                "        Found path with exact stack {}",
438                                self[*path].display(graph, partials)
439                            );
440                        }
441                    }
442                    result.extend(paths.iter().copied());
443                }
444                // paths that have an extension of this symbol stack
445                if symbol_stack.has_variable() {
446                    if let Some(paths) = self
447                        .root_paths_by_precondition_prefix
448                        .get(key.back_handle())
449                    {
450                        #[cfg(feature = "copious-debugging")]
451                        {
452                            for path in paths {
453                                copious_debugging!(
454                                    "        Found path with smaller stack {}",
455                                    self[*path].display(graph, partials)
456                                );
457                            }
458                        }
459                        result.extend(paths.iter().copied());
460                    }
461                }
462                loop {
463                    // paths that have a prefix of this symbol stack
464                    if let Some(paths) = self
465                        .root_paths_by_precondition_with_variable
466                        .get(key.back_handle())
467                    {
468                        #[cfg(feature = "copious-debugging")]
469                        {
470                            for path in paths {
471                                copious_debugging!(
472                                    "        Found path with smaller stack {}",
473                                    self[*path].display(graph, partials)
474                                );
475                            }
476                        }
477                        result.extend(paths.iter().copied());
478                    }
479                    if key.pop_back(self).is_none() {
480                        break;
481                    }
482                }
483            }
484            None => {
485                copious_debugging!("      Search for all root paths");
486                for (_, paths) in self
487                    .root_paths_by_precondition_with_variable
488                    .iter()
489                    .chain(self.root_paths_by_precondition_without_variable.iter())
490                {
491                    #[cfg(feature = "copious-debugging")]
492                    {
493                        for path in paths {
494                            copious_debugging!(
495                                "        Found path {}",
496                                self[*path].display(graph, partials)
497                            );
498                        }
499                    }
500                    result.extend(paths.iter().copied());
501                }
502            }
503        }
504    }
505
506    /// Find all partial paths in the database that start at the given node.  We don't filter the
507    /// results any further than that, since we have to check each partial path for compatibility
508    /// as we try to append it to the current incomplete path anyway, and non-root nodes will
509    /// typically have a small number of outgoing edges.
510    #[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))]
511    pub fn find_candidate_partial_paths_from_node<R>(
512        &self,
513        graph: &StackGraph,
514        partials: &mut PartialPaths,
515        start_node: Handle<Node>,
516        result: &mut R,
517    ) where
518        R: std::iter::Extend<Handle<PartialPath>>,
519    {
520        copious_debugging!("      Search for start node {}", start_node.display(graph));
521        // Return all of the partial paths that start at the requested node.
522        if let Some(paths) = self.paths_by_start_node.get(start_node) {
523            #[cfg(feature = "copious-debugging")]
524            {
525                for path in paths {
526                    copious_debugging!(
527                        "        Found path {}",
528                        self[*path].display(graph, partials)
529                    );
530                }
531            }
532            result.extend(paths.iter().copied());
533        }
534    }
535
536    /// Returns the number of paths in this database that share the given end node.
537    pub fn get_incoming_path_degree(&self, end_node: Handle<Node>) -> Degree {
538        self.incoming_paths[end_node]
539    }
540
541    /// Determines which nodes in the stack graph are “local”, taking into account the partial
542    /// paths in this database.
543    ///
544    /// A local node has no partial path that connects it to the root node in either direction.
545    /// That means that it cannot participate in any paths that leave the file.
546    ///
547    /// This method is meant to be used at index time, to calculate the set of nodes that are local
548    /// after having just calculated the set of partial paths for the file.
549    pub fn find_local_nodes(&mut self) {
550        // Assume that any node that is the start or end of a partial path is local to this file
551        // until we see a path connecting the root node to it (in either direction).
552        self.local_nodes.clear();
553        for handle in self.iter_partial_paths() {
554            self.local_nodes.add(self[handle].start_node);
555            self.local_nodes.add(self[handle].end_node);
556        }
557
558        // The root node and jump-to-scope node are the most obvious non-local nodes.
559        let mut nonlocal_start_nodes = HandleSet::new();
560        let mut nonlocal_end_nodes = HandleSet::new();
561        self.local_nodes.remove(StackGraph::root_node());
562        nonlocal_start_nodes.add(StackGraph::root_node());
563        nonlocal_end_nodes.add(StackGraph::root_node());
564        self.local_nodes.remove(StackGraph::jump_to_node());
565        nonlocal_start_nodes.add(StackGraph::jump_to_node());
566        nonlocal_end_nodes.add(StackGraph::jump_to_node());
567
568        // Other nodes are non-local if we see any partial path that connects it to another
569        // non-local node.  Repeat until we reach a fixed point.
570        let mut keep_checking = true;
571        while keep_checking {
572            keep_checking = false;
573            for handle in self.iter_partial_paths() {
574                let start_node = self[handle].start_node;
575                let end_node = self[handle].end_node;
576
577                // First check forwards paths, where non-localness propagates from the start node
578                // of each path.
579                let start_node_is_nonlocal = nonlocal_start_nodes.contains(start_node);
580                let end_node_is_nonlocal = nonlocal_start_nodes.contains(end_node);
581                if start_node_is_nonlocal && !end_node_is_nonlocal {
582                    keep_checking = true;
583                    nonlocal_start_nodes.add(end_node);
584                    self.local_nodes.remove(end_node);
585                }
586
587                // Then check reverse paths, where non-localness propagates from the end node of
588                // each path.
589                let start_node_is_nonlocal = nonlocal_end_nodes.contains(start_node);
590                let end_node_is_nonlocal = nonlocal_end_nodes.contains(end_node);
591                if !start_node_is_nonlocal && end_node_is_nonlocal {
592                    keep_checking = true;
593                    nonlocal_end_nodes.add(start_node);
594                    self.local_nodes.remove(start_node);
595                }
596            }
597        }
598    }
599
600    /// Marks that a stack graph node is local.
601    ///
602    /// This method is meant to be used at query time.  You will have precalculated the set of
603    /// local nodes for a file at index time; at query time, you will load this information from
604    /// your storage layer and use this method to update our internal view of which nodes are
605    /// local.
606    pub fn mark_local_node(&mut self, node: Handle<Node>) {
607        self.local_nodes.add(node);
608    }
609
610    /// Returns whether a node is local according to the partial paths in this database.  You must
611    /// have already called [`find_local_nodes`][] or [`mark_local_node`][], depending on whether
612    /// it is index time or query time.
613    pub fn node_is_local(&self, node: Handle<Node>) -> bool {
614        self.local_nodes.contains(node)
615    }
616
617    /// Returns an iterator over all of the handles of all of the partial paths in this database.
618    /// (Note that because we're only returning _handles_, this iterator does not retain a
619    /// reference to the `Database`.)
620    pub fn iter_partial_paths(&self) -> impl Iterator<Item = Handle<PartialPath>> {
621        self.partial_paths.iter_handles()
622    }
623
624    pub fn ensure_both_directions(&mut self, partials: &mut PartialPaths) {
625        for path in self.partial_paths.iter_handles() {
626            self.partial_paths
627                .get_mut(path)
628                .ensure_both_directions(partials);
629        }
630    }
631
632    pub fn ensure_forwards(&mut self, partials: &mut PartialPaths) {
633        for path in self.partial_paths.iter_handles() {
634            self.partial_paths.get_mut(path).ensure_forwards(partials);
635        }
636    }
637}
638
639impl std::ops::Index<Handle<PartialPath>> for Database {
640    type Output = PartialPath;
641    #[inline(always)]
642    fn index(&self, handle: Handle<PartialPath>) -> &PartialPath {
643        self.partial_paths.get(handle)
644    }
645}
646
647impl ToAppendable<Handle<PartialPath>, PartialPath> for Database {
648    fn get_appendable<'a>(&'a self, handle: &'a Handle<PartialPath>) -> &'a PartialPath {
649        &self[*handle]
650    }
651}
652
653pub struct DatabaseCandidates<'a> {
654    graph: &'a StackGraph,
655    partials: &'a mut PartialPaths,
656    database: &'a mut Database,
657}
658
659impl<'a> DatabaseCandidates<'a> {
660    pub fn new(
661        graph: &'a StackGraph,
662        partials: &'a mut PartialPaths,
663        database: &'a mut Database,
664    ) -> Self {
665        Self {
666            graph,
667            partials,
668            database,
669        }
670    }
671}
672
673impl ForwardCandidates<Handle<PartialPath>, PartialPath, Database, CancellationError>
674    for DatabaseCandidates<'_>
675{
676    fn get_forward_candidates<R>(&mut self, path: &PartialPath, result: &mut R)
677    where
678        R: std::iter::Extend<Handle<PartialPath>>,
679    {
680        self.database
681            .find_candidate_partial_paths(self.graph, self.partials, path, result);
682    }
683
684    fn get_joining_candidate_degree(&self, path: &PartialPath) -> Degree {
685        self.database.get_incoming_path_degree(path.end_node)
686    }
687
688    fn get_graph_partials_and_db(&mut self) -> (&StackGraph, &mut PartialPaths, &Database) {
689        (self.graph, self.partials, self.database)
690    }
691}
692
693/// The key type that we use to find partial paths that start from the root node and have a
694/// particular symbol stack as their precondition.
695#[derive(Clone, Copy)]
696pub struct SymbolStackKey {
697    // Note: the symbols are stored in reverse order, with the "front" of the List being the "back"
698    // of the symbol stack.  That lets us easily get a handle to the back of the symbol stack, and
699    // also lets us easily pops items off the back of key, which we need to do to search for all
700    // prefixes of a particular symbol stack down in `find_candidate_partial_paths_from_root`.
701    symbols: List<Handle<Symbol>>,
702}
703
704#[derive(Clone, Eq, Hash, PartialEq)]
705struct SymbolStackCacheKey {
706    head: Handle<Symbol>,
707    tail: SymbolStackKeyHandle,
708}
709
710type SymbolStackKeyCell = ListCell<Handle<Symbol>>;
711type SymbolStackKeyHandle = Handle<SymbolStackKeyCell>;
712
713impl SymbolStackKey {
714    /// Returns an empty symbol stack key.
715    fn empty() -> SymbolStackKey {
716        SymbolStackKey {
717            symbols: List::empty(),
718        }
719    }
720
721    fn is_empty(&self) -> bool {
722        self.symbols.is_empty()
723    }
724
725    /// Pushes a new symbol onto the back of this symbol stack key.
726    fn push_back(&mut self, db: &mut Database, symbol: Handle<Symbol>) {
727        let cache_key = SymbolStackCacheKey {
728            head: symbol,
729            tail: self.back_handle(),
730        };
731        if let Some(handle) = db.symbol_stack_key_cache.get(&cache_key) {
732            self.symbols = List::from_handle(*handle);
733            return;
734        }
735        // push_front because we store the key's symbols in reverse order.
736        self.symbols.push_front(&mut db.symbol_stack_keys, symbol);
737        let handle = self.back_handle();
738        db.symbol_stack_key_cache.insert(cache_key, handle);
739    }
740
741    /// Pops a symbol from the back of this symbol stack key.
742    fn pop_back(&mut self, db: &Database) -> Option<Handle<Symbol>> {
743        // pop_front because we store the key's symbols in reverse order.
744        self.symbols.pop_front(&db.symbol_stack_keys).copied()
745    }
746
747    /// Extracts a new symbol stack key from a partial symbol stack.
748    pub fn from_partial_symbol_stack(
749        partials: &mut PartialPaths,
750        db: &mut Database,
751        mut stack: PartialSymbolStack,
752    ) -> SymbolStackKey {
753        let mut result = SymbolStackKey::empty();
754        while let Some(symbol) = stack.pop_front(partials) {
755            result.push_back(db, symbol.symbol);
756        }
757        result
758    }
759
760    /// Returns a handle to the back of the symbol stack key.
761    fn back_handle(self) -> SymbolStackKeyHandle {
762        // Because the symbols are stored in reverse order, the handle to the "front" of the list
763        // is a handle to the "back" of the key.
764        self.symbols.handle()
765    }
766
767    #[cfg(feature = "copious-debugging")]
768    fn display<'a>(self, graph: &'a StackGraph, db: &'a Database) -> impl Display + 'a {
769        DisplaySymbolStackKey(self, graph, db)
770    }
771}
772
773#[cfg(feature = "copious-debugging")]
774struct DisplaySymbolStackKey<'a>(SymbolStackKey, &'a StackGraph, &'a Database);
775
776#[cfg(feature = "copious-debugging")]
777impl<'a> Display for DisplaySymbolStackKey<'a> {
778    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
779        // Use a recursive function to print the contents of the key out in reverse order.
780        fn display_one(
781            mut key: SymbolStackKey,
782            graph: &StackGraph,
783            db: &Database,
784            f: &mut std::fmt::Formatter,
785        ) -> std::fmt::Result {
786            let last = match key.pop_back(db) {
787                Some(last) => last,
788                None => return Ok(()),
789            };
790            display_one(key, graph, db, f)?;
791            last.display(graph).fmt(f)
792        }
793        display_one(self.0, self.1, self.2, f)
794    }
795}
796
797//-------------------------------------------------------------------------------------------------
798// Stitching partial paths together
799
800/// Implements a phased forward partial path stitching algorithm.
801///
802/// Our overall goal is to start with a set of _seed_ partial paths, and to repeatedly extend each
803/// partial path by concatenating another, compatible partial path onto the end of it.  (If there
804/// are multiple compatible partial paths, we concatenate each of them separately, resulting in
805/// more than one extension for the current path.)
806///
807/// We perform this processing in _phases_.  At the start of each phase, we have a _current set_ of
808/// partial paths that need to be processed.  As we extend those partial paths, we add the
809/// extensions to the set of partial paths to process in the _next_ phase.  Phases are processed
810/// one at a time, each time you invoke the [`process_next_phase`][] method.
811///
812/// [`process_next_phase`]: #method.process_next_phase
813///
814/// After each phase has completed, you can use the [`previous_phase_partial_paths`][] method to
815/// retrieve all of the partial paths that were discovered during that phase.  That gives you a
816/// chance to add to the `Database` all of the other partial paths that we might need to extend
817/// those partial paths with before invoking the next phase.
818///
819/// [`previous_phase_partial_paths`]: #method.previous_phase_partial_paths
820///
821/// If you don't care about this phasing nonsense, you can instead preload your `Database` with all
822/// possible partial paths, and run the forward partial path stitching algorithm all the way to
823/// completion, using the [`find_all_complete_partial_paths`][] method.
824///
825/// [`find_all_complete_partial_paths`]: #method.find_all_complete_partial_paths
826pub struct ForwardPartialPathStitcher<H> {
827    candidates: Vec<H>,
828    extensions: Vec<(PartialPath, AppendingCycleDetector<H>)>,
829    queue: VecDeque<(PartialPath, AppendingCycleDetector<H>, bool)>,
830    // tracks the number of initial paths in the queue because we do not want call
831    // extend_until on those
832    initial_paths_in_queue: usize,
833    // next_iteration is a tuple of queues instead of an queue of tuples so that the path queue
834    // can be cheaply exposed through the C API as a continuous memory block
835    next_iteration: (
836        VecDeque<PartialPath>,
837        VecDeque<AppendingCycleDetector<H>>,
838        VecDeque<bool>,
839    ),
840    appended_paths: Appendables<H>,
841    similar_path_detector: Option<SimilarPathDetector<PartialPath>>,
842    check_only_join_nodes: bool,
843    max_work_per_phase: usize,
844    initial_paths: usize,
845    stats: Option<Stats>,
846    #[cfg(feature = "copious-debugging")]
847    phase_number: usize,
848}
849
850impl<H> ForwardPartialPathStitcher<H> {
851    /// Creates a new forward partial path stitcher that is "seeded" with a set of initial partial
852    /// paths. If the sticher is used to find complete paths, it is the responsibility of the caller
853    /// to ensure precondition variables are eliminated by calling [`PartialPath::eliminate_precondition_stack_variables`][].
854    pub fn from_partial_paths<I>(
855        _graph: &StackGraph,
856        _partials: &mut PartialPaths,
857        initial_partial_paths: I,
858    ) -> Self
859    where
860        I: IntoIterator<Item = PartialPath>,
861    {
862        let mut appended_paths = Appendables::new();
863        let next_iteration: (VecDeque<_>, VecDeque<_>, VecDeque<_>) = initial_partial_paths
864            .into_iter()
865            .map(|p| {
866                let c = AppendingCycleDetector::from(&mut appended_paths, p.clone().into());
867                (p, c, false)
868            })
869            .multiunzip();
870        let initial_paths = next_iteration.0.len();
871        Self {
872            candidates: Vec::new(),
873            extensions: Vec::new(),
874            queue: VecDeque::new(),
875            initial_paths_in_queue: initial_paths,
876            next_iteration,
877            appended_paths,
878            // By default, all paths are checked for similarity
879            similar_path_detector: Some(SimilarPathDetector::new()),
880            // By default, all nodes are checked for cycles and (if enabled) similarity
881            check_only_join_nodes: false,
882            // By default, there's no artificial bound on the amount of work done per phase
883            max_work_per_phase: usize::MAX,
884            initial_paths,
885            stats: None,
886            #[cfg(feature = "copious-debugging")]
887            phase_number: 1,
888        }
889    }
890
891    /// Sets whether similar path detection should be enabled during path stitching. Paths are similar
892    /// if start and end node, and pre- and postconditions are the same. The presence of similar paths
893    /// can lead to exponential blow up during path stitching. Similar path detection is enabled by
894    /// default.
895    pub fn set_similar_path_detection(&mut self, detect_similar_paths: bool) {
896        if !detect_similar_paths {
897            self.similar_path_detector = None;
898        } else if self.similar_path_detector.is_none() {
899            let mut similar_path_detector = SimilarPathDetector::new();
900            similar_path_detector.set_collect_stats(self.stats.is_some());
901            self.similar_path_detector = Some(similar_path_detector);
902        }
903    }
904
905    /// Sets whether all nodes are checked for cycles and (if enabled) similar paths, or only nodes with multiple
906    /// incoming candidates. Checking only join nodes is **unsafe** unless the database of candidates is stable
907    /// between all stitching phases. If paths are added to the database from one phase to another, for example if
908    /// paths are dynamically loaded from storage, setting this to true is incorrect and might lead to non-termination!
909    pub fn set_check_only_join_nodes(&mut self, check_only_join_nodes: bool) {
910        self.check_only_join_nodes = check_only_join_nodes;
911    }
912
913    /// Sets the maximum amount of work that can be performed during each phase of the algorithm.
914    /// By bounding our work this way, you can ensure that it's not possible for our CPU-bound
915    /// algorithm to starve any worker threads or processes that you might be using.  If you don't
916    /// call this method, then we allow ourselves to process all of the extensions of all of the
917    /// paths found in the previous phase, with no additional bound.
918    pub fn set_max_work_per_phase(&mut self, max_work_per_phase: usize) {
919        self.max_work_per_phase = max_work_per_phase;
920    }
921
922    /// Sets whether to collect statistics during stitching.
923    pub fn set_collect_stats(&mut self, collect_stats: bool) {
924        if !collect_stats {
925            self.stats = None;
926        } else if self.stats.is_none() {
927            let mut stats = Stats::default();
928            stats.initial_paths.record(self.initial_paths);
929            self.stats = Some(stats);
930        }
931        if let Some(similar_path_detector) = &mut self.similar_path_detector {
932            similar_path_detector.set_collect_stats(collect_stats);
933        }
934    }
935
936    pub fn into_stats(mut self) -> Stats {
937        if let (Some(stats), Some(similar_path_detector)) =
938            (&mut self.stats, self.similar_path_detector)
939        {
940            stats.similar_paths_stats = similar_path_detector.stats();
941        }
942        self.stats.unwrap_or_default()
943    }
944}
945
946impl<H: Clone> ForwardPartialPathStitcher<H> {
947    /// Returns an iterator of all of the (possibly incomplete) partial paths that were encountered
948    /// during the most recent phase of the algorithm.
949    pub fn previous_phase_partial_paths(&self) -> impl Iterator<Item = &PartialPath> + '_ {
950        self.next_iteration.0.iter()
951    }
952
953    /// Returns a slice of all of the (possibly incomplete) partial paths that were encountered
954    /// during the most recent phase of the algorithm.
955    pub fn previous_phase_partial_paths_slice(&mut self) -> &[PartialPath] {
956        self.next_iteration.0.make_contiguous();
957        self.next_iteration.0.as_slices().0
958    }
959
960    /// Returns a mutable slice of all of the (possibly incomplete) partial paths that were
961    /// encountered during the most recent phase of the algorithm.
962    pub fn previous_phase_partial_paths_slice_mut(&mut self) -> &mut [PartialPath] {
963        self.next_iteration.0.make_contiguous();
964        self.next_iteration.0.as_mut_slices().0
965    }
966
967    /// Attempts to extend one partial path as part of the algorithm.  When calling this function,
968    /// you are responsible for ensuring that `db` already contains all of the possible appendables
969    /// that we might want to extend `partial_path` with.
970    fn extend<A, Db, C, Err>(
971        &mut self,
972        candidates: &mut C,
973        partial_path: &PartialPath,
974        cycle_detector: AppendingCycleDetector<H>,
975        has_split: bool,
976    ) -> usize
977    where
978        A: Appendable,
979        Db: ToAppendable<H, A>,
980        C: ForwardCandidates<H, A, Db, Err>,
981    {
982        let check_cycle = !self.check_only_join_nodes
983            || partial_path.start_node == partial_path.end_node
984            || candidates.get_joining_candidate_degree(partial_path) == Degree::Multiple;
985
986        let (graph, partials, db) = candidates.get_graph_partials_and_db();
987        copious_debugging!("    Extend {}", partial_path.display(graph, partials));
988
989        if check_cycle {
990            // Check is path is cyclic, in which case we do not extend it. We only do this if the start and end nodes are the same,
991            // or the current end node has multiple incoming edges. If neither of these hold, the path cannot end in a cycle.
992            let has_precondition_variables = partial_path.symbol_stack_precondition.has_variable()
993                || partial_path.scope_stack_precondition.has_variable();
994            let cycles = cycle_detector
995                .is_cyclic(graph, partials, db, &mut self.appended_paths)
996                .expect("cyclic test failed when stitching partial paths");
997            let cyclic = match has_precondition_variables {
998                // If the precondition has no variables, we allow cycles that strengthen the
999                // precondition, because we know they cannot strengthen the precondition of
1000                // the overall path.
1001                false => !cycles
1002                    .into_iter()
1003                    .all(|c| c == Cyclicity::StrengthensPrecondition),
1004                // If the precondition has variables, do not allow any cycles, not even those
1005                // that strengthen the precondition. This is more strict than necessary. Better
1006                // might be to disallow precondition strengthening cycles only if they would
1007                // strengthen the overall path precondition.
1008                true => !cycles.is_empty(),
1009            };
1010            if cyclic {
1011                copious_debugging!("      is discontinued: cyclic");
1012                return 0;
1013            }
1014        }
1015
1016        // find candidates to append
1017        self.candidates.clear();
1018        candidates.get_forward_candidates(partial_path, &mut self.candidates);
1019        let (graph, partials, db) = candidates.get_graph_partials_and_db();
1020
1021        // try to extend path with candidates
1022        let candidate_count = self.candidates.len();
1023        self.extensions.clear();
1024        self.extensions.reserve(candidate_count);
1025        for candidate in &self.candidates {
1026            let appendable = db.get_appendable(candidate);
1027            copious_debugging!("      with {}", appendable.display(graph, partials));
1028
1029            let mut new_partial_path = partial_path.clone();
1030            let mut new_cycle_detector = cycle_detector.clone();
1031            // If there are errors concatenating these partial paths, or resolving the resulting
1032            // partial path, just skip the extension — it's not a fatal error.
1033            #[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))]
1034            {
1035                if let Err(err) = appendable.append_to(graph, partials, &mut new_partial_path) {
1036                    copious_debugging!("        is invalid: {:?}", err);
1037                    continue;
1038                }
1039            }
1040            new_cycle_detector.append(&mut self.appended_paths, candidate.clone());
1041            copious_debugging!("        is {}", new_partial_path.display(graph, partials));
1042            self.extensions.push((new_partial_path, new_cycle_detector));
1043        }
1044
1045        let extension_count = self.extensions.len();
1046        let new_has_split = has_split || self.extensions.len() > 1;
1047        self.next_iteration.0.reserve(extension_count);
1048        self.next_iteration.1.reserve(extension_count);
1049        self.next_iteration.2.reserve(extension_count);
1050        for (new_partial_path, new_cycle_detector) in self.extensions.drain(..) {
1051            let check_similar_path = new_has_split
1052                && (!self.check_only_join_nodes
1053                    || candidates.get_joining_candidate_degree(&new_partial_path)
1054                        == Degree::Multiple);
1055            let (graph, partials, _) = candidates.get_graph_partials_and_db();
1056            if check_similar_path {
1057                if let Some(similar_path_detector) = &mut self.similar_path_detector {
1058                    if similar_path_detector.add_path(
1059                        graph,
1060                        partials,
1061                        &new_partial_path,
1062                        |ps, left, right| {
1063                            if !left.equals(ps, right) {
1064                                None
1065                            } else {
1066                                if left.shadows(ps, right) {
1067                                    Some(Ordering::Less)
1068                                } else if right.shadows(ps, left) {
1069                                    Some(Ordering::Greater)
1070                                } else {
1071                                    Some(Ordering::Equal)
1072                                }
1073                            }
1074                        },
1075                    ) {
1076                        copious_debugging!(
1077                            " extension {}",
1078                            new_partial_path.display(graph, partials)
1079                        );
1080                        copious_debugging!("        is rejected: too many similar");
1081                        continue;
1082                    }
1083                }
1084            }
1085
1086            self.next_iteration.0.push(new_partial_path);
1087            self.next_iteration.1.push(new_cycle_detector);
1088            self.next_iteration.2.push(new_has_split);
1089        }
1090
1091        if let Some(stats) = &mut self.stats {
1092            let (graph, _, _) = candidates.get_graph_partials_and_db();
1093            let end_node = &graph[partial_path.end_node];
1094            if end_node.is_root() {
1095                stats.candidates_per_root_path.record(candidate_count);
1096                stats.extensions_per_root_path.record(extension_count);
1097                stats.root_visits += 1;
1098            } else {
1099                stats.candidates_per_node_path.record(candidate_count);
1100                stats.extensions_per_node_path.record(extension_count);
1101                stats.node_visits.record(end_node.id());
1102            }
1103            if extension_count == 0 {
1104                stats.terminal_path_lengh.record(partial_path.edges.len());
1105            }
1106        }
1107        candidate_count
1108    }
1109
1110    /// Returns whether the algorithm has completed.
1111    pub fn is_complete(&self) -> bool {
1112        self.queue.is_empty() && self.next_iteration.0.is_empty()
1113    }
1114
1115    /// Runs the next phase of the algorithm.  We will have built up a set of incomplete partial
1116    /// paths during the _previous_ phase.  Before calling this function, you must ensure that `db`
1117    /// contains all of the possible appendables that we might want to extend any of those
1118    /// candidate partial paths with.
1119    ///
1120    /// After this method returns, you can use [`previous_phase_partial_paths`][] to retrieve a
1121    /// list of the (possibly incomplete) partial paths that were encountered during this phase.
1122    ///
1123    /// The `extend_while` closure is used to control whether the extended paths are further extended
1124    /// or not. It is not called on the initial paths.
1125    ///
1126    /// [`previous_phase_partial_paths`]: #method.previous_phase_partial_paths
1127    pub fn process_next_phase<A, Db, C, E, Err>(&mut self, candidates: &mut C, extend_while: E)
1128    where
1129        A: Appendable,
1130        Db: ToAppendable<H, A>,
1131        C: ForwardCandidates<H, A, Db, Err>,
1132        E: Fn(&StackGraph, &mut PartialPaths, &PartialPath) -> bool,
1133    {
1134        copious_debugging!("==> Start phase {}", self.phase_number);
1135        self.queue.extend(izip!(
1136            self.next_iteration.0.drain(..),
1137            self.next_iteration.1.drain(..),
1138            self.next_iteration.2.drain(..),
1139        ));
1140        if let Some(stats) = &mut self.stats {
1141            stats.queued_paths_per_phase.record(self.queue.len());
1142        }
1143        let mut work_performed = 0;
1144        while let Some((partial_path, cycle_detector, has_split)) = self.queue.pop_front() {
1145            let (graph, partials, _) = candidates.get_graph_partials_and_db();
1146            copious_debugging!(
1147                "--> Candidate partial path {}",
1148                partial_path.display(graph, partials)
1149            );
1150            if self.initial_paths_in_queue > 0 {
1151                self.initial_paths_in_queue -= 1;
1152            } else if !extend_while(graph, partials, &partial_path) {
1153                copious_debugging!(
1154                    "    Do not extend {}",
1155                    partial_path.display(graph, partials)
1156                );
1157                continue;
1158            }
1159            work_performed += self.extend(candidates, &partial_path, cycle_detector, has_split);
1160            if work_performed >= self.max_work_per_phase {
1161                break;
1162            }
1163        }
1164        if let Some(stats) = &mut self.stats {
1165            stats.processed_paths_per_phase.record(work_performed);
1166        }
1167
1168        #[cfg(feature = "copious-debugging")]
1169        {
1170            if let Some(similar_path_detector) = &self.similar_path_detector {
1171                copious_debugging!(
1172                    "    Max similar path bucket size: {}",
1173                    similar_path_detector.max_bucket_size()
1174                );
1175            }
1176            copious_debugging!("==> End phase {}", self.phase_number);
1177            self.phase_number += 1;
1178        }
1179    }
1180}
1181
1182impl ForwardPartialPathStitcher<Edge> {
1183    /// Finds a minimal set of partial paths in a file, calling the `visit` closure for each one.
1184    ///
1185    /// This function ensures that the set of visited partial paths
1186    ///  (a) is minimal, no path can be constructed by stitching other paths in the set, and
1187    ///  (b) covers all complete paths, from references to definitions, when used for path stitching
1188    ///
1189    /// This function will not return until all reachable partial paths have been processed, so
1190    /// your database must already contain all partial paths that might be needed.  If you have a
1191    /// very large stack graph stored in some other storage system, and want more control over
1192    /// lazily loading only the necessary pieces, then you should code up your own loop that calls
1193    /// [`process_next_phase`][] manually.
1194    ///
1195    /// Caveat: Edges between nodes of different files are not used. Hence the returned set of partial
1196    /// paths will not cover paths going through those edges.
1197    ///
1198    /// [`process_next_phase`]: #method.process_next_phase
1199    pub fn find_minimal_partial_path_set_in_file<F>(
1200        graph: &StackGraph,
1201        partials: &mut PartialPaths,
1202        file: Handle<File>,
1203        config: StitcherConfig,
1204        cancellation_flag: &dyn CancellationFlag,
1205        mut visit: F,
1206    ) -> Result<Stats, CancellationError>
1207    where
1208        F: FnMut(&StackGraph, &mut PartialPaths, &PartialPath),
1209    {
1210        fn as_complete_as_necessary(graph: &StackGraph, path: &PartialPath) -> bool {
1211            path.starts_at_endpoint(graph)
1212                && (path.ends_at_endpoint(graph) || path.ends_in_jump(graph))
1213        }
1214
1215        let initial_paths = graph
1216            .nodes_for_file(file)
1217            .chain(std::iter::once(StackGraph::root_node()))
1218            .filter(|node| graph[*node].is_endpoint())
1219            .map(|node| PartialPath::from_node(graph, partials, node))
1220            .collect::<Vec<_>>();
1221        let mut stitcher =
1222            ForwardPartialPathStitcher::from_partial_paths(graph, partials, initial_paths);
1223        config.apply(&mut stitcher);
1224        stitcher.set_check_only_join_nodes(true);
1225
1226        let mut accepted_path_length = FrequencyDistribution::default();
1227        while !stitcher.is_complete() {
1228            cancellation_flag.check("finding complete partial paths")?;
1229            stitcher.process_next_phase(
1230                &mut GraphEdgeCandidates::new(graph, partials, Some(file)),
1231                |g, _ps, p| !as_complete_as_necessary(g, p),
1232            );
1233            for path in stitcher.previous_phase_partial_paths() {
1234                if as_complete_as_necessary(graph, path) {
1235                    accepted_path_length.record(path.edges.len());
1236                    visit(graph, partials, path);
1237                }
1238            }
1239        }
1240
1241        Ok(Stats {
1242            accepted_path_length,
1243            ..stitcher.into_stats()
1244        })
1245    }
1246}
1247
1248impl<H: Clone> ForwardPartialPathStitcher<H> {
1249    /// Finds all complete partial paths that are reachable from a set of starting nodes,
1250    /// building them up by stitching together partial paths from this database, and calling
1251    /// the `visit` closure on each one.
1252    ///
1253    /// This function will not return until all reachable partial paths have been processed, so
1254    /// your database must already contain all partial paths that might be needed.  If you have a
1255    /// very large stack graph stored in some other storage system, and want more control over
1256    /// lazily loading only the necessary pieces, then you should code up your own loop that calls
1257    /// [`process_next_phase`][] manually.
1258    ///
1259    /// [`process_next_phase`]: #method.process_next_phase
1260    pub fn find_all_complete_partial_paths<I, F, A, Db, C, Err>(
1261        candidates: &mut C,
1262        starting_nodes: I,
1263        config: StitcherConfig,
1264        cancellation_flag: &dyn CancellationFlag,
1265        mut visit: F,
1266    ) -> Result<Stats, Err>
1267    where
1268        I: IntoIterator<Item = Handle<Node>>,
1269        A: Appendable,
1270        Db: ToAppendable<H, A>,
1271        C: ForwardCandidates<H, A, Db, Err>,
1272        F: FnMut(&StackGraph, &mut PartialPaths, &PartialPath),
1273        Err: std::convert::From<CancellationError>,
1274    {
1275        let (graph, partials, _) = candidates.get_graph_partials_and_db();
1276        let initial_paths = starting_nodes
1277            .into_iter()
1278            .filter(|n| graph[*n].is_reference())
1279            .map(|n| {
1280                let mut p = PartialPath::from_node(graph, partials, n);
1281                p.eliminate_precondition_stack_variables(partials);
1282                p
1283            })
1284            .collect::<Vec<_>>();
1285        let mut stitcher =
1286            ForwardPartialPathStitcher::from_partial_paths(graph, partials, initial_paths);
1287        config.apply(&mut stitcher);
1288        stitcher.set_check_only_join_nodes(true);
1289
1290        let mut accepted_path_length = FrequencyDistribution::default();
1291        while !stitcher.is_complete() {
1292            cancellation_flag.check("finding complete partial paths")?;
1293            for path in stitcher.previous_phase_partial_paths() {
1294                candidates.load_forward_candidates(path, cancellation_flag)?;
1295            }
1296            stitcher.process_next_phase(candidates, |_, _, _| true);
1297            let (graph, partials, _) = candidates.get_graph_partials_and_db();
1298            for path in stitcher.previous_phase_partial_paths() {
1299                if path.is_complete(graph) {
1300                    accepted_path_length.record(path.edges.len());
1301                    visit(graph, partials, path);
1302                }
1303            }
1304        }
1305
1306        Ok(Stats {
1307            accepted_path_length,
1308            ..stitcher.into_stats()
1309        })
1310    }
1311}
1312
1313#[derive(Clone, Debug, Default)]
1314pub struct Stats {
1315    /// The distribution of the number of initial paths
1316    pub initial_paths: FrequencyDistribution<usize>,
1317    /// The distribution of the number of queued paths per stitching phase
1318    pub queued_paths_per_phase: FrequencyDistribution<usize>,
1319    /// The distribution of the number of processed paths per stitching phase
1320    pub processed_paths_per_phase: FrequencyDistribution<usize>,
1321    /// The distribution of the length of accepted paths
1322    pub accepted_path_length: FrequencyDistribution<usize>,
1323    /// The distribution of the maximal length of paths (when they cannot be extended more)
1324    pub terminal_path_lengh: FrequencyDistribution<usize>,
1325    /// The distribution of the number of candidates for paths ending in a regular node
1326    pub candidates_per_node_path: FrequencyDistribution<usize>,
1327    /// The distribution of the number of candidates for paths ending in the root node
1328    pub candidates_per_root_path: FrequencyDistribution<usize>,
1329    /// The distribution of the number of extensions (accepted candidates) for paths ending in a regular node
1330    pub extensions_per_node_path: FrequencyDistribution<usize>,
1331    /// The distribution of the number of extensions (accepted candidates) for paths ending in the root node
1332    pub extensions_per_root_path: FrequencyDistribution<usize>,
1333    /// The number of times the root node is visited
1334    pub root_visits: usize,
1335    /// The distribution of the number of times a regular node is visited
1336    pub node_visits: FrequencyDistribution<crate::graph::NodeID>,
1337    /// The distribution of the number of similar paths between node pairs.
1338    pub similar_paths_stats: SimilarPathStats,
1339}
1340
1341impl std::ops::AddAssign<Self> for Stats {
1342    fn add_assign(&mut self, rhs: Self) {
1343        self.initial_paths += rhs.initial_paths;
1344        self.queued_paths_per_phase += rhs.queued_paths_per_phase;
1345        self.processed_paths_per_phase += rhs.processed_paths_per_phase;
1346        self.accepted_path_length += rhs.accepted_path_length;
1347        self.terminal_path_lengh += rhs.terminal_path_lengh;
1348        self.candidates_per_node_path += rhs.candidates_per_node_path;
1349        self.candidates_per_root_path += rhs.candidates_per_root_path;
1350        self.extensions_per_node_path += rhs.extensions_per_node_path;
1351        self.extensions_per_root_path += rhs.extensions_per_root_path;
1352        self.root_visits += rhs.root_visits;
1353        self.node_visits += rhs.node_visits;
1354        self.similar_paths_stats += rhs.similar_paths_stats;
1355    }
1356}
1357
1358impl std::ops::AddAssign<&Self> for Stats {
1359    fn add_assign(&mut self, rhs: &Self) {
1360        self.initial_paths += &rhs.initial_paths;
1361        self.processed_paths_per_phase += &rhs.processed_paths_per_phase;
1362        self.accepted_path_length += &rhs.accepted_path_length;
1363        self.terminal_path_lengh += &rhs.terminal_path_lengh;
1364        self.candidates_per_node_path += &rhs.candidates_per_node_path;
1365        self.candidates_per_root_path += &rhs.candidates_per_root_path;
1366        self.extensions_per_node_path += &rhs.extensions_per_node_path;
1367        self.extensions_per_root_path += &rhs.extensions_per_root_path;
1368        self.root_visits += rhs.root_visits;
1369        self.node_visits += &rhs.node_visits;
1370        self.similar_paths_stats += &rhs.similar_paths_stats;
1371    }
1372}
1373
1374/// Configuration for partial path stitchers.
1375#[derive(Clone, Copy, Debug)]
1376pub struct StitcherConfig {
1377    /// Enables similar path detection during path stitching.
1378    detect_similar_paths: bool,
1379    /// Collect statistics about path stitching.
1380    collect_stats: bool,
1381}
1382
1383impl StitcherConfig {
1384    pub fn detect_similar_paths(&self) -> bool {
1385        self.detect_similar_paths
1386    }
1387
1388    pub fn with_detect_similar_paths(mut self, detect_similar_paths: bool) -> Self {
1389        self.detect_similar_paths = detect_similar_paths;
1390        self
1391    }
1392
1393    pub fn collect_stats(&self) -> bool {
1394        self.collect_stats
1395    }
1396
1397    pub fn with_collect_stats(mut self, collect_stats: bool) -> Self {
1398        self.collect_stats = collect_stats;
1399        self
1400    }
1401}
1402
1403impl StitcherConfig {
1404    fn apply<H>(&self, stitcher: &mut ForwardPartialPathStitcher<H>) {
1405        stitcher.set_similar_path_detection(self.detect_similar_paths);
1406        stitcher.set_collect_stats(self.collect_stats);
1407    }
1408}
1409
1410impl Default for StitcherConfig {
1411    fn default() -> Self {
1412        Self {
1413            detect_similar_paths: true,
1414            collect_stats: false,
1415        }
1416    }
1417}