Files
ReactionSystems/src/rsprocess/graph.rs

560 lines
12 KiB
Rust
Raw Normal View History

2025-07-10 15:02:14 +02:00
//! Definitions for generating graphs from a simulation.
use petgraph::{Graph, Directed};
2025-07-05 14:54:43 +02:00
use std::collections::HashMap;
use super::structure::{RSlabel, RSsystem, RSset, RSprocess};
2025-07-05 14:54:43 +02:00
use super::support_structures::TransitionsIterator;
2025-07-13 17:28:13 +02:00
use super::translator::{self, IdType};
2025-07-07 01:25:38 +02:00
use std::rc::Rc;
2025-07-05 14:54:43 +02:00
type RSgraph = Graph<RSsystem, RSlabel, Directed, u32>;
2025-07-10 15:02:14 +02:00
/// Creates a graph starting from a system as root node
2025-07-05 14:54:43 +02:00
pub fn digraph(
system: RSsystem
) -> Result<RSgraph, String> {
let mut graph = Graph::default();
2025-07-05 14:54:43 +02:00
let node = graph.add_node(system.clone());
let mut association = HashMap::new();
association.insert(system.clone(), node);
let mut stack = vec![system];
let mut current;
while !stack.is_empty() {
// depth first
current = stack.pop().unwrap();
let current_node = *association.get(&current).unwrap();
for (label, next) in TransitionsIterator::from(&current)? {
2025-07-05 14:54:43 +02:00
// 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)
}
2025-07-07 01:25:38 +02:00
// -----------------------------------------------------------------------------
// helper functions
// -----------------------------------------------------------------------------
// Nodes -----------------------------------------------------------------------
/// Helper structure that specifies what information to display for nodes.
#[derive(Clone)]
2025-07-07 01:25:38 +02:00
pub enum GraphMapNodes {
Hide,
Entities,
MaskEntities { mask: RSset },
Context,
}
type GraphMapNodesFnTy =
dyn Fn(petgraph::prelude::NodeIndex, &RSsystem) -> String;
/// Helper structure that holds a formatting function from node as RSsystem to
/// string
2025-07-07 01:25:38 +02:00
pub struct GraphMapNodesTy {
function: Box<GraphMapNodesFnTy>
}
impl GraphMapNodesTy {
pub fn from(
f: GraphMapNodes,
translator: Rc<translator::Translator>
) -> Self {
use GraphMapNodes::*;
let function: Box<GraphMapNodesFnTy> =
// rust cant unify closures (they all have different types) so box needs
// to happen inside the match
// we use move because translator is from the env, so we transfer the
// borrow to the struct, also translator needs to be in box, a reference
// is not enough
match f {
Hide => {
Box::new(
|_, _|
String::new()
)
},
Entities => {
Box::new(
move |_, node: &RSsystem|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&node.available_entities)
2025-07-07 01:25:38 +02:00
)
)
},
MaskEntities { mask } => {
Box::new(
move |_, node: &RSsystem| {
let masked_entities =
node.available_entities
2025-07-07 01:25:38 +02:00
.intersection(&mask);
format!("{}",
translator::RSsetDisplay::from(
&translator,
&masked_entities)
)
}
)
},
Context => {
Box::new(
move |_, node: &RSsystem|
format!("{}",
translator::RSprocessDisplay::from(
&translator,
&node.context_process)
2025-07-07 01:25:38 +02:00
)
)
},
};
GraphMapNodesTy { function }
}
pub fn get(&self) -> &GraphMapNodesFnTy {
&self.function
}
}
impl From<GraphMapNodesTy> for Box<GraphMapNodesFnTy> {
fn from(g: GraphMapNodesTy) -> Self {
g.function
}
}
// Edges -----------------------------------------------------------------------
/// Helper structure that specifies what information to display for edges
#[derive(Clone)]
2025-07-07 01:25:38 +02:00
pub enum GraphMapEdges {
Hide,
Products,
MaskProducts { mask: RSset },
Entities,
MaskEntities { mask: RSset },
Context,
MaskContext { mask: RSset },
Union,
MaskUnion { mask: RSset },
Difference,
MaskDifference { mask: RSset },
EntitiesDeleted,
MaskEntitiesDeleted { mask: RSset },
EntitiesAdded,
MaskEntitiesAdded { mask: RSset },
}
type GraphMapEdgesFnTy = dyn Fn(petgraph::prelude::EdgeIndex, &RSlabel) -> String;
/// Helper structure that holds a formatting function from node as RSsystem to
/// string
2025-07-07 01:25:38 +02:00
pub struct GraphMapEdgesTy {
function: Box<GraphMapEdgesFnTy>
}
impl GraphMapEdgesTy {
pub fn from(
f: GraphMapEdges,
translator: Rc<translator::Translator>
) -> Self {
use GraphMapEdges::*;
let function: Box<GraphMapEdgesFnTy> =
// rust cant unify closures (they all have different types) so box needs
// to happen inside the match
// we use move because translator is from the env, so we transfer the
// borrow to the struct, also translator needs to be in box, a reference
// is not enough
match f {
Hide => {
Box::new(
|_, _|
String::new()
)
},
Products => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&edge.products
)
)
)
},
MaskProducts { mask } => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&mask.intersection(&edge.products)
)
)
)
},
Entities => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&edge.available_entities
)
)
)
},
MaskEntities { mask } => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&mask.intersection(&edge.available_entities)
)
)
)
},
Context => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&edge.context
)
)
)
},
MaskContext { mask } => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&mask.intersection(&edge.context)
)
)
)
},
Union => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&edge.t
)
)
)
},
MaskUnion { mask } => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&mask.intersection(&edge.t)
)
)
)
},
Difference => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&edge.context.subtraction(
&edge.available_entities
)
)
)
)
},
MaskDifference { mask } => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&mask.intersection(
&edge.context.subtraction(
&edge.available_entities
)
)
)
)
)
},
EntitiesDeleted => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&edge.available_entities.subtraction(
&edge.products
)
)
)
)
},
MaskEntitiesDeleted { mask } => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&mask.intersection(
&edge.available_entities.subtraction(
&edge.products
)
)
)
)
)
},
EntitiesAdded => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&edge.products.subtraction(
&edge.available_entities
)
)
)
)
},
MaskEntitiesAdded { mask } => {
Box::new(
move |_, edge: &RSlabel|
format!("{}",
translator::RSsetDisplay::from(
&translator,
&mask.intersection(
&edge.products.subtraction(
&edge.available_entities
)
)
)
)
)
},
};
GraphMapEdgesTy { function }
}
pub fn get(&self) -> &GraphMapEdgesFnTy {
&self.function
}
}
// -----------------------------------------------------------------------------
// Formatting Nodes & Edges
// -----------------------------------------------------------------------------
use petgraph::visit::{IntoNodeReferences, IntoEdgeReferences, EdgeRef};
type RSdotGraph = Graph<String, String, Directed, u32>;
type RSformatNodeTy =
2025-07-13 17:28:13 +02:00
dyn Fn(
&RSdotGraph,
<&RSdotGraph as IntoNodeReferences>::NodeRef
) -> Option<String>;
type RSformatEdgeTy =
2025-07-13 17:28:13 +02:00
dyn Fn(
&RSdotGraph,
<&RSdotGraph as IntoEdgeReferences>::EdgeRef
) -> String;
2025-07-13 17:28:13 +02:00
#[derive(Clone, Copy)]
pub enum OperationType {
Equals,
Subset,
SubsetEqual,
Superset,
SupersetEqual
}
impl OperationType {
pub fn evaluate(&self, a: &RSset, b: &RSset) -> 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)
}
}
}
}
#[derive(Clone)]
pub enum ContextColorConditional {
Nill,
RecursiveIdentifier(IdType),
EntitySet(OperationType, RSset),
NonDeterministicChoice,
Summation,
WaitEntity
}
#[derive(Clone)]
pub enum NodeColorConditional {
ContextConditional(ContextColorConditional),
EntitiesConditional(OperationType, RSset)
}
#[derive(Clone)]
pub struct NodeColor {
pub conditionals: Vec<(NodeColorConditional, String)>,
pub base_color: String
}
2025-07-13 18:14:35 +02:00
pub fn node_formatter_base_color(
base_color: String
) -> String
{
", fillcolor=".to_string() + &base_color
}
2025-07-13 17:28:13 +02:00
pub fn node_formatter(
original_graph: Rc<RSgraph>,
rule: NodeColorConditional,
color: String,
star: Option<IdType>,
) -> Box<RSformatNodeTy>
{
2025-07-13 17:28:13 +02:00
match rule {
NodeColorConditional::ContextConditional(ccc) => {
match ccc {
ContextColorConditional::Nill => {
Box::new(
move |_, n| {
let rssystem = original_graph.node_weight(n.0).unwrap();
if rssystem.context_process == RSprocess::Nill {
Some(", fillcolor=".to_string() + &color)
} else {
None
}
}
)
},
ContextColorConditional::RecursiveIdentifier(s) => {
Box::new(
move |_, n| {
let rssystem = original_graph.node_weight(n.0).unwrap();
match (Some(s) == star, &rssystem.context_process) {
(true, RSprocess::RecursiveIdentifier { identifier: _ }) => {
Some(", fillcolor=".to_string() + &color)
},
(false, RSprocess::RecursiveIdentifier { identifier: id }) if id == &s => {
Some(", fillcolor=".to_string() + &color)
},
_ => {None}
}
}
)
},
ContextColorConditional::EntitySet(ot, set) => {
Box::new(
move |_, n| {
let rssystem = original_graph.node_weight(n.0).unwrap();
match &rssystem.context_process {
RSprocess::EntitySet { entities, next_process: _ } if ot.evaluate(entities, &set) => {
Some(", fillcolor=".to_string() + &color)
},
_ => {None}
}
}
)
},
ContextColorConditional::NonDeterministicChoice => {
Box::new(
move |_, n| {
let rssystem = original_graph.node_weight(n.0).unwrap();
if let RSprocess::NondeterministicChoice { children: _ } = rssystem.context_process {
Some(", fillcolor=".to_string() + &color)
} else {
None
}
}
)
},
ContextColorConditional::Summation => {
Box::new(
move |_, n| {
let rssystem = original_graph.node_weight(n.0).unwrap();
if let RSprocess::Summation { children: _ } = rssystem.context_process {
Some(", fillcolor=".to_string() + &color)
} else {
None
}
}
)
},
ContextColorConditional::WaitEntity => {
Box::new(
move |_, n| {
let rssystem = original_graph.node_weight(n.0).unwrap();
if let RSprocess::WaitEntity { repeat: _, repeated_process: _, next_process: _ } = &rssystem.context_process {
Some(", fillcolor=".to_string() + &color)
} else {
None
}
}
)
},
}
2025-07-13 17:28:13 +02:00
},
NodeColorConditional::EntitiesConditional(ot, set) => {
Box::new(
move |_, n| {
let rssystem = original_graph.node_weight(n.0).unwrap();
if ot.evaluate(&rssystem.available_entities, &set) {
Some(", fillcolor=".to_string() + &color)
} else {
None
}
}
)
},
}
}
2025-07-13 17:28:13 +02:00
pub fn edge_formatter(
original_graph: Rc<RSgraph>
) -> Box<RSformatEdgeTy>
{
Box::new(
move |_g, e| String::from(
if original_graph.edge_weight(e.id()).unwrap().products.is_empty() {
"color=black, fontcolor=black"
} else {
"color=blue, fontcolor=blue"
}
))
}