LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
tikz.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_IO_TIKZ_H
6#define LINE_IO_TIKZ_H
7
8/**
9 * @file
10 * @ingroup line_io
11 * Port of `jline.io.tikz.TikZExporter`, the network diagram behind MATLAB's
12 * `MNetwork.toTikZ`, `exportTikZToFile`, `exportTikZ` and `tikzExportPNG`.
13 *
14 * `to_tikz` returns a complete `standalone` LaTeX document: the JAR's preamble
15 * and styles, one TikZ node per network node at its layered-layout position
16 * (tikz/tikz_layout.h), then one arrow per connection, routed around the nodes
17 * in its way (tikz/tikz_edge_router.h). The text is the JAR's for the same
18 * model and options, byte for byte.
19 *
20 * EDGE ORDER. Source nodes in model order (the drawn ones), and for each its
21 * targets in ascending node index, the order of the connection-matrix row,
22 * whether they are reached directly or through hidden class switches. The JAR
23 * writes the same order (a node-index `TreeSet`, since it replaced a `HashSet`
24 * of identity-hashed nodes), and so does MATLAB, which draws through the JAR.
25 *
26 * Every arrow is unlabelled: `generateTikZ` passes a NaN probability for every
27 * edge, so `show_routing_prob` never takes effect, here as there.
28 *
29 * `export_tikz` and `tikz_export_png` shell out to `pdflatex` (and `pdftoppm`,
30 * from poppler-utils) through `util::capture`; `tikz_pdflatex_available` says
31 * whether the first can run. `tikz_view` is `MNetwork.tikzView` (the JAR's
32 * `TikZExporter.display`): it compiles the PDF into a directory it leaves in
33 * place and opens it in a standalone viewer when asked to and one is on PATH.
34 */
35
36#include <cstdlib>
37#include <fstream>
38#include <iostream>
39#include <sstream>
40#include <string>
41#include <vector>
42
43#include <sys/stat.h>
44
52#include "line/util/error.h"
54#include "line/util/tempdir.h"
55
56namespace line {
57namespace io {
58
59namespace tikz_detail {
60
61/** `isAutoGeneratedNode`: a ClassSwitch named `CS_..._to_...`, the name `link` gives the switch it inserts. */
62inline bool is_auto_generated(const TikzNode& nd) {
63 return nd.type == lang::NodeType::ClassSwitch && nd.name.compare(0, 3, "CS_") == 0 &&
64 nd.name.find("_to_") != std::string::npos;
65}
66
67/** `getOutputAnchor`: a Queue's arrows leave its server circle when one is drawn; every other node, a Delay included, its box. */
68inline std::string output_anchor(const TikzNode& nd, const TikzOptions& opt) {
69 return nd.type == lang::NodeType::Queue && opt.show_server_count ? "_server.east" : ".east";
70}
71
72/** `renderSelfLoop`: the wide loop from the server circle of a Queue that draws one, the plain-node loop otherwise. */
73inline std::string self_loop(const std::string& id, const TikzNode& nd, const TikzOptions& opt) {
74 if (nd.type == lang::NodeType::Queue && opt.show_server_count)
75 return "\\draw[conn] (" + id + "_server.north) -- ++(0,0.6) -- ++(-1.8,0) -- ++(0,-0.6) -- (" +
76 id + ".west);\n";
77 return "\\draw[conn] (" + id + ".north) -- ++(0,0.5) -- ++(-0.8,0) -- ++(0,-0.5) -- (" + id +
78 ".west);\n";
79}
80
81inline bool file_exists(const std::string& p) {
82 struct stat st;
83 return ::stat(p.c_str(), &st) == 0 && S_ISREG(st.st_mode);
84}
85
86inline bool ends_with(const std::string& s, const std::string& suf) {
87 return s.size() >= suf.size() && s.compare(s.size() - suf.size(), suf.size(), suf) == 0;
88}
89
90inline void write_text(const std::string& path, const std::string& text) {
91 std::ofstream out(path.c_str(), std::ios::binary);
92 if (!out) throw InputError("tikz: cannot write '" + path + "'");
93 out << text;
94 if (!out) throw InputError("tikz: failed writing '" + path + "'");
95}
96
97inline void copy_file(const std::string& from, const std::string& to) {
98 std::ifstream in(from.c_str(), std::ios::binary);
99 if (!in) throw InputError("tikz: cannot read '" + from + "'");
100 std::ofstream out(to.c_str(), std::ios::binary);
101 if (!out) throw InputError("tikz: cannot write '" + to + "'");
102 out << in.rdbuf();
103 if (!out) throw InputError("tikz: failed writing '" + to + "'");
104}
105
106/** Seconds a pdflatex or pdftoppm run may take before it is killed; a TikZ picture needs a few. */
107constexpr int kToolTimeout = 600;
108
109/**
110 * `exportToPDF`: compile `tex` in a fresh directory and return the PDF path.
111 * The directory belongs to `dir`, so the caller copies the PDF out before it goes.
112 */
113inline std::string compile_pdf(const std::string& tex, const util::TempDir& dir) {
114 if (util::capture({"pdflatex", "--version"}, 60, true).exitCode != 0)
115 throw Error("pdflatex is not available. Please install TeX Live or MiKTeX.");
116 const std::string texfile = dir.file("network.tex");
117 const std::string pdffile = dir.file("network.pdf");
118 write_text(texfile, tex);
120 {"pdflatex", "-interaction=nonstopmode", "-output-directory=" + dir.path(), texfile},
121 kToolTimeout, true);
122 if (r.timedOut)
123 throw Error("pdflatex did not finish within " + std::to_string(kToolTimeout) + " s");
124 if (r.exitCode != 0 || !file_exists(pdffile))
125 throw Error("pdflatex compilation failed:\n" + r.out);
126 return pdffile;
127}
128
129} // namespace tikz_detail
130
131/** `TikZExporter.generateTikZ` over an extracted graph. */
132inline std::string to_tikz(const TikzGraph& g, const TikzOptions& opt = TikzOptions()) {
133 const TikzLayout L = tikz_layout(g, opt);
134 const std::size_t n = g.nodes.size();
135 std::ostringstream sb;
136 sb << tikz_preamble(opt) << "\n";
137 sb << "\\begin{document}\n\\begin{tikzpicture}\n\n";
138
139 std::vector<std::size_t> visible;
140 std::vector<bool> is_visible(n, false);
141 for (std::size_t v = 0; v < n; ++v)
142 if (!opt.hide_auto_generated_nodes || !tikz_detail::is_auto_generated(g.nodes[v])) {
143 visible.push_back(v);
144 is_visible[v] = true;
145 }
146
147 sb << "% Nodes\n";
148 for (std::size_t v : visible) sb << tikz_render_node(g.nodes[v], L.x[v], L.y[v], opt);
149 sb << "\n";
150
151 sb << "% Connections\n";
152 TikzEdgeRouter router(L.x, L.y, visible);
153 for (std::size_t from : visible) {
154 // buildCollapsedConnections: the visible nodes reached through hidden ones
155 std::vector<bool> seen(n, false), target(n, false);
156 std::vector<std::size_t> queue;
157 for (std::size_t j = 0; j < n; ++j)
158 if (g.conn[from][j] && !seen[j]) {
159 seen[j] = true;
160 queue.push_back(j);
161 }
162 for (std::size_t q = 0; q < queue.size(); ++q) {
163 const std::size_t cur = queue[q];
164 if (is_visible[cur]) {
165 target[cur] = true;
166 continue;
167 }
168 for (std::size_t j = 0; j < n; ++j)
169 if (g.conn[cur][j] && !seen[j]) {
170 seen[j] = true;
171 queue.push_back(j);
172 }
173 }
174 const std::string fid = tikz_detail::sanitize_id(g.nodes[from].name);
175 for (std::size_t to = 0; to < n; ++to) {
176 if (!target[to]) continue;
177 if (to == from) {
178 sb << tikz_detail::self_loop(fid, g.nodes[from], opt);
179 continue;
180 }
181 const std::vector<TikzPoint> wp = router.compute_waypoints(from, to);
183 tikz_detail::output_anchor(g.nodes[from], opt), ".west", wp,
184 std::numeric_limits<double>::quiet_NaN(), opt);
185 }
186 }
187 sb << "\n\\end{tikzpicture}\n\\end{document}\n";
188 return sb.str();
189}
190
191/** `Network.toTikZ(options)` over a network struct. */
192template <class T>
193std::string to_tikz(const qn::NetworkStruct<T>& sn, const TikzOptions& opt = TikzOptions()) {
194 return to_tikz(tikz_graph(sn), opt);
195}
196
197/** `Network.toTikZ(options)`; refreshes the struct, as the JAR's `getStruct()` does. */
198template <class T>
199std::string to_tikz(qn::Network<T>& model, const TikzOptions& opt = TikzOptions()) {
200 return to_tikz(model.get_struct(), opt);
201}
202
203/** Can `pdflatex --version` run? (`TikZExporter.isPdfLatexAvailable`) */
205 return util::capture({"pdflatex", "--version"}, 60, true).exitCode == 0;
206}
207
208/** `Network.exportTikZToFile`: the document, written to `tex_path` as given. */
209template <class M>
210void export_tikz_to_file(M& model, const std::string& tex_path, const TikzOptions& opt = TikzOptions()) {
211 tikz_detail::write_text(tex_path, to_tikz(model, opt));
212}
213
214/**
215 * `Network.exportTikZ`: write `<stem>.tex` and compile it to `<stem>.pdf`,
216 * where the stem is `path` less a `.pdf` or `.tex` suffix. Returns the PDF path.
217 *
218 * The JAR writes the same `.tex` and returns a PDF left in a temporary
219 * directory; this port puts the PDF beside the `.tex` instead, which is where
220 * a caller that named a path expects to find it, and leaves no directory behind.
221 */
222template <class M>
223std::string export_tikz(M& model, const std::string& path, const TikzOptions& opt = TikzOptions()) {
224 std::string stem = path;
225 if (tikz_detail::ends_with(stem, ".pdf") || tikz_detail::ends_with(stem, ".tex"))
226 stem.resize(stem.size() - 4);
227 const std::string tex = to_tikz(model, opt);
228 tikz_detail::write_text(stem + ".tex", tex);
229 util::TempDir dir("tikz");
230 const std::string pdf = tikz_detail::compile_pdf(tex, dir);
231 tikz_detail::copy_file(pdf, stem + ".pdf");
232 return stem + ".pdf";
233}
234
235/**
236 * `Network.tikzExportPNG(path, dpi)`: compile the document and rasterise it
237 * with `pdftoppm -png -r dpi -singlefile`. `.png` is appended when missing, as
238 * the JAR does. Returns the PNG path.
239 */
240template <class M>
241std::string tikz_export_png(M& model, const std::string& path, int dpi = 150,
242 const TikzOptions& opt = TikzOptions()) {
243 const std::string png = tikz_detail::ends_with(path, ".png") ? path : path + ".png";
244 util::TempDir dir("tikz");
245 const std::string pdf = tikz_detail::compile_pdf(to_tikz(model, opt), dir);
246 const util::ProcResult r =
247 util::capture({"pdftoppm", "-png", "-r", std::to_string(dpi), "-singlefile", pdf,
248 png.substr(0, png.size() - 4)},
250 if (r.timedOut)
251 throw Error("pdftoppm did not finish within " + std::to_string(tikz_detail::kToolTimeout) + " s");
252 if (r.exitCode != 0) throw Error("pdftoppm conversion failed:\n" + r.out);
253 return png;
254}
255
256namespace tikz_detail {
257
258/** The first executable named `cmd` on PATH, empty when there is none (`TikZViewer.isCommandAvailable`). */
259inline std::string find_on_path(const std::string& cmd) {
260 const char* p = std::getenv("PATH");
261 if (p == nullptr) return std::string();
262 std::stringstream ss(p);
263 std::string dir;
264 while (std::getline(ss, dir, ':')) {
265 const std::string f = (dir.empty() ? std::string(".") : dir) + "/" + cmd;
266 if (file_exists(f) && ::access(f.c_str(), X_OK) == 0) return f;
267 }
268 return std::string();
269}
270
271/** Start `argv` detached (own session, std streams on /dev/null, double fork so no zombie); false if it cannot start. */
272inline bool spawn_detached(const std::vector<std::string>& argv) {
273 const pid_t pid = ::fork();
274 if (pid < 0) return false;
275 if (pid == 0) {
276 ::setsid();
277 if (::fork() != 0) ::_exit(0);
278 const int devnull = ::open("/dev/null", O_RDWR);
279 if (devnull >= 0) {
280 ::dup2(devnull, STDIN_FILENO);
281 ::dup2(devnull, STDOUT_FILENO);
282 ::dup2(devnull, STDERR_FILENO);
283 if (devnull > STDERR_FILENO) ::close(devnull);
284 }
285 util::detail::exec_never_returns(argv);
286 }
287 int status = 0;
288 ::waitpid(pid, &status, 0);
289 return WIFEXITED(status) && WEXITSTATUS(status) == 0;
290}
291
292/** `TikZViewer.displayPDF`: a standalone viewer first, then the desktop default; false when neither is found. */
293inline bool display_pdf(const std::string& pdf) {
294 static const char* const viewers[] = {"evince", "okular", "mupdf", "zathura", "qpdfview", "acroread"};
295 for (const char* v : viewers)
296 if (!find_on_path(v).empty() && spawn_detached({v, pdf})) return true;
297#ifdef __APPLE__
298 const char* desktop = "open";
299#else
300 const char* desktop = "xdg-open";
301#endif
302 return !find_on_path(desktop).empty() && spawn_detached({desktop, pdf});
303}
304
305} // namespace tikz_detail
306
307namespace tikz_detail {
308inline const std::string& model_name(const TikzGraph& g) { return g.name; }
309template <class T>
310const std::string& model_name(const qn::NetworkStruct<T>& sn) { return sn.name; }
311template <class T>
312const std::string& model_name(qn::Network<T>& m) { return m.raw_struct().name; }
313} // namespace tikz_detail
314
315/** What `tikz_view` produced. */
317 std::string path; ///< the compiled PDF, or the `.tex` saved when it could not be compiled
318 bool compiled = false; ///< `path` is a PDF
319 bool opened = false; ///< a viewer was started on it
320};
321
322/**
323 * `MNetwork.tikzView` (`TikZExporter.display`). With pdflatex, compile the
324 * document into a fresh `line_tikz_*` directory that is LEFT IN PLACE (the
325 * viewer reads it after this returns) and, when `open_viewer`, start a PDF
326 * viewer on it. Without pdflatex, or when compilation fails, save the source
327 * to `network-diagram.tex` in the working directory, as the JAR does, and say
328 * so on stdout. Nothing is thrown for a missing tool; the result says what exists.
329 */
330template <class M>
331TikzViewResult tikz_view(M& model, const TikzOptions& opt = TikzOptions(), bool open_viewer = true) {
332 TikzViewResult res;
333 const std::string tex = to_tikz(model, opt);
335 try {
336 util::TempDir dir("tikz");
337 res.path = tikz_detail::compile_pdf(tex, dir);
338 dir.keep();
339 res.compiled = true;
340 } catch (const Error& e) {
341 std::cerr << "Failed to generate visualization: " << e.what() << "\n";
342 }
343 }
344 if (!res.compiled) {
345 res.path = "network-diagram.tex";
347 std::cout << "TikZ code saved to: " << res.path << "\nCompile manually with: pdflatex " << res.path
348 << "\n";
349 return res;
350 }
351 if (open_viewer) {
353 if (res.opened)
354 std::cout << "Opened diagram for: " << tikz_detail::model_name(model) << "\n";
355 else
356 std::cout << "Could not open PDF viewer. File saved at: " << res.path << "\n";
357 }
358 return res;
359}
360
361} // namespace io
362} // namespace line
363
364#endif // LINE_IO_TIKZ_H
Base error for the multiprecision C++ port.
Definition error.h:31
Error(const std::string &what)
Definition error.h:33
InputError(const std::string &what)
Definition error.h:39
std::vector< TikzPoint > compute_waypoints(std::size_t from, std::size_t to)
computeWaypoints: empty when the straight segment is clear, and for a self-loop.
A network plus its refreshed NetworkStruct.
A queueing network under construction.
const NetworkStruct< T > & get_struct()
The refreshed struct, MATLAB's model.getStruct().
NetworkStruct< T > & raw_struct()
The struct WITHOUT refreshing it, for a caller that is still building.
std::string file(const std::string &name) const
A file inside it.
Definition tempdir.h:127
void keep()
Leave the directory in place, for a caller that wants to inspect it.
Definition tempdir.h:130
const std::string & path() const
The directory itself, with no trailing separator.
Definition tempdir.h:124
The exception types the port throws.
std::string compile_pdf(const std::string &tex, const util::TempDir &dir)
exportToPDF: compile tex in a fresh directory and return the PDF path.
Definition tikz.h:113
void write_text(const std::string &path, const std::string &text)
Definition tikz.h:90
const std::string & model_name(const TikzGraph &g)
Definition tikz.h:308
bool spawn_detached(const std::vector< std::string > &argv)
Start argv detached (own session, std streams on /dev/null, double fork so no zombie); false if it ca...
Definition tikz.h:272
bool file_exists(const std::string &p)
Definition tikz.h:81
bool display_pdf(const std::string &pdf)
TikZViewer.displayPDF: a standalone viewer first, then the desktop default; false when neither is fou...
Definition tikz.h:293
std::string output_anchor(const TikzNode &nd, const TikzOptions &opt)
getOutputAnchor: a Queue's arrows leave its server circle when one is drawn; every other node,...
Definition tikz.h:68
std::string self_loop(const std::string &id, const TikzNode &nd, const TikzOptions &opt)
renderSelfLoop: the wide loop from the server circle of a Queue that draws one, the plain-node loop o...
Definition tikz.h:73
std::string sanitize_id(const std::string &name)
name.replaceAll("[^a-zA-Z0-9]", "_"): one underscore per CODE POINT, so a UTF-8 sequence is one chara...
Definition tikz_graph.h:134
std::string find_on_path(const std::string &cmd)
The first executable named cmd on PATH, empty when there is none (TikZViewer.isCommandAvailable).
Definition tikz.h:259
void copy_file(const std::string &from, const std::string &to)
Definition tikz.h:97
constexpr int kToolTimeout
Seconds a pdflatex or pdftoppm run may take before it is killed; a TikZ picture needs a few.
Definition tikz.h:107
bool ends_with(const std::string &s, const std::string &suf)
Definition tikz.h:86
bool is_auto_generated(const TikzNode &nd)
isAutoGeneratedNode: a ClassSwitch named CS_..._to_..., the name link gives the switch it inserts.
Definition tikz.h:62
std::string tikz_preamble(const TikzOptions &opt)
TikZNodeRenderer.getPreamble, byte for byte.
bool tikz_pdflatex_available()
Can pdflatex --version run?
Definition tikz.h:204
TikzLayout tikz_layout(const TikzGraph &g, const TikzOptions &opt)
TikZLayoutEngine.computeLayout.
Definition tikz_layout.h:65
std::string tikz_render_node(const TikzNode &nd, double x, double y, const TikzOptions &opt)
TikZNodeRenderer.renderNode.
std::string to_tikz(const TikzGraph &g, const TikzOptions &opt=TikzOptions())
TikZExporter.generateTikZ over an extracted graph.
Definition tikz.h:132
TikzViewResult tikz_view(M &model, const TikzOptions &opt=TikzOptions(), bool open_viewer=true)
MNetwork.tikzView (TikZExporter.display).
Definition tikz.h:331
std::string export_tikz(M &model, const std::string &path, const TikzOptions &opt=TikzOptions())
Network.exportTikZ: write <stem>.tex and compile it to <stem>.pdf, where the stem is path less a ....
Definition tikz.h:223
void export_tikz_to_file(M &model, const std::string &tex_path, const TikzOptions &opt=TikzOptions())
Network.exportTikZToFile: the document, written to tex_path as given.
Definition tikz.h:210
TikzGraph tikz_graph(const qn::NetworkStruct< T > &sn)
The drawable graph of a network struct.
Definition tikz_graph.h:186
std::string tikz_export_png(M &model, const std::string &path, int dpi=150, const TikzOptions &opt=TikzOptions())
Network.tikzExportPNG(path, dpi): compile the document and rasterise it with pdftoppm -png -r dpi -si...
Definition tikz.h:241
std::string tikz_render_routed_edge(const std::string &from_id, const std::string &to_id, const std::string &from_anchor, const std::string &to_anchor, const std::vector< TikzPoint > &wp, double prob, const TikzOptions &opt)
TikZEdgeRouter.renderRoutedEdge.
ProcResult capture(const std::vector< std::string > &argv, int timeoutSeconds, bool mergeStderr=false)
Runs a command, capturing stdout and discarding stderr.
Definition subprocess.h:82
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
The Network constructor API: Queue, Delay, Source, Sink, Router, ClassSwitch, Cache,...
A queueing network and its refreshed NetworkStruct.
Nodes in model order plus sn.connmatrix over them.
Definition tikz_graph.h:54
std::vector< std::vector< bool > > conn
conn[i][j]: node i is linked to node j, 0-based
Definition tikz_graph.h:57
std::string name
Definition tikz_graph.h:55
std::vector< TikzNode > nodes
Definition tikz_graph.h:56
Node positions in cm, indexed like TikzGraph::nodes, and the layers that produced them.
Definition tikz_layout.h:32
std::vector< double > y
Definition tikz_layout.h:33
std::vector< double > x
Definition tikz_layout.h:33
One node as the TikZ exporter sees it.
Definition tikz_graph.h:46
lang::NodeType type
Definition tikz_graph.h:48
std::string name
Definition tikz_graph.h:47
Layout and rendering options of the network TikZ exporter (TikZOptions).
What tikz_view produced.
Definition tikz.h:316
bool compiled
path is a PDF
Definition tikz.h:318
bool opened
a viewer was started on it
Definition tikz.h:319
std::string path
the compiled PDF, or the .tex saved when it could not be compiled
Definition tikz.h:317
Outcome of a captured command.
Definition subprocess.h:42
int exitCode
Exit status, or -1 when the command could not run.
Definition subprocess.h:43
bool timedOut
True when the deadline expired and the child was killed.
Definition subprocess.h:45
std::string out
Everything the command wrote to stdout.
Definition subprocess.h:44
Running an external command and capturing its output, with a deadline.
A scratch directory for the subprocess wrappers, the port's lineTempName.
Port of jline.io.tikz.TikZEdgeRouter: orthogonal waypoints that keep an edge off the nodes it would o...
The part of a network the TikZ exporter draws, and the text helpers the JAR's exporter uses to write ...
Port of jline.io.tikz.TikZLayoutEngine: a layered (Sugiyama-style) layout.
Port of jline.io.tikz.TikZNodeRenderer: the preamble with the node styles, and one TikZ node per netw...
Port of jline.io.tikz.TikZOptions: the knobs of the network TikZ exporter.