use fixedbitset::FixedBitSet;
use std::marker;
use hashbrown::HashMap;
use super::digraph::PyDiGraph;
use pyo3::prelude::*;
use petgraph::algo;
use petgraph::stable_graph::NodeIndex;
use petgraph::stable_graph::StableDiGraph;
use petgraph::visit::{EdgeRef, GetAdjacencyMatrix, IntoEdgeReferences};
use petgraph::{Directed, Incoming};
#[derive(Debug)]
struct Vf2State {
mapping: Vec<NodeIndex>,
out: Vec<usize>,
ins: Vec<usize>,
out_size: usize,
ins_size: usize,
adjacency_matrix: FixedBitSet,
generation: usize,
_etype: marker::PhantomData<Directed>,
}
impl Vf2State {
pub fn new(dag: &PyDiGraph) -> Self {
let g = &dag.graph;
let c0 = g.node_count();
let mut state = Vf2State {
mapping: Vec::with_capacity(c0),
out: Vec::with_capacity(c0),
ins: Vec::with_capacity(c0 * (g.is_directed() as usize)),
out_size: 0,
ins_size: 0,
adjacency_matrix: g.adjacency_matrix(),
generation: 0,
_etype: marker::PhantomData,
};
for _ in 0..c0 {
state.mapping.push(NodeIndex::end());
state.out.push(0);
state.ins.push(0);
}
state
}
pub fn is_complete(&self) -> bool {
self.generation == self.mapping.len()
}
pub fn push_mapping(
&mut self,
from: NodeIndex,
to: NodeIndex,
dag: &PyDiGraph,
) {
let g = &dag.graph;
self.generation += 1;
let s = self.generation;
self.mapping[from.index()] = to;
for ix in g.neighbors(from) {
if self.out[ix.index()] == 0 {
self.out[ix.index()] = s;
self.out_size += 1;
}
}
if g.is_directed() {
for ix in g.neighbors_directed(from, Incoming) {
if self.ins[ix.index()] == 0 {
self.ins[ix.index()] = s;
self.ins_size += 1;
}
}
}
}
pub fn pop_mapping(&mut self, from: NodeIndex, dag: &PyDiGraph) {
let g = &dag.graph;
let s = self.generation;
self.generation -= 1;
self.mapping[from.index()] = NodeIndex::end();
for ix in g.neighbors(from) {
if self.out[ix.index()] == s {
self.out[ix.index()] = 0;
self.out_size -= 1;
}
}
if g.is_directed() {
for ix in g.neighbors_directed(from, Incoming) {
if self.ins[ix.index()] == s {
self.ins[ix.index()] = 0;
self.ins_size -= 1;
}
}
}
}
pub fn next_out_index(&self, from_index: usize) -> Option<usize> {
self.out[from_index..]
.iter()
.enumerate()
.find(move |&(index, elt)| {
*elt > 0 && self.mapping[from_index + index] == NodeIndex::end()
})
.map(|(index, _)| index)
}
pub fn next_in_index(&self, from_index: usize) -> Option<usize> {
self.ins[from_index..]
.iter()
.enumerate()
.find(move |&(index, elt)| {
*elt > 0 && self.mapping[from_index + index] == NodeIndex::end()
})
.map(|(index, _)| index)
}
pub fn next_rest_index(&self, from_index: usize) -> Option<usize> {
self.mapping[from_index..]
.iter()
.enumerate()
.find(|&(_, elt)| *elt == NodeIndex::end())
.map(|(index, _)| index)
}
}
pub fn is_isomorphic(dag0: &PyDiGraph, dag1: &PyDiGraph) -> PyResult<bool> {
let g0 = &dag0.graph;
let g1 = &dag1.graph;
if g0.node_count() != g1.node_count() || g0.edge_count() != g1.edge_count()
{
return Ok(false);
}
let mut st = [Vf2State::new(dag0), Vf2State::new(dag1)];
let res = try_match(
&mut st,
dag0,
dag1,
&mut NoSemanticMatch,
&mut NoSemanticMatch,
)?;
Ok(res.unwrap_or(false))
}
fn reindex_graph(py: Python, dag: &PyDiGraph) -> PyDiGraph {
let mut new_graph = StableDiGraph::<PyObject, PyObject>::new();
let mut id_map: HashMap<NodeIndex, NodeIndex> = HashMap::new();
for node_index in dag.graph.node_indices() {
let node_data = dag.graph.node_weight(node_index).unwrap();
let new_index = new_graph.add_node(node_data.clone_ref(py));
id_map.insert(node_index, new_index);
}
for edge in dag.graph.edge_references() {
let edge_w = edge.weight();
let p_index = id_map.get(&edge.source()).unwrap();
let c_index = id_map.get(&edge.target()).unwrap();
new_graph.add_edge(*p_index, *c_index, edge_w.clone_ref(py));
}
PyDiGraph {
graph: new_graph,
cycle_state: algo::DfsSpace::default(),
check_cycle: dag.check_cycle,
node_removed: false,
multigraph: true,
}
}
pub fn is_isomorphic_matching<F, G>(
py: Python,
dag0: &PyDiGraph,
dag1: &PyDiGraph,
mut node_match: F,
mut edge_match: G,
) -> PyResult<bool>
where
F: FnMut(&PyObject, &PyObject) -> PyResult<bool>,
G: FnMut(&PyObject, &PyObject) -> PyResult<bool>,
{
let inner_temp_dag0: PyDiGraph;
let inner_temp_dag1: PyDiGraph;
let dag0_out = if dag0.node_removed {
inner_temp_dag0 = reindex_graph(py, dag0);
&inner_temp_dag0
} else {
dag0
};
let dag1_out = if dag1.node_removed {
inner_temp_dag1 = reindex_graph(py, dag1);
&inner_temp_dag1
} else {
dag1
};
let g0 = &dag0_out.graph;
let g1 = &dag1_out.graph;
if g0.node_count() != g1.node_count() || g0.edge_count() != g1.edge_count()
{
return Ok(false);
}
let mut st = [Vf2State::new(&dag0_out), Vf2State::new(&dag1_out)];
let res = try_match(
&mut st,
&dag0_out,
&dag1_out,
&mut node_match,
&mut edge_match,
)?;
Ok(res.unwrap_or(false))
}
trait SemanticMatcher<T> {
fn enabled() -> bool;
fn eq(&mut self, _: &T, _: &T) -> PyResult<bool>;
}
struct NoSemanticMatch;
impl<T> SemanticMatcher<T> for NoSemanticMatch {
#[inline]
fn enabled() -> bool {
false
}
#[inline]
fn eq(&mut self, _: &T, _: &T) -> PyResult<bool> {
Ok(true)
}
}
impl<T, F> SemanticMatcher<T> for F
where
F: FnMut(&T, &T) -> PyResult<bool>,
{
#[inline]
fn enabled() -> bool {
true
}
#[inline]
fn eq(&mut self, a: &T, b: &T) -> PyResult<bool> {
let res = self(a, b)?;
Ok(res)
}
}
fn try_match<F, G>(
mut st: &mut [Vf2State; 2],
dag0: &PyDiGraph,
dag1: &PyDiGraph,
node_match: &mut F,
edge_match: &mut G,
) -> PyResult<Option<bool>>
where
F: SemanticMatcher<PyObject>,
G: SemanticMatcher<PyObject>,
{
let g0 = &dag0.graph;
let g1 = &dag1.graph;
if st[0].is_complete() {
return Ok(Some(true));
}
let dag = [dag0, dag1];
let g = [g0, g1];
let graph_indices = 0..2;
let end = NodeIndex::end();
#[derive(Copy, Clone, PartialEq, Debug)]
enum OpenList {
Out,
In,
Other,
}
#[derive(Clone, PartialEq, Debug)]
enum Frame<N: marker::Copy> {
Outer,
Inner { nodes: [N; 2], open_list: OpenList },
Unwind { nodes: [N; 2], open_list: OpenList },
}
let next_candidate =
|st: &mut [Vf2State; 2]| -> Option<(NodeIndex, NodeIndex, OpenList)> {
let mut to_index;
let mut from_index = None;
let mut open_list = OpenList::Out;
to_index = st[1].next_out_index(0);
if to_index.is_some() {
from_index = st[0].next_out_index(0);
open_list = OpenList::Out;
}
if to_index.is_none() || from_index.is_none() {
to_index = st[1].next_in_index(0);
if to_index.is_some() {
from_index = st[0].next_in_index(0);
open_list = OpenList::In;
}
}
if to_index.is_none() || from_index.is_none() {
to_index = st[1].next_rest_index(0);
if to_index.is_some() {
from_index = st[0].next_rest_index(0);
open_list = OpenList::Other;
}
}
match (from_index, to_index) {
(Some(n), Some(m)) => {
Some((NodeIndex::new(n), NodeIndex::new(m), open_list))
}
_ => None,
}
};
let next_from_ix = |st: &mut [Vf2State; 2],
nx: NodeIndex,
open_list: OpenList|
-> Option<NodeIndex> {
let start = nx.index() + 1;
let cand0 = match open_list {
OpenList::Out => st[0].next_out_index(start),
OpenList::In => st[0].next_in_index(start),
OpenList::Other => st[0].next_rest_index(start),
}
.map(|c| c + start); match cand0 {
None => None, Some(ix) => {
debug_assert!(ix >= start);
Some(NodeIndex::new(ix))
}
}
};
let pop_state = |st: &mut [Vf2State; 2], nodes: [NodeIndex; 2]| {
for j in graph_indices.clone() {
st[j].pop_mapping(nodes[j], dag[j]);
}
};
let push_state = |st: &mut [Vf2State; 2], nodes: [NodeIndex; 2]| {
for j in graph_indices.clone() {
st[j].push_mapping(nodes[j], nodes[1 - j], dag[j]);
}
};
let mut is_feasible = |st: &mut [Vf2State; 2],
nodes: [NodeIndex; 2]|
-> PyResult<bool> {
let mut succ_count = [0, 0];
for j in graph_indices.clone() {
for n_neigh in g[j].neighbors(nodes[j]) {
succ_count[j] += 1;
let m_neigh = if nodes[j] != n_neigh {
st[j].mapping[n_neigh.index()]
} else {
nodes[1 - j]
};
if m_neigh == end {
continue;
}
let has_edge = g[1 - j].is_adjacent(
&st[1 - j].adjacency_matrix,
nodes[1 - j],
m_neigh,
);
if !has_edge {
return Ok(false);
}
}
}
if succ_count[0] != succ_count[1] {
return Ok(false);
}
if g[0].is_directed() {
let mut pred_count = [0, 0];
for j in graph_indices.clone() {
for n_neigh in g[j].neighbors_directed(nodes[j], Incoming) {
pred_count[j] += 1;
let m_neigh = st[j].mapping[n_neigh.index()];
if m_neigh == end {
continue;
}
let has_edge = g[1 - j].is_adjacent(
&st[1 - j].adjacency_matrix,
m_neigh,
nodes[1 - j],
);
if !has_edge {
return Ok(false);
}
}
}
if pred_count[0] != pred_count[1] {
return Ok(false);
}
}
let match_result = node_match.eq(&g[0][nodes[0]], &g[1][nodes[1]])?;
if F::enabled() && !match_result {
return Ok(false);
}
if G::enabled() {
for j in graph_indices.clone() {
let mut edges = g[j].neighbors(nodes[j]).detach();
while let Some((n_edge, n_neigh)) = edges.next(g[j]) {
let m_neigh = if nodes[j] != n_neigh {
st[j].mapping[n_neigh.index()]
} else {
nodes[1 - j]
};
if m_neigh == end {
continue;
}
match g[1 - j].find_edge(nodes[1 - j], m_neigh) {
Some(m_edge) => {
let match_result = edge_match
.eq(&g[j][n_edge], &g[1 - j][m_edge])?;
if !match_result {
return Ok(false);
}
}
None => unreachable!(), }
}
}
if g[0].is_directed() {
for j in graph_indices.clone() {
let mut edges =
g[j].neighbors_directed(nodes[j], Incoming).detach();
while let Some((n_edge, n_neigh)) = edges.next(g[j]) {
let m_neigh = st[j].mapping[n_neigh.index()];
if m_neigh == end {
continue;
}
match g[1 - j].find_edge(m_neigh, nodes[1 - j]) {
Some(m_edge) => {
let match_result = edge_match
.eq(&g[j][n_edge], &g[1 - j][m_edge])?;
if !match_result {
return Ok(false);
}
}
None => unreachable!(), }
}
}
}
}
Ok(true)
};
let mut stack: Vec<Frame<NodeIndex>> = vec![Frame::Outer];
while let Some(frame) = stack.pop() {
match frame {
Frame::Unwind {
nodes,
open_list: ol,
} => {
pop_state(&mut st, nodes);
match next_from_ix(&mut st, nodes[0], ol) {
None => continue,
Some(nx) => {
let f = Frame::Inner {
nodes: [nx, nodes[1]],
open_list: ol,
};
stack.push(f);
}
}
}
Frame::Outer => match next_candidate(&mut st) {
None => continue,
Some((nx, mx, ol)) => {
let f = Frame::Inner {
nodes: [nx, mx],
open_list: ol,
};
stack.push(f);
}
},
Frame::Inner {
nodes,
open_list: ol,
} => {
let feasible = is_feasible(&mut st, nodes)?;
if feasible {
push_state(&mut st, nodes);
if st[0].is_complete() {
return Ok(Some(true));
}
if st[0].out_size == st[1].out_size
&& st[0].ins_size == st[1].ins_size
{
let f0 = Frame::Unwind {
nodes,
open_list: ol,
};
stack.push(f0);
stack.push(Frame::Outer);
continue;
}
pop_state(&mut st, nodes);
}
match next_from_ix(&mut st, nodes[0], ol) {
None => continue,
Some(nx) => {
let f = Frame::Inner {
nodes: [nx, nodes[1]],
open_list: ol,
};
stack.push(f);
}
}
}
}
}
Ok(None)
}