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;
135inline GammaInc gammainc(
double a,
double x) {
136 if (a <= 0.0)
throw InputError(
"pfqn_sjn: the incomplete gamma needs a positive order");
137 if (x < 0.0)
throw InputError(
"pfqn_sjn: the incomplete gamma needs a non-negative argument");
138 if (x == 0.0)
return GammaInc{0.0, 1.0};
139 const double gln = std::lgamma(a);
140 const double lead = std::exp(-x + a * std::log(x) - gln);
142 double ap = a, del = 1.0 / a, sum = del;
143 for (
int n = 0; n < 1000; ++n) {
147 if (std::fabs(del) < std::fabs(sum) * 1e-16)
break;
149 const double p = sum * lead;
150 return GammaInc{p, 1.0 - p};
152 const double tiny = 1e-300;
153 double b = x + 1.0 - a, c = 1.0 / tiny, d = 1.0 / b, h = d;
154 for (
int i = 1; i <= 1000; ++i) {
155 const double an = -
static_cast<double>(i) * (
static_cast<double>(i) - a);
158 if (std::fabs(d) < tiny) d = tiny;
160 if (std::fabs(c) < tiny) c = tiny;
162 const double del = d * c;
164 if (std::fabs(del - 1.0) < 1e-16)
break;
166 const double q = lead * h;
167 return GammaInc{1.0 - q, q};
172 std::vector<double> w;
174 std::vector<double> mu;
175 bool empty()
const {
return w.empty(); }
182inline SjnFit sjn_fit(
double s,
double cv2) {
184 if (s <= 0.0)
return f;
185 if (cv2 < 0.0)
throw InputError(
"pfqn_sjn: negative squared coefficient of variation");
186 if (std::fabs(cv2 - 1.0) < 1e-8) {
190 }
else if (cv2 < 1.0) {
191 const double kd = std::ceil(1.0 / cv2);
192 const double p = (kd * cv2 - std::sqrt(kd * (1.0 + cv2) - kd * kd * cv2)) / (1.0 + cv2);
193 const double mu = (kd - p) / s;
195 f.k = {
static_cast<int>(kd) - 1,
static_cast<int>(kd)};
198 const double p = 0.5 * (1.0 + std::sqrt((cv2 - 1.0) / (cv2 + 1.0)));
201 f.mu = {2.0 * p / s, 2.0 * (1.0 - p) / s};
207inline std::vector<double> sjn_pdf(
const SjnFit& f,
const std::vector<double>& x) {
209 const double floorx = std::numeric_limits<double>::min();
210 std::vector<double> y(x.size(), 0.0);
211 for (std::size_t j = 0; j < f.w.size(); ++j) {
212 const double kd =
static_cast<double>(f.k[j]), mu = f.mu[j];
213 for (std::size_t i = 0; i < x.size(); ++i)
214 y[i] += f.w[j] * std::exp(kd * std::log(mu) +
215 (kd - 1.0) * std::log(std::max(x[i], floorx)) - mu * x[i] -
222inline std::vector<double> sjn_theta(
const SjnFit& f,
const std::vector<double>& x) {
223 std::vector<double> y(x.size(), 0.0);
224 for (std::size_t j = 0; j < f.w.size(); ++j) {
225 const double kd =
static_cast<double>(f.k[j]), mu = f.mu[j];
226 for (std::size_t i = 0; i < x.size(); ++i)
227 y[i] += f.w[j] * (kd / mu) * gammainc(kd + 1.0, mu * x[i]).p;
233inline double sjn_ccdf(
const SjnFit& f,
double x) {
235 for (std::size_t j = 0; j < f.w.size(); ++j)
236 y += f.w[j] * gammainc(
static_cast<double>(f.k[j]), f.mu[j] * x).q;
245inline double sjn_tailmom(
const SjnFit& f,
double Lx,
double c,
int order) {
247 for (std::size_t j = 0; j < f.w.size(); ++j) {
248 const double kd =
static_cast<double>(f.k[j]), mu = f.mu[j];
249 const double rate = mu + c;
250 const double g = gammainc(kd +
static_cast<double>(order), rate * Lx).q;
251 if (g <= 0.0)
continue;
252 double lg = c * Lx + kd * std::log(mu / rate) + std::log(g);
253 if (order == 1) lg += std::log(kd / rate);
254 y += f.w[j] * std::exp(lg);
260inline double sjn_simpson(
const std::vector<double>& y,
double dx) {
261 const std::size_t n = y.size();
262 double odd = 0.0, even = 0.0;
263 for (std::size_t i = 1; i + 1 < n; i += 2) odd += y[i];
264 for (std::size_t i = 2; i + 1 < n; i += 2) even += y[i];
265 return dx / 3.0 * (y[0] + y[n - 1] + 4.0 * odd + 2.0 * even);
274inline std::vector<double> sjn_cumsimpson(
const std::vector<double>& y,
double dx) {
275 const std::size_t n = y.size();
276 std::vector<double> I(n, 0.0);
277 for (std::size_t i = 2; i < n; i += 2)
278 I[i] = I[i - 2] + dx / 3.0 * (y[i - 2] + 4.0 * y[i - 1] + y[i]);
279 for (std::size_t i = 1; i < n; i += 2) {
281 I[i] = I[i - 1] + dx / 12.0 * (5.0 * y[i - 1] + 8.0 * y[i] - y[i + 1]);
283 I[i] = I[i - 1] + dx / 12.0 * (-y[i - 2] + 8.0 * y[i - 1] + 5.0 * y[i]);
290 double Lx = 0.0, dx = 0.0;
291 std::vector<double> x;
292 Matrix<double> f, theta;
293 std::vector<double> tail0, tail1;
294 std::vector<SjnFit> fit;
298inline SjnGrid sjn_setup(
const std::vector<double>& S,
const std::vector<double>& scv,
299 std::size_t ns,
double Lfactor) {
300 const std::size_t R = S.size(), ngrid = ns + 1;
302 for (
double s : S) smax = std::max(smax, s);
304 throw InputError(
"pfqn_sjn: the station has zero service demand in every class");
306 G.Lx = Lfactor * smax;
307 G.dx = G.Lx /
static_cast<double>(ns);
308 G.x.assign(ngrid, 0.0);
309 for (std::size_t i = 0; i < ngrid; ++i) G.x[i] =
static_cast<double>(i) * G.dx;
310 G.f = Matrix<double>(ngrid, R, 0.0);
311 G.theta = Matrix<double>(ngrid, R, 0.0);
312 G.tail0.assign(R, 0.0);
313 G.tail1.assign(R, 0.0);
314 G.fit.assign(R, SjnFit());
315 for (std::size_t r = 0; r < R; ++r) {
316 G.fit[r] = sjn_fit(S[r], scv[r]);
317 if (G.fit[r].empty())
continue;
318 const std::vector<double> fr = sjn_pdf(G.fit[r], G.x);
319 const std::vector<double> tr = sjn_theta(G.fit[r], G.x);
320 for (std::size_t i = 0; i < ngrid; ++i) {
322 G.theta(i, r) = tr[i];
324 G.tail0[r] = sjn_ccdf(G.fit[r], G.Lx);
325 G.tail1[r] = S[r] - tr[ngrid - 1];
332 std::vector<double> lam, U, Q;
333 const Matrix<double>* W =
nullptr;
334 const Matrix<double>* phi =
nullptr;
335 std::vector<double> phiinf;
339struct SjnStationResult {
341 std::vector<double> W, phi;
343 double tail[3] = {0.0, 0.0, 0.0};
347inline NumericError sjn_singular(std::size_t m) {
349 "pfqn_sjn: the SJN recursion at station " + std::to_string(m + 1) +
350 " has no solution: the work brought by jobs no longer than the tagged one saturates the "
351 "server, at which point long jobs starve and the arrival theorem no longer holds. Reduce "
352 "the load at that station or model it with SolverCTMC or SolverLDES");
364inline SjnStationResult sjn_station(std::size_t m, std::size_t r,
const SjnGrid& G,
365 const std::vector<double>& S,
const std::vector<double>& scv,
366 const std::vector<double>& V,
const SjnState& st,
367 const std::vector<double>& beta,
bool useprio,
368 const std::vector<int>& prio) {
369 const std::size_t R = S.size(), ngrid = G.x.size();
370 std::vector<double> lamb(R), Ub(R), Qb(R);
372 for (std::size_t k = 0; k < R; ++k) {
373 lamb[k] = beta[k] * st.lam[k];
374 Ub[k] = beta[k] * st.U[k];
375 Qb[k] = beta[k] * st.Q[k];
376 RL += (1.0 + scv[k]) * S[k] * Ub[k] / 2.0;
378 std::vector<double> num(ngrid), den(ngrid);
379 double numinf = 0.0, deninf = 0.0;
381 double base = RL, uhi = 0.0;
382 for (std::size_t k = 0; k < R; ++k)
383 if (prio[k] < prio[r]) {
384 base += S[k] * (Qb[k] - Ub[k]);
387 for (std::size_t i = 0; i < ngrid; ++i) {
388 num[i] = base + lamb[r] * (*st.phi)(i, r);
389 den[i] = 1.0 - uhi - lamb[r] * G.theta(i, r);
391 numinf = base + lamb[r] * st.phiinf[r];
392 deninf = 1.0 - uhi - lamb[r] * S[r];
394 double usum = 0.0, phiinfsum = 0.0;
395 for (std::size_t k = 0; k < R; ++k) {
396 usum += lamb[k] * S[k];
397 phiinfsum += lamb[k] * st.phiinf[k];
399 for (std::size_t i = 0; i < ngrid; ++i) {
400 double n = RL, d = 1.0;
401 for (std::size_t k = 0; k < R; ++k) {
402 n += lamb[k] * (*st.phi)(i, k);
403 d -= lamb[k] * G.theta(i, k);
408 numinf = RL + phiinfsum;
411 for (std::size_t i = 0; i < ngrid; ++i)
412 if (den[i] <= 0.0)
throw sjn_singular(m);
413 if (deninf <= 0.0)
throw sjn_singular(m);
415 SjnStationResult out;
416 out.W.assign(ngrid, 0.0);
417 for (std::size_t i = 0; i < ngrid; ++i) out.W[i] = num[i] / den[i];
418 const double Winf = numinf / deninf;
422 slope = G.Lx * lamb[r] * G.f(ngrid - 1, r) *
423 ((*st.W)(ngrid - 1, r) + out.W[ngrid - 1]) / den[ngrid - 1];
426 for (std::size_t k = 0; k < R; ++k)
427 acc += lamb[k] * G.f(ngrid - 1, k) * ((*st.W)(ngrid - 1, k) + out.W[ngrid - 1]);
428 slope = G.Lx * acc / den[ngrid - 1];
430 double a = Winf, b = Winf - out.W[ngrid - 1], c;
434 }
else if (slope < 0.0) {
435 throw NumericError(
"pfqn_sjn: the conditional waiting time at SJN station " +
436 std::to_string(m + 1) +
437 " decreases in the job size, which the discipline forbids: the "
438 "recursion has become numerically unstable");
446 std::vector<double> integrand(ngrid);
447 for (std::size_t i = 0; i < ngrid; ++i) integrand[i] = out.W[i] * G.x[i] * G.f(i, r);
448 out.phi = sjn_cumsimpson(integrand, G.dx);
449 out.phiinf = out.phi[ngrid - 1] + a * G.tail1[r] - b * sjn_tailmom(G.fit[r], G.Lx, c, 1);
450 for (std::size_t i = 0; i < ngrid; ++i) integrand[i] = out.W[i] * G.f(i, r);
452 sjn_simpson(integrand, G.dx) + a * G.tail0[r] - b * sjn_tailmom(G.fit[r], G.Lx, c, 0);
453 out.C = V[r] * (S[r] + Wbar);
458inline std::vector<double> sjn_thru(
const Matrix<double>& C,
const std::vector<double>& N,
459 const std::vector<double>& Z) {
460 const std::size_t M = C.rows(), R = N.size();
461 std::vector<double> X(R, 0.0);
462 for (std::size_t r = 0; r < R; ++r) {
463 if (N[r] <= 0.0)
continue;
465 for (std::size_t m = 0; m < M; ++m) den += C(m, r);
473 std::vector<double> X, kappa;
491inline SjnCapResult sjn_cap(Matrix<double>& C,
const Matrix<double>& L,
492 const std::vector<double>& N,
const std::vector<double>& Z,
493 const std::vector<std::size_t>& sjnset,
double umax) {
494 const std::size_t nsjn = sjnset.size(), R = N.size();
496 out.kappa.assign(nsjn, 1.0);
497 out.X = sjn_thru(C, N, Z);
498 if (nsjn == 0)
return out;
500 throw InputError(
"pfqn_sjn: the utilization cap must be strictly below one, the response "
501 "time equation is singular at one");
503 const auto rho_at = [&](std::size_t m,
const std::vector<double>& Wq,
double kappa) {
504 std::vector<double> saved(R);
505 for (std::size_t r = 0; r < R; ++r) {
507 C(m, r) = L(m, r) + kappa * Wq[r];
509 const std::vector<double> X = sjn_thru(C, N, Z);
511 for (std::size_t r = 0; r < R; ++r) rho += X[r] * L(m, r);
512 for (std::size_t r = 0; r < R; ++r) C(m, r) = saved[r];
515 for (
int sweep = 0; sweep < 20; ++sweep) {
517 for (std::size_t q = 0; q < nsjn; ++q) {
518 const std::size_t m = sjnset[q];
520 for (std::size_t r = 0; r < R; ++r) rho += out.X[r] * L(m, r);
521 if (rho <= umax)
continue;
524 std::vector<double> Wq(R);
525 for (std::size_t r = 0; r < R; ++r) Wq[r] = C(m, r) - L(m, r);
527 while (rho_at(m, Wq, hi) > umax) {
531 "pfqn_sjn: station " + std::to_string(m + 1) +
532 " cannot be brought under the utilization cap by any waiting time: its "
533 "service demands alone saturate it at this population");
536 for (
int b = 0; b < 200; ++b) {
537 const double mid = (lo + hi) / 2.0;
538 if (rho_at(m, Wq, mid) > umax)
544 for (std::size_t r = 0; r < R; ++r) C(m, r) = L(m, r) + hi * Wq[r];
545 out.X = sjn_thru(C, N, Z);
547 if (!viol)
return out;
549 throw NumericError(
"pfqn_sjn: the utilization cap did not settle across the SJN stations");
554 std::size_t M = 0, R = 0;
555 std::vector<double> N, Z;
556 Matrix<double> scv, V, S;
557 std::vector<std::size_t> sjnset;
562inline SjnArgs sjn_args(
const Matrix<double>& L,
const std::vector<double>& N,
563 const std::vector<double>& Z,
const Matrix<double>& scv,
564 const std::vector<std::size_t>& sjnset,
const Matrix<double>& V,
565 const SjnOptions& options) {
571 throw InputError(
"pfqn_sjn: demand matrix and population vector have different number of "
573 a.N.assign(a.R, 0.0);
574 for (std::size_t r = 0; r < a.R; ++r) {
575 a.N[r] = std::round(N[r]);
576 if (a.N[r] < 0.0)
throw InputError(
"pfqn_sjn: negative class populations");
578 a.Z = Z.empty() ? std::vector<double>(a.R, 0.0) : Z;
579 if (a.Z.size() != a.R)
throw InputError(
"pfqn_sjn: Z has the wrong length");
580 a.scv = scv.empty() ? Matrix<double>(a.M, a.R, 1.0) : scv;
581 if (a.scv.rows() != a.M || a.scv.cols() != a.R)
582 throw InputError(
"pfqn_sjn: scv has the wrong shape");
584 for (std::size_t i = 0; i < a.sjnset.size(); ++i) {
585 if (a.sjnset[i] >= a.M)
586 throw InputError(
"pfqn_sjn: sjnset contains a station index outside 1..M");
587 for (std::size_t j = i + 1; j < a.sjnset.size(); ++j)
588 if (a.sjnset[i] == a.sjnset[j])
589 throw InputError(
"pfqn_sjn: sjnset repeats a station index");
591 a.V = V.empty() ? Matrix<double>(a.M, a.R, 1.0) : V;
592 if (a.V.rows() != a.M || a.V.cols() != a.R)
593 throw InputError(
"pfqn_sjn: V has the wrong shape");
596 a.S = Matrix<double>(a.M, a.R, 0.0);
597 for (std::size_t m = 0; m < a.M; ++m)
598 for (std::size_t r = 0; r < a.R; ++r)
599 if (a.V(m, r) > 0.0) a.S(m, r) = L(m, r) / a.V(m, r);
600 if (a.opt.ns == 0 || a.opt.ns % 2 != 0)
601 throw InputError(
"pfqn_sjn: options.ns must be even, composite Simpson integrates over "
602 "panels of two subdivisions");
603 if (a.opt.umax <= 0.0 || a.opt.umax >= 1.0)
604 throw InputError(
"pfqn_sjn: options.umax must lie strictly between zero and one");
605 if (!a.opt.prio.empty()) {
606 if (a.opt.prio.size() != a.R)
607 throw InputError(
"pfqn_sjn: options.prio must have one priority level per class");
608 for (std::size_t i = 0; i < a.R; ++i)
609 for (std::size_t j = i + 1; j < a.R; ++j)
610 if (a.opt.prio[i] == a.opt.prio[j])
611 throw InputError(
"pfqn_sjn: options.prio must assign distinct levels, ties "
612 "across classes are not covered by the SJN priority "
645 const detail::SjnArgs a = detail::sjn_args(L, N, Z, scv, sjnset, V, options);
646 const std::size_t M = a.M, R = a.R, nsjn = a.sjnset.size();
647 const bool useprio = !a.opt.prio.empty();
648 const std::size_t ns = a.opt.ns, ngrid = ns + 1;
650 std::vector<detail::SjnGrid> G(nsjn);
651 for (std::size_t q = 0; q < nsjn; ++q) {
652 std::vector<double> Sq(R), scvq(R);
653 for (std::size_t r = 0; r < R; ++r) {
654 Sq[r] = a.S(a.sjnset[q], r);
655 scvq[r] = a.scv(a.sjnset[q], r);
657 G[q] = detail::sjn_setup(Sq, scvq, ns, a.opt.Lfactor);
662 double lattice = 1.0;
663 for (std::size_t r = 0; r < R; ++r) lattice *= a.N[r] + 1.0;
665 throw InputError(
"pfqn_mvasjn: the population lattice has " + std::to_string(lattice) +
666 " states and does not fit; use pfqn_amvasjn (method 'amva')");
667 std::vector<std::size_t> stride(R, 1);
668 std::size_t npop = 1;
669 for (std::size_t r = 0; r < R; ++r) {
671 npop *=
static_cast<std::size_t
>(a.N[r]) + 1;
676 std::vector<double> Xp(npop * R, 0.0), Qp(npop * M * R, 0.0), Up(npop * M * R, 0.0),
677 Cp(npop * M * R, 0.0);
678 std::vector<Matrix<double>> Wp(nsjn * npop), Pp(nsjn * npop);
679 std::vector<double> Ip(nsjn * npop * R, 0.0), Tp(nsjn * npop * R * 3, 0.0);
685 for (std::size_t idx = 1; idx < npop; ++idx) {
686 std::vector<double> n(R, 0.0);
687 for (std::size_t r = 0; r < R; ++r)
688 n[r] =
static_cast<double>((idx / stride[r]) %
689 (
static_cast<std::size_t
>(a.N[r]) + 1));
691 for (std::size_t r = 0; r < R; ++r) {
692 if (n[r] == 0.0)
continue;
693 const std::size_t iprev = idx - stride[r];
694 for (std::size_t m = 0; m < M; ++m) {
695 std::size_t q = nsjn;
696 for (std::size_t t = 0; t < nsjn; ++t)
697 if (a.sjnset[t] == m) q = t;
700 for (std::size_t k = 0; k < R; ++k) qsum += Qp[(iprev * M + m) * R + k];
701 Call(m, r) = L(m, r) * (1.0 + qsum);
706 const std::vector<double> beta(R, 1.0);
708 st.lam.assign(R, 0.0);
711 st.phiinf.assign(R, 0.0);
712 for (std::size_t k = 0; k < R; ++k) {
713 st.lam[k] = Xp[iprev * R + k] * a.V(m, k);
714 st.U[k] = Up[(iprev * M + m) * R + k];
715 st.Q[k] = Qp[(iprev * M + m) * R + k];
716 st.phiinf[k] = Ip[(q * npop + iprev) * R + k];
718 st.W = &Wp[q * npop + iprev];
719 st.phi = &Pp[q * npop + iprev];
720 std::vector<double> Sm(R), scvm(R), Vm(R);
721 for (std::size_t k = 0; k < R; ++k) {
723 scvm[k] = a.scv(m, k);
726 const detail::SjnStationResult sr =
727 detail::sjn_station(m, r, G[q], Sm, scvm, Vm, st, beta, useprio, a.opt.prio);
729 for (std::size_t i = 0; i < ngrid; ++i) {
730 Wp[q * npop + idx](i, r) = sr.W[i];
731 Pp[q * npop + idx](i, r) = sr.phi[i];
733 Ip[(q * npop + idx) * R + r] = sr.phiinf;
734 for (
int t = 0; t < 3; ++t)
735 Tp[((q * npop + idx) * R + r) * 3 + t] = sr.tail[t];
738 const detail::SjnCapResult cap = detail::sjn_cap(Call, L, n, a.Z, a.sjnset, a.opt.umax);
742 for (std::size_t r = 0; r < R; ++r)
743 npos += (r ?
" " :
"") + std::to_string(
static_cast<long long>(n[r]));
745 "pfqn_mvasjn: the utilization cap of " + std::to_string(a.opt.umax) +
746 " was binding at an SJN station at population [" + npos +
747 "]: the station is in the starvation regime, where the conditional waiting time "
748 "equation has no solution and the population lattice no valid continuation. Use "
749 "the Schweitzer fixed point (pfqn_amvasjn, method 'amva'), SolverCTMC or "
752 for (std::size_t r = 0; r < R; ++r) Xp[idx * R + r] = cap.X[r];
753 for (std::size_t m = 0; m < M; ++m)
754 for (std::size_t r = 0; r < R; ++r) {
755 Cp[(idx * M + m) * R + r] = Call(m, r);
756 Qp[(idx * M + m) * R + r] = cap.X[r] * Call(m, r);
757 Up[(idx * M + m) * R + r] = cap.X[r] * L(m, r);
761 const std::size_t last = npop - 1;
762 res.
XN.assign(R, 0.0);
766 for (std::size_t r = 0; r < R; ++r) res.
XN[r] = Xp[last * R + r];
767 for (std::size_t m = 0; m < M; ++m)
768 for (std::size_t r = 0; r < R; ++r) {
769 res.
QN(m, r) = Qp[(last * M + m) * R + r];
770 res.
UN(m, r) = Up[(last * M + m) * R + r];
771 res.
CN(m, r) = Cp[(last * M + m) * R + r];
774 for (std::size_t q = 0; q < nsjn; ++q) {
775 res.
WX[q].station = a.sjnset[q] + 1;
776 res.
WX[q].x = G[q].x;
777 res.
WX[q].W = Wp[q * npop + last];
779 for (std::size_t r = 0; r < R; ++r)
780 for (
int t = 0; t < 3; ++t)
781 res.
WX[q].tail(r, t) = Tp[((q * npop + last) * R + r) * 3 + t];
804 const detail::SjnArgs a = detail::sjn_args(L, N, Z, scv, sjnset, V, options);
805 const std::size_t M = a.M, R = a.R, nsjn = a.sjnset.size();
806 const bool useprio = !a.opt.prio.empty();
807 const std::size_t ns = a.opt.ns, ngrid = ns + 1;
809 std::vector<detail::SjnGrid> G(nsjn);
810 for (std::size_t q = 0; q < nsjn; ++q) {
811 std::vector<double> Sq(R), scvq(R);
812 for (std::size_t r = 0; r < R; ++r) {
813 Sq[r] = a.S(a.sjnset[q], r);
814 scvq[r] = a.scv(a.sjnset[q], r);
816 G[q] = detail::sjn_setup(Sq, scvq, ns, a.opt.Lfactor);
823 pfqn_bs(L, a.N, a.Z, std::vector<AmvaSched>(), a.opt.tol, a.opt.iter_max);
830 std::vector<Matrix<double>> W(nsjn,
Matrix<double>(ngrid, R, 0.0));
831 std::vector<Matrix<double>> P(nsjn,
Matrix<double>(ngrid, R, 0.0));
836 bool converged =
false;
838 while (!converged && it < a.opt.iter_max) {
841 std::vector<Matrix<double>> Wit = W, Pit = P, Tit =
Tail;
843 for (std::size_t r = 0; r < R; ++r) {
844 if (a.N[r] == 0.0)
continue;
845 std::vector<double> beta(R, 1.0);
846 beta[r] = (a.N[r] - 1.0) / a.N[r];
847 for (std::size_t m = 0; m < M; ++m) {
848 std::size_t q = nsjn;
849 for (std::size_t t = 0; t < nsjn; ++t)
850 if (a.sjnset[t] == m) q = t;
853 for (std::size_t k = 0; k < R; ++k) qsum += beta[k] * res.
QN(m, k);
854 Cit(m, r) = L(m, r) * (1.0 + qsum);
858 st.lam.assign(R, 0.0);
861 st.phiinf.assign(R, 0.0);
862 for (std::size_t k = 0; k < R; ++k) {
863 st.lam[k] = res.
XN[k] * a.V(m, k);
864 st.U[k] = res.
UN(m, k);
865 st.Q[k] = res.
QN(m, k);
866 st.phiinf[k] = Iinf(q, k);
870 std::vector<double> Sm(R), scvm(R), Vm(R);
871 for (std::size_t k = 0; k < R; ++k) {
873 scvm[k] = a.scv(m, k);
876 const detail::SjnStationResult sr =
877 detail::sjn_station(m, r, G[q], Sm, scvm, Vm, st, beta, useprio, a.opt.prio);
879 for (std::size_t i = 0; i < ngrid; ++i) {
880 Wit[q](i, r) = sr.W[i];
881 Pit[q](i, r) = sr.phi[i];
883 Iit(q, r) = sr.phiinf;
884 for (
int t = 0; t < 3; ++t) Tit[q](r, t) = sr.tail[t];
887 const detail::SjnCapResult cap = detail::sjn_cap(Cit, L, a.N, a.Z, a.sjnset, a.opt.umax);
890 for (std::size_t q = 0; q < nsjn; ++q) {
891 const double kq = cap.kappa[q];
892 for (std::size_t i = 0; i < ngrid; ++i)
893 for (std::size_t r = 0; r < R; ++r) {
897 for (std::size_t r = 0; r < R; ++r) {
905 for (std::size_t m = 0; m < M; ++m)
906 for (std::size_t r = 0; r < R; ++r) {
907 Qit(m, r) = cap.X[r] * Cit(m, r);
908 Uit(m, r) = cap.X[r] * L(m, r);
911 for (std::size_t m = 0; m < M; ++m)
912 for (std::size_t r = 0; r < R; ++r)
913 delta = std::max(delta, std::fabs(Qit(m, r) - res.
QN(m, r)));
914 for (std::size_t q = 0; q < nsjn; ++q)
915 for (std::size_t i = 0; i < ngrid; ++i)
916 for (std::size_t r = 0; r < R; ++r)
917 delta = std::max(delta, std::fabs(Wit[q](i, r) - W[q](i, r)));
926 converged = delta < a.opt.tol;
932 for (std::size_t q = 0; q < nsjn; ++q) {
933 res.
WX[q].station = a.sjnset[q] + 1;
934 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.
Bard-Schweitzer approximate MVA.
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