Files
ReactionSystems/src/rsprocess/presets.rs

875 lines
26 KiB
Rust
Raw Normal View History

2025-07-11 19:36:20 +02:00
//! Module that holds useful presets for interacting with other modules.
use std::env;
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;
use petgraph::Graph;
// grammar is defined in lib.rs, calling lalrpop_mod! twice, generates twice
// the code
use crate::grammar;
use super::structure::RSlabel;
use super::structure::{RSset, RSsystem};
use super::translator::Translator;
use super::*;
// -----------------------------------------------------------------------------
// Structures
// -----------------------------------------------------------------------------
pub struct SaveOptions {
pub print: bool,
2025-07-12 15:41:10 +02:00
pub save: Option<Vec<String>>,
}
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());
}
}
}
pub fn new() -> Self {
2025-07-12 15:41:10 +02:00
SaveOptions {
print: false,
save: None,
}
}
}
impl Default for SaveOptions {
fn default() -> Self {
2025-07-12 15:41:10 +02:00
SaveOptions::new()
}
}
#[derive(Clone)]
pub enum NodeDisplay {
Separator(String),
2025-07-12 15:41:10 +02:00
Display(graph::GraphMapNodes),
}
2025-07-13 17:28:13 +02:00
#[derive(Clone)]
pub enum EdgeDisplay {
Separator(String),
2025-07-12 15:41:10 +02:00
Display(graph::GraphMapEdges),
}
pub enum GraphSaveOptions {
2025-07-12 15:41:10 +02:00
Dot {
node_display: Vec<NodeDisplay>,
edge_display: Vec<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 {
node_display: Vec<NodeDisplay>,
edge_display: Vec<EdgeDisplay>,
so: SaveOptions,
},
Serialize {
path: String,
},
}
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 },
LimitFrequency { experiment: String, so: SaveOptions },
FastFrequency { experiment: String, so: SaveOptions },
Digraph { gso: Vec<GraphSaveOptions> },
2025-07-16 00:05:14 +02:00
Bisimilarity { system_b: String, so: SaveOptions }
}
pub enum System {
Deserialize { path: String },
2025-07-12 15:41:10 +02:00
RSsystem { sys: RSsystem },
}
2025-07-12 02:42:28 +02:00
impl System {
pub fn compute(
&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
}
}
}
}
pub struct Instructions {
pub system: System,
2025-07-12 15:41:10 +02:00
pub instructions: Vec<Instruction>,
}
2025-07-11 19:36:20 +02:00
// -----------------------------------------------------------------------------
// Helper Functions
// -----------------------------------------------------------------------------
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)
}
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
{
match e {
2025-07-12 15:41:10 +02:00
ParseError::ExtraToken { token: (l, t, r) } => Err(format!(
"Unexpected token \"{t}\" \
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}\" \
between positions {l} and {r}. Expected: "
);
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
}
}
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-12 15:41:10 +02:00
fn save_file(contents: &String, path_string: String) -> Result<(), String> {
// 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()),
};
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())
),
};
match write!(f, "{contents}") {
2025-07-12 15:41:10 +02:00
Ok(_) => {}
Err(_) => return Err("Error writing to file.".into()),
}
Ok(())
}
2025-07-11 19:36:20 +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
/// 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 00:05:14 +02:00
pub fn bisimilar(
system_a: &mut EvaluatedSystem,
system_b: String
) -> Result<String, String>
{
digraph(system_a)?;
let system_b = read_file(system_a.get_translator(),
system_b.to_string(),
parser_instructions)?;
let system_b = match system_b.system {
System::RSsystem { sys } => sys,
_ => return Err("Not implemented yet".into())
};
let mut system_b = EvaluatedSystem::System { sys: system_b,
translator: system_a.get_translator().clone() };
digraph(&mut system_b)?;
match (system_a, &system_b) {
(EvaluatedSystem::Graph { graph: a, translator: _ },
EvaluatedSystem::Graph { graph: b, translator: _ }) => {
// needed because rust cant see that we want to use &graph::RSgraph
// and not &mut graph::RSgraph
let a: &graph::RSgraph = a;
let b: &graph::RSgraph = b;
Ok(format!("{}", super::bisimilarity::bisimilarity_kanellakis_smolka(&a, &b)))
},
_ => { unreachable!() }
}
}
// -----------------------------------------------------------------------------
2025-07-11 19:36:20 +02:00
// Output Functions
// -----------------------------------------------------------------------------
2025-07-11 19:36:20 +02:00
#[allow(clippy::type_complexity)]
fn generate_node_pringting_fn<'a>(
node_display: &'a Vec<NodeDisplay>,
2025-07-12 15:41:10 +02:00
translator: Rc<Translator>,
) -> Box<dyn Fn(petgraph::prelude::NodeIndex, &'a RSsystem) -> String + 'a> {
// The type cannot be aliased since rust doesnt like generics.
// We are iterating over the node_display and constructing a function
// (accumulator) that prints out our formatted nodes. So at each step we
// call the previous function and add the next string or function.
2025-07-12 15:41:10 +02:00
let mut accumulator: Box<dyn Fn(petgraph::prelude::NodeIndex, &RSsystem) -> String> =
Box::new(|_, _| String::new());
for nd in node_display {
2025-07-12 15:41:10 +02:00
accumulator = match nd {
NodeDisplay::Display(d) => {
// retrieve from the graph module the correct formatting
// function
let val = translator.clone();
Box::new(move |i, n| {
(accumulator)(i, n)
+ &graph::GraphMapNodesTy::from(d.clone(), val.clone()).get()(i, n)
})
}
NodeDisplay::Separator(s) => {
// we have a string so simply add it at the end
Box::new(move |i, n| (accumulator)(i, n) + s)
}
};
}
accumulator
}
#[allow(clippy::type_complexity)]
fn generate_edge_pringting_fn<'a>(
edge_display: &'a Vec<EdgeDisplay>,
2025-07-12 15:41:10 +02:00
translator: Rc<Translator>,
) -> Box<dyn Fn(petgraph::prelude::EdgeIndex, &'a RSlabel) -> String + 'a> {
// The type cannot be aliased since rust doesnt like generics.
// We are iterating over the edge_display and constructing a function
// (accumulator) that prints out our formatted nodes. So at each step we
// call the previous function and add the next string or function.
2025-07-12 15:41:10 +02:00
let mut accumulator: Box<dyn Fn(petgraph::prelude::EdgeIndex, &RSlabel) -> String> =
Box::new(|_, _| String::new());
for nd in edge_display {
2025-07-12 15:41:10 +02:00
accumulator = match nd {
EdgeDisplay::Display(d) => {
// retrieve from the graph module the correct formatting
// function
let val = translator.clone();
Box::new(move |i, n| {
(accumulator)(i, n)
+ &graph::GraphMapEdgesTy::from(d.clone(), val.clone()).get()(i, n)
})
}
EdgeDisplay::Separator(s) => {
// we have a string so simply add it at the end
Box::new(move |i, n| (accumulator)(i, n) + s)
}
};
}
accumulator
}
2025-07-13 17:28:13 +02:00
use petgraph::visit::IntoNodeReferences;
#[allow(clippy::type_complexity)]
fn generate_node_color_fn<'a>(
node_color: &'a graph::NodeColor,
2025-07-16 00:05:14 +02:00
original_graph: Rc<graph::RSgraph>,
2025-07-13 17:28:13 +02:00
translator: Rc<Translator>,
) -> Box<dyn Fn(&Graph<String, String>, <&Graph<String, String, petgraph::Directed, u32> as IntoNodeReferences>::NodeRef) -> String + 'a> {
Box::new(
move |i, n| {
let cloned_node_color = node_color.clone();
for (rule, color) in cloned_node_color.conditionals {
if let Some(s) = graph::node_formatter(
original_graph.clone(),
rule,
color,
translator.encode_not_mut("*")
)(i, n) {
return s
}
}
2025-07-13 18:14:35 +02:00
graph::node_formatter_base_color(node_color.base_color.to_string())
2025-07-13 17:28:13 +02:00
}
)
}
2025-07-13 19:08:39 +02:00
use petgraph::visit::IntoEdgeReferences;
#[allow(clippy::type_complexity)]
fn generate_edge_color_fn<'a>(
edge_color: &'a graph::EdgeColor,
original_graph: Rc<Graph<RSsystem, RSlabel>>,
) -> Box<dyn Fn(&Graph<String, String>, <&Graph<String, String, petgraph::Directed, u32> as IntoEdgeReferences>::EdgeRef) -> String + 'a> {
Box::new(
move |i, n| {
let cloned_edge_color = edge_color.clone();
for (rule, color) in cloned_edge_color.conditionals {
if let Some(s) = graph::edge_formatter(
original_graph.clone(),
rule,
color
)(i, n) {
return s
}
}
graph::edge_formatter_base_color(edge_color.base_color.to_string())
}
)
}
2025-07-11 19:36:20 +02:00
/// Writes the specified graph to a file in .dot format.
pub fn dot(
system: &EvaluatedSystem,
node_display: Vec<NodeDisplay>,
2025-07-12 15:41:10 +02:00
edge_display: Vec<EdgeDisplay>,
2025-07-13 19:08:39 +02:00
node_color: &graph::NodeColor,
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());
// map each value to the corresponding value we want to display
// this is awful but rust is not a functional language so its all
// fine...
let modified_graph = graph.map(
generate_node_pringting_fn(&node_display,
Rc::clone(&rc_translator)),
generate_edge_pringting_fn(&edge_display,
2025-07-13 17:28:13 +02:00
Rc::clone(&rc_translator)),
2025-07-12 15:41:10 +02:00
);
let graph = Rc::new(graph.to_owned());
let node_formatter =
2025-07-13 19:08:39 +02:00
generate_node_color_fn(node_color, Rc::clone(&graph), rc_translator);
let edge_formatter =
generate_edge_color_fn(edge_color, 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,
node_display: Vec<NodeDisplay>,
edge_display: Vec<EdgeDisplay>,
) -> 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-13 17:28:13 +02:00
generate_node_pringting_fn(&node_display,
Rc::clone(&rc_translator)),
generate_edge_pringting_fn(&edge_display,
rc_translator),
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
}
}
//------------------------------------------------------------------------------
// 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-13 19:08:39 +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-07-16 00:05:14 +02:00
Instruction::Bisimilarity { system_b, so } => {
save_options!(bisimilar(system, system_b)?, so);
}
2025-07-12 02:42:28 +02:00
}
Ok(())
}
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-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
}
Ok(())
}