use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::VecDeque;
#[cfg(feature = "copious-debugging")]
use std::fmt::Display;
use itertools::izip;
use itertools::Itertools;
use crate::arena::Arena;
use crate::arena::Handle;
use crate::arena::HandleSet;
use crate::arena::List;
use crate::arena::ListArena;
use crate::arena::ListCell;
use crate::arena::SupplementalArena;
use crate::cycles::Appendables;
use crate::cycles::AppendingCycleDetector;
use crate::cycles::SimilarPathDetector;
use crate::cycles::SimilarPathStats;
use crate::graph::Degree;
use crate::graph::Edge;
use crate::graph::File;
use crate::graph::Node;
use crate::graph::StackGraph;
use crate::graph::Symbol;
use crate::partial::Cyclicity;
use crate::partial::PartialPath;
use crate::partial::PartialPaths;
use crate::partial::PartialSymbolStack;
use crate::paths::Extend;
use crate::paths::PathResolutionError;
use crate::stats::FrequencyDistribution;
use crate::CancellationError;
use crate::CancellationFlag;
pub trait Appendable {
fn append_to(
&self,
graph: &StackGraph,
partials: &mut PartialPaths,
path: &mut PartialPath,
) -> Result<(), PathResolutionError>;
fn start_node(&self) -> Handle<Node>;
fn end_node(&self) -> Handle<Node>;
fn display<'a>(
&'a self,
graph: &'a StackGraph,
partials: &'a mut PartialPaths,
) -> Box<dyn std::fmt::Display + 'a>;
}
impl Appendable for Edge {
fn append_to(
&self,
graph: &StackGraph,
partials: &mut PartialPaths,
path: &mut PartialPath,
) -> Result<(), PathResolutionError> {
path.resolve_to_node(graph, partials, self.source)?;
path.append(graph, partials, *self)
}
fn start_node(&self) -> Handle<Node> {
self.source
}
fn end_node(&self) -> Handle<Node> {
self.sink
}
fn display<'a>(
&'a self,
graph: &'a StackGraph,
_partials: &'a mut PartialPaths,
) -> Box<dyn std::fmt::Display + 'a> {
Box::new(format!(
"{} -> {}",
self.source.display(graph),
self.sink.display(graph)
))
}
}
impl Appendable for PartialPath {
fn append_to(
&self,
graph: &StackGraph,
partials: &mut PartialPaths,
path: &mut PartialPath,
) -> Result<(), PathResolutionError> {
path.resolve_to_node(graph, partials, self.start_node)?;
path.ensure_no_overlapping_variables(partials, self);
path.concatenate(graph, partials, self)?;
Ok(())
}
fn start_node(&self) -> Handle<Node> {
self.start_node
}
fn end_node(&self) -> Handle<Node> {
self.end_node
}
fn display<'a>(
&'a self,
graph: &'a StackGraph,
partials: &'a mut PartialPaths,
) -> Box<dyn std::fmt::Display + 'a> {
Box::new(self.display(graph, partials))
}
}
pub trait ToAppendable<H, A>
where
A: Appendable,
{
fn get_appendable<'a>(&'a self, handle: &'a H) -> &'a A;
}
pub trait ForwardCandidates<H, A, Db, Err>
where
A: Appendable,
Db: ToAppendable<H, A>,
{
fn load_forward_candidates(
&mut self,
_path: &PartialPath,
_cancellation_flag: &dyn CancellationFlag,
) -> Result<(), Err> {
Ok(())
}
fn get_forward_candidates<R>(&mut self, path: &PartialPath, result: &mut R)
where
R: std::iter::Extend<H>;
fn get_joining_candidate_degree(&self, path: &PartialPath) -> Degree;
fn get_graph_partials_and_db(&mut self) -> (&StackGraph, &mut PartialPaths, &Db);
}
pub struct GraphEdgeCandidates<'a> {
graph: &'a StackGraph,
partials: &'a mut PartialPaths,
file: Option<Handle<File>>,
edges: GraphEdges,
}
impl<'a> GraphEdgeCandidates<'a> {
pub fn new(
graph: &'a StackGraph,
partials: &'a mut PartialPaths,
file: Option<Handle<File>>,
) -> Self {
Self {
graph,
partials,
file,
edges: GraphEdges,
}
}
}
impl ForwardCandidates<Edge, Edge, GraphEdges, CancellationError> for GraphEdgeCandidates<'_> {
fn get_forward_candidates<R>(&mut self, path: &PartialPath, result: &mut R)
where
R: std::iter::Extend<Edge>,
{
result.extend(self.graph.outgoing_edges(path.end_node).filter(|e| {
self.file
.map_or(true, |file| self.graph[e.sink].is_in_file(file))
}));
}
fn get_joining_candidate_degree(&self, path: &PartialPath) -> Degree {
self.graph.incoming_edge_degree(path.end_node)
}
fn get_graph_partials_and_db(&mut self) -> (&StackGraph, &mut PartialPaths, &GraphEdges) {
(self.graph, self.partials, &self.edges)
}
}
pub struct GraphEdges;
impl ToAppendable<Edge, Edge> for GraphEdges {
fn get_appendable<'a>(&'a self, edge: &'a Edge) -> &'a Edge {
edge
}
}
pub struct Database {
pub(crate) partial_paths: Arena<PartialPath>,
pub(crate) local_nodes: HandleSet<Node>,
symbol_stack_keys: ListArena<Handle<Symbol>>,
symbol_stack_key_cache: HashMap<SymbolStackCacheKey, SymbolStackKeyHandle>,
paths_by_start_node: SupplementalArena<Node, Vec<Handle<PartialPath>>>,
root_paths_by_precondition_prefix:
SupplementalArena<SymbolStackKeyCell, Vec<Handle<PartialPath>>>,
root_paths_by_precondition_with_variable:
SupplementalArena<SymbolStackKeyCell, Vec<Handle<PartialPath>>>,
root_paths_by_precondition_without_variable:
SupplementalArena<SymbolStackKeyCell, Vec<Handle<PartialPath>>>,
incoming_paths: SupplementalArena<Node, Degree>,
}
impl Database {
pub fn new() -> Database {
Database {
partial_paths: Arena::new(),
local_nodes: HandleSet::new(),
symbol_stack_keys: List::new_arena(),
symbol_stack_key_cache: HashMap::new(),
paths_by_start_node: SupplementalArena::new(),
root_paths_by_precondition_prefix: SupplementalArena::new(),
root_paths_by_precondition_with_variable: SupplementalArena::new(),
root_paths_by_precondition_without_variable: SupplementalArena::new(),
incoming_paths: SupplementalArena::new(),
}
}
#[cfg_attr(not(feature = "storage"), allow(dead_code))]
pub(crate) fn clear(&mut self) {
self.partial_paths.clear();
self.local_nodes.clear();
self.symbol_stack_keys.clear();
self.symbol_stack_key_cache.clear();
self.paths_by_start_node.clear();
self.root_paths_by_precondition_prefix.clear();
self.root_paths_by_precondition_with_variable.clear();
self.root_paths_by_precondition_without_variable.clear();
self.incoming_paths.clear();
}
pub fn add_partial_path(
&mut self,
graph: &StackGraph,
partials: &mut PartialPaths,
path: PartialPath,
) -> Handle<PartialPath> {
let start_node = path.start_node;
let end_node = path.end_node;
copious_debugging!(
" Add {} path to database {}",
if graph[start_node].is_root() {
"root"
} else {
"node"
},
path.display(graph, partials)
);
let symbol_stack_precondition = path.symbol_stack_precondition;
let handle = self.partial_paths.add(path);
if graph[start_node].is_root() {
let mut key = SymbolStackKey::from_partial_symbol_stack(
partials,
self,
symbol_stack_precondition,
);
if !key.is_empty() {
match symbol_stack_precondition.has_variable() {
true => self.root_paths_by_precondition_with_variable[key.back_handle()]
.push(handle),
false => self.root_paths_by_precondition_without_variable[key.back_handle()]
.push(handle),
}
}
while key.pop_back(self).is_some() && !key.is_empty() {
self.root_paths_by_precondition_prefix[key.back_handle()].push(handle);
}
} else {
self.paths_by_start_node[start_node].push(handle);
}
self.incoming_paths[end_node] += Degree::One;
handle
}
pub fn find_candidate_partial_paths<R>(
&mut self,
graph: &StackGraph,
partials: &mut PartialPaths,
path: &PartialPath,
result: &mut R,
) where
R: std::iter::Extend<Handle<PartialPath>>,
{
if graph[path.end_node].is_root() {
self.find_candidate_partial_paths_from_root(
graph,
partials,
Some(path.symbol_stack_postcondition),
result,
);
} else {
self.find_candidate_partial_paths_from_node(graph, partials, path.end_node, result);
}
}
#[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))]
pub fn find_candidate_partial_paths_from_root<R>(
&mut self,
graph: &StackGraph,
partials: &mut PartialPaths,
symbol_stack: Option<PartialSymbolStack>,
result: &mut R,
) where
R: std::iter::Extend<Handle<PartialPath>>,
{
match symbol_stack {
Some(symbol_stack) => {
let mut key =
SymbolStackKey::from_partial_symbol_stack(partials, self, symbol_stack);
copious_debugging!(
" Search for symbol stack <{}>",
key.display(graph, self)
);
if let Some(paths) = self
.root_paths_by_precondition_without_variable
.get(key.back_handle())
{
#[cfg(feature = "copious-debugging")]
{
for path in paths {
copious_debugging!(
" Found path with exact stack {}",
self[*path].display(graph, partials)
);
}
}
result.extend(paths.iter().copied());
}
if symbol_stack.has_variable() {
if let Some(paths) = self
.root_paths_by_precondition_prefix
.get(key.back_handle())
{
#[cfg(feature = "copious-debugging")]
{
for path in paths {
copious_debugging!(
" Found path with smaller stack {}",
self[*path].display(graph, partials)
);
}
}
result.extend(paths.iter().copied());
}
}
loop {
if let Some(paths) = self
.root_paths_by_precondition_with_variable
.get(key.back_handle())
{
#[cfg(feature = "copious-debugging")]
{
for path in paths {
copious_debugging!(
" Found path with smaller stack {}",
self[*path].display(graph, partials)
);
}
}
result.extend(paths.iter().copied());
}
if key.pop_back(self).is_none() {
break;
}
}
}
None => {
copious_debugging!(" Search for all root paths");
for (_, paths) in self
.root_paths_by_precondition_with_variable
.iter()
.chain(self.root_paths_by_precondition_without_variable.iter())
{
#[cfg(feature = "copious-debugging")]
{
for path in paths {
copious_debugging!(
" Found path {}",
self[*path].display(graph, partials)
);
}
}
result.extend(paths.iter().copied());
}
}
}
}
#[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))]
pub fn find_candidate_partial_paths_from_node<R>(
&self,
graph: &StackGraph,
partials: &mut PartialPaths,
start_node: Handle<Node>,
result: &mut R,
) where
R: std::iter::Extend<Handle<PartialPath>>,
{
copious_debugging!(" Search for start node {}", start_node.display(graph));
if let Some(paths) = self.paths_by_start_node.get(start_node) {
#[cfg(feature = "copious-debugging")]
{
for path in paths {
copious_debugging!(
" Found path {}",
self[*path].display(graph, partials)
);
}
}
result.extend(paths.iter().copied());
}
}
pub fn get_incoming_path_degree(&self, end_node: Handle<Node>) -> Degree {
self.incoming_paths[end_node]
}
pub fn find_local_nodes(&mut self) {
self.local_nodes.clear();
for handle in self.iter_partial_paths() {
self.local_nodes.add(self[handle].start_node);
self.local_nodes.add(self[handle].end_node);
}
let mut nonlocal_start_nodes = HandleSet::new();
let mut nonlocal_end_nodes = HandleSet::new();
self.local_nodes.remove(StackGraph::root_node());
nonlocal_start_nodes.add(StackGraph::root_node());
nonlocal_end_nodes.add(StackGraph::root_node());
self.local_nodes.remove(StackGraph::jump_to_node());
nonlocal_start_nodes.add(StackGraph::jump_to_node());
nonlocal_end_nodes.add(StackGraph::jump_to_node());
let mut keep_checking = true;
while keep_checking {
keep_checking = false;
for handle in self.iter_partial_paths() {
let start_node = self[handle].start_node;
let end_node = self[handle].end_node;
let start_node_is_nonlocal = nonlocal_start_nodes.contains(start_node);
let end_node_is_nonlocal = nonlocal_start_nodes.contains(end_node);
if start_node_is_nonlocal && !end_node_is_nonlocal {
keep_checking = true;
nonlocal_start_nodes.add(end_node);
self.local_nodes.remove(end_node);
}
let start_node_is_nonlocal = nonlocal_end_nodes.contains(start_node);
let end_node_is_nonlocal = nonlocal_end_nodes.contains(end_node);
if !start_node_is_nonlocal && end_node_is_nonlocal {
keep_checking = true;
nonlocal_end_nodes.add(start_node);
self.local_nodes.remove(start_node);
}
}
}
}
pub fn mark_local_node(&mut self, node: Handle<Node>) {
self.local_nodes.add(node);
}
pub fn node_is_local(&self, node: Handle<Node>) -> bool {
self.local_nodes.contains(node)
}
pub fn iter_partial_paths(&self) -> impl Iterator<Item = Handle<PartialPath>> {
self.partial_paths.iter_handles()
}
pub fn ensure_both_directions(&mut self, partials: &mut PartialPaths) {
for path in self.partial_paths.iter_handles() {
self.partial_paths
.get_mut(path)
.ensure_both_directions(partials);
}
}
pub fn ensure_forwards(&mut self, partials: &mut PartialPaths) {
for path in self.partial_paths.iter_handles() {
self.partial_paths.get_mut(path).ensure_forwards(partials);
}
}
}
impl std::ops::Index<Handle<PartialPath>> for Database {
type Output = PartialPath;
#[inline(always)]
fn index(&self, handle: Handle<PartialPath>) -> &PartialPath {
self.partial_paths.get(handle)
}
}
impl ToAppendable<Handle<PartialPath>, PartialPath> for Database {
fn get_appendable<'a>(&'a self, handle: &'a Handle<PartialPath>) -> &'a PartialPath {
&self[*handle]
}
}
pub struct DatabaseCandidates<'a> {
graph: &'a StackGraph,
partials: &'a mut PartialPaths,
database: &'a mut Database,
}
impl<'a> DatabaseCandidates<'a> {
pub fn new(
graph: &'a StackGraph,
partials: &'a mut PartialPaths,
database: &'a mut Database,
) -> Self {
Self {
graph,
partials,
database,
}
}
}
impl ForwardCandidates<Handle<PartialPath>, PartialPath, Database, CancellationError>
for DatabaseCandidates<'_>
{
fn get_forward_candidates<R>(&mut self, path: &PartialPath, result: &mut R)
where
R: std::iter::Extend<Handle<PartialPath>>,
{
self.database
.find_candidate_partial_paths(self.graph, self.partials, path, result);
}
fn get_joining_candidate_degree(&self, path: &PartialPath) -> Degree {
self.database.get_incoming_path_degree(path.end_node)
}
fn get_graph_partials_and_db(&mut self) -> (&StackGraph, &mut PartialPaths, &Database) {
(self.graph, self.partials, self.database)
}
}
#[derive(Clone, Copy)]
pub struct SymbolStackKey {
symbols: List<Handle<Symbol>>,
}
#[derive(Clone, Eq, Hash, PartialEq)]
struct SymbolStackCacheKey {
head: Handle<Symbol>,
tail: SymbolStackKeyHandle,
}
type SymbolStackKeyCell = ListCell<Handle<Symbol>>;
type SymbolStackKeyHandle = Handle<SymbolStackKeyCell>;
impl SymbolStackKey {
fn empty() -> SymbolStackKey {
SymbolStackKey {
symbols: List::empty(),
}
}
fn is_empty(&self) -> bool {
self.symbols.is_empty()
}
fn push_back(&mut self, db: &mut Database, symbol: Handle<Symbol>) {
let cache_key = SymbolStackCacheKey {
head: symbol,
tail: self.back_handle(),
};
if let Some(handle) = db.symbol_stack_key_cache.get(&cache_key) {
self.symbols = List::from_handle(*handle);
return;
}
self.symbols.push_front(&mut db.symbol_stack_keys, symbol);
let handle = self.back_handle();
db.symbol_stack_key_cache.insert(cache_key, handle);
}
fn pop_back(&mut self, db: &Database) -> Option<Handle<Symbol>> {
self.symbols.pop_front(&db.symbol_stack_keys).copied()
}
pub fn from_partial_symbol_stack(
partials: &mut PartialPaths,
db: &mut Database,
mut stack: PartialSymbolStack,
) -> SymbolStackKey {
let mut result = SymbolStackKey::empty();
while let Some(symbol) = stack.pop_front(partials) {
result.push_back(db, symbol.symbol);
}
result
}
fn back_handle(self) -> SymbolStackKeyHandle {
self.symbols.handle()
}
#[cfg(feature = "copious-debugging")]
fn display<'a>(self, graph: &'a StackGraph, db: &'a Database) -> impl Display + 'a {
DisplaySymbolStackKey(self, graph, db)
}
}
#[cfg(feature = "copious-debugging")]
struct DisplaySymbolStackKey<'a>(SymbolStackKey, &'a StackGraph, &'a Database);
#[cfg(feature = "copious-debugging")]
impl<'a> Display for DisplaySymbolStackKey<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
fn display_one(
mut key: SymbolStackKey,
graph: &StackGraph,
db: &Database,
f: &mut std::fmt::Formatter,
) -> std::fmt::Result {
let last = match key.pop_back(db) {
Some(last) => last,
None => return Ok(()),
};
display_one(key, graph, db, f)?;
last.display(graph).fmt(f)
}
display_one(self.0, self.1, self.2, f)
}
}
pub struct ForwardPartialPathStitcher<H> {
candidates: Vec<H>,
extensions: Vec<(PartialPath, AppendingCycleDetector<H>)>,
queue: VecDeque<(PartialPath, AppendingCycleDetector<H>, bool)>,
initial_paths_in_queue: usize,
next_iteration: (
VecDeque<PartialPath>,
VecDeque<AppendingCycleDetector<H>>,
VecDeque<bool>,
),
appended_paths: Appendables<H>,
similar_path_detector: Option<SimilarPathDetector<PartialPath>>,
check_only_join_nodes: bool,
max_work_per_phase: usize,
initial_paths: usize,
stats: Option<Stats>,
#[cfg(feature = "copious-debugging")]
phase_number: usize,
}
impl<H> ForwardPartialPathStitcher<H> {
pub fn from_partial_paths<I>(
_graph: &StackGraph,
_partials: &mut PartialPaths,
initial_partial_paths: I,
) -> Self
where
I: IntoIterator<Item = PartialPath>,
{
let mut appended_paths = Appendables::new();
let next_iteration: (VecDeque<_>, VecDeque<_>, VecDeque<_>) = initial_partial_paths
.into_iter()
.map(|p| {
let c = AppendingCycleDetector::from(&mut appended_paths, p.clone().into());
(p, c, false)
})
.multiunzip();
let initial_paths = next_iteration.0.len();
Self {
candidates: Vec::new(),
extensions: Vec::new(),
queue: VecDeque::new(),
initial_paths_in_queue: initial_paths,
next_iteration,
appended_paths,
similar_path_detector: Some(SimilarPathDetector::new()),
check_only_join_nodes: false,
max_work_per_phase: usize::MAX,
initial_paths,
stats: None,
#[cfg(feature = "copious-debugging")]
phase_number: 1,
}
}
pub fn set_similar_path_detection(&mut self, detect_similar_paths: bool) {
if !detect_similar_paths {
self.similar_path_detector = None;
} else if self.similar_path_detector.is_none() {
let mut similar_path_detector = SimilarPathDetector::new();
similar_path_detector.set_collect_stats(self.stats.is_some());
self.similar_path_detector = Some(similar_path_detector);
}
}
pub fn set_check_only_join_nodes(&mut self, check_only_join_nodes: bool) {
self.check_only_join_nodes = check_only_join_nodes;
}
pub fn set_max_work_per_phase(&mut self, max_work_per_phase: usize) {
self.max_work_per_phase = max_work_per_phase;
}
pub fn set_collect_stats(&mut self, collect_stats: bool) {
if !collect_stats {
self.stats = None;
} else if self.stats.is_none() {
let mut stats = Stats::default();
stats.initial_paths.record(self.initial_paths);
self.stats = Some(stats);
}
if let Some(similar_path_detector) = &mut self.similar_path_detector {
similar_path_detector.set_collect_stats(collect_stats);
}
}
pub fn into_stats(mut self) -> Stats {
if let (Some(stats), Some(similar_path_detector)) =
(&mut self.stats, self.similar_path_detector)
{
stats.similar_paths_stats = similar_path_detector.stats();
}
self.stats.unwrap_or_default()
}
}
impl<H: Clone> ForwardPartialPathStitcher<H> {
pub fn previous_phase_partial_paths(&self) -> impl Iterator<Item = &PartialPath> + '_ {
self.next_iteration.0.iter()
}
pub fn previous_phase_partial_paths_slice(&mut self) -> &[PartialPath] {
self.next_iteration.0.make_contiguous();
self.next_iteration.0.as_slices().0
}
pub fn previous_phase_partial_paths_slice_mut(&mut self) -> &mut [PartialPath] {
self.next_iteration.0.make_contiguous();
self.next_iteration.0.as_mut_slices().0
}
fn extend<A, Db, C, Err>(
&mut self,
candidates: &mut C,
partial_path: &PartialPath,
cycle_detector: AppendingCycleDetector<H>,
has_split: bool,
) -> usize
where
A: Appendable,
Db: ToAppendable<H, A>,
C: ForwardCandidates<H, A, Db, Err>,
{
let check_cycle = !self.check_only_join_nodes
|| partial_path.start_node == partial_path.end_node
|| candidates.get_joining_candidate_degree(partial_path) == Degree::Multiple;
let (graph, partials, db) = candidates.get_graph_partials_and_db();
copious_debugging!(" Extend {}", partial_path.display(graph, partials));
if check_cycle {
let has_precondition_variables = partial_path.symbol_stack_precondition.has_variable()
|| partial_path.scope_stack_precondition.has_variable();
let cycles = cycle_detector
.is_cyclic(graph, partials, db, &mut self.appended_paths)
.expect("cyclic test failed when stitching partial paths");
let cyclic = match has_precondition_variables {
false => !cycles
.into_iter()
.all(|c| c == Cyclicity::StrengthensPrecondition),
true => !cycles.is_empty(),
};
if cyclic {
copious_debugging!(" is discontinued: cyclic");
return 0;
}
}
self.candidates.clear();
candidates.get_forward_candidates(partial_path, &mut self.candidates);
let (graph, partials, db) = candidates.get_graph_partials_and_db();
let candidate_count = self.candidates.len();
self.extensions.clear();
self.extensions.reserve(candidate_count);
for candidate in &self.candidates {
let appendable = db.get_appendable(candidate);
copious_debugging!(" with {}", appendable.display(graph, partials));
let mut new_partial_path = partial_path.clone();
let mut new_cycle_detector = cycle_detector.clone();
#[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))]
{
if let Err(err) = appendable.append_to(graph, partials, &mut new_partial_path) {
copious_debugging!(" is invalid: {:?}", err);
continue;
}
}
new_cycle_detector.append(&mut self.appended_paths, candidate.clone());
copious_debugging!(" is {}", new_partial_path.display(graph, partials));
self.extensions.push((new_partial_path, new_cycle_detector));
}
let extension_count = self.extensions.len();
let new_has_split = has_split || self.extensions.len() > 1;
self.next_iteration.0.reserve(extension_count);
self.next_iteration.1.reserve(extension_count);
self.next_iteration.2.reserve(extension_count);
for (new_partial_path, new_cycle_detector) in self.extensions.drain(..) {
let check_similar_path = new_has_split
&& (!self.check_only_join_nodes
|| candidates.get_joining_candidate_degree(&new_partial_path)
== Degree::Multiple);
let (graph, partials, _) = candidates.get_graph_partials_and_db();
if check_similar_path {
if let Some(similar_path_detector) = &mut self.similar_path_detector {
if similar_path_detector.add_path(
graph,
partials,
&new_partial_path,
|ps, left, right| {
if !left.equals(ps, right) {
None
} else {
if left.shadows(ps, right) {
Some(Ordering::Less)
} else if right.shadows(ps, left) {
Some(Ordering::Greater)
} else {
Some(Ordering::Equal)
}
}
},
) {
copious_debugging!(
" extension {}",
new_partial_path.display(graph, partials)
);
copious_debugging!(" is rejected: too many similar");
continue;
}
}
}
self.next_iteration.0.push(new_partial_path);
self.next_iteration.1.push(new_cycle_detector);
self.next_iteration.2.push(new_has_split);
}
if let Some(stats) = &mut self.stats {
let (graph, _, _) = candidates.get_graph_partials_and_db();
let end_node = &graph[partial_path.end_node];
if end_node.is_root() {
stats.candidates_per_root_path.record(candidate_count);
stats.extensions_per_root_path.record(extension_count);
stats.root_visits += 1;
} else {
stats.candidates_per_node_path.record(candidate_count);
stats.extensions_per_node_path.record(extension_count);
stats.node_visits.record(end_node.id());
}
if extension_count == 0 {
stats.terminal_path_lengh.record(partial_path.edges.len());
}
}
candidate_count
}
pub fn is_complete(&self) -> bool {
self.queue.is_empty() && self.next_iteration.0.is_empty()
}
pub fn process_next_phase<A, Db, C, E, Err>(&mut self, candidates: &mut C, extend_while: E)
where
A: Appendable,
Db: ToAppendable<H, A>,
C: ForwardCandidates<H, A, Db, Err>,
E: Fn(&StackGraph, &mut PartialPaths, &PartialPath) -> bool,
{
copious_debugging!("==> Start phase {}", self.phase_number);
self.queue.extend(izip!(
self.next_iteration.0.drain(..),
self.next_iteration.1.drain(..),
self.next_iteration.2.drain(..),
));
if let Some(stats) = &mut self.stats {
stats.queued_paths_per_phase.record(self.queue.len());
}
let mut work_performed = 0;
while let Some((partial_path, cycle_detector, has_split)) = self.queue.pop_front() {
let (graph, partials, _) = candidates.get_graph_partials_and_db();
copious_debugging!(
"--> Candidate partial path {}",
partial_path.display(graph, partials)
);
if self.initial_paths_in_queue > 0 {
self.initial_paths_in_queue -= 1;
} else if !extend_while(graph, partials, &partial_path) {
copious_debugging!(
" Do not extend {}",
partial_path.display(graph, partials)
);
continue;
}
work_performed += self.extend(candidates, &partial_path, cycle_detector, has_split);
if work_performed >= self.max_work_per_phase {
break;
}
}
if let Some(stats) = &mut self.stats {
stats.processed_paths_per_phase.record(work_performed);
}
#[cfg(feature = "copious-debugging")]
{
if let Some(similar_path_detector) = &self.similar_path_detector {
copious_debugging!(
" Max similar path bucket size: {}",
similar_path_detector.max_bucket_size()
);
}
copious_debugging!("==> End phase {}", self.phase_number);
self.phase_number += 1;
}
}
}
impl ForwardPartialPathStitcher<Edge> {
pub fn find_minimal_partial_path_set_in_file<F>(
graph: &StackGraph,
partials: &mut PartialPaths,
file: Handle<File>,
config: StitcherConfig,
cancellation_flag: &dyn CancellationFlag,
mut visit: F,
) -> Result<Stats, CancellationError>
where
F: FnMut(&StackGraph, &mut PartialPaths, &PartialPath),
{
fn as_complete_as_necessary(graph: &StackGraph, path: &PartialPath) -> bool {
path.starts_at_endpoint(graph)
&& (path.ends_at_endpoint(graph) || path.ends_in_jump(graph))
}
let initial_paths = graph
.nodes_for_file(file)
.chain(std::iter::once(StackGraph::root_node()))
.filter(|node| graph[*node].is_endpoint())
.map(|node| PartialPath::from_node(graph, partials, node))
.collect::<Vec<_>>();
let mut stitcher =
ForwardPartialPathStitcher::from_partial_paths(graph, partials, initial_paths);
config.apply(&mut stitcher);
stitcher.set_check_only_join_nodes(true);
let mut accepted_path_length = FrequencyDistribution::default();
while !stitcher.is_complete() {
cancellation_flag.check("finding complete partial paths")?;
stitcher.process_next_phase(
&mut GraphEdgeCandidates::new(graph, partials, Some(file)),
|g, _ps, p| !as_complete_as_necessary(g, p),
);
for path in stitcher.previous_phase_partial_paths() {
if as_complete_as_necessary(graph, path) {
accepted_path_length.record(path.edges.len());
visit(graph, partials, path);
}
}
}
Ok(Stats {
accepted_path_length,
..stitcher.into_stats()
})
}
}
impl<H: Clone> ForwardPartialPathStitcher<H> {
pub fn find_all_complete_partial_paths<I, F, A, Db, C, Err>(
candidates: &mut C,
starting_nodes: I,
config: StitcherConfig,
cancellation_flag: &dyn CancellationFlag,
mut visit: F,
) -> Result<Stats, Err>
where
I: IntoIterator<Item = Handle<Node>>,
A: Appendable,
Db: ToAppendable<H, A>,
C: ForwardCandidates<H, A, Db, Err>,
F: FnMut(&StackGraph, &mut PartialPaths, &PartialPath),
Err: std::convert::From<CancellationError>,
{
let (graph, partials, _) = candidates.get_graph_partials_and_db();
let initial_paths = starting_nodes
.into_iter()
.filter(|n| graph[*n].is_reference())
.map(|n| {
let mut p = PartialPath::from_node(graph, partials, n);
p.eliminate_precondition_stack_variables(partials);
p
})
.collect::<Vec<_>>();
let mut stitcher =
ForwardPartialPathStitcher::from_partial_paths(graph, partials, initial_paths);
config.apply(&mut stitcher);
stitcher.set_check_only_join_nodes(true);
let mut accepted_path_length = FrequencyDistribution::default();
while !stitcher.is_complete() {
cancellation_flag.check("finding complete partial paths")?;
for path in stitcher.previous_phase_partial_paths() {
candidates.load_forward_candidates(path, cancellation_flag)?;
}
stitcher.process_next_phase(candidates, |_, _, _| true);
let (graph, partials, _) = candidates.get_graph_partials_and_db();
for path in stitcher.previous_phase_partial_paths() {
if path.is_complete(graph) {
accepted_path_length.record(path.edges.len());
visit(graph, partials, path);
}
}
}
Ok(Stats {
accepted_path_length,
..stitcher.into_stats()
})
}
}
#[derive(Clone, Debug, Default)]
pub struct Stats {
pub initial_paths: FrequencyDistribution<usize>,
pub queued_paths_per_phase: FrequencyDistribution<usize>,
pub processed_paths_per_phase: FrequencyDistribution<usize>,
pub accepted_path_length: FrequencyDistribution<usize>,
pub terminal_path_lengh: FrequencyDistribution<usize>,
pub candidates_per_node_path: FrequencyDistribution<usize>,
pub candidates_per_root_path: FrequencyDistribution<usize>,
pub extensions_per_node_path: FrequencyDistribution<usize>,
pub extensions_per_root_path: FrequencyDistribution<usize>,
pub root_visits: usize,
pub node_visits: FrequencyDistribution<crate::graph::NodeID>,
pub similar_paths_stats: SimilarPathStats,
}
impl std::ops::AddAssign<Self> for Stats {
fn add_assign(&mut self, rhs: Self) {
self.initial_paths += rhs.initial_paths;
self.queued_paths_per_phase += rhs.queued_paths_per_phase;
self.processed_paths_per_phase += rhs.processed_paths_per_phase;
self.accepted_path_length += rhs.accepted_path_length;
self.terminal_path_lengh += rhs.terminal_path_lengh;
self.candidates_per_node_path += rhs.candidates_per_node_path;
self.candidates_per_root_path += rhs.candidates_per_root_path;
self.extensions_per_node_path += rhs.extensions_per_node_path;
self.extensions_per_root_path += rhs.extensions_per_root_path;
self.root_visits += rhs.root_visits;
self.node_visits += rhs.node_visits;
self.similar_paths_stats += rhs.similar_paths_stats;
}
}
impl std::ops::AddAssign<&Self> for Stats {
fn add_assign(&mut self, rhs: &Self) {
self.initial_paths += &rhs.initial_paths;
self.processed_paths_per_phase += &rhs.processed_paths_per_phase;
self.accepted_path_length += &rhs.accepted_path_length;
self.terminal_path_lengh += &rhs.terminal_path_lengh;
self.candidates_per_node_path += &rhs.candidates_per_node_path;
self.candidates_per_root_path += &rhs.candidates_per_root_path;
self.extensions_per_node_path += &rhs.extensions_per_node_path;
self.extensions_per_root_path += &rhs.extensions_per_root_path;
self.root_visits += rhs.root_visits;
self.node_visits += &rhs.node_visits;
self.similar_paths_stats += &rhs.similar_paths_stats;
}
}
#[derive(Clone, Copy, Debug)]
pub struct StitcherConfig {
detect_similar_paths: bool,
collect_stats: bool,
}
impl StitcherConfig {
pub fn detect_similar_paths(&self) -> bool {
self.detect_similar_paths
}
pub fn with_detect_similar_paths(mut self, detect_similar_paths: bool) -> Self {
self.detect_similar_paths = detect_similar_paths;
self
}
pub fn collect_stats(&self) -> bool {
self.collect_stats
}
pub fn with_collect_stats(mut self, collect_stats: bool) -> Self {
self.collect_stats = collect_stats;
self
}
}
impl StitcherConfig {
fn apply<H>(&self, stitcher: &mut ForwardPartialPathStitcher<H>) {
stitcher.set_similar_path_detection(self.detect_similar_paths);
stitcher.set_collect_stats(self.collect_stats);
}
}
impl Default for StitcherConfig {
fn default() -> Self {
Self {
detect_similar_paths: true,
collect_stats: false,
}
}
}