Files
ReactionSystems/src/rsprocess/graph.rs

795 lines
22 KiB
Rust
Raw Normal View History

2025-07-10 15:02:14 +02:00
//! Definitions for generating graphs from a simulation.
2025-08-24 03:57:06 +02:00
use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences};
use petgraph::{Directed, Graph};
2025-07-07 01:25:38 +02:00
use std::rc::Rc;
2025-07-05 14:54:43 +02:00
use super::element::IdType;
use super::label::Label;
use super::set::{BasicSet, Set};
use super::system::System;
2025-08-27 23:58:43 +02:00
use super::translator;
2025-07-16 00:05:14 +02:00
pub type SystemGraph = Graph<System, Label, Directed, u32>;
fn common_system_entities(graph: &SystemGraph) -> Set {
graph
.node_references()
.fold(None, |acc, node| match acc {
None => Some(node.1.available_entities.clone()),
Some(acc) => Some(node.1.available_entities.intersection(&acc)),
})
.unwrap_or(Set::default())
}
macro_rules! common_label {
(
$name:ident,
[$edge_name:ident, $acc_name:ident],
$empty_expr:expr,
$some_expr:expr
) => {
fn $name(graph: &SystemGraph) -> Set {
graph
.edge_references()
.fold(None, |$acc_name, $edge_name| {
let $edge_name = $edge_name.weight();
match $acc_name {
None => Some($empty_expr),
Some($acc_name) => Some($some_expr),
}
})
.unwrap_or(Set::default())
}
};
}
common_label!(
common_label_products,
[edge, acc],
edge.products.clone(),
edge.products.intersection(&acc)
);
common_label!(
common_label_entities,
[edge, acc],
edge.available_entities.clone(),
edge.available_entities.intersection(&acc)
);
common_label!(
common_label_context,
[edge, acc],
edge.context.clone(),
edge.context.intersection(&acc)
);
common_label!(
common_label_union,
[edge, acc],
edge.t.clone(),
edge.t.intersection(&acc)
);
common_label!(
common_label_difference,
[edge, acc],
edge.context.subtraction(&edge.available_entities),
edge.context
.subtraction(&edge.available_entities)
.intersection(&acc)
);
common_label!(
common_label_entities_deleted,
[edge, acc],
edge.available_entities.subtraction(&edge.products),
edge.available_entities
.subtraction(&edge.products)
.intersection(&acc)
);
common_label!(
common_label_entities_added,
[edge, acc],
edge.products.subtraction(&edge.available_entities),
edge.products
.subtraction(&edge.available_entities)
.intersection(&acc)
);
2025-07-07 01:25:38 +02:00
// -----------------------------------------------------------------------------
// helper functions
// -----------------------------------------------------------------------------
2025-08-15 00:32:58 +02:00
/// Very inelegant way to provide our graph with a map method where the edges
/// are mapped until the first error.
pub trait MapEdges<'a, N: 'a, E, Ty, Ix>
where
Ty: petgraph::EdgeType,
Ix: petgraph::graph::IndexType,
2025-08-15 00:32:58 +02:00
{
fn map_edges(
&self,
2025-09-10 22:41:40 +02:00
edge_map: &super::assert::relabel::Assert,
translator: &mut super::translator::Translator,
2025-09-10 22:41:40 +02:00
) -> Result<
Graph<System, super::assert::relabel::AssertReturnValue, Ty, Ix>,
String,
>;
2025-08-15 00:32:58 +02:00
}
impl<'a> MapEdges<'a, System, Label, Directed, u32> for SystemGraph {
2025-08-15 00:32:58 +02:00
fn map_edges(
&self,
2025-09-10 22:41:40 +02:00
edge_map: &super::assert::relabel::Assert,
translator: &mut super::translator::Translator,
2025-09-10 22:41:40 +02:00
) -> Result<
Graph<System, super::assert::relabel::AssertReturnValue, Directed, u32>,
String,
> {
use petgraph::graph::EdgeIndex;
let mut g = Graph::with_capacity(self.node_count(), self.edge_count());
let nodes = self.raw_nodes();
let edges = self.raw_edges();
let edges = edges
.iter()
.enumerate()
2025-09-10 22:41:40 +02:00
.map(|(i, edge)| {
match edge_map.execute(self, &EdgeIndex::new(i), translator) {
Err(e) => Err(e),
Ok(val) => Ok((edge.source(), edge.target(), val)),
2025-09-10 22:41:40 +02:00
}
})
.collect::<Result<Vec<_>, _>>()?;
nodes.iter().for_each(|node| {
g.add_node(node.weight.clone());
});
edges.into_iter().for_each(|(source, target, v)| {
g.add_edge(source, target, v);
});
Ok(g)
2025-08-15 00:32:58 +02:00
}
}
2025-07-07 01:25:38 +02:00
// Nodes -----------------------------------------------------------------------
/// Helper structure that specifies what information to display for nodes.
#[derive(Clone)]
pub enum NodeDisplayBase {
String { string: String },
2025-07-07 01:25:38 +02:00
Hide,
Entities,
MaskEntities { mask: Set },
ExcludeEntities { mask: Set },
2025-07-07 01:25:38 +02:00
Context,
UncommonEntities,
MaskUncommonEntities { mask: Set },
}
pub struct NodeDisplay {
pub base: Vec<NodeDisplayBase>,
2025-07-07 01:25:38 +02:00
}
2025-09-10 22:41:40 +02:00
type GraphMapNodesFnTy<'a> =
dyn Fn(petgraph::prelude::NodeIndex, &'a System) -> String + 'a;
fn match_node_display<'a>(
base: &NodeDisplayBase,
common_entities: Rc<Set>,
translator: Rc<translator::Translator>,
) -> Box<GraphMapNodesFnTy<'a>> {
use super::format_helpers::graph_map_nodes_ty_from::*;
use NodeDisplayBase::*;
match base {
String { string } => format_string(string.clone()),
Hide => format_hide(translator),
Entities => format_entities(translator),
MaskEntities { mask } => format_mask_entities(translator, mask.clone()),
2025-09-10 22:41:40 +02:00
ExcludeEntities { mask } => {
format_exclude_entities(translator, mask.clone())
}
Context => format_context(translator),
2025-09-10 22:41:40 +02:00
UncommonEntities => {
format_exclude_entities(translator, (*common_entities).clone())
}
2025-09-10 22:41:40 +02:00
MaskUncommonEntities { mask } => format_exclude_entities(
translator,
mask.intersection(&common_entities),
),
}
}
impl NodeDisplay {
fn contains_uncommon(&self) -> bool {
self.base.iter().any(|b| {
matches!(
b,
NodeDisplayBase::UncommonEntities
| NodeDisplayBase::MaskUncommonEntities { mask: _ }
)
})
}
pub fn generate<'a>(
self,
translator: Rc<translator::Translator>,
current_graph: &SystemGraph,
) -> Box<GraphMapNodesFnTy<'a>> {
let common_entities = if self.contains_uncommon() {
Rc::new(common_system_entities(current_graph))
} else {
Rc::new(Set::default())
};
Box::new(move |i, n| {
let mut accumulator = String::new();
for b in &self.base {
2025-09-10 22:41:40 +02:00
let f = match_node_display(
b,
Rc::clone(&common_entities),
Rc::clone(&translator),
);
accumulator.push_str(&(f)(i, n));
}
accumulator
})
2025-07-07 01:25:38 +02:00
}
}
// Edges -----------------------------------------------------------------------
#[derive(Clone)]
pub enum EdgeDisplayBase {
String {
string: String,
},
2025-07-07 01:25:38 +02:00
Hide,
Products {
mask: Option<Set>,
filter_common: bool,
},
Entities {
mask: Option<Set>,
filter_common: bool,
},
Context {
mask: Option<Set>,
filter_common: bool,
},
Union {
mask: Option<Set>,
filter_common: bool,
},
Difference {
mask: Option<Set>,
filter_common: bool,
},
EntitiesDeleted {
mask: Option<Set>,
filter_common: bool,
},
EntitiesAdded {
mask: Option<Set>,
filter_common: bool,
},
2025-07-07 01:25:38 +02:00
}
pub struct EdgeDisplay {
pub base: Vec<EdgeDisplayBase>,
2025-07-07 01:25:38 +02:00
}
2025-09-10 22:41:40 +02:00
type GraphMapEdgesFnTy<'a> =
dyn Fn(petgraph::prelude::EdgeIndex, &'a Label) -> String + 'a;
2025-07-26 20:16:32 +02:00
#[derive(Default, Clone)]
struct CommonEntities {
common_products: Set,
common_entities: Set,
common_context: Set,
common_union: Set,
common_difference: Set,
common_entities_deleted: Set,
common_entities_added: Set,
}
2025-07-26 16:46:48 +02:00
fn match_edge_display<'a>(
base: &'a EdgeDisplayBase,
translator: Rc<translator::Translator>,
common: CommonEntities,
) -> Box<GraphMapEdgesFnTy<'a>> {
use super::format_helpers::graph_map_edges_ty_from::*;
use EdgeDisplayBase::*;
2025-07-26 20:16:32 +02:00
match base {
String { string } => format_string(translator, string.clone()),
Hide => format_hide(translator),
Products {
mask,
filter_common,
} => {
if *filter_common {
2025-09-10 22:41:40 +02:00
format_products(
translator,
mask.clone(),
Some(common.common_products),
)
} else {
format_products(translator, mask.clone(), None)
}
}
Entities {
mask,
filter_common,
} => {
if *filter_common {
2025-09-10 22:41:40 +02:00
format_entities(
translator,
mask.clone(),
Some(common.common_entities),
)
} else {
format_entities(translator, mask.clone(), None)
}
}
Context {
mask,
filter_common,
} => {
if *filter_common {
2025-09-10 22:41:40 +02:00
format_context(
translator,
mask.clone(),
Some(common.common_context),
)
} else {
format_context(translator, mask.clone(), None)
}
}
Union {
mask,
filter_common,
} => {
if *filter_common {
2025-09-10 22:41:40 +02:00
format_union(
translator,
mask.clone(),
Some(common.common_union),
)
} else {
format_union(translator, mask.clone(), None)
}
}
Difference {
mask,
filter_common,
} => {
if *filter_common {
2025-09-10 22:41:40 +02:00
format_difference(
translator,
mask.clone(),
Some(common.common_difference),
)
} else {
format_difference(translator, mask.clone(), None)
}
}
EntitiesDeleted {
mask,
filter_common,
} => {
if *filter_common {
format_entities_deleted(
translator,
mask.clone(),
Some(common.common_entities_deleted),
)
} else {
format_entities_deleted(translator, mask.clone(), None)
}
}
EntitiesAdded {
mask,
filter_common,
} => {
if *filter_common {
2025-09-10 22:41:40 +02:00
format_entities_added(
translator,
mask.clone(),
Some(common.common_entities_added),
)
} else {
format_entities_added(translator, mask.clone(), None)
}
}
2025-07-07 01:25:38 +02:00
}
2025-07-26 20:16:32 +02:00
}
2025-07-07 01:25:38 +02:00
macro_rules! common_entity {
($name:ident, $match:pat, $filter_common:ident) => {
fn $name(&self) -> bool {
self.base.iter().any(|b| {
if let $match = b {
*$filter_common
} else {
false
}
})
}
};
}
impl EdgeDisplay {
common_entity!(
common_products,
EdgeDisplayBase::Products {
mask: _,
filter_common
},
filter_common
);
common_entity!(
common_entities,
EdgeDisplayBase::Entities {
mask: _,
filter_common
},
filter_common
);
common_entity!(
common_context,
EdgeDisplayBase::Context {
mask: _,
filter_common
},
filter_common
);
common_entity!(
common_union,
EdgeDisplayBase::Union {
mask: _,
filter_common
},
filter_common
);
common_entity!(
common_difference,
EdgeDisplayBase::Difference {
mask: _,
filter_common
},
filter_common
);
common_entity!(
common_entities_deleted,
EdgeDisplayBase::EntitiesDeleted {
mask: _,
filter_common
},
filter_common
);
common_entity!(
common_entities_added,
EdgeDisplayBase::EntitiesAdded {
mask: _,
filter_common
},
filter_common
);
pub fn generate<'a>(
self,
translator: Rc<translator::Translator>,
current_graph: &SystemGraph,
) -> Box<GraphMapEdgesFnTy<'a>> {
// create the structure for common entities if required
let common = {
let mut tmp = CommonEntities::default();
if self.common_products() {
tmp.common_products = common_label_products(current_graph);
}
if self.common_entities() {
tmp.common_entities = common_label_entities(current_graph);
}
if self.common_context() {
tmp.common_context = common_label_context(current_graph);
}
if self.common_union() {
tmp.common_union = common_label_union(current_graph);
}
if self.common_difference() {
tmp.common_difference = common_label_difference(current_graph);
}
if self.common_entities_deleted() {
2025-09-10 22:41:40 +02:00
tmp.common_entities_deleted =
common_label_entities_deleted(current_graph);
}
if self.common_entities_added() {
2025-09-10 22:41:40 +02:00
tmp.common_entities_added =
common_label_entities_added(current_graph);
}
tmp
};
Box::new(move |i, n| {
let mut accumulator = String::new();
for b in &self.base {
2025-09-10 22:41:40 +02:00
let f = match_edge_display(
b,
Rc::clone(&translator),
common.clone(),
);
accumulator.push_str(&(f)(i, n));
}
accumulator
})
2025-07-07 01:25:38 +02:00
}
}
// -----------------------------------------------------------------------------
2025-07-26 20:01:46 +02:00
// Color Nodes & Edges
// -----------------------------------------------------------------------------
2025-07-26 21:36:56 +02:00
// Node ------------------------------------------------------------------------
type RSdotGraph = Graph<String, String, Directed, u32>;
2025-09-10 22:41:40 +02:00
type RSformatNodeTy<'a> = dyn Fn(
&'a RSdotGraph,
<&'a RSdotGraph as IntoNodeReferences>::NodeRef,
) -> String
+ 'a;
type RSformatNodeTyOpt<'a> = dyn Fn(
&'a RSdotGraph,
<&'a RSdotGraph as IntoNodeReferences>::NodeRef,
) -> Option<String>
+ 'a;
2025-07-13 17:28:13 +02:00
#[derive(Clone, Copy)]
pub enum OperationType {
Equals,
Subset,
SubsetEqual,
Superset,
SupersetEqual,
2025-07-13 17:28:13 +02:00
}
impl OperationType {
pub fn evaluate(&self, a: &Set, b: &Set) -> bool {
match self {
Self::Equals => a.is_subset(b) && b.is_subset(a),
Self::Subset => a.is_subset(b) && !b.is_subset(a),
Self::SubsetEqual => a.is_subset(b),
Self::Superset => b.is_subset(a) && !a.is_subset(b),
Self::SupersetEqual => b.is_subset(a),
}
2025-07-13 17:28:13 +02:00
}
}
#[derive(Clone)]
pub enum ContextColorConditional {
Nill,
RecursiveIdentifier(IdType),
EntitySet(OperationType, Set),
2025-07-13 17:28:13 +02:00
NonDeterministicChoice,
Summation,
WaitEntity,
2025-07-13 17:28:13 +02:00
}
#[derive(Clone)]
pub enum NodeColorConditional {
ContextConditional(ContextColorConditional),
EntitiesConditional(OperationType, Set),
2025-07-13 17:28:13 +02:00
}
#[derive(Clone)]
pub struct NodeColor {
pub conditionals: Vec<(NodeColorConditional, String)>,
2025-07-26 21:22:30 +02:00
pub base_color: String,
2025-07-13 17:28:13 +02:00
}
2025-07-26 21:22:30 +02:00
#[inline(always)]
fn node_formatter_base_color(base_color: String) -> String {
2025-07-13 18:14:35 +02:00
", fillcolor=".to_string() + &base_color
}
2025-07-26 21:22:30 +02:00
#[inline(always)]
fn match_node_color_conditional<'a>(
2025-07-26 21:36:56 +02:00
rule: &'a NodeColorConditional,
color: &'a String,
original_graph: Rc<SystemGraph>,
star: Option<IdType>,
2025-07-26 21:22:30 +02:00
) -> Box<RSformatNodeTyOpt<'a>> {
2025-07-26 16:46:48 +02:00
use super::format_helpers::node_formatter::*;
2025-07-13 17:28:13 +02:00
match rule {
NodeColorConditional::ContextConditional(ccc) => match ccc {
ContextColorConditional::Nill => {
format_nill(Rc::clone(&original_graph), color.to_string(), star)
}
ContextColorConditional::RecursiveIdentifier(s) => {
2025-09-10 22:41:40 +02:00
format_recursive_identifier(
Rc::clone(&original_graph),
color.to_string(),
star,
*s,
)
}
ContextColorConditional::EntitySet(ot, set) => format_entity_set(
Rc::clone(&original_graph),
color.to_string(),
star,
*ot,
set.clone(),
),
ContextColorConditional::NonDeterministicChoice => {
2025-09-10 22:41:40 +02:00
format_non_deterministic_choice(
Rc::clone(&original_graph),
color.to_string(),
star,
)
}
2025-09-10 22:41:40 +02:00
ContextColorConditional::Summation => format_summation(
Rc::clone(&original_graph),
color.to_string(),
star,
),
ContextColorConditional::WaitEntity => format_wait_entity(
Rc::clone(&original_graph),
color.to_string(),
star,
),
},
2025-09-10 22:41:40 +02:00
NodeColorConditional::EntitiesConditional(ot, set) => {
format_entities_conditional(
Rc::clone(&original_graph),
color.to_string(),
star,
*ot,
set.clone(),
)
}
2025-07-13 17:28:13 +02:00
}
}
2025-07-26 21:22:30 +02:00
impl NodeColor {
pub fn generate<'a>(
self,
original_graph: Rc<SystemGraph>,
star: Option<IdType>,
2025-07-26 21:22:30 +02:00
) -> Box<RSformatNodeTy<'a>> {
Box::new(move |i, n| {
for (rule, color) in &self.conditionals {
2025-09-10 22:41:40 +02:00
let f = match_node_color_conditional(
rule,
color,
Rc::clone(&original_graph),
star,
);
if let Some(s) = (f)(i, n) {
return s;
}
}
node_formatter_base_color(self.base_color.clone())
})
2025-07-26 21:22:30 +02:00
}
}
2025-07-26 21:36:56 +02:00
// Edge ------------------------------------------------------------------------
2025-09-10 22:41:40 +02:00
type RSformatEdgeTy<'a> = dyn Fn(
&'a RSdotGraph,
<&'a RSdotGraph as IntoEdgeReferences>::EdgeRef,
) -> String
+ 'a;
type RSformatEdgeTyOpt<'a> = dyn Fn(
&'a RSdotGraph,
<&'a RSdotGraph as IntoEdgeReferences>::EdgeRef,
) -> Option<String>
+ 'a;
2025-07-13 19:08:39 +02:00
#[derive(Clone)]
pub enum EdgeColorConditional {
Entities(OperationType, Set),
Context(OperationType, Set),
T(OperationType, Set),
Reactants(OperationType, Set),
ReactantsAbsent(OperationType, Set),
Inhibitors(OperationType, Set),
InhibitorsPresent(OperationType, Set),
Products(OperationType, Set),
2025-07-13 19:08:39 +02:00
}
#[derive(Clone)]
pub struct EdgeColor {
pub conditionals: Vec<(EdgeColorConditional, String)>,
pub base_color: String,
2025-07-13 19:08:39 +02:00
}
fn edge_formatter_base_color(base_color: String) -> String {
2025-07-13 19:32:11 +02:00
", color=".to_string() + &base_color
2025-07-13 19:08:39 +02:00
}
2025-07-26 21:36:56 +02:00
fn match_edge_color_conditional<'a>(
rule: &'a EdgeColorConditional,
color: &'a String,
original_graph: Rc<SystemGraph>,
2025-07-26 21:36:56 +02:00
) -> Box<RSformatEdgeTyOpt<'a>> {
2025-07-26 16:46:48 +02:00
use super::format_helpers::edge_formatter::*;
2025-07-13 19:08:39 +02:00
match rule {
EdgeColorConditional::Entities(ot, set) => format_entities(
Rc::clone(&original_graph),
color.to_string(),
*ot,
set.clone(),
),
EdgeColorConditional::Context(ot, set) => format_context(
Rc::clone(&original_graph),
color.to_string(),
*ot,
set.clone(),
),
EdgeColorConditional::T(ot, set) => format_t(
Rc::clone(&original_graph),
color.to_string(),
*ot,
set.clone(),
),
EdgeColorConditional::Reactants(ot, set) => format_reactants(
Rc::clone(&original_graph),
color.to_string(),
*ot,
set.clone(),
),
2025-09-10 22:41:40 +02:00
EdgeColorConditional::ReactantsAbsent(ot, set) => {
format_reactants_absent(
Rc::clone(&original_graph),
color.to_string(),
*ot,
set.clone(),
)
}
EdgeColorConditional::Inhibitors(ot, set) => format_inhibitors(
Rc::clone(&original_graph),
color.to_string(),
*ot,
set.clone(),
),
2025-09-10 22:41:40 +02:00
EdgeColorConditional::InhibitorsPresent(ot, set) => {
format_inhibitors_present(
Rc::clone(&original_graph),
color.to_string(),
*ot,
set.clone(),
)
}
EdgeColorConditional::Products(ot, set) => format_products(
Rc::clone(&original_graph),
color.to_string(),
*ot,
set.clone(),
),
2025-07-13 19:08:39 +02:00
}
}
2025-07-26 21:36:56 +02:00
impl EdgeColor {
2025-09-10 22:41:40 +02:00
pub fn generate<'a>(
self,
original_graph: Rc<SystemGraph>,
) -> Box<RSformatEdgeTy<'a>> {
Box::new(move |i, n| {
for (rule, color) in &self.conditionals {
2025-09-10 22:41:40 +02:00
let f = match_edge_color_conditional(
rule,
color,
Rc::clone(&original_graph),
);
if let Some(s) = (f)(i, n) {
return s;
}
}
edge_formatter_base_color(self.base_color.clone())
})
2025-07-26 21:36:56 +02:00
}
}