2025-07-11 19:36:20 +02:00
|
|
|
//! Module that holds useful presets for interacting with other modules.
|
|
|
|
|
|
|
|
|
|
use std::env;
|
2025-07-11 23:49:59 +02:00
|
|
|
use std::fmt::Display;
|
2025-07-11 19:36:20 +02:00
|
|
|
use std::fs;
|
|
|
|
|
use std::io;
|
|
|
|
|
use std::io::prelude::*;
|
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
|
|
|
|
|
use lalrpop_util::ParseError;
|
2025-08-15 00:32:58 +02:00
|
|
|
use petgraph::Graph;
|
2025-07-11 19:36:20 +02:00
|
|
|
|
|
|
|
|
// grammar is defined in lib.rs, calling lalrpop_mod! twice, generates twice
|
|
|
|
|
// the code
|
|
|
|
|
use crate::grammar;
|
2025-08-15 00:32:58 +02:00
|
|
|
use crate::rsprocess::graph::MapEdges;
|
2025-07-11 19:36:20 +02:00
|
|
|
|
|
|
|
|
use super::structure::{RSset, RSsystem};
|
|
|
|
|
use super::translator::Translator;
|
|
|
|
|
use super::*;
|
|
|
|
|
|
2025-07-11 23:49:59 +02:00
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Structures
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
2025-07-16 18:03:31 +02:00
|
|
|
/// Describes how the result of some computation has to be saved.
|
2025-07-11 23:49:59 +02:00
|
|
|
pub struct SaveOptions {
|
|
|
|
|
pub print: bool,
|
2025-07-12 15:41:10 +02:00
|
|
|
pub save: Option<Vec<String>>,
|
2025-07-11 23:49:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SaveOptions {
|
|
|
|
|
pub fn combine(&mut self, other: &mut Self) {
|
2025-07-12 15:41:10 +02:00
|
|
|
self.print = self.print || other.print;
|
|
|
|
|
match (self.save.is_some(), other.save.is_some()) {
|
|
|
|
|
(false, false) | (true, false) => {}
|
|
|
|
|
(false, true) => {
|
|
|
|
|
self.save = other.save.to_owned();
|
|
|
|
|
}
|
|
|
|
|
(true, true) => {
|
|
|
|
|
self.save
|
|
|
|
|
.as_mut()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.append(other.save.as_mut().unwrap());
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-11 23:49:59 +02:00
|
|
|
}
|
|
|
|
|
pub fn new() -> Self {
|
2025-07-12 15:41:10 +02:00
|
|
|
SaveOptions {
|
|
|
|
|
print: false,
|
|
|
|
|
save: None,
|
|
|
|
|
}
|
2025-07-11 23:49:59 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for SaveOptions {
|
|
|
|
|
fn default() -> Self {
|
2025-07-12 15:41:10 +02:00
|
|
|
SaveOptions::new()
|
2025-07-11 23:49:59 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-16 18:03:31 +02:00
|
|
|
// Describes output options for a graph.
|
2025-07-11 23:49:59 +02:00
|
|
|
pub enum GraphSaveOptions {
|
2025-07-12 15:41:10 +02:00
|
|
|
Dot {
|
2025-07-26 22:46:10 +02:00
|
|
|
node_display: graph::NodeDisplay,
|
2025-07-27 14:59:35 +02:00
|
|
|
edge_display: graph::EdgeDisplay,
|
2025-07-13 17:28:13 +02:00
|
|
|
node_color: graph::NodeColor,
|
2025-07-13 19:08:39 +02:00
|
|
|
edge_color: graph::EdgeColor,
|
2025-07-12 15:41:10 +02:00
|
|
|
so: SaveOptions,
|
|
|
|
|
},
|
|
|
|
|
GraphML {
|
2025-07-26 22:46:10 +02:00
|
|
|
node_display: graph::NodeDisplay,
|
2025-07-27 14:59:35 +02:00
|
|
|
edge_display: graph::EdgeDisplay,
|
2025-07-12 15:41:10 +02:00
|
|
|
so: SaveOptions,
|
|
|
|
|
},
|
|
|
|
|
Serialize {
|
|
|
|
|
path: String,
|
|
|
|
|
},
|
2025-07-11 23:49:59 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-16 18:03:31 +02:00
|
|
|
/// Describes the computation to apply to the input system or graph.
|
2025-07-11 23:49:59 +02:00
|
|
|
pub enum Instruction {
|
|
|
|
|
Stats { so: SaveOptions },
|
|
|
|
|
Target { so: SaveOptions },
|
|
|
|
|
Run { so: SaveOptions },
|
|
|
|
|
Loop { symbol: String, so: SaveOptions },
|
2025-07-12 02:42:28 +02:00
|
|
|
Frequency { so: SaveOptions },
|
2025-07-11 23:49:59 +02:00
|
|
|
LimitFrequency { experiment: String, so: SaveOptions },
|
|
|
|
|
FastFrequency { experiment: String, so: SaveOptions },
|
|
|
|
|
Digraph { gso: Vec<GraphSaveOptions> },
|
2025-08-15 00:32:58 +02:00
|
|
|
Bisimilarity { system_b: String,
|
2025-08-17 01:01:56 +02:00
|
|
|
edge_relabeler: Box<super::structure::RSassert>,
|
2025-08-15 00:32:58 +02:00
|
|
|
so: SaveOptions }
|
2025-07-11 23:49:59 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-16 18:03:31 +02:00
|
|
|
/// Describes a system or a graph.
|
2025-07-11 23:49:59 +02:00
|
|
|
pub enum System {
|
|
|
|
|
Deserialize { path: String },
|
2025-07-12 15:41:10 +02:00
|
|
|
RSsystem { sys: RSsystem },
|
2025-07-11 23:49:59 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-12 02:42:28 +02:00
|
|
|
impl System {
|
2025-07-16 18:03:31 +02:00
|
|
|
/// Deserialize the graph if applicable.
|
2025-07-12 02:42:28 +02:00
|
|
|
pub fn compute(
|
2025-07-12 15:32:21 +02:00
|
|
|
&self,
|
|
|
|
|
translator: Translator
|
2025-07-12 02:42:28 +02:00
|
|
|
) -> Result<EvaluatedSystem, String>
|
|
|
|
|
{
|
2025-07-12 15:41:10 +02:00
|
|
|
match self {
|
|
|
|
|
Self::RSsystem { sys } => Ok(EvaluatedSystem::System {
|
|
|
|
|
sys: sys.to_owned(),
|
|
|
|
|
translator,
|
|
|
|
|
}),
|
|
|
|
|
Self::Deserialize { path } => {
|
|
|
|
|
let (graph, translator) = deserialize(path.into())?;
|
|
|
|
|
Ok(EvaluatedSystem::Graph { graph, translator })
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-16 00:05:14 +02:00
|
|
|
pub enum EvaluatedSystem {
|
|
|
|
|
Graph {
|
|
|
|
|
graph: graph::RSgraph,
|
|
|
|
|
translator: Translator,
|
|
|
|
|
},
|
|
|
|
|
System {
|
|
|
|
|
sys: RSsystem,
|
|
|
|
|
translator: Translator,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl EvaluatedSystem {
|
|
|
|
|
pub fn get_translator(&mut self) -> &mut Translator {
|
|
|
|
|
match self {
|
|
|
|
|
EvaluatedSystem::Graph { graph: _, translator } => {
|
|
|
|
|
translator
|
|
|
|
|
},
|
|
|
|
|
EvaluatedSystem::System { sys: _, translator } => {
|
|
|
|
|
translator
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-16 18:03:31 +02:00
|
|
|
/// Holds all the computations to be done on the system
|
2025-07-11 23:49:59 +02:00
|
|
|
pub struct Instructions {
|
|
|
|
|
pub system: System,
|
2025-07-12 15:41:10 +02:00
|
|
|
pub instructions: Vec<Instruction>,
|
2025-07-11 23:49:59 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 19:36:20 +02:00
|
|
|
// -----------------------------------------------------------------------------
|
2025-07-26 19:43:20 +02:00
|
|
|
// IO Helper Functions
|
2025-07-11 19:36:20 +02:00
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
fn read_file<T, F>(
|
|
|
|
|
translator: &mut Translator,
|
|
|
|
|
path_string: String,
|
|
|
|
|
parser: F
|
|
|
|
|
) -> Result<T, String>
|
|
|
|
|
where
|
2025-07-12 15:41:10 +02:00
|
|
|
F: Fn(&mut Translator, String) -> Result<T, String>,
|
2025-07-11 19:36:20 +02:00
|
|
|
{
|
|
|
|
|
// relative path
|
|
|
|
|
let mut path = match env::current_dir() {
|
2025-07-12 15:41:10 +02:00
|
|
|
Ok(p) => p,
|
|
|
|
|
Err(_) => return Err("Error getting current directory.".into()),
|
2025-07-11 19:36:20 +02:00
|
|
|
};
|
|
|
|
|
path = path.join(path_string);
|
|
|
|
|
|
|
|
|
|
// we read the file with a buffer
|
|
|
|
|
let f = match fs::File::open(path) {
|
2025-07-12 15:41:10 +02:00
|
|
|
Ok(f) => f,
|
|
|
|
|
Err(_) => return Err("Error opening file.".into()),
|
2025-07-11 19:36:20 +02:00
|
|
|
};
|
|
|
|
|
let mut buf_reader = io::BufReader::new(f);
|
|
|
|
|
let mut contents = String::new();
|
|
|
|
|
match buf_reader.read_to_string(&mut contents) {
|
2025-07-12 15:41:10 +02:00
|
|
|
Ok(_) => {}
|
|
|
|
|
Err(_) => return Err("Error reading file.".into()),
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parse
|
|
|
|
|
let result = parser(translator, contents)?;
|
|
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 23:49:59 +02:00
|
|
|
fn reformat_error<T, S>(
|
|
|
|
|
e: ParseError<usize, T, &'static str>
|
|
|
|
|
) -> Result<S, String>
|
|
|
|
|
where
|
2025-07-12 15:41:10 +02:00
|
|
|
T: Display,
|
2025-07-11 19:36:20 +02:00
|
|
|
{
|
2025-07-11 23:49:59 +02:00
|
|
|
match e {
|
2025-07-12 15:41:10 +02:00
|
|
|
ParseError::ExtraToken { token: (l, t, r) } => Err(format!(
|
|
|
|
|
"Unexpected token \"{t}\" \
|
2025-07-11 23:49:59 +02:00
|
|
|
between positions {l} and {r}."
|
2025-07-12 15:41:10 +02:00
|
|
|
)),
|
|
|
|
|
ParseError::UnrecognizedEof {
|
|
|
|
|
location: _,
|
|
|
|
|
expected: _,
|
|
|
|
|
} => Err("End of file encountered while parsing.".into()),
|
|
|
|
|
ParseError::InvalidToken { location } => {
|
|
|
|
|
Err(format!("Invalid token at position {location}."))
|
|
|
|
|
}
|
|
|
|
|
ParseError::UnrecognizedToken {
|
|
|
|
|
token: (l, t, r),
|
|
|
|
|
expected,
|
2025-07-13 17:28:13 +02:00
|
|
|
} => {
|
|
|
|
|
let mut err = format!(
|
|
|
|
|
"Unrecognized token \"{t}\" \
|
2025-07-16 18:03:31 +02:00
|
|
|
between positions {l} and {r}."
|
2025-07-13 17:28:13 +02:00
|
|
|
);
|
2025-07-16 18:03:31 +02:00
|
|
|
|
|
|
|
|
// Temporary debug.
|
|
|
|
|
err.push_str("Expected: ");
|
2025-07-13 17:28:13 +02:00
|
|
|
let mut it = expected.iter().peekable();
|
|
|
|
|
while let Some(s) = it.next() {
|
|
|
|
|
err.push('(');
|
2025-07-13 19:08:39 +02:00
|
|
|
err.push_str(s);
|
2025-07-13 17:28:13 +02:00
|
|
|
err.push(')');
|
|
|
|
|
if it.peek().is_some() {
|
|
|
|
|
err.push(',');
|
|
|
|
|
err.push(' ');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
Err(err)
|
|
|
|
|
},
|
2025-07-12 15:41:10 +02:00
|
|
|
ParseError::User { error } => Err(error.to_string()),
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parser_experiment(
|
|
|
|
|
translator: &mut Translator,
|
2025-07-12 15:41:10 +02:00
|
|
|
contents: String,
|
|
|
|
|
) -> Result<(Vec<u32>, Vec<RSset>), String> {
|
|
|
|
|
match grammar::ExperimentParser::new().parse(translator, &contents) {
|
|
|
|
|
Ok(sys) => Ok(sys),
|
|
|
|
|
Err(e) => reformat_error(e),
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 23:49:59 +02:00
|
|
|
fn parser_instructions(
|
|
|
|
|
translator: &mut Translator,
|
2025-07-12 15:41:10 +02:00
|
|
|
contents: String,
|
|
|
|
|
) -> Result<Instructions, String> {
|
|
|
|
|
match grammar::RunParser::new().parse(translator, &contents) {
|
|
|
|
|
Ok(sys) => Ok(sys),
|
|
|
|
|
Err(e) => reformat_error(e),
|
2025-07-11 23:49:59 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 15:41:10 +02:00
|
|
|
fn save_file(contents: &String, path_string: String) -> Result<(), String> {
|
2025-07-11 23:49:59 +02:00
|
|
|
// relative path
|
|
|
|
|
let mut path = match env::current_dir() {
|
2025-07-12 15:41:10 +02:00
|
|
|
Ok(p) => p,
|
|
|
|
|
Err(_) => return Err("Error getting current directory.".into()),
|
2025-07-11 23:49:59 +02:00
|
|
|
};
|
|
|
|
|
path = path.join(path_string);
|
|
|
|
|
|
|
|
|
|
let mut f = match fs::File::create(&path) {
|
2025-07-12 15:41:10 +02:00
|
|
|
Ok(f) => f,
|
|
|
|
|
Err(_) =>
|
|
|
|
|
return Err(
|
|
|
|
|
format!("Error creating file {}.", path.to_str().unwrap())
|
|
|
|
|
),
|
2025-07-11 23:49:59 +02:00
|
|
|
};
|
|
|
|
|
match write!(f, "{contents}") {
|
2025-07-12 15:41:10 +02:00
|
|
|
Ok(_) => {}
|
|
|
|
|
Err(_) => return Err("Error writing to file.".into()),
|
2025-07-11 23:49:59 +02:00
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 19:36:20 +02:00
|
|
|
// -----------------------------------------------------------------------------
|
2025-07-11 23:49:59 +02:00
|
|
|
// main_do
|
2025-07-11 19:36:20 +02:00
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
/// Prints statistics of the system.
|
|
|
|
|
/// Equivalent main_do(stat) or main_do(stat, MissingE)
|
2025-07-12 02:42:28 +02:00
|
|
|
pub fn stats(system: &EvaluatedSystem) -> Result<String, String> {
|
|
|
|
|
match system {
|
2025-07-12 15:41:10 +02:00
|
|
|
EvaluatedSystem::System { sys, translator } =>
|
|
|
|
|
Ok(statistics::of_RSsystem(translator, sys)),
|
|
|
|
|
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))
|
|
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
}
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Prints a final set of entities in a terminating Reaction System.
|
2025-07-12 02:42:28 +02:00
|
|
|
/// The system needs to terminate to return.
|
2025-07-11 19:36:20 +02:00
|
|
|
/// Equivalent to main_do(target, E)
|
2025-07-12 02:42:28 +02:00
|
|
|
pub fn target(system: &EvaluatedSystem) -> Result<String, String> {
|
|
|
|
|
let (res, translator) = match system {
|
2025-07-12 15:41:10 +02:00
|
|
|
EvaluatedSystem::System { sys, translator } =>
|
|
|
|
|
(transitions::target(sys)?, 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)
|
|
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
};
|
|
|
|
|
Ok(format!(
|
2025-07-12 15:41:10 +02:00
|
|
|
"After {} steps we arrive at state:\n{}",
|
|
|
|
|
res.0,
|
|
|
|
|
translator::RSsetDisplay::from(translator, &res.1)
|
2025-07-12 02:42:28 +02:00
|
|
|
))
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Finds the list of traversed states in a (deterministic) terminating
|
|
|
|
|
/// reaction.
|
2025-07-12 02:42:28 +02:00
|
|
|
/// The system needs to terminate to return.
|
2025-07-11 19:36:20 +02:00
|
|
|
/// equivalent to main_do(run,Es)
|
2025-07-12 02:42:28 +02:00
|
|
|
pub fn traversed(system: &EvaluatedSystem) -> Result<String, String> {
|
|
|
|
|
let (res, translator) = match system {
|
2025-07-12 15:41:10 +02:00
|
|
|
EvaluatedSystem::System { sys, translator } => {
|
|
|
|
|
(transitions::run_separated(sys)?, 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)
|
|
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
};
|
2025-07-11 19:36:20 +02:00
|
|
|
|
2025-07-12 02:42:28 +02:00
|
|
|
let mut output = String::new();
|
2025-07-11 19:36:20 +02:00
|
|
|
|
2025-07-12 02:42:28 +02:00
|
|
|
output.push_str("The trace is composed by the set of entities:");
|
2025-07-11 19:36:20 +02:00
|
|
|
for (e, _c, _t) in res {
|
2025-07-12 15:41:10 +02:00
|
|
|
output.push_str(&format!(
|
|
|
|
|
"{}",
|
|
|
|
|
translator::RSsetDisplay::from(translator, &e)
|
|
|
|
|
));
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
Ok(output)
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Finds the looping list of states in a reaction system with a perpetual
|
2025-07-11 23:49:59 +02:00
|
|
|
/// context. IMPORTANT: for loops, we assume Delta defines the process constant
|
2025-07-11 19:36:20 +02:00
|
|
|
/// x = Q.x and the context process is x .
|
|
|
|
|
/// equivalent to main_do(loop,Es)
|
2025-07-12 02:42:28 +02:00
|
|
|
pub fn hoop(
|
2025-07-12 15:41:10 +02:00
|
|
|
system: &EvaluatedSystem,
|
|
|
|
|
symbol: String
|
|
|
|
|
) -> Result<String, String> {
|
2025-07-12 02:42:28 +02:00
|
|
|
let (res, translator) = match system {
|
2025-07-12 15:41:10 +02:00
|
|
|
EvaluatedSystem::System { sys, translator } => (sys, translator),
|
|
|
|
|
EvaluatedSystem::Graph { graph, translator } => {
|
|
|
|
|
let Some(sys) = graph.node_weights().next() else {
|
|
|
|
|
return Err("No node found in graph".into());
|
|
|
|
|
};
|
|
|
|
|
(sys, translator)
|
|
|
|
|
}
|
2025-07-11 19:36:20 +02:00
|
|
|
};
|
2025-07-12 02:42:28 +02:00
|
|
|
// we retrieve the id for "x" and use it to find the corresponding loop
|
2025-07-12 15:41:10 +02:00
|
|
|
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) {
|
|
|
|
|
Some(o) => o,
|
|
|
|
|
None => {
|
|
|
|
|
return Err("No loop found.".into());
|
|
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut output = String::new();
|
2025-07-11 19:36:20 +02:00
|
|
|
|
2025-07-12 02:42:28 +02:00
|
|
|
output.push_str("The loop is composed by the sets:");
|
2025-07-11 19:36:20 +02:00
|
|
|
for e in res {
|
2025-07-12 15:41:10 +02:00
|
|
|
output.push_str(&format!(
|
|
|
|
|
"{}",
|
|
|
|
|
translator::RSsetDisplay::from(translator, &e)
|
|
|
|
|
));
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-12 02:42:28 +02:00
|
|
|
Ok(output)
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Finds the frequency of each entity in the traversed states for a
|
|
|
|
|
/// (deterministic) terminating Reaction System.
|
|
|
|
|
/// equivalent to main_do(freq, PairList)
|
2025-07-12 15:41:10 +02:00
|
|
|
pub fn freq(system: &EvaluatedSystem) -> Result<String, String> {
|
2025-07-12 02:42:28 +02:00
|
|
|
let (sys, translator) = match system {
|
2025-07-12 15:41:10 +02:00
|
|
|
EvaluatedSystem::System { sys, translator } => (sys, translator),
|
|
|
|
|
EvaluatedSystem::Graph { graph, translator } => {
|
|
|
|
|
let Some(sys) = graph.node_weights().next() else {
|
|
|
|
|
return Err("No node found in graph".into());
|
|
|
|
|
};
|
|
|
|
|
(sys, translator)
|
|
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
};
|
2025-07-11 19:36:20 +02:00
|
|
|
|
2025-07-12 02:42:28 +02:00
|
|
|
let res = frequency::naive_frequency(sys)?;
|
2025-07-11 19:36:20 +02:00
|
|
|
|
2025-07-12 02:42:28 +02:00
|
|
|
Ok(format!(
|
2025-07-12 15:41:10 +02:00
|
|
|
"Frequency of encountered symbols:\n{}",
|
|
|
|
|
translator::FrequencyDisplay::from(translator, &res)
|
2025-07-12 02:42:28 +02:00
|
|
|
))
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Finds the frequency of each entity in the limit loop of a nonterminating
|
|
|
|
|
/// Reaction System whose context has the form Q1 ... Q1.Q2 ... Q2 ... Qn ...
|
|
|
|
|
/// equivalent to main_do(limitfreq, PairList)
|
|
|
|
|
pub fn limit_freq(
|
2025-07-12 02:42:28 +02:00
|
|
|
system: &mut EvaluatedSystem,
|
|
|
|
|
experiment: String
|
2025-07-12 15:41:10 +02:00
|
|
|
) -> Result<String, String> {
|
2025-07-12 02:42:28 +02:00
|
|
|
let (sys, translator): (&RSsystem, &mut Translator) = match system {
|
2025-07-12 15:41:10 +02:00
|
|
|
EvaluatedSystem::System { sys, translator } => (sys, translator),
|
|
|
|
|
EvaluatedSystem::Graph { graph, translator } => {
|
|
|
|
|
let Some(sys) = graph.node_weights().next() else {
|
|
|
|
|
return Err("No node found in graph".into());
|
|
|
|
|
};
|
|
|
|
|
(sys, translator)
|
|
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
};
|
2025-07-11 19:36:20 +02:00
|
|
|
|
2025-07-12 15:41:10 +02:00
|
|
|
let (_, sets) = read_file(translator, experiment, parser_experiment)?;
|
2025-07-11 19:36:20 +02:00
|
|
|
|
2025-07-12 15:41:10 +02:00
|
|
|
let res =
|
|
|
|
|
match frequency::limit_frequency(
|
|
|
|
|
&sets,
|
|
|
|
|
&sys.reaction_rules,
|
|
|
|
|
&sys.available_entities)
|
|
|
|
|
{
|
|
|
|
|
Some(e) => e,
|
|
|
|
|
None => {
|
|
|
|
|
return Err("Error calculating frequency.".into());
|
|
|
|
|
}
|
2025-07-11 19:36:20 +02:00
|
|
|
};
|
|
|
|
|
|
2025-07-12 02:42:28 +02:00
|
|
|
Ok(format!(
|
2025-07-12 15:41:10 +02:00
|
|
|
"Frequency of encountered symbols:\n{}",
|
|
|
|
|
translator::FrequencyDisplay::from(translator, &res)
|
2025-07-12 02:42:28 +02:00
|
|
|
))
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Finds the frequency of each entity in the traversed loops of a terminating
|
|
|
|
|
/// reaction system whose context has the form
|
|
|
|
|
/// Q1 ... Q1.Q2 ... Q2 ... Qn ... Qn.nil and each Qi is repeated Wi times
|
|
|
|
|
/// read from a corresponding file.
|
|
|
|
|
/// equivalent to main_do(fastfreq, PairList)
|
|
|
|
|
pub fn fast_freq(
|
2025-07-12 02:42:28 +02:00
|
|
|
system: &mut EvaluatedSystem,
|
|
|
|
|
experiment: String
|
|
|
|
|
) -> Result<String, String>
|
2025-07-11 19:36:20 +02:00
|
|
|
{
|
2025-07-12 02:42:28 +02:00
|
|
|
let (sys, translator): (&RSsystem, &mut Translator) = match system {
|
2025-07-12 15:41:10 +02:00
|
|
|
EvaluatedSystem::System { sys, translator } => (sys, translator),
|
|
|
|
|
EvaluatedSystem::Graph { graph, translator } => {
|
|
|
|
|
let Some(sys) = graph.node_weights().next() else {
|
|
|
|
|
return Err("No node found in graph".into());
|
|
|
|
|
};
|
|
|
|
|
(sys, translator)
|
|
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
};
|
2025-07-11 19:36:20 +02:00
|
|
|
|
2025-07-12 15:41:10 +02:00
|
|
|
let (weights, sets) = read_file(translator, experiment, parser_experiment)?;
|
|
|
|
|
|
|
|
|
|
let res = match frequency::fast_frequency(
|
|
|
|
|
&sets,
|
|
|
|
|
&sys.reaction_rules,
|
|
|
|
|
&sys.available_entities,
|
|
|
|
|
&weights,
|
|
|
|
|
) {
|
|
|
|
|
Some(e) => e,
|
|
|
|
|
None => {
|
|
|
|
|
return Err("Error calculating frequency.".into());
|
|
|
|
|
}
|
2025-07-11 19:36:20 +02:00
|
|
|
};
|
|
|
|
|
|
2025-07-12 02:42:28 +02:00
|
|
|
Ok(format!(
|
2025-07-12 15:41:10 +02:00
|
|
|
"Frequency of encountered symbols:\n{}",
|
|
|
|
|
translator::FrequencyDisplay::from(translator, &res)
|
2025-07-12 02:42:28 +02:00
|
|
|
))
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Computes the LTS.
|
|
|
|
|
/// equivalent to main_do(digraph, Arcs) or to main_do(advdigraph, Arcs)
|
2025-07-12 15:41:10 +02:00
|
|
|
pub fn digraph(system: &mut EvaluatedSystem) -> Result<(), String> {
|
2025-07-16 00:05:14 +02:00
|
|
|
if let EvaluatedSystem::System { sys, translator } = system {
|
|
|
|
|
*system =
|
|
|
|
|
EvaluatedSystem::Graph {
|
|
|
|
|
graph: graph::digraph(sys.clone())?,
|
|
|
|
|
translator: translator.to_owned(),
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
Ok(())
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-16 18:03:31 +02:00
|
|
|
/// Computes bisimularity of two provided systems
|
2025-07-16 00:05:14 +02:00
|
|
|
pub fn bisimilar(
|
|
|
|
|
system_a: &mut EvaluatedSystem,
|
2025-08-17 01:01:56 +02:00
|
|
|
edge_relabeler: &super::structure::RSassert,
|
2025-07-16 00:05:14 +02:00
|
|
|
system_b: String
|
|
|
|
|
) -> Result<String, String>
|
|
|
|
|
{
|
2025-08-17 01:25:35 +02:00
|
|
|
use super::structure::assert::AssertReturnValue;
|
2025-08-15 00:32:58 +02:00
|
|
|
|
2025-07-16 00:05:14 +02:00
|
|
|
let system_b = read_file(system_a.get_translator(),
|
|
|
|
|
system_b.to_string(),
|
|
|
|
|
parser_instructions)?;
|
2025-07-16 18:03:31 +02:00
|
|
|
let mut system_b =
|
|
|
|
|
match system_b.system.compute(system_a.get_translator().clone())? {
|
|
|
|
|
EvaluatedSystem::System { sys, translator } =>
|
|
|
|
|
EvaluatedSystem::System { sys, translator },
|
|
|
|
|
EvaluatedSystem::Graph { graph, translator } => {
|
|
|
|
|
if translator != *system_a.get_translator() {
|
|
|
|
|
return Err("Bisimilarity not implemented for systems with \
|
2025-07-16 18:34:33 +02:00
|
|
|
different encodings. Serialize the systems \
|
|
|
|
|
with the same translator.".into());
|
2025-07-16 18:03:31 +02:00
|
|
|
}
|
|
|
|
|
EvaluatedSystem::Graph { graph, translator }
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-07-16 00:05:14 +02:00
|
|
|
|
2025-07-16 18:34:33 +02:00
|
|
|
digraph(system_a)?;
|
2025-07-16 00:05:14 +02:00
|
|
|
digraph(&mut system_b)?;
|
|
|
|
|
|
2025-07-16 18:34:33 +02:00
|
|
|
// since we ran digraph on both they have to be graphs
|
2025-08-15 00:32:58 +02:00
|
|
|
match (system_a, &mut system_b) {
|
2025-07-16 00:05:14 +02:00
|
|
|
(EvaluatedSystem::Graph { graph: a, translator: _ },
|
2025-08-15 00:32:58 +02:00
|
|
|
EvaluatedSystem::Graph { graph: b, translator: translator_b }) => {
|
|
|
|
|
let a: Graph<RSsystem, AssertReturnValue> =
|
|
|
|
|
a.map_edges(edge_relabeler, translator_b)?;
|
|
|
|
|
let b: Graph<RSsystem, AssertReturnValue> =
|
|
|
|
|
b.map_edges(edge_relabeler, translator_b)?;
|
2025-07-16 18:34:33 +02:00
|
|
|
Ok(format!(
|
|
|
|
|
"{}",
|
2025-08-15 00:32:58 +02:00
|
|
|
// super::bisimilarity::bisimilarity_kanellakis_smolka(&&a, &&b)
|
|
|
|
|
// super::bisimilarity::bisimilarity_paige_tarjan_ignore_labels(&&a, &&b)
|
|
|
|
|
super::bisimilarity::bisimilarity_paige_tarjan(&&a, &&b)
|
2025-07-16 18:34:33 +02:00
|
|
|
))
|
2025-07-16 00:05:14 +02:00
|
|
|
},
|
|
|
|
|
_ => { unreachable!() }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-26 21:22:30 +02:00
|
|
|
|
|
|
|
|
|
2025-07-11 23:49:59 +02:00
|
|
|
// -----------------------------------------------------------------------------
|
2025-07-11 19:36:20 +02:00
|
|
|
// Output Functions
|
2025-07-11 23:49:59 +02:00
|
|
|
// -----------------------------------------------------------------------------
|
2025-07-11 19:36:20 +02:00
|
|
|
|
|
|
|
|
/// Writes the specified graph to a file in .dot format.
|
2025-07-12 15:32:21 +02:00
|
|
|
pub fn dot(
|
|
|
|
|
system: &EvaluatedSystem,
|
2025-07-26 22:46:10 +02:00
|
|
|
node_display: graph::NodeDisplay,
|
2025-07-27 14:59:35 +02:00
|
|
|
edge_display: graph::EdgeDisplay,
|
2025-07-26 21:22:30 +02:00
|
|
|
node_color: graph::NodeColor,
|
2025-07-26 21:36:56 +02:00
|
|
|
edge_color: graph::EdgeColor
|
2025-07-12 15:41:10 +02:00
|
|
|
) -> Result<String, String> {
|
2025-07-12 02:42:28 +02:00
|
|
|
match system {
|
2025-07-12 15:41:10 +02:00
|
|
|
EvaluatedSystem::System {
|
|
|
|
|
sys: _,
|
|
|
|
|
translator: _,
|
|
|
|
|
} => Err("Supplied system is not a graph".into()),
|
|
|
|
|
EvaluatedSystem::Graph { graph, translator } => {
|
|
|
|
|
let rc_translator = Rc::new(translator.clone());
|
|
|
|
|
let modified_graph = graph.map(
|
2025-07-26 22:46:10 +02:00
|
|
|
node_display.generate(Rc::clone(&rc_translator), graph),
|
2025-07-27 14:59:35 +02:00
|
|
|
edge_display.generate(Rc::clone(&rc_translator), graph),
|
2025-07-12 15:41:10 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let graph = Rc::new(graph.to_owned());
|
|
|
|
|
|
|
|
|
|
let node_formatter =
|
2025-07-26 21:22:30 +02:00
|
|
|
node_color.generate(Rc::clone(&graph),
|
|
|
|
|
translator.encode_not_mut("*"));
|
2025-07-13 19:08:39 +02:00
|
|
|
let edge_formatter =
|
2025-07-26 21:36:56 +02:00
|
|
|
edge_color.generate(Rc::clone(&graph));
|
2025-07-12 15:41:10 +02:00
|
|
|
|
|
|
|
|
let dot = rsdot::RSDot::with_attr_getters(
|
|
|
|
|
&modified_graph,
|
|
|
|
|
&[],
|
2025-07-13 19:08:39 +02:00
|
|
|
&edge_formatter,
|
2025-07-12 15:41:10 +02:00
|
|
|
&node_formatter,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Ok(format!("{dot}"))
|
|
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
}
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Writes the specified graph to a file in .graphml format.
|
2025-07-13 17:28:13 +02:00
|
|
|
pub fn graphml(
|
|
|
|
|
system: &EvaluatedSystem,
|
2025-07-26 22:46:10 +02:00
|
|
|
node_display: graph::NodeDisplay,
|
2025-07-27 14:59:35 +02:00
|
|
|
edge_display: graph::EdgeDisplay,
|
2025-07-13 17:28:13 +02:00
|
|
|
) -> Result<String, String> {
|
2025-07-12 02:42:28 +02:00
|
|
|
match system {
|
2025-07-12 15:41:10 +02:00
|
|
|
EvaluatedSystem::System {
|
|
|
|
|
sys: _,
|
|
|
|
|
translator: _,
|
|
|
|
|
} => Err("Supplied system is not a graph".into()),
|
|
|
|
|
EvaluatedSystem::Graph { graph, translator } => {
|
|
|
|
|
let rc_translator = Rc::new(translator.to_owned());
|
|
|
|
|
|
|
|
|
|
// map each value to the corresponding value we want to display
|
|
|
|
|
let modified_graph = graph.map(
|
2025-07-26 22:46:10 +02:00
|
|
|
node_display.generate(Rc::clone(&rc_translator),
|
|
|
|
|
graph),
|
2025-07-27 14:59:35 +02:00
|
|
|
edge_display.generate(rc_translator,
|
|
|
|
|
graph),
|
2025-07-12 15:41:10 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
use petgraph_graphml::GraphMl;
|
|
|
|
|
let graphml = GraphMl::new(&modified_graph)
|
|
|
|
|
.pretty_print(true)
|
|
|
|
|
.export_node_weights_display()
|
|
|
|
|
.export_edge_weights_display();
|
|
|
|
|
|
|
|
|
|
Ok(format!("{graphml}"))
|
|
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
}
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Writes the specified graph, translator tuple to file.
|
|
|
|
|
/// N.B. graph size in memory might be much larger after serialization and
|
|
|
|
|
/// deserialization.
|
2025-07-12 15:41:10 +02:00
|
|
|
pub fn serialize(system: &EvaluatedSystem, path: String) -> Result<(), String> {
|
2025-07-12 02:42:28 +02:00
|
|
|
match system {
|
2025-07-12 15:41:10 +02:00
|
|
|
EvaluatedSystem::System {
|
|
|
|
|
sys: _,
|
|
|
|
|
translator: _,
|
|
|
|
|
} => Err("Supplied system is not a graph".into()),
|
|
|
|
|
EvaluatedSystem::Graph { graph, translator } => {
|
|
|
|
|
// relative path
|
|
|
|
|
let mut path = std::path::PathBuf::from(path);
|
|
|
|
|
path.set_extension("cbor");
|
|
|
|
|
|
|
|
|
|
let f = match fs::File::create(&path) {
|
|
|
|
|
Ok(f) => f,
|
|
|
|
|
Err(_) => return Err(format!("Error creating file {}.",
|
|
|
|
|
path.to_str().unwrap())),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match serialize::ser(f, graph, translator) {
|
|
|
|
|
Ok(_) => Ok(()),
|
|
|
|
|
Err(_) => Err("Error during serialization.".into()),
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Reads the specified serialized system from a file.
|
|
|
|
|
/// N.B. graph size in memory might be much larger after serialization and
|
|
|
|
|
/// deserialization
|
|
|
|
|
pub fn deserialize(
|
2025-07-16 00:05:14 +02:00
|
|
|
input_path: String,
|
|
|
|
|
) -> Result<(graph::RSgraph, Translator), String>
|
2025-07-11 19:36:20 +02:00
|
|
|
{
|
|
|
|
|
// relative path
|
|
|
|
|
let mut path = match env::current_dir() {
|
2025-07-12 15:41:10 +02:00
|
|
|
Ok(p) => p,
|
|
|
|
|
Err(_) => return Err("Error getting current directory.".into()),
|
2025-07-11 19:36:20 +02:00
|
|
|
};
|
|
|
|
|
path = path.join(input_path);
|
|
|
|
|
path.set_extension("cbor");
|
|
|
|
|
|
|
|
|
|
let f = match fs::File::open(&path) {
|
2025-07-12 15:41:10 +02:00
|
|
|
Ok(f) => f,
|
|
|
|
|
Err(_) => return Err(format!("Error opening file {}.",
|
|
|
|
|
path.to_str().unwrap())),
|
2025-07-11 19:36:20 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match serialize::de(f) {
|
2025-07-12 15:41:10 +02:00
|
|
|
Ok(a) => Ok(a),
|
|
|
|
|
Err(_) => Err("Error during deserialization.".into()),
|
2025-07-11 19:36:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-11 23:49:59 +02:00
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
// Interpreting Instructions
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2025-07-12 02:42:28 +02:00
|
|
|
macro_rules! save_options {
|
|
|
|
|
($assignment: expr, $so: ident) => {
|
2025-07-12 15:41:10 +02:00
|
|
|
let SaveOptions { print, save } = $so;
|
|
|
|
|
let output = $assignment;
|
|
|
|
|
if print {
|
|
|
|
|
println!("{output}");
|
|
|
|
|
}
|
|
|
|
|
if let Some(save) = save {
|
|
|
|
|
for file in save {
|
|
|
|
|
save_file(&output, file)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn execute(
|
|
|
|
|
instruction: Instruction,
|
|
|
|
|
system: &mut EvaluatedSystem
|
|
|
|
|
) -> Result<(), String> {
|
|
|
|
|
match instruction {
|
2025-07-12 15:41:10 +02:00
|
|
|
Instruction::Stats { so } => {
|
|
|
|
|
save_options!(stats(system)?, so);
|
|
|
|
|
}
|
|
|
|
|
Instruction::Target { so } => {
|
|
|
|
|
save_options!(target(system)?, so);
|
|
|
|
|
}
|
|
|
|
|
Instruction::Run { so } => {
|
|
|
|
|
save_options!(traversed(system)?, so);
|
|
|
|
|
}
|
|
|
|
|
Instruction::Loop { symbol, so } => {
|
|
|
|
|
save_options!(hoop(system, symbol)?, so);
|
|
|
|
|
}
|
|
|
|
|
Instruction::Frequency { so } => {
|
|
|
|
|
save_options!(freq(system)?, so);
|
|
|
|
|
}
|
|
|
|
|
Instruction::LimitFrequency { experiment, so } => {
|
|
|
|
|
save_options!(limit_freq(system, experiment)?, so);
|
|
|
|
|
}
|
|
|
|
|
Instruction::FastFrequency { experiment, so } => {
|
|
|
|
|
save_options!(fast_freq(system, experiment)?, so);
|
|
|
|
|
}
|
|
|
|
|
Instruction::Digraph { gso } => {
|
|
|
|
|
for save in gso {
|
|
|
|
|
digraph(system)?;
|
|
|
|
|
match save {
|
|
|
|
|
GraphSaveOptions::Dot {
|
|
|
|
|
node_display: nd,
|
|
|
|
|
edge_display: ed,
|
2025-07-13 17:28:13 +02:00
|
|
|
node_color: nc,
|
2025-07-13 19:08:39 +02:00
|
|
|
edge_color: ec,
|
2025-07-12 15:41:10 +02:00
|
|
|
so,
|
|
|
|
|
} => {
|
2025-07-26 21:36:56 +02:00
|
|
|
save_options!(dot(system, nd, ed, nc, ec)?, so);
|
2025-07-12 15:41:10 +02:00
|
|
|
}
|
|
|
|
|
GraphSaveOptions::GraphML {
|
2025-07-13 17:28:13 +02:00
|
|
|
node_display: nd,
|
|
|
|
|
edge_display: ed,
|
2025-07-12 15:41:10 +02:00
|
|
|
so,
|
|
|
|
|
} => {
|
2025-07-13 17:28:13 +02:00
|
|
|
save_options!(graphml(system, nd, ed)?, so);
|
2025-07-12 15:41:10 +02:00
|
|
|
}
|
|
|
|
|
GraphSaveOptions::Serialize { path } => {
|
|
|
|
|
serialize(system, path)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-15 00:32:58 +02:00
|
|
|
Instruction::Bisimilarity { system_b, edge_relabeler, so } => {
|
|
|
|
|
edge_relabeler.typecheck()?;
|
|
|
|
|
save_options!(bisimilar(system, &edge_relabeler, system_b)?, so);
|
2025-07-16 00:05:14 +02:00
|
|
|
}
|
2025-07-12 02:42:28 +02:00
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-26 19:43:20 +02:00
|
|
|
/// Interprets file at supplied path, then executes the code specified as
|
|
|
|
|
/// instructions inside the file.
|
2025-07-11 23:49:59 +02:00
|
|
|
pub fn run(path: String) -> Result<(), String> {
|
|
|
|
|
let mut translator = Translator::new();
|
|
|
|
|
|
2025-07-12 15:41:10 +02:00
|
|
|
let Instructions {
|
|
|
|
|
system,
|
|
|
|
|
instructions,
|
|
|
|
|
} = read_file(&mut translator, path, parser_instructions)?;
|
2025-07-11 23:49:59 +02:00
|
|
|
|
2025-07-12 02:42:28 +02:00
|
|
|
let mut system = system.compute(translator)?;
|
|
|
|
|
|
|
|
|
|
for instr in instructions {
|
2025-07-12 15:41:10 +02:00
|
|
|
execute(instr, &mut system)?;
|
2025-07-12 02:42:28 +02:00
|
|
|
}
|
2025-07-11 23:49:59 +02:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|