2025-07-16 00:06:40 +02:00
|
|
|
use std::collections::{BTreeSet, HashMap};
|
|
|
|
|
|
2025-07-16 18:34:33 +02:00
|
|
|
use petgraph::visit::{ EdgeRef,
|
|
|
|
|
GraphBase,
|
2025-07-16 01:35:57 +02:00
|
|
|
IntoEdgeReferences,
|
|
|
|
|
IntoEdges,
|
2025-07-16 18:34:33 +02:00
|
|
|
IntoNodeReferences };
|
2025-07-16 00:06:40 +02:00
|
|
|
|
2025-07-16 18:34:33 +02:00
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Helper Functions
|
|
|
|
|
// -----------------------------------------------------------------------------
|
2025-07-16 01:35:57 +02:00
|
|
|
|
2025-07-16 18:34:33 +02:00
|
|
|
#[inline(always)]
|
2025-07-16 01:35:57 +02:00
|
|
|
fn equal_vectors<T>(a: &Vec<T>, b: &Vec<T>) -> bool
|
|
|
|
|
where
|
|
|
|
|
T: PartialEq
|
|
|
|
|
{
|
|
|
|
|
for el in a {
|
|
|
|
|
if !b.contains(el) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for el in b {
|
|
|
|
|
if !a.contains(el) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-16 18:34:33 +02:00
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Bisimilarity
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
struct GraphPartition<'a, G>
|
2025-07-16 00:06:40 +02:00
|
|
|
where
|
2025-07-16 18:34:33 +02:00
|
|
|
G: GraphBase,
|
2025-07-16 00:06:40 +02:00
|
|
|
G::NodeId: std::cmp::Eq + std::hash::Hash,
|
2025-07-16 18:34:33 +02:00
|
|
|
{
|
|
|
|
|
pub node_to_block: HashMap<(usize, G::NodeId), u32>,
|
|
|
|
|
pub block_to_node: HashMap<u32, Vec<(usize, G::NodeId)>>,
|
|
|
|
|
pub graphs: [&'a G; 2],
|
|
|
|
|
last_block: u32,
|
|
|
|
|
blocks: BTreeSet<u32>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, G> GraphPartition<'a, G>
|
|
|
|
|
where
|
|
|
|
|
G: GraphBase,
|
|
|
|
|
G::NodeId: std::cmp::Eq + std::hash::Hash
|
2025-07-16 00:06:40 +02:00
|
|
|
{
|
|
|
|
|
pub fn new(graph_a: &'a G, graph_b: &'a G) -> Self {
|
|
|
|
|
GraphPartition { node_to_block: HashMap::new(),
|
|
|
|
|
block_to_node: HashMap::new(),
|
|
|
|
|
graphs: [graph_a, graph_b],
|
|
|
|
|
last_block: 0,
|
|
|
|
|
blocks: BTreeSet::new() }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
pub fn add_node_last_partition(&mut self, node: G::NodeId, graph: usize) {
|
|
|
|
|
self.node_to_block.insert((graph, node), self.last_block);
|
|
|
|
|
self.block_to_node
|
|
|
|
|
.entry(self.last_block)
|
|
|
|
|
.or_default()
|
|
|
|
|
.push((graph, node));
|
|
|
|
|
self.blocks.insert(self.last_block);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-16 18:34:33 +02:00
|
|
|
#[inline(always)]
|
2025-07-16 00:06:40 +02:00
|
|
|
pub fn iterate_blocks(&self) -> Vec<u32> {
|
|
|
|
|
self.blocks.iter().cloned().collect::<Vec<_>>()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn bisimilar(&self) -> bool {
|
|
|
|
|
// if there is a block that has only elements from one graph then they are
|
|
|
|
|
// not bisimilar
|
|
|
|
|
for (_block, node_list) in self.block_to_node.iter() {
|
|
|
|
|
let graph_id = node_list.first().unwrap().0;
|
|
|
|
|
if node_list.iter().all(|el| el.0 == graph_id) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
2025-07-16 18:34:33 +02:00
|
|
|
}
|
2025-07-16 00:06:40 +02:00
|
|
|
|
2025-07-16 18:34:33 +02:00
|
|
|
impl<'a, G> GraphPartition<'a, G>
|
|
|
|
|
where
|
|
|
|
|
G: IntoEdges,
|
|
|
|
|
G::NodeId: std::cmp::Eq + std::hash::Hash,
|
|
|
|
|
{
|
2025-07-16 00:06:40 +02:00
|
|
|
fn reachable_blocks(
|
|
|
|
|
&self,
|
|
|
|
|
label: &G::EdgeRef,
|
|
|
|
|
s: &(usize, G::NodeId)
|
2025-07-16 01:35:57 +02:00
|
|
|
) -> Vec<u32>
|
2025-07-16 00:06:40 +02:00
|
|
|
where <G as IntoEdgeReferences>::EdgeRef: PartialEq
|
|
|
|
|
{
|
|
|
|
|
let mut val = vec![];
|
|
|
|
|
for el in self.graphs[s.0].edges(s.1).filter(|x| x == label) {
|
2025-07-16 01:35:57 +02:00
|
|
|
let tmp = (s.0, el.target());
|
|
|
|
|
val.push(*self.node_to_block.get(&tmp).unwrap());
|
2025-07-16 00:06:40 +02:00
|
|
|
}
|
|
|
|
|
val
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn split(&mut self, block: u32, label: &G::EdgeRef) -> bool
|
|
|
|
|
where <G as IntoEdgeReferences>::EdgeRef: PartialEq
|
|
|
|
|
{
|
|
|
|
|
let Some(nodes) = self.block_to_node.get(&block)
|
|
|
|
|
else {
|
|
|
|
|
return true
|
|
|
|
|
};
|
|
|
|
|
let mut nodes = nodes.iter();
|
|
|
|
|
let s = nodes.next().unwrap();
|
|
|
|
|
|
|
|
|
|
let mut b1 = vec![s];
|
|
|
|
|
let mut b2 = vec![];
|
|
|
|
|
|
|
|
|
|
let reachable_blocks_s = self.reachable_blocks(label, s);
|
|
|
|
|
|
2025-07-16 01:35:57 +02:00
|
|
|
for t in nodes {
|
2025-07-16 00:06:40 +02:00
|
|
|
let reachable_blocks_t = self.reachable_blocks(label, t);
|
2025-07-16 01:35:57 +02:00
|
|
|
if equal_vectors(&reachable_blocks_s, &reachable_blocks_t) {
|
|
|
|
|
b1.push(t);
|
|
|
|
|
} else {
|
|
|
|
|
b2.push(*t);
|
2025-07-16 00:06:40 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if b2.is_empty() {
|
|
|
|
|
// all elements go to the same block with label label, so no change
|
|
|
|
|
false
|
|
|
|
|
} else {
|
|
|
|
|
// some elements need to be split into a different block
|
|
|
|
|
self.last_block += 1;
|
|
|
|
|
let new_block = self.last_block;
|
|
|
|
|
self.blocks.insert(new_block);
|
|
|
|
|
|
|
|
|
|
for b in b2 {
|
|
|
|
|
self.node_to_block.entry(b).and_modify(|e| *e = new_block);
|
|
|
|
|
self.block_to_node.entry(new_block).or_default().push(b);
|
|
|
|
|
self.block_to_node.entry(block).and_modify(|e| {
|
|
|
|
|
let index = e.iter().position(|x| *x == b).unwrap();
|
|
|
|
|
e.remove(index);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn bisimilarity_kanellakis_smolka<'a, G>(
|
|
|
|
|
graph_a: &'a G,
|
|
|
|
|
graph_b: &'a G
|
|
|
|
|
) -> bool
|
|
|
|
|
where
|
2025-07-16 18:34:33 +02:00
|
|
|
G: IntoNodeReferences + IntoEdges,
|
2025-07-16 00:06:40 +02:00
|
|
|
G::NodeId: std::cmp::Eq + std::hash::Hash,
|
|
|
|
|
G::EdgeRef: PartialEq
|
|
|
|
|
{
|
|
|
|
|
let graphs = [graph_a, graph_b];
|
|
|
|
|
|
|
|
|
|
let mut partition: GraphPartition<G> = GraphPartition::new(graph_a, graph_b);
|
|
|
|
|
for (p, graph) in graphs.iter().enumerate() {
|
|
|
|
|
for node in graph.node_identifiers() {
|
|
|
|
|
partition.add_node_last_partition(node, p);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let labels =
|
|
|
|
|
graph_a.edge_references()
|
|
|
|
|
.chain(graph_b.edge_references())
|
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
|
|
let mut changed = true;
|
|
|
|
|
|
|
|
|
|
while changed {
|
|
|
|
|
changed = false;
|
|
|
|
|
|
|
|
|
|
for block in partition.iterate_blocks() {
|
|
|
|
|
for label in labels.iter() {
|
|
|
|
|
if partition.split(block, label) {
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
partition.bisimilar()
|
|
|
|
|
}
|