From 8a492c7b8af484a3e99d78ed0e606bd1d80224ac Mon Sep 17 00:00:00 2001 From: elvis Date: Sat, 23 Aug 2025 23:40:19 +0200 Subject: [PATCH] Refactoring :), in the middle of so please be patient --- src/rsprocess/assert/dsl.rs | 8 +- src/rsprocess/assert/rsassert.rs | 8 +- .../bisimilarity_kanellakis_smolka.rs | 198 ++++++ .../bisimilarity_paige_tarkan.rs} | 608 +----------------- src/rsprocess/bisimilarity/mod.rs | 8 + .../bisimilarity/test_kenallakis_smolka.rs | 193 ++++++ .../bisimilarity/test_paige_tarjan.rs | 201 ++++++ src/rsprocess/choices.rs | 100 +++ src/rsprocess/classical.rs | 34 - src/rsprocess/confluence.rs | 179 ------ src/rsprocess/environment.rs | 484 ++++++++++++++ src/rsprocess/format_helpers.rs | 34 +- src/rsprocess/frequency.rs | 163 ++--- src/rsprocess/graph.rs | 134 ++-- src/rsprocess/label.rs | 117 ++++ src/rsprocess/mod.rs | 24 +- src/rsprocess/perpetual.rs | 321 --------- src/rsprocess/presets.rs | 32 +- src/rsprocess/process.rs | 122 ++++ src/rsprocess/reaction.rs | 151 +++++ src/rsprocess/serialize.rs | 6 +- src/rsprocess/set.rs | 141 ++++ src/rsprocess/statistics.rs | 134 ---- src/rsprocess/structure.rs | 597 +---------------- src/rsprocess/support_structures.rs | 88 --- src/rsprocess/system.rs | 495 ++++++++++++++ src/rsprocess/system_test.rs | 124 ++++ src/rsprocess/transitions.rs | 412 +++--------- src/rsprocess/translator.rs | 3 +- 29 files changed, 2630 insertions(+), 2489 deletions(-) create mode 100644 src/rsprocess/bisimilarity/bisimilarity_kanellakis_smolka.rs rename src/rsprocess/{bisimilarity.rs => bisimilarity/bisimilarity_paige_tarkan.rs} (51%) create mode 100644 src/rsprocess/bisimilarity/mod.rs create mode 100644 src/rsprocess/bisimilarity/test_kenallakis_smolka.rs create mode 100644 src/rsprocess/bisimilarity/test_paige_tarjan.rs create mode 100644 src/rsprocess/choices.rs delete mode 100644 src/rsprocess/classical.rs delete mode 100644 src/rsprocess/confluence.rs create mode 100644 src/rsprocess/environment.rs create mode 100644 src/rsprocess/label.rs delete mode 100644 src/rsprocess/perpetual.rs create mode 100644 src/rsprocess/process.rs create mode 100644 src/rsprocess/reaction.rs create mode 100644 src/rsprocess/set.rs delete mode 100644 src/rsprocess/statistics.rs delete mode 100644 src/rsprocess/support_structures.rs create mode 100644 src/rsprocess/system.rs create mode 100644 src/rsprocess/system_test.rs diff --git a/src/rsprocess/assert/dsl.rs b/src/rsprocess/assert/dsl.rs index ad51fff..4323fbf 100644 --- a/src/rsprocess/assert/dsl.rs +++ b/src/rsprocess/assert/dsl.rs @@ -741,7 +741,7 @@ impl AssertReturnValue { self, u: &Unary, translator: &mut translator::Translator, - graph: &graph::RSgraph, + graph: &graph::SystemGraph, ) -> Result { match (self, u) { (AssertReturnValue::Boolean(b), Unary::Not) => { @@ -1029,7 +1029,7 @@ pub(super) fn execute( tree: &Tree, c: &mut Context, translator: &mut translator::Translator, - graph: &graph::RSgraph, + graph: &graph::SystemGraph, ) -> Result, String> where S: SpecialVariables { match tree { @@ -1083,7 +1083,7 @@ fn range_into_iter( range: &Range, c: &mut Context, translator: &mut translator::Translator, - graph: &graph::RSgraph, + graph: &graph::SystemGraph, ) -> Result where S: SpecialVariables { use petgraph::visit::EdgeRef; @@ -1129,7 +1129,7 @@ fn execute_exp( exp: &Expression, c: &Context, translator: &mut translator::Translator, - graph: &graph::RSgraph, + graph: &graph::SystemGraph, ) -> Result where S: SpecialVariables { match exp { diff --git a/src/rsprocess/assert/rsassert.rs b/src/rsprocess/assert/rsassert.rs index 6dd0789..e1a77f2 100644 --- a/src/rsprocess/assert/rsassert.rs +++ b/src/rsprocess/assert/rsassert.rs @@ -123,8 +123,8 @@ impl RSassert { pub fn execute( &self, - graph: &graph::RSgraph, - edge: &::EdgeId, + graph: &graph::SystemGraph, + edge: &::EdgeId, translator: &mut translator::Translator, ) -> Result { let label = graph.edge_weight(*edge) @@ -239,8 +239,8 @@ impl RSassert { pub fn execute( &self, - graph: &graph::RSgraph, - node: &::NodeId, + graph: &graph::SystemGraph, + node: &::NodeId, translator: &mut translator::Translator, ) -> Result { let structure::RSsystem {available_entities: entities, ..} = diff --git a/src/rsprocess/bisimilarity/bisimilarity_kanellakis_smolka.rs b/src/rsprocess/bisimilarity/bisimilarity_kanellakis_smolka.rs new file mode 100644 index 0000000..83e7204 --- /dev/null +++ b/src/rsprocess/bisimilarity/bisimilarity_kanellakis_smolka.rs @@ -0,0 +1,198 @@ +//! Bisimilarity by Kanellakis and Smolka from The algorithmics of bisimilarity +//! by Luca Aceto, Anna Ingolfsdottir and Jirí Srba; pages 105 to 110 +//! https://doi.org/10.1017/CBO9780511792588.004 + +use std::collections::{BTreeSet, HashMap, HashSet}; + +use petgraph::visit::{ EdgeRef, GraphBase, IntoEdgeReferences, + IntoEdges, IntoNodeIdentifiers, + IntoNodeReferences }; + +// ----------------------------------------------------------------------------- +// Helper Functions +// ----------------------------------------------------------------------------- + +#[inline(always)] +fn equal_vectors(a: &Vec, b: &Vec) -> 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 +} + +// ----------------------------------------------------------------------------- +// Bisimilarity +// ----------------------------------------------------------------------------- + +struct GraphPartition<'a, G> +where + G: GraphBase, + G::NodeId: std::cmp::Eq + std::hash::Hash, +{ + pub node_to_block: HashMap<(usize, G::NodeId), u32>, + pub block_to_node: HashMap>, + pub graphs: [&'a G; 2], + last_block: u32, + blocks: BTreeSet +} + +impl<'a, G> GraphPartition<'a, G> +where + G: GraphBase, + G::NodeId: std::cmp::Eq + std::hash::Hash +{ + 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); + } + + #[inline(always)] + pub fn iterate_blocks(&self) -> Vec { + self.blocks.iter().cloned().collect::>() + } + + 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 + } +} + +impl<'a, G> GraphPartition<'a, G> +where + G: IntoEdges, + G::NodeId: std::cmp::Eq + std::hash::Hash, +{ + fn reachable_blocks( + &self, + label: &G::EdgeWeight, + s: &(usize, G::NodeId) + ) -> Vec + where G::EdgeWeight: PartialEq + { + let mut val = vec![]; + for el in self.graphs[s.0].edges(s.1).filter(|x| x.weight() == label) { + let tmp = (s.0, el.target()); + val.push(*self.node_to_block.get(&tmp).unwrap()); + } + val + } + + pub fn split(&mut self, block: u32, label: &G::EdgeWeight) -> bool + where G::EdgeWeight: 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); + + for t in nodes { + let reachable_blocks_t = self.reachable_blocks(label, t); + if equal_vectors(&reachable_blocks_s, &reachable_blocks_t) { + b1.push(t); + } else { + b2.push(*t); + } + } + + 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<'a, G>( + graph_a: &'a G, + graph_b: &'a G +) -> bool +where + G: IntoNodeReferences + IntoEdges, + G::NodeId: std::cmp::Eq + std::hash::Hash, + G::EdgeWeight: std::cmp::Eq + std::hash::Hash + Clone +{ + let graphs = [graph_a, graph_b]; + + let mut partition: GraphPartition = + 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()) + .map(|e| e.weight().clone()) + .collect::>(); + + 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() +} diff --git a/src/rsprocess/bisimilarity.rs b/src/rsprocess/bisimilarity/bisimilarity_paige_tarkan.rs similarity index 51% rename from src/rsprocess/bisimilarity.rs rename to src/rsprocess/bisimilarity/bisimilarity_paige_tarkan.rs index aeb4015..e5dd59b 100644 --- a/src/rsprocess/bisimilarity.rs +++ b/src/rsprocess/bisimilarity/bisimilarity_paige_tarkan.rs @@ -1,408 +1,17 @@ +//! Bisimilarity by Paige and Tarjan from Three Partition Refinement Algorithms +//! by Robert Paige L., Robert Endre Tarjan; pages 977 to 983 +//! https://doi.org/10.1137/0216062 + use std::cell::RefCell; use std::collections::hash_map::Entry; -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::collections::{HashMap, HashSet}; use std::rc::Rc; use petgraph::visit::{ EdgeCount, EdgeRef, GraphBase, IntoEdgeReferences, - IntoEdges, IntoNeighborsDirected, IntoNodeIdentifiers, + IntoNeighborsDirected, IntoNodeIdentifiers, IntoNodeReferences, NodeCount }; use petgraph::Direction::{Incoming, Outgoing}; -// ----------------------------------------------------------------------------- -// Helper Functions -// ----------------------------------------------------------------------------- - -#[inline(always)] -fn equal_vectors(a: &Vec, b: &Vec) -> 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 -} - - -// ----------------------------------------------------------------------------- -// Bisimilarity -// ----------------------------------------------------------------------------- - - - -// ----------------------------------------------------------------------------- -// Bisimilarity by Kanellakis and Smolka from The algorithmics of bisimilarity -// by Luca Aceto, Anna Ingolfsdottir and Jirí Srba; pages 105 to 110 -// https://doi.org/10.1017/CBO9780511792588.004 -// ----------------------------------------------------------------------------- - -struct GraphPartition<'a, G> -where - G: GraphBase, - G::NodeId: std::cmp::Eq + std::hash::Hash, -{ - pub node_to_block: HashMap<(usize, G::NodeId), u32>, - pub block_to_node: HashMap>, - pub graphs: [&'a G; 2], - last_block: u32, - blocks: BTreeSet -} - -impl<'a, G> GraphPartition<'a, G> -where - G: GraphBase, - G::NodeId: std::cmp::Eq + std::hash::Hash -{ - 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); - } - - #[inline(always)] - pub fn iterate_blocks(&self) -> Vec { - self.blocks.iter().cloned().collect::>() - } - - 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 - } -} - -impl<'a, G> GraphPartition<'a, G> -where - G: IntoEdges, - G::NodeId: std::cmp::Eq + std::hash::Hash, -{ - fn reachable_blocks( - &self, - label: &G::EdgeWeight, - s: &(usize, G::NodeId) - ) -> Vec - where G::EdgeWeight: PartialEq - { - let mut val = vec![]; - for el in self.graphs[s.0].edges(s.1).filter(|x| x.weight() == label) { - let tmp = (s.0, el.target()); - val.push(*self.node_to_block.get(&tmp).unwrap()); - } - val - } - - pub fn split(&mut self, block: u32, label: &G::EdgeWeight) -> bool - where G::EdgeWeight: 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); - - for t in nodes { - let reachable_blocks_t = self.reachable_blocks(label, t); - if equal_vectors(&reachable_blocks_s, &reachable_blocks_t) { - b1.push(t); - } else { - b2.push(*t); - } - } - - 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 - G: IntoNodeReferences + IntoEdges, - G::NodeId: std::cmp::Eq + std::hash::Hash, - G::EdgeWeight: std::cmp::Eq + std::hash::Hash + Clone -{ - let graphs = [graph_a, graph_b]; - - let mut partition: GraphPartition = - 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()) - .map(|e| e.weight().clone()) - .collect::>(); - - 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() -} - - -#[test] -fn identity_kanellakis_smolka() { - use petgraph::Graph; - let mut graph_a = Graph::new(); - - let node_a_1 = graph_a.add_node(1); - let node_a_2 = graph_a.add_node(2); - graph_a.add_edge(node_a_1, node_a_2, 1); - - assert!(bisimilarity_kanellakis_smolka(&&graph_a, &&graph_a)) -} - -#[test] -fn identity_kanellakis_smolka_2() { - use petgraph::Graph; - let mut graph_a = Graph::new(); - - let node_a_1 = graph_a.add_node(1); - let node_a_2 = graph_a.add_node(2); - graph_a.add_edge(node_a_1, node_a_2, 1); - let node_a_3 = graph_a.add_node(3); - graph_a.add_edge(node_a_1, node_a_3, 1); - let node_a_6 = graph_a.add_node(6); - graph_a.add_edge(node_a_2, node_a_6, 2); - let node_a_4 = graph_a.add_node(4); - graph_a.add_edge(node_a_3, node_a_4, 2); - let node_a_7 = graph_a.add_node(7); - graph_a.add_edge(node_a_6, node_a_7, 2); - let node_a_5 = graph_a.add_node(5); - graph_a.add_edge(node_a_4, node_a_5, 2); - let node_a_8 = graph_a.add_node(8); - graph_a.add_edge(node_a_7, node_a_8, 3); - graph_a.add_edge(node_a_8, node_a_7, 3); - graph_a.add_edge(node_a_8, node_a_8, 3); - - assert!(bisimilarity_kanellakis_smolka(&&graph_a, &&graph_a)) -} - -#[test] -fn identity_different_weights_kanellakis_smolka() { - use petgraph::Graph; - let mut graph_a = Graph::new(); - - let node_a_1 = graph_a.add_node(1); - let node_a_2 = graph_a.add_node(2); - graph_a.add_edge(node_a_1, node_a_2, 1); - - let mut graph_b = Graph::new(); - - let node_b_1 = graph_b.add_node(1); - let node_b_2 = graph_b.add_node(2); - graph_b.add_edge(node_b_1, node_b_2, 2); - - assert!(!bisimilarity_kanellakis_smolka(&&graph_a, &&graph_b)) -} - -#[test] -fn not_bisimilar_kanellakis_smolka() { - use petgraph::Graph; - let mut graph_a = Graph::new(); - - let node_a_1 = graph_a.add_node(1); - let node_a_2 = graph_a.add_node(2); - graph_a.add_edge(node_a_1, node_a_2, 1); - - let mut graph_b = Graph::new(); - - let node_b_1 = graph_b.add_node(1); - let node_b_2 = graph_b.add_node(2); - graph_b.add_edge(node_b_1, node_b_2, 1); - let node_b_3 = graph_b.add_node(3); - graph_b.add_edge(node_b_1, node_b_3, 2); - - assert!(!bisimilarity_kanellakis_smolka(&&graph_a, &&graph_b)) -} - -#[test] -fn not_bisimilar_kanellakis_smolka_2() { - use petgraph::Graph; - let mut graph_a = Graph::new(); - - let node_a_1 = graph_a.add_node(1); - let node_a_2 = graph_a.add_node(2); - graph_a.add_edge(node_a_1, node_a_2, 1); - let node_a_3 = graph_a.add_node(3); - graph_a.add_edge(node_a_1, node_a_3, 1); - let node_a_6 = graph_a.add_node(6); - graph_a.add_edge(node_a_2, node_a_6, 2); - let node_a_4 = graph_a.add_node(4); - graph_a.add_edge(node_a_3, node_a_4, 2); - let node_a_7 = graph_a.add_node(7); - graph_a.add_edge(node_a_6, node_a_7, 2); - let node_a_5 = graph_a.add_node(5); - graph_a.add_edge(node_a_4, node_a_5, 2); - let node_a_8 = graph_a.add_node(8); - graph_a.add_edge(node_a_7, node_a_8, 3); - graph_a.add_edge(node_a_8, node_a_7, 3); - graph_a.add_edge(node_a_8, node_a_8, 3); - - let mut graph_b = Graph::new(); - - let node_b_1 = graph_b.add_node(1); - let node_b_2 = graph_b.add_node(2); - graph_b.add_edge(node_b_1, node_b_2, 1); - let node_b_3 = graph_b.add_node(3); - graph_b.add_edge(node_b_2, node_b_3, 2); - let node_b_4 = graph_b.add_node(4); - graph_b.add_edge(node_b_3, node_b_4, 2); - - assert!(!bisimilarity_kanellakis_smolka(&&graph_a, &&graph_b)) -} - -#[test] -fn not_bisimilar_kanellakis_smolka_3() { - use petgraph::Graph; - let mut graph_b = Graph::new(); - - let node_b_1 = graph_b.add_node(1); - let node_b_2 = graph_b.add_node(2); - graph_b.add_edge(node_b_1, node_b_2, 1); - let node_b_3 = graph_b.add_node(3); - graph_b.add_edge(node_b_2, node_b_3, 2); - let node_b_4 = graph_b.add_node(4); - graph_b.add_edge(node_b_3, node_b_4, 2); - - let mut graph_c = Graph::new(); - - let node_c_1 = graph_c.add_node(1); - let node_c_2 = graph_c.add_node(2); - graph_c.add_edge(node_c_1, node_c_2, 1); - let node_c_3 = graph_c.add_node(3); - graph_c.add_edge(node_c_1, node_c_3, 2); - graph_c.add_edge(node_c_2, node_c_3, 2); - - assert!(!bisimilarity_kanellakis_smolka(&&graph_b, &&graph_c)) -} - -#[test] -fn bisimilar_kanellakis_smolka() { - use petgraph::Graph; - let mut graph_b = Graph::new(); - - let node_b_1 = graph_b.add_node(1); - let node_b_2 = graph_b.add_node(2); - graph_b.add_edge(node_b_1, node_b_2, 1); - let node_b_3 = graph_b.add_node(3); - graph_b.add_edge(node_b_2, node_b_3, 2); - let node_b_4 = graph_b.add_node(4); - graph_b.add_edge(node_b_3, node_b_4, 2); - - let mut graph_c = Graph::new(); - - let node_c_1 = graph_c.add_node(1); - let node_c_2 = graph_c.add_node(2); - graph_c.add_edge(node_c_1, node_c_2, 1); - let node_c_3 = graph_c.add_node(3); - graph_c.add_edge(node_c_2, node_c_3, 2); - let node_c_4 = graph_c.add_node(4); - graph_c.add_edge(node_c_3, node_c_4, 2); - let node_c_5 = graph_c.add_node(5); - graph_c.add_edge(node_c_1, node_c_5, 1); - let node_c_6 = graph_c.add_node(6); - graph_c.add_edge(node_c_5, node_c_6, 2); - let node_c_7 = graph_c.add_node(7); - graph_c.add_edge(node_c_6, node_c_7, 2); - - assert!(bisimilarity_kanellakis_smolka(&&graph_b, &&graph_c)) -} - -#[test] -fn bisimilar_kanellakis_smolka_2() { - use petgraph::Graph; - let mut graph_a = Graph::new(); - - let node_a_1 = graph_a.add_node(1); - let node_a_2 = graph_a.add_node(2); - graph_a.add_edge(node_a_1, node_a_2, 1); - let node_a_3 = graph_a.add_node(3); - graph_a.add_edge(node_a_1, node_a_3, 1); - graph_a.add_edge(node_a_2, node_a_3, 2); - graph_a.add_edge(node_a_3, node_a_3, 2); - - let mut graph_b = Graph::new(); - - let node_b_1 = graph_b.add_node(1); - let node_b_2 = graph_b.add_node(2); - graph_b.add_edge(node_b_1, node_b_2, 1); - graph_b.add_edge(node_b_2, node_b_2, 2); - - assert!(bisimilarity_kanellakis_smolka(&&graph_a, &&graph_b)) -} - -// ----------------------------------------------------------------------------- -// Bisimilarity by Paige and Tarjan from Three Partition Refinement Algorithms -// by Robert Paige L., Robert Endre Tarjan; pages 977 to 983 -// https://doi.org/10.1137/0216062 -// ----------------------------------------------------------------------------- type NodeIdType = u32; type GraphIdType = u32; @@ -1084,7 +693,7 @@ where // ----------------------------------------------------------------------------- -pub fn bisimilarity_paige_tarjan( +pub fn bisimilarity( graph_a: &G, graph_b: &G ) -> bool @@ -1119,7 +728,7 @@ where result } -pub fn bisimilarity_paige_tarjan_ignore_labels( +pub fn bisimilarity_ignore_labels( graph_a: &G, graph_b: &G ) -> bool @@ -1156,204 +765,3 @@ where result.is_none() } - - -#[test] -fn identity_paige_tarjan() { - use petgraph::Graph; - let mut graph_a = Graph::new(); - - let node_a_1 = graph_a.add_node(1); - let node_a_2 = graph_a.add_node(2); - graph_a.add_edge(node_a_1, node_a_2, 1); - - assert!(bisimilarity_paige_tarjan(&&graph_a, &&graph_a)); - assert!(bisimilarity_paige_tarjan_ignore_labels(&&graph_a, &&graph_a)) -} - -#[test] -fn identity_paige_tarjan_2() { - use petgraph::Graph; - let mut graph_a = Graph::new(); - - let node_a_1 = graph_a.add_node(1); - let node_a_2 = graph_a.add_node(2); - graph_a.add_edge(node_a_1, node_a_2, 1); - let node_a_3 = graph_a.add_node(3); - graph_a.add_edge(node_a_1, node_a_3, 1); - let node_a_6 = graph_a.add_node(6); - graph_a.add_edge(node_a_2, node_a_6, 2); - let node_a_4 = graph_a.add_node(4); - graph_a.add_edge(node_a_3, node_a_4, 2); - let node_a_7 = graph_a.add_node(7); - graph_a.add_edge(node_a_6, node_a_7, 2); - let node_a_5 = graph_a.add_node(5); - graph_a.add_edge(node_a_4, node_a_5, 2); - let node_a_8 = graph_a.add_node(8); - graph_a.add_edge(node_a_7, node_a_8, 3); - graph_a.add_edge(node_a_8, node_a_7, 3); - graph_a.add_edge(node_a_8, node_a_8, 3); - - assert!(bisimilarity_paige_tarjan(&&graph_a, &&graph_a)); - assert!(bisimilarity_paige_tarjan_ignore_labels(&&graph_a, &&graph_a)) -} - -#[test] -fn identity_different_weights_paige_tarjan() { - use petgraph::Graph; - let mut graph_a = Graph::new(); - - let node_a_1 = graph_a.add_node(1); - let node_a_2 = graph_a.add_node(2); - graph_a.add_edge(node_a_1, node_a_2, 1); - - let mut graph_b = Graph::new(); - - let node_b_1 = graph_b.add_node(1); - let node_b_2 = graph_b.add_node(2); - graph_b.add_edge(node_b_1, node_b_2, 2); - - assert!(!bisimilarity_paige_tarjan(&&graph_a, &&graph_b)); - assert!(bisimilarity_paige_tarjan_ignore_labels(&&graph_a, &&graph_b)) -} - -#[test] -fn not_bisimilar_paige_tarjan() { - use petgraph::Graph; - let mut graph_a = Graph::new(); - - let node_a_1 = graph_a.add_node(1); - let node_a_2 = graph_a.add_node(2); - graph_a.add_edge(node_a_1, node_a_2, 1); - - let mut graph_b = Graph::new(); - - let node_b_1 = graph_b.add_node(1); - let node_b_2 = graph_b.add_node(2); - graph_b.add_edge(node_b_1, node_b_2, 1); - let node_b_3 = graph_b.add_node(3); - graph_b.add_edge(node_b_1, node_b_3, 2); - - assert!(!bisimilarity_paige_tarjan(&&graph_a, &&graph_b)); - assert!(bisimilarity_paige_tarjan_ignore_labels(&&graph_a, &&graph_b)) -} - -#[test] -fn not_bisimilar_paige_tarjan_2() { - use petgraph::Graph; - let mut graph_a = Graph::new(); - - let node_a_1 = graph_a.add_node(1); - let node_a_2 = graph_a.add_node(2); - graph_a.add_edge(node_a_1, node_a_2, 1); - let node_a_3 = graph_a.add_node(3); - graph_a.add_edge(node_a_1, node_a_3, 1); - let node_a_6 = graph_a.add_node(6); - graph_a.add_edge(node_a_2, node_a_6, 2); - let node_a_4 = graph_a.add_node(4); - graph_a.add_edge(node_a_3, node_a_4, 2); - let node_a_7 = graph_a.add_node(7); - graph_a.add_edge(node_a_6, node_a_7, 2); - let node_a_5 = graph_a.add_node(5); - graph_a.add_edge(node_a_4, node_a_5, 2); - let node_a_8 = graph_a.add_node(8); - graph_a.add_edge(node_a_7, node_a_8, 3); - graph_a.add_edge(node_a_8, node_a_7, 3); - graph_a.add_edge(node_a_8, node_a_8, 3); - - let mut graph_b = Graph::new(); - - let node_b_1 = graph_b.add_node(1); - let node_b_2 = graph_b.add_node(2); - graph_b.add_edge(node_b_1, node_b_2, 1); - let node_b_3 = graph_b.add_node(3); - graph_b.add_edge(node_b_2, node_b_3, 2); - let node_b_4 = graph_b.add_node(4); - graph_b.add_edge(node_b_3, node_b_4, 2); - - assert!(!bisimilarity_paige_tarjan(&&graph_a, &&graph_b)); - assert!(!bisimilarity_paige_tarjan_ignore_labels(&&graph_a, &&graph_b)) -} - -#[test] -fn not_bisimilar_paige_tarjan_3() { - use petgraph::Graph; - let mut graph_b = Graph::new(); - - let node_b_1 = graph_b.add_node(1); - let node_b_2 = graph_b.add_node(2); - graph_b.add_edge(node_b_1, node_b_2, 1); - let node_b_3 = graph_b.add_node(3); - graph_b.add_edge(node_b_2, node_b_3, 2); - let node_b_4 = graph_b.add_node(4); - graph_b.add_edge(node_b_3, node_b_4, 2); - - let mut graph_c = Graph::new(); - - let node_c_1 = graph_c.add_node(1); - let node_c_2 = graph_c.add_node(2); - graph_c.add_edge(node_c_1, node_c_2, 1); - let node_c_3 = graph_c.add_node(3); - graph_c.add_edge(node_c_1, node_c_3, 2); - graph_c.add_edge(node_c_2, node_c_3, 2); - - assert!(!bisimilarity_paige_tarjan(&&graph_b, &&graph_c)); - assert!(!bisimilarity_paige_tarjan_ignore_labels(&&graph_b, &&graph_c)) -} - -#[test] -fn bisimilar_paige_tarjan() { - use petgraph::Graph; - let mut graph_b = Graph::new(); - - let node_b_1 = graph_b.add_node(1); - let node_b_2 = graph_b.add_node(2); - graph_b.add_edge(node_b_1, node_b_2, 1); - let node_b_3 = graph_b.add_node(3); - graph_b.add_edge(node_b_2, node_b_3, 2); - let node_b_4 = graph_b.add_node(4); - graph_b.add_edge(node_b_3, node_b_4, 2); - - let mut graph_c = Graph::new(); - - let node_c_1 = graph_c.add_node(1); - let node_c_2 = graph_c.add_node(2); - graph_c.add_edge(node_c_1, node_c_2, 1); - let node_c_3 = graph_c.add_node(3); - graph_c.add_edge(node_c_2, node_c_3, 2); - let node_c_4 = graph_c.add_node(4); - graph_c.add_edge(node_c_3, node_c_4, 2); - let node_c_5 = graph_c.add_node(5); - graph_c.add_edge(node_c_1, node_c_5, 1); - let node_c_6 = graph_c.add_node(6); - graph_c.add_edge(node_c_5, node_c_6, 2); - let node_c_7 = graph_c.add_node(7); - graph_c.add_edge(node_c_6, node_c_7, 2); - - assert!(bisimilarity_paige_tarjan(&&graph_b, &&graph_c)); - assert!(bisimilarity_paige_tarjan_ignore_labels(&&graph_b, &&graph_c)) -} - -#[test] -fn bisimilar_paige_tarjan_2() { - use petgraph::Graph; - let mut graph_a = Graph::new(); - - let node_a_1 = graph_a.add_node(1); - let node_a_2 = graph_a.add_node(2); - graph_a.add_edge(node_a_1, node_a_2, 1); - let node_a_3 = graph_a.add_node(3); - graph_a.add_edge(node_a_1, node_a_3, 1); - graph_a.add_edge(node_a_2, node_a_3, 2); - graph_a.add_edge(node_a_3, node_a_3, 2); - - let mut graph_b = Graph::new(); - - let node_b_1 = graph_b.add_node(1); - let node_b_2 = graph_b.add_node(2); - graph_b.add_edge(node_b_1, node_b_2, 1); - graph_b.add_edge(node_b_2, node_b_2, 2); - - assert!(bisimilarity_paige_tarjan(&&graph_a, &&graph_b)); - assert!(bisimilarity_paige_tarjan_ignore_labels(&&graph_a, &&graph_b)) -} diff --git a/src/rsprocess/bisimilarity/mod.rs b/src/rsprocess/bisimilarity/mod.rs new file mode 100644 index 0000000..64b7559 --- /dev/null +++ b/src/rsprocess/bisimilarity/mod.rs @@ -0,0 +1,8 @@ +pub mod bisimilarity_kanellakis_smolka; +pub mod bisimilarity_paige_tarkan; + +#[cfg(test)] +mod test_kenallakis_smolka; + +#[cfg(test)] +mod test_paige_tarjan; diff --git a/src/rsprocess/bisimilarity/test_kenallakis_smolka.rs b/src/rsprocess/bisimilarity/test_kenallakis_smolka.rs new file mode 100644 index 0000000..072d9b7 --- /dev/null +++ b/src/rsprocess/bisimilarity/test_kenallakis_smolka.rs @@ -0,0 +1,193 @@ +use super::bisimilarity_kanellakis_smolka::bisimilarity; + +#[test] +fn identity_kanellakis_smolka() { + use petgraph::Graph; + let mut graph_a = Graph::new(); + + let node_a_1 = graph_a.add_node(1); + let node_a_2 = graph_a.add_node(2); + graph_a.add_edge(node_a_1, node_a_2, 1); + + assert!(bisimilarity(&&graph_a, &&graph_a)) +} + +#[test] +fn identity_kanellakis_smolka_2() { + use petgraph::Graph; + let mut graph_a = Graph::new(); + + let node_a_1 = graph_a.add_node(1); + let node_a_2 = graph_a.add_node(2); + graph_a.add_edge(node_a_1, node_a_2, 1); + let node_a_3 = graph_a.add_node(3); + graph_a.add_edge(node_a_1, node_a_3, 1); + let node_a_6 = graph_a.add_node(6); + graph_a.add_edge(node_a_2, node_a_6, 2); + let node_a_4 = graph_a.add_node(4); + graph_a.add_edge(node_a_3, node_a_4, 2); + let node_a_7 = graph_a.add_node(7); + graph_a.add_edge(node_a_6, node_a_7, 2); + let node_a_5 = graph_a.add_node(5); + graph_a.add_edge(node_a_4, node_a_5, 2); + let node_a_8 = graph_a.add_node(8); + graph_a.add_edge(node_a_7, node_a_8, 3); + graph_a.add_edge(node_a_8, node_a_7, 3); + graph_a.add_edge(node_a_8, node_a_8, 3); + + assert!(bisimilarity(&&graph_a, &&graph_a)) +} + +#[test] +fn identity_different_weights_kanellakis_smolka() { + use petgraph::Graph; + let mut graph_a = Graph::new(); + + let node_a_1 = graph_a.add_node(1); + let node_a_2 = graph_a.add_node(2); + graph_a.add_edge(node_a_1, node_a_2, 1); + + let mut graph_b = Graph::new(); + + let node_b_1 = graph_b.add_node(1); + let node_b_2 = graph_b.add_node(2); + graph_b.add_edge(node_b_1, node_b_2, 2); + + assert!(!bisimilarity(&&graph_a, &&graph_b)) +} + +#[test] +fn not_bisimilar_kanellakis_smolka() { + use petgraph::Graph; + let mut graph_a = Graph::new(); + + let node_a_1 = graph_a.add_node(1); + let node_a_2 = graph_a.add_node(2); + graph_a.add_edge(node_a_1, node_a_2, 1); + + let mut graph_b = Graph::new(); + + let node_b_1 = graph_b.add_node(1); + let node_b_2 = graph_b.add_node(2); + graph_b.add_edge(node_b_1, node_b_2, 1); + let node_b_3 = graph_b.add_node(3); + graph_b.add_edge(node_b_1, node_b_3, 2); + + assert!(!bisimilarity(&&graph_a, &&graph_b)) +} + +#[test] +fn not_bisimilar_kanellakis_smolka_2() { + use petgraph::Graph; + let mut graph_a = Graph::new(); + + let node_a_1 = graph_a.add_node(1); + let node_a_2 = graph_a.add_node(2); + graph_a.add_edge(node_a_1, node_a_2, 1); + let node_a_3 = graph_a.add_node(3); + graph_a.add_edge(node_a_1, node_a_3, 1); + let node_a_6 = graph_a.add_node(6); + graph_a.add_edge(node_a_2, node_a_6, 2); + let node_a_4 = graph_a.add_node(4); + graph_a.add_edge(node_a_3, node_a_4, 2); + let node_a_7 = graph_a.add_node(7); + graph_a.add_edge(node_a_6, node_a_7, 2); + let node_a_5 = graph_a.add_node(5); + graph_a.add_edge(node_a_4, node_a_5, 2); + let node_a_8 = graph_a.add_node(8); + graph_a.add_edge(node_a_7, node_a_8, 3); + graph_a.add_edge(node_a_8, node_a_7, 3); + graph_a.add_edge(node_a_8, node_a_8, 3); + + let mut graph_b = Graph::new(); + + let node_b_1 = graph_b.add_node(1); + let node_b_2 = graph_b.add_node(2); + graph_b.add_edge(node_b_1, node_b_2, 1); + let node_b_3 = graph_b.add_node(3); + graph_b.add_edge(node_b_2, node_b_3, 2); + let node_b_4 = graph_b.add_node(4); + graph_b.add_edge(node_b_3, node_b_4, 2); + + assert!(!bisimilarity(&&graph_a, &&graph_b)) +} + +#[test] +fn not_bisimilar_kanellakis_smolka_3() { + use petgraph::Graph; + let mut graph_b = Graph::new(); + + let node_b_1 = graph_b.add_node(1); + let node_b_2 = graph_b.add_node(2); + graph_b.add_edge(node_b_1, node_b_2, 1); + let node_b_3 = graph_b.add_node(3); + graph_b.add_edge(node_b_2, node_b_3, 2); + let node_b_4 = graph_b.add_node(4); + graph_b.add_edge(node_b_3, node_b_4, 2); + + let mut graph_c = Graph::new(); + + let node_c_1 = graph_c.add_node(1); + let node_c_2 = graph_c.add_node(2); + graph_c.add_edge(node_c_1, node_c_2, 1); + let node_c_3 = graph_c.add_node(3); + graph_c.add_edge(node_c_1, node_c_3, 2); + graph_c.add_edge(node_c_2, node_c_3, 2); + + assert!(!bisimilarity(&&graph_b, &&graph_c)) +} + +#[test] +fn bisimilar_kanellakis_smolka() { + use petgraph::Graph; + let mut graph_b = Graph::new(); + + let node_b_1 = graph_b.add_node(1); + let node_b_2 = graph_b.add_node(2); + graph_b.add_edge(node_b_1, node_b_2, 1); + let node_b_3 = graph_b.add_node(3); + graph_b.add_edge(node_b_2, node_b_3, 2); + let node_b_4 = graph_b.add_node(4); + graph_b.add_edge(node_b_3, node_b_4, 2); + + let mut graph_c = Graph::new(); + + let node_c_1 = graph_c.add_node(1); + let node_c_2 = graph_c.add_node(2); + graph_c.add_edge(node_c_1, node_c_2, 1); + let node_c_3 = graph_c.add_node(3); + graph_c.add_edge(node_c_2, node_c_3, 2); + let node_c_4 = graph_c.add_node(4); + graph_c.add_edge(node_c_3, node_c_4, 2); + let node_c_5 = graph_c.add_node(5); + graph_c.add_edge(node_c_1, node_c_5, 1); + let node_c_6 = graph_c.add_node(6); + graph_c.add_edge(node_c_5, node_c_6, 2); + let node_c_7 = graph_c.add_node(7); + graph_c.add_edge(node_c_6, node_c_7, 2); + + assert!(bisimilarity(&&graph_b, &&graph_c)) +} + +#[test] +fn bisimilar_kanellakis_smolka_2() { + use petgraph::Graph; + let mut graph_a = Graph::new(); + + let node_a_1 = graph_a.add_node(1); + let node_a_2 = graph_a.add_node(2); + graph_a.add_edge(node_a_1, node_a_2, 1); + let node_a_3 = graph_a.add_node(3); + graph_a.add_edge(node_a_1, node_a_3, 1); + graph_a.add_edge(node_a_2, node_a_3, 2); + graph_a.add_edge(node_a_3, node_a_3, 2); + + let mut graph_b = Graph::new(); + + let node_b_1 = graph_b.add_node(1); + let node_b_2 = graph_b.add_node(2); + graph_b.add_edge(node_b_1, node_b_2, 1); + graph_b.add_edge(node_b_2, node_b_2, 2); + + assert!(bisimilarity(&&graph_a, &&graph_b)) +} diff --git a/src/rsprocess/bisimilarity/test_paige_tarjan.rs b/src/rsprocess/bisimilarity/test_paige_tarjan.rs new file mode 100644 index 0000000..6d11437 --- /dev/null +++ b/src/rsprocess/bisimilarity/test_paige_tarjan.rs @@ -0,0 +1,201 @@ +use super::bisimilarity_paige_tarkan::{bisimilarity, bisimilarity_ignore_labels}; + +#[test] +fn identity_paige_tarjan() { + use petgraph::Graph; + let mut graph_a = Graph::new(); + + let node_a_1 = graph_a.add_node(1); + let node_a_2 = graph_a.add_node(2); + graph_a.add_edge(node_a_1, node_a_2, 1); + + assert!(bisimilarity(&&graph_a, &&graph_a)); + assert!(bisimilarity_ignore_labels(&&graph_a, &&graph_a)) +} + +#[test] +fn identity_paige_tarjan_2() { + use petgraph::Graph; + let mut graph_a = Graph::new(); + + let node_a_1 = graph_a.add_node(1); + let node_a_2 = graph_a.add_node(2); + graph_a.add_edge(node_a_1, node_a_2, 1); + let node_a_3 = graph_a.add_node(3); + graph_a.add_edge(node_a_1, node_a_3, 1); + let node_a_6 = graph_a.add_node(6); + graph_a.add_edge(node_a_2, node_a_6, 2); + let node_a_4 = graph_a.add_node(4); + graph_a.add_edge(node_a_3, node_a_4, 2); + let node_a_7 = graph_a.add_node(7); + graph_a.add_edge(node_a_6, node_a_7, 2); + let node_a_5 = graph_a.add_node(5); + graph_a.add_edge(node_a_4, node_a_5, 2); + let node_a_8 = graph_a.add_node(8); + graph_a.add_edge(node_a_7, node_a_8, 3); + graph_a.add_edge(node_a_8, node_a_7, 3); + graph_a.add_edge(node_a_8, node_a_8, 3); + + assert!(bisimilarity(&&graph_a, &&graph_a)); + assert!(bisimilarity_ignore_labels(&&graph_a, &&graph_a)) +} + +#[test] +fn identity_different_weights_paige_tarjan() { + use petgraph::Graph; + let mut graph_a = Graph::new(); + + let node_a_1 = graph_a.add_node(1); + let node_a_2 = graph_a.add_node(2); + graph_a.add_edge(node_a_1, node_a_2, 1); + + let mut graph_b = Graph::new(); + + let node_b_1 = graph_b.add_node(1); + let node_b_2 = graph_b.add_node(2); + graph_b.add_edge(node_b_1, node_b_2, 2); + + assert!(!bisimilarity(&&graph_a, &&graph_b)); + assert!(bisimilarity_ignore_labels(&&graph_a, &&graph_b)) +} + +#[test] +fn not_bisimilar_paige_tarjan() { + use petgraph::Graph; + let mut graph_a = Graph::new(); + + let node_a_1 = graph_a.add_node(1); + let node_a_2 = graph_a.add_node(2); + graph_a.add_edge(node_a_1, node_a_2, 1); + + let mut graph_b = Graph::new(); + + let node_b_1 = graph_b.add_node(1); + let node_b_2 = graph_b.add_node(2); + graph_b.add_edge(node_b_1, node_b_2, 1); + let node_b_3 = graph_b.add_node(3); + graph_b.add_edge(node_b_1, node_b_3, 2); + + assert!(!bisimilarity(&&graph_a, &&graph_b)); + assert!(bisimilarity_ignore_labels(&&graph_a, &&graph_b)) +} + +#[test] +fn not_bisimilar_paige_tarjan_2() { + use petgraph::Graph; + let mut graph_a = Graph::new(); + + let node_a_1 = graph_a.add_node(1); + let node_a_2 = graph_a.add_node(2); + graph_a.add_edge(node_a_1, node_a_2, 1); + let node_a_3 = graph_a.add_node(3); + graph_a.add_edge(node_a_1, node_a_3, 1); + let node_a_6 = graph_a.add_node(6); + graph_a.add_edge(node_a_2, node_a_6, 2); + let node_a_4 = graph_a.add_node(4); + graph_a.add_edge(node_a_3, node_a_4, 2); + let node_a_7 = graph_a.add_node(7); + graph_a.add_edge(node_a_6, node_a_7, 2); + let node_a_5 = graph_a.add_node(5); + graph_a.add_edge(node_a_4, node_a_5, 2); + let node_a_8 = graph_a.add_node(8); + graph_a.add_edge(node_a_7, node_a_8, 3); + graph_a.add_edge(node_a_8, node_a_7, 3); + graph_a.add_edge(node_a_8, node_a_8, 3); + + let mut graph_b = Graph::new(); + + let node_b_1 = graph_b.add_node(1); + let node_b_2 = graph_b.add_node(2); + graph_b.add_edge(node_b_1, node_b_2, 1); + let node_b_3 = graph_b.add_node(3); + graph_b.add_edge(node_b_2, node_b_3, 2); + let node_b_4 = graph_b.add_node(4); + graph_b.add_edge(node_b_3, node_b_4, 2); + + assert!(!bisimilarity(&&graph_a, &&graph_b)); + assert!(!bisimilarity_ignore_labels(&&graph_a, &&graph_b)) +} + +#[test] +fn not_bisimilar_paige_tarjan_3() { + use petgraph::Graph; + let mut graph_b = Graph::new(); + + let node_b_1 = graph_b.add_node(1); + let node_b_2 = graph_b.add_node(2); + graph_b.add_edge(node_b_1, node_b_2, 1); + let node_b_3 = graph_b.add_node(3); + graph_b.add_edge(node_b_2, node_b_3, 2); + let node_b_4 = graph_b.add_node(4); + graph_b.add_edge(node_b_3, node_b_4, 2); + + let mut graph_c = Graph::new(); + + let node_c_1 = graph_c.add_node(1); + let node_c_2 = graph_c.add_node(2); + graph_c.add_edge(node_c_1, node_c_2, 1); + let node_c_3 = graph_c.add_node(3); + graph_c.add_edge(node_c_1, node_c_3, 2); + graph_c.add_edge(node_c_2, node_c_3, 2); + + assert!(!bisimilarity(&&graph_b, &&graph_c)); + assert!(!bisimilarity_ignore_labels(&&graph_b, &&graph_c)) +} + +#[test] +fn bisimilar_paige_tarjan() { + use petgraph::Graph; + let mut graph_b = Graph::new(); + + let node_b_1 = graph_b.add_node(1); + let node_b_2 = graph_b.add_node(2); + graph_b.add_edge(node_b_1, node_b_2, 1); + let node_b_3 = graph_b.add_node(3); + graph_b.add_edge(node_b_2, node_b_3, 2); + let node_b_4 = graph_b.add_node(4); + graph_b.add_edge(node_b_3, node_b_4, 2); + + let mut graph_c = Graph::new(); + + let node_c_1 = graph_c.add_node(1); + let node_c_2 = graph_c.add_node(2); + graph_c.add_edge(node_c_1, node_c_2, 1); + let node_c_3 = graph_c.add_node(3); + graph_c.add_edge(node_c_2, node_c_3, 2); + let node_c_4 = graph_c.add_node(4); + graph_c.add_edge(node_c_3, node_c_4, 2); + let node_c_5 = graph_c.add_node(5); + graph_c.add_edge(node_c_1, node_c_5, 1); + let node_c_6 = graph_c.add_node(6); + graph_c.add_edge(node_c_5, node_c_6, 2); + let node_c_7 = graph_c.add_node(7); + graph_c.add_edge(node_c_6, node_c_7, 2); + + assert!(bisimilarity(&&graph_b, &&graph_c)); + assert!(bisimilarity_ignore_labels(&&graph_b, &&graph_c)) +} + +#[test] +fn bisimilar_paige_tarjan_2() { + use petgraph::Graph; + let mut graph_a = Graph::new(); + + let node_a_1 = graph_a.add_node(1); + let node_a_2 = graph_a.add_node(2); + graph_a.add_edge(node_a_1, node_a_2, 1); + let node_a_3 = graph_a.add_node(3); + graph_a.add_edge(node_a_1, node_a_3, 1); + graph_a.add_edge(node_a_2, node_a_3, 2); + graph_a.add_edge(node_a_3, node_a_3, 2); + + let mut graph_b = Graph::new(); + + let node_b_1 = graph_b.add_node(1); + let node_b_2 = graph_b.add_node(2); + graph_b.add_edge(node_b_1, node_b_2, 1); + graph_b.add_edge(node_b_2, node_b_2, 2); + + assert!(bisimilarity(&&graph_a, &&graph_b)); + assert!(bisimilarity_ignore_labels(&&graph_a, &&graph_b)) +} diff --git a/src/rsprocess/choices.rs b/src/rsprocess/choices.rs new file mode 100644 index 0000000..987725e --- /dev/null +++ b/src/rsprocess/choices.rs @@ -0,0 +1,100 @@ +use std::rc::Rc; + +use super::set::Set; +use super::process::Process; + +#[derive(Clone, Debug)] +pub struct Choices { + context_moves: Vec<(Rc, Rc)>, +} + +impl Choices { + pub fn new() -> Self { + Choices { + context_moves: vec![], + } + } + + pub fn new_not_empty() -> Self { + Choices { + context_moves: vec![(Rc::new(Set::new()), + Rc::new(Process::Nill))], + } + } + + pub fn append(&mut self, a: &mut Choices) { + self.context_moves.append(&mut a.context_moves); + } + + pub fn replace(&mut self, a: Rc) { + self.context_moves = self + .context_moves + .iter_mut() + .map(|(c1, _)| (Rc::clone(c1), Rc::clone(&a))) + .collect::>(); + } + + pub fn shuffle(&mut self, choices: Choices) { + match ( + self.context_moves.is_empty(), + choices.context_moves.is_empty(), + ) { + (true, true) => {} + (true, false) => self.context_moves = choices.context_moves, + (false, true) => {} + (false, false) => { + let mut new_self = vec![]; + for item_self in &self.context_moves { + for item_choices in &choices.context_moves { + new_self.push(( + Rc::new(item_self.0.union(&item_choices.0)), + Rc::new(item_self.1.concat(&item_choices.1)), + )); + } + } + self.context_moves = new_self; + } + } + } + + pub fn iter(&self) -> std::slice::Iter<'_, (Rc, Rc)> { + self.context_moves.iter() + } +} + +impl Default for Choices { + fn default() -> Self { + Self::new() + } +} + +impl IntoIterator for Choices { + type Item = (Rc, Rc); + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.context_moves.into_iter() + } +} + +impl From<[(Rc, Rc); N]> for Choices { + fn from(arr: [(Rc, Rc); N]) -> Self { + Choices { + context_moves: arr.to_vec(), + } + } +} + +impl From<&[(Rc, Rc)]> for Choices { + fn from(arr: &[(Rc, Rc)]) -> Self { + Choices { + context_moves: arr.to_vec(), + } + } +} + +impl From, Rc)>> for Choices { + fn from(arr: Vec<(Rc, Rc)>) -> Self { + Choices { context_moves: arr } + } +} diff --git a/src/rsprocess/classical.rs b/src/rsprocess/classical.rs deleted file mode 100644 index 94f09df..0000000 --- a/src/rsprocess/classical.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Definitions for the 'classical' mechanism for computation. -//! -//! This initial part allows to define the 'classical' mechanism to compute in a -//! Reaction System (RS) Framework. -//! The data is held in RSset or RSreaction, in the latter the reagents, -//! inhibitors and products are held. -use super::structure::{RSreaction, RSset}; - -/// Computes the result of a single reaction (if enabled returns the products) -/// otherwise returns None. -/// see result -pub fn compute_step<'a>( - current_state: &'a RSset, - reaction: &'a RSreaction -) -> Option<&'a RSset> { - if reaction.enabled(current_state) { - Some(&reaction.products) - } else { - None - } -} - -/// Computes the result of a series of reactions. Returns the union of all -/// products. -/// see result -pub fn compute_all<'a>( - current_state: &'a RSset, - reactions: &'a [RSreaction] -) -> RSset { - reactions.iter().fold(RSset::new(), |mut acc, r| { - acc.union_option(compute_step(current_state, r)); - acc - }) -} diff --git a/src/rsprocess/confluence.rs b/src/rsprocess/confluence.rs deleted file mode 100644 index d28e626..0000000 --- a/src/rsprocess/confluence.rs +++ /dev/null @@ -1,179 +0,0 @@ -//! Definitions for confluence, strong confluence, loop confluence - -use super::perpetual::{ - lollipops_decomposed_named, lollipops_prefix_len_loop_decomposed, - lollipops_prefix_len_loop_decomposed_named, -}; -use super::structure::{RSenvironment, RSreaction, RSset}; -use super::translator::IdType; -use std::cmp; -use std::collections::HashSet; - - -/// Two set of entities E1 and E2 are confluent w.r.t. the perpetual context -/// delta iff they reach the same loop. -/// confluent checks if all the sets of entities in ```entities``` are confluent -/// and if so returns the maximal length of prefixes traversed to reached the -/// loop, its dimension (length) and the loop. -/// see confluent, confluents -pub fn confluent( - delta: &RSenvironment, - reaction_rules: &[RSreaction], - entities: &[RSset], -) -> Option<(usize, usize, Vec)> { - let all_loops = lollipops_prefix_len_loop_decomposed(delta, - reaction_rules, - entities.first()?); - let (prefix_len, hoop) = all_loops.first()?.clone(); - let dimension = hoop.len(); - let mut max_distance = prefix_len; - - for available_entities in entities.iter().skip(1) { - let all_loops = - lollipops_prefix_len_loop_decomposed(delta, - reaction_rules, - available_entities); - let (prefix_len, new_hoop) = all_loops.first()?; - - if new_hoop.len() != dimension || !hoop.contains(new_hoop.first()?) { - return None; - } - max_distance = cmp::max(max_distance, *prefix_len); - } - Some((max_distance, dimension, hoop)) -} - -/// Two set of entities E1 and E2 are confluent w.r.t. the perpetual context Q -/// iff they reach the same loop. -/// The predicate confluent(Rs,Q,Es,Loop,Distance,Dimension) checks if all the -/// sets of entities in Es are confluent and if so returns the Loop, the maximal -/// length of prefixes traversed to reached the loop and its dimension (length). -/// see confluent, confluents -pub fn confluent_named( - delta: &RSenvironment, - reaction_rules: &[RSreaction], - entities: &[RSset], - symb: IdType, -) -> Option<(usize, usize, Vec)> { - let (prefix_len, first_hoop) = - lollipops_prefix_len_loop_decomposed_named(delta, - reaction_rules, - entities.first()?, - symb)?; - let dimension = first_hoop.len(); - let mut max_distance = prefix_len; - let hoop = first_hoop; - - for available_entities in entities.iter().skip(1) { - let (prefix_len, new_hoop) = lollipops_prefix_len_loop_decomposed_named( - delta, - reaction_rules, - available_entities, - symb, - )?; - - if new_hoop.len() != dimension || !hoop.contains(new_hoop.first()?) { - return None; - } - max_distance = cmp::max(max_distance, prefix_len); - } - Some((max_distance, dimension, hoop)) -} - -// ----------------------------------------------------------------------------- - -/// invariant_named checks if all the sets of entities in ```entities``` are -/// confluent and if so returns the set of all traversed states, together with -/// the loop. -/// see invariant -pub fn invariant_named( - delta: &RSenvironment, - reaction_rules: &[RSreaction], - entities: &[RSset], - symb: IdType, -) -> Option<(Vec, Vec)> { - let (prefix, hoop) = - lollipops_decomposed_named(delta, - reaction_rules, - entities.first()?, - symb)?; - let mut invariant = vec![]; - invariant.append(&mut prefix.clone()); - invariant.append(&mut hoop.clone()); - let dimension = hoop.len(); - - for available_entities in entities { - let (new_prefix, new_hoop) = - lollipops_decomposed_named(delta, - reaction_rules, - available_entities, - symb)?; - if new_hoop.len() != dimension || !hoop.contains(new_hoop.first()?) { - return None; - } - invariant.append(&mut new_prefix.clone()); - } - // remove duplicates, maybe better with sorting? - invariant = invariant - .iter() - .cloned() - .collect::>() - .iter() - .cloned() - .collect::>(); - Some((invariant, hoop)) -} - -// ----------------------------------------------------------------------------- - -/// Suppose the context has the form -/// Q1. ... Q1.Q2. ... Q2. ... Qn. ... Qn. ... -/// and that each context Q1, Q2, ... , Q(n-1) is provided for a large number -/// of times, enough to stabilize the system in a loop (while Qn is provided -/// infinitely many times). Then it can be the case that when the context -/// switches from Qi to Q(i+1), no matter what is the current state of the loop -/// for Qi at the moment of the switching, the system will stabilize in the same -/// loop for Q(i+1): if this is the case the system is called "loop confluent". -/// loop_confluent_named checks this property over the list of contexts -/// [Q1,Q2,...,Qn] and returns the lists of Loops, Distances and Dimensions for -/// all Qi's. -/// see loop_confluent -pub fn loop_confluent_named( - deltas: &[RSenvironment], - reaction_rules: &[RSreaction], - entities: &[RSset], - symb: IdType, -) -> Option)>> { - deltas - .iter() - .map(|q| confluent_named(q, reaction_rules, entities, symb)) - .collect::>>() -} - -/// "strong confluence" requires loop confluence and additionally check -/// that even if the context is switched BEFORE REACHING THE LOOP for Qi -/// the traversed states are still confluent for Q(i+1) -/// IMPORTANT: this notion of confluence assumes each context can be executed 0 -/// or more times -/// see strong_confluent -#[allow(clippy::type_complexity)] -pub fn strong_confluent_named( - deltas: &[RSenvironment], - reaction_rules: &[RSreaction], - entities: &[RSset], - symb: IdType, -) -> Option, usize, Vec)>> { - deltas - .iter() - .map(|q| { - let (invariant, hoop) = invariant_named(q, - reaction_rules, - entities, - symb)?; - let length = invariant.len(); - Some((invariant, length, hoop)) - }) - .collect::>>() -} - -// TODO: weak confluence diff --git a/src/rsprocess/environment.rs b/src/rsprocess/environment.rs new file mode 100644 index 0000000..4366cbf --- /dev/null +++ b/src/rsprocess/environment.rs @@ -0,0 +1,484 @@ +use serde::{Deserialize, Serialize}; +use std::cmp; +use std::collections::{HashMap, HashSet}; +use std::rc::Rc; + +use super::choices::Choices; +use super::process::Process; +use super::reaction::Reaction; +use super::set::Set; +use super::translator::IdType; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Environment { + definitions: HashMap, +} + +impl Environment { + pub fn new() -> Environment { + Environment { + definitions: HashMap::new(), + } + } + + pub fn get(&self, k: IdType) -> Option<&Process> { + self.definitions.get(&k) + } + + pub fn iter(&self) -> std::collections::hash_map::Iter<'_, u32, Process> { + self.definitions.iter() + } + + pub fn all_elements(&self) -> Set { + let mut acc = Set::new(); + for (_, process) in self.definitions.iter() { + acc.push(&process.all_elements()); + } + acc + } + + /// unfold returns the list of choices for the context given the process + /// definitions environment. choices::Choices is a list of context moves + /// mapping a set of entities and the continuation. + /// see unfold + pub fn unfold( + &self, + context_process: &Process, + current_entities: &Set, + ) -> Result { + match context_process { + Process::Nill => { + Ok(Choices::new()) + }, + Process::RecursiveIdentifier { identifier } => { + let newprocess = self.get(*identifier); + if let Some(newprocess) = newprocess { + self.unfold(newprocess, current_entities) + } else { + Err(format!("Missing symbol in context: {identifier}")) + } + } + Process::EntitySet { entities, next_process, } => { + Ok(Choices::from([( + Rc::new(entities.clone()), + Rc::clone(next_process), + )])) + }, + Process::Guarded { reaction, next_process } => { + if reaction.enabled(current_entities) { + Ok(Choices::from([(Rc::new(reaction.products.clone()), + Rc::clone(next_process))])) + } else { + Ok(Choices::new()) + } + } + Process::WaitEntity { repeat, repeated_process: _, next_process, } + if *repeat <= 0 => { + self.unfold(next_process, current_entities) + }, + Process::WaitEntity { repeat, repeated_process, next_process, } + if *repeat == 1 => { + let mut choices1 = self.unfold(repeated_process, + current_entities)?; + choices1.replace(Rc::clone(next_process)); + Ok(choices1) + } + Process::WaitEntity { repeat, repeated_process, next_process, } => + { + let mut choices1 = self.unfold(repeated_process, + current_entities)?; + choices1.replace(Rc::new(Process::WaitEntity { + repeat: (*repeat - 1), + repeated_process: Rc::clone(repeated_process), + next_process: Rc::clone(next_process), + })); + Ok(choices1) + } + Process::Summation { children } => { + // short-circuits with try_fold. + children.iter().try_fold(Choices::new(), |mut acc, x| { + match self.unfold(x, current_entities) { + Ok(mut choices) => { + acc.append(&mut choices); + Ok(acc) + } + Err(e) => Err(e), + } + }) + } + Process::NondeterministicChoice { children } => { + // short-circuits with try_fold. + if children.is_empty() { + Ok(Choices::from(vec![( + Rc::new(Set::new()), + Rc::new(Process::Nill), + )])) + } else { + children.iter().try_fold(Choices::new(), |mut acc, x| { + acc.shuffle(self.unfold(x, current_entities)?); + Ok(acc) + }) + } + } + } + } +} + +impl Default for Environment { + fn default() -> Self { + Environment::new() + } +} + +impl From<[(IdType, Process); N]> for Environment { + fn from(arr: [(IdType, Process); N]) -> Self { + Environment { + definitions: HashMap::from(arr), + } + } +} + +impl From<&[(IdType, Process)]> for Environment { + fn from(arr: &[(IdType, Process)]) -> Self { + Environment { + definitions: HashMap::from_iter(arr.to_vec()), + } + } +} + +impl From> for Environment { + fn from(arr: Vec<(IdType, Process)>) -> Self { + Environment { + definitions: HashMap::from_iter(arr), + } + } +} + +// ----------------------------------------------------------------------------- +// Loops +// ----------------------------------------------------------------------------- + + +impl Environment { + /// A special case of systems is when the context recursively provides + /// always the same set of entities. The corresponding computation is + /// infinite. It consists of a finite sequence of states followed by a + /// looping sequence. IMPORTANT: We return all loops for all X = Q.X, by + /// varing X. The set of reactions Rs and the context x are constant. Each + /// state of the computation is distinguished by the current entities E. + /// Under these assumptions, the predicate lollipop finds the Prefixes and + /// the Loops sequences of entities. + /// see lollipop + pub fn lollipops_decomposed( + &self, + reaction_rules: &[Reaction], + available_entities: &Set, + ) -> Vec<(Vec, Vec)> { + // FIXME: i think we are only interested in "x", not all symbols that + // satisfy X = pre(Q, rec(X)) + let filtered = self.iter().filter_map(|l| l.1.filter_delta(l.0)); + + let find_loop_fn = + |q| Reaction::find_loop(reaction_rules, + available_entities.clone(), + q); + + filtered.map(find_loop_fn).collect::>() + } + + + pub fn lollipops_prefix_len_loop_decomposed( + &self, + reaction_rules: &[Reaction], + available_entities: &Set, + ) -> Vec<(usize, Vec)> { + let filtered = self.iter().filter_map(|l| l.1.filter_delta(l.0)); + + let find_loop_fn = + |q| Reaction::find_prefix_len_loop(reaction_rules, + available_entities.clone(), + q); + + filtered.map(find_loop_fn).collect::>() + } + + + /// see loop + pub fn lollipops_only_loop_decomposed( + &self, + reaction_rules: &[Reaction], + available_entities: &Set, + ) -> Vec> { + let filtered = self.iter().filter_map(|l| l.1.filter_delta(l.0)); + + let find_loop_fn = + |q| Reaction::find_only_loop(reaction_rules, + available_entities.clone(), + q); + + filtered.map(find_loop_fn).collect::>() + } + + + + /// A special case of systems is when the context recursively provides + /// always the same set of entities. The corresponding computation is + /// infinite. It consists of a finite sequence of states followed by a + /// looping sequence. IMPORTANT: We return all loops for all X = Q.X, by + /// varing X. The set of reactions Rs and the context x are constant. Each + /// state of the computation is distinguished by the current entities E. + /// Under these assumptions, the predicate lollipop finds the Prefixes and + /// the Loops sequences of entities. + /// see lollipop + pub fn lollipops_decomposed_named( + &self, + reaction_rules: &[Reaction], + available_entities: &Set, + symb: IdType, + ) -> Option<(Vec, Vec)> { + let filtered = self + .iter() + .filter_map( + |l| + if *l.0 == symb { + l.1.filter_delta(&symb) + } else { + None + } + ) + .next(); + + let find_loop_fn = |q| Reaction::find_loop(reaction_rules, + available_entities.clone(), + q); + + filtered.map(find_loop_fn) + } + + + + pub fn lollipops_prefix_len_loop_decomposed_named( + &self, + reaction_rules: &[Reaction], + available_entities: &Set, + symb: IdType, + ) -> Option<(usize, Vec)> { + let filtered = self + .iter() + .filter_map( + |l| + if *l.0 == symb { + l.1.filter_delta(&symb) + } else { + None + } + ) + .next(); + + let find_loop_fn = |q| + Reaction::find_prefix_len_loop(reaction_rules, + available_entities.clone(), + q); + + filtered.map(find_loop_fn) + } + + + /// see loop + pub fn lollipops_only_loop_decomposed_named( + &self, + reaction_rules: &[Reaction], + available_entities: &Set, + symb: IdType, + ) -> Option> { + let filtered = self + .iter() + .filter_map( + |l| + if *l.0 == symb { + l.1.filter_delta(&symb) + } else { + None + } + ) + .next(); + + let find_loop_fn = + |q| Reaction::find_only_loop(reaction_rules, + available_entities.clone(), + q); + + filtered.map(find_loop_fn) + } +} + + + +// ----------------------------------------------------------------------------- +// Confluence +// ----------------------------------------------------------------------------- + +impl Environment { + /// Two set of entities E1 and E2 are confluent w.r.t. the perpetual context + /// delta iff they reach the same loop. + /// confluent checks if all the sets of entities in ```entities``` are confluent + /// and if so returns the maximal length of prefixes traversed to reached the + /// loop, its dimension (length) and the loop. + /// see confluent, confluents + pub fn confluent( + &self, + reaction_rules: &[Reaction], + entities: &[Set], + ) -> Option<(usize, usize, Vec)> { + let all_loops = + self.lollipops_prefix_len_loop_decomposed(reaction_rules, + entities.first()?); + let (prefix_len, hoop) = all_loops.first()?.clone(); + let dimension = hoop.len(); + let mut max_distance = prefix_len; + + for available_entities in entities.iter().skip(1) { + let all_loops = + self.lollipops_prefix_len_loop_decomposed(reaction_rules, + available_entities); + let (prefix_len, new_hoop) = all_loops.first()?; + + if new_hoop.len() != dimension || !hoop.contains(new_hoop.first()?) { + return None; + } + max_distance = cmp::max(max_distance, *prefix_len); + } + Some((max_distance, dimension, hoop)) + } + + + /// Two set of entities E1 and E2 are confluent w.r.t. the perpetual context Q + /// iff they reach the same loop. + /// The predicate confluent(Rs,Q,Es,Loop,Distance,Dimension) checks if all the + /// sets of entities in Es are confluent and if so returns the Loop, the maximal + /// length of prefixes traversed to reached the loop and its dimension (length). + /// see confluent, confluents + pub fn confluent_named( + &self, + reaction_rules: &[Reaction], + entities: &[Set], + symb: IdType, + ) -> Option<(usize, usize, Vec)> { + let (prefix_len, first_hoop) = + self.lollipops_prefix_len_loop_decomposed_named(reaction_rules, + entities.first()?, + symb)?; + let dimension = first_hoop.len(); + let mut max_distance = prefix_len; + let hoop = first_hoop; + + for available_entities in entities.iter().skip(1) { + let (prefix_len, new_hoop) = + self.lollipops_prefix_len_loop_decomposed_named( + reaction_rules, + available_entities, + symb, + )?; + + if new_hoop.len() != dimension || !hoop.contains(new_hoop.first()?) { + return None; + } + max_distance = cmp::max(max_distance, prefix_len); + } + Some((max_distance, dimension, hoop)) + } + + + /// invariant_named checks if all the sets of entities in ```entities``` are + /// confluent and if so returns the set of all traversed states, together with + /// the loop. + /// see invariant + pub fn invariant_named( + &self, + reaction_rules: &[Reaction], + entities: &[Set], + symb: IdType, + ) -> Option<(Vec, Vec)> { + let (prefix, hoop) = + self.lollipops_decomposed_named(reaction_rules, + entities.first()?, + symb)?; + let mut invariant = vec![]; + invariant.append(&mut prefix.clone()); + invariant.append(&mut hoop.clone()); + let dimension = hoop.len(); + + for available_entities in entities { + let (new_prefix, new_hoop) = + self.lollipops_decomposed_named(reaction_rules, + available_entities, + symb)?; + if new_hoop.len() != dimension || !hoop.contains(new_hoop.first()?) { + return None; + } + invariant.append(&mut new_prefix.clone()); + } + // remove duplicates, maybe better with sorting? + invariant = invariant + .iter() + .cloned() + .collect::>() + .iter() + .cloned() + .collect::>(); + Some((invariant, hoop)) + } + + + /// Suppose the context has the form + /// Q1. ... Q1.Q2. ... Q2. ... Qn. ... Qn. ... + /// and that each context Q1, Q2, ... , Q(n-1) is provided for a large number + /// of times, enough to stabilize the system in a loop (while Qn is provided + /// infinitely many times). Then it can be the case that when the context + /// switches from Qi to Q(i+1), no matter what is the current state of the loop + /// for Qi at the moment of the switching, the system will stabilize in the same + /// loop for Q(i+1): if this is the case the system is called "loop confluent". + /// loop_confluent_named checks this property over the list of contexts + /// [Q1,Q2,...,Qn] and returns the lists of Loops, Distances and Dimensions for + /// all Qi's. + /// see loop_confluent + pub fn loop_confluent_named( + deltas: &[Self], + reaction_rules: &[Reaction], + entities: &[Set], + symb: IdType, + ) -> Option)>> { + deltas + .iter() + .map(|q| q.confluent_named(reaction_rules, entities, symb)) + .collect::>>() + } + + + /// "strong confluence" requires loop confluence and additionally check + /// that even if the context is switched BEFORE REACHING THE LOOP for Qi + /// the traversed states are still confluent for Q(i+1) + /// IMPORTANT: this notion of confluence assumes each context can be executed 0 + /// or more times + /// see strong_confluent + #[allow(clippy::type_complexity)] + pub fn strong_confluent_named( + deltas: &[Self], + reaction_rules: &[Reaction], + entities: &[Set], + symb: IdType, + ) -> Option, usize, Vec)>> { + deltas + .iter() + .map(|q| { + let (invariant, hoop) = q.invariant_named(reaction_rules, + entities, + symb)?; + let length = invariant.len(); + Some((invariant, length, hoop)) + }) + .collect::>>() + } + + // TODO: weak confluence +} diff --git a/src/rsprocess/format_helpers.rs b/src/rsprocess/format_helpers.rs index ddae0bc..be967d6 100644 --- a/src/rsprocess/format_helpers.rs +++ b/src/rsprocess/format_helpers.rs @@ -218,7 +218,7 @@ pub mod graph_map_edges_ty_from { pub mod node_formatter { use super::super::translator::IdType; - use super::super::graph::{RSgraph, OperationType}; + use super::super::graph::{SystemGraph, OperationType}; use std::rc::Rc; use super::super::structure::{RSset, RSprocess}; @@ -234,7 +234,7 @@ pub mod node_formatter { ) -> Option; pub fn format_nill( - original_graph: Rc, + original_graph: Rc, color: String, _star: Option, ) -> Box { @@ -251,7 +251,7 @@ pub mod node_formatter { } pub fn format_recursive_identifier( - original_graph: Rc, + original_graph: Rc, color: String, star: Option, s: IdType @@ -275,7 +275,7 @@ pub mod node_formatter { } pub fn format_entity_set( - original_graph: Rc, + original_graph: Rc, color: String, _star: Option, ot: OperationType, @@ -297,7 +297,7 @@ pub mod node_formatter { pub fn format_non_deterministic_choice( - original_graph: Rc, + original_graph: Rc, color: String, _star: Option, ) -> Box { @@ -316,7 +316,7 @@ pub mod node_formatter { } pub fn format_summation( - original_graph: Rc, + original_graph: Rc, color: String, _star: Option, ) -> Box { @@ -336,7 +336,7 @@ pub mod node_formatter { pub fn format_wait_entity( - original_graph: Rc, + original_graph: Rc, color: String, _star: Option, ) -> Box { @@ -357,7 +357,7 @@ pub mod node_formatter { } pub fn format_entities_conditional( - original_graph: Rc, + original_graph: Rc, color: String, _star: Option, ot: OperationType, @@ -377,7 +377,7 @@ pub mod node_formatter { } pub mod edge_formatter { - use super::super::graph::{RSgraph, OperationType}; + use super::super::graph::{SystemGraph, OperationType}; use std::rc::Rc; use super::super::structure::RSset; @@ -393,7 +393,7 @@ pub mod edge_formatter { pub fn format_entities( - original_graph: Rc, + original_graph: Rc, color: String, ot: OperationType, set: RSset @@ -411,7 +411,7 @@ pub mod edge_formatter { } pub fn format_context( - original_graph: Rc, + original_graph: Rc, color: String, ot: OperationType, set: RSset @@ -429,7 +429,7 @@ pub mod edge_formatter { } pub fn format_t( - original_graph: Rc, + original_graph: Rc, color: String, ot: OperationType, set: RSset @@ -447,7 +447,7 @@ pub mod edge_formatter { } pub fn format_reactants( - original_graph: Rc, + original_graph: Rc, color: String, ot: OperationType, set: RSset @@ -465,7 +465,7 @@ pub mod edge_formatter { } pub fn format_reactants_absent( - original_graph: Rc, + original_graph: Rc, color: String, ot: OperationType, set: RSset @@ -483,7 +483,7 @@ pub mod edge_formatter { } pub fn format_inhibitors( - original_graph: Rc, + original_graph: Rc, color: String, ot: OperationType, set: RSset @@ -501,7 +501,7 @@ pub mod edge_formatter { } pub fn format_inhibitors_present( - original_graph: Rc, + original_graph: Rc, color: String, ot: OperationType, set: RSset @@ -519,7 +519,7 @@ pub mod edge_formatter { } pub fn format_products( - original_graph: Rc, + original_graph: Rc, color: String, ot: OperationType, set: RSset diff --git a/src/rsprocess/frequency.rs b/src/rsprocess/frequency.rs index d3469d0..a366260 100644 --- a/src/rsprocess/frequency.rs +++ b/src/rsprocess/frequency.rs @@ -1,11 +1,10 @@ //! Definitions and structure for frequency of elements in a simulation -use crate::rsprocess::perpetual::lollipops_only_loop_decomposed_q; use std::collections::HashMap; -use super::perpetual::lollipops_only_loop_named; -use super::structure::{RSreaction, RSset, RSsystem}; -use super::transitions::run_separated; +use super::reaction::Reaction; +use super::set::Set; +use super::system::System; use super::translator::IdType; /// structure that holds the frequency of elements of a run or multiple runs, @@ -26,7 +25,7 @@ impl Frequency { } } - pub fn add(&mut self, e: &RSset, run: usize) { + pub fn add(&mut self, e: &Set, run: usize) { for &el in e.iter() { let entry = self.frequency_map.entry(el).or_insert(vec![0; run + 1]); @@ -59,83 +58,95 @@ impl Default for Frequency { // ----------------------------------------------------------------------------- -/// assume the system is finite, calculate the frequency of each symbol in all -/// traversed states -/// see naiveFreq -pub fn naive_frequency(system: &RSsystem) -> Result { - let ect = run_separated(system)?; - let es = ect.iter().map(|(e, _, _)| e).collect::>(); - let mut freq = Frequency::new(); - freq.append_weight(1); +impl Frequency { + /// Assuming the system is finite, calculates the frequency of each symbol + /// in all traversed states. + /// see naiveFreq + pub fn naive_frequency( + system: &System + ) -> Result { + let ect = system.run_separated()?; + let es = ect.iter().map(|(e, _, _)| e).collect::>(); - es.iter().for_each(|e| freq.add(e, 0)); + let mut freq = Frequency::new(); + freq.append_weight(1); - Ok(freq) -} + es.iter().for_each(|e| freq.add(e, 0)); -/// assume the system stabilizes in a loop, calculate the frequency of each -/// symbol in all states of the loop -/// see loopFreq -pub fn loop_frequency(system: &RSsystem, symb: IdType) -> Frequency { - let mut freq = Frequency::new(); - freq.append_weight(1); - - if let Some(hoop) = lollipops_only_loop_named(system, symb) { - hoop.iter().for_each(|e| freq.add(e, 0)); - } - freq -} - -/// ```q[i]``` is given enough times such that the stabilizes in a loop, -/// calculate the frequency of the symbols in any state in the last loop -/// see limitFreq -pub fn limit_frequency( - q: &[RSset], - reaction_rules: &[RSreaction], - available_entities: &RSset, -) -> Option { - let mut available_entities = available_entities.clone(); - - for q in q.iter().rev().skip(1).rev() { - let res = lollipops_only_loop_decomposed_q(q, - reaction_rules, - &available_entities); - available_entities = res.into_iter().next()?; + Ok(freq) } - let mut freq = Frequency::new(); - freq.append_weight(1); + /// Assume the system stabilizes in a loop, calculates the frequency of each + /// symbol in all states of the loop. + /// see loopFreq + pub fn loop_frequency( + system: &System, + symb: IdType + ) -> Self { + let mut freq = Frequency::new(); + freq.append_weight(1); - lollipops_only_loop_decomposed_q(q.last().unwrap(), - reaction_rules, - &available_entities) - .iter() - .for_each(|e| freq.add(e, 0)); - Some(freq) -} - -/// ```q[i]``` is given enough times such that the stabilizes in a loop, -/// calculate the frequency of the symbols in any state in any loop, weighted. -/// see fastFreq -pub fn fast_frequency( - q: &[RSset], - reaction_rules: &[RSreaction], - available_entities: &RSset, - weights: &[u32], -) -> Option { - // FIXME: we return the empty frequency or do we not return anything? - let mut available_entities = available_entities.clone(); - - let mut freq = Frequency::new(); - - for (pos, (q, &w)) in q.iter().zip(weights).enumerate() { - freq.append_weight(w); - let hoop = lollipops_only_loop_decomposed_q(q, - reaction_rules, - &available_entities); - hoop.iter().for_each(|e| freq.add(e, pos)); - available_entities = hoop.into_iter().next()?; + if let Some(hoop) = system.lollipops_only_loop_named(symb) { + hoop.iter().for_each(|e| freq.add(e, 0)); + } + freq + } + + /// Assuming ```q[i]``` is given enough times such that the system + /// stabilizes in a loop, calculates the frequency of the symbols in any + /// state in the last loop. + /// see limitFreq + pub fn limit_frequency( + q: &[Set], + reaction_rules: &[Reaction], + available_entities: &Set, + ) -> Option { + let mut available_entities = available_entities.clone(); + + for q in q.iter().rev().skip(1).rev() { + let res = + Reaction::lollipops_only_loop_decomposed_q(reaction_rules, + q, + &available_entities); + available_entities = res.into_iter().next()?; + } + + let mut freq = Frequency::new(); + freq.append_weight(1); + + Reaction::lollipops_only_loop_decomposed_q(reaction_rules, + q.last().unwrap(), + &available_entities) + .iter() + .for_each(|e| freq.add(e, 0)); + Some(freq) + } + + /// Assuming ```q[i]``` is given enough times such that the system + /// stabilizes in a loop, calculates the frequency of the symbols in any + /// state in any loop, weighted. + /// see fastFreq + pub fn fast_frequency( + q: &[Set], + reaction_rules: &[Reaction], + available_entities: &Set, + weights: &[u32], + ) -> Option { + // FIXME: we return the empty frequency or do we not return anything? + let mut available_entities = available_entities.clone(); + + let mut freq = Frequency::new(); + + for (pos, (q, &w)) in q.iter().zip(weights).enumerate() { + freq.append_weight(w); + let hoop = + Reaction::lollipops_only_loop_decomposed_q(reaction_rules, + q, + &available_entities); + hoop.iter().for_each(|e| freq.add(e, pos)); + available_entities = hoop.into_iter().next()?; + } + Some(freq) } - Some(freq) } diff --git a/src/rsprocess/graph.rs b/src/rsprocess/graph.rs index f6b7a77..a503746 100644 --- a/src/rsprocess/graph.rs +++ b/src/rsprocess/graph.rs @@ -1,45 +1,16 @@ //! Definitions for generating graphs from a simulation. use petgraph::{Graph, Directed}; -use std::collections::HashMap; -use super::structure::{RSlabel, RSsystem, RSset}; -use super::support_structures::TransitionsIterator; -use super::translator::{self, IdType}; use std::rc::Rc; +use super::label::Label; +use super::set::Set; +use super::system::System; +use super::translator::{self, IdType}; -pub type RSgraph = Graph; +pub type SystemGraph = Graph; -/// Creates a graph starting from a system as root node -pub fn digraph( - system: RSsystem -) -> Result { - let mut graph = Graph::default(); - let node = graph.add_node(system.clone()); - - let mut association = HashMap::new(); - association.insert(system.clone(), node); - - let mut stack = vec![system]; - - while let Some(current) = stack.pop() { - // depth first - let current_node = *association.get(¤t).unwrap(); - - for (label, next) in TransitionsIterator::from(¤t)? { - // if not already visited - let next_node = association.entry(next.clone()).or_insert_with(|| { - stack.push(next.clone()); - graph.add_node(next) - }); - graph.add_edge(current_node, *next_node, label); - } - } - Ok(graph) -} - - -fn common_system_entities(graph: &RSgraph) -> RSset { +fn common_system_entities(graph: &SystemGraph) -> Set { graph.node_references().fold( None, |acc, node| @@ -47,7 +18,7 @@ fn common_system_entities(graph: &RSgraph) -> RSset { None => Some(node.1.available_entities.clone()), Some(acc) => Some(node.1.available_entities.intersection(&acc)) } - ).unwrap_or(RSset::new()) + ).unwrap_or(Set::new()) } macro_rules! common_label { @@ -57,7 +28,7 @@ macro_rules! common_label { $empty_expr:expr, $some_expr:expr ) => { - fn $name(graph: &RSgraph) -> RSset { + fn $name(graph: &SystemGraph) -> Set { graph.edge_references().fold( None, |$acc_name, $edge_name| { @@ -67,7 +38,7 @@ macro_rules! common_label { Some($acc_name) => Some($some_expr) } } - ).unwrap_or(RSset::new()) + ).unwrap_or(Set::new()) } }; } @@ -130,17 +101,20 @@ where &self, edge_map: &super::structure::RSassert, translator: &mut super::translator::Translator - ) -> Result, String>; + ) -> Result< + Graph + , String>; } -impl<'a> MapEdges<'a, RSsystem, RSlabel, Directed, u32> - for RSgraph +impl<'a> MapEdges<'a, System, Label, Directed, u32> + for SystemGraph { fn map_edges( &self, edge_map: &super::structure::RSassert, translator: &mut super::translator::Translator - )-> Result, String> { + )-> Result, String> { use petgraph::graph::EdgeIndex; let mut g = Graph::with_capacity(self.node_count(), self.edge_count()); @@ -171,11 +145,11 @@ pub enum NodeDisplayBase { String { string: String }, Hide, Entities, - MaskEntities { mask: RSset }, - ExcludeEntities { mask: RSset }, + MaskEntities { mask: Set }, + ExcludeEntities { mask: Set }, Context, UncommonEntities, - MaskUncommonEntities { mask: RSset } + MaskUncommonEntities { mask: Set } } pub struct NodeDisplay { @@ -183,12 +157,12 @@ pub struct NodeDisplay { } type GraphMapNodesFnTy<'a> = - dyn Fn(petgraph::prelude::NodeIndex, &'a RSsystem) -> String + 'a; + dyn Fn(petgraph::prelude::NodeIndex, &'a System) -> String + 'a; fn match_node_display<'a>( base: &NodeDisplayBase, - common_entities: Rc, + common_entities: Rc, translator: Rc ) -> Box> { use NodeDisplayBase::*; @@ -235,13 +209,13 @@ impl NodeDisplay { pub fn generate<'a>( self, translator: Rc, - current_graph: &RSgraph + current_graph: &SystemGraph ) -> Box> { let common_entities = if self.contains_uncommon() { Rc::new(common_system_entities(current_graph)) } else { - Rc::new(RSset::new()) + Rc::new(Set::new()) }; Box::new( @@ -267,13 +241,13 @@ impl NodeDisplay { pub enum EdgeDisplayBase { String { string: String }, Hide, - Products { mask: Option, filter_common: bool }, - Entities { mask: Option, filter_common: bool }, - Context { mask: Option, filter_common: bool }, - Union { mask: Option, filter_common: bool }, - Difference { mask: Option, filter_common: bool }, - EntitiesDeleted { mask: Option, filter_common: bool }, - EntitiesAdded { mask: Option, filter_common: bool }, + Products { mask: Option, filter_common: bool }, + Entities { mask: Option, filter_common: bool }, + Context { mask: Option, filter_common: bool }, + Union { mask: Option, filter_common: bool }, + Difference { mask: Option, filter_common: bool }, + EntitiesDeleted { mask: Option, filter_common: bool }, + EntitiesAdded { mask: Option, filter_common: bool }, } pub struct EdgeDisplay { @@ -281,17 +255,17 @@ pub struct EdgeDisplay { } type GraphMapEdgesFnTy<'a> = - dyn Fn(petgraph::prelude::EdgeIndex, &'a RSlabel) -> String + 'a; + dyn Fn(petgraph::prelude::EdgeIndex, &'a Label) -> String + 'a; #[derive(Default, Clone)] struct CommonEntities { - common_products: RSset, - common_entities: RSset, - common_context: RSset, - common_union: RSset, - common_difference: RSset, - common_entities_deleted: RSset, - common_entities_added: RSset, + common_products: Set, + common_entities: Set, + common_context: Set, + common_union: Set, + common_difference: Set, + common_entities_deleted: Set, + common_entities_added: Set, } fn match_edge_display<'a>( @@ -411,7 +385,7 @@ impl EdgeDisplay { pub fn generate<'a>( self, translator: Rc, - current_graph: &RSgraph + current_graph: &SystemGraph ) -> Box> { // create the structure for common entities if required let common = { @@ -485,7 +459,7 @@ pub enum OperationType { } impl OperationType { - pub fn evaluate(&self, a: &RSset, b: &RSset) -> bool { + pub fn evaluate(&self, a: &Set, b: &Set) -> bool { match self { Self::Equals => { a.is_subset(b) && b.is_subset(a) @@ -510,7 +484,7 @@ impl OperationType { pub enum ContextColorConditional { Nill, RecursiveIdentifier(IdType), - EntitySet(OperationType, RSset), + EntitySet(OperationType, Set), NonDeterministicChoice, Summation, WaitEntity @@ -519,7 +493,7 @@ pub enum ContextColorConditional { #[derive(Clone)] pub enum NodeColorConditional { ContextConditional(ContextColorConditional), - EntitiesConditional(OperationType, RSset) + EntitiesConditional(OperationType, Set) } #[derive(Clone)] @@ -540,7 +514,7 @@ fn node_formatter_base_color( fn match_node_color_conditional<'a>( rule: &'a NodeColorConditional, color: &'a String, - original_graph: Rc, + original_graph: Rc, star: Option ) -> Box> { use super::format_helpers::node_formatter::*; @@ -595,7 +569,7 @@ fn match_node_color_conditional<'a>( impl NodeColor { pub fn generate<'a>( self, - original_graph: Rc, + original_graph: Rc, star: Option ) -> Box> { Box::new( @@ -635,14 +609,14 @@ type RSformatEdgeTyOpt<'a> = #[derive(Clone)] pub enum EdgeColorConditional { - Entities(OperationType, RSset), - Context(OperationType, RSset), - T(OperationType, RSset), - Reactants(OperationType, RSset), - ReactantsAbsent(OperationType, RSset), - Inhibitors(OperationType, RSset), - InhibitorsPresent(OperationType, RSset), - Products(OperationType, RSset), + Entities(OperationType, Set), + Context(OperationType, Set), + T(OperationType, Set), + Reactants(OperationType, Set), + ReactantsAbsent(OperationType, Set), + Inhibitors(OperationType, Set), + InhibitorsPresent(OperationType, Set), + Products(OperationType, Set), } #[derive(Clone)] @@ -662,7 +636,7 @@ fn edge_formatter_base_color( fn match_edge_color_conditional<'a>( rule: &'a EdgeColorConditional, color: &'a String, - original_graph: Rc + original_graph: Rc ) -> Box> { use super::format_helpers::edge_formatter::*; match rule { @@ -720,7 +694,7 @@ fn match_edge_color_conditional<'a>( impl EdgeColor { pub fn generate<'a>( self, - original_graph: Rc, + original_graph: Rc, ) -> Box> { Box::new( move |i, n| { diff --git a/src/rsprocess/label.rs b/src/rsprocess/label.rs new file mode 100644 index 0000000..9c1860d --- /dev/null +++ b/src/rsprocess/label.rs @@ -0,0 +1,117 @@ +use std::hash::Hash; +use serde::{Deserialize, Serialize}; + +use super::set::Set; + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialOrd)] +pub struct Label { + pub available_entities: Set, + pub context: Set, + pub t: Set, + pub reactants: Set, + pub reactants_absent: Set, + pub inhibitors: Set, + pub inhibitors_present: Set, + pub products: Set, +} + +impl Label { + pub fn new() -> Self { + Label { + available_entities: Set::new(), + context: Set::new(), + t: Set::new(), + reactants: Set::new(), + reactants_absent: Set::new(), + inhibitors: Set::new(), + inhibitors_present: Set::new(), + products: Set::new(), + } + } + + #[allow(clippy::too_many_arguments)] + pub fn from( + available_entities: Set, + context: Set, + t: Set, + reactants: Set, + reactants_absent: Set, + inhibitors: Set, + inhibitors_present: Set, + products: Set, + ) -> Self { + Label { + available_entities, + context, + t, + reactants, + reactants_absent, + inhibitors, + inhibitors_present, + products, + } + } + + #[allow(clippy::too_many_arguments)] + pub fn create( + available_entities: Set, + context: Set, + reactants: Set, + reactants_absent: Set, + inhibitors: Set, + inhibitors_present: Set, + products: Set, + ) -> Self { + Label { + available_entities: available_entities.clone(), + context: context.clone(), + t: available_entities.union(&context), + reactants, + reactants_absent, + inhibitors, + inhibitors_present, + products, + } + } + + pub fn get_context(&self) -> (&Set, &Set, &Set) { + ( + &self.available_entities, + &self.context, + &self.t, + ) + } +} + +impl Default for Label { + fn default() -> Self { + Self::new() + } +} + +impl PartialEq for Label { + fn eq(&self, other: &Self) -> bool { + self.available_entities == other.available_entities && + self.context == other.context && + // self.t == other.t && // no need since its the union of the above + // // elements + self.reactants == other.reactants && + self.reactants_absent == other.reactants_absent && + self.inhibitors == other.inhibitors && + self.inhibitors_present == other.inhibitors_present && + self.products == other.products + } +} + +impl Hash for Label { + fn hash(&self, state: &mut H) { + self.available_entities.hash(state); + self.context.hash(state); + // self.t.hash(state); + self.reactants.hash(state); + self.reactants_absent.hash(state); + self.inhibitors.hash(state); + self.inhibitors_present.hash(state); + self.products.hash(state); + } +} diff --git a/src/rsprocess/mod.rs b/src/rsprocess/mod.rs index 0e818c6..d626648 100644 --- a/src/rsprocess/mod.rs +++ b/src/rsprocess/mod.rs @@ -1,19 +1,27 @@ //! Crate root -pub mod classical; -pub mod confluence; -pub mod frequency; -pub mod perpetual; -pub mod statistics; pub mod structure; -pub mod support_structures; -pub mod transitions; + pub mod translator; + pub mod graph; +pub mod transitions; pub mod rsdot; pub mod serialize; pub mod presets; -pub mod bisimilarity; pub mod assert; +pub mod bisimilarity; +pub mod frequency; mod format_helpers; + +mod set; +mod reaction; +mod process; +mod choices; +mod environment; +mod system; +mod label; + +#[cfg(test)] +mod system_test; diff --git a/src/rsprocess/perpetual.rs b/src/rsprocess/perpetual.rs deleted file mode 100644 index 9ab7eb4..0000000 --- a/src/rsprocess/perpetual.rs +++ /dev/null @@ -1,321 +0,0 @@ -//! Definitions for finding loops in simulation. - -use super::classical::compute_all; -use super::structure::{RSenvironment, RSprocess, RSreaction, RSset, RSsystem}; -use super::translator::IdType; - -/// Returns the prefix and the loop from a trace. -fn split<'a>( - set: &'a RSset, - trace: &'a [RSset] -) -> Option<(&'a [RSset], &'a [RSset])> { - let position = trace.iter().rposition(|x| x == set); - position.map(|pos| trace.split_at(pos)) -} - -/// Finds the loops by simulating the system. -fn find_loop( - rs: &[RSreaction], - entities: RSset, - q: &RSset -) -> (Vec, Vec) { - let mut entities = entities; - let mut trace = vec![]; - loop { - if let Some((prefix, hoop)) = split(&entities, &trace) { - return (prefix.to_vec(), hoop.to_vec()); - } else { - let t = entities.union(q); - let products = compute_all(&t, rs); - trace.push(entities.clone()); - entities = products; - } - } -} - -/// Finds the loops by simulating the system. -fn find_only_loop( - rs: &[RSreaction], - entities: RSset, - q: &RSset -) -> Vec { - let mut entities = entities; - let mut trace = vec![]; - loop { - if let Some((_prefix, hoop)) = split(&entities, &trace) { - return hoop.to_vec(); - } else { - let t = entities.union(q); - let products = compute_all(&t, rs); - trace.push(entities.clone()); - entities = products; - } - } -} - -/// Finds the loops and the length of the prefix by simulating the system. -fn find_prefix_len_loop( - rs: &[RSreaction], - entities: RSset, - q: &RSset -) -> (usize, Vec) { - let mut entities = entities; - let mut trace = vec![]; - loop { - if let Some((prefix, hoop)) = split(&entities, &trace) { - return (prefix.len(), hoop.to_vec()); - } else { - let t = entities.union(q); - let products = compute_all(&t, rs); - trace.push(entities.clone()); - entities = products; - } - } -} - -// ----------------------------------------------------------------------------- - -/// Finds only the rules X = pre(Q, rec(X)), but not only x = pre(Q, rec(x)) -/// to use in filter_map. -fn filter_delta<'a>(x: (&IdType, &'a RSprocess)) -> Option<&'a RSset> { - use super::structure::RSprocess::*; - let (id, rest) = x; - - if let EntitySet { entities, next_process } = rest - && let RecursiveIdentifier { identifier } = &**next_process - && identifier == id - { - return Some(entities); - } - - None -} - -/// A special case of systems is when the context recursively provides always -/// the same set of entities. The corresponding computation is infinite. It -/// consists of a finite sequence of states followed by a looping sequence. -/// IMPORTANT: We return all loops for all X = Q.X, by varing X. The set of -/// reactions Rs and the context x are constant. Each state of the computation -/// is distinguished by the current entities E. Under these assumptions, the -/// predicate lollipop finds the Prefixes and the Loops sequences of entities. -/// see lollipop -pub fn lollipops_decomposed( - delta: &RSenvironment, - reaction_rules: &[RSreaction], - available_entities: &RSset, -) -> Vec<(Vec, Vec)> { - // FIXME: i think we are only interested in "x", not all symbols that - // satisfy X = pre(Q, rec(X)) - let filtered = delta.iter().filter_map(filter_delta); - - let find_loop_fn = |q| find_loop(reaction_rules, - available_entities.clone(), - q); - - filtered.map(find_loop_fn).collect::>() -} - -/// A special case of systems is when the context recursively provides always -/// the same set of entities. The corresponding computation is infinite. It -/// consists of a finite sequence of states followed by a looping sequence. -/// IMPORTANT: We return all loops for all X = Q.X, by varing X. The set of -/// reactions Rs and the context x are constant. Each state of the computation -/// is distinguished by the current entities E. Under these assumptions, the -/// predicate lollipop finds the Prefixes and the Loops sequences of entities. -/// see lollipop -pub fn lollipops(system: RSsystem) -> Vec<(Vec, Vec)> { - lollipops_decomposed( - &system.delta, - &system.reaction_rules, - &system.available_entities, - ) -} - -/// Only returns the loop part of the lollipop, returns for all X, where X = Q.X -/// see loop -pub fn lollipops_only_loop(system: RSsystem) -> Vec> { - let filtered = system.delta.iter().filter_map(filter_delta); - - let find_loop_fn = |q| { - find_only_loop( - &system.reaction_rules, - system.available_entities.clone(), - q, - ) - }; - - filtered.map(find_loop_fn).collect::>() -} - -pub fn lollipops_prefix_len_loop_decomposed( - delta: &RSenvironment, - reaction_rules: &[RSreaction], - available_entities: &RSset, -) -> Vec<(usize, Vec)> { - let filtered = delta.iter().filter_map(filter_delta); - - let find_loop_fn = |q| find_prefix_len_loop(reaction_rules, - available_entities.clone(), - q); - - filtered.map(find_loop_fn).collect::>() -} - -/// see loop -pub fn lollipops_only_loop_decomposed( - delta: &RSenvironment, - reaction_rules: &[RSreaction], - available_entities: &RSset, -) -> Vec> { - let filtered = delta.iter().filter_map(filter_delta); - - let find_loop_fn = |q| find_only_loop(reaction_rules, - available_entities.clone(), - q); - - filtered.map(find_loop_fn).collect::>() -} - -// ----------------------------------------------------------------------------- -// Named versions -// ----------------------------------------------------------------------------- - -/// Finds only the rules symb = pre(Q, rec(symb)), get symb from a translator -/// to use in filter_map. -fn filter_delta_named<'a>( - x: (&IdType, &'a RSprocess), - symb: &IdType -) -> Option<&'a RSset> { - use super::structure::RSprocess::*; - let (id, rest) = x; - if id != symb { - return None; - } - - if let EntitySet { entities, next_process } = rest - && let RecursiveIdentifier { identifier } = &**next_process - && identifier == id - { - return Some(entities); - } - None -} - -/// A special case of systems is when the context recursively provides always -/// the same set of entities. The corresponding computation is infinite. It -/// consists of a finite sequence of states followed by a looping sequence. -/// IMPORTANT: We return all loops for all X = Q.X, by varing X. The set of -/// reactions Rs and the context x are constant. Each state of the computation -/// is distinguished by the current entities E. Under these assumptions, the -/// predicate lollipop finds the Prefixes and the Loops sequences of entities. -/// see lollipop -pub fn lollipops_decomposed_named( - delta: &RSenvironment, - reaction_rules: &[RSreaction], - available_entities: &RSset, - symb: IdType, -) -> Option<(Vec, Vec)> { - let filtered = delta - .iter() - .filter_map(|x| filter_delta_named(x, &symb)) - .next(); - - let find_loop_fn = |q| find_loop(reaction_rules, - available_entities.clone(), - q); - - filtered.map(find_loop_fn) -} - -/// A special case of systems is when the context recursively provides always -/// the same set of entities. The corresponding computation is infinite. It -/// consists of a finite sequence of states followed by a looping sequence. -/// IMPORTANT: We return all loops for all X = Q.X, by varing X. The set of -/// reactions Rs and the context x are constant. Each state of the computation -/// is distinguished by the current entities E. Under these assumptions, the -/// predicate lollipop finds the Prefixes and the Loops sequences of entities. -/// see lollipop -pub fn lollipops_named( - system: &RSsystem, - symb: IdType -) -> Option<(Vec, Vec)> { - lollipops_decomposed_named( - &system.delta, - &system.reaction_rules, - &system.available_entities, - symb, - ) -} - -/// Only returns the loop part of the lollipop, returns for all X, where X = Q.X -/// see loop -pub fn lollipops_only_loop_named( - system: &RSsystem, - symb: IdType -) -> Option> { - let filtered = system - .delta - .iter() - .filter_map(|x| filter_delta_named(x, &symb)) - .next(); - - let find_loop_fn = |q| { - find_only_loop( - &system.reaction_rules, - system.available_entities.clone(), - q, - ) - }; - - filtered.map(find_loop_fn) -} - -pub fn lollipops_prefix_len_loop_decomposed_named( - delta: &RSenvironment, - reaction_rules: &[RSreaction], - available_entities: &RSset, - symb: IdType, -) -> Option<(usize, Vec)> { - let filtered = delta - .iter() - .filter_map(|x| filter_delta_named(x, &symb)) - .next(); - - let find_loop_fn = |q| find_prefix_len_loop(reaction_rules, - available_entities.clone(), - q); - - filtered.map(find_loop_fn) -} - -/// see loop -pub fn lollipops_only_loop_decomposed_named( - delta: &RSenvironment, - reaction_rules: &[RSreaction], - available_entities: &RSset, - symb: IdType, -) -> Option> { - let filtered = delta - .iter() - .filter_map(|x| filter_delta_named(x, &symb)) - .next(); - - let find_loop_fn = |q| find_only_loop(reaction_rules, - available_entities.clone(), - q); - - filtered.map(find_loop_fn) -} - -/// see loop/5 -pub fn lollipops_only_loop_decomposed_q( - q: &RSset, - reaction_rules: &[RSreaction], - available_entities: &RSset, -) -> Vec { - let find_loop_fn = |q| find_only_loop(reaction_rules, - available_entities.clone(), - q); - - find_loop_fn(q) -} diff --git a/src/rsprocess/presets.rs b/src/rsprocess/presets.rs index 3a00419..fa01afc 100644 --- a/src/rsprocess/presets.rs +++ b/src/rsprocess/presets.rs @@ -121,7 +121,7 @@ impl System { pub enum EvaluatedSystem { Graph { - graph: graph::RSgraph, + graph: graph::SystemGraph, translator: Translator, }, System { @@ -333,12 +333,12 @@ fn save_file(contents: &String, path_string: String) -> Result<(), String> { pub fn stats(system: &EvaluatedSystem) -> Result { match system { EvaluatedSystem::System { sys, translator } => - Ok(statistics::of_RSsystem(translator, sys)), + Ok(sys.statistics(translator)), EvaluatedSystem::Graph { graph, translator } => { let Some(sys) = graph.node_weights().next() else { return Err("No node found in graph".into()); }; - Ok(statistics::of_RSsystem(translator, sys)) + Ok(sys.statistics(translator)) } } } @@ -349,12 +349,12 @@ pub fn stats(system: &EvaluatedSystem) -> Result { pub fn target(system: &EvaluatedSystem) -> Result { let (res, translator) = match system { EvaluatedSystem::System { sys, translator } => - (transitions::target(sys)?, translator), + (sys.target()?, translator), EvaluatedSystem::Graph { graph, translator } => { let Some(sys) = graph.node_weights().next() else { return Err("No node found in graph".into()); }; - (transitions::target(sys)?, translator) + (sys.target()?, translator) } }; Ok(format!( @@ -371,13 +371,13 @@ pub fn target(system: &EvaluatedSystem) -> Result { pub fn traversed(system: &EvaluatedSystem) -> Result { let (res, translator) = match system { EvaluatedSystem::System { sys, translator } => { - (transitions::run_separated(sys)?, translator) + (sys.run_separated()?, translator) } EvaluatedSystem::Graph { graph, translator } => { let Some(sys) = graph.node_weights().next() else { return Err("No node found in graph".into()); }; - (transitions::run_separated(sys)?, translator) + (sys.run_separated()?, translator) } }; @@ -414,7 +414,7 @@ pub fn hoop( let Some(id) = translator.encode_not_mut(&symbol) else { return Err(format!("Symbol {symbol} not found")); }; - let res = match perpetual::lollipops_only_loop_named(res, id) { + let res = match res.lollipops_only_loop_named(id) { Some(o) => o, None => { return Err("No loop found.".into()); @@ -448,7 +448,7 @@ pub fn freq(system: &EvaluatedSystem) -> Result { } }; - let res = frequency::naive_frequency(sys)?; + let res = frequency::Frequency::naive_frequency(sys)?; Ok(format!( "Frequency of encountered symbols:\n{}", @@ -476,7 +476,7 @@ pub fn limit_freq( let (_, sets) = read_file(translator, experiment, parser_experiment)?; let res = - match frequency::limit_frequency( + match frequency::Frequency::limit_frequency( &sets, &sys.reaction_rules, &sys.available_entities) @@ -515,7 +515,7 @@ pub fn fast_freq( let (weights, sets) = read_file(translator, experiment, parser_experiment)?; - let res = match frequency::fast_frequency( + let res = match frequency::Frequency::fast_frequency( &sets, &sys.reaction_rules, &sys.available_entities, @@ -539,7 +539,7 @@ pub fn digraph(system: &mut EvaluatedSystem) -> Result<(), String> { if let EvaluatedSystem::System { sys, translator } = system { *system = EvaluatedSystem::Graph { - graph: graph::digraph(sys.clone())?, + graph: sys.clone().digraph()?, translator: translator.to_owned(), }; } @@ -585,9 +585,9 @@ pub fn bisimilar( b.map_edges(edge_relabeler, translator_b)?; Ok(format!( "{}", - // super::bisimilarity::bisimilarity_kanellakis_smolka(&&a, &&b) - // super::bisimilarity::bisimilarity_paige_tarjan_ignore_labels(&&a, &&b) - super::bisimilarity::bisimilarity_paige_tarjan(&&a, &&b) + // super::bisimilarity::bisimilarity_kanellakis_smolka::bisimilarity(&&a, &&b) + // super::bisimilarity::bisimilarity_paige_tarjan::bisimilarity_ignore_labels(&&a, &&b) + super::bisimilarity::bisimilarity_paige_tarkan::bisimilarity(&&a, &&b) )) }, _ => { unreachable!() } @@ -706,7 +706,7 @@ pub fn serialize(system: &EvaluatedSystem, path: String) -> Result<(), String> { /// deserialization pub fn deserialize( input_path: String, -) -> Result<(graph::RSgraph, Translator), String> +) -> Result<(graph::SystemGraph, Translator), String> { // relative path let mut path = match env::current_dir() { diff --git a/src/rsprocess/process.rs b/src/rsprocess/process.rs new file mode 100644 index 0000000..25cfdf7 --- /dev/null +++ b/src/rsprocess/process.rs @@ -0,0 +1,122 @@ +use super::translator::IdType; +use std::collections::VecDeque; +use std::hash::Hash; +use std::rc::Rc; +use serde::{Deserialize, Serialize}; + +use super::set::Set; +use super::reaction::Reaction; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Process { + Nill, + RecursiveIdentifier { + identifier: IdType, + }, + EntitySet { + entities: Set, + next_process: Rc, + }, + Guarded { + reaction: Reaction, + next_process: Rc, + }, + WaitEntity { + repeat: i64, + repeated_process: Rc, + next_process: Rc, + }, + Summation { + children: Vec>, + }, + NondeterministicChoice { + children: Vec>, + }, +} + +impl Process { + // TODO: remove all the clone() + pub fn concat(&self, new: &Process) -> Process { + match (self, new) { + ( + Process::NondeterministicChoice { children: c1 }, + Process::NondeterministicChoice { children: c2 }, + ) => Process::NondeterministicChoice { + children: [c1.clone(), c2.clone()].concat(), + }, + (Process::NondeterministicChoice { children }, new) + | (new, Process::NondeterministicChoice { children }) => { + let mut new_children = children.clone(); + new_children.push(Rc::new(new.clone())); + Process::NondeterministicChoice { + children: new_children, + } + } + (_, _) => Process::NondeterministicChoice { + children: vec![Rc::new(self.clone()), Rc::new(new.clone())], + }, + } + } + + /// returns all elements used + pub fn all_elements(&self) -> Set { + let mut queue = VecDeque::from([self]); + let mut elements = Set::new(); + + while let Some(el) = queue.pop_front() { + match el { + Self::Nill => {} + Self::RecursiveIdentifier { identifier: _ } => {} + Self::EntitySet { + entities, + next_process, + } => { + elements.push(entities); + queue.push_back(next_process); + } + Self::Guarded { reaction, next_process } => { + elements.push(&reaction.reactants); + elements.push(&reaction.inhibitors); + elements.push(&reaction.products); + queue.push_back(next_process); + } + Self::WaitEntity { + repeat: _, + repeated_process, + next_process, + } => { + queue.push_back(repeated_process); + queue.push_back(next_process); + } + Self::Summation { children } => { + for c in children { + queue.push_back(c); + } + } + Self::NondeterministicChoice { children } => { + for c in children { + queue.push_back(c); + } + } + } + } + elements + } + + + /// Finds only the rules X = pre(Q, rec(X)), but not only x = pre(Q, rec(x)) + /// to use in filter_map. + pub fn filter_delta<'a>( + &'a self, + id: &IdType + ) -> Option<&'a Set> { + if let Self::EntitySet { entities, next_process } = self + && let Self::RecursiveIdentifier { identifier } = &**next_process + && identifier == id + { + return Some(entities); + } + + None + } +} diff --git a/src/rsprocess/reaction.rs b/src/rsprocess/reaction.rs new file mode 100644 index 0000000..6d22f7e --- /dev/null +++ b/src/rsprocess/reaction.rs @@ -0,0 +1,151 @@ +//! Definitions for the 'classical' mechanism for computation. +//! +//! Allows to define the 'classical' mechanism to compute in a Reaction System +//! (RS) Framework. + +use std::hash::Hash; +use serde::{Deserialize, Serialize}; +use super::set::Set; + +/// Basic structure for a reaction. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct Reaction { + pub reactants: Set, + pub inhibitors: Set, + pub products: Set, +} + +impl Reaction { + pub fn new() -> Self { + Reaction { + reactants: Set::new(), + inhibitors: Set::new(), + products: Set::new(), + } + } + + pub fn from(reactants: Set, inhibitors: Set, products: Set) -> Self { + Reaction { + reactants, + inhibitors, + products, + } + } + + /// returns true if ```current_state``` enables the reaction + /// see enable + pub fn enabled(&self, current_state: &Set) -> bool { + self.reactants.is_subset(current_state) + && self.inhibitors.is_disjoint(current_state) + } + + /// Computes the result of a single reaction (if enabled returns the products) + /// otherwise returns None. + /// see result + pub fn compute_step<'a>( + &'a self, + current_state: &'a Set, + ) -> Option<&'a Set> { + if self.enabled(current_state) { + Some(&self.products) + } else { + None + } + } + + /// Computes the result of a series of reactions. Returns the union of all + /// products. + /// see result + pub fn compute_all<'a>( + current_state: &'a Set, + reactions: &'a [Self] + ) -> Set { + reactions.iter().fold(Set::new(), |mut acc, r| { + acc.union_option(r.compute_step(current_state)); + acc + }) + } + + + /// Finds the loops by simulating the system. + pub fn find_loop( + rs: &[Self], + entities: Set, + q: &Set + ) -> (Vec, Vec) { + let mut entities = entities; + let mut trace = vec![]; + loop { + if let Some((prefix, hoop)) = entities.split(&trace) { + return (prefix.to_vec(), hoop.to_vec()); + } else { + let t = entities.union(q); + let products = Self::compute_all(&t, rs); + trace.push(entities.clone()); + entities = products; + } + } + } + + + /// Finds the loops by simulating the system. + pub fn find_only_loop( + rs: &[Self], + entities: Set, + q: &Set + ) -> Vec { + let mut entities = entities; + let mut trace = vec![]; + loop { + if let Some((_prefix, hoop)) = entities.split(&trace) { + return hoop.to_vec(); + } else { + let t = entities.union(q); + let products = Self::compute_all(&t, rs); + trace.push(entities.clone()); + entities = products; + } + } + } + + + /// Finds the loops and the length of the prefix by simulating the system. + pub fn find_prefix_len_loop( + rs: &[Self], + entities: Set, + q: &Set + ) -> (usize, Vec) { + let mut entities = entities; + let mut trace = vec![]; + loop { + if let Some((prefix, hoop)) = entities.split(&trace) { + return (prefix.len(), hoop.to_vec()); + } else { + let t = entities.union(q); + let products = Self::compute_all(&t, rs); + trace.push(entities.clone()); + entities = products; + } + } + } + + + /// see loop/5 + pub fn lollipops_only_loop_decomposed_q( + reaction_rules: &[Self], + q: &Set, + available_entities: &Set, + ) -> Vec { + let find_loop_fn = + |q| Reaction::find_only_loop(reaction_rules, + available_entities.clone(), + q); + find_loop_fn(q) + } +} + +impl Default for Reaction { + fn default() -> Self { + Reaction::new() + } +} diff --git a/src/rsprocess/serialize.rs b/src/rsprocess/serialize.rs index 227485a..f7a21ac 100644 --- a/src/rsprocess/serialize.rs +++ b/src/rsprocess/serialize.rs @@ -12,14 +12,14 @@ use super::translator::Translator; #[derive(Serialize, Deserialize)] struct GraphAndTranslator { - graph: graph::RSgraph, + graph: graph::SystemGraph, translator: Translator } /// Serializer for graph and translator. pub fn ser( writer: W, - graph: &graph::RSgraph, + graph: &graph::SystemGraph, translator: &Translator ) -> Result<(), serde_cbor_2::Error> where @@ -35,7 +35,7 @@ where /// Deserializer for file that contains graph and translator. pub fn de( reader: R -) -> Result<(graph::RSgraph, Translator), serde_cbor_2::Error> +) -> Result<(graph::SystemGraph, Translator), serde_cbor_2::Error> where R: io::Read, { diff --git a/src/rsprocess/set.rs b/src/rsprocess/set.rs new file mode 100644 index 0000000..7635b1c --- /dev/null +++ b/src/rsprocess/set.rs @@ -0,0 +1,141 @@ +use super::translator::IdType; +use std::collections::BTreeSet; +use std::hash::Hash; +use serde::{Deserialize, Serialize}; + +/// Basic set of entities. +#[derive(Clone, Debug, PartialOrd, Eq, Ord, Serialize, Deserialize)] +pub struct Set { + pub identifiers: BTreeSet, +} + +impl From<[IdType; N]> for Set { + fn from(arr: [IdType; N]) -> Self { + Set { + identifiers: BTreeSet::from(arr), + } + } +} + +impl From<&[IdType]> for Set { + fn from(arr: &[IdType]) -> Self { + Set { + identifiers: BTreeSet::from_iter(arr.to_vec()), + } + } +} + +impl From> for Set { + fn from(arr: Vec) -> Self { + Set { + identifiers: BTreeSet::from_iter(arr), + } + } +} + +impl Set { + pub const fn new() -> Self { + Set { + identifiers: BTreeSet::new(), + } + } + + pub fn is_subset(&self, b: &Set) -> bool { + self.identifiers.is_subset(&b.identifiers) + } + + pub fn is_disjoint(&self, b: &Set) -> bool { + self.identifiers.is_disjoint(&b.identifiers) + } + + // returns the new set a \cup b + pub fn union(&self, b: &Set) -> Set { + let mut ret: Set = b.clone(); + ret.identifiers.extend(self.identifiers.iter()); + ret + } + + pub fn union_option(&mut self, b: Option<&Set>) { + if let Some(b) = b { + self.identifiers.extend(b.iter()); + } + } + + /// returns the new set a \cap b + pub fn intersection(&self, b: &Set) -> Set { + // TODO maybe find more efficient way without copy/clone + let res: BTreeSet<_> = b + .identifiers + .intersection(&self.identifiers) + .copied() + .collect(); + Set { identifiers: res } + } + + /// returns the new set a ∖ b + pub fn subtraction(&self, b: &Set) -> Set { + // TODO maybe find more efficient way without copy/clone + let res: BTreeSet<_> = self + .identifiers + .difference(&b.identifiers) + .copied() + .collect(); + Set { identifiers: res } + } + + pub fn iter(&self) -> std::collections::btree_set::Iter<'_, IdType> { + self.identifiers.iter() + } + + pub fn len(&self) -> usize { + self.identifiers.len() + } + + pub fn insert(&mut self, el: IdType) -> bool { + self.identifiers.insert(el) + } + + pub fn push(&mut self, b: &Set) { + self.identifiers.extend(b.iter()) + } + + pub fn is_empty(&self) -> bool { + self.identifiers.is_empty() + } + + /// Returns the prefix and the loop from a trace. + pub fn split<'a>( + &'a self, + trace: &'a [Self] + ) -> Option<(&'a [Self], &'a [Self])> { + let position = trace.iter().rposition(|x| x == self); + position.map(|pos| trace.split_at(pos)) + } +} + +impl Default for Set { + fn default() -> Self { + Set::new() + } +} + +impl PartialEq for Set { + fn eq(&self, other: &Self) -> bool { + self.identifiers.eq(&other.identifiers) + } +} + +impl Hash for Set { + fn hash(&self, state: &mut H) { + self.identifiers.hash(state) + } +} + +impl IntoIterator for Set { + type Item = IdType; + type IntoIter = std::collections::btree_set::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.identifiers.into_iter() + } +} diff --git a/src/rsprocess/statistics.rs b/src/rsprocess/statistics.rs deleted file mode 100644 index b12ddcf..0000000 --- a/src/rsprocess/statistics.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! Non simulated statistics of a system. - -use super::structure::RSset; -use super::structure::RSsystem; -use super::translator; -use super::translator::Translator; - -/// Returns statistics about the system. -/// see main_do(stat,MissingE) -#[allow(non_snake_case)] -pub fn of_RSsystem<'a>(translator: &'a Translator, system: &'a RSsystem) -> String { - let mut result: String = "Statistics:\n".into(); - result.push_str( - "=============================================================\n" - ); - result.push_str(&format!( - "the initial state has {} entities:\n", - system.available_entities.len() - )); - result.push_str(&format!( - "{}\n", - translator::RSsetDisplay::from(translator, &system.available_entities) - )); - - let reactants = system - .reaction_rules - .iter() - .fold(RSset::new(), |acc, new| acc.union(&new.reactants)); - result.push_str(&format!( - "The reactants are {}:\n{}\n", - reactants.len(), - translator::RSsetDisplay::from(translator, &reactants) - )); - - let inhibitors = system - .reaction_rules - .iter() - .fold(RSset::new(), |acc, new| acc.union(&new.inhibitors)); - result.push_str(&format!( - "The inhibitors are {}:\n{}\n", - inhibitors.len(), - translator::RSsetDisplay::from(translator, &inhibitors) - )); - - let products = system - .reaction_rules - .iter() - .fold(RSset::new(), |acc, new| acc.union(&new.products)); - result.push_str(&format!( - "The products are {}:\n{}\n", - products.len(), - translator::RSsetDisplay::from(translator, &products) - )); - - let total = reactants.union(&inhibitors.union(&products)); - result.push_str(&format!( - "The reactions involve {} entities:\n{}\n", - total.len(), - translator::RSsetDisplay::from(translator, &total) - )); - - let entities_env = system.delta.all_elements(); - result.push_str(&format!( - "The environment involves {} entities:\n{}\n", - entities_env.len(), - translator::RSsetDisplay::from(translator, &entities_env) - )); - - let entities_context = system.context_process.all_elements(); - result.push_str(&format!( - "The context involves {} entities:\n{}\n", - entities_context.len(), - translator::RSsetDisplay::from(translator, &entities_context) - )); - - let entities_all = total - .union(&entities_env) - .union(&entities_context) - .union(&system.available_entities); - - result.push_str(&format!( - "The whole RS involves {} entities:\n{}\n", - entities_all.len(), - translator::RSsetDisplay::from(translator, &entities_all) - )); - - let possible_e = products - .union(&system.available_entities) - .union(&entities_context); - let missing_e = reactants.subtraction(&possible_e); - result.push_str(&format!( - "There are {} reactants that will never be available:\n{}\n", - missing_e.len(), - translator::RSsetDisplay::from(translator, &missing_e) - )); - - let entities_not_needed = entities_context.subtraction(&total); - result.push_str(&format!( - "The context can provide {} entities that will never be used:\n{}\n", - entities_not_needed.len(), - translator::RSsetDisplay::from(translator, &entities_not_needed) - )); - - result.push_str(&format!( - "There are {} reactions in total.\n", - system.reaction_rules.len() - )); - - let mut admissible_reactions = vec![]; - let mut nonadmissible_reactions = vec![]; - - for reaction in system.reaction_rules.iter() { - if reaction.reactants.is_disjoint(&missing_e) { - admissible_reactions.push(reaction); - } else { - nonadmissible_reactions.push(reaction); - } - } - - result.push_str(&format!( - "- the applicable reactions are {}.\n", - admissible_reactions.len() - )); - - result.push_str(&format!( - "- there are {} reactions that will never be enabled.\n", - nonadmissible_reactions.len() - )); - result.push_str( - "=============================================================" - ); - - result -} diff --git a/src/rsprocess/structure.rs b/src/rsprocess/structure.rs index 81a6d0d..6cf3910 100644 --- a/src/rsprocess/structure.rs +++ b/src/rsprocess/structure.rs @@ -1,629 +1,46 @@ //! Module for all basic structures. -use super::translator::IdType; -use std::collections::{BTreeSet, HashMap, VecDeque}; -use std::hash::Hash; -use std::rc::Rc; -use serde::{Deserialize, Serialize}; - // ----------------------------------------------------------------------------- // RSset // ----------------------------------------------------------------------------- -/// Basic set of entities. -#[derive(Clone, Debug, PartialOrd, Eq, Ord, Serialize, Deserialize)] -pub struct RSset { - pub identifiers: BTreeSet, -} - -impl From<[IdType; N]> for RSset { - fn from(arr: [IdType; N]) -> Self { - RSset { - identifiers: BTreeSet::from(arr), - } - } -} - -impl From<&[IdType]> for RSset { - fn from(arr: &[IdType]) -> Self { - RSset { - identifiers: BTreeSet::from_iter(arr.to_vec()), - } - } -} - -impl From> for RSset { - fn from(arr: Vec) -> Self { - RSset { - identifiers: BTreeSet::from_iter(arr), - } - } -} - -impl RSset { - pub const fn new() -> Self { - RSset { - identifiers: BTreeSet::new(), - } - } - - pub fn is_subset(&self, b: &RSset) -> bool { - self.identifiers.is_subset(&b.identifiers) - } - - pub fn is_disjoint(&self, b: &RSset) -> bool { - self.identifiers.is_disjoint(&b.identifiers) - } - - // returns the new set a \cup b - pub fn union(&self, b: &RSset) -> RSset { - let mut ret: RSset = b.clone(); - ret.identifiers.extend(self.identifiers.iter()); - ret - } - - pub fn union_option(&mut self, b: Option<&RSset>) { - if let Some(b) = b { - self.identifiers.extend(b.iter()); - } - } - - /// returns the new set a \cap b - pub fn intersection(&self, b: &RSset) -> RSset { - // TODO maybe find more efficient way without copy/clone - let res: BTreeSet<_> = b - .identifiers - .intersection(&self.identifiers) - .copied() - .collect(); - RSset { identifiers: res } - } - - /// returns the new set a ∖ b - pub fn subtraction(&self, b: &RSset) -> RSset { - // TODO maybe find more efficient way without copy/clone - let res: BTreeSet<_> = self - .identifiers - .difference(&b.identifiers) - .copied() - .collect(); - RSset { identifiers: res } - } - - pub fn iter(&self) -> std::collections::btree_set::Iter<'_, IdType> { - self.identifiers.iter() - } - - pub fn len(&self) -> usize { - self.identifiers.len() - } - - pub fn insert(&mut self, el: IdType) -> bool { - self.identifiers.insert(el) - } - - pub fn push(&mut self, b: &RSset) { - self.identifiers.extend(b.iter()) - } - - pub fn is_empty(&self) -> bool { - self.identifiers.is_empty() - } -} - -impl Default for RSset { - fn default() -> Self { - RSset::new() - } -} - -impl PartialEq for RSset { - fn eq(&self, other: &Self) -> bool { - self.identifiers.eq(&other.identifiers) - } -} - -impl Hash for RSset { - fn hash(&self, state: &mut H) { - self.identifiers.hash(state) - } -} - -impl IntoIterator for RSset { - type Item = IdType; - type IntoIter = std::collections::btree_set::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.identifiers.into_iter() - } -} +pub type RSset = super::set::Set; // ----------------------------------------------------------------------------- // RSreaction // ----------------------------------------------------------------------------- -/// Basic structure for a reaction. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct RSreaction { - pub reactants: RSset, - pub inhibitors: RSset, - pub products: RSset, -} - -impl RSreaction { - pub fn new() -> Self { - RSreaction { - reactants: RSset::new(), - inhibitors: RSset::new(), - products: RSset::new(), - } - } - - pub fn from(reactants: RSset, inhibitors: RSset, products: RSset) -> Self { - RSreaction { - reactants, - inhibitors, - products, - } - } - - /// returns true if ```current_state``` enables the reaction - /// see enable - pub fn enabled(&self, current_state: &RSset) -> bool { - self.reactants.is_subset(current_state) - && self.inhibitors.is_disjoint(current_state) - } -} - -impl Default for RSreaction { - fn default() -> Self { - RSreaction::new() - } -} +pub type RSreaction = super::reaction::Reaction; // ----------------------------------------------------------------------------- // RSprocess // ----------------------------------------------------------------------------- -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum RSprocess { - Nill, - RecursiveIdentifier { - identifier: IdType, - }, - EntitySet { - entities: RSset, - next_process: Rc, - }, - Guarded { - reaction: RSreaction, - next_process: Rc, - }, - WaitEntity { - repeat: i64, - repeated_process: Rc, - next_process: Rc, - }, - Summation { - children: Vec>, - }, - NondeterministicChoice { - children: Vec>, - }, -} - -impl RSprocess { - // TODO: remove all the clone() - pub fn concat(&self, new: &RSprocess) -> RSprocess { - match (self, new) { - ( - RSprocess::NondeterministicChoice { children: c1 }, - RSprocess::NondeterministicChoice { children: c2 }, - ) => RSprocess::NondeterministicChoice { - children: [c1.clone(), c2.clone()].concat(), - }, - (RSprocess::NondeterministicChoice { children }, new) - | (new, RSprocess::NondeterministicChoice { children }) => { - let mut new_children = children.clone(); - new_children.push(Rc::new(new.clone())); - RSprocess::NondeterministicChoice { - children: new_children, - } - } - (_, _) => RSprocess::NondeterministicChoice { - children: vec![Rc::new(self.clone()), Rc::new(new.clone())], - }, - } - } - - /// returns all elements used - pub fn all_elements(&self) -> RSset { - let mut queue = VecDeque::from([self]); - let mut elements = RSset::new(); - - while let Some(el) = queue.pop_front() { - match el { - Self::Nill => {} - Self::RecursiveIdentifier { identifier: _ } => {} - Self::EntitySet { - entities, - next_process, - } => { - elements.push(entities); - queue.push_back(next_process); - } - Self::Guarded { reaction, next_process } => { - elements.push(&reaction.reactants); - elements.push(&reaction.inhibitors); - elements.push(&reaction.products); - queue.push_back(next_process); - } - Self::WaitEntity { - repeat: _, - repeated_process, - next_process, - } => { - queue.push_back(repeated_process); - queue.push_back(next_process); - } - Self::Summation { children } => { - for c in children { - queue.push_back(c); - } - } - Self::NondeterministicChoice { children } => { - for c in children { - queue.push_back(c); - } - } - } - } - elements - } -} +pub type RSprocess = super::process::Process; // ----------------------------------------------------------------------------- // RSchoices // ----------------------------------------------------------------------------- -#[derive(Clone, Debug)] -pub struct RSchoices { - context_moves: Vec<(Rc, Rc)>, -} -impl RSchoices { - pub fn new() -> Self { - RSchoices { - context_moves: vec![], - } - } - - pub fn new_not_empty() -> Self { - RSchoices { - context_moves: vec![(Rc::new(RSset::new()), - Rc::new(RSprocess::Nill))], - } - } - - pub fn append(&mut self, a: &mut RSchoices) { - self.context_moves.append(&mut a.context_moves); - } - - pub fn replace(&mut self, a: Rc) { - self.context_moves = self - .context_moves - .iter_mut() - .map(|(c1, _)| (Rc::clone(c1), Rc::clone(&a))) - .collect::>(); - } - - pub fn shuffle(&mut self, choices: RSchoices) { - match ( - self.context_moves.is_empty(), - choices.context_moves.is_empty(), - ) { - (true, true) => {} - (true, false) => self.context_moves = choices.context_moves, - (false, true) => {} - (false, false) => { - let mut new_self = vec![]; - for item_self in &self.context_moves { - for item_choices in &choices.context_moves { - new_self.push(( - Rc::new(item_self.0.union(&item_choices.0)), - Rc::new(item_self.1.concat(&item_choices.1)), - )); - } - } - self.context_moves = new_self; - } - } - } - - pub fn iter(&self) -> std::slice::Iter<'_, (Rc, Rc)> { - self.context_moves.iter() - } -} - -impl Default for RSchoices { - fn default() -> Self { - Self::new() - } -} - -impl IntoIterator for RSchoices { - type Item = (Rc, Rc); - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.context_moves.into_iter() - } -} - -impl From<[(Rc, Rc); N]> for RSchoices { - fn from(arr: [(Rc, Rc); N]) -> Self { - RSchoices { - context_moves: arr.to_vec(), - } - } -} - -impl From<&[(Rc, Rc)]> for RSchoices { - fn from(arr: &[(Rc, Rc)]) -> Self { - RSchoices { - context_moves: arr.to_vec(), - } - } -} - -impl From, Rc)>> for RSchoices { - fn from(arr: Vec<(Rc, Rc)>) -> Self { - RSchoices { context_moves: arr } - } -} +pub type RSchoices = super::choices::Choices; // ----------------------------------------------------------------------------- // RSenvironment // ----------------------------------------------------------------------------- -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RSenvironment { - definitions: HashMap, -} -impl RSenvironment { - pub fn new() -> RSenvironment { - RSenvironment { - definitions: HashMap::new(), - } - } - - pub fn get(&self, k: IdType) -> Option<&RSprocess> { - self.definitions.get(&k) - } - - pub fn iter(&self) -> std::collections::hash_map::Iter<'_, u32, RSprocess> { - self.definitions.iter() - } - - pub fn all_elements(&self) -> RSset { - let mut acc = RSset::new(); - for (_, process) in self.definitions.iter() { - acc.push(&process.all_elements()); - } - acc - } -} - -impl Default for RSenvironment { - fn default() -> Self { - RSenvironment::new() - } -} - -impl From<[(IdType, RSprocess); N]> for RSenvironment { - fn from(arr: [(IdType, RSprocess); N]) -> Self { - RSenvironment { - definitions: HashMap::from(arr), - } - } -} - -impl From<&[(IdType, RSprocess)]> for RSenvironment { - fn from(arr: &[(IdType, RSprocess)]) -> Self { - RSenvironment { - definitions: HashMap::from_iter(arr.to_vec()), - } - } -} - -impl From> for RSenvironment { - fn from(arr: Vec<(IdType, RSprocess)>) -> Self { - RSenvironment { - definitions: HashMap::from_iter(arr), - } - } -} +pub type RSenvironment = super::environment::Environment; // ----------------------------------------------------------------------------- // RSsystem // ----------------------------------------------------------------------------- -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct RSsystem { - pub delta: Rc, - pub available_entities: RSset, - pub context_process: RSprocess, - pub reaction_rules: Rc>, -} -impl RSsystem { - pub fn new() -> RSsystem { - RSsystem { - delta: Rc::new(RSenvironment::new()), - available_entities: RSset::new(), - context_process: RSprocess::Nill, - reaction_rules: Rc::new(vec![]), - } - } - - pub fn from( - delta: Rc, - available_entities: RSset, - context_process: RSprocess, - reaction_rules: Rc>, - ) -> RSsystem { - RSsystem { - delta: Rc::clone(&delta), - available_entities, - context_process, - reaction_rules: Rc::clone(&reaction_rules), - } - } -} - -/// Equality does not care about delta or reaction rules, only entities and -/// context is compared -impl PartialEq for RSsystem { - // we ignore delta and reaction rules - fn eq(&self, other: &RSsystem) -> bool { - self.available_entities == other.available_entities && - self.context_process == other.context_process - } -} - -/// Equality does not care about delta or reaction rules, only entities and -/// context is compared -impl Eq for RSsystem {} - -/// Hash does not care about delta or reaction rules, only entities and -/// context is hashed -impl Hash for RSsystem { - // ignores delta and reaction rules - fn hash(&self, state: &mut H) { - self.available_entities.hash(state); - self.context_process.hash(state); - } -} - -impl Default for RSsystem { - fn default() -> Self { - RSsystem::new() - } -} +pub type RSsystem = super::system::System; // ----------------------------------------------------------------------------- // RSlabel // ----------------------------------------------------------------------------- -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialOrd)] -pub struct RSlabel { - pub available_entities: RSset, - pub context: RSset, - pub t: RSset, - pub reactants: RSset, - pub reactants_absent: RSset, - pub inhibitors: RSset, - pub inhibitors_present: RSset, - pub products: RSset, -} -impl RSlabel { - pub fn new() -> Self { - RSlabel { - available_entities: RSset::new(), - context: RSset::new(), - t: RSset::new(), - reactants: RSset::new(), - reactants_absent: RSset::new(), - inhibitors: RSset::new(), - inhibitors_present: RSset::new(), - products: RSset::new(), - } - } - - #[allow(clippy::too_many_arguments)] - pub fn from( - available_entities: RSset, - context: RSset, - t: RSset, - reactants: RSset, - reactants_absent: RSset, - inhibitors: RSset, - inhibitors_present: RSset, - products: RSset, - ) -> Self { - RSlabel { - available_entities, - context, - t, - reactants, - reactants_absent, - inhibitors, - inhibitors_present, - products, - } - } - - #[allow(clippy::too_many_arguments)] - pub fn create( - available_entities: RSset, - context: RSset, - reactants: RSset, - reactants_absent: RSset, - inhibitors: RSset, - inhibitors_present: RSset, - products: RSset, - ) -> Self { - RSlabel { - available_entities: available_entities.clone(), - context: context.clone(), - t: available_entities.union(&context), - reactants, - reactants_absent, - inhibitors, - inhibitors_present, - products, - } - } - - pub fn get_context(&self) -> (&RSset, &RSset, &RSset) { - ( - &self.available_entities, - &self.context, - &self.t, - ) - } -} - -impl Default for RSlabel { - fn default() -> Self { - Self::new() - } -} - -impl PartialEq for RSlabel { - fn eq(&self, other: &Self) -> bool { - self.available_entities == other.available_entities && - self.context == other.context && - // self.t == other.t && // no need since its the union of the above - // // elements - self.reactants == other.reactants && - self.reactants_absent == other.reactants_absent && - self.inhibitors == other.inhibitors && - self.inhibitors_present == other.inhibitors_present && - self.products == other.products - } -} - -impl Hash for RSlabel { - fn hash(&self, state: &mut H) { - self.available_entities.hash(state); - self.context.hash(state); - // self.t.hash(state); - self.reactants.hash(state); - self.reactants_absent.hash(state); - self.inhibitors.hash(state); - self.inhibitors_present.hash(state); - self.products.hash(state); - } -} +pub type RSlabel = super::label::Label; // ----------------------------------------------------------------------------- // RSassert diff --git a/src/rsprocess/support_structures.rs b/src/rsprocess/support_structures.rs deleted file mode 100644 index f70ec99..0000000 --- a/src/rsprocess/support_structures.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! Module for helper structure for simulation - -use super::structure::{RSlabel, RSprocess, RSset, RSsystem}; -use super::transitions::unfold; -use std::rc::Rc; - -#[derive(Clone, Debug)] -pub struct TransitionsIterator<'a> { - choices_iterator: std::vec::IntoIter<(Rc, Rc)>, - system: &'a RSsystem, -} - -impl<'a> TransitionsIterator<'a> { - pub fn from( - system: &'a RSsystem - ) -> Result, String> { - match unfold(&system.delta, &system.context_process, &system.available_entities) { - Ok(o) => Ok(TransitionsIterator { - choices_iterator: o.into_iter(), - system, - }), - Err(e) => Err(e), - } - } -} - -impl<'a> Iterator for TransitionsIterator<'a> { - type Item = (RSlabel, RSsystem); - - /// Creates the next arc from the current system. - fn next(&mut self) -> Option<(RSlabel, RSsystem)> { - let (c, k) = self.choices_iterator.next()?; - let t = self.system.available_entities.union(c.as_ref()); - let ( - reactants, - reactants_absent, - inhibitors, - inhibitors_present, - products - ) = - self.system.reaction_rules.iter().fold( - ( - RSset::new(), // reactants - RSset::new(), // reactants_absent - RSset::new(), // inhibitors - RSset::new(), // inhibitors_present - RSset::new(), // products - ), - |acc, reaction| { - if reaction.enabled(&t) { - ( - acc.0.union(&reaction.reactants), - acc.1, - acc.2.union(&reaction.inhibitors), - acc.3, - acc.4.union(&reaction.products), - ) - } else { - ( - acc.0, - acc.1.union(&reaction.inhibitors.intersection(&t)), - acc.2, - acc.3.union(&reaction.reactants.subtraction(&t)), - acc.4, - ) - } - }, - ); - - let label = RSlabel::from( - self.system.available_entities.clone(), - (*c).clone(), - t, - reactants, - reactants_absent, - inhibitors, - inhibitors_present, - products.clone(), - ); - let new_system = RSsystem::from( - Rc::clone(&self.system.delta), - products, - (*k).clone(), - Rc::clone(&self.system.reaction_rules), - ); - Some((label, new_system)) - } -} diff --git a/src/rsprocess/system.rs b/src/rsprocess/system.rs new file mode 100644 index 0000000..f459b40 --- /dev/null +++ b/src/rsprocess/system.rs @@ -0,0 +1,495 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::hash::Hash; +use std::rc::Rc; + +use super::environment::Environment; +use super::graph::SystemGraph; +use super::label::Label; +use super::process::Process; +use super::reaction::Reaction; +use super::set::Set; + +use super::translator::IdType; +use super::translator::Translator; + +use super::transitions::TransitionsIterator; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct System { + pub delta: Rc, + pub available_entities: Set, + pub context_process: Process, + pub reaction_rules: Rc>, +} + +type Trace = Vec<(Option>, Rc)>; + +impl System { + pub fn new() -> System { + System { + delta: Rc::new(Environment::new()), + available_entities: Set::new(), + context_process: Process::Nill, + reaction_rules: Rc::new(vec![]), + } + } + + pub fn from( + delta: Rc, + available_entities: Set, + context_process: Process, + reaction_rules: Rc>, + ) -> System { + System { + delta: Rc::clone(&delta), + available_entities, + context_process, + reaction_rules: Rc::clone(&reaction_rules), + } + } + + pub fn to_transitions_iterator<'a>( + &'a self + ) -> Result, String> { + TransitionsIterator::from(self) + } + + + /// see oneTransition, transition, smartTransition, smartOneTransition + pub fn one_transition( + &self + ) -> Result, String> { + let mut tr = self.to_transitions_iterator()?; + Ok(tr.next()) + } + + + pub fn nth_transition( + &self, + n: usize, + ) -> Result, String> { + let mut tr = self.to_transitions_iterator()?; + Ok(tr.nth(n)) + } + + + /// see allTransitions, smartAllTransitions + pub fn all_transitions( + &self + ) -> Result, String> { + let tr = self.to_transitions_iterator()?; + Ok(tr.collect::>()) + } + + + /// see oneTarget, smartOneTarget, target, smartTarget + pub fn target( + &self + ) -> Result<(i64, Set), String> { + let current = self.one_transition()?; + if current.is_none() { + return Ok((0, self.available_entities.clone())); + } + let mut n = 1; + let mut current = current.unwrap().1; + while let Some((_, next)) = current.one_transition()? { + current = next; + n += 1; + } + Ok((n, current.available_entities.clone())) + } + + /// see oneRun, run, smartOneRunEK, smartRunEK + pub fn run( + self + ) -> Result>, String> { + let mut res = vec![Rc::new(self)]; + while let Some((_, next_sys)) = res.last().unwrap().one_transition()? { + res.push(Rc::new(next_sys)); + } + Ok(res) + } + + + /// see smartOneRunECT, smartRunECT + pub fn run_separated( + &self + ) -> Result, String> { + let mut res = vec![]; + let current = self.one_transition()?; + if current.is_none() { + return Ok(res); + } + let current = current.unwrap(); + let (available_entities, context, t) = current.0.get_context(); + res.push((available_entities.clone(), context.clone(), t.clone())); + let mut current = current.1; + while let Some((label, next)) = current.one_transition()? { + current = next; + let (available_entities, context, t) = label.get_context(); + res.push((available_entities.clone(), context.clone(), t.clone())); + } + Ok(res) + } + + pub fn traces( + self, + n: usize, + ) -> Result, String> { + if n == 0 { + return Ok(vec![]) + } + let mut n = n; + let mut res : Vec = vec![]; + let mut current_trace: Trace = vec![(None, Rc::new(self))]; + let mut branch = vec![0]; + let mut depth = 0; + let mut new_branch = true; + + loop { + let next_sys = + current_trace[depth].1.nth_transition(branch[depth])?; + + if let Some((current_label, next_sys)) = next_sys { + depth += 1; + if depth >= branch.len() { + branch.push(0); + current_trace.push((Some(Rc::new(current_label)), + Rc::new(next_sys))); + } else { + branch[depth] = 0; + current_trace[depth] = (Some(Rc::new(current_label)), + Rc::new(next_sys)); + } + new_branch = true; + } else { + // at the bottom of a trace, we save to res, then backtrack + // until we find another possible path. + if new_branch { + res.push(current_trace[0..depth].to_vec()); + new_branch = false; + n -= 1; + } + if n == 0 { + break; + } + + if depth == 0 { + break; + } + + depth -= 1; + branch[depth] += 1; + } + } + + Ok(res) + } +} + +/// Equality does not care about delta or reaction rules. Only entities and +/// context is compared +impl PartialEq for System { + // we ignore delta and reaction rules + fn eq(&self, other: &System) -> bool { + self.available_entities == other.available_entities && + self.context_process == other.context_process + } +} + +/// Equality does not care about delta or reaction rules. Only entities and +/// context is compared +impl Eq for System {} + + +/// Hash does not care about delta or reaction rules. Only entities and +/// context is hashed +impl Hash for System { + // ignores delta and reaction rules + fn hash(&self, state: &mut H) { + self.available_entities.hash(state); + self.context_process.hash(state); + } +} + +impl Default for System { + fn default() -> Self { + System::new() + } +} + + +// ----------------------------------------------------------------------------- +// Loops +// ----------------------------------------------------------------------------- + +impl System { + /// A special case of systems is when the context recursively provides + /// always the same set of entities. The corresponding computation is + /// infinite. It consists of a finite sequence of states followed by a + /// looping sequence. IMPORTANT: We return all loops for all X = Q.X, by + /// varing X. The set of reactions Rs and the context x are constant. Each + /// state of the computation is distinguished by the current entities E. + /// Under these assumptions, the predicate lollipop finds the Prefixes and + /// the Loops sequences of entities. + /// see lollipop + pub fn lollipops( + &self + ) -> Vec<(Vec, Vec)> { + self.delta.lollipops_decomposed( + &self.reaction_rules, + &self.available_entities, + ) + } + + + /// Only returns the loop part of the lollipop, returns for all X, where + /// X = Q.X + /// see loop + pub fn lollipops_only_loop( + self + ) -> Vec> { + let filtered = + self.delta.iter().filter_map( + |l| + l.1.filter_delta(l.0) + ); + + let find_loop_fn = |q| { + Reaction::find_only_loop(&self.reaction_rules, + self.available_entities.clone(), + q) + }; + + filtered.map(find_loop_fn).collect::>() + } + + + /// A special case of systems is when the context recursively provides + /// always the same set of entities. The corresponding computation is + /// infinite. It consists of a finite sequence of states followed by a + /// looping sequence. IMPORTANT: We return all loops for all X = Q.X, by + /// varing X. The set of reactions Rs and the context x are constant. Each + /// state of the computation is distinguished by the current entities E. + /// Under these assumptions, the predicate lollipop finds the Prefixes and + /// the Loops sequences of entities. + /// see lollipop + pub fn lollipops_named( + &self, + symb: IdType + ) -> Option<(Vec, Vec)> { + self.delta.lollipops_decomposed_named( + &self.reaction_rules, + &self.available_entities, + symb, + ) + } + + + /// Only returns the loop part of the lollipop, returns for all X, where + /// X = Q.X + /// see loop + pub fn lollipops_only_loop_named( + &self, + symb: IdType + ) -> Option> { + let filtered = self + .delta + .iter() + .filter_map( + |l| + if *l.0 == symb { + l.1.filter_delta(&symb) + } else { + None + } + ) + .next(); + + let find_loop_fn = |q| { + Reaction::find_only_loop(&self.reaction_rules, + self.available_entities.clone(), + q) + }; + + filtered.map(find_loop_fn) + } +} + +// ----------------------------------------------------------------------------- +// Graph +// ----------------------------------------------------------------------------- + +impl System { + /// Creates a graph starting from a system as root node + pub fn digraph( + self + ) -> Result { + use petgraph::Graph; + + let mut graph = Graph::default(); + let node = graph.add_node(self.clone()); + + let mut association = HashMap::new(); + association.insert(self.clone(), node); + + let mut stack = vec![self]; + + while let Some(current) = stack.pop() { + // depth first + let current_node = *association.get(¤t).unwrap(); + + for (label, next) in TransitionsIterator::from(¤t)? { + // if not already visited + let next_node = association.entry(next.clone()).or_insert_with(|| { + stack.push(next.clone()); + graph.add_node(next) + }); + graph.add_edge(current_node, *next_node, label); + } + } + Ok(graph) + } +} + + +// ----------------------------------------------------------------------------- +// Statistics +// ----------------------------------------------------------------------------- + +impl System { + /// Non simulated statistics of a system. + /// Returns statistics about the system as a string. + /// see main_do(stat,MissingE) + pub fn statistics( + &self, + translator: &Translator, + ) -> String { + use super::translator; + + let mut result: String = "Statistics:\n".into(); + result.push_str( + "=============================================================\n" + ); + result.push_str(&format!( + "the initial state has {} entities:\n", + self.available_entities.len() + )); + result.push_str(&format!( + "{}\n", + translator::SetDisplay::from(translator, &self.available_entities) + )); + + let reactants = self + .reaction_rules + .iter() + .fold(Set::new(), |acc, new| acc.union(&new.reactants)); + result.push_str(&format!( + "The reactants are {}:\n{}\n", + reactants.len(), + translator::SetDisplay::from(translator, &reactants) + )); + + let inhibitors = self + .reaction_rules + .iter() + .fold(Set::new(), |acc, new| acc.union(&new.inhibitors)); + result.push_str(&format!( + "The inhibitors are {}:\n{}\n", + inhibitors.len(), + translator::SetDisplay::from(translator, &inhibitors) + )); + + let products = self + .reaction_rules + .iter() + .fold(Set::new(), |acc, new| acc.union(&new.products)); + result.push_str(&format!( + "The products are {}:\n{}\n", + products.len(), + translator::SetDisplay::from(translator, &products) + )); + + let total = reactants.union(&inhibitors.union(&products)); + result.push_str(&format!( + "The reactions involve {} entities:\n{}\n", + total.len(), + translator::SetDisplay::from(translator, &total) + )); + + let entities_env = self.delta.all_elements(); + result.push_str(&format!( + "The environment involves {} entities:\n{}\n", + entities_env.len(), + translator::SetDisplay::from(translator, &entities_env) + )); + + let entities_context = self.context_process.all_elements(); + result.push_str(&format!( + "The context involves {} entities:\n{}\n", + entities_context.len(), + translator::SetDisplay::from(translator, &entities_context) + )); + + let entities_all = total + .union(&entities_env) + .union(&entities_context) + .union(&self.available_entities); + + result.push_str(&format!( + "The whole RS involves {} entities:\n{}\n", + entities_all.len(), + translator::SetDisplay::from(translator, &entities_all) + )); + + let possible_e = products + .union(&self.available_entities) + .union(&entities_context); + let missing_e = reactants.subtraction(&possible_e); + result.push_str(&format!( + "There are {} reactants that will never be available:\n{}\n", + missing_e.len(), + translator::SetDisplay::from(translator, &missing_e) + )); + + let entities_not_needed = entities_context.subtraction(&total); + result.push_str(&format!( + "The context can provide {} entities that will never be used:\ + \n{}\n", + entities_not_needed.len(), + translator::SetDisplay::from(translator, &entities_not_needed) + )); + + result.push_str(&format!( + "There are {} reactions in total.\n", + self.reaction_rules.len() + )); + + let mut admissible_reactions = vec![]; + let mut nonadmissible_reactions = vec![]; + + for reaction in self.reaction_rules.iter() { + if reaction.reactants.is_disjoint(&missing_e) { + admissible_reactions.push(reaction); + } else { + nonadmissible_reactions.push(reaction); + } + } + + result.push_str(&format!( + "- the applicable reactions are {}.\n", + admissible_reactions.len() + )); + + result.push_str(&format!( + "- there are {} reactions that will never be enabled.\n", + nonadmissible_reactions.len() + )); + result.push_str( + "=============================================================" + ); + + result + } +} diff --git a/src/rsprocess/system_test.rs b/src/rsprocess/system_test.rs new file mode 100644 index 0000000..10996b4 --- /dev/null +++ b/src/rsprocess/system_test.rs @@ -0,0 +1,124 @@ +#[test] +fn traces_1() { + use super::system::System; + use super::reaction::Reaction; + use super::process::Process; + use super::environment::Environment; + use super::set::Set; + use std::rc::Rc; + + let system = System { + delta: Rc::new(Environment::from([ + (100, Process::WaitEntity { + repeat: 2, + repeated_process: Rc::new(Process::EntitySet { + entities: Set::from([1]), + next_process: Rc::new(Process::Nill) + }), + next_process: Rc::new(Process::Nill) }), + (102, Process::WaitEntity { + repeat: 3, + repeated_process: Rc::new(Process::EntitySet { + entities: Set::from([2]), + next_process: Rc::new(Process::Nill) }), + next_process: Rc::new(Process::Nill) }), + (103, Process::WaitEntity { + repeat: 4, + repeated_process: Rc::new(Process::EntitySet { + entities: Set::from([3]), + next_process: Rc::new(Process::Nill) }), + next_process: Rc::new(Process::Nill) }), + (101, Process::Summation { children: vec![ + Rc::new(Process::EntitySet { + entities: Set::from([10]), + next_process: Rc::new(Process::RecursiveIdentifier { + identifier: 100 }) }), + Rc::new(Process::EntitySet { + entities: Set::from([11]), + next_process: Rc::new(Process::RecursiveIdentifier { + identifier: 102 }) }), + Rc::new(Process::EntitySet { + entities: Set::from([11]), + next_process: Rc::new(Process::RecursiveIdentifier { + identifier: 103 }) }) + ] }), + ])), + available_entities: Set::from([1, 2]), + context_process: Process::RecursiveIdentifier { identifier: 101 }, + reaction_rules: + Rc::new(vec![Reaction { reactants: Set::from([1]), + inhibitors: Set::from([3]), + products: Set::from([3]), }, + Reaction { reactants: Set::from([3]), + inhibitors: Set::from([1]), + products: Set::from([1]), }, + Reaction { reactants: Set::from([2]), + inhibitors: Set::new(), + products: Set::from([4]), }, + ]) + }; + + // for (pos, trace) in res.iter().enumerate() { + // println!("trace {}:", pos); + // for (_, sy) in trace { + // let ent = format!("{:?}", sy.available_entities); + // let con = format!("{:?}", sy.context_process); + // println!("\t({}, {})", ent, con); + // } + // } + + let res = system.clone().traces(1).unwrap(); + assert_eq!(res.len(), 1); + + let res = system.clone().traces(2).unwrap(); + let mut res = res.iter().map(|x| x.len()).collect::>(); + res.sort(); + assert_eq!(res, [3, 4]); + + let res = system.clone().traces(3).unwrap(); + let mut res = res.iter().map(|x| x.len()).collect::>(); + res.sort(); + assert_eq!(res, [3, 4, 5]); + + let res = system.clone().traces(4).unwrap(); + assert_eq!(res.len(), 3); + + let res = system.clone().traces(0).unwrap(); + assert_eq!(res.len(), 0); +} + +#[test] +fn traces_empty_env() { + use super::system::System; + use super::reaction::Reaction; + use super::process::Process; + use super::environment::Environment; + use super::set::Set; + use std::rc::Rc; + + let system = System { + delta: Rc::new(Environment::from([])), + available_entities: Set::from([1, 2]), + context_process: Process::WaitEntity { + repeat: 10, + repeated_process: Rc::new(Process::EntitySet { + entities: Set::from([1, 2]), + next_process: Rc::new(Process::Nill) }), + next_process: Rc::new(Process::Nill) }, + reaction_rules: + Rc::new(vec![Reaction { reactants: Set::from([1]), + inhibitors: Set::from([3]), + products: Set::from([3]), }, + Reaction { reactants: Set::from([3]), + inhibitors: Set::from([1]), + products: Set::from([1]), }, + Reaction { reactants: Set::from([2]), + inhibitors: Set::new(), + products: Set::from([4]), }, + ]) + }; + + let res = system.clone().traces(10).unwrap(); + assert_eq!(res.len(), 1); + assert_eq!(res[0].len(), 10); +} diff --git a/src/rsprocess/transitions.rs b/src/rsprocess/transitions.rs index b6d49c0..28897c8 100644 --- a/src/rsprocess/transitions.rs +++ b/src/rsprocess/transitions.rs @@ -1,344 +1,88 @@ -//! Definitions for simple simulation steps. +//! Module for helper structure for simulation -use super::structure::{RSchoices, - RSenvironment, - RSlabel, - RSprocess, - RSset, - RSsystem}; -use super::support_structures::TransitionsIterator; +use super::structure::{RSlabel, RSprocess, RSset, RSsystem}; use std::rc::Rc; -/// unfold returns the list of choices for the context given the process -/// definitions environment. RSchoices is a list of context moves mapping a set -/// of entities and the continuation. -/// see unfold -pub fn unfold( - environment: &RSenvironment, - context_process: &RSprocess, - current_entities: &RSset, -) -> Result { - match context_process { - RSprocess::Nill => { - Ok(RSchoices::new()) - }, - RSprocess::RecursiveIdentifier { identifier } => { - let newprocess = environment.get(*identifier); - if let Some(newprocess) = newprocess { - unfold(environment, newprocess, current_entities) - } else { - Err(format!("Missing symbol in context: {identifier}")) - } - } - RSprocess::EntitySet { entities, next_process, } => { - Ok(RSchoices::from([( - Rc::new(entities.clone()), - Rc::clone(next_process), - )])) - }, - RSprocess::Guarded { reaction, next_process } => { - if reaction.enabled(current_entities) { - Ok(RSchoices::from([(Rc::new(reaction.products.clone()), - Rc::clone(next_process))])) - } else { - Ok(RSchoices::new()) - } - } - RSprocess::WaitEntity { repeat, repeated_process: _, next_process, } - if *repeat <= 0 => { - unfold(environment, next_process, current_entities) - }, - RSprocess::WaitEntity { repeat, repeated_process, next_process, } - if *repeat == 1 => { - let mut choices1 = unfold(environment, - repeated_process, - current_entities)?; - choices1.replace(Rc::clone(next_process)); - Ok(choices1) - } - RSprocess::WaitEntity { repeat, repeated_process, next_process, } => { - let mut choices1 = unfold(environment, - repeated_process, - current_entities)?; - choices1.replace(Rc::new(RSprocess::WaitEntity { - repeat: (*repeat - 1), - repeated_process: Rc::clone(repeated_process), - next_process: Rc::clone(next_process), - })); - Ok(choices1) - } - RSprocess::Summation { children } => { - // short-circuits with try_fold. - children.iter().try_fold(RSchoices::new(), |mut acc, x| { - match unfold(environment, x, current_entities) { - Ok(mut choices) => { - acc.append(&mut choices); - Ok(acc) - } - Err(e) => Err(e), - } - }) - } - RSprocess::NondeterministicChoice { children } => { - // short-circuits with try_fold. - if children.is_empty() { - Ok(RSchoices::from(vec![( - Rc::new(RSset::new()), - Rc::new(RSprocess::Nill), - )])) - } else { - children.iter().try_fold(RSchoices::new(), |mut acc, x| { - acc.shuffle(unfold(environment, x, current_entities)?); - Ok(acc) - }) - } - } +#[derive(Clone, Debug)] +pub struct TransitionsIterator<'a> { + choices_iterator: std::vec::IntoIter<(Rc, Rc)>, + system: &'a RSsystem, +} + +impl<'a> TransitionsIterator<'a> { + pub fn from( + system: &'a RSsystem + ) -> Result, String> { + match system.delta.unfold(&system.context_process, + &system.available_entities) { + Ok(o) => Ok(TransitionsIterator { + choices_iterator: o.into_iter(), + system, + }), + Err(e) => Err(e), + } } } -pub fn iterator_transitions<'a>( - system: &'a RSsystem -) -> Result, String> { - TransitionsIterator::from(system) -} +impl<'a> Iterator for TransitionsIterator<'a> { + type Item = (RSlabel, RSsystem); -/// see oneTransition, transition, smartTransition, smartOneTransition -pub fn one_transition( - system: &RSsystem -) -> Result, String> { - let mut tr = TransitionsIterator::from(system)?; - Ok(tr.next()) -} + /// Creates the next arc from the current system. + fn next(&mut self) -> Option<(RSlabel, RSsystem)> { + let (c, k) = self.choices_iterator.next()?; + let t = self.system.available_entities.union(c.as_ref()); + let ( + reactants, + reactants_absent, + inhibitors, + inhibitors_present, + products + ) = + self.system.reaction_rules.iter().fold( + ( + RSset::new(), // reactants + RSset::new(), // reactants_absent + RSset::new(), // inhibitors + RSset::new(), // inhibitors_present + RSset::new(), // products + ), + |acc, reaction| { + if reaction.enabled(&t) { + ( + acc.0.union(&reaction.reactants), + acc.1, + acc.2.union(&reaction.inhibitors), + acc.3, + acc.4.union(&reaction.products), + ) + } else { + ( + acc.0, + acc.1.union(&reaction.inhibitors.intersection(&t)), + acc.2, + acc.3.union(&reaction.reactants.subtraction(&t)), + acc.4, + ) + } + }, + ); -/// see allTransitions, smartAllTransitions -pub fn all_transitions( - system: &RSsystem -) -> Result, String> { - let tr = TransitionsIterator::from(system)?; - Ok(tr.collect::>()) -} - -/// see oneTarget, smartOneTarget, target, smartTarget -pub fn target( - system: &RSsystem -) -> Result<(i64, RSset), String> { - let current = one_transition(system)?; - if current.is_none() { - return Ok((0, system.available_entities.clone())); + let label = RSlabel::from( + self.system.available_entities.clone(), + (*c).clone(), + t, + reactants, + reactants_absent, + inhibitors, + inhibitors_present, + products.clone(), + ); + let new_system = RSsystem::from( + Rc::clone(&self.system.delta), + products, + (*k).clone(), + Rc::clone(&self.system.reaction_rules), + ); + Some((label, new_system)) } - let mut n = 1; - let mut current = current.unwrap().1; - while let Some((_, next)) = one_transition(¤t)? { - current = next; - n += 1; - } - Ok((n, current.available_entities.clone())) -} - -/// see oneRun, run, smartOneRunEK, smartRunEK -pub fn run(system: RSsystem) -> Result>, String> { - let mut res = vec![Rc::new(system)]; - while let Some((_, next_sys)) = one_transition(res.last().unwrap())? { - res.push(Rc::new(next_sys)); - } - Ok(res) -} - -/// see smartOneRunECT, smartRunECT -pub fn run_separated( - system: &RSsystem -) -> Result, String> { - let mut res = vec![]; - let current = one_transition(system)?; - if current.is_none() { - return Ok(res); - } - let current = current.unwrap(); - let (available_entities, context, t) = current.0.get_context(); - res.push((available_entities.clone(), context.clone(), t.clone())); - let mut current = current.1; - while let Some((label, next)) = one_transition(¤t)? { - current = next; - let (available_entities, context, t) = label.get_context(); - res.push((available_entities.clone(), context.clone(), t.clone())); - } - Ok(res) -} - - - -fn nth_transition( - system: &RSsystem, - n: usize, -) -> Result, String> { - let mut tr = TransitionsIterator::from(system)?; - Ok(tr.nth(n)) -} - -type Trace = Vec<(Option>, Rc)>; - -pub fn traces( - system: RSsystem, - n: usize, -) -> Result, String> { - if n == 0 { - return Ok(vec![]) - } - let mut n = n; - let mut res : Vec = vec![]; - let mut current_trace: Trace = vec![(None, Rc::new(system))]; - let mut branch = vec![0]; - let mut depth = 0; - let mut new_branch = true; - - loop { - let next_sys = nth_transition(¤t_trace[depth].1, - branch[depth])?; - - if let Some((current_label, next_sys)) = next_sys { - depth += 1; - if depth >= branch.len() { - branch.push(0); - current_trace.push((Some(Rc::new(current_label)), - Rc::new(next_sys))); - } else { - branch[depth] = 0; - current_trace[depth] = (Some(Rc::new(current_label)), - Rc::new(next_sys)); - } - new_branch = true; - } else { - // at the bottom of a trace, we save to res, then backtrack until - // we find another possible path. - if new_branch { - res.push(current_trace[0..depth].to_vec()); - new_branch = false; - n -= 1; - } - if n == 0 { - break; - } - - if depth == 0 { - break; - } - - depth -= 1; - branch[depth] += 1; - } - } - - Ok(res) -} - -#[test] -fn traces_1() { - use super::structure::RSreaction; - - let system = RSsystem { - delta: Rc::new(RSenvironment::from([ - (100, RSprocess::WaitEntity { - repeat: 2, - repeated_process: Rc::new(RSprocess::EntitySet { - entities: RSset::from([1]), - next_process: Rc::new(RSprocess::Nill) - }), - next_process: Rc::new(RSprocess::Nill) }), - (102, RSprocess::WaitEntity { - repeat: 3, - repeated_process: Rc::new(RSprocess::EntitySet { - entities: RSset::from([2]), - next_process: Rc::new(RSprocess::Nill) }), - next_process: Rc::new(RSprocess::Nill) }), - (103, RSprocess::WaitEntity { - repeat: 4, - repeated_process: Rc::new(RSprocess::EntitySet { - entities: RSset::from([3]), - next_process: Rc::new(RSprocess::Nill) }), - next_process: Rc::new(RSprocess::Nill) }), - (101, RSprocess::Summation { children: vec![ - Rc::new(RSprocess::EntitySet { - entities: RSset::from([10]), - next_process: Rc::new(RSprocess::RecursiveIdentifier { - identifier: 100 }) }), - Rc::new(RSprocess::EntitySet { - entities: RSset::from([11]), - next_process: Rc::new(RSprocess::RecursiveIdentifier { - identifier: 102 }) }), - Rc::new(RSprocess::EntitySet { - entities: RSset::from([11]), - next_process: Rc::new(RSprocess::RecursiveIdentifier { - identifier: 103 }) }) - ] }), - ])), - available_entities: RSset::from([1, 2]), - context_process: RSprocess::RecursiveIdentifier { identifier: 101 }, - reaction_rules: - Rc::new(vec![RSreaction { reactants: RSset::from([1]), - inhibitors: RSset::from([3]), - products: RSset::from([3]), }, - RSreaction { reactants: RSset::from([3]), - inhibitors: RSset::from([1]), - products: RSset::from([1]), }, - RSreaction { reactants: RSset::from([2]), - inhibitors: RSset::new(), - products: RSset::from([4]), }, - ]) - }; - - // for (pos, trace) in res.iter().enumerate() { - // println!("trace {}:", pos); - // for (_, sy) in trace { - // let ent = format!("{:?}", sy.available_entities); - // let con = format!("{:?}", sy.context_process); - // println!("\t({}, {})", ent, con); - // } - // } - - let res = traces(system.clone(), 1).unwrap(); - assert_eq!(res.len(), 1); - - let res = traces(system.clone(), 2).unwrap(); - assert_eq!(res.len(), 2); - assert_eq!(res[0].len() + 1, res[1].len()); - - let res = traces(system.clone(), 3).unwrap(); - assert_eq!(res.len(), 3); - - let res = traces(system.clone(), 4).unwrap(); - assert_eq!(res.len(), 3); - - let res = traces(system.clone(), 0).unwrap(); - assert_eq!(res.len(), 0); -} - -#[test] -fn traces_empty_env() { - use super::structure::RSreaction; - - let system = RSsystem { - delta: Rc::new(RSenvironment::from([])), - available_entities: RSset::from([1, 2]), - context_process: RSprocess::WaitEntity { - repeat: 10, - repeated_process: Rc::new(RSprocess::EntitySet { - entities: RSset::from([1, 2]), - next_process: Rc::new(RSprocess::Nill) }), - next_process: Rc::new(RSprocess::Nill) }, - reaction_rules: - Rc::new(vec![RSreaction { reactants: RSset::from([1]), - inhibitors: RSset::from([3]), - products: RSset::from([3]), }, - RSreaction { reactants: RSset::from([3]), - inhibitors: RSset::from([1]), - products: RSset::from([1]), }, - RSreaction { reactants: RSset::from([2]), - inhibitors: RSset::new(), - products: RSset::from([4]), }, - ]) - }; - - let res = traces(system.clone(), 10).unwrap(); - assert_eq!(res.len(), 1); - assert_eq!(res[0].len(), 10); } diff --git a/src/rsprocess/translator.rs b/src/rsprocess/translator.rs index 4a8d386..9ff6086 100644 --- a/src/rsprocess/translator.rs +++ b/src/rsprocess/translator.rs @@ -131,6 +131,7 @@ fn print_set( write!(f, "}}") } +translator_structure!(SetDisplay, RSset, set, print_set); translator_structure!(RSsetDisplay, RSset, set, print_set); @@ -162,7 +163,7 @@ fn print_process( translator: &Translator, process: &RSprocess, ) -> fmt::Result { - use super::structure::RSprocess::*; + use super::process::Process::*; match process { Nill => { write!(f, "Nill")