//! Definitions for generating graphs from a simulation. use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences}; use petgraph::{Directed, Graph}; use std::rc::Rc; use super::element::IdType; use super::label::Label; use super::set::{BasicSet, Set}; use super::system::System; use super::translator; pub type SystemGraph = Graph; 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) ); // ----------------------------------------------------------------------------- // helper functions // ----------------------------------------------------------------------------- /// 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, { fn map_edges( &self, edge_map: &super::assert::relabel::Assert, translator: &mut super::translator::Translator, ) -> Result< Graph, String, >; } impl<'a> MapEdges<'a, System, Label, Directed, u32> for SystemGraph { fn map_edges( &self, edge_map: &super::assert::relabel::Assert, translator: &mut super::translator::Translator, ) -> Result< Graph, 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() .map(|(i, edge)| { match edge_map.execute(self, &EdgeIndex::new(i), translator) { Err(e) => Err(e), Ok(val) => Ok((edge.source(), edge.target(), val)), } }) .collect::, _>>()?; 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) } } // Nodes ----------------------------------------------------------------------- /// Helper structure that specifies what information to display for nodes. #[derive(Clone)] pub enum NodeDisplayBase { String { string: String }, Hide, Entities, MaskEntities { mask: Set }, ExcludeEntities { mask: Set }, Context, UncommonEntities, MaskUncommonEntities { mask: Set }, } pub struct NodeDisplay { pub base: Vec, } type GraphMapNodesFnTy<'a> = dyn Fn(petgraph::prelude::NodeIndex, &'a System) -> String + 'a; fn match_node_display<'a>( base: &NodeDisplayBase, common_entities: Rc, translator: Rc, ) -> Box> { 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()), ExcludeEntities { mask } => { format_exclude_entities(translator, mask.clone()) } Context => format_context(translator), UncommonEntities => { format_exclude_entities(translator, (*common_entities).clone()) } 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, current_graph: &SystemGraph, ) -> Box> { 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 { let f = match_node_display( b, Rc::clone(&common_entities), Rc::clone(&translator), ); accumulator.push_str(&(f)(i, n)); } accumulator }) } } // Edges ----------------------------------------------------------------------- #[derive(Clone)] 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, }, } pub struct EdgeDisplay { pub base: Vec, } type GraphMapEdgesFnTy<'a> = dyn Fn(petgraph::prelude::EdgeIndex, &'a Label) -> String + 'a; #[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, } fn match_edge_display<'a>( base: &'a EdgeDisplayBase, translator: Rc, common: CommonEntities, ) -> Box> { use super::format_helpers::graph_map_edges_ty_from::*; use EdgeDisplayBase::*; match base { String { string } => format_string(translator, string.clone()), Hide => format_hide(translator), Products { mask, filter_common, } => { if *filter_common { format_products( translator, mask.clone(), Some(common.common_products), ) } else { format_products(translator, mask.clone(), None) } } Entities { mask, filter_common, } => { if *filter_common { format_entities( translator, mask.clone(), Some(common.common_entities), ) } else { format_entities(translator, mask.clone(), None) } } Context { mask, filter_common, } => { if *filter_common { format_context( translator, mask.clone(), Some(common.common_context), ) } else { format_context(translator, mask.clone(), None) } } Union { mask, filter_common, } => { if *filter_common { format_union( translator, mask.clone(), Some(common.common_union), ) } else { format_union(translator, mask.clone(), None) } } Difference { mask, filter_common, } => { if *filter_common { 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 { format_entities_added( translator, mask.clone(), Some(common.common_entities_added), ) } else { format_entities_added(translator, mask.clone(), None) } } } } 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, current_graph: &SystemGraph, ) -> Box> { // 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() { tmp.common_entities_deleted = common_label_entities_deleted(current_graph); } if self.common_entities_added() { 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 { let f = match_edge_display( b, Rc::clone(&translator), common.clone(), ); accumulator.push_str(&(f)(i, n)); } accumulator }) } } // ----------------------------------------------------------------------------- // Color Nodes & Edges // ----------------------------------------------------------------------------- // Node ------------------------------------------------------------------------ type RSdotGraph = Graph; 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 + 'a; #[derive(Clone, Copy)] pub enum OperationType { Equals, Subset, SubsetEqual, Superset, SupersetEqual, } 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), } } } #[derive(Clone)] pub enum ContextColorConditional { Nill, RecursiveIdentifier(IdType), EntitySet(OperationType, Set), NonDeterministicChoice, Summation, WaitEntity, } #[derive(Clone)] pub enum NodeColorConditional { ContextConditional(ContextColorConditional), EntitiesConditional(OperationType, Set), } #[derive(Clone)] pub struct NodeColor { pub conditionals: Vec<(NodeColorConditional, String)>, pub base_color: String, } #[inline(always)] fn node_formatter_base_color(base_color: String) -> String { ", fillcolor=".to_string() + &base_color } #[inline(always)] fn match_node_color_conditional<'a>( rule: &'a NodeColorConditional, color: &'a String, original_graph: Rc, star: Option, ) -> Box> { use super::format_helpers::node_formatter::*; match rule { NodeColorConditional::ContextConditional(ccc) => match ccc { ContextColorConditional::Nill => { format_nill(Rc::clone(&original_graph), color.to_string(), star) } ContextColorConditional::RecursiveIdentifier(s) => { 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 => { format_non_deterministic_choice( Rc::clone(&original_graph), color.to_string(), star, ) } 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, ), }, NodeColorConditional::EntitiesConditional(ot, set) => { format_entities_conditional( Rc::clone(&original_graph), color.to_string(), star, *ot, set.clone(), ) } } } impl NodeColor { pub fn generate<'a>( self, original_graph: Rc, star: Option, ) -> Box> { Box::new(move |i, n| { for (rule, color) in &self.conditionals { 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()) }) } } // Edge ------------------------------------------------------------------------ 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 + 'a; #[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), } #[derive(Clone)] pub struct EdgeColor { pub conditionals: Vec<(EdgeColorConditional, String)>, pub base_color: String, } fn edge_formatter_base_color(base_color: String) -> String { ", color=".to_string() + &base_color } fn match_edge_color_conditional<'a>( rule: &'a EdgeColorConditional, color: &'a String, original_graph: Rc, ) -> Box> { use super::format_helpers::edge_formatter::*; 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(), ), 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(), ), 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(), ), } } impl EdgeColor { pub fn generate<'a>( self, original_graph: Rc, ) -> Box> { Box::new(move |i, n| { for (rule, color) in &self.conditionals { 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()) }) } }