5#ifndef LINE_API_PFQN_SJN_H
6#define LINE_API_PFQN_SJN_H
92 std::vector<double>
x;
99 std::vector<double>
XN;
101 std::vector<SjnProfile>
WX;
136inline GammaInc gammainc(
double a,
double x) {
137 if (a <= 0.0)
throw InputError(
"pfqn_sjn: the incomplete gamma needs a positive order");
138 if (x < 0.0)
throw InputError(
"pfqn_sjn: the incomplete gamma needs a non-negative argument");
139 if (x == 0.0)
return GammaInc{0.0, 1.0};
140 const double gln = std::lgamma(a);
141 const double lead = std::exp(-x + a * std::log(x) - gln);
143 double ap = a, del = 1.0 / a, sum = del;
144 for (
int n = 0; n < 1000; ++n) {
148 if (std::fabs(del) < std::fabs(sum) * 1e-16)
break;
150 const double p = sum * lead;
151 return GammaInc{p, 1.0 - p};
153 const double tiny = 1e-300;
154 double b = x + 1.0 - a, c = 1.0 / tiny, d = 1.0 / b, h = d;
155 for (
int i = 1; i <= 1000; ++i) {
156 const double an = -
static_cast<double>(i) * (
static_cast<double>(i) - a);
159 if (std::fabs(d) < tiny) d = tiny;
161 if (std::fabs(c) < tiny) c = tiny;
163 const double del = d * c;
165 if (std::fabs(del - 1.0) < 1e-16)
break;
167 const double q = lead * h;
168 return GammaInc{1.0 - q, q};
173 std::vector<double> w;
175 std::vector<double> mu;
176 bool empty()
const {
return w.empty(); }
183inline SjnFit sjn_fit(
double s,
double cv2) {
185 if (s <= 0.0)
return f;
186 if (cv2 < 0.0)
throw InputError(
"pfqn_sjn: negative squared coefficient of variation");
187 if (std::fabs(cv2 - 1.0) < 1e-8) {
191 }
else if (cv2 < 1.0) {
192 const double kd = std::ceil(1.0 / cv2);
193 const double p = (kd * cv2 - std::sqrt(kd * (1.0 + cv2) - kd * kd * cv2)) / (1.0 + cv2);
194 const double mu = (kd - p) / s;
196 f.k = {
static_cast<int>(kd) - 1,
static_cast<int>(kd)};
199 const double p = 0.5 * (1.0 + std::sqrt((cv2 - 1.0) / (cv2 + 1.0)));
202 f.mu = {2.0 * p / s, 2.0 * (1.0 - p) / s};
208inline std::vector<double> sjn_pdf(
const SjnFit& f,
const std::vector<double>& x) {
210 const double floorx = std::numeric_limits<double>::min();
211 std::vector<double> y(x.size(), 0.0);
212 for (std::size_t j = 0; j < f.w.size(); ++j) {
213 const double kd =
static_cast<double>(f.k[j]), mu = f.mu[j];
214 for (std::size_t i = 0; i < x.size(); ++i)
215 y[i] += f.w[j] * std::exp(kd * std::log(mu) +
216 (kd - 1.0) * std::log(std::max(x[i], floorx)) - mu * x[i] -
223inline std::vector<double> sjn_theta(
const SjnFit& f,
const std::vector<double>& x) {
224 std::vector<double> y(x.size(), 0.0);
225 for (std::size_t j = 0; j < f.w.size(); ++j) {
226 const double kd =
static_cast<double>(f.k[j]), mu = f.mu[j];
227 for (std::size_t i = 0; i < x.size(); ++i)
228 y[i] += f.w[j] * (kd / mu) * gammainc(kd + 1.0, mu * x[i]).p;
234inline double sjn_ccdf(
const SjnFit& f,
double x) {
236 for (std::size_t j = 0; j < f.w.size(); ++j)
237 y += f.w[j] * gammainc(
static_cast<double>(f.k[j]), f.mu[j] * x).q;
246inline double sjn_tailmom(
const SjnFit& f,
double Lx,
double c,
int order) {
248 for (std::size_t j = 0; j < f.w.size(); ++j) {
249 const double kd =
static_cast<double>(f.k[j]), mu = f.mu[j];
250 const double rate = mu + c;
251 const double g = gammainc(kd +
static_cast<double>(order), rate * Lx).q;
252 if (g <= 0.0)
continue;
253 double lg = c * Lx + kd * std::log(mu / rate) + std::log(g);
254 if (order == 1) lg += std::log(kd / rate);
255 y += f.w[j] * std::exp(lg);
261inline double sjn_simpson(
const std::vector<double>& y,
double dx) {
262 const std::size_t n = y.size();
263 double odd = 0.0, even = 0.0;
264 for (std::size_t i = 1; i + 1 < n; i += 2) odd += y[i];
265 for (std::size_t i = 2; i + 1 < n; i += 2) even += y[i];
266 return dx / 3.0 * (y[0] + y[n - 1] + 4.0 * odd + 2.0 * even);
275inline std::vector<double> sjn_cumsimpson(
const std::vector<double>& y,
double dx) {
276 const std::size_t n = y.size();
277 std::vector<double> I(n, 0.0);
278 for (std::size_t i = 2; i < n; i += 2)
279 I[i] = I[i - 2] + dx / 3.0 * (y[i - 2] + 4.0 * y[i - 1] + y[i]);
280 for (std::size_t i = 1; i < n; i += 2) {
282 I[i] = I[i - 1] + dx / 12.0 * (5.0 * y[i - 1] + 8.0 * y[i] - y[i + 1]);
284 I[i] = I[i - 1] + dx / 12.0 * (-y[i - 2] + 8.0 * y[i - 1] + 5.0 * y[i]);
291 double Lx = 0.0, dx = 0.0;
292 std::vector<double> x;
293 Matrix<double> f, theta;
294 std::vector<double> tail0, tail1;
295 std::vector<SjnFit> fit;
299inline SjnGrid sjn_setup(
const std::vector<double>& S,
const std::vector<double>& scv,
300 std::size_t ns,
double Lfactor) {
301 const std::size_t R = S.size(), ngrid = ns + 1;
303 for (
double s : S) smax = std::max(smax, s);
305 throw InputError(
"pfqn_sjn: the station has zero service demand in every class");
307 G.Lx = Lfactor * smax;
308 G.dx = G.Lx /
static_cast<double>(ns);
309 G.x.assign(ngrid, 0.0);
310 for (std::size_t i = 0; i < ngrid; ++i) G.x[i] =
static_cast<double>(i) * G.dx;
311 G.f = Matrix<double>(ngrid, R, 0.0);
312 G.theta = Matrix<double>(ngrid, R, 0.0);
313 G.tail0.assign(R, 0.0);
314 G.tail1.assign(R, 0.0);
315 G.fit.assign(R, SjnFit());
316 for (std::size_t r = 0; r < R; ++r) {
317 G.fit[r] = sjn_fit(S[r], scv[r]);
318 if (G.fit[r].empty())
continue;
319 const std::vector<double> fr = sjn_pdf(G.fit[r], G.x);
320 const std::vector<double> tr = sjn_theta(G.fit[r], G.x);
321 for (std::size_t i = 0; i < ngrid; ++i) {
323 G.theta(i, r) = tr[i];
325 G.tail0[r] = sjn_ccdf(G.fit[r], G.Lx);
326 G.tail1[r] = S[r] - tr[ngrid - 1];
333 std::vector<double> lam, U, Q;
334 const Matrix<double>* W =
nullptr;
335 const Matrix<double>* phi =
nullptr;
336 std::vector<double> phiinf;
340struct SjnStationResult {
342 std::vector<double> W, phi;
344 double tail[3] = {0.0, 0.0, 0.0};
348inline NumericError sjn_singular(std::size_t m) {
350 "pfqn_sjn: the SJN recursion at station " + std::to_string(m + 1) +
351 " has no solution: the work brought by jobs no longer than the tagged one saturates the "
352 "server, at which point long jobs starve and the arrival theorem no longer holds. Reduce "
353 "the load at that station or model it with SolverCTMC or SolverLDES");
365inline SjnStationResult sjn_station(std::size_t m, std::size_t r,
const SjnGrid& G,
366 const std::vector<double>& S,
const std::vector<double>& scv,
367 const std::vector<double>& V,
const SjnState& st,
368 const std::vector<double>& beta,
bool useprio,
369 const std::vector<int>& prio) {
370 const std::size_t R = S.size(), ngrid = G.x.size();
371 std::vector<double> lamb(R), Ub(R), Qb(R);
373 for (std::size_t k = 0; k < R; ++k) {
374 lamb[k] = beta[k] * st.lam[k];
375 Ub[k] = beta[k] * st.U[k];
376 Qb[k] = beta[k] * st.Q[k];
377 RL += (1.0 + scv[k]) * S[k] * Ub[k] / 2.0;
379 std::vector<double> num(ngrid), den(ngrid);
380 double numinf = 0.0, deninf = 0.0;
382 double base = RL, uhi = 0.0;
383 for (std::size_t k = 0; k < R; ++k)
384 if (prio[k] < prio[r]) {
385 base += S[k] * (Qb[k] - Ub[k]);
388 for (std::size_t i = 0; i < ngrid; ++i) {
389 num[i] = base + lamb[r] * (*st.phi)(i, r);
390 den[i] = 1.0 - uhi - lamb[r] * G.theta(i, r);
392 numinf = base + lamb[r] * st.phiinf[r];
393 deninf = 1.0 - uhi - lamb[r] * S[r];
395 double usum = 0.0, phiinfsum = 0.0;
396 for (std::size_t k = 0; k < R; ++k) {
397 usum += lamb[k] * S[k];
398 phiinfsum += lamb[k] * st.phiinf[k];
400 for (std::size_t i = 0; i < ngrid; ++i) {
401 double n = RL, d = 1.0;
402 for (std::size_t k = 0; k < R; ++k) {
403 n += lamb[k] * (*st.phi)(i, k);
404 d -= lamb[k] * G.theta(i, k);
409 numinf = RL + phiinfsum;
412 for (std::size_t i = 0; i < ngrid; ++i)
413 if (den[i] <= 0.0)
throw sjn_singular(m);
414 if (deninf <= 0.0)
throw sjn_singular(m);
416 SjnStationResult out;
417 out.W.assign(ngrid, 0.0);
418 for (std::size_t i = 0; i < ngrid; ++i) out.W[i] = num[i] / den[i];
419 const double Winf = numinf / deninf;
423 slope = G.Lx * lamb[r] * G.f(ngrid - 1, r) *
424 ((*st.W)(ngrid - 1, r) + out.W[ngrid - 1]) / den[ngrid - 1];
427 for (std::size_t k = 0; k < R; ++k)
428 acc += lamb[k] * G.f(ngrid - 1, k) * ((*st.W)(ngrid - 1, k) + out.W[ngrid - 1]);
429 slope = G.Lx * acc / den[ngrid - 1];
431 double a = Winf, b = Winf - out.W[ngrid - 1], c;
435 }
else if (slope < 0.0) {
436 throw NumericError(ErrorKind{
"Instability"},
"pfqn_sjn: the conditional waiting time at SJN station " +
437 std::to_string(m + 1) +
438 " decreases in the job size, which the discipline forbids: the "
439 "recursion has become numerically unstable");
447 std::vector<double> integrand(ngrid);
448 for (std::size_t i = 0; i < ngrid; ++i) integrand[i] = out.W[i] * G.x[i] * G.f(i, r);
449 out.phi = sjn_cumsimpson(integrand, G.dx);
450 out.phiinf = out.phi[ngrid - 1] + a * G.tail1[r] - b * sjn_tailmom(G.fit[r], G.Lx, c, 1);
451 for (std::size_t i = 0; i < ngrid; ++i) integrand[i] = out.W[i] * G.f(i, r);
453 sjn_simpson(integrand, G.dx) + a * G.tail0[r] - b * sjn_tailmom(G.fit[r], G.Lx, c, 0);
454 out.C = V[r] * (S[r] + Wbar);
459inline std::vector<double> sjn_thru(
const Matrix<double>& C,
const std::vector<double>& N,
460 const std::vector<double>& Z) {
461 const std::size_t M = C.rows(), R = N.size();
462 std::vector<double> X(R, 0.0);
463 for (std::size_t r = 0; r < R; ++r) {
464 if (N[r] <= 0.0)
continue;
466 for (std::size_t m = 0; m < M; ++m) den += C(m, r);
474 std::vector<double> X, kappa;
492inline SjnCapResult sjn_cap(Matrix<double>& C,
const Matrix<double>& L,
493 const std::vector<double>& N,
const std::vector<double>& Z,
494 const std::vector<std::size_t>& sjnset,
double umax) {
495 const std::size_t nsjn = sjnset.size(), R = N.size();
497 out.kappa.assign(nsjn, 1.0);
498 out.X = sjn_thru(C, N, Z);
499 if (nsjn == 0)
return out;
501 throw InputError(
"pfqn_sjn: the utilization cap must be strictly below one, the response "
502 "time equation is singular at one");
504 const auto rho_at = [&](std::size_t m,
const std::vector<double>& Wq,
double kappa) {
505 std::vector<double> saved(R);
506 for (std::size_t r = 0; r < R; ++r) {
508 C(m, r) = L(m, r) + kappa * Wq[r];
510 const std::vector<double> X = sjn_thru(C, N, Z);
512 for (std::size_t r = 0; r < R; ++r) rho += X[r] * L(m, r);
513 for (std::size_t r = 0; r < R; ++r) C(m, r) = saved[r];
516 for (
int sweep = 0; sweep < 20; ++sweep) {
518 for (std::size_t q = 0; q < nsjn; ++q) {
519 const std::size_t m = sjnset[q];
521 for (std::size_t r = 0; r < R; ++r) rho += out.X[r] * L(m, r);
522 if (rho <= umax)
continue;
525 std::vector<double> Wq(R);
526 for (std::size_t r = 0; r < R; ++r) Wq[r] = C(m, r) - L(m, r);
528 while (rho_at(m, Wq, hi) > umax) {
532 "pfqn_sjn: station " + std::to_string(m + 1) +
533 " cannot be brought under the utilization cap by any waiting time: its "
534 "service demands alone saturate it at this population");
537 for (
int b = 0; b < 200; ++b) {
538 const double mid = (lo + hi) / 2.0;
539 if (rho_at(m, Wq, mid) > umax)
545 for (std::size_t r = 0; r < R; ++r) C(m, r) = L(m, r) + hi * Wq[r];
546 out.X = sjn_thru(C, N, Z);
548 if (!viol)
return out;
550 throw NumericError(ErrorKind{
"NonConvergence"},
"pfqn_sjn: the utilization cap did not settle across the SJN stations");
555 std::size_t M = 0, R = 0;
556 std::vector<double> N, Z;
557 Matrix<double> scv, V, S;
558 std::vector<std::size_t> sjnset;
563inline SjnArgs sjn_args(
const Matrix<double>& L,
const std::vector<double>& N,
564 const std::vector<double>& Z,
const Matrix<double>& scv,
565 const std::vector<std::size_t>& sjnset,
const Matrix<double>& V,
566 const SjnOptions& options) {
572 throw InputError(
"pfqn_sjn: demand matrix and population vector have different number of "
574 a.N.assign(a.R, 0.0);
575 for (std::size_t r = 0; r < a.R; ++r) {
576 a.N[r] = std::round(N[r]);
577 if (a.N[r] < 0.0)
throw InputError(
"pfqn_sjn: negative class populations");
579 a.Z = Z.empty() ? std::vector<double>(a.R, 0.0) : Z;
580 if (a.Z.size() != a.R)
throw InputError(
"pfqn_sjn: Z has the wrong length");
581 a.scv = scv.empty() ? Matrix<double>(a.M, a.R, 1.0) : scv;
582 if (a.scv.rows() != a.M || a.scv.cols() != a.R)
583 throw InputError(
"pfqn_sjn: scv has the wrong shape");
585 for (std::size_t i = 0; i < a.sjnset.size(); ++i) {
586 if (a.sjnset[i] >= a.M)
587 throw InputError(
"pfqn_sjn: sjnset contains a station index outside 1..M");
588 for (std::size_t j = i + 1; j < a.sjnset.size(); ++j)
589 if (a.sjnset[i] == a.sjnset[j])
590 throw InputError(
"pfqn_sjn: sjnset repeats a station index");
592 a.V = V.empty() ? Matrix<double>(a.M, a.R, 1.0) : V;
593 if (a.V.rows() != a.M || a.V.cols() != a.R)
594 throw InputError(
"pfqn_sjn: V has the wrong shape");
597 a.S = Matrix<double>(a.M, a.R, 0.0);
598 for (std::size_t m = 0; m < a.M; ++m)
599 for (std::size_t r = 0; r < a.R; ++r)
600 if (a.V(m, r) > 0.0) a.S(m, r) = L(m, r) / a.V(m, r);
601 if (a.opt.ns == 0 || a.opt.ns % 2 != 0)
602 throw InputError(
"pfqn_sjn: options.ns must be even, composite Simpson integrates over "
603 "panels of two subdivisions");
604 if (a.opt.umax <= 0.0 || a.opt.umax >= 1.0)
605 throw InputError(
"pfqn_sjn: options.umax must lie strictly between zero and one");
606 if (!a.opt.prio.empty()) {
607 if (a.opt.prio.size() != a.R)
608 throw InputError(
"pfqn_sjn: options.prio must have one priority level per class");
609 for (std::size_t i = 0; i < a.R; ++i)
610 for (std::size_t j = i + 1; j < a.R; ++j)
611 if (a.opt.prio[i] == a.opt.prio[j])
612 throw InputError(
"pfqn_sjn: options.prio must assign distinct levels, ties "
613 "across classes are not covered by the SJN priority "
646 const detail::SjnArgs a = detail::sjn_args(L, N, Z, scv, sjnset, V, options);
647 const std::size_t M = a.M, R = a.R, nsjn = a.sjnset.size();
648 const bool useprio = !a.opt.prio.empty();
649 const std::size_t ns = a.opt.ns, ngrid = ns + 1;
651 std::vector<detail::SjnGrid> G(nsjn);
652 for (std::size_t q = 0; q < nsjn; ++q) {
653 std::vector<double> Sq(R), scvq(R);
654 for (std::size_t r = 0; r < R; ++r) {
655 Sq[r] = a.S(a.sjnset[q], r);
656 scvq[r] = a.scv(a.sjnset[q], r);
658 G[q] = detail::sjn_setup(Sq, scvq, ns, a.opt.Lfactor);
663 double lattice = 1.0;
664 for (std::size_t r = 0; r < R; ++r) lattice *= a.N[r] + 1.0;
666 throw InputError(
"pfqn_mvasjn: the population lattice has " + std::to_string(lattice) +
667 " states and does not fit; use pfqn_amvasjn (method 'amva')");
668 std::vector<std::size_t> stride(R, 1);
669 std::size_t npop = 1;
670 for (std::size_t r = 0; r < R; ++r) {
672 npop *=
static_cast<std::size_t
>(a.N[r]) + 1;
677 std::vector<double> Xp(npop * R, 0.0), Qp(npop * M * R, 0.0), Up(npop * M * R, 0.0),
678 Cp(npop * M * R, 0.0);
679 std::vector<Matrix<double>> Wp(nsjn * npop), Pp(nsjn * npop);
680 std::vector<double> Ip(nsjn * npop * R, 0.0), Tp(nsjn * npop * R * 3, 0.0);
686 for (std::size_t idx = 1; idx < npop; ++idx) {
687 std::vector<double> n(R, 0.0);
688 for (std::size_t r = 0; r < R; ++r)
689 n[r] =
static_cast<double>((idx / stride[r]) %
690 (
static_cast<std::size_t
>(a.N[r]) + 1));
692 for (std::size_t r = 0; r < R; ++r) {
693 if (n[r] == 0.0)
continue;
694 const std::size_t iprev = idx - stride[r];
695 for (std::size_t m = 0; m < M; ++m) {
696 std::size_t q = nsjn;
697 for (std::size_t t = 0; t < nsjn; ++t)
698 if (a.sjnset[t] == m) q = t;
701 for (std::size_t k = 0; k < R; ++k) qsum += Qp[(iprev * M + m) * R + k];
702 Call(m, r) = L(m, r) * (1.0 + qsum);
707 const std::vector<double> beta(R, 1.0);
709 st.lam.assign(R, 0.0);
712 st.phiinf.assign(R, 0.0);
713 for (std::size_t k = 0; k < R; ++k) {
714 st.lam[k] = Xp[iprev * R + k] * a.V(m, k);
715 st.U[k] = Up[(iprev * M + m) * R + k];
716 st.Q[k] = Qp[(iprev * M + m) * R + k];
717 st.phiinf[k] = Ip[(q * npop + iprev) * R + k];
719 st.W = &Wp[q * npop + iprev];
720 st.phi = &Pp[q * npop + iprev];
721 std::vector<double> Sm(R), scvm(R), Vm(R);
722 for (std::size_t k = 0; k < R; ++k) {
724 scvm[k] = a.scv(m, k);
727 const detail::SjnStationResult sr =
728 detail::sjn_station(m, r, G[q], Sm, scvm, Vm, st, beta, useprio, a.opt.prio);
730 for (std::size_t i = 0; i < ngrid; ++i) {
731 Wp[q * npop + idx](i, r) = sr.W[i];
732 Pp[q * npop + idx](i, r) = sr.phi[i];
734 Ip[(q * npop + idx) * R + r] = sr.phiinf;
735 for (
int t = 0; t < 3; ++t)
736 Tp[((q * npop + idx) * R + r) * 3 + t] = sr.tail[t];
739 const detail::SjnCapResult cap = detail::sjn_cap(Call, L, n, a.Z, a.sjnset, a.opt.umax);
743 for (std::size_t r = 0; r < R; ++r)
744 npos += (r ?
" " :
"") + std::to_string(
static_cast<long long>(n[r]));
746 "pfqn_mvasjn: the utilization cap of " + std::to_string(a.opt.umax) +
747 " was binding at an SJN station at population [" + npos +
748 "]: the station is in the starvation regime, where the conditional waiting time "
749 "equation has no solution and the population lattice no valid continuation. Use "
750 "the Schweitzer fixed point (pfqn_amvasjn, method 'amva'), SolverCTMC or "
753 for (std::size_t r = 0; r < R; ++r) Xp[idx * R + r] = cap.X[r];
754 for (std::size_t m = 0; m < M; ++m)
755 for (std::size_t r = 0; r < R; ++r) {
756 Cp[(idx * M + m) * R + r] = Call(m, r);
757 Qp[(idx * M + m) * R + r] = cap.X[r] * Call(m, r);
758 Up[(idx * M + m) * R + r] = cap.X[r] * L(m, r);
762 const std::size_t last = npop - 1;
763 res.
XN.assign(R, 0.0);
767 for (std::size_t r = 0; r < R; ++r) res.
XN[r] = Xp[last * R + r];
768 for (std::size_t m = 0; m < M; ++m)
769 for (std::size_t r = 0; r < R; ++r) {
770 res.
QN(m, r) = Qp[(last * M + m) * R + r];
771 res.
UN(m, r) = Up[(last * M + m) * R + r];
772 res.
CN(m, r) = Cp[(last * M + m) * R + r];
775 for (std::size_t q = 0; q < nsjn; ++q) {
776 res.
WX[q].station = a.sjnset[q] + 1;
777 res.
WX[q].x = G[q].x;
778 res.
WX[q].W = Wp[q * npop + last];
780 for (std::size_t r = 0; r < R; ++r)
781 for (
int t = 0; t < 3; ++t)
782 res.
WX[q].tail(r, t) = Tp[((q * npop + last) * R + r) * 3 + t];
805 const detail::SjnArgs a = detail::sjn_args(L, N, Z, scv, sjnset, V, options);
806 const std::size_t M = a.M, R = a.R, nsjn = a.sjnset.size();
807 const bool useprio = !a.opt.prio.empty();
808 const std::size_t ns = a.opt.ns, ngrid = ns + 1;
810 std::vector<detail::SjnGrid> G(nsjn);
811 for (std::size_t q = 0; q < nsjn; ++q) {
812 std::vector<double> Sq(R), scvq(R);
813 for (std::size_t r = 0; r < R; ++r) {
814 Sq[r] = a.S(a.sjnset[q], r);
815 scvq[r] = a.scv(a.sjnset[q], r);
817 G[q] = detail::sjn_setup(Sq, scvq, ns, a.opt.Lfactor);
824 pfqn_bs(L, a.N, a.Z, std::vector<AmvaSched>(), a.opt.tol, a.opt.iter_max);
831 std::vector<Matrix<double>> W(nsjn,
Matrix<double>(ngrid, R, 0.0));
832 std::vector<Matrix<double>> P(nsjn,
Matrix<double>(ngrid, R, 0.0));
837 bool converged =
false;
839 while (!converged && it < a.opt.iter_max) {
842 std::vector<Matrix<double>> Wit = W, Pit = P, Tit =
Tail;
844 for (std::size_t r = 0; r < R; ++r) {
845 if (a.N[r] == 0.0)
continue;
846 std::vector<double> beta(R, 1.0);
847 beta[r] = (a.N[r] - 1.0) / a.N[r];
848 for (std::size_t m = 0; m < M; ++m) {
849 std::size_t q = nsjn;
850 for (std::size_t t = 0; t < nsjn; ++t)
851 if (a.sjnset[t] == m) q = t;
854 for (std::size_t k = 0; k < R; ++k) qsum += beta[k] * res.
QN(m, k);
855 Cit(m, r) = L(m, r) * (1.0 + qsum);
859 st.lam.assign(R, 0.0);
862 st.phiinf.assign(R, 0.0);
863 for (std::size_t k = 0; k < R; ++k) {
864 st.lam[k] = res.
XN[k] * a.V(m, k);
865 st.U[k] = res.
UN(m, k);
866 st.Q[k] = res.
QN(m, k);
867 st.phiinf[k] = Iinf(q, k);
871 std::vector<double> Sm(R), scvm(R), Vm(R);
872 for (std::size_t k = 0; k < R; ++k) {
874 scvm[k] = a.scv(m, k);
877 const detail::SjnStationResult sr =
878 detail::sjn_station(m, r, G[q], Sm, scvm, Vm, st, beta, useprio, a.opt.prio);
880 for (std::size_t i = 0; i < ngrid; ++i) {
881 Wit[q](i, r) = sr.W[i];
882 Pit[q](i, r) = sr.phi[i];
884 Iit(q, r) = sr.phiinf;
885 for (
int t = 0; t < 3; ++t) Tit[q](r, t) = sr.tail[t];
888 const detail::SjnCapResult cap = detail::sjn_cap(Cit, L, a.N, a.Z, a.sjnset, a.opt.umax);
891 for (std::size_t q = 0; q < nsjn; ++q) {
892 const double kq = cap.kappa[q];
893 for (std::size_t i = 0; i < ngrid; ++i)
894 for (std::size_t r = 0; r < R; ++r) {
898 for (std::size_t r = 0; r < R; ++r) {
906 for (std::size_t m = 0; m < M; ++m)
907 for (std::size_t r = 0; r < R; ++r) {
908 Qit(m, r) = cap.X[r] * Cit(m, r);
909 Uit(m, r) = cap.X[r] * L(m, r);
912 for (std::size_t m = 0; m < M; ++m)
913 for (std::size_t r = 0; r < R; ++r)
914 delta = std::max(delta, std::fabs(Qit(m, r) - res.
QN(m, r)));
915 for (std::size_t q = 0; q < nsjn; ++q)
916 for (std::size_t i = 0; i < ngrid; ++i)
917 for (std::size_t r = 0; r < R; ++r)
918 delta = std::max(delta, std::fabs(Wit[q](i, r) - W[q](i, r)));
927 converged = delta < a.opt.tol;
933 for (std::size_t q = 0; q < nsjn; ++q) {
934 res.
WX[q].station = a.sjnset[q] + 1;
935 res.
WX[q].x = G[q].x;
NumericError(const std::string &what)
SjnStarvationError(const std::string &what)
The exception types the port throws.
Dense matrix and non-owning view.
@ Tail
the geometric single-class identity
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.
SjnResult pfqn_amvasjn(const Matrix< double > &L, const std::vector< double > &N, const std::vector< double > &Z, const Matrix< double > &scv, const std::vector< std::size_t > &sjnset, const Matrix< double > &V, const SjnOptions &options)
Schweitzer fixed point counterpart of pfqn_mvasjn.
SjnResult pfqn_mvasjn(const Matrix< double > &L, const std::vector< double > &N, const std::vector< double > &Z, const Matrix< double > &scv, const std::vector< std::size_t > &sjnset, const Matrix< double > &V, const SjnOptions &options)
Exact-lattice MVA for closed networks with SJN stations, the unidirectional scheme of Kant 1992.
Conservation laws of a layered queueing network, enumerated from its structure.
Bard-Schweitzer approximate MVA.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Matrix< T > RN
(M x R) residence time
std::vector< T > XN
(R) throughput
Matrix< T > UN
(M x R) utilization
Matrix< T > QN
(M x R) queue length
Options of the SJN solvers, the fields sjn_args fills in.
std::size_t ns
grid subdivisions, must be even
double umax
utilization cap, strictly below one
double Lfactor
grid extent in units of the largest mean service time
std::vector< int > prio
distinct levels, 1 = highest; empty for the pooled reading
The conditional waiting time profile at one SJN station, the reference's WX.
Matrix< double > tail
(R x 3), the tail parameters a, b, c
Matrix< double > W
(ngrid x R)
std::vector< double > x
the grid
std::size_t station
1-based row index into L
Return block of pfqn_mvasjn and pfqn_amvasjn.
Matrix< double > CN
(M x R)
std::vector< double > XN
(R)
std::vector< SjnProfile > WX
bool capped
the utilization cap was binding somewhere
bool converged
always true for the lattice recursion