5#ifndef LINE_SOLVERS_MVA_SOLVER_MVA_H
6#define LINE_SOLVERS_MVA_SOLVER_MVA_H
92 std::size_t lcfspr_ist) {
105 std::vector<T> alpha(R, one), beta(R, one);
106 std::vector<int> N(R, 0);
107 for (std::size_t r = 0; r < R; ++r) {
108 const double nr = L.
classes[r].population;
109 if (!(nr > 0.0))
continue;
110 N[r] =
static_cast<int>(std::llround(nr));
111 const T mu_l = L.
rates(lcfs_ist - 1, r);
112 const T mu_p = L.
rates(lcfspr_ist - 1, r);
114 throw InputError(
"solver_mva_lcfsqn: invalid service rate at the LCFS station for class " +
115 std::to_string(r + 1));
117 throw InputError(
"solver_mva_lcfsqn: invalid service rate at the LCFS-PR station for "
118 "class " + std::to_string(r + 1));
119 alpha[r] = T(one / mu_l);
120 beta[r] = T(one / mu_p);
130 out.
C.assign(R, zero);
131 out.
X.assign(R, zero);
132 for (std::size_t r = 0; r < R; ++r) {
133 out.
Q(lcfs_ist - 1, r) = res.
Q(0, r);
134 out.
Q(lcfspr_ist - 1, r) = res.
Q(1, r);
135 out.
U(lcfs_ist - 1, r) = res.
U(0, r);
136 out.
U(lcfspr_ist - 1, r) = res.
U(1, r);
137 if (N[r] <= 0)
continue;
138 out.
X[r] = res.
T_[r];
139 out.
Tp(lcfs_ist - 1, r) = res.
T_[r];
140 out.
Tp(lcfspr_ist - 1, r) = res.
T_[r];
142 for (std::size_t i : {lcfs_ist - 1, lcfspr_ist - 1})
143 for (std::size_t r = 0; r < R; ++r)
144 if (out.
Tp(i, r) > zero) out.
R(i, r) = T(out.
Q(i, r) / out.
Tp(i, r));
145 for (std::size_t r = 0; r < R; ++r)
146 if (N[r] > 0) out.
C[r] = T(out.
R(lcfs_ist - 1, r) + out.
R(lcfspr_ist - 1, r));
152 out.
lG = std::numeric_limits<double>::quiet_NaN();
166 std::vector<std::size_t> lcfs, lcfspr;
167 for (std::size_t i = 0; i < M; ++i) {
168 if (L.
stations[i].sched == SchedStrategy::LCFS) lcfs.push_back(i + 1);
169 if (L.
stations[i].sched == SchedStrategy::LCFSPR) lcfspr.push_back(i + 1);
171 if (!lcfs.empty() && !lcfspr.empty()) {
172 if (lcfs.size() != 1 || lcfspr.size() != 1)
174 "solver_mva: LCFS MVA requires exactly one LCFS and one LCFS-PR station");
175 for (std::size_t c = 0; c < C; ++c)
176 if (std::isinf(d.
Nchain[c]))
181 for (std::size_t ist : {lcfs[0], lcfspr[0]}) {
183 for (std::size_t r = 0; r < Rn; ++r)
184 if (L.
rt.rows() == S * Rn && L.
rt(sf * Rn + r, sf * Rn + r) > zero)
186 "solver_mva: LCFS MVA does not support self-loops at stations");
191 throw UnsupportedError(
ErrorKind{
"SchedStrategy_LCFS"},
"solver_mva: LCFS scheduling requires a paired LCFS-PR station");
200 if (!L.
sched_is_product_form()) why =
"a station's scheduling is outside BCMP (INF, PS, FCFS, LCFS, LCFS-PR)";
203 else if (!L.
fj.empty()) why =
"the model has a fork-join";
204 else if (L.
has_sd_routing()) why =
"a node routes by state-dependent rules (RROBIN, WRROBIN, JSQ, SQ, SDR)";
205 else if (L.
has_blocking()) why =
"a station has a finite buffer or the model has a capacity region";
206 else why =
"an FCFS station has non-exponential service";
210 std::vector<std::size_t> infSET, qSET;
211 for (std::size_t i = 0; i < M; ++i) {
213 case SchedStrategy::EXT:
break;
214 case SchedStrategy::INF: infSET.push_back(i);
break;
215 case SchedStrategy::PS:
216 case SchedStrategy::LCFSPR:
217 case SchedStrategy::FCFS:
218 case SchedStrategy::SIRO: qSET.push_back(i);
break;
226 Matrix<T> Lq(qSET.size(), C, zero), Zd(infSET.size(), C, zero);
227 for (std::size_t a = 0; a < qSET.size(); ++a)
228 for (std::size_t c = 0; c < C; ++c)
230 for (std::size_t a = 0; a < infSET.size(); ++a)
231 for (std::size_t c = 0; c < C; ++c)
232 Zd(a, c) = T(d.
STchain(infSET[a], c) * d.
Vchain(infSET[a], c));
235 std::vector<T> lambda(C, zero);
236 std::vector<int> N(C, 0);
237 for (std::size_t c = 0; c < C; ++c) {
238 if (!std::isfinite(d.
Nchain[c])) {
244 N[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
247 for (std::size_t a = 0; a < qSET.size(); ++a) {
248 const double s = L.
stations[qSET[a]].nservers;
249 if (!std::isfinite(s))
251 "is not inf-scheduled");
252 S.push_back(
static_cast<int>(std::llround(s)));
265 Matrix<T> Qchain(M, C, zero), Wchain(M, C, zero), Tchain(M, C, zero), Uchain(M, C, zero);
266 std::vector<T> Xchain = pf.
XN;
267 for (std::size_t a = 0; a < qSET.size(); ++a)
268 for (std::size_t c = 0; c < C; ++c) Qchain(qSET[a], c) = pf.
QN(a, c);
269 for (std::size_t a = 0; a < infSET.size(); ++a)
270 for (std::size_t c = 0; c < C; ++c)
271 Qchain(infSET[a], c) = T(Xchain[c] * d.
STchain(infSET[a], c) * d.
Vchain(infSET[a], c));
273 std::vector<std::size_t> rset;
274 for (std::size_t c = 0; c < C; ++c)
275 if (d.
Nchain[c] != 0.0) rset.push_back(c);
277 for (std::size_t c : rset) {
278 for (std::size_t i : infSET) Wchain(i, c) = d.
STchain(i, c);
279 for (std::size_t i : qSET) {
280 if (std::isinf(L.
stations[i].nservers)) {
281 Wchain(i, c) = d.
STchain(i, c);
282 }
else if (d.
Vchain(i, c) == zero || Xchain[c] == zero) {
285 Wchain(i, c) = T(Qchain(i, c) / (Xchain[c] * d.
Vchain(i, c)));
290 std::vector<T> Cc(C, zero);
291 for (std::size_t c : rset) {
293 for (std::size_t i = 0; i < M; ++i) sw += Wchain(i, c);
298 for (std::size_t i = 0; i < M; ++i) cyc += d.
Vchain(i, c) * Wchain(i, c);
301 if (cyc != zero && std::isfinite(d.
Nchain[c]))
304 for (std::size_t i = 0; i < M; ++i) {
305 Qchain(i, c) = T(Xchain[c] * d.
Vchain(i, c) * Wchain(i, c));
306 Tchain(i, c) = T(Xchain[c] * d.
Vchain(i, c));
309 for (std::size_t i = 0; i < M; ++i)
310 for (std::size_t c : rset) {
312 Uchain(i, c) = std::isinf(L.
stations[i].nservers)
318 for (std::size_t i = 0; i < M; ++i) {
319 if (!(L.
stations[i].sched == SchedStrategy::FCFS || L.
stations[i].sched == SchedStrategy::PS))
322 for (std::size_t c = 0; c < C; ++c) usum += Uchain(i, c);
325 for (std::size_t c = 0; c < C; ++c) den += d.
Vchain(i, c) * d.
STchain(i, c) * Xchain[c];
326 if (den == zero)
continue;
328 for (std::size_t c = 0; c < C; ++c) {
330 Uchain(i, c) = T(cap * d.
Vchain(i, c) * d.
STchain(i, c) * Xchain[c] / den);
335 for (std::size_t i = 0; i < M; ++i)
336 for (std::size_t c = 0; c < C; ++c)
337 if (Tchain(i, c) != zero) Rchain(i, c) = T(Qchain(i, c) / Tchain(i, c));
338 for (std::size_t c = 0; c < C; ++c) {
339 if (d.
Nchain[c] != 0.0)
continue;
341 for (std::size_t i = 0; i < M; ++i) {
385std::vector<T> ms_term(
const qn::NetworkStruct<T>& L,
const std::vector<qn::SchedStrategy>& sched,
386 const std::vector<T>& narrival,
const std::string& multiserver) {
390 std::vector<T> r(M, one);
391 if (multiserver ==
"suri")
return r;
392 if (!(multiserver ==
"default" || multiserver ==
"seidmann" || multiserver ==
"softmin"))
394 "' is not implemented in this port");
395 for (std::size_t i = 0; i < M; ++i) {
396 const double c = L.
stations[i].nservers;
399 if (sc == SchedStrategy::INF)
continue;
400 const bool fcfs_family =
401 sc == SchedStrategy::FCFS || sc == SchedStrategy::SIRO || sc == SchedStrategy::LCFSPR;
402 if (multiserver ==
"seidmann" || (multiserver ==
"default" && fcfs_family)) {
403 r[i] = std::isinf(c) ? one : T(one / num_traits<T>::from_double(c));
410 if constexpr (num_traits<T>::has_transcendental) {
412 const double alpha = 20.0;
413 const double n = num_traits<T>::to_double(narrival[i]);
414 const double lo = std::min(n, c), hi = std::max(n, c);
415 const double gap = hi - lo;
416 const double w = alpha * gap > 745.0 ? 0.0 : std::exp(-alpha * gap);
417 const double sm = lo + gap * w / (1.0 + w);
418 r[i] = num_traits<T>::from_double(sm > 0.0 ? 1.0 / sm : 1.0 / std::min(n, c));
421 "solver_amvald: the soft-minimum multiserver term evaluates exp, which exact "
422 "arithmetic has no representation for; use the double or real backend, the "
423 "seidmann or suri multiserver rule, or a layer whose queueing stations are "
424 "FCFS-family or infinite-server");
439std::vector<T> suri_factor(
const qn::NetworkStruct<T>& L,
const Matrix<T>& Uin,
double tol) {
440 const std::size_t M = L.nstations, K = Uin.cols();
441 const T one = num_traits<T>::from_int(1), zero = num_traits<T>::from_int(0);
442 std::vector<T> f(M, one);
443 if constexpr (!num_traits<T>::has_transcendental) {
445 "solver_amvald: the Suri multiserver factor is a real power of the utilization, "
446 "which exact arithmetic has no representation for; use the double or real backend");
448 const double alpha = 4.464, beta = 0.676;
449 for (std::size_t i = 0; i < M; ++i) {
450 const double c = L.stations[i].nservers;
451 if (std::isinf(c) || c == 0.0) {
455 if (!(c > 1.0))
continue;
457 for (std::size_t s = 0; s < K; ++s) usum += Uin(i, s);
458 double rho = num_traits<T>::to_double(usum) / c;
459 if (rho > 1.0 - tol) rho = 1.0 - tol;
460 f[i] = rho > 0.0 ? num_traits<T>::from_double(
461 std::pow(rho, alpha * (std::pow(c, beta) - 1.0)) / c)
462 : T(one / num_traits<T>::from_double(c));
476 const MvaOptions&
opt,
const std::string& method,
bool& converged,
491 "solver_amvald: the approximate MVA is an iterative fixed point whose iterates "
492 "accumulate the product of every denominator seen so far, so exact rational "
493 "arithmetic grows without bound and the solve does not terminate; rerun with "
494 "--arith double or --arith real, or give the model a product form (BCMP type 1 "
495 "asks FCFS service to be exponential) so the exact recursion applies");
501 for (std::size_t i = 0; i < M; ++i) {
503 if (!(s == SchedStrategy::INF || s == SchedStrategy::PS || s == SchedStrategy::FCFS ||
504 s == SchedStrategy::SIRO || s == SchedStrategy::LCFSPR || s == SchedStrategy::EXT ||
505 s == SchedStrategy::DPS || s == SchedStrategy::HOL ||
506 s == SchedStrategy::FCFSPRPRIO))
508 " scheduling is not implemented in this port");
519 bool any_queue =
false;
520 for (std::size_t i = 0; i < M && !any_queue; ++i)
521 if (L.
stations[i].sched != SchedStrategy::INF && L.
stations[i].sched != SchedStrategy::EXT)
523 const bool linmethod = (method ==
"lin" || method ==
"qdlin");
525 !(linmethod || method ==
"qd" || method ==
"default" || method ==
"bs" ||
526 method ==
"egflin" || method ==
"gflin" || method ==
"qli" || method ==
"fli" ||
527 method ==
"aql" || method ==
"qdaql" || method ==
"tay" || method ==
"priomva"))
532 for (std::size_t c = 0; c < K; ++c)
536 std::vector<T> deltaclass(K, one);
537 for (std::size_t c = 0; c < K; ++c)
542 std::vector<std::size_t> nnz, ccl, ocl;
543 std::vector<bool> isopen(K,
false);
544 for (std::size_t c = 0; c < K; ++c) {
545 if (!(d.
Nchain[c] > 0.0))
continue;
547 if (std::isfinite(d.
Nchain[c])) ccl.push_back(c);
548 else { ocl.push_back(c); isopen[c] =
true; }
558 std::vector<std::size_t> ocl_exo, ocl_aux;
559 for (std::size_t c : ocl) {
562 allaux = !L.
inchain[c].empty();
563 for (std::size_t r : L.
inchain[c])
566 if (allaux) ocl_aux.push_back(c);
else ocl_exo.push_back(c);
571 if (init_sol.rows() == M && init_sol.cols() == K) {
575 for (std::size_t c = 0; c < K; ++c) {
576 if (!std::isfinite(d.
Nchain[c]))
continue;
577 for (std::size_t i = 0; i < M; ++i)
583 std::vector<T> Xchain(K, zero);
584 for (std::size_t c = 0; c < K; ++c) {
588 Xchain[c] = st > zero ? T(one / st) : zero;
592 for (std::size_t i = 0; i < M; ++i) s += d.
STchain(i, c);
593 if (s != zero) Xchain[c] = T(one / s);
595 Matrix<T> Uchain(M, K, zero), Tchain(M, K, zero), Wchain(M, K, zero), STeff = d.
STchain;
596 for (std::size_t i = 0; i < M; ++i)
597 for (std::size_t c : nnz) {
599 Uchain(i, c) = std::isinf(L.
stations[i].nservers)
605 std::vector<Matrix<T>> gamma(K,
Matrix<T>(M, K, zero));
609 const int max_totiter = std::min(
opt.iter_max, 10000);
611 const int inner_cap =
static_cast<int>(std::sqrt(
static_cast<double>(
opt.iter_max)));
615 std::vector<int> chainprio(K, 0);
616 for (std::size_t c = 0; c < K && c < L.
inchain.size(); ++c) {
618 for (std::size_t k1 : L.
inchain[c]) {
619 const int p = L.
classes[k1 - 1].prio;
620 chainprio[c] = first ? p : std::min(chainprio[c], p);
625 const bool has_prio = [&] {
628 for (std::size_t c = 0; c < K; ++c) {
629 const int p = chainprio[c];
630 if (first) { lo = hi = p; first =
false; }
631 lo = std::min(lo, p);
632 hi = std::max(hi, p);
636 std::vector<std::vector<std::size_t>> ehprio(K), hprio(K), eprio(K), lprio(K);
637 for (std::size_t r : nnz) {
640 for (std::size_t s : nnz) {
641 ehprio[r].push_back(s);
642 eprio[r].push_back(s);
646 for (std::size_t s : nnz) {
647 const int pr = chainprio[r], ps = chainprio[s];
648 if (ps <= pr) ehprio[r].push_back(s);
649 if (ps < pr) hprio[r].push_back(s);
650 if (ps == pr) eprio[r].push_back(s);
651 if (ps > pr) lprio[r].push_back(s);
656 std::size_t smax = 0;
657 for (
const auto& st : L.
stations) smax = std::max(smax, st.lldscaling.size());
661 for (std::size_t i = 0; i < M; ++i)
662 for (std::size_t k = 0; k < L.
stations[i].lldscaling.size(); ++k)
663 lldscaling(i, k) = L.
stations[i].lldscaling[k];
665 std::vector<lang::CdScaling<T>> cdscaling;
671 if (!cdscaling.empty())
672 for (std::size_t i = 0; i < M; ++i) cdscaling[i] = L.
stations[i].cdscaling;
677 std::vector<lang::CdScaling<T>> jdscaling;
683 if (!jdscaling.empty())
684 for (std::size_t i = 0; i < M; ++i) jdscaling[i] = L.
stations[i].jdscaling;
685 const bool has_scaling = (smax > 0) || !cdscaling.empty() || !jdscaling.empty();
693 std::vector<SchedStrategy> schedfw(M);
694 for (std::size_t i = 0; i < M; ++i) {
696 const double cs = L.
stations[i].nservers;
697 if (!std::isfinite(cs))
continue;
698 bool any =
false, open =
false;
700 for (std::size_t c = 0; c < K; ++c) {
704 if (std::isfinite(d.
Nchain[c])) nvis += d.
Nchain[c];
else open =
true;
706 if (!any || open || nvis > cs)
continue;
708 if (!(s == SchedStrategy::FCFS || s == SchedStrategy::SIRO || s == SchedStrategy::LCFS ||
709 s == SchedStrategy::LCFSPR || s == SchedStrategy::PS || s == SchedStrategy::HOL ||
710 s == SchedStrategy::FCFSPRPRIO))
713 for (
const T& v : L.
stations[i].lldscaling)
715 if (!hasLD && !L.
stations[i].cdscaling && !L.
stations[i].jdscaling) schedfw[i] = SchedStrategy::INF;
719 std::vector<std::vector<T>> tau(K, std::vector<T>(K, zero));
731 auto forward = [&](
const Matrix<T>& Qin,
const std::vector<T>& Xin,
const Matrix<T>& Uin,
734 for (std::size_t c = 0; c < K; ++c)
735 if (std::isfinite(Nc[c])) Ntl += Nc[c];
737 std::vector<T> dcl(K, one);
738 for (std::size_t c = 0; c < K; ++c)
739 if (std::isfinite(Nc[c]) && Nc[c] > 0.0)
742 std::vector<T> interpTot(M, zero);
743 Matrix<T> selfArvl(M, K, zero), totArvl(M, K, zero), totArvlOpen(M, K, zero);
744 for (std::size_t i = 0; i < M; ++i) {
746 for (std::size_t c : nnz) s += Qin(i, c);
759 T sc = zero, so = zero;
760 for (std::size_t c : ccl) sc += Qin(i, c);
761 for (std::size_t c : ocl_aux) sc += Qin(i, c);
762 for (std::size_t c : ocl_exo) so += Qin(i, c);
763 interpTot[i] = T(deltaL * sc + so);
764 const bool hol = schedfw[i] == SchedStrategy::HOL;
765 for (std::size_t c : nnz) {
766 selfArvl(i, c) = T(dcl[c] * Qin(i, c));
768 T eh = zero, ehx = zero;
769 for (std::size_t s2 : ehprio[c]) {
771 if (s2 != c) ehx += Qin(i, s2);
773 totArvlOpen(i, c) = eh;
774 totArvl(i, c) = T(dcl[c] * Qin(i, c) + ehx);
776 totArvlOpen(i, c) = s;
777 totArvl(i, c) = T(dcl[c] * Qin(i, c) + s - Qin(i, c));
783 std::vector<T> gmean(M, zero);
788 for (std::size_t i = 0; i < M; ++i) {
790 for (std::size_t s : ccl) {
792 for (std::size_t r : ccl)
794 acc += T(deltaL * gs);
796 gmean[i] = T(acc / nccl);
801 for (std::size_t i = 0; i < M; ++i)
802 for (std::size_t r : ccl)
805 for (std::size_t i = 0; i < M; ++i) gmean[i] = sc;
808 std::vector<T> narrival(M, zero);
809 for (std::size_t i = 0; i < M; ++i) narrival[i] = T(one + interpTot[i] + gmean[i]);
810 const std::vector<T> msterm = detail::ms_term(L, schedfw, narrival,
opt.multiserver);
811 std::vector<T> suri(M, one);
812 if (
opt.multiserver ==
"suri") suri = detail::suri_factor(L, Uin,
opt.tol);
820 "solver_amvald: load-dependent scaling interpolates a rate lattice with a "
821 "soft minimum, which exact arithmetic has no representation for; use the "
822 "double or real backend");
824 if (linmethod && !ccl.empty() && !nnz.empty()) {
825 for (std::size_t r : nnz) {
826 std::vector<T> arg(M, zero);
827 for (std::size_t i = 0; i < M; ++i) {
829 for (std::size_t s : ccl)
831 gcorr -= gamma[r](i, r);
832 arg[i] = T(one + interpTot[i] + gcorr);
834 const std::vector<T> v =
836 for (std::size_t i = 0; i < M; ++i) lldterm(i, r) = v[i];
839 std::vector<T> arg(M, zero);
840 for (std::size_t i = 0; i < M; ++i) arg[i] = T(one + interpTot[i]);
841 const std::vector<T> v =
843 for (std::size_t i = 0; i < M; ++i)
844 for (std::size_t c = 0; c < K; ++c) lldterm(i, c) = v[i];
851 if (!cdscaling.empty()) {
852 for (std::size_t r : nnz) {
854 const bool closed = std::isfinite(Nc[r]);
855 for (std::size_t i = 0; i < M; ++i)
856 for (std::size_t c = 0; c < K; ++c)
857 arg(i, c) = T(one + (closed ? selfArvl(i, c) : Qin(i, c)));
858 if (closed && linmethod)
859 for (std::size_t i = 0; i < M; ++i) {
862 for (std::size_t c = 0; c < K; ++c) arg(i, c) = T(arg(i, c) + gself);
865 for (std::size_t i = 0; i < M; ++i) cdterm(i, r) = v[i];
881 if (!jdscaling.empty()) {
882 for (std::size_t r : nnz) {
883 const bool closed = std::isfinite(Nc[r]);
885 for (std::size_t i = 0; i < M; ++i) {
886 for (std::size_t c : nnz) arg(i, c) = Qin(i, c);
887 T self = T(one + (closed ? selfArvl(i, r) : Qin(i, r)));
888 if (closed && linmethod)
893 for (std::size_t i = 0; i < M; ++i) jdterm(i, r) = v[i];
898 for (std::size_t c : nnz)
899 for (std::size_t i = 0; i < M; ++i)
901 T(d.
STchain(i, c) * lldterm(i, c) * msterm[i] * cdterm(i, c) * jdterm(i, c));
905 if (method ==
"qli" || method ==
"fli") {
906 const bool qli = (method ==
"qli");
907 for (std::size_t i = 0; i < M; ++i) {
908 const bool hol = schedfw[i] == SchedStrategy::HOL;
909 for (std::size_t r : nnz) {
912 for (std::size_t s2 : ehprio[r]) tot += Qin(i, s2);
914 for (std::size_t s2 : nnz) tot += Qin(i, s2);
917 totArvl(i, r) = T(tot - Qin(i, r));
920 const T num = T(STeffOut(i, r) * (one + tot - Qin(i, r)));
922 for (std::size_t m = 0; m < M; ++m)
923 if (schedfw[m] == SchedStrategy::INF) den += STeffOut(m, r);
924 for (std::size_t m = 0; m < M; ++m) {
926 if (schedfw[m] == SchedStrategy::HOL) {
927 for (std::size_t s2 : ehprio[r]) totm += Qin(m, s2);
929 for (std::size_t s2 : nnz) totm += Qin(m, s2);
931 den += T(STeffOut(m, r) * (one + totm - Qin(m, r)));
933 if (den == zero)
continue;
936 totArvl(i, r) = T(tot - f * (Qin(i, r) - num / den));
939 totArvl(i, r) = T(tot - f * Qin(i, r) + num / den);
950 if (!ILchain.
empty()) {
951 for (std::size_t i = 0; i < M; ++i)
952 for (std::size_t c : nnz) {
954 for (std::size_t c2 : nnz)
955 if (c2 != c) ilq += ILchain(c, c2) * Qin(i, c2);
957 T adj = T(totArvl(i, c) - ilq);
958 if (adj < selfArvl(i, c)) adj = selfArvl(i, c);
965 for (std::size_t c : nnz) {
966 for (std::size_t i = 0; i < M; ++i) {
969 if (sc == SchedStrategy::EXT)
continue;
970 if (sc == SchedStrategy::INF) {
971 Wout(i, c) = STeffOut(i, c);
974 const double cs = L.
stations[i].nservers;
976 if (sc == SchedStrategy::PS) {
979 for (std::size_t s : ccl)
981 corr -= gamma[c](i, c);
985 if (
opt.multiserver ==
"suri") {
988 const T Lm = isopen[c] ? totArvlOpen(i, c)
989 : T(totArvl(i, c) + corr);
990 Wout(i, c) = T(STeffOut(i, c) * (one + Lm * suri[i]));
993 if (
opt.multiserver ==
"seidmann")
997 Wout(i, c) = T(Wout(i, c) + STeffOut(i, c) * (one + totArvlOpen(i, c)));
1005 Wout(i, c) = T(Wout(i, c) + STeffOut(i, c) * (one + totArvl(i, c) + corr));
1010 if (sc == SchedStrategy::DPS) {
1013 const std::vector<T>& w = L.
stations[i].schedparam;
1016 "solver_amvald: the DPS station '" + L.
stations[i].name +
1017 "' has no per-class weights");
1019 if (cs > 1.0 && std::isfinite(cs))
1021 acc += T(STeffOut(i, c) * (one + selfArvl(i, c)));
1022 for (std::size_t s : nnz) {
1023 if (s == c)
continue;
1025 acc += T(STeffOut(i, c) * Qin(i, s));
1027 acc += T(STeffOut(i, c) * Qin(i, s) * w[s] / w[c]);
1033 const bool fcfs_family = (sc == SchedStrategy::FCFS || sc == SchedStrategy::SIRO ||
1034 sc == SchedStrategy::LCFSPR);
1035 if (!fcfs_family && sc != SchedStrategy::HOL && sc != SchedStrategy::FCFSPRPRIO)
1037 " scheduling is not implemented in this port");
1038 if (STeffOut(i, c) <= zero)
continue;
1040 const std::vector<T>& Xarv = Xref.empty() ? Xin : Xref;
1053 if (sc == SchedStrategy::FCFSPRPRIO) {
1054 if (cs > 1.0 && std::isfinite(cs))
1056 "solver_amvald: FCFSPRPRIO with more than one server. The "
1057 "preemptive-resume priority arm (priomva) is implemented for "
1058 "single-server stations only; use SolverCTMC or SolverSSA for "
1059 "multiserver PRS.");
1063 for (std::size_t h : hprio[c])
1064 uh_prs += T(d.
Vchain(i, h) * STeffOut(i, h) * T(Xarv[h] + tau[c][h]));
1066 vps = std::max(
opt.tol, std::min(vps, 1.0 -
opt.tol));
1070 T queued = T(STeffOut(i, c) * (isopen[c] ? Qin(i, c) : selfArvl(i, c)));
1071 for (std::size_t s : ehprio[c])
1072 if (s != c) queued += T(STeffOut(i, s) * Qin(i, s));
1074 Wout(i, c) = T((STeffOut(i, c) + queued) / ps_prs);
1078 auto Ur = [&](std::size_t k, std::size_t s) -> T {
1079 if (!(Xin[s] > zero))
return Uin(k, s);
1080 return T(Uin(k, s) / Xin[s] * (Xarv[s] + tau[c][s]));
1083 const bool per_server = cs > 1.0 && std::isfinite(cs) &&
1084 (
opt.multiserver ==
"softmin" ||
opt.multiserver ==
"suri");
1085 auto STps = [&](std::size_t s) -> T {
1086 if (!per_server)
return STeffOut(i, s);
1091 auto prio_scaling = [&](std::size_t r) -> T {
1092 if (sc != SchedStrategy::HOL)
return one;
1094 for (std::size_t h : hprio[r]) {
1097 const T xh =
opt.np_priority ==
"shadow" ? Xarv[h] : T(Xarv[h] + tau[r][h]);
1098 uh += T(d.
Vchain(i, h) * STps(h) * xh);
1101 v = std::max(
opt.tol, std::min(v, 1.0 -
opt.tol));
1104 const T ps_r = prio_scaling(c);
1111 auto hol_ehprio_backlog = [&](
const std::vector<T>& Bkw) -> T {
1113 for (std::size_t s : ehprio[c])
1114 if (s != c) acc += T(STeffOut(i, s) * Qin(i, s) * Bkw[s]);
1117 auto hol_np_residual = [&](
const std::vector<T>& Bkw) -> T {
1118 if (
opt.highvar ==
"hvmva")
return zero;
1120 for (std::size_t s : lprio[c])
1121 acc += T(d.
Vchain(i, s) * STps(s) * (Xarv[s] + tau[c][s]) * STps(s) * Bkw[s]);
1126 std::vector<T> Bk(K, one);
1127 if (cs > 1.0 && std::isfinite(cs)) {
1131 const bool hol_softmin = sc == SchedStrategy::HOL &&
opt.multiserver ==
"softmin";
1133 for (std::size_t s : nnz) {
1134 const T dr = hol_softmin ? dcl[s] : ((s == c) ? dcl[c] : one);
1135 load += dr * Xin[s] * d.
Vchain(i, s) * STeffOut(i, s);
1142 const unsigned e =
static_cast<unsigned>(std::llround(cs) - (hol_softmin ? 0 : 1));
1145 for (std::size_t s : nnz)
1152 if (
opt.highvar ==
"hvmva") {
1154 for (std::size_t s : ccl) usum += Ur(i, s);
1155 w = T(STeffOut(i, c) * (one - usum));
1156 for (std::size_t s : ccl)
1157 w += T(STeffOut(i, s) / prio_scaling(s) * Ur(i, s) *
1159 }
else if (
opt.highvar !=
"default") {
1161 "' is not implemented");
1165 if (sc == SchedStrategy::HOL) {
1167 const std::vector<T>
ones(K, one);
1168 w += T((STeffOut(i, c) * (isopen[c] ? Qin(i, c) : selfArvl(i, c)) +
1169 hol_ehprio_backlog(
ones) + hol_np_residual(
ones)) / ps_r);
1171 w += STeffOut(i, c) * (isopen[c] ? Qin(i, c) : selfArvl(i, c));
1172 for (std::size_t s : nnz)
1173 if (s != c) w += STeffOut(i, s) * Qin(i, s);
1180 if (
opt.multiserver ==
"suri") {
1181 T Lm = isopen[c] ? T(deltaclass[c] * Qin(i, c)) : selfArvl(i, c);
1182 if (sc != SchedStrategy::HOL)
1183 for (std::size_t s : nnz)
1184 if (s != c) Lm += Qin(i, s);
1185 if (sc == SchedStrategy::HOL) {
1186 Lm = isopen[c] ? Qin(i, c) : selfArvl(i, c);
1187 const std::vector<T>
ones(K, one);
1188 Wout(i, c) = T(STeffOut(i, c) +
1189 ((STeffOut(i, c) * Lm + hol_ehprio_backlog(
ones)) * suri[i] +
1190 hol_np_residual(
ones)) / ps_r);
1193 Wout(i, c) = T(STeffOut(i, c) / ps_r + STeffOut(i, c) * Lm * suri[i] / ps_r);
1196 if (
opt.multiserver ==
"softmin") {
1200 if (sc == SchedStrategy::HOL) {
1202 (STeffOut(i, c) * (isopen[c] ? Qin(i, c) : selfArvl(i, c)) * Bk[c] +
1203 hol_ehprio_backlog(Bk) + hol_np_residual(Bk)) / ps_r);
1206 const T selfQ = isopen[c] ? Qin(i, c) : selfArvl(i, c);
1208 T wq = T(STeffOut(i, c) * selfQ);
1209 for (std::size_t s : nnz)
1212 wq += T(STeffOut(i, s) * Qin(i, s));
1214 T w = T(STeffOut(i, c) / msterm[i]);
1216 T qfrac = T(nB + one - one / msterm[i]);
1217 if (qfrac < zero) qfrac = zero;
1218 w += T(wq * qfrac / nB);
1225 w += STeffOut(i, c);
1226 if (sc == SchedStrategy::HOL) {
1227 w += T((STeffOut(i, c) * (isopen[c] ? Qin(i, c) : selfArvl(i, c)) * Bk[c] +
1228 hol_ehprio_backlog(Bk) + hol_np_residual(Bk)) / ps_r);
1230 w += STeffOut(i, c) *
1231 (isopen[c] ? T(deltaclass[c] * Qin(i, c)) : selfArvl(i, c)) * Bk[c];
1232 for (std::size_t s : nnz)
1233 if (s != c) w += STeffOut(i, s) * Bk[s] * Qin(i, s);
1247 const std::vector<T> Xprev = X;
1250 forward(Qprev, Xprev, Uprev, Nc, Wout, STeffOut);
1254 for (std::size_t i = 0; i < Q.
rows(); ++i)
1255 for (std::size_t c = 0; c < Q.
cols(); ++c)
1259 "AMVA sweep %d: queue-length residual %.3e",
1262 if (totiter >= max_totiter)
break;
1263 for (std::size_t c : nnz) {
1265 for (std::size_t i = 0; i < M; ++i) sw += Wout(i, c);
1268 }
else if (!isopen[c]) {
1270 for (std::size_t i = 0; i < M; ++i) cyc += d.
Vchain(i, c) * Wout(i, c);
1273 (one - omicron) * Xprev[c]);
1277 for (std::size_t i = 0; i < M; ++i) {
1278 Q(i, c) = T(omicron * X[c] * d.
Vchain(i, c) * Wout(i, c) +
1279 (one - omicron) * Qprev(i, c));
1280 U(i, c) = T(omicron * d.
Vchain(i, c) * STeffOut(i, c) * X[c] +
1281 (one - omicron) * Uprev(i, c));
1286 for (std::size_t i = 0; i < M; ++i)
1287 for (std::size_t c = 0; c < K; ++c)
1289 if (err <=
opt.iter_tol)
break;
1291 if (iter > inner_cap)
break;
1301 QouterPrev = Qchain;
1302 const std::vector<T> XouterPrev = Xchain;
1304 if (linmethod) Xref = XouterPrev;
1307 if (linmethod && std::isfinite(Nt) && Nt > 0.0) {
1308 for (std::size_t s = 0; s < K; ++s) {
1309 if (!std::isfinite(d.
Nchain[s]))
continue;
1310 std::vector<double> Ns = d.
Nchain;
1314 std::vector<T> Xs = Xchain;
1316 for (std::size_t i = 0; i < M; ++i)
1317 for (std::size_t c = 0; c < K; ++c) {
1318 Qs(i, c) = T(Qs(i, c) * shrink);
1319 Us(i, c) = T(Us(i, c) * shrink);
1321 for (std::size_t c = 0; c < K; ++c) Xs[c] = T(Xs[c] * shrink);
1323 const std::vector<T> Xs_in = Xs;
1324 const Matrix<T> Qs_prev = inner_loop(Qs, Xs, Us, Ns, Ws, STs);
1326 for (std::size_t c : nnz) tau[s][c] = T(Xs[c] - XouterPrev[c]);
1345 if (method ==
"qdlin") {
1346 for (std::size_t i = 0; i < M; ++i) {
1347 T qs = zero, qo = zero;
1348 for (std::size_t c = 0; c < K; ++c) {
1349 qs += Qs_prev(i, c);
1350 qo += QouterPrev(i, c);
1361 for (std::size_t i = 0; i < M; ++i)
1362 for (std::size_t c : nnz)
1363 if (std::isfinite(d.
Nchain[c]) && Ns[c] > 0.0)
1368 if (totiter >= max_totiter)
break;
1371 if (totiter >= max_totiter)
break;
1374 const Matrix<T> Qprev = inner_loop(Qchain, Xchain, Uchain, d.
Nchain, Wtmp, STtmp);
1379 for (std::size_t i = 0; i < M; ++i)
1380 for (std::size_t c = 0; c < K; ++c)
1382 converged = err <=
opt.iter_tol;
1383 if (outer >= 2 && converged)
break;
1384 if (outer >= inner_cap || totiter > max_totiter)
break;
1387 for (std::size_t i = 0; i < M; ++i)
1388 for (std::size_t c = 0; c < K; ++c) Tchain(i, c) = T(Xchain[c] * d.
Vchain(i, c));
1391 for (std::size_t i = 0; i < M; ++i) {
1393 if (!(sc == SchedStrategy::FCFS || sc == SchedStrategy::SIRO || sc == SchedStrategy::PS ||
1394 sc == SchedStrategy::LCFSPR || sc == SchedStrategy::DPS || sc == SchedStrategy::HOL))
1397 for (std::size_t c = 0; c < K; ++c) usum += Uchain(i, c);
1400 for (std::size_t c = 0; c < K; ++c) den += d.
Vchain(i, c) * STeff(i, c) * Xchain[c];
1401 if (den == zero)
continue;
1402 for (std::size_t c = 0; c < K; ++c) {
1403 if (!(d.
Vchain(i, c) * STeff(i, c) > zero))
continue;
1404 Uchain(i, c) = T(one * d.
Vchain(i, c) * STeff(i, c) * Xchain[c] / den);
1409 for (std::size_t i = 0; i < M; ++i)
1410 for (std::size_t c = 0; c < K; ++c)
1411 if (Tchain(i, c) != zero) Rchain(i, c) = T(Qchain(i, c) / Tchain(i, c));
1412 for (std::size_t c = 0; c < K; ++c) {
1413 if (d.
Nchain[c] != 0.0)
continue;
1415 for (std::size_t i = 0; i < M; ++i) {
1416 Uchain(i, c) = zero;
1417 Rchain(i, c) = zero;
1418 Tchain(i, c) = zero;
1430 bool declares_dependence =
false;
1432 if (!st.lldscaling.empty() || st.cdscaling || st.jdscaling) declares_dependence =
true;
1434 L, d,
Matrix<T>(), declares_dependence ? Uchain :
Matrix<T>(), Rchain, Tchain, Xchain);
1454 for (std::size_t ist = 0; ist < M; ++ist) {
1457 "SolverMVA: station '" + L.
stations[ist].name +
1458 "' declares class-dependent service without a peak rate. Utilization at a "
1459 "class-dependent station is reported as T*S/peak, so pass the peak to "
1460 "setClassDependence");
1463 "SolverMVA: station '" + L.
stations[ist].name +
1464 "' declares joint-dependent service without a peak rate; pass the peak to "
1465 "setJointDependence");
1467 bool anyOpen =
false;
1468 for (
const auto& c : L.
classes)
1469 if (std::isinf(c.population)) anyOpen =
true;
1471 const std::size_t Kcls = L.
nclasses;
1472 for (std::size_t ist = 0; ist < M; ++ist) {
1473 const std::vector<T>* peak =
nullptr;
1475 peak = &L.
stations[ist].cdscalingpeak;
1476 else if (L.
stations[ist].jdscaling)
1477 peak = &L.
stations[ist].jdscalingpeak;
1478 if (peak ==
nullptr)
continue;
1479 for (std::size_t k = 0; k < Kcls; ++k) {
1481 const T bmax = k < peak->size() ? (*peak)[k] : zero;
1482 if (std::isfinite(rate) && rate > 0.0 && bmax > zero)
1483 out.
U(ist, k) = T(out.
Tp(ist, k) / L.
rates(ist, k) / bmax);
1485 out.
U(ist, k) = zero;
1496 std::snprintf(buf,
sizeof(buf),
"AMVA finished after %d sweeps", totiter);
1497 line::util::LineConsole::detail(buf);
1515 if (m ==
"amva.qli")
return "qli";
1516 if (m ==
"amva.qd" || m ==
"amva.qdamva" || m ==
"qdamva")
return "qd";
1517 if (m ==
"amva.aql")
return "aql";
1518 if (m ==
"amva.qsa")
return "qsa";
1519 if (m ==
"amva.qdaql")
return "qdaql";
1520 if (m ==
"amva.tay")
return "tay";
1521 if (m ==
"amva.scat")
return "scat";
1522 if (m ==
"amva.lin")
return "lin";
1523 if (m ==
"amva.qdlin")
return "qdlin";
1524 if (m ==
"amva.fli")
return "fli";
1525 if (m ==
"amva.bs")
return "bs";
1526 if (m ==
"amva.ab")
return "ab";
1527 if (m ==
"amva.schmidt")
return "schmidt";
1528 if (m ==
"amva.schmidt-ext")
return "schmidt-ext";
1529 if (m ==
"amva.lcp")
return "lcp";
1530 if (m ==
"amva.chow")
return "chow";
1531 if (m ==
"amva.pamb")
return "pamb";
1532 if (m ==
"amva.pami")
return "pami";
1533 if (m ==
"amva.pamt")
return "pamt";
1534 if (m ==
"amva.clust")
return "clust";
1535 if (m ==
"amva.dmlin")
return "dmlin";
1536 if (m ==
"amva.priomva")
return "priomva";
1537 if (m ==
"amva.marie")
return "marie";
1543 const Matrix<T>& init_sol,
bool& converged) {
1547 opt.iter_max = std::min(
opt.iter_max, 10000);
1551 if (method ==
"default" || method ==
"amva") {
1553 bool anysmall =
false;
1554 for (std::size_t c = 0; c < C; ++c) {
1556 if (d.
Nchain[c] < 1.0) anysmall =
true;
1558 if (Nsum <= 2.0 || anysmall) {
1561 bool anyfinite =
false, allone =
true;
1563 if (std::isfinite(s.nservers)) {
1565 if (s.nservers != 1.0) allone =
false;
1568 method = (anyfinite && allone) ?
"egflin" :
"lin";
1589 return solver_amvald(L, d, o2, method, converged, init_sol);
1594 const bool het_fcfs_own =
1595 (method ==
"ab" || method ==
"schmidt" || method ==
"schmidt-ext");
1600 if (!st.lldscaling.empty()) cond2 =
false;
1606 if (!(cond1 && cond2 && cond3))
return solver_amvald(L, d,
opt, method, converged, init_sol);
1620 std::vector<int> N(C, 0);
1621 for (std::size_t c = 0; c < C; ++c)
1622 N[c] = std::isfinite(d.
Nchain[c]) ?
static_cast<int>(std::llround(d.
Nchain[c]))
1626 std::vector<T> Nt(C, zero);
1627 for (std::size_t c = 0; c < C; ++c)
1631 std::vector<pfqn::SchedStrategy> types;
1632 for (std::size_t a = 0; a < nq; ++a) {
1646 for (std::size_t a = 0; a < nq && !bad; ++a)
1647 for (std::size_t c = 0; c < C; ++c) {
1649 if (Q0(a, c) < zero) {
1658 const bool direct_ms = (method ==
"ab" || method ==
"schmidt" || method ==
"schmidt-ext");
1659 const bool seidmann = (
opt.multiserver ==
"default" ||
opt.multiserver ==
"seidmann");
1666 for (std::size_t a = 0; a < nq; ++a) {
1667 const double c = pf.
S[a];
1668 if (!std::isfinite(c))
continue;
1669 for (std::size_t k = 0; k < C; ++k)
1672 for (std::size_t k = 0; k < C; ++k)
1676 }
else if (
opt.multiserver ==
"softmin") {
1684 std::vector<T> Zsum(C, zero);
1685 for (std::size_t c = 0; c < C; ++c)
1686 for (std::size_t a = 0; a < Zm.rows(); ++a) Zsum[c] += Zm(a, c);
1688 bool allone =
true, anyinf =
false;
1689 for (
double s : pf.
S) {
1690 if (!std::isfinite(s)) anyinf =
true;
1691 else if (s != 1.0) allone =
false;
1695 Matrix<T> Qq(nq, C, zero), Uq(nq, C, zero), Qz(nz, C, zero);
1696 std::vector<T> X(C, zero);
1698 bool have_delay_rows =
false;
1700 if (method ==
"sqni") {
1702 if (!(M == 2 && nq == 1 && nz == 1))
1704 "solver_amva: method 'sqni' applies only to a model of one queueing station and "
1705 "one infinite server");
1708 "solver_amva: method 'sqni' solves a quadratic and evaluates a square root, "
1709 "which exact arithmetic has no representation for; use the double or real "
1712 std::vector<T> Lv(C, zero);
1713 for (std::size_t c = 0; c < C; ++c) Lv[c] = Dm(0, c);
1715 for (std::size_t c = 0; c < C; ++c) {
1722 }
else if (method ==
"bs") {
1723 std::vector<pfqn::AmvaSched> bstype;
1724 for (std::size_t a = 0; a < nq; ++a)
1734 }
else if (method ==
"lcp" || method ==
"chow") {
1738 std::vector<pfqn::AmvaSched> lctype;
1739 for (std::size_t a = 0; a < nq; ++a)
1746 static_cast<std::size_t
>(
opt.iter_max), Q0)
1748 static_cast<std::size_t
>(
opt.iter_max), Q0);
1753 }
else if (method ==
"pamb" || method ==
"pami" || method ==
"pamt") {
1763 }
else if (method ==
"clust") {
1767 Dm, Nt, Zsum, std::vector<std::vector<std::size_t> >(),
1769 static_cast<std::size_t
>(
opt.iter_max));
1774 }
else if (method ==
"dmlin") {
1784 }
else if (method ==
"tay") {
1791 "solver_amva: Tay's approximation is defined for single-server stations; use "
1792 "'default' or 'lin'");
1795 "solver_amva: method 'tay' stops on a tolerance evaluated with transcendentals, "
1796 "which exact arithmetic has no representation for; use the double or real "
1800 Dm, Nt, Zsum,
opt.tol,
static_cast<std::size_t
>(
opt.iter_max), Q0);
1806 }
else if (method ==
"scat") {
1816 }
else if (method ==
"aql") {
1821 "solver_amva: AQL cannot handle multi-server stations; use 'default' or 'lin'");
1824 "solver_amva: method 'aql' stops on a relative tolerance evaluated with "
1825 "transcendentals, which exact arithmetic has no representation for; use the "
1826 "double or real backend");
1835 }
else if (method ==
"qsa") {
1840 "solver_amva: QSA cannot handle multi-server stations; use 'default' or 'lin'");
1843 "solver_amva: method 'qsa' stops on a residual tolerance evaluated with "
1844 "transcendentals, which exact arithmetic has no representation for; use the "
1845 "double or real backend");
1851 static_cast<std::size_t
>(
opt.iter_max));
1857 }
else if (direct_ms) {
1861 std::vector<int> nsfull;
1862 std::vector<pfqn::SchedStrategy> schedfull;
1864 for (std::size_t a = 0; a < nz; ++a) {
1865 for (std::size_t c = 0; c < C; ++c) Dfull(a, c) = pf.
Z(a, c);
1866 nsfull.push_back(1);
1870 for (std::size_t a = 0; a < nq; ++a) {
1871 for (std::size_t c = 0; c < C; ++c) {
1872 Dfull(nz + a, c) = pf.
D(a, c);
1877 const int c_i = std::isfinite(pf.
S[a]) ?
static_cast<int>(std::llround(pf.
S[a])) : 1;
1878 nsfull.push_back(c_i);
1879 Sfull(nz + a, 0) = c_i;
1880 schedfull.push_back(types[a]);
1884 "solver_amva: the Akyildiz-Bolch and Schmidt multiserver methods use marginal "
1885 "weights with non-integer powers and floors, which exact arithmetic has no "
1886 "representation for; use the double or real backend");
1887 }
else if (method ==
"ab") {
1890 for (std::size_t a = 0; a < nq; ++a)
1891 for (std::size_t c = 0; c < C; ++c) {
1892 Qq(a, c) = r.
QN(nz + a, c);
1893 Uq(a, c) = r.
UN(nz + a, c);
1895 for (std::size_t a = 0; a < nz; ++a)
1896 for (std::size_t c = 0; c < C; ++c) Qz(a, c) = r.
QN(a, c);
1898 iters =
static_cast<int>(r.
totiter);
1899 }
else if (method ==
"schmidt") {
1901 for (std::size_t a = 0; a < nq; ++a)
1902 for (std::size_t c = 0; c < C; ++c) Qq(a, c) = r.
QN(nz + a, c);
1903 for (std::size_t a = 0; a < nz; ++a)
1904 for (std::size_t c = 0; c < C; ++c) Qz(a, c) = r.
QN(a, c);
1913 std::vector<double> sx_n(C, 0.0);
1914 for (std::size_t c = 0; c < C; ++c) sx_n[c] = d.
Nchain[c];
1915 std::vector<bool> sx_fcfs;
1916 for (std::size_t a = 0; a < nq; ++a)
1918 SchedStrategy::FCFS);
1919 const std::string sx_reason =
1924 for (std::size_t a = 0; a < nq; ++a)
1925 for (std::size_t c = 0; c < C; ++c) Qq(a, c) = r.
QN(nz + a, c);
1926 for (std::size_t a = 0; a < nz; ++a)
1927 for (std::size_t c = 0; c < C; ++c) Qz(a, c) = r.
QN(a, c);
1931 have_delay_rows =
true;
1934 for (std::size_t a = 0; a < nq; ++a) {
1935 const double c_i = std::isfinite(pf.
S[a]) ? pf.
S[a] : 1.0;
1936 for (std::size_t k = 0; k < C; ++k)
1939 }
else if (method ==
"lin" || method ==
"gflin" || method ==
"egflin") {
1943 if (st.cdscaling || st.jdscaling)
1951 return solver_amvald(L, d, o2, method, converged, init_sol);
1953 if (allone ||
opt.multiserver ==
"krzesinski") {
1954 std::vector<int> ns;
1955 for (std::size_t a = 0; a < nq; ++a)
1956 ns.push_back(std::isfinite(pf.
S[a]) ?
static_cast<int>(std::llround(pf.
S[a])) : 1);
1957 if (allone) ns.assign(nq, 1);
1959 pf.
lambda, Dm, N, Zm, ns, types,
opt.tol,
opt.iter_max, mx, Q0);
1964 }
else if (
opt.multiserver ==
"conway") {
1967 "solver_amva: the Conway multiserver correction evaluates transcendentals, "
1968 "which exact arithmetic has no representation for; use the double or real "
1971 std::vector<int> ns;
1972 for (std::size_t a = 0; a < nq; ++a)
1973 ns.push_back(std::isfinite(pf.
S[a]) ?
static_cast<int>(std::llround(pf.
S[a]))
1982 }
else if (
opt.multiserver ==
"default" ||
opt.multiserver ==
"softmin" ||
1983 opt.multiserver ==
"seidmann" ||
opt.multiserver ==
"suri" ||
1984 opt.multiserver ==
"erlang") {
1995 bool conserves =
true;
1996 for (std::size_t c = 0; c < C && conserves; ++c) {
1997 const double Nc = d.
Nchain[c];
1998 if (!std::isfinite(Nc) || Nc <= 0.0)
continue;
2000 for (std::size_t k : L.
inchain[c])
2001 for (std::size_t i = 0; i < sol.
Q.rows(); ++i) {
2003 if (!std::isnan(v)) qc += v;
2005 if (!std::isfinite(qc) || std::abs(qc - Nc) > 1e-3 * Nc) conserves =
false;
2007 if (converged || conserves)
return sol;
2018 "'. Supported: default, softmin, seidmann, suri, conway, "
2019 "erlang, krzesinski.");
2027 return solver_amvald(L, d, o2, method, converged, init_sol);
2030 Matrix<T> Q(M, C, zero), U(M, C, zero), Tp(M, C, zero), R(M, C, zero);
2031 for (std::size_t a = 0; a < nq; ++a)
2032 for (std::size_t c = 0; c < C; ++c) {
2040 for (std::size_t a = 0; a < nz; ++a)
2041 for (std::size_t c = 0; c < C; ++c) {
2045 (void)have_delay_rows;
2048 if (seidmann && !direct_ms) {
2049 for (std::size_t a = 0; a < nq; ++a) {
2050 const double c = pf.
S[a];
2051 if (!std::isfinite(c) || c <= 1.0)
continue;
2052 for (std::size_t k = 0; k < C; ++k)
2058 for (std::size_t i = 0; i < M; ++i)
2059 for (std::size_t c = 0; c < C; ++c) {
2060 Tp(i, c) = T(d.
Vchain(i, c) * X[c]);
2061 if (Tp(i, c) != zero) R(i, c) = T(Q(i, c) / Tp(i, c));
2066 std::vector<T> Cyc(C, zero);
2067 for (std::size_t c = 0; c < C; ++c) {
2068 if (!(X[c] > zero))
continue;
2070 for (std::size_t a = 0; a < nz; ++a) z += pf.
Z(a, c);
2124 for (std::size_t c = 0; c < C; ++c)
2126 const std::size_t NT =
static_cast<std::size_t
>(std::llround(Nt));
2132 std::vector<T> lambda(C, zero);
2133 std::vector<int> N(C, 0);
2134 std::vector<bool> openChain(C,
false);
2135 std::size_t nOpenChains = 0;
2136 for (std::size_t c = 0; c < C; ++c) {
2137 if (std::isfinite(d.
Nchain[c])) {
2138 N[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
2142 openChain[c] =
true;
2145 if (st > zero) lambda[c] = T(one / st);
2148 Matrix<T> Qchain(M, C, zero), Uchain(M, C, zero);
2149 std::vector<T> Xchain(C, zero);
2150 if (nOpenChains == 0) {
2156 for (std::size_t i = 0; i < M; ++i) {
2157 if (std::isinf(L.
stations[i].nservers)) {
2158 for (std::size_t n = 0; n < NT; ++n)
2160 }
else if (!L.
stations[i].lldscaling.empty()) {
2161 const std::vector<T>& a = L.
stations[i].lldscaling;
2162 for (std::size_t n = 0; n < NT; ++n) mu(i, n) = n < a.size() ? a[n] : a.back();
2166 for (std::size_t i = 0; i < M; ++i)
2167 for (std::size_t c = 0; c < C; ++c) Dm(i, c) = d.
Lchain(i, c);
2186 std::vector<bool> sourceStation(M,
false), delayStation(M,
false);
2187 for (std::size_t c = 0; c < C; ++c)
2188 if (openChain[c]) sourceStation[
static_cast<std::size_t
>(d.
refstatchain[c] - 1)] =
true;
2189 std::vector<std::size_t> queueStations;
2190 for (std::size_t i = 0; i < M; ++i) {
2191 if (sourceStation[i])
continue;
2192 if (std::isinf(L.
stations[i].nservers))
2193 delayStation[i] =
true;
2195 queueStations.push_back(i);
2197 const std::size_t nq = queueStations.size();
2200 for (std::size_t c = 0; c < C; ++c) {
2202 for (std::size_t i = 0; i < M; ++i)
2203 if (delayStation[i]) z = T(z + d.
Lchain(i, c));
2207 std::size_t ncol = NT > 0 ? NT : 1;
2208 for (std::size_t k = 0; k < nq; ++k) {
2210 const std::vector<T>& a = L.
stations[queueStations[k]].lldscaling;
2211 std::size_t b = a.size();
2212 while (b > 1 && a[b - 2] == a[b - 1]) --b;
2213 ncol = std::max(ncol, b);
2216 for (std::size_t k = 0; k < nq; ++k) {
2217 const std::vector<T>& a = L.
stations[queueStations[k]].lldscaling;
2218 if (a.empty())
continue;
2220 for (std::size_t n = 0; n < ncol; ++n) mu(k, n) = n < a.size() ? a[n] : a.back();
2223 for (std::size_t k = 0; k < nq; ++k)
2224 for (std::size_t c = 0; c < C; ++c) Dq(k, c) = d.
Lchain(queueStations[k], c);
2228 for (std::size_t k = 0; k < nq; ++k)
2229 for (std::size_t c = 0; c < C; ++c) {
2230 Qchain(queueStations[k], c) = pf.
QN(k, c);
2231 Uchain(queueStations[k], c) = pf.
UN(k, c);
2233 for (std::size_t i = 0; i < M; ++i)
2234 if (delayStation[i])
2235 for (std::size_t c = 0; c < C; ++c)
2237 Qchain(i, c) = T(d.
Lchain(i, c) * Xchain[c]);
2252 Matrix<T> Tchain(M, C, zero), Rchain(M, C, zero);
2253 for (std::size_t i = 0; i < M; ++i)
2254 for (std::size_t c = 0; c < C; ++c) {
2255 Tchain(i, c) = T(Xchain[c] * d.
Vchain(i, c));
2256 if (Tchain(i, c) > zero) Rchain(i, c) = T(Qchain(i, c) / Tchain(i, c));
2271 for (std::size_t i = 0; i < M; ++i) {
2272 if (L.
stations[i].lldscaling.empty() || !std::isfinite(L.
stations[i].nservers))
continue;
2273 double ceff = L.
stations[i].nservers;
2274 for (
const T& v : L.
stations[i].lldscaling)
2276 for (std::size_t r = 0; r < L.
nclasses; ++r) {
2278 const T st = L.
rates(i, r) > zero ? T(one / L.
rates(i, r)) : zero;
2309 "solver_mva_sum: the summation method locates the root of its population constraint "
2310 "by bisection and evaluates the Erlang-C waiting probability, neither of which exact "
2311 "arithmetic has a representation for; use the double or real backend");
2313 std::vector<std::size_t> rows;
2314 std::vector<sum::Servers> mi;
2315 for (std::size_t i = 0; i < M; ++i) {
2317 if (sc == SchedStrategy::EXT)
continue;
2318 if (sc == SchedStrategy::INF) {
2323 if (sc == SchedStrategy::PS || sc == SchedStrategy::LCFSPR ||
2324 sc == SchedStrategy::FCFS || sc == SchedStrategy::SIRO) {
2326 const double c = L.
stations[i].nservers;
2327 mi.push_back(std::isfinite(c) ?
sum::Servers::of(
static_cast<long>(std::llround(c)))
2336 const std::size_t Mr = rows.size();
2337 Matrix<T> Lm(Mr, K, zero), scv(Mr, K, one);
2338 for (std::size_t a = 0; a < Mr; ++a) {
2339 const std::size_t i = rows[a];
2340 const bool sensitive = L.
stations[i].sched == SchedStrategy::FCFS ||
2341 L.
stations[i].sched == SchedStrategy::SIRO;
2342 for (std::size_t c = 0; c < K; ++c) {
2344 if (!sensitive)
continue;
2350 std::vector<long> N(K, 0);
2351 std::vector<T> Z(K, zero);
2352 std::vector<std::size_t> ocl;
2353 for (std::size_t c = 0; c < K; ++c) {
2354 if (std::isfinite(d.
Nchain[c])) N[c] =
static_cast<long>(std::llround(d.
Nchain[c]));
2355 else ocl.push_back(c);
2359 std::vector<T> Xchain;
2360 std::size_t iters = 0;
2364 so.
maxiter =
static_cast<std::size_t
>(
opt.iter_max);
2371 std::vector<T> lambda(K, zero), scva(K, one);
2372 for (std::size_t c : ocl) {
2382 "solver_mva_sum: open chain " + std::to_string(c + 1) +
2383 " has a non-positive service time at its reference station, so it carries no "
2384 "arrival rate; the summation method cannot place it");
2385 lambda[c] = T(one / st);
2399 Matrix<T> Qchain(M, K, zero), Uchain(M, K, zero), Tchain(M, K, zero), Rchain(M, K, zero);
2400 for (std::size_t a = 0; a < Mr; ++a)
2401 for (std::size_t c = 0; c < K; ++c) {
2402 Qchain(rows[a], c) = Qrows(a, c);
2403 Uchain(rows[a], c) = Urows(a, c);
2405 for (std::size_t i = 0; i < M; ++i)
2406 for (std::size_t c = 0; c < K; ++c) {
2407 Tchain(i, c) = T(Xchain[c] * d.
Vchain(i, c));
2408 if (Tchain(i, c) > zero) Rchain(i, c) = T(Qchain(i, c) / Tchain(i, c));
2410 for (std::size_t c = 0; c < K; ++c) {
2411 if (d.
Nchain[c] != 0.0)
continue;
2413 for (std::size_t i = 0; i < M; ++i) {
2414 Qchain(i, c) = zero;
2415 Uchain(i, c) = zero;
2416 Rchain(i, c) = zero;
2417 Tchain(i, c) = zero;
2431 out.
iter =
static_cast<int>(iters);
2463 "solver_sqd: the blocking-after-service calibration evaluates exp, log and real "
2464 "powers, which exact rational arithmetic cannot represent");
2470 "solver_sqd: Smith queue decomposition supports single-chain closed networks only; "
2471 "this model has " + std::to_string(L.
nchains) +
" chains");
2479 "solver_sqd: Smith queue decomposition is defined for a CLOSED population; this model "
2480 "has an open class, whose jobs the fixed point cannot count");
2482 std::vector<T> ST(M, zero), V(M, zero), cap(M, zero);
2483 std::vector<bool> isDelay(M,
false);
2484 std::vector<std::size_t> s2sf(M, 0);
2486 for (std::size_t i = 0; i < M; ++i) {
2489 isDelay[i] = (L.
stations[i].sched == SchedStrategy::INF ||
2490 L.
stations[i].sched == SchedStrategy::EXT);
2492 const double c = L.
cap.empty() ? std::numeric_limits<double>::infinity() : L.
cap[i];
2493 cap[i] = (isDelay[i] || !(c <= 1e14)) ? inf : num_traits<T>::from_double(c);
2500 static_cast<int>(std::llround(L.
nclosedjobs())), sopt);
2505 Matrix<T> Tchain(M, 1, zero), Qchain(M, 1, zero), Uchain(M, 1, zero), Rchain(M, 1, zero);
2506 for (std::size_t i = 0; i < M; ++i) {
2507 Tchain(i, 0) = r.
X[i];
2508 Qchain(i, 0) = r.
Q[i];
2509 Uchain(i, 0) = r.
U[i];
2510 Rchain(i, 0) = r.
R[i];
2512 std::vector<T> Xchain(1, r.
X[refstat - 1]);
2524 out.
lG = std::numeric_limits<double>::quiet_NaN();
2546 const std::string alt =
" Use SolverCTMC or SolverJMT instead.";
2547 for (
const auto& c : L.
classes)
2548 if (std::isinf(c.population))
2549 return {
"MVABLO is defined for closed networks only; this model has an open class." + alt,
"OpenClass"};
2551 return {
"MVABLO supports a single closed class; this model has " + std::to_string(L.
nclasses) +
2552 " classes (class switching included)." + alt,
"MultiClass"};
2553 if (!(L.
nclosedjobs() > 0.0))
return {
"MVABLO requires a positive closed population." + alt,
"ClosedClass"};
2554 for (
const auto& n : L.
nodes)
2555 if (n.nodetype == qn::NodeType::Fork || n.nodetype == qn::NodeType::Join)
2556 return {
"MVABLO does not support fork-join." + alt,
"Fork"};
2558 return {
"MVABLO supports networks whose stateful nodes are all stations (queues and delays)." + alt,
"Node"};
2560 for (std::size_t i = 0; i < M; ++i) {
2562 if (!st.lldscaling.empty() || st.cdscaling || st.jdscaling)
2563 return {
"MVABLO does not support load-, class- or joint-dependent service." + alt,
"LoadDependence"};
2565 auto is_delay = [&](std::size_t i) {
return L.
stations[i].sched == SchedStrategy::INF; };
2566 for (std::size_t i = 0; i < M; ++i) {
2568 if (!is_delay(i) && st.sched != SchedStrategy::FCFS) {
2570 for (
char& ch : s) ch =
static_cast<char>(std::toupper(
static_cast<unsigned char>(ch)));
2571 return {
"MVABLO supports FCFS queues and INF delays; station " + st.name +
" is " + s +
"." + alt,
2574 if (!is_delay(i) && st.nservers != 1.0)
2575 return {
"MVABLO supports single-server queues; station " + st.name +
" has " +
2576 std::to_string(
static_cast<long>(st.nservers)) +
" servers." + alt,
"MultiServer"};
2578 return {
"MVABLO requires exponential service; station " + st.name +
" is not exponential." + alt,
2583 auto rt = [&](std::size_t j, std::size_t i) {
2586 auto is_bas = [&](std::size_t i) {
2589 for (std::size_t i = 0; i < M; ++i) {
2590 const double cap = L.
cap.empty() ? std::numeric_limits<double>::infinity() : L.
cap[i];
2591 if (is_delay(i) || std::isinf(cap) || cap >= N)
continue;
2592 bool bas = is_bas(i);
2593 for (std::size_t j = 0; j < M && !bas; ++j) bas = is_bas(j) && rt(j, i);
2595 return {
"MVABLO models Blocking-After-Service only; the finite buffer of station " +
2596 L.
stations[i].name +
" has no BAS drop rule." + alt,
"FiniteCapacity"};
2597 for (std::size_t j = 0; j < M; ++j)
2598 if (is_delay(j) && rt(j, i))
2599 return {
"MVABLO cannot block a delay: " + L.
stations[j].name +
" feeds the finite buffer of " +
2600 L.
stations[i].name +
"." + alt,
"Delay"};
2602 return {std::string(), std::string()};
2622 std::vector<T> S(M, zero);
2623 std::vector<double> K(M, std::numeric_limits<double>::infinity());
2624 std::vector<bool> isdelay(M,
false);
2626 for (std::size_t i = 0; i < M; ++i) {
2627 isdelay[i] = L.
stations[i].sched == qn::SchedStrategy::INF;
2628 S[i] = T(one / L.
service[i][0].rate());
2629 if (!isdelay[i] && !L.
cap.empty()) K[i] = L.
cap[i];
2630 for (std::size_t j = 0; j < M; ++j)
2635 const std::size_t ref = L.
classes[0].refstat - 1;
2641 for (std::size_t i = 0; i < M; ++i) {
2642 out.
Q(i, 0) = r.
Q[i];
2643 out.
U(i, 0) = r.
U[i];
2644 out.
R(i, 0) = r.
R[i];
2645 out.
Tp(i, 0) = r.
X[i];
2652 out.
lG = std::numeric_limits<double>::quiet_NaN();
2664 for (
const auto& c : L.
classes)
2665 if (std::isinf(c.population))
return false;
2684 for (
const auto& c : L.
classes)
2685 if (std::isinf(c.population))
return false;
2702 std::size_t nsrc = 0, nq = 0, nsnk = 0;
2704 if (nd.
nodetype == qn::NodeType::Source) ++nsrc;
2705 else if (nd.
nodetype == qn::NodeType::Queue) ++nq;
2706 else if (nd.
nodetype == qn::NodeType::Sink) ++nsnk;
2708 if (nsrc != 1 || nq != 1 || nsnk != 1)
return false;
2709 std::size_t qist = 0, sist = 0;
2710 for (std::size_t i = 0; i < L.
stations.size(); ++i) {
2711 if (L.
stations[i].nodetype == qn::NodeType::Queue) qist = i + 1;
2712 else if (L.
stations[i].nodetype == qn::NodeType::Source) sist = i + 1;
2714 if (qist == 0 || sist == 0)
return false;
2715 if (L.
stations[qist - 1].nservers != 1.0)
return false;
2716 if (L.
droprule.size() < qist || L.
droprule[qist - 1].empty())
return false;
2718 if (L.
cap.size() < qist)
return false;
2719 if (!std::isfinite(L.
cap[qist - 1]) || L.
cap[qist - 1] <= 0.0)
return false;
2722 if (std::fabs(scvs - 1.0) > 1e-6 || std::fabs(scvq - 1.0) > 1e-6)
return false;
2742 bool has_ld =
false;
2744 if (!st.lldscaling.empty() || st.cdscaling || st.jdscaling) has_ld =
true;
2766 for (
const auto& nd : L.
nodes)
2767 if (nd.nodetype == qn::NodeType::Fork)
return false;
2768 const std::string& method =
opt.method;
2769 bool has_open =
false, has_closed =
false, integral =
true, has_finite_server =
false;
2770 double maxsrv = -1.0, Nsum = 0.0;
2771 for (
const auto& c : L.
classes) {
2772 if (std::isinf(c.population)) {
2775 if (c.population > 0.0) has_closed =
true;
2776 if (c.population != std::floor(c.population)) integral =
false;
2778 Nsum += c.population;
2781 if (std::isfinite(st.nservers)) {
2782 has_finite_server =
true;
2783 maxsrv = std::max(maxsrv, st.nservers);
2786 const bool pfqn_mva_can_take_it = !has_open && integral && (maxsrv < 0.0 || maxsrv <= 1.0);
2788 if (method ==
"exact" || method ==
"mva")
return pfqn_mva_can_take_it;
2789 static const char* amva_methods[] = {
2790 "amva",
"bs",
"qd",
"qli",
"fli",
"lin",
"qdlin",
"sqni",
2791 "gflin",
"egflin",
"ab",
"schmidt",
"schmidt-ext",
"tay",
"scat",
"aql",
2792 "qsa",
"lcp",
"chow",
"pamb",
"pami",
"pamt",
"clust",
"dmlin",
2794 for (
const char* m : amva_methods)
2796 if (method ==
"default") {
2798 const bool exact_mixed = has_open && has_closed && has_finite_server && maxsrv == 1.0 &&
2802 if (exact_mixed || exact_small)
return pfqn_mva_can_take_it;
2818 bool converged =
true;
2827 if (
opt.method ==
"qna")
2829 "solver_mva_analyzer: 'qna' is dispatched by mva_dispatch, not by the analyzer");
2830 if (
opt.method ==
"rqt")
2832 "solver_mva_analyzer: 'rqt' is dispatched by mva_dispatch, not by the analyzer");
2833 if (
opt.method ==
"rqna")
2835 "solver_mva_analyzer: 'rqna' is dispatched by mva_dispatch, not by the analyzer");
2836 if (
opt.method !=
"default") {
2848 if (st.cdscaling || st.jdscaling) {
2856 bool il_needs_amva =
false;
2857 if (!
opt.interlock.empty()) {
2860 if (std::isfinite(st.nservers) && st.nservers > 1.0) il_needs_amva =
true;
2865 bool anyopen =
false, anyclosed =
false, integral =
true;
2866 for (
const auto& cl : L.
classes) {
2867 if (std::isinf(cl.population)) {
2871 if (cl.population > 0.0) anyclosed =
true;
2872 if (cl.population != std::floor(cl.population)) integral =
false;
2874 bool anyfinite =
false, allone =
true;
2876 if (std::isfinite(st.nservers)) {
2878 if (st.nservers != 1.0) allone =
false;
2880 if (anyopen && anyclosed && anyfinite && allone && integral && L.
has_product_form())
2894 if (!il_needs_amva) {
2895 bool all_open =
true, has_finite_server =
false, has_lld =
false;
2896 for (
const auto& cl : L.
classes)
2897 if (!std::isinf(cl.population)) all_open =
false;
2898 for (
const auto& st : L.
stations) {
2899 if (std::isfinite(st.nservers) && st.nservers >= 1.0) has_finite_server =
true;
2900 if (!st.lldscaling.empty()) has_lld =
true;
2913 for (std::size_t c = 0; c < L.
nchains; ++c) Nsum += d.
Nchain[c];
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
bool has_multi_class_heter_fcfs() const
bool has_class_switching() const
std::size_t stateful_of_station(std::size_t st) const
std::size_t nof_stateful() const
std::vector< std::size_t > stateful_nodes
1-based node indices, ascending
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
std::vector< bool > fjauxclass
sn.fjauxclass: (nclasses+1, 1-based) true where the class is an MMT AUXILIARY OPEN class,...
std::vector< std::vector< bool > > disabled
Matrix< T > rt
sn.rt and sn.rtnodes: the class-expanded routing.
bool has_priorities() const
std::vector< double > cap
sn.cap and sn.classcap: the total and per-class buffers.
std::vector< JobClass > classes
bool has_product_form_not_het_fcfs(bool check_means=true) const
Port of sn_has_product_form_not_het_fcfs: LCFS is excluded, and at FCFS the service must be exponenti...
std::vector< Station< T > > stations
stations[k-1] is the k-th station
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
std::vector< std::vector< std::size_t > > inchain
1-based class indices per chain
bool has_multi_server() const
std::vector< NodeDef > nodes
every node, in creation order
bool has_sd_routing() const
sn_has_sd_routing: RROBIN, WRROBIN, JSQ, SQ or SDR at some node.
bool has_open_classes() const
bool sched_is_product_form() const
std::vector< std::vector< DropStrategy > > droprule
bool has_blocking() const
Some station can REFUSE a job: its own buffer BINDS, or a finite capacity region caps a set of statio...
bool has_homogeneous_scheduling(SchedStrategy strategy) const
Port of sn_has_homogeneous_scheduling: true when EVERY station uses strategy, the reference's length(...
double nclosedjobs() const
sn.nclosedjobs: the total population of the closed classes.
std::vector< std::pair< std::size_t, std::size_t > > fj
fj(f,j): the Join node j that closes the Fork node f, 1-based.
bool has_product_form() const
sn_has_product_form, conjunct for conjunct.
bool has_fractional_populations() const
static void loop(const char *fmt,...)
Announce an iteration loop and reset its reporting budget.
static void iter(long k, const char *fmt,...)
Report iteration k of the current loop.
static bool owns_log()
True only inside the OUTERMOST open run; gates EMISSION.
The exception types the port throws.
Running progress log of a LINE solver run (the "solver console").
The option and result types every MVA analyzer shares.
bool sn_has_product_form(const qn::NetworkStruct< T > &sn)
Port of sn_has_product_form.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
DropStrategy
Blocking and loss rules, with the values of MATLAB DropStrategy.
const char * sched_to_text(SchedStrategy s)
std::function< std::vector< T >(const std::vector< T > &)> CdScaling
A class-dependent scaling map, sn.cdscaling.
std::pair< std::string, std::string > mvablo_scope_reason(const qn::NetworkStruct< T > &L)
Port of mvablo_scope_reason.m: why method 'mvablo' cannot solve L, as {reason, kind}...
PfChainParams< T > sn_get_product_form_chain_params(const qn::NetworkStruct< T > &L, const ChainDemands< T > &d)
Port of sn_get_product_form_chain_params.
MvaSolution< T > solver_mva(const qn::NetworkStruct< T > &L, const ChainDemands< T > &d, const MvaOptions &opt)
Port of solver_mva.m, closed and mixed product-form networks.
MvaSolution< T > solver_mvablo(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mvablo.m.
MvaSolution< T > solver_amvald(const qn::NetworkStruct< T > &L, const ChainDemands< T > &d, const MvaOptions &opt, const std::string &method, bool &converged, const Matrix< T > &init_sol=Matrix< T >())
Port of solver_amvald.m together with solver_amvald_forward.m, restricted to the INF / PS / FCFS-fami...
std::string mva_schmidt_ext_reason(const std::vector< double > &njobs, const std::vector< bool > &fcfs, const std::string &method)
The extended Schmidt method needs a customer of every class to tag.
bool sn_is_mm1k_loss(const qn::NetworkStruct< T > &L)
Port of matlab/src/api/sn/sn_is_mm1k_loss.m: a single-class open Source-Queue-Sink system whose queue...
bool amva_uses_pf_kernels(const qn::NetworkStruct< T > &L)
Conservative form of the branch test in solver_amva: true when this model may be solved by the produc...
std::string amva_method_alias(const std::string &m)
The amva.
bool sn_is_bas_model(const qn::NetworkStruct< T > &L)
Port of matlab/src/api/sn/sn_is_bas_model.m: a closed single-CLASS model with a BAS drop rule.
Matrix< T > sn_interlock_chain(const qn::NetworkStruct< T > &L, const std::vector< std::vector< double > > &ILclass)
Aggregate a class-indexed interlock matrix to the chain basis the MVA analyzers work in.
MvaSolution< T > solver_mva_sum(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mva_sum.m: the SUM / ESUM summation method (Bolch et al., Secs.
bool mva_is_bas_model(const qn::NetworkStruct< T > &L)
Port of isBasModel in solver_mva_analyzer.m: a closed single-CHAIN model with at least one blocking-a...
ClassResults< T > sn_deaggregate_chain_results(const qn::NetworkStruct< T > &L, const ChainDemands< T > &d, const Matrix< T > &Qchain, const Matrix< T > &Uchain, const Matrix< T > &Rchain, const Matrix< T > &Tchain, const std::vector< T > &Xchain)
Port of sn_deaggregate_chain_results.
MvaSolution< T > solver_sqd(const qn::NetworkStruct< T > &L, const ChainDemands< T > &d)
Port of solver_sqd.m: Smith queue decomposition for blocking-after-service.
std::string mva_closed_population_reason(const qn::NetworkStruct< T > &L, const std::string &method)
The closed-population AMVA family: an open chain, or a network outside strict product form,...
MvaSolution< T > solver_mvald(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
Port of solver_mvald.m: exact MVA on a load-dependent model, through pfqn_mvaldmx.
MvaSolution< T > solver_mva_lcfsqn(const qn::NetworkStruct< T > &L, std::size_t lcfs_ist, std::size_t lcfspr_ist)
Port of solver_mva_lcfsqn.m: the closed two-station LCFS + LCFS-PR network.
MvaSolution< T > solver_amva(const qn::NetworkStruct< T > &L, const ChainDemands< T > &d, MvaOptions opt, const Matrix< T > &init_sol, bool &converged)
bool mva_carries_interlock(const qn::NetworkStruct< T > &L, const MvaOptions &opt)
True when the MVA path this model already dispatches to carries a class-level interlock matrix (Frank...
MvaSolution< T > solver_mva_analyzer(const qn::NetworkStruct< T > &L, const MvaOptions &opt, const Matrix< T > &init_sol)
Port of solver_mva_analyzer.m, default and the explicit method names.
ChainDemands< T > sn_get_demands_chain(const qn::NetworkStruct< T > &L)
Port of sn_get_demands_chain.
SqdResult< T > npfqn_sqd(const std::vector< T > &ST, const std::vector< T > &V, const std::vector< T > &cap, const std::vector< bool > &isDelay, const Matrix< T > &rt, const std::vector< std::size_t > &stationToStateful, std::size_t nclasses, int N, const SqdOptions< T > &opt)
Smith Queue Decomposition (SQD): approximate MVA for closed networks under Blocking-After-Service (ma...
SqniResult< T > pfqn_sqni(const std::vector< T > &N, const std::vector< T > &L, const std::vector< T > &Z)
Square-root non-iterative (SQNI) approximation for a single queueing station with per-class delay.
SchmidtExtResult< T > pfqn_schmidt_ext(const Matrix< T > &D, const std::vector< int > &N, const Matrix< int > &S, const std::vector< SchedStrategy > &sched)
Extended Schmidt MVA with queue-aware alpha corrections.
LinearizerResult< T > pfqn_scat(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< SchedStrategy > &type, double tol, int maxiter, const Matrix< T > &QN0)
Neuse-Chandy SCAT (Self-Correcting Approximation Technique) approximate MVA.
AmvaResult< T > pfqn_clust(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const std::vector< std::vector< std::size_t > > &subnets, const std::vector< std::vector< std::size_t > > &localclasses, ClustInner inner=ClustInner::Linearizer, double tol=1e-6, std::size_t maxiter=1000)
de Souza e Silva-Lavenberg-Muntz Clustering Approximation (CA).
AmvaResult< T > pfqn_pam(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, PamVariant variant=PamVariant::Basic)
Hsieh-Lam Proportional Approximation Methods (PAMB / PAMI / PAMT).
MvaResult< T > pfqn_mvams(const std::vector< T > &lambda, const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< int > &mi, const std::vector< int > &S)
General-purpose exact MVA for mixed networks with multiserver stations.
constexpr int OPEN_CLASS
Marks an open (infinite-population) class in a population vector.
LinearizerMxMethod
Which Linearizer variant solves the closed subnetwork.
MvabloResult< T > pfqn_mvablo(long N, const std::vector< T > &S, const Matrix< T > &P, const std::vector< double > &K, std::vector< bool > isdelay, double tol=1e-4, int iter_max=1000)
Runs MVABLO.
std::vector< T > pfqn_jdfun(const Matrix< T > &nvec, const std::vector< JdScaling< T > > &jdscaling, std::size_t classIdx)
AMVA joint-dependence function for non-product-form scaling.
LinearizerResult< T > pfqn_conwayms(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< int > &nservers, const std::vector< SchedStrategy > &type, double tol, int maxiter, const Matrix< T > &QN0)
Conway's multiserver Linearizer for chain-dependent FCFS queues (Conway 1989, "Fast Approximate Solut...
LinearizerResult< T > pfqn_dmlin(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< SchedStrategy > &type, double tol, int maxiter, const Matrix< T > &QN0, int npasses=3)
de Souza e Silva-Muntz Improved Linearizer (IL).
AmvaResult< T > pfqn_tay(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, double tol=1e-6, std::size_t maxiter=1000, const Matrix< T > &QN0=Matrix< T >())
Tay's arrival-instant approximate MVA.
AmvaResult< T > pfqn_qsa(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const std::vector< AmvaSched > &type, double tol=1e-10, std::size_t maxiter=100, int levels=3)
Queue-Shift Approximation (QSA) for closed product-form networks.
std::vector< T > pfqn_lldfun(const std::vector< T > &n, const Matrix< T > &lldscaling, const std::vector< double > &nservers)
AMVA-QD limited-load-dependence function.
SchmidtResult< T > pfqn_schmidt(const Matrix< T > &D, const std::vector< int > &N, const Matrix< int > &S, const std::vector< SchedStrategy > &sched, const Matrix< T > &v)
Schmidt's MVA for closed networks with general scheduling disciplines and class-dependent multiserver...
AmvaResult< T > pfqn_chow(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const std::vector< AmvaSched > &type, double tol=1e-6, std::size_t maxiter=1000, const Matrix< T > &QN0=Matrix< T >(), ChowVariant variant=ChowVariant::Forward)
JMT-compatible Chow approximate MVA.
MvaResult< T > pfqn_mvaldmx(const std::vector< T > &lambda, const Matrix< T > &D, const std::vector< int > &N, const Matrix< T > &Z, const Matrix< T > &mu)
Exact MVA for mixed open/closed networks with limited load dependence.
constexpr int kOpenClass
Population sentinel marking an open class, standing in for MATLAB's Inf.
LinearizerResult< T > pfqn_linearizermx(const std::vector< T > &lambda, const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< int > &nservers, const std::vector< SchedStrategy > &type, double tol, int maxiter, LinearizerMxMethod method, const Matrix< T > &QN0)
Linearizer for mixed open/closed queueing networks.
AmvaResult< T > pfqn_bs(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const std::vector< AmvaSched > &type, double tol=1e-6, std::size_t maxiter=1000, const Matrix< T > &QN0=Matrix< T >())
Bard-Schweitzer approximate MVA.
AbAmvaResult< T > pfqn_ab_amva(const Matrix< T > &S, const std::vector< int > &N, const Matrix< T > &v, const std::vector< int > &nservers, const std::vector< SchedStrategy > &sched, bool fcfsSchmidt, AbMarginalMethod method)
Akyildiz-Bolch approximate MVA for multi-server BCMP networks.
AmvaResult< T > pfqn_lcp(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const std::vector< AmvaSched > &type, double tol=1e-6, std::size_t maxiter=1000, const Matrix< T > &QN0=Matrix< T >())
Bard Large Customer Population (LCP) approximate MVA.
AmvaResult< T > pfqn_aql(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, double tol=1e-7, std::size_t maxiter=1000)
Aggregate Queue Length (AQL) approximate MVA.
PamVariant
Which of the three proportional approximations to run.
std::vector< T > pfqn_cdfun(const Matrix< T > &nvec, const std::vector< CdScaling< T > > &cdscaling, std::size_t classIdx)
AMVA-QD class-dependence function.
MvaResult< T > pfqn_mvams_ilock(const std::vector< T > &lambda, const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< int > &mi, const std::vector< int > &S, const Matrix< T > &IL)
MVA entry point for models carrying the interlocked-flow correction.
LcfsMvaResult< T > pfqn_lcfsqn_mva(const std::vector< T > &alpha, const std::vector< T > &beta, const std::vector< int > &N)
Exact mean value analysis of the two-station multiclass LCFS network of Casale, QUESTA 2026 (station ...
@ Ab
the Akyildiz-Bolch weight function
SumClosedResult< T > sum_closed(const Matrix< T > &L, const std::vector< long > &N, const std::vector< T > &Z, const std::vector< Servers > &mi, const Matrix< T > &scv, const SumOptions &options=SumOptions())
Summation method (SUM) and its extension (ESUM) for closed queueing networks, including non-product-f...
SumClosingResult< T > sum_closing(const std::vector< T > &lambda0, const std::vector< T > &scva, const Matrix< T > &L, const std::vector< Servers > &mi, const Matrix< T > &scv, const std::vector< long > &N, const std::vector< T > &Z, const ClosingOptions &options=ClosingOptions())
Closing method for open and mixed non-product-form queueing networks, solved with the summation metho...
Conservation laws of a layered queueing network, enumerated from its structure.
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
std::vector< T > ones(std::size_t n)
Column vector of ones, the ubiquitous e in MAP algebra.
A queueing network and its refreshed NetworkStruct.
Smith Queue Decomposition (SQD): approximate MVA for closed networks under Blocking-After-Service (ma...
Akyildiz-Bolch approximate MVA for multi-server BCMP networks.
Aggregate Queue Length (AQL) approximate MVA.
Bard-Schweitzer approximate MVA.
AMVA-QD class-dependence function.
JMT-compatible Chow approximate MVA.
de Souza e Silva-Lavenberg-Muntz Clustering Approximation (CA).
Conway's multiserver Linearizer for chain-dependent FCFS queues (Conway 1989, "Fast Approximate Solut...
de Souza e Silva-Muntz Improved Linearizer (IL).
AMVA joint-dependence function for non-product-form scaling.
Exact mean value analysis of the two-station multiclass LCFS network of Casale, QUESTA 2026 (station ...
Bard Large Customer Population (LCP) approximate MVA.
Linearizer for mixed open/closed queueing networks.
AMVA-QD limited-load-dependence function.
MVABLO: approximate MVA for closed networks with Blocking-After-Service (BAS).
Exact Mean Value Analysis for mixed open/closed networks with multiserver stations.
Hsieh-Lam Proportional Approximation Methods (PAMB / PAMI / PAMT).
Queue-Shift Approximation (QSA) for closed product-form networks.
Neuse-Chandy SCAT (Self-Correcting Approximation Technique) approximate MVA.
Schmidt's MVA for closed networks with general scheduling disciplines and class-dependent multiserver...
Extended Schmidt MVA with queue-aware alpha corrections.
Square-root non-iterative (SQNI) approximation for a single queueing station with per-class delay.
Tay's arrival-instant approximate MVA.
Chain aggregation and de-aggregation.
Ports of the sn_has_* / sn_is_* predicate family of matlab/src/api/sn.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
The chain-level view of a layer, as sn_get_demands_chain returns it.
std::vector< std::size_t > refstatchain
(C) 1-based reference station
std::vector< double > Nchain
(C) population, infinite for an open chain
Matrix< T > STchain
(M x C) mean service time
Matrix< T > Lchain
(M x C) demand
Matrix< T > Vchain
(M x C) visits
Matrix< T > SCVchain
(M x C)
Class-level results, as sn_deaggregate_chain_results returns them.
The options SolverMVA reads.
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
double lG
log of the normalizing constant, the reference's lG.
std::optional< bool > converged
Whether the fixed point met its tolerance, or empty when the handler reports none.
Product-form chain parameters, the split into queueing and delay stations.
Matrix< T > D
(Mq x C) demands at queueing stations
std::vector< T > lambda
(C) chain arrival rates, zero on a closed chain
Matrix< T > Z
(Md x C) demands at delay stations
std::vector< std::size_t > delay_stations
1-based
std::vector< std::size_t > queue_stations
1-based
std::vector< double > S
(Mq) server counts
The four option switches of the reference, with its defaults.
Return value of npfqn_sqd, mirroring [X, Q, U, R].
std::vector< T > R
(M) per-station residence time
std::vector< T > Q
(M) per-station queue length
std::vector< T > U
(M) per-station utilization, capped at one
std::vector< T > X
(M) per-station throughput
Return value of pfqn_ab_amva, mirroring [QN,UN,RN,CN,XN,totiter].
Matrix< T > UN
(M x R) utilization
std::size_t totiter
iterations of the final core pass
std::vector< T > XN
(R) class throughput AT STATION 1
Matrix< T > QN
(M x R) mean queue length
std::vector< T > XN
(R) throughput
Matrix< T > UN
(M x R) utilization
Matrix< T > QN
(M x R) queue length
Matrix< T > Q
(2 x R) mean queue lengths
std::vector< T > T_
(R) per-class throughput
Matrix< T > U
(2 x R) utilizations
Return value of the Linearizer family, mirroring [Q,U,W,C,X,totiter].
Matrix< T > U
(M x R) utilization
std::vector< T > X
(R) per-class throughput
Matrix< T > Q
(M x R) mean queue length
int totiter
total inner iterations across all Core calls
std::vector< T > XN
(R) per-class throughput
Matrix< T > QN
(M x R) mean queue length
double lG
log of the normalizing constant
Matrix< T > UN
(M x R) utilization
Return value of pfqn_mvablo, mirroring [X,Q,U,R,BT,iter,converged]; vectors are (M).
std::vector< T > Q
mean number of jobs, blocked jobs at the source
int iter
total number of passes over all populations
std::vector< T > X
throughput of each station
std::vector< T > U
utilization X.*S, blocked time excluded
bool converged
false if a population exhausted iter_max passes (deadlock)
std::vector< T > R
mean residence time per visit, blocking time included
Return value of pfqn_schmidt_ext, mirroring [XN,QN,UN,CN].
Matrix< T > QN
(M x R) mean queue length
std::vector< T > XN
(R) per-class throughput
Return value of pfqn_schmidt, mirroring [XN,QN,UN,CN].
std::vector< T > XN
(R) per-class throughput
Matrix< T > QN
(M x R) mean queue length
Return value of pfqn_sqni, mirroring [Q, U, X] for the single station.
One station of the network.
CdScaling< T > jdscaling
sn.jdscaling for this station: MATLAB's Station.ljdScaling, the JOINT dependence map eta_i(n),...
std::vector< T > lldscaling
sn.lldscaling for this station: the multiplier at population 1, 2, ... Empty when the station is not ...
CdScaling< T > cdscaling
sn.cdscaling for this station: the class-dependence map, empty when unset.
Closing controls; Kclosed is the population given to the open classes.
static Servers of(long m)
Mirrors the [XN, QN, UN, RN, it] return list of the MATLAB function.
Matrix< T > QN
(M x R) mean queue lengths
Matrix< T > UN
(M x R) utilizations, per server at queueing stations
std::size_t it
iterations of the outer loop
std::vector< T > XN
(R) class throughputs
Mirrors the [XN, QN, UN, RN, TN, it] return list of the MATLAB function.
std::vector< T > XN
(R) class throughputs
Matrix< T > QN
(M x R) queue lengths at the original stations
Matrix< T > UN
(M x R) utilizations at the original stations
Convergence controls, mirroring the trailing (tol, maxiter) arguments.
Summation method (SUM) and its extension (ESUM) for closed queueing networks, including non-product-f...
Closing method for open and mixed non-product-form queueing networks, solved with the summation metho...