5#ifndef LINE_SOLVERS_FLUID_FLUID_SYMODES_H
6#define LINE_SOLVERS_FLUID_FLUID_SYMODES_H
80 std::vector<lang::SchedStrategy>
sched;
83 std::vector<double>
S;
84 std::vector<double>
x0;
109 std::size_t c, std::size_t ki,
double& coeff,
const FluidLayout& L,
111 const std::vector<double>& fcfs_w, std::size_t var) {
115 if (method ==
"closing") {
121 for (std::size_t k = 1; k < L.
kic[i][c]; ++k)
135 default: f.
type =
"lin";
break;
144 f.
type = (method ==
"softmin") ?
"fcfsws" :
"fcfsw";
145 coeff *= fcfs_w[var];
148 default: f.
type =
"lin";
break;
170 double pstar,
const std::vector<double>& init_sol) {
171 const std::size_t M =
sn.nstations, K =
sn.nclasses;
172 std::string method = method_in;
173 if (method.compare(0, 6,
"fluid.") == 0) method = method.substr(6);
176 if (method ==
"default" || method ==
"matrix" || method ==
"pnorm") {
178 sys.
method = (method ==
"default") ?
"matrix" : method;
179 }
else if (method ==
"closing" || method ==
"statedep" || method ==
"softmin") {
184 "fluid_symodes: the symbolic ODE export is unsupported for method '" + method_in +
185 "'; supported are default, matrix, pnorm, closing, statedep and softmin");
191 for (std::size_t i = 0; i < M; ++i) {
193 sys.
sched[i] =
sn.stations[i].sched;
197 for (std::size_t r = 0; r < K; ++r) sys.
class_names[r] =
sn.classes[r].name;
199 double closed_pop = 0.0;
200 for (std::size_t r = 0; r < K; ++r)
201 if (std::isfinite(
sn.classes[r].population)) closed_pop +=
sn.classes[r].population;
202 sys.
S.assign(M, 1.0);
203 for (std::size_t i = 0; i < M; ++i) {
204 const double c =
sn.stations[i].nservers;
205 sys.
S[i] = std::isfinite(c) ? c : closed_pop;
210 if (sys.
form ==
"W") {
220 sys.
smoothing = (pstar > 0.0) ?
"pnorm" :
"min";
221 sys.
pstar.assign(M, pstar);
226 for (std::size_t i = 0; i < M; ++i)
227 for (std::size_t r = 0; r < K; ++r) {
228 const std::size_t p = L.
kic[i][r];
229 if (p == 0)
continue;
230 for (std::size_t k = 0; k < p; ++k) {
237 throw NumericError(
"fluid_symodes: the W-form state metadata does not match the drift");
242 const bool sd = (method ==
"statedep" || method ==
"softmin");
244 for (std::size_t i = 0; i < M; ++i)
247 "' method does not support open models, so their ODE "
248 "system cannot be exported; use 'matrix' or 'closing'");
254 for (std::size_t i = 0; i < M; ++i)
255 for (std::size_t c = 0; c < K; ++c)
256 for (std::size_t k = 0; k < L.
kic[i][c]; ++k) {
263 for (std::size_t i = 0; i < M; ++i) {
266 for (std::size_t r = 0; r < K; ++r) {
267 const double w = (r <
sn.stations[i].schedparam.size())
274 for (std::size_t r = 0; r < K; ++r) sys.
dpsw(i, r) /= tot;
279 for (std::size_t i = 0; i < M; ++i) {
281 for (std::size_t c = 0; c < K; ++c) {
282 if (!L.
enabled[i][c])
continue;
283 for (std::size_t k = 0; k < L.
kic[i][c]; ++k)
290 std::vector<std::vector<std::vector<double>>> mu(M, std::vector<std::vector<double>>(K));
291 std::vector<std::vector<std::vector<double>>> phi(M, std::vector<std::vector<double>>(K));
292 std::vector<std::vector<std::vector<double>>> pie(M, std::vector<std::vector<double>>(K));
293 for (std::size_t i = 0; i < M; ++i)
294 for (std::size_t r = 0; r < K; ++r) {
296 pie[i][r] = std::vector<double>{1.0};
299 detail::fluid_mu_phi(
sn.service[i][r], mu[i][r], phi[i][r]);
300 pie[i][r] = detail::fluid_pie(
sn.service[i][r]);
302 const std::size_t NS =
sn.nof_stateful();
303 const bool have_rt =
sn.rt.rows() == NS * K;
304 std::vector<std::size_t> sf(M, 0);
305 for (std::size_t i = 0; i < M; ++i) sf[i] =
sn.stateful_of_station(i + 1) - 1;
306 const auto route = [&](std::size_t i, std::size_t c, std::size_t j, std::size_t l) ->
double {
307 if (!have_rt)
return 0.0;
311 std::vector<std::vector<double>> cols;
313 for (std::size_t i = 0; i < M; ++i)
314 for (std::size_t c = 0; c < K; ++c) {
315 if (!L.
enabled[i][c])
continue;
316 for (std::size_t j = 0; j < M; ++j)
317 for (std::size_t l = 0; l < K; ++l) {
318 if (!(route(i, c, j, l) > 0.0))
continue;
319 for (std::size_t ki = 0; ki < L.
kic[i][c]; ++ki)
320 for (std::size_t kj = 0; kj < L.
kic[j][l]; ++kj) {
326 if (!detail::sym_handled_sched(
sn.stations[i].sched))
continue;
328 const double pj = kj < pie[j][l].size() ? pie[j][l][kj] : 0.0;
329 double base = phi[i][c][ki] * mu[i][c][ki] * route(i, c, j, l) * pj;
330 if (!(base > 0.0))
continue;
331 std::vector<double> col(L.
nstates, 0.0);
332 col[L.
qidx[i][c] + ki] -= 1.0;
333 col[L.
qidx[j][l] + kj] += 1.0;
334 const std::size_t var = L.
qidx[i][c] + ki;
336 detail::sym_factor(method,
sn.stations[i].sched, i, c, ki, base, L,
339 sys.
coeff.push_back(base);
346 for (std::size_t i = 0; i < M; ++i)
347 for (std::size_t c = 0; c < K; ++c) {
348 if (!L.
enabled[i][c])
continue;
349 if (sd && !detail::sym_handled_sched(
sn.stations[i].sched))
continue;
350 for (std::size_t ki = 0; ki + 1 < L.
kic[i][c]; ++ki)
351 for (std::size_t kp = 0; kp < L.
kic[i][c]; ++kp) {
352 if (kp == ki)
continue;
354 if (!(base > 0.0))
continue;
355 std::vector<double> col(L.
nstates, 0.0);
356 col[L.
qidx[i][c] + ki] = -1.0;
357 col[L.
qidx[i][c] + kp] = 1.0;
358 const std::size_t var = L.
qidx[i][c] + ki;
360 detail::sym_factor(method,
sn.stations[i].sched, i, c, ki, base, L, sys.
S,
363 sys.
coeff.push_back(base);
371 for (std::size_t e = 0; e < sys.
nevents; ++e)
372 for (std::size_t s = 0; s < L.
nstates; ++s) sys.
J(s, e) = cols[e][s];
373 if (method ==
"softmin") sys.
alpha = 20.0;
380 std::vector<std::string>
rhs;
387inline std::string sym_state_index(std::size_t v) {
389 std::snprintf(buf,
sizeof(buf),
"%llu",
static_cast<unsigned long long>(v));
390 return std::string(buf);
394inline std::string sym_plain_num(
double v) {
396 if (v == std::floor(v) && std::fabs(v) < 1e15) {
397 std::snprintf(buf,
sizeof(buf),
"%lld",
static_cast<long long>(v));
398 return std::string(buf);
400 std::snprintf(buf,
sizeof(buf),
"%.17g", v);
401 return std::string(buf);
405inline std::string sym_join_sum(
const std::vector<std::string>& parts,
const std::string& offset) {
406 if (parts.empty())
return "(" + offset +
")";
408 for (std::size_t i = 0; i < parts.size(); ++i) {
409 if (i) body +=
" + ";
412 return (offset ==
"0") ?
"(" + body +
")" :
"(" + offset +
" + " + body +
")";
416inline std::string sym_station_sum(
const FluidSymSystem& sys, std::size_t station1,
417 const std::vector<std::string>& vars,
const std::string& offset) {
418 std::vector<std::string> parts;
419 for (std::size_t s = 0; s < sys.nstates; ++s)
420 if (sys.state_station[s] == station1) parts.push_back(vars[s]);
421 return sym_join_sum(parts, offset);
426inline std::string sym_phase_weighted_station_sum(
const FluidSymSystem& sys, std::size_t station1,
427 const std::vector<std::string>& vars,
428 const std::string& offset) {
429 std::vector<std::string> parts;
430 for (std::size_t s = 0; s < sys.nstates; ++s) {
431 if (sys.state_station[s] != station1)
continue;
432 const double w = sys.fcfs_phase_w[s];
433 if (w == 0.0)
continue;
434 parts.push_back(
"(" + sym_plain_num(w) +
")*" + vars[s]);
436 return sym_join_sum(parts, offset);
447inline std::string sym_softmin_expr(
const std::string& x,
const std::string& y,
double alpha) {
448 const std::string a = sym_plain_num(alpha);
449 return "((" + x +
")*exp(-(" + a +
")*(" + x +
")) + (" + y +
")*exp(-(" + a +
")*(" + y +
450 ")))/(exp(-(" + a +
")*(" + x +
")) + exp(-(" + a +
")*(" + y +
")))";
475 for (std::size_t s = 0; s < sys.
nstates; ++s)
476 out.
vars[s] =
"x" + detail::sym_state_index(s + 1);
482 if (sys.
form ==
"W") {
487 "fluid_symbolic_drift: the drift of this method scales rates by min(n_i, S_i), "
488 "which is not differentiable at n_i = S_i, so it has no Jacobian there. Set pstar "
489 "to use the p-norm smoothing, or use the 'softmin' method");
490 std::vector<std::string> theta(sys.
nstates,
"0");
491 for (std::size_t s = 0; s < sys.
nstates; ++s) {
494 const double S = sys.
S[i - 1];
495 const double p = sys.
pstar[i - 1];
499 theta[s] = out.
vars[s];
502 if (S <= 0.0 || p <= 0.0) {
503 theta[s] = out.
vars[s];
506 const std::string ni = detail::sym_station_sum(sys, i, out.
vars, eps0);
507 theta[s] = out.
vars[s] +
"/(1 + (" + ni +
"/" + detail::sym_plain_num(S) +
")^" +
508 detail::sym_plain_num(p) +
")^(1/" + detail::sym_plain_num(p) +
")";
510 for (std::size_t s = 0; s < sys.
nstates; ++s) {
511 std::vector<std::string> terms;
512 for (std::size_t t = 0; t < sys.
nstates; ++t) {
513 const double w = sys.
W(t, s);
514 if (w == 0.0 || theta[t] ==
"0")
continue;
515 terms.push_back(
"(" + detail::sym_plain_num(w) +
")*(" + theta[t] +
")");
517 if (sys.
alambda[s] != 0.0) terms.push_back(detail::sym_plain_num(sys.
alambda[s]));
518 if (terms.empty())
continue;
520 for (std::size_t i = 0; i < terms.size(); ++i) {
521 if (i) body +=
" + ";
535 std::vector<std::string> rate(sys.
nevents,
"0");
536 for (std::size_t e = 0; e < sys.
nevents; ++e) {
540 if (f.
type ==
"lin") {
542 }
else if (f.
type ==
"ext1") {
547 for (std::size_t k = 0; k < f.
others.size(); ++k) {
548 if (k) body +=
" + ";
551 factor =
"(1 - (" + body +
"))";
553 }
else if (f.
type ==
"fcfsws") {
554 const std::string ni = detail::sym_station_sum(sys, f.
station, out.
vars,
"0");
555 const std::string nhat =
556 detail::sym_phase_weighted_station_sum(sys, f.
station, out.
vars, eps0);
558 detail::sym_softmin_expr(ni, detail::sym_plain_num(sys.
S[f.
station - 1]),
563 "fluid_symbolic_drift: event " + detail::sym_state_index(e + 1) +
564 " scales its rate by the non-smooth factor '" + f.
type +
565 "', which has no derivative where the regime switches, so the system has no "
566 "Jacobian. Use the 'softmin' method, or the p-norm smoothing of the 'matrix' "
569 rate[e] =
"(" + detail::sym_plain_num(sys.
coeff[e]) +
")*(" + factor +
")";
571 for (std::size_t s = 0; s < sys.
nstates; ++s) {
572 std::vector<std::string> terms;
573 for (std::size_t e = 0; e < sys.
nevents; ++e) {
574 const double j = sys.
J(s, e);
575 if (j == 0.0)
continue;
576 terms.push_back(
"(" + detail::sym_plain_num(j) +
")*(" + rate[e] +
")");
578 if (terms.empty())
continue;
580 for (std::size_t i = 0; i < terms.size(); ++i) {
581 if (i) body +=
" + ";
592 std::vector<std::string>
rhs;
593 std::vector<std::vector<std::string> >
J;
599inline bool sym_in_station(
const FluidSymSystem& sys, std::size_t station1, std::size_t m) {
616inline std::string sym_softmin_deriv(
const std::string& a,
const std::string& b,
double alpha) {
617 const std::string al = sym_plain_num(alpha);
618 const std::string ea =
"exp(-(" + al +
")*(" + a +
"))";
619 const std::string eb =
"exp(-(" + al +
")*(" + b +
"))";
620 const std::string D =
"(" + ea +
" + " + eb +
")";
621 const std::string sm = sym_softmin_expr(a, b, alpha);
622 return "(" + ea +
"*(1 - (" + al +
")*(" + a +
") + (" + al +
")*(" + sm +
")))/" + D;
626inline std::string sym_mul(
const std::string& u,
const std::string& v) {
627 if (u ==
"0" || v ==
"0")
return "0";
628 if (u ==
"1")
return v;
629 if (v ==
"1")
return u;
630 return "(" + u +
")*(" + v +
")";
634inline std::string sym_sum(
const std::vector<std::string>& parts) {
636 for (std::size_t i = 0; i < parts.size(); ++i) {
637 if (parts[i] ==
"0")
continue;
638 if (!body.empty()) body +=
" + ";
641 return body.empty() ? std::string(
"0") : body;
676 const std::size_t n = sys.
nstates;
677 out.
J.assign(n, std::vector<std::string>(n,
"0"));
680 if (sys.
form ==
"W") {
684 std::vector<std::vector<std::string> > dtheta(n, std::vector<std::string>(n,
"0"));
685 for (std::size_t s = 0; s < n; ++s) {
688 const double S = sys.
S[i - 1], p = sys.
pstar[i - 1];
689 if ((s < sys.
is_inf.size() && sys.
is_inf[s]) || S <= 0.0 || p <= 0.0) {
693 const std::string ni = detail::sym_station_sum(sys, i, out.
vars, eps0);
694 const std::string Sn = detail::sym_plain_num(S), pn = detail::sym_plain_num(p);
695 const std::string base =
"(1 + ((" + ni +
")/" + Sn +
")^" + pn +
")";
696 const std::string Q = base +
"^(1/" + pn +
")";
697 const std::string dQ = base +
"^(1/" + pn +
" - 1)*((" + ni +
")/" + Sn +
")^(" + pn +
699 for (std::size_t m = 0; m < n; ++m) {
700 std::vector<std::string> parts;
701 if (m == s) parts.push_back(
"1/(" + Q +
")");
702 if (detail::sym_in_station(sys, i, m))
703 parts.push_back(
"-(" + out.
vars[s] +
")*(" + dQ +
")/(" + Q +
")^2");
704 dtheta[s][m] = detail::sym_sum(parts);
707 for (std::size_t s = 0; s < n; ++s)
708 for (std::size_t m = 0; m < n; ++m) {
709 std::vector<std::string> parts;
710 for (std::size_t t = 0; t < n; ++t) {
711 const double w = sys.
W(t, s);
712 if (w == 0.0 || dtheta[t][m] ==
"0")
continue;
713 parts.push_back(detail::sym_mul(detail::sym_plain_num(w), dtheta[t][m]));
715 out.
J[s][m] = detail::sym_sum(parts);
723 std::vector<std::vector<std::string> > drate(sys.
nevents, std::vector<std::string>(n,
"0"));
724 for (std::size_t e = 0; e < sys.
nevents; ++e) {
727 const std::string cf = detail::sym_plain_num(sys.
coeff[e]);
728 if (f.
type ==
"lin") {
730 }
else if (f.
type ==
"ext1") {
731 for (std::size_t k = 0; k < f.
others.size(); ++k)
732 drate[e][f.
others[k] - 1] =
"-(" + cf +
")";
733 }
else if (f.
type ==
"fcfsws") {
739 const std::string ni = detail::sym_station_sum(sys, f.
station, out.
vars,
"0");
740 const std::string nhat =
741 detail::sym_phase_weighted_station_sum(sys, f.
station, out.
vars, eps0);
742 const std::string Sn = detail::sym_plain_num(sys.
S[f.
station - 1]);
743 const std::string sm = detail::sym_softmin_expr(ni, Sn, sys.
alpha);
744 const std::string dsm = detail::sym_softmin_deriv(ni, Sn, sys.
alpha);
745 for (std::size_t m = 0; m < n; ++m) {
746 std::vector<std::string> parts;
747 if (m == v) parts.push_back(
"(" + sm +
")/(" + nhat +
")");
748 if (detail::sym_in_station(sys, f.
station, m)) {
749 parts.push_back(
"(" + out.
vars[v] +
")*(" + dsm +
")/(" + nhat +
")");
752 parts.push_back(
"-(" + out.
vars[v] +
")*(" + sm +
")*(" +
753 detail::sym_plain_num(w) +
")/(" + nhat +
")^2");
755 const std::string body = detail::sym_sum(parts);
756 drate[e][m] = body ==
"0" ? std::string(
"0") : detail::sym_mul(cf, body);
760 "fluid_symbolic_jacobian: event " + detail::sym_state_index(e + 1) +
761 " scales its rate by the non-smooth factor '" + f.
type +
762 "', which has no derivative where the regime switches, so the system has no "
763 "Jacobian. Use the 'softmin' method, or the p-norm smoothing of the 'matrix' "
768 for (std::size_t s = 0; s < n; ++s)
769 for (std::size_t m = 0; m < n; ++m) {
770 std::vector<std::string> parts;
771 for (std::size_t e = 0; e < sys.
nevents; ++e) {
772 const double j = sys.
J(s, e);
773 if (j == 0.0 || drate[e][m] ==
"0")
continue;
774 parts.push_back(detail::sym_mul(detail::sym_plain_num(j), drate[e][m]));
776 out.
J[s][m] = detail::sym_sum(parts);
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
The exception types the port throws.
The matrix fluid method: a port of solver_fluid_matrix.m, the formulation of Ruuskanen,...
The fluid drift: a port of solver_fluid_odes.m and the ode_jumps_new / ode_rate_base / ode_rates_clos...
Dense matrix and non-owning view.
FluidLayout fluid_layout(const qn::NetworkStruct< T > &sn)
Port of the layout half of solver_fluid_odes.m.
FluidMatrixSystem fluid_matrix_system(const qn::NetworkStruct< T > &sn, const std::vector< double > &init_sol, double pstar)
Assemble the matrix-form drift of sn.
FluidSymbolicDrift fluid_symbolic_drift(const FluidSymSystem &sys)
Port of @@SolverFLD/getSymbolicDrift: the right-hand side of the mean-field ODE system as expression ...
FluidSymbolicJacobian fluid_symbolic_jacobian(const FluidSymSystem &sys)
Port of @@SolverFLD/getJacobian: d f_i / d x_j of the mean-field drift, as expression strings.
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.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
const char * sched_to_text(SchedStrategy s)
A queueing network and its refreshed NetworkStruct.
Where each (station, class) block sits in the state vector.
std::vector< std::vector< std::size_t > > qidx
0-based first index of (i,r)
std::size_t nstates
length of the state vector
std::vector< std::vector< bool > > enabled
whether (i,r) is served at all
std::vector< std::vector< std::size_t > > kic
phases held by (i,r); 0 when disabled
The assembled matrix-form drift and the maps that read metrics off it.
std::vector< double > alambda
(n) external arrivals per state
Matrix< double > W
(n x n) drift generator
std::vector< bool > is_inf
(n) state belongs to an INF station
std::vector< double > x0
(n) initial state
std::vector< bool > is_source
(n) state belongs to an EXT station
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::vector< double > fcfs_phase_w
std::string form
"W" or "J"
std::vector< std::size_t > state_phase
1-based
std::vector< double > pstar
std::string smoothing
"min" or "pnorm"
std::vector< std::string > station_names
std::vector< double > alambda
std::vector< bool > is_inf
(n) state belongs to an INF station
double alpha
softmin sharpness, set only for that method
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 drift as expression strings, one per state variable, plus their names.
std::vector< std::string > vars
std::vector< std::string > rhs
What @@SolverFLD/getJacobian returns: d f_i / d x_j as expression strings.
std::vector< std::string > vars
the state variable names
std::vector< std::string > rhs
the drift, one per variable
std::vector< std::vector< std::string > > J
J[i][j] = d f_i / d x_j.
The state-dependent factor attached to one event's driving variable.
std::vector< std::size_t > others
ext1: the other phases, 1-based state indices
std::string type
lin, min, pnorm, ext1, dpsmin, dpspw, fcfsw, fcfsws
std::size_t station
1-based
static constexpr double FineTol