taco_threshold_automaton/
dot.rs1use std::fmt::{self};
20
21use taco_display_utils::{indent_all, join_iterator};
22
23use crate::{RuleDefinition, ThresholdAutomaton};
24
25const GRAPH_OPTIONS: &str = "\
27rankdir=LR \
28fontname=\"Helvetica,Arial,sans-serif\" \
29node [fontname=\"Helvetica,Arial,sans-serif\"] \
30edge [fontname=\"Helvetica,Arial,sans-serif\"];";
31
32const INITIAL_LOC_OPTIONS: &str = "shape = circle";
34const LOC_OPTIONS: &str = "shape = circle";
36
37const REMOVED: &str = "color = red, fontcolor = red";
40const ADDED: &str = "color = green, fontcolor = green";
42
43pub trait ToDOT {
51 fn get_dot_graph(&self) -> String;
57}
58
59trait AutomatonEncoding {
64 type EdgeLabel: fmt::Display + PartialEq;
66
67 fn get_name(&self) -> String;
69
70 fn get_initial_locations(&self) -> impl Iterator<Item = String>;
75
76 fn get_locations(&self) -> impl Iterator<Item = String>;
82
83 fn get_transitions(&self) -> impl Iterator<Item = (String, Self::EdgeLabel, String)>;
85
86 fn get_node_encoding(nodes: impl Iterator<Item = String>, node_options: &String) -> String {
88 let mut nodes = join_iterator(nodes, "\n");
89 if !nodes.is_empty() {
90 nodes = format!("node [{}];\n{}\n;", node_options, indent_all(nodes));
91 nodes = indent_all(nodes);
92 nodes += "\n";
93 }
94
95 nodes
96 }
97
98 fn get_edge_encoding(
101 edges: impl Iterator<Item = (String, Self::EdgeLabel, String)>,
102 edge_options: &String,
103 ) -> String {
104 let edge_options = if edge_options.is_empty() {
105 String::new()
106 } else {
107 format!(", {edge_options}")
108 };
109
110 let edges = edges
111 .map(|(src, label, tgt)| format!("{src} -> {tgt} [label = \"{label}\"{edge_options}]"));
112 let mut edges = join_iterator(edges, ";\n");
113
114 if !edges.is_empty() {
115 edges += ";";
116 edges = indent_all(edges);
117 edges += "\n";
118 }
119
120 edges
121 }
122}
123
124impl<T: AutomatonEncoding> ToDOT for T {
126 fn get_dot_graph(&self) -> String {
127 let graph_title = format!(
128 "graph [label =<<B>{}</B>>, labelloc = t, fontsize = 35];\n",
129 self.get_name()
130 );
131
132 let init_states = Self::get_node_encoding(
133 self.get_initial_locations(),
134 &INITIAL_LOC_OPTIONS.to_string(),
135 );
136
137 let loc = Self::get_node_encoding(self.get_locations(), &LOC_OPTIONS.to_string());
138
139 let transitions = Self::get_edge_encoding(self.get_transitions(), &String::new());
140
141 format!(
142 "digraph {} {{\n{}\n{}{}{}{}}}",
143 self.get_name(),
144 indent_all(GRAPH_OPTIONS),
145 indent_all(graph_title),
146 init_states,
147 loc,
148 transitions,
149 )
150 }
151}
152
153impl<T> AutomatonEncoding for T
156where
157 T: ThresholdAutomaton,
158{
159 type EdgeLabel = String;
160
161 fn get_name(&self) -> String {
162 self.name().to_string()
163 }
164
165 fn get_initial_locations(&self) -> impl Iterator<Item = String> {
166 self.locations()
167 .filter(|l| self.can_be_initial_location(l))
168 .map(|l| l.to_string())
169 }
170
171 fn get_locations(&self) -> impl Iterator<Item = String> {
172 self.locations().map(|l| l.to_string())
173 }
174
175 fn get_transitions(&self) -> impl Iterator<Item = (String, Self::EdgeLabel, String)> {
176 self.rules().map(|r| {
177 (
178 r.source().to_string(),
179 r.guard().to_string(),
180 r.target().to_string(),
181 )
182 })
183 }
184}
185
186pub trait DOTDiff {
193 fn get_dot_diff(&self, other: &Self) -> String;
199}
200
201impl<T: AutomatonEncoding> DOTDiff for T {
202 fn get_dot_diff(&self, other: &Self) -> String {
203 let graph_title = format!(
204 "graph [label =<<B>Diff: {} - {} </B>>, labelloc = t, fontsize = 35];\n",
205 self.get_name(),
206 other.get_name()
207 );
208
209 let removed_init_states = self
210 .get_initial_locations()
211 .filter(|l| !other.get_initial_locations().any(|ol| *l == ol));
212 let removed_init_states = Self::get_node_encoding(
213 removed_init_states,
214 &format!("{INITIAL_LOC_OPTIONS}, {REMOVED}"),
215 );
216
217 let added_init_states = other
218 .get_initial_locations()
219 .filter(|l| !self.get_initial_locations().any(|ol| *l == ol));
220 let added_init_states = Self::get_node_encoding(
221 added_init_states,
222 &format!("{INITIAL_LOC_OPTIONS}, {ADDED}"),
223 );
224
225 let regular_init_states = self
226 .get_initial_locations()
227 .filter(|l| other.get_initial_locations().any(|ol| *l == ol));
228 let regular_init_states =
229 Self::get_node_encoding(regular_init_states, &INITIAL_LOC_OPTIONS.to_string());
230 let init_states = format!("{removed_init_states}{added_init_states}{regular_init_states}");
231
232 let removed_loc = self
233 .get_locations()
234 .filter(|l| !other.get_locations().any(|ol| *l == ol));
235 let removed_loc =
236 Self::get_node_encoding(removed_loc, &format!("{LOC_OPTIONS}, {REMOVED}"));
237
238 let added_loc = other
239 .get_locations()
240 .filter(|l| !self.get_locations().any(|ol| *l == ol));
241 let added_loc = Self::get_node_encoding(added_loc, &format!("{LOC_OPTIONS}, {ADDED}"));
242
243 let regular_loc = self
244 .get_locations()
245 .filter(|l| other.get_locations().any(|ol| *l == ol));
246 let regular_loc = Self::get_node_encoding(regular_loc, &LOC_OPTIONS.to_string());
247 let loc = format!("{removed_loc}{added_loc}{regular_loc}");
248
249 let removed_transitions = self.get_transitions().filter(|(src, lbl, tgt)| {
250 !other
251 .get_transitions()
252 .any(|(osrc, olbl, otgt)| *src == osrc && *tgt == otgt && *lbl == olbl)
253 });
254 let removed_transitions =
255 Self::get_edge_encoding(removed_transitions, &format!("{REMOVED}, {REMOVED}"));
256
257 let added_transitions = other.get_transitions().filter(|(src, lbl, tgt)| {
258 !self
259 .get_transitions()
260 .any(|(osrc, olbl, otgt)| *src == osrc && *tgt == otgt && *lbl == olbl)
261 });
262 let added_transitions =
263 Self::get_edge_encoding(added_transitions, &format!("{ADDED}, {ADDED}"));
264
265 let regular_transitions = self.get_transitions().filter(|(src, lbl, tgt)| {
266 other
267 .get_transitions()
268 .any(|(osrc, olbl, otgt)| *src == osrc && *tgt == otgt && *lbl == olbl)
269 });
270 let regular_transitions = Self::get_edge_encoding(regular_transitions, &String::new());
271 let transitions = format!("{removed_transitions}{added_transitions}{regular_transitions}");
272
273 format!(
274 "digraph {} {{\n{}\n{}{}{}{}}}",
275 self.get_name(),
276 indent_all(GRAPH_OPTIONS),
277 indent_all(graph_title),
278 init_states,
279 loc,
280 transitions,
281 )
282 }
283}