use crate::{
graph::{DependencyDirection, GraphSpec, ix_set::IxSet},
petgraph_support::{
dfs::{
BufferedEdgeFilter, ReversedBufferedFilter, SimpleEdgeFilterFn,
dfs_next_buffered_filter,
},
scc::{NodeIter, Sccs},
walk::EdgeDfs,
},
};
use debug_ignore::DebugIgnore;
use fixedbitset::FixedBitSet;
use petgraph::{
graph::{EdgeReference, IndexType},
prelude::*,
visit::{IntoEdges, IntoNeighbors, NodeFiltered, Reversed, Visitable},
};
use std::fmt;
#[derive(Clone)]
pub(super) struct ResolveCore<G> {
pub(super) included: IxSet<G>,
}
impl<G: GraphSpec> fmt::Debug for ResolveCore<G> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ResolveCore")
.field("included", &self.included)
.finish()
}
}
impl<G: GraphSpec> ResolveCore<G> {
pub(super) fn new(
graph: &Graph<G::Node, G::Edge, Directed, G::Ix>,
initials: impl IntoIterator<Item = NodeIndex<G::Ix>>,
direction: DependencyDirection,
) -> Self {
let (included, len) = match direction {
DependencyDirection::Forward => reachable_map(graph, initials),
DependencyDirection::Reverse => reachable_map(Reversed(graph), initials),
};
Self {
included: IxSet::from_visit_map(included, len, graph.node_count()),
}
}
pub(super) fn all_nodes(graph: &Graph<G::Node, G::Edge, Directed, G::Ix>) -> Self {
let (included, len) = all_visit_map(graph);
Self {
included: IxSet::from_visit_map(included, len, graph.node_count()),
}
}
pub(super) fn empty(graph: &Graph<G::Node, G::Edge, Directed, G::Ix>) -> Self {
Self {
included: IxSet::empty(graph.node_count()),
}
}
pub(super) fn with_edge_filter<'g>(
graph: &'g Graph<G::Node, G::Edge, Directed, G::Ix>,
initials: impl IntoIterator<Item = NodeIndex<G::Ix>>,
direction: DependencyDirection,
edge_filter: impl FnMut(EdgeReference<'g, G::Edge, G::Ix>) -> bool,
) -> Self {
let (included, len) = match direction {
DependencyDirection::Forward => {
reachable_map_buffered_filter(graph, SimpleEdgeFilterFn(edge_filter), initials)
}
DependencyDirection::Reverse => reachable_map_buffered_filter(
Reversed(graph),
ReversedBufferedFilter(SimpleEdgeFilterFn(edge_filter)),
initials,
),
};
Self {
included: IxSet::from_visit_map(included, len, graph.node_count()),
}
}
pub(super) fn with_buffered_edge_filter<'g>(
graph: &'g Graph<G::Node, G::Edge, Directed, G::Ix>,
initials: impl IntoIterator<Item = NodeIndex<G::Ix>>,
direction: DependencyDirection,
filter: impl BufferedEdgeFilter<&'g Graph<G::Node, G::Edge, Directed, G::Ix>>,
) -> Self {
let (included, len) = match direction {
DependencyDirection::Forward => reachable_map_buffered_filter(graph, filter, initials),
DependencyDirection::Reverse => reachable_map_buffered_filter(
Reversed(graph),
ReversedBufferedFilter(filter),
initials,
),
};
Self {
included: IxSet::from_visit_map(included, len, graph.node_count()),
}
}
pub(super) fn from_ixs(
ixs: impl IntoIterator<Item = NodeIndex<G::Ix>>,
graph: &Graph<G::Node, G::Edge, Directed, G::Ix>,
) -> Self {
Self {
included: IxSet::from_ixs(ixs, graph.node_count()),
}
}
pub(super) fn from_included<T: Into<FixedBitSet>>(
included: T,
graph: &Graph<G::Node, G::Edge, Directed, G::Ix>,
) -> Self {
Self {
included: IxSet::from_bits(included.into(), graph.node_count()),
}
}
pub(super) fn len(&self) -> usize {
self.included.len()
}
pub(super) fn is_empty(&self) -> bool {
self.included.is_empty()
}
pub(super) fn contains(&self, ix: NodeIndex<G::Ix>) -> bool {
self.included.contains(ix)
}
pub(super) fn union_with(&mut self, other: &Self) {
self.included.union_with(&other.included);
}
pub(super) fn intersect_with(&mut self, other: &Self) {
self.included.intersect_with(&other.included);
}
pub(super) fn difference(&self, other: &Self) -> Self {
Self {
included: self.included.difference(&other.included),
}
}
pub(super) fn symmetric_difference_with(&mut self, other: &Self) {
self.included.symmetric_difference_with(&other.included);
}
pub(super) fn roots(
&self,
graph: &Graph<G::Node, G::Edge, Directed, G::Ix>,
sccs: &Sccs<G::Ix>,
direction: DependencyDirection,
) -> Vec<NodeIndex<G::Ix>> {
match direction {
DependencyDirection::Forward => sccs
.externals(&NodeFiltered(graph, &self.included))
.collect(),
DependencyDirection::Reverse => sccs
.externals(&NodeFiltered(Reversed(graph), &self.included))
.collect(),
}
}
pub(super) fn topo<'g>(
&'g self,
sccs: &'g Sccs<G::Ix>,
direction: DependencyDirection,
) -> Topo<'g, G> {
let node_iter = sccs.node_iter(direction.into());
Topo {
node_iter,
included: &self.included,
remaining: self.included.len(),
}
}
pub(super) fn links<'g>(
&'g self,
graph: &'g Graph<G::Node, G::Edge, Directed, G::Ix>,
sccs: &Sccs<G::Ix>,
direction: DependencyDirection,
) -> Links<'g, G> {
let edge_dfs = match direction {
DependencyDirection::Forward => {
let filtered_graph = NodeFiltered(graph, &self.included);
EdgeDfs::new(&filtered_graph, sccs.externals(&filtered_graph))
}
DependencyDirection::Reverse => {
let filtered_reversed_graph = NodeFiltered(Reversed(graph), &self.included);
EdgeDfs::new(
&filtered_reversed_graph,
sccs.externals(&filtered_reversed_graph),
)
}
};
Links {
graph: DebugIgnore(graph),
included: &self.included,
edge_dfs,
direction,
}
}
}
impl<G: GraphSpec> PartialEq for ResolveCore<G> {
fn eq(&self, other: &Self) -> bool {
self.included == other.included
}
}
impl<G: GraphSpec> Eq for ResolveCore<G> {}
#[derive(Clone, Debug)]
pub(super) struct Topo<'g, G: GraphSpec> {
node_iter: NodeIter<'g, G::Ix>,
included: &'g IxSet<G>,
remaining: usize,
}
impl<G: GraphSpec> Iterator for Topo<'_, G> {
type Item = NodeIndex<G::Ix>;
fn next(&mut self) -> Option<Self::Item> {
for ix in &mut self.node_iter {
if !self.included.contains(ix) {
continue;
}
self.remaining -= 1;
return Some(ix);
}
None
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<G: GraphSpec> ExactSizeIterator for Topo<'_, G> {
fn len(&self) -> usize {
self.remaining
}
}
#[derive(Clone, Debug)]
#[allow(clippy::type_complexity)]
pub(super) struct Links<'g, G: GraphSpec> {
graph: DebugIgnore<&'g Graph<G::Node, G::Edge, Directed, G::Ix>>,
included: &'g IxSet<G>,
edge_dfs: EdgeDfs<EdgeIndex<G::Ix>, NodeIndex<G::Ix>, FixedBitSet>,
direction: DependencyDirection,
}
impl<G: GraphSpec> Iterator for Links<'_, G> {
#[allow(clippy::type_complexity)]
type Item = (NodeIndex<G::Ix>, NodeIndex<G::Ix>, EdgeIndex<G::Ix>);
fn next(&mut self) -> Option<Self::Item> {
match self.direction {
DependencyDirection::Forward => {
let filtered = NodeFiltered(self.graph.0, self.included);
self.edge_dfs.next(&filtered)
}
DependencyDirection::Reverse => {
let filtered_reversed = NodeFiltered(Reversed(self.graph.0), self.included);
self.edge_dfs
.next(&filtered_reversed)
.map(|(source_ix, target_ix, edge_ix)| {
(target_ix, source_ix, edge_ix)
})
}
}
}
}
fn all_visit_map<G, Ix>(graph: G) -> (FixedBitSet, usize)
where
G: Visitable<NodeId = NodeIndex<Ix>, Map = FixedBitSet>,
Ix: IndexType,
{
let mut visit_map = graph.visit_map();
visit_map.insert_range(..);
let len = visit_map.len();
(visit_map, len)
}
fn reachable_map<G, Ix>(
graph: G,
roots: impl IntoIterator<Item = G::NodeId>,
) -> (FixedBitSet, usize)
where
G: Visitable<NodeId = NodeIndex<Ix>, Map = FixedBitSet> + IntoNeighbors,
Ix: IndexType,
{
let mut dfs = DfsPostOrder::empty(graph);
dfs.stack = roots.into_iter().collect();
while dfs.next(graph).is_some() {}
debug_assert_eq!(
dfs.discovered, dfs.finished,
"discovered and finished maps match at the end"
);
let reachable = dfs.discovered;
let len = reachable.count_ones(..);
(reachable, len)
}
fn reachable_map_buffered_filter<G, Ix>(
graph: G,
mut filter: impl BufferedEdgeFilter<G>,
roots: impl IntoIterator<Item = G::NodeId>,
) -> (FixedBitSet, usize)
where
G: Visitable<NodeId = NodeIndex<Ix>, Map = FixedBitSet> + IntoEdges,
Ix: IndexType,
{
let mut dfs = DfsPostOrder::empty(graph);
dfs.stack = roots.into_iter().collect();
while dfs_next_buffered_filter(&mut dfs, graph, &mut filter).is_some() {}
debug_assert_eq!(
dfs.discovered, dfs.finished,
"discovered and finished maps match at the end"
);
let reachable = dfs.discovered;
let len = reachable.count_ones(..);
(reachable, len)
}