LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fluid_export_odes.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_SOLVERS_FLUID_FLUID_EXPORT_ODES_H
6#define LINE_SOLVERS_FLUID_FLUID_EXPORT_ODES_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * `@@SolverFLD/exportODEs.m`: the fluid ODE system as a standalone LaTeX
12 * document, in a form meant to be read by a person AND parsed by a program.
13 *
14 * THE MACHINE-READABLE PART is the comment header, before the preamble: one
15 * `% STATE s station=... class=... phase=...` line per state variable and, in
16 * the J form, one `% EVENT e var=... type=... coeff=...` line per event. A tool
17 * that wants the system and not the typesetting reads those and stops at
18 * `\documentclass`. The comments are the contract; the body below them is
19 * presentation, and the two are generated from the same `FluidSymSystem`.
20 *
21 * TWO NOTATIONS. `scalar` writes one expanded equation per state variable,
22 * which is what one reads to understand a small model. `matrix` writes the
23 * compact form -- dx/dt = W' theta(x) + lambda, or dx/dt = J r(x) -- with the
24 * numeric matrices printed once, which is the only readable option once the
25 * state space passes a few dozen entries.
26 *
27 * WHAT IS NOT REPRODUCED, and is called out in the exported Remarks exactly as
28 * the reference calls it out: the regularization of vanishing denominators, the
29 * FCFS non-exponential re-fitting loop, and any immediate-transition
30 * elimination. The exported system is the nominal one.
31 */
32
33#include <algorithm>
34#include <cmath>
35#include <cstddef>
36#include <cstdio>
37#include <string>
38#include <vector>
39
43#include "line/util/error.h"
44
45namespace line {
46namespace fluid {
47
48namespace detail {
49
50/** `fmtnum`: integers plain, everything else at %.8g, infinity as `\infty`. */
51inline std::string sym_fmtnum(double v) {
52 if (std::isinf(v)) return v > 0 ? "\\infty" : "-\\infty";
53 char buf[64];
54 if (v == std::floor(v) && std::fabs(v) < 1e15) {
55 std::snprintf(buf, sizeof(buf), "%lld", static_cast<long long>(v));
56 return std::string(buf);
57 }
58 std::snprintf(buf, sizeof(buf), "%.8g", v);
59 return std::string(buf);
60}
61
62/** `texesc`: escape the identifier characters LaTeX would otherwise eat. */
63inline std::string sym_texesc(const std::string& s) {
64 std::string out;
65 for (char ch : s) {
66 if (ch == '_' || ch == '%' || ch == '&' || ch == '#') out.push_back('\\');
67 out.push_back(ch);
68 }
69 return out;
70}
71
72inline std::string sym_int(std::size_t v) {
73 char buf[32];
74 std::snprintf(buf, sizeof(buf), "%llu", static_cast<unsigned long long>(v));
75 return std::string(buf);
76}
77
78/** `factor_tex`: the state-dependent factor of a term driven by variable v. */
79inline std::string sym_factor_tex(std::size_t v1, const SymFactor& f) {
80 const std::string xv = "x_{" + sym_int(v1) + "}";
81 if (f.type == "lin") return xv;
82 if (f.type == "dpspw")
83 return xv + "\\,g_{" + sym_int(f.station) + "," + sym_int(f.cls) + "}(\\mathbf{x})";
84 if (f.type == "ext1") {
85 if (f.others.empty()) return ""; // a single-phase source class is unit mass
86 std::string s = "\\bigl(1 - ";
87 for (std::size_t i = 0; i < f.others.size(); ++i) {
88 if (i) s += " - ";
89 s += "x_{" + sym_int(f.others[i]) + "}";
90 }
91 return s + "\\bigr)";
92 }
93 return xv + "\\,g_{" + sym_int(f.station) + "}(\\mathbf{x})";
94}
95
96/** `term_tex`: a term with positive coefficient c and factor fstr. */
97inline std::string sym_term_tex(double c, const std::string& fstr) {
98 if (fstr.empty()) return sym_fmtnum(c);
99 if (c == 1.0) return fstr;
100 return sym_fmtnum(c) + "\\," + fstr;
101}
102
103inline std::string sym_num_matrix(const Matrix<double>& A) {
104 std::string body;
105 for (std::size_t i = 0; i < A.rows(); ++i) {
106 if (i) body += " \\\\ ";
107 for (std::size_t j = 0; j < A.cols(); ++j) {
108 if (j) body += " & ";
109 body += sym_fmtnum(A(i, j));
110 }
111 }
112 const std::size_t mx = std::max(A.rows(), A.cols());
113 if (mx > 12) return "{\\scriptsize\\begin{bmatrix} " + body + " \\end{bmatrix}}";
114 return "\\begin{bmatrix} " + body + " \\end{bmatrix}";
115}
116
117inline std::string sym_num_vector(const std::vector<double>& v) {
118 std::string body;
119 for (std::size_t i = 0; i < v.size(); ++i) {
120 if (i) body += " & ";
121 body += sym_fmtnum(v[i]);
122 }
123 return "\\begin{pmatrix} " + body + " \\end{pmatrix}";
124}
125
126/**
127 * `build_terms`: T(s,v) is the constant coefficient of the term driven by
128 * variable v in the equation of state s, `varFactor` the factor of v, and
129 * `constTerm` the additive constant of state s.
130 */
131struct SymTerms {
132 Matrix<double> T;
133 std::vector<SymFactor> var_factor;
134 std::vector<bool> has_factor;
135 std::vector<double> const_term;
136};
137inline SymTerms sym_build_terms(const FluidSymSystem& sys) {
138 const std::size_t n = sys.nstates;
139 SymTerms t;
140 t.T = Matrix<double>(n, n, 0.0);
141 t.var_factor.assign(n, SymFactor());
142 t.has_factor.assign(n, false);
143 t.const_term.assign(n, 0.0);
144 if (sys.form == "W") {
145 for (std::size_t s = 0; s < n; ++s)
146 for (std::size_t v = 0; v < n; ++v)
147 t.T(s, v) = sys.is_source[v] ? 0.0 : sys.W(v, s); // W', Source theta is zero
148 for (std::size_t v = 0; v < n; ++v) {
149 if (sys.is_source[v]) continue;
150 const std::size_t i = sys.state_station[v];
151 SymFactor f;
152 f.station = i;
153 f.cls = sys.state_class[v];
154 f.type = std::isinf(sys.S[i - 1]) ? "lin" : sys.smoothing;
155 t.var_factor[v] = f;
156 t.has_factor[v] = true;
157 }
158 t.const_term = sys.alambda;
159 return t;
160 }
161 for (std::size_t e = 0; e < sys.nevents; ++e) {
162 const std::size_t v = sys.event_var[e];
163 for (std::size_t s = 0; s < n; ++s) t.T(s, v) += sys.J(s, e) * sys.coeff[e];
164 if (!t.has_factor[v]) {
165 t.var_factor[v] = sys.factor[e];
166 t.has_factor[v] = true;
167 }
168 }
169 return t;
170}
171
172/** `build_defs`: the station-level auxiliary quantities the factors refer to. */
173inline std::vector<std::string> sym_build_defs(const FluidSymSystem& sys, const SymTerms& t) {
174 const std::size_t n = sys.nstates, M = sys.station_names.size(),
175 K = sys.class_names.size();
176 std::vector<bool> need_n(M, false), need_nt(M, false), need_nh(M, false);
177 std::vector<std::string> gdef(M);
178 for (std::size_t v = 0; v < n; ++v) {
179 if (!t.has_factor[v]) continue;
180 const SymFactor& f = t.var_factor[v];
181 const std::size_t i = f.station, i0 = i - 1;
182 if (f.type == "lin") continue;
183 if (f.type == "min") {
184 need_n[i0] = true;
185 gdef[i0] = "g_{" + sym_int(i) + "}(\\mathbf{x}) &= \\frac{\\min(n_{" + sym_int(i) +
186 "}(\\mathbf{x}),\\, " + sym_fmtnum(sys.S[i0]) + ")}{n_{" + sym_int(i) +
187 "}(\\mathbf{x})}";
188 } else if (f.type == "pnorm") {
189 need_n[i0] = true;
190 const std::string ps = sym_fmtnum(i0 < sys.pstar.size() ? sys.pstar[i0] : 0.0);
191 gdef[i0] = "g_{" + sym_int(i) + "}(\\mathbf{x}) &= \\Bigl(1 + \\bigl(n_{" +
192 sym_int(i) + "}(\\mathbf{x})/" + sym_fmtnum(sys.S[i0]) + "\\bigr)^{" + ps +
193 "}\\Bigr)^{-1/" + ps + "}";
194 } else if (f.type == "dpsmin") {
195 need_n[i0] = true;
196 need_nt[i0] = true;
197 gdef[i0] = "g_{" + sym_int(i) + "}(\\mathbf{x}) &= \\frac{\\min(n_{" + sym_int(i) +
198 "}(\\mathbf{x}),\\, " + sym_fmtnum(sys.S[i0]) + ")}{\\tilde{n}_{" +
199 sym_int(i) + "}(\\mathbf{x})}";
200 } else if (f.type == "dpspw") {
201 need_n[i0] = true;
202 need_nt[i0] = true;
203 } else if (f.type == "fcfsw" || f.type == "fcfsws") {
204 need_n[i0] = true;
205 need_nh[i0] = true;
206 const std::string inner = (f.type == "fcfsw")
207 ? "\\min(n_{" + sym_int(i) + "}(\\mathbf{x}),\\, " +
208 sym_fmtnum(sys.S[i0]) + ")"
209 : "\\mathrm{softmin}\\bigl(n_{" + sym_int(i) +
210 "}(\\mathbf{x}),\\, " + sym_fmtnum(sys.S[i0]) +
211 "\\bigr)";
212 gdef[i0] = "g_{" + sym_int(i) + "}(\\mathbf{x}) &= \\frac{" + inner + "}{\\hat{n}_{" +
213 sym_int(i) + "}(\\mathbf{x})}";
214 }
215 }
216 std::vector<std::string> defs;
217 for (std::size_t i0 = 0; i0 < M; ++i0) {
218 if (!need_n[i0]) continue;
219 std::string s = "n_{" + sym_int(i0 + 1) + "}(\\mathbf{x}) &= ";
220 bool first = true;
221 for (std::size_t v = 0; v < n; ++v)
222 if (sys.state_station[v] == i0 + 1) {
223 if (!first) s += " + ";
224 s += "x_{" + sym_int(v + 1) + "}";
225 first = false;
226 }
227 defs.push_back(s + "\\\\");
228 }
229 for (std::size_t i0 = 0; i0 < M; ++i0) {
230 if (!need_nt[i0]) continue;
231 std::string s = "\\tilde{n}_{" + sym_int(i0 + 1) + "}(\\mathbf{x}) &= ";
232 bool firstpart = true;
233 for (std::size_t r = 0; r < K; ++r) {
234 std::string inner;
235 bool first = true;
236 for (std::size_t v = 0; v < n; ++v)
237 if (sys.state_station[v] == i0 + 1 && sys.state_class[v] == r + 1) {
238 if (!first) inner += " + ";
239 inner += "x_{" + sym_int(v + 1) + "}";
240 first = false;
241 }
242 if (inner.empty()) continue;
243 if (!firstpart) s += " + ";
244 s += sym_fmtnum(sys.dpsw(i0, r)) + "\\,(" + inner + ")";
245 firstpart = false;
246 }
247 defs.push_back(s + "\\\\");
248 }
249 for (std::size_t i0 = 0; i0 < M; ++i0) {
250 if (!need_nh[i0]) continue;
251 std::string s = "\\hat{n}_{" + sym_int(i0 + 1) + "}(\\mathbf{x}) &= ";
252 bool first = true;
253 for (std::size_t v = 0; v < n; ++v)
254 if (sys.state_station[v] == i0 + 1) {
255 if (!first) s += " + ";
256 s += sym_fmtnum(sys.fcfs_phase_w[v]) + "\\,x_{" + sym_int(v + 1) + "}";
257 first = false;
258 }
259 defs.push_back(s + "\\\\");
260 }
261 for (std::size_t i0 = 0; i0 < M; ++i0)
262 if (!gdef[i0].empty()) defs.push_back(gdef[i0] + "\\\\");
263 for (std::size_t v = 0; v < n; ++v) {
264 if (!t.has_factor[v] || t.var_factor[v].type != "dpspw") continue;
265 const std::size_t i = t.var_factor[v].station, r = t.var_factor[v].cls;
266 const std::string Si = sym_fmtnum(sys.S[i - 1]);
267 const std::string d = "g_{" + sym_int(i) + "," + sym_int(r) +
268 "}(\\mathbf{x}) &= \\begin{cases} 1 & n_{" + sym_int(i) +
269 "}(\\mathbf{x}) \\le " + Si + "\\\\ \\dfrac{" +
270 sym_fmtnum(sys.S[i - 1] * sys.dpsw(i - 1, r - 1)) + "}{\\tilde{n}_{" +
271 sym_int(i) + "}(\\mathbf{x})} & n_{" + sym_int(i) +
272 "}(\\mathbf{x}) > " + Si + " \\end{cases}\\\\";
273 if (std::find(defs.begin(), defs.end(), d) == defs.end()) defs.push_back(d);
274 }
275 if (sys.alpha > 0.0) {
276 bool any_ws = false;
277 for (std::size_t v = 0; v < n; ++v)
278 if (t.has_factor[v] && t.var_factor[v].type == "fcfsws") any_ws = true;
279 if (any_ws)
280 defs.push_back(
281 "\\mathrm{softmin}(a,b) &= \\frac{a\\,e^{-\\alpha a} + b\\,e^{-\\alpha "
282 "b}}{e^{-\\alpha a} + e^{-\\alpha b}}, \\qquad \\alpha = " +
283 sym_fmtnum(sys.alpha) + "\\\\");
284 }
285 if (!defs.empty()) {
286 std::string& last = defs.back();
287 if (last.size() >= 2 && last.compare(last.size() - 2, 2, "\\\\") == 0)
288 last.erase(last.size() - 2);
289 }
290 return defs;
291}
292
293/** `render_equation`: one align row of the scalar notation. */
294inline std::string sym_render_equation(const FluidSymSystem& sys, std::size_t s, const SymTerms& t,
295 bool is_last) {
296 std::vector<std::pair<double, std::string>> terms;
297 for (std::size_t v = 0; v < sys.nstates; ++v) {
298 const double c = t.T(s, v);
299 if (c == 0.0) continue;
300 terms.push_back(std::make_pair(c, sym_factor_tex(v + 1, t.var_factor[v])));
301 }
302 if (t.const_term[s] != 0.0)
303 terms.push_back(std::make_pair(t.const_term[s], std::string()));
304 std::string rhs;
305 if (terms.empty()) {
306 rhs = "0";
307 } else {
308 for (std::size_t k = 0; k < terms.size(); ++k) {
309 const double c = terms[k].first;
310 const std::string body = sym_term_tex(std::fabs(c), terms[k].second);
311 if (k == 0)
312 rhs += (c < 0.0) ? ("-" + body) : body;
313 else
314 rhs += (c < 0.0) ? (" - " + body) : (" + " + body);
315 if ((k + 1) % 4 == 0 && k + 1 < terms.size()) rhs += "\\nonumber\\\\\n&\\quad ";
316 }
317 }
318 return "\\frac{\\mathrm{d}x_{" + sym_int(s + 1) + "}}{\\mathrm{d}t} &= " + rhs +
319 (is_last ? "" : "\\\\");
320}
321
322} // namespace detail
323
324/**
325 * Render the fluid ODE system of `sn` as a LaTeX document.
326 *
327 * @param notation "scalar" (one expanded ODE per state) or "matrix"
328 * @param model_name the name printed in the header, `model.getName` in the reference
329 * @param sn the refreshed network struct
330 * @param opt fluid options, which fix the method whose ODEs are exported
331 */
332template <class T>
334 const std::string& notation = "scalar",
335 const std::string& model_name = "model") {
336 using namespace detail;
337 if (notation != "scalar" && notation != "matrix")
338 throw InputError("fluid_export_odes: unknown notation '" + notation +
339 "'; valid notations are scalar and matrix");
340
341 std::string m = opt.method;
342 if (m.compare(0, 6, "fluid.") == 0) m = m.substr(6);
343 const bool use_pnorm = (m == "pnorm");
344 std::vector<double> init = opt.init_sol;
345 if (init.empty()) init = detail::fluid_default_initsol(sn, fluid_layout(sn));
346 const FluidSymSystem sys =
347 fluid_symodes(sn, opt.method, use_pnorm ? opt.pstar : 0.0, init);
348 const std::size_t n = sys.nstates;
349 const SymTerms terms = sym_build_terms(sys);
350
351 std::vector<std::string> L;
352 // ---- machine-readable header -----------------------------------------
353 L.push_back("% Mean-field fluid ODE system exported by LINE SolverFLD");
354 L.push_back("% model: " + model_name);
355 L.push_back("% method: " + sys.method);
356 L.push_back(sys.form == "W" ? "% form: dx/dt = W^T*theta(x) + lambda"
357 : "% form: dx/dt = J*r(x)");
358 L.push_back("% notation: " + notation);
359 L.push_back("% nstates: " + sym_int(n));
360 if (sys.form == "J") L.push_back("% nevents: " + sym_int(sys.nevents));
361 for (std::size_t s = 0; s < n; ++s)
362 L.push_back("% STATE " + sym_int(s + 1) + " station=" +
363 sys.station_names[sys.state_station[s] - 1] + " class=" +
364 sys.class_names[sys.state_class[s] - 1] + " phase=" +
365 sym_int(sys.state_phase[s]));
366 if (sys.form == "J")
367 for (std::size_t e = 0; e < sys.nevents; ++e) {
368 char buf[64];
369 std::snprintf(buf, sizeof(buf), "%.15g", sys.coeff[e]);
370 L.push_back("% EVENT " + sym_int(e + 1) + " var=" + sym_int(sys.event_var[e] + 1) +
371 " type=" + sys.factor[e].type + " coeff=" + std::string(buf));
372 }
373
374 // ---- preamble ---------------------------------------------------------
375 L.push_back("\\documentclass{article}");
376 L.push_back("\\usepackage{amsmath}");
377 L.push_back("\\usepackage[margin=2.5cm]{geometry}");
378 L.push_back("\\allowdisplaybreaks");
379 L.push_back("\\setcounter{MaxMatrixCols}{500}");
380 L.push_back("\\begin{document}");
381 L.push_back("\\section*{Mean-field fluid ODE system}");
382 L.push_back("\\noindent Model: \\texttt{" + sym_texesc(model_name) +
383 "}. Solver: \\texttt{SolverFLD}, method \\texttt{" + sym_texesc(sys.method) +
384 "}, " + notation + " notation.");
385 if (sys.form == "W")
386 L.push_back("The system has " + sym_int(n) +
387 " state variables and reads $\\frac{\\mathrm{d}\\mathbf{x}}{\\mathrm{d}t} = "
388 "W^{\\top}\\theta(\\mathbf{x}) + \\boldsymbol{\\lambda}$.");
389 else
390 L.push_back("The system has " + sym_int(n) + " state variables and " +
391 sym_int(sys.nevents) +
392 " events and reads $\\frac{\\mathrm{d}\\mathbf{x}}{\\mathrm{d}t} = "
393 "J\\,r(\\mathbf{x})$.");
394
395 // ---- state legend -----------------------------------------------------
396 L.push_back("\\subsection*{State variables}");
397 L.push_back(
398 "Each state variable $x_{s}$ is the mean number of jobs of a class in a service phase at "
399 "a station:");
400 L.push_back("\\begin{center}");
401 const std::size_t chunk = 48;
402 for (std::size_t s0 = 0; s0 < n; s0 += chunk) {
403 const std::size_t s1 = std::min(n, s0 + chunk);
404 L.push_back("\\begin{tabular}{rlll}");
405 L.push_back("\\hline");
406 L.push_back("$s$ & station & class & phase\\\\");
407 L.push_back("\\hline");
408 for (std::size_t s = s0; s < s1; ++s)
409 L.push_back(sym_int(s + 1) + " & \\texttt{" +
410 sym_texesc(sys.station_names[sys.state_station[s] - 1]) + "} & \\texttt{" +
411 sym_texesc(sys.class_names[sys.state_class[s] - 1]) + "} & " +
412 sym_int(sys.state_phase[s]) + "\\\\");
413 L.push_back("\\hline");
414 L.push_back("\\end{tabular}");
415 if (s1 < n) L.push_back("\\par\\medskip");
416 }
417 L.push_back("\\end{center}");
418
419 std::vector<std::size_t> used;
420 for (std::size_t s = 0; s < n; ++s)
421 if (std::find(used.begin(), used.end(), sys.state_station[s]) == used.end())
422 used.push_back(sys.state_station[s]);
423 std::sort(used.begin(), used.end());
424 L.push_back("\\begin{center}");
425 L.push_back("\\begin{tabular}{rlll}");
426 L.push_back("\\hline");
427 L.push_back("$i$ & station & scheduling & $S_{i}$\\\\");
428 L.push_back("\\hline");
429 for (std::size_t i : used)
430 L.push_back(sym_int(i) + " & \\texttt{" + sym_texesc(sys.station_names[i - 1]) + "} & " +
431 sym_texesc(sys.sched_names[i - 1]) + " & $" + sym_fmtnum(sys.S[i - 1]) +
432 "$\\\\");
433 L.push_back("\\hline");
434 L.push_back("\\end{tabular}");
435 L.push_back("\\end{center}");
436
437 // ---- definitions ------------------------------------------------------
438 const std::vector<std::string> defs = sym_build_defs(sys, terms);
439 if (!defs.empty()) {
440 L.push_back("\\subsection*{Definitions}");
441 L.push_back("\\begin{align*}");
442 for (const std::string& d : defs) L.push_back(d);
443 L.push_back("\\end{align*}");
444 }
445
446 // ---- the system itself ------------------------------------------------
447 if (notation == "scalar") {
448 L.push_back("\\subsection*{ODE system (scalar notation)}");
449 L.push_back("\\begin{align}");
450 for (std::size_t s = 0; s < n; ++s)
451 L.push_back(sym_render_equation(sys, s, terms, s + 1 == n));
452 L.push_back("\\end{align}");
453 } else {
454 L.push_back("\\subsection*{ODE system (matrix notation)}");
455 if (sys.form == "W") {
456 bool have_lambda = false;
457 for (double v : sys.alambda)
458 if (v != 0.0) have_lambda = true;
459 L.push_back("\\begin{equation}");
460 L.push_back(have_lambda
461 ? "\\frac{\\mathrm{d}\\mathbf{x}}{\\mathrm{d}t} = "
462 "W^{\\top}\\,\\theta(\\mathbf{x}) + \\boldsymbol{\\lambda}"
463 : "\\frac{\\mathrm{d}\\mathbf{x}}{\\mathrm{d}t} = "
464 "W^{\\top}\\,\\theta(\\mathbf{x})");
465 L.push_back("\\end{equation}");
466 L.push_back("with $\\theta_{s}(\\mathbf{x})$ given componentwise by");
467 L.push_back("\\begin{equation*}");
468 {
469 std::string rows;
470 for (std::size_t v = 0; v < n; ++v) {
471 if (v) rows += " \\\\ ";
472 rows += sys.is_source[v] ? std::string("0")
473 : sym_factor_tex(v + 1, terms.var_factor[v]);
474 }
475 L.push_back("\\theta(\\mathbf{x}) = \\begin{bmatrix} " + rows +
476 " \\end{bmatrix}");
477 }
478 L.push_back("\\end{equation*}");
479 L.push_back("and");
480 L.push_back("\\begin{equation*}");
481 {
482 Matrix<double> Wt(n, n, 0.0);
483 for (std::size_t i = 0; i < n; ++i)
484 for (std::size_t j = 0; j < n; ++j) Wt(i, j) = sys.W(j, i);
485 L.push_back("W^{\\top} = " + sym_num_matrix(Wt));
486 }
487 L.push_back("\\end{equation*}");
488 if (have_lambda) {
489 L.push_back("\\begin{equation*}");
490 L.push_back("\\boldsymbol{\\lambda} = " + sym_num_vector(sys.alambda) +
491 "^{\\top}");
492 L.push_back("\\end{equation*}");
493 }
494 } else {
495 L.push_back("\\begin{equation}");
496 L.push_back("\\frac{\\mathrm{d}\\mathbf{x}}{\\mathrm{d}t} = J\\,r(\\mathbf{x})");
497 L.push_back("\\end{equation}");
498 L.push_back("with stoichiometry matrix");
499 L.push_back("\\begin{equation*}");
500 L.push_back("J = " + sym_num_matrix(sys.J));
501 L.push_back("\\end{equation*}");
502 L.push_back("and event rate functions");
503 L.push_back("\\begin{align*}");
504 for (std::size_t e = 0; e < sys.nevents; ++e)
505 L.push_back("r_{" + sym_int(e + 1) + "}(\\mathbf{x}) &= " +
506 sym_term_tex(sys.coeff[e],
507 sym_factor_tex(sys.event_var[e] + 1, sys.factor[e])) +
508 (e + 1 < sys.nevents ? "\\\\" : ""));
509 L.push_back("\\end{align*}");
510 }
511 }
512
513 // ---- initial condition and remarks ------------------------------------
514 if (!sys.x0.empty()) {
515 L.push_back("\\subsection*{Initial condition}");
516 L.push_back("\\begin{equation*}");
517 L.push_back("\\mathbf{x}(0) = " + sym_num_vector(sys.x0) + "^{\\top}");
518 L.push_back("\\end{equation*}");
519 }
520 L.push_back("\\subsection*{Remarks}");
521 L.push_back("\\begin{itemize}");
522 L.push_back(
523 "\\item For each station $i$, $n_{i}(\\mathbf{x})$ denotes the total mass at the station "
524 "and $S_{i}$ the number of servers (infinite-server stations use the closed job "
525 "population, $\\infty$ denotes infinity).");
526 L.push_back(
527 "\\item The numerical solver regularizes vanishing denominators with a small positive "
528 "constant; these regularizations are omitted here.");
529 if (sys.form == "J") {
530 bool fcfs_fac = false, dps_fac = false;
531 for (const SymFactor& f : sys.factor) {
532 if (f.type == "fcfsw" || f.type == "fcfsws") fcfs_fac = true;
533 if (f.type == "dpsmin") dps_fac = true;
534 }
535 if (fcfs_fac)
536 L.push_back(
537 "\\item At FCFS stations, the mean phase residence times $w_{u} = "
538 "-1/[D_{0}]_{kk}$ weight the backlog $\\hat{n}_{i}$; the factors $w_{u}$ of the "
539 "departing phases are folded into the rate coefficients.");
540 if (dps_fac)
541 L.push_back(
542 "\\item At DPS stations, weights are normalized to sum to one and the weight "
543 "$w_{ir}$ of the departing class is folded into the rate coefficient; the class "
544 "shares $w_{ir}x/\\tilde{n}_{i}$ divide the station capacity $\\min(n_{i},S_{i})$, "
545 "so they sum to one whenever the station is busy.");
546 }
547 {
548 bool any_fcfs = false;
549 for (std::size_t s = 0; s < n; ++s)
550 if (sys.sched[sys.state_station[s] - 1] == lang::SchedStrategy::FCFS) any_fcfs = true;
551 if (any_fcfs && (sys.method == "matrix" || sys.method == "closing"))
552 L.push_back(
553 "\\item For FCFS stations with non-exponential service, the solver may "
554 "iteratively re-fit the service distributions (non-exponential approximation); "
555 "the exported system uses the nominal model parameters.");
556 }
557 L.push_back("\\end{itemize}");
558 L.push_back("\\end{document}");
559
560 std::string tex;
561 for (std::size_t i = 0; i < L.size(); ++i) {
562 tex += L[i];
563 tex += "\n";
564 }
565 return tex;
566}
567
568} // namespace fluid
569} // namespace line
570
571#endif // LINE_SOLVERS_FLUID_FLUID_EXPORT_ODES_H
InputError(const std::string &what)
Definition error.h:39
A network plus its refreshed NetworkStruct.
The exception types the port throws.
A symbolic description of the fluid ODE system: a port of solver_fluid_symodes.m, which is what @@Sol...
FluidLayout fluid_layout(const qn::NetworkStruct< T > &sn)
Port of the layout half of solver_fluid_odes.m.
Definition fluid_odes.h:282
std::string fluid_export_odes(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, const std::string &notation="scalar", const std::string &model_name="model")
Render the fluid ODE system of sn as a LaTeX document.
FluidSymSystem fluid_symodes(const qn::NetworkStruct< T > &sn, const std::string &method_in, double pstar, const std::vector< double > &init_sol)
Build the symbolic system of sn under opt.method.
A queueing network and its refreshed NetworkStruct.
SolverFluid: the closing method, a port of solver_fluid.m, solver_fluid_iteration....
Controls, defaulting to SolverOptions('Fluid') in the reference.
The symbolic system, in whichever of the two forms the method implies.
std::vector< double > coeff
std::vector< std::size_t > event_var
0-based state index driving the event
std::vector< double > x0
std::vector< std::size_t > state_station
std::string form
"W" or "J"
std::vector< std::size_t > state_phase
1-based
std::vector< std::string > station_names
std::vector< double > alambda
std::vector< bool > is_source
std::vector< std::size_t > state_class
std::vector< std::string > sched_names
std::vector< std::string > class_names
std::string method
resolved: default becomes matrix
std::vector< lang::SchedStrategy > sched
std::vector< double > S
servers per station, infinite already substituted
std::vector< SymFactor > factor
The state-dependent factor attached to one event's driving variable.
std::string type
lin, min, pnorm, ext1, dpsmin, dpspw, fcfsw, fcfsws