5#ifndef LINE_API_PFQN_MARIE_H
6#define LINE_API_PFQN_MARIE_H
122 "marie_cox_fit requires transcendental arithmetic: the hypoexponential branch "
123 "matches the second moment through a square root");
132 if (!(mean > zero))
throw InputError(
"marie_cox_fit: the mean must be strictly positive");
133 if (!(scv > zero))
throw InputError(
"marie_cox_fit: the SCV must be strictly positive");
136 if (scv >= T(one - coarse) && scv <= T(one + coarse)) {
138 f.
mu.push_back(T(one / mean));
139 f.
phi.push_back(one);
140 }
else if (scv > T(half + coarse) && scv < T(one - coarse)) {
142 const T d = T(sqrt(T(two * scv - one)));
143 f.
mu.push_back(T(two / mean / T(one + d)));
144 f.
mu.push_back(T(two / mean / T(one - d)));
145 f.
phi.push_back(zero);
146 f.
phi.push_back(one);
147 }
else if (scv <= T(half + coarse)) {
150 const long n =
static_cast<long>(std::ceil(inv));
151 if (n < 1 || n > 100000)
152 throw InputError(
"marie_cox_fit: the Erlang branch needs an unreasonable phase count");
154 f.
mu.assign(
static_cast<std::size_t
>(n), rate);
155 f.
phi.assign(
static_cast<std::size_t
>(n), zero);
159 const T mu1 = T(two / mean);
160 const T mu2 = T(mu1 / T(two * scv));
163 f.
phi.push_back(T(one - mu2 / mu1));
164 f.
phi.push_back(one);
182std::vector<T> marie_isol_condtput(
const std::vector<T>& lam,
const std::vector<T>& rate,
183 const std::vector<T>& phi,
int N,
int m) {
186 const std::size_t P = rate.size();
187 const std::size_t S = 1 +
static_cast<std::size_t
>(N) * P;
190 const auto idx = [P](
int n, std::size_t k) {
191 return 1 +
static_cast<std::size_t
>(n - 1) * P + k;
194 Matrix<T> Gq(S, S, zero);
195 Gq(0, idx(1, 0)) += lam[0];
196 for (
int n = 1; n <= N; ++n) {
197 const T sc = num_traits<T>::from_int(n < m ? n : m);
198 for (std::size_t k = 0; k < P; ++k) {
199 const std::size_t r = idx(n, k);
200 if (n < N) Gq(r, idx(n + 1, k)) += lam[
static_cast<std::size_t
>(n)];
201 const T compl_ = rate[k] * phi[k] * sc;
202 const T adv = rate[k] * T(one - phi[k]) * sc;
203 if (adv > zero && k + 1 < P) Gq(r, idx(n, k + 1)) += adv;
206 Gq(r, idx(n - 1, 0)) += compl_;
215 std::vector<T> muvec(
static_cast<std::size_t
>(N), zero);
216 for (
int n = 1; n <= N; ++n) {
217 const T sc = num_traits<T>::from_int(n < m ? n : m);
218 T Pn = zero, dep = zero;
219 for (std::size_t k = 0; k < P; ++k) {
220 const T pk = p[idx(n, k)];
222 dep += pk * rate[k] * phi[k] * sc;
225 muvec[
static_cast<std::size_t
>(n - 1)] = dep / Pn;
229 for (std::size_t k = 0; k < P; ++k) mean += one / rate[k];
230 muvec[
static_cast<std::size_t
>(n - 1)] = sc / mean;
256inline std::size_t marie_box_index(
const std::vector<int>& n,
const std::vector<int>& N) {
258 for (std::size_t d = 0; d < N.size(); ++d)
259 lin = lin *
static_cast<std::size_t
>(N[d] + 1) +
static_cast<std::size_t
>(n[d]);
263inline std::size_t marie_box_size(
const std::vector<int>& N) {
265 for (std::size_t d = 0; d < N.size(); ++d) sz *=
static_cast<std::size_t
>(N[d] + 1);
274T marie_ndlininterp(
const std::vector<T>& A,
const std::vector<T>& x,
const std::vector<int>& N) {
275 const std::size_t R = N.size();
276 const T zero = num_traits<T>::from_int(0);
277 const T one = num_traits<T>::from_int(1);
278 std::vector<int> lo(R), hi(R);
279 std::vector<T> fr(R);
280 for (std::size_t d = 0; d < R; ++d) {
282 if (xd < zero) xd = zero;
283 const T ub = num_traits<T>::from_int(N[d]);
284 if (xd > ub) xd = ub;
285 const double f = std::floor(num_traits<T>::to_double(xd));
286 lo[d] =
static_cast<int>(f);
287 if (lo[d] > N[d]) lo[d] = N[d];
288 hi[d] = lo[d] + 1 < N[d] ? lo[d] + 1 : N[d];
289 fr[d] = T(xd - num_traits<T>::from_int(lo[d]));
292 std::vector<int> sub(R);
293 const unsigned long corners = 1ul << R;
294 for (
unsigned long mask = 0; mask < corners; ++mask) {
296 for (std::size_t d = 0; d < R; ++d) {
297 if ((mask >> d) & 1ul) {
305 if (w == zero)
continue;
306 v += w * A[marie_box_index(sub, N)];
320 const std::size_t R = nv.size();
322 std::vector<T> be(R, one);
328 for (std::size_t r = 0; r < R; ++r) {
329 const T num = detail::marie_ndlininterp(cd.
muCox[r], nv, cd.
N);
330 const T den = detail::marie_ndlininterp(cd.
muExp[r], nv, cd.
N);
331 const bool okNum = num == num && num > zero && num < inf;
332 const bool okDen = den == den && den > zero && den < inf;
333 be[r] = (okNum && okDen) ? T(num / den) : one;
334 if (be[r] < lo) be[r] = lo;
335 if (be[r] > hi) be[r] = hi;
355 std::vector<MarieCdScaling<T>>
cds;
367void marie_amva_qd(
const Matrix<T>& L,
const std::vector<int>& N,
const std::vector<T>& Z,
370 const std::size_t M = L.
rows();
371 const std::size_t R = L.
cols();
378 for (std::size_t i = 0; i < M; ++i)
385 for (std::size_t i = 0; i < M; ++i)
386 for (std::size_t r = 0; r < R; ++r) Qprev(i, r) = T(Q(i, r) + one);
388 std::vector<T> nv(R), Leff(R);
392 for (std::size_t i = 0; i < M; ++i)
393 for (std::size_t r = 0; r < R; ++r) {
394 const T d =
num_abs(T(Q(i, r) - Qprev(i, r)));
395 if (d > delta) delta = d;
397 if (!(delta > tol) || it >= 5000)
break;
400 for (std::size_t r = 0; r < R; ++r) {
401 for (std::size_t i = 0; i < M; ++i) {
402 for (std::size_t s = 0; s < R; ++s) nv[s] = Q(i, s);
408 for (std::size_t s = 0; s < R; ++s) {
409 Leff[s] = L(i, s) / be[s];
410 w += Leff[s] * nv[s];
412 W(i, r) = Leff[r] + w;
415 for (std::size_t i = 0; i < M; ++i) denom += W(i, r);
417 for (std::size_t i = 0; i < M; ++i) Q(i, r) = X[r] * W(i, r);
421 for (std::size_t r = 0; r < R; ++r)
422 for (std::size_t i = 0; i < M; ++i) U(i, r) = X[r] * L(i, r);
433std::vector<std::vector<T>> marie_isol_mc(
const std::vector<T>& lam,
434 const std::vector<MarieCoxFit<T>>& fits,
435 const std::vector<int>& N) {
436 const std::size_t R = N.size();
438 const std::size_t npops = marie_box_size(N);
446 std::vector<StateId> ids;
447 ids.push_back(StateId{marie_box_index(std::vector<int>(R, 0), N), 0, 0});
449 std::vector<std::vector<long>> byPop(npops);
450 std::vector<std::vector<int>> popVec(npops, std::vector<int>(R, 0));
452 std::vector<int> n(R, 0);
453 for (std::size_t p = 0; p < npops; ++p) {
456 for (std::size_t d = R; d-- > 0;) {
457 const std::size_t w =
static_cast<std::size_t
>(N[d] + 1);
458 n[d] =
static_cast<int>(rem % w);
464 for (std::size_t p = 0; p < npops; ++p) {
466 for (std::size_t r = 0; r < R; ++r) tot += popVec[p][r];
467 if (tot == 0)
continue;
468 std::vector<long> slot;
469 for (std::size_t c = 0; c < R; ++c) {
470 const std::size_t P = fits[c].mu.size();
471 for (std::size_t k = 0; k < P; ++k) {
472 if (popVec[p][c] > 0) {
473 slot.push_back(
static_cast<long>(ids.size()));
474 ids.push_back(StateId{p, c, k});
482 std::vector<std::size_t> phaseOff(R + 1, 0);
483 for (std::size_t c = 0; c < R; ++c) phaseOff[c + 1] = phaseOff[c] + fits[c].mu.size();
485 const auto getid = [&](std::size_t p, std::size_t c, std::size_t k) -> std::size_t {
486 const long v = byPop[p][phaseOff[c] + k];
487 if (v < 0)
throw NumericError(
"pfqn_marie: isolation state does not exist");
488 return static_cast<std::size_t
>(v);
491 const std::size_t S = ids.size();
492 Matrix<T> Gq(S, S, zero);
493 std::vector<int> nn(R);
494 for (std::size_t s = 0; s < S; ++s) {
495 const StateId& st = ids[s];
496 const bool empty = (s == 0);
497 const std::vector<int>& nvec = empty ? popVec[ids[0].pop] : popVec[st.pop];
498 for (std::size_t r = 0; r < R; ++r) {
499 if (nvec[r] < N[r] && lam[r] > zero) {
502 const std::size_t pn = marie_box_index(nn, N);
504 Gq(s, getid(pn, r, 0)) += lam[r];
506 Gq(s, getid(pn, st.cls, st.phase)) += lam[r];
510 const std::size_t c = st.cls, k = st.phase;
511 const T rate = fits[c].mu[k];
512 const T compl_ = rate * fits[c].phi[k];
513 const T adv = rate * T(num_traits<T>::from_int(1) - fits[c].phi[k]);
514 if (adv > zero && k + 1 < fits[c].mu.size()) Gq(s, getid(st.pop, c, k + 1)) += adv;
519 for (std::size_t r = 0; r < R; ++r) tot += nn[r];
523 const std::size_t pn = marie_box_index(nn, N);
524 for (std::size_t cp = 0; cp < R; ++cp)
526 Gq(s, getid(pn, cp, 0)) +=
527 compl_ * num_traits<T>::from_int(nn[cp]) / num_traits<T>::from_int(tot);
534 std::vector<T> Ppop(npops, zero);
535 std::vector<std::vector<T>> dep(R, std::vector<T>(npops, zero));
536 for (std::size_t s = 1; s < S; ++s) {
537 const StateId& st = ids[s];
538 Ppop[st.pop] += p[s];
539 dep[st.cls][st.pop] += p[s] * fits[st.cls].mu[st.phase] * fits[st.cls].phi[st.phase];
541 std::vector<std::vector<T>> mumat(R, std::vector<T>(npops, zero));
542 for (std::size_t r = 0; r < R; ++r)
543 for (std::size_t q = 0; q < npops; ++q)
544 if (Ppop[q] > zero) mumat[r][q] = dep[r][q] / Ppop[q];
550MarieResult<T> marie_multi(
const Matrix<T>& L,
const std::vector<int>& N,
const std::vector<T>& Z,
551 const Matrix<T>& scv,
double tol,
int maxiter) {
552 const std::size_t M = L.rows();
553 const std::size_t R = L.cols();
554 const T zero = num_traits<T>::from_int(0);
555 const T one = num_traits<T>::from_int(1);
562 for (std::size_t i = 0; i < M && isPF; ++i)
563 for (std::size_t r = 0; r < R; ++r)
564 if (scv(i, r) != one) {
569 const T eps = num_traits<T>::from_double(1e-12);
570 for (std::size_t i = 0; i < M && isPF; ++i) {
571 T lo = L(i, 0), hi = L(i, 0);
572 for (std::size_t r = 1; r < R; ++r) {
573 if (L(i, r) < lo) lo = L(i, r);
574 if (L(i, r) > hi) hi = L(i, r);
576 if (T(hi - lo) > eps) isPF =
false;
580 Matrix<T> Zmat(1, R);
581 for (std::size_t r = 0; r < R; ++r) Zmat(0, r) = Z[r];
582 const MvaResult<T> m =
pfqn_mva(L, N, Zmat);
591 std::vector<std::vector<MarieCoxFit<T>>> phCox(M, std::vector<MarieCoxFit<T>>(R));
592 std::vector<std::vector<MarieCoxFit<T>>> phExp(M, std::vector<MarieCoxFit<T>>(R));
593 for (std::size_t i = 0; i < M; ++i)
594 for (std::size_t r = 0; r < R; ++r) {
597 e.mu.push_back(T(one / L(i, r)));
598 e.phi.push_back(one);
602 res.cds.assign(M, MarieCdScaling<T>());
603 std::vector<T> Xprev(R, num_traits<T>::from_double(std::numeric_limits<double>::infinity()));
604 const T tolT = num_traits<T>::from_double(tol);
605 res.X.assign(R, zero);
609 while (res.it < maxiter) {
611 marie_amva_qd(L, N, Z, res.cds, res.X, res.Q, res.U, res.C);
612 for (std::size_t i = 0; i < M; ++i) {
613 MarieCdScaling<T> cd;
615 cd.muCox = marie_isol_mc(res.X, phCox[i], N);
616 cd.muExp = marie_isol_mc(res.X, phExp[i], N);
620 for (std::size_t r = 0; r < R; ++r) {
621 const T d =
num_abs(T(res.X[r] - Xprev[r]));
622 if (d > delta) delta = d;
624 if (delta < tolT)
break;
648 const Matrix<T>& scv,
double tol,
int maxiter,
649 const std::vector<int>& nservers) {
651 "pfqn_marie requires transcendental arithmetic: it is a fixed-point "
652 "decomposition stopped on a tolerance, and its Coxian fit needs a square root");
654 const std::size_t M = L.
rows();
655 const std::size_t R = L.
cols();
656 if (R != N.size())
throw InputError(
"pfqn_marie: L and N disagree on the class count");
657 if (M == 0)
throw InputError(
"pfqn_marie: no stations");
658 if (!Z.empty() && Z.size() != R)
throw InputError(
"pfqn_marie: Z has the wrong length");
660 throw InputError(
"pfqn_marie: scv has the wrong shape");
661 if (maxiter < 1)
throw InputError(
"pfqn_marie: maxiter must be at least one");
665 for (std::size_t i = 0; i < M; ++i)
666 for (std::size_t r = 0; r < R; ++r)
667 if (!(L(i, r) > zero))
668 throw InputError(
"pfqn_marie: every service demand must be strictly positive");
672 std::vector<T> Zv = Z;
673 if (Zv.empty()) Zv.assign(R, zero);
676 if (!nservers.empty())
677 for (std::size_t i = 0; i < nservers.size(); ++i)
678 if (nservers[i] != 1)
680 "pfqn_marie: the multiclass path has no multiserver isolation chain, as in "
682 return detail::marie_multi(L, N, Zv, SCV, tol, maxiter);
687 if (Nt < 1)
throw InputError(
"pfqn_marie: the population must be at least one");
688 std::vector<int> ns = nservers;
689 if (ns.empty()) ns.assign(M, 1);
690 if (ns.size() == 1 && M > 1) ns.assign(M, ns[0]);
691 if (ns.size() != M)
throw InputError(
"pfqn_marie: nservers has the wrong length");
692 for (std::size_t i = 0; i < M; ++i)
693 if (ns[i] < 1)
throw InputError(
"pfqn_marie: the server count must be at least one");
696 for (std::size_t r = 0; r < Zv.size(); ++r) Ztot += Zv[r];
700 std::vector<MarieCoxFit<T>> fit(M);
701 for (std::size_t i = 0; i < M; ++i) fit[i] =
marie_cox_fit(L(i, 0), SCV(i, 0));
704 Matrix<T> mu(M,
static_cast<std::size_t
>(Nt), one);
705 for (std::size_t i = 0; i < M; ++i)
706 for (
int n = 1; n <= Nt; ++n)
711 res.
X.assign(1, zero);
716 std::vector<T> lam(
static_cast<std::size_t
>(Nt), zero);
718 while (res.
it < maxiter) {
722 for (std::size_t i = 0; i < M; ++i) {
723 for (
int n = 0; n < Nt; ++n) {
724 const T pn = ld.
PI(i,
static_cast<std::size_t
>(n));
725 lam[
static_cast<std::size_t
>(n)] =
726 pn > zero ? T(mu(i,
static_cast<std::size_t
>(n)) / L(i, 0) *
727 ld.
PI(i,
static_cast<std::size_t
>(n) + 1) / pn)
730 const std::vector<T> muabs =
731 detail::marie_isol_condtput(lam, fit[i].mu, fit[i].phi, Nt, ns[i]);
732 for (
int n = 0; n < Nt; ++n)
733 muNew(i,
static_cast<std::size_t
>(n)) = muabs[
static_cast<std::size_t
>(n)] * L(i, 0);
736 for (std::size_t i = 0; i < M; ++i)
737 for (std::size_t n = 0; n < static_cast<std::size_t>(Nt); ++n) {
738 const T d =
num_abs(T(muNew(i, n) - mu(i, n)));
739 if (d > delta) delta = d;
743 for (std::size_t i = 0; i < M; ++i) {
744 res.
Q(i, 0) = ld.
QN(i, 0);
745 res.
U(i, 0) = ld.
UN[i];
747 res.
C(0, 0) = ld.
CN[0];
748 if (delta < tolT)
break;
758 return pfqn_marie(L, N, Z, scv, 1e-8, 1000, std::vector<int>());
NumericError(const std::string &what)
Steady-state distribution of a continuous-time Markov chain.
The exception types the port throws.
Dense matrix and non-owning view.
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
MvaResult< T > pfqn_mva(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< int > &mi)
Exact Mean Value Analysis for closed product-form networks (Reiser and Lavenberg 1980).
MarieResult< T > pfqn_marie(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const Matrix< T > &scv, double tol, int maxiter, const std::vector< int > &nservers)
Marie's method for a closed network with FCFS Coxian service.
MarieCoxFit< T > marie_cox_fit(const T &mean, const T &scv)
Closed-form Coxian fit of a mean and an SCV (matlab/src/lang/processes/Coxian.m, fitMeanAndSCV),...
MvaLdResult< T > pfqn_mvald(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const Matrix< T > &mu, bool stabilize=true)
Exact MVA for a closed network of load-dependent stations.
std::vector< T > marie_cd_eval(const MarieCdScaling< T > &cd, const std::vector< T > &nv)
Evaluate a class-dependent scaling at a real-valued population vector (cdscale_eval in the reference)...
Number-type abstraction for the templated API port.
Exact Mean Value Analysis for closed product-form networks (Reiser and Lavenberg 1980).
Exact Mean Value Analysis for mixed open/closed networks with multiserver stations.
Class-dependent scaling of one station, tabulated on the integer population box.
std::vector< std::vector< T > > muCox
muCox[r][lin], lin the row-major box index
std::vector< std::vector< T > > muExp
muExp[r][lin]
std::vector< int > N
box bounds; the lattice is [0..N]
Coxian phase representation: phase rates and per-phase completion probabilities.
std::vector< T > mu
phase rates
std::vector< T > phi
completion probability out of each phase, phi.back() == 1
Result of pfqn_marie, mirroring the six MATLAB outputs.
int it
iterations performed
Matrix< T > Q
(M x R) mean queue length
Matrix< T > C
Single class: (1 x 1), the CYCLE time returned by pfqn_mvald.
std::vector< T > X
(R) per-class throughput
std::vector< MarieCdScaling< T > > cds
R > 1: converged per-station cd scalings.
Matrix< T > mu
single class: (M x N) converged multiplier lattice; empty for R > 1
Matrix< T > U
(M x R) utilization
Result of pfqn_mvald, mirroring the seven MATLAB outputs.
Matrix< T > QN
(M x R) mean queue length
std::vector< T > XN
(R) per-class throughput
std::vector< T > UN
(M) utilization, 1 - P(station empty)
std::vector< T > CN
(R) cycle time, exclusive of think time
Matrix< T > PI
(M x (Nt+1)) marginal queue-length distribution at N