5#ifndef LINE_SOLVERS_FLUID_FLUID_EXPORT_ODES_H
6#define LINE_SOLVERS_FLUID_FLUID_EXPORT_ODES_H
51inline std::string sym_fmtnum(
double v) {
52 if (std::isinf(v))
return v > 0 ?
"\\infty" :
"-\\infty";
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);
58 std::snprintf(buf,
sizeof(buf),
"%.8g", v);
59 return std::string(buf);
63inline std::string sym_texesc(
const std::string& s) {
66 if (ch ==
'_' || ch ==
'%' || ch ==
'&' || ch ==
'#') out.push_back(
'\\');
72inline std::string sym_int(std::size_t v) {
74 std::snprintf(buf,
sizeof(buf),
"%llu",
static_cast<unsigned long long>(v));
75 return std::string(buf);
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 "";
86 std::string s =
"\\bigl(1 - ";
87 for (std::size_t i = 0; i < f.others.size(); ++i) {
89 s +=
"x_{" + sym_int(f.others[i]) +
"}";
93 return xv +
"\\,g_{" + sym_int(f.station) +
"}(\\mathbf{x})";
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;
103inline std::string sym_num_matrix(
const Matrix<double>& A) {
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));
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}";
117inline std::string sym_num_vector(
const std::vector<double>& v) {
119 for (std::size_t i = 0; i < v.size(); ++i) {
120 if (i) body +=
" & ";
121 body += sym_fmtnum(v[i]);
123 return "\\begin{pmatrix} " + body +
" \\end{pmatrix}";
133 std::vector<SymFactor> var_factor;
134 std::vector<bool> has_factor;
135 std::vector<double> const_term;
137inline SymTerms sym_build_terms(
const FluidSymSystem& sys) {
138 const std::size_t n = sys.nstates;
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);
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];
153 f.cls = sys.state_class[v];
154 f.type = std::isinf(sys.S[i - 1]) ?
"lin" : sys.smoothing;
156 t.has_factor[v] =
true;
158 t.const_term = sys.alambda;
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;
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") {
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) +
188 }
else if (f.type ==
"pnorm") {
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") {
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") {
203 }
else if (f.type ==
"fcfsw" || f.type ==
"fcfsws") {
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]) +
212 gdef[i0] =
"g_{" + sym_int(i) +
"}(\\mathbf{x}) &= \\frac{" + inner +
"}{\\hat{n}_{" +
213 sym_int(i) +
"}(\\mathbf{x})}";
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}) &= ";
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) +
"}";
227 defs.push_back(s +
"\\\\");
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) {
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) +
"}";
242 if (inner.empty())
continue;
243 if (!firstpart) s +=
" + ";
244 s += sym_fmtnum(sys.dpsw(i0, r)) +
"\\,(" + inner +
")";
247 defs.push_back(s +
"\\\\");
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}) &= ";
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) +
"}";
259 defs.push_back(s +
"\\\\");
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);
275 if (sys.alpha > 0.0) {
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;
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) +
"\\\\");
286 std::string& last = defs.back();
287 if (last.size() >= 2 && last.compare(last.size() - 2, 2,
"\\\\") == 0)
288 last.erase(last.size() - 2);
294inline std::string sym_render_equation(
const FluidSymSystem& sys, std::size_t s,
const SymTerms& t,
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])));
302 if (t.const_term[s] != 0.0)
303 terms.push_back(std::make_pair(t.const_term[s], std::string()));
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);
312 rhs += (c < 0.0) ? (
"-" + body) : body;
314 rhs += (c < 0.0) ? (
" - " + body) : (
" + " + body);
315 if ((k + 1) % 4 == 0 && k + 1 < terms.size()) rhs +=
"\\nonumber\\\\\n&\\quad ";
318 return "\\frac{\\mathrm{d}x_{" + sym_int(s + 1) +
"}}{\\mathrm{d}t} &= " + rhs +
319 (is_last ?
"" :
"\\\\");
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");
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));
348 const std::size_t n = sys.
nstates;
349 const SymTerms terms = sym_build_terms(sys);
351 std::vector<std::string> L;
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=" +
367 for (std::size_t e = 0; e < sys.
nevents; ++e) {
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));
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.");
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}$.");
390 L.push_back(
"The system has " + sym_int(n) +
" state variables and " +
392 " events and reads $\\frac{\\mathrm{d}\\mathbf{x}}{\\mathrm{d}t} = "
393 "J\\,r(\\mathbf{x})$.");
396 L.push_back(
"\\subsection*{State variables}");
398 "Each state variable $x_{s}$ is the mean number of jobs of a class in a service phase at "
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{" +
413 L.push_back(
"\\hline");
414 L.push_back(
"\\end{tabular}");
415 if (s1 < n) L.push_back(
"\\par\\medskip");
417 L.push_back(
"\\end{center}");
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())
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]) +
433 L.push_back(
"\\hline");
434 L.push_back(
"\\end{tabular}");
435 L.push_back(
"\\end{center}");
438 const std::vector<std::string> defs = sym_build_defs(sys, terms);
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*}");
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}");
454 L.push_back(
"\\subsection*{ODE system (matrix notation)}");
455 if (sys.
form ==
"W") {
456 bool have_lambda =
false;
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*}");
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]);
475 L.push_back(
"\\theta(\\mathbf{x}) = \\begin{bmatrix} " + rows +
478 L.push_back(
"\\end{equation*}");
480 L.push_back(
"\\begin{equation*}");
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));
487 L.push_back(
"\\end{equation*}");
489 L.push_back(
"\\begin{equation*}");
490 L.push_back(
"\\boldsymbol{\\lambda} = " + sym_num_vector(sys.
alambda) +
492 L.push_back(
"\\end{equation*}");
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],
508 (e + 1 < sys.
nevents ?
"\\\\" :
""));
509 L.push_back(
"\\end{align*}");
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*}");
520 L.push_back(
"\\subsection*{Remarks}");
521 L.push_back(
"\\begin{itemize}");
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).");
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;
532 if (f.
type ==
"fcfsw" || f.
type ==
"fcfsws") fcfs_fac =
true;
533 if (f.
type ==
"dpsmin") dps_fac =
true;
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.");
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.");
548 bool any_fcfs =
false;
549 for (std::size_t s = 0; s < n; ++s)
551 if (any_fcfs && (sys.
method ==
"matrix" || sys.
method ==
"closing"))
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.");
557 L.push_back(
"\\end{itemize}");
558 L.push_back(
"\\end{document}");
561 for (std::size_t i = 0; i < L.size(); ++i) {
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.
std::string fluid_export_odes(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, const std::string ¬ation="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< 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