MUSIC unknown
Analysis for the MUSIC active-target ionization chamber
Loading...
Searching...
No Matches
SelectionDiagram.cpp
Go to the documentation of this file.
2#include <TSystem.h>
3#include <fstream>
4#include <iostream>
5#include <sstream>
6
7namespace {
8
9// Greedy word wrap to about `width` characters per line, lines joined by
10// `br`. Node labels are one short line otherwise and the boxes grow wide.
11TString Wrap(const TString &text, Int_t width, const char *br) {
12 TString out, line, tok;
13 Ssiz_t from = 0;
14 while (text.Tokenize(tok, from, " ")) {
15 if (line.Length() > 0 && line.Length() + 1 + tok.Length() > width) {
16 out += line + br;
17 line = "";
18 }
19 if (line.Length() > 0)
20 line += " ";
21 line += tok;
22 }
23 return out + line;
24}
25
26TString EscapeHtml(const TString &s) {
27 TString e(s);
28 e.ReplaceAll("&", "&amp;");
29 e.ReplaceAll("<", "&lt;");
30 e.ReplaceAll(">", "&gt;");
31 e.ReplaceAll("\"", "&quot;");
32 return e;
33}
34
35// Mermaid labels sit in double quotes; entity codes stand in for the
36// characters its parser would otherwise read.
37TString EscapeMermaid(const TString &s) {
38 TString e(s);
39 e.ReplaceAll("&", "&amp;");
40 e.ReplaceAll("<", "&lt;");
41 e.ReplaceAll(">", "&gt;");
42 e.ReplaceAll("\"", "#quot;");
43 return e;
44}
45
46// Only what the configuration applies is drawn: a step that is off is not
47// in the diagram at all.
48std::vector<SelectionStep> Live(const std::vector<SelectionStep> &steps) {
49 std::vector<SelectionStep> live;
50 for (size_t i = 0; i < steps.size(); i++)
51 if (steps[i].on)
52 live.push_back(steps[i]);
53 return live;
54}
55
56// A step that filters: it has a reject exit. Input, the loop marker and the
57// outcome nodes do not.
58Bool_t IsCut(const SelectionStep &st) {
59 return (st.pre_cut >= 0 || (st.tag_cut >= 0 && st.tag_cut != kTAG_PASS));
60}
61
62const char *RejectLabel(const SelectionStep &st) {
63 return st.stage == SelectionStep::kEVENT_LEVEL ? "drop event"
64 : "no tag at reac";
65}
66
67const char *StageTitle(SelectionStep::Stage s) {
68 switch (s) {
70 return "input";
72 return "event-level cuts";
74 return "per reaction strip";
76 return "outcome";
77 }
78 return "";
79}
80
81// The DOT layout runs in three columns, so a landscape page: input and
82// event-level cuts, per-strip conditions, outcome.
83Int_t Column(SelectionStep::Stage s) {
84 switch (s) {
87 return 0;
89 return 1;
91 return 2;
92 }
93 return 0;
94}
95
96// The chain: every step feeds the next within and across stages; "xs" from
97// "tagged" and "n_beam"; the beam count's origin is in its text, no edge.
98struct Edge {
99 TString from, to;
100 Bool_t across; // between columns
101};
102
103std::vector<Edge> Chain(const std::vector<SelectionStep> &steps) {
104 std::vector<Edge> edges;
105 TString prev, last_strip;
106 Int_t prev_col = -1;
107 for (size_t i = 0; i < steps.size(); i++) {
108 const SelectionStep &st = steps[i];
109 const Int_t col = Column(st.stage);
110 if (st.stage == SelectionStep::kOUTCOME) {
111 if (st.id == "tagged" && last_strip.Length())
112 edges.push_back({last_strip, st.id, kTRUE});
113 else if (st.id == "xs") {
114 edges.push_back({"tagged", st.id, kFALSE});
115 edges.push_back({"n_beam", st.id, kFALSE});
116 }
117 continue;
118 }
119 if (prev.Length())
120 edges.push_back({prev, st.id, col != prev_col});
121 prev = st.id;
122 prev_col = col;
124 last_strip = st.id;
125 }
126 return edges;
127}
128
129} // namespace
130
131TString SelectionDiagram::Dot(const std::vector<SelectionStep> &all,
132 const TString &title) {
133 const std::vector<SelectionStep> steps = Live(all);
134 const Int_t kWrap = 44;
135 std::ostringstream s;
136 s << "digraph selection {" << std::endl;
137 // Orthogonal edges: the column connectors run as clean L shapes through
138 // the gaps between the columns.
139 s << Form(" graph [rankdir=TB, newrank=true, splines=ortho, nodesep=0.25, "
140 "ranksep=0.3, "
141 "fontname=\"Helvetica\", fontsize=13, labelloc=t, "
142 "label=<<B>%s</B>>];",
143 EscapeHtml(title).Data())
144 << std::endl;
145 s << " node [shape=box, style=\"rounded,filled\", fillcolor=\"#f4f4f4\", "
146 "color=\"#555555\", fontname=\"Helvetica\", fontsize=10, "
147 "margin=\"0.16,0.08\"];"
148 << std::endl;
149 s << " edge [color=\"#555555\", fontname=\"Helvetica\", fontsize=9, "
150 "arrowsize=0.7];"
151 << std::endl;
152 for (Int_t stage = SelectionStep::kINPUT; stage <= SelectionStep::kOUTCOME;
153 stage++) {
154 s << Form(" subgraph cluster_%d {", stage) << std::endl;
155 s << Form(" label=\"%s\"; labeljust=\"l\"; fontname=\"Helvetica-Bold\"; "
156 "fontsize=11; color=\"#aaaaaa\"; style=\"rounded\";",
157 StageTitle(SelectionStep::Stage(stage)))
158 << std::endl;
159 for (size_t i = 0; i < steps.size(); i++) {
160 const SelectionStep &st = steps[i];
161 if (st.stage != stage)
162 continue;
163 const TString name = EscapeHtml(st.name);
164 const TString detail = Wrap(EscapeHtml(st.detail), kWrap, "<BR/>");
165 const char *fill = IsCut(st) ? "#f4f4f4"
166 : st.id == "tagged" ? "#e3f2e1"
167 : "#e8eef7";
168 s << Form(" %s [fillcolor=\"%s\", label=<<B>%s</B><BR/>%s>];",
169 st.id.Data(), fill, name.Data(), detail.Data())
170 << std::endl;
171 if (IsCut(st)) {
172 // The reject exit beside the box, same rank, to its left, so the
173 // right gap stays free for the connector (flat tail, dir=back).
174 s << Form(" rej_%s [shape=plaintext, style=\"\", fontsize=9, "
175 "fontcolor=\"#b00020\", label=\"%s\"];",
176 st.id.Data(), RejectLabel(st))
177 << std::endl;
178 s << Form(" rej_%s -> %s [dir=back, color=\"#b00020\", "
179 "arrowsize=0.6];",
180 st.id.Data(), st.id.Data())
181 << std::endl;
182 s << Form(" {rank=same; rej_%s; %s;}", st.id.Data(), st.id.Data())
183 << std::endl;
184 }
185 }
186 s << " }" << std::endl;
187 }
188 // The columns: the first node of each on one rank, ordered left to right
189 // by an invisible flat edge; the edges between columns carry no rank.
190 TString heads;
191 Int_t last_col = -1;
192 for (size_t i = 0; i < steps.size(); i++) {
193 const Int_t col = Column(steps[i].stage);
194 if (col == last_col)
195 continue;
196 heads += (heads.Length() ? " -> " : "") + steps[i].id;
197 last_col = col;
198 }
199 s << Form(" {rank=same; %s [style=invis];}", heads.Data()) << std::endl;
200 const std::vector<Edge> edges = Chain(steps);
201 for (size_t i = 0; i < edges.size(); i++)
202 s << Form(" %s -> %s%s;", edges[i].from.Data(), edges[i].to.Data(),
203 edges[i].across ? " [constraint=false]" : "")
204 << std::endl;
205 s << "}" << std::endl;
206 return s.str();
207}
208
209TString SelectionDiagram::Mermaid(const std::vector<SelectionStep> &all,
210 const TString &title) {
211 const std::vector<SelectionStep> steps = Live(all);
212 const Int_t kWrap = 44;
213 std::ostringstream s;
214 s << "---" << std::endl;
215 s << "title: " << title << std::endl;
216 s << "---" << std::endl;
217 s << "flowchart TB" << std::endl;
218 s << " classDef cut fill:#f4f4f4,stroke:#555,color:#000;" << std::endl;
219 s << " classDef note fill:#e8eef7,stroke:#555,color:#000;" << std::endl;
220 s << " classDef tagged fill:#e3f2e1,stroke:#555,color:#000;" << std::endl;
221 s << " classDef rej fill:none,stroke:none,color:#b00020;" << std::endl;
222 for (Int_t stage = SelectionStep::kINPUT; stage <= SelectionStep::kOUTCOME;
223 stage++) {
224 s << Form(" subgraph stage_%d[\"%s\"]", stage,
225 StageTitle(SelectionStep::Stage(stage)))
226 << std::endl;
227 s << " direction TB" << std::endl;
228 for (size_t i = 0; i < steps.size(); i++) {
229 const SelectionStep &st = steps[i];
230 if (st.stage != stage)
231 continue;
232 const TString name = EscapeMermaid(st.name);
233 const TString detail = Wrap(EscapeMermaid(st.detail), kWrap, "<br/>");
234 const char *cls = IsCut(st) ? "cut"
235 : st.id == "tagged" ? "tagged"
236 : "note";
237 s << Form(" %s[\"<b>%s</b><br/>%s\"]:::%s", st.id.Data(), name.Data(),
238 detail.Data(), cls)
239 << std::endl;
240 if (IsCut(st))
241 s << Form(" rej_%s[\"%s\"]:::rej", st.id.Data(), RejectLabel(st))
242 << std::endl;
243 }
244 s << " end" << std::endl;
245 }
246 const std::vector<Edge> edges = Chain(steps);
247 for (size_t i = 0; i < edges.size(); i++)
248 s << Form(" %s --> %s", edges[i].from.Data(), edges[i].to.Data())
249 << std::endl;
250 for (size_t i = 0; i < steps.size(); i++)
251 if (IsCut(steps[i]))
252 s << Form(" %s -.-> rej_%s", steps[i].id.Data(), steps[i].id.Data())
253 << std::endl;
254 return s.str();
255}
256
257void SelectionDiagram::Write(const std::vector<SelectionStep> &steps,
258 const TString &dir, const TString &title) {
259 gSystem->mkdir(dir, kTRUE);
260 const TString base = dir + "/selection";
261 {
262 std::ofstream f((base + ".dot").Data());
263 if (!f) {
264 std::cerr << "selection diagram: cannot write " << base << ".dot"
265 << std::endl;
266 return;
267 }
268 f << Dot(steps, title);
269 }
270 {
271 std::ofstream f((base + ".mmd").Data());
272 if (f)
273 f << Mermaid(steps, title);
274 }
275 // Graphviz renders the DOT when it is about; the text files stand alone
276 // otherwise.
277 char *dot = gSystem->Which(gSystem->Getenv("PATH"), "dot");
278 if (!dot) {
279 std::cout << "selection diagram: " << base << ".dot and .mmd written "
280 << "(no `dot` on the PATH, not rendered)" << std::endl;
281 return;
282 }
283 const char *fmt[2] = {"png", "pdf"};
284 Bool_t ok = kTRUE;
285 for (Int_t k = 0; k < 2; k++) {
286 const TString cmd =
287 Form("\"%s\" -T%s%s -o \"%s.%s\" \"%s.dot\"", dot, fmt[k],
288 k == 0 ? " -Gdpi=150" : "", base.Data(), fmt[k], base.Data());
289 if (gSystem->Exec(cmd) != 0)
290 ok = kFALSE;
291 }
292 delete[] dot;
293 std::cout << "selection diagram: " << base << ".{dot,mmd"
294 << (ok ? ",png,pdf}" : "} (dot failed to render)") << std::endl;
295}
Block diagram of the event selection, generated from the configuration.
@ kTAG_PASS
void Write(const std::vector< SelectionStep > &steps, const TString &dir, const TString &title)
Write selection.dot and selection.mmd under dir, and render the DOT to selection.png and selection....
TString Mermaid(const std::vector< SelectionStep > &steps, const TString &title)
The steps as a Mermaid flowchart.
TString Dot(const std::vector< SelectionStep > &steps, const TString &title)
The steps as a Graphviz DOT graph.
One step of the event selection, as StripSumScatter::DescribeSelection lists them: in the order the c...
Int_t tag_cut
The TagCut it counts under, or -1.
TString id
Node identifier, [a-z0-9_].
Stage
Where in the selection the step sits.
Int_t pre_cut
The PreCut it counts under, or -1.
TString detail
One line: the condition to pass, with the numbers in.
TString name
Short name, as in the cut report.