537 const std::string& method =
opt.method;
556 if (!refusal.empty())
throw UnsupportedError(
"solver_ba_analyzer: " + refusal);
563 if (method ==
"auto.upper" || method ==
"auto.lower") {
564 const bool up = method ==
"auto.upper";
565 const detail::ScDemands<T> sc = detail::ba_sc_demands(L);
566 static const char*
const cand_up[] = {
"aba.upper",
"bjb.upper",
"pb.upper",
"gb.upper",
567 "sb.upper",
"mwba.upper",
"ssd.upper",
"cub.upper"};
568 static const char*
const cand_lo[] = {
"aba.lower",
"bjb.lower",
"pb.lower",
569 "gb.lower",
"sb.lower",
"mwba.lower",
570 "ssd.lower",
"mbjb.lower",
"ldbcmp.lower"};
571 const char*
const* cand = up ? cand_up : cand_lo;
572 const std::size_t ncand = up ?
sizeof(cand_up) /
sizeof(*cand_up)
573 :
sizeof(cand_lo) /
sizeof(*cand_lo);
576 for (std::size_t ci = 0; ci < ncand; ++ci) {
582 }
catch (
const std::exception&) {
585 if (r.
X.empty())
continue;
588 if (!have || (up && Xc < Xbest) || (!up && Xc > Xbest)) {
595 "' found no feasible bound for this model");
596 return detail::ba_fill(L, sc.V, sc.N, Xbest, sc.Z, sc.D, up);
601 const bool single_class_family =
602 method ==
"aba.upper" || method ==
"aba.lower" || method ==
"bjb.upper" ||
603 method ==
"bjb.lower" || method ==
"pb.upper" || method ==
"pb.lower" ||
604 method ==
"sb.upper" || method ==
"sb.lower" || method ==
"gb.upper" ||
605 method ==
"gb.lower";
607 if (single_class_family) {
608 const detail::ScDemands<T> sc = detail::ba_sc_demands(L);
609 const std::vector<T>& V = sc.V;
610 const std::vector<T>& D = sc.D;
614 const detail::DemandStats<T> st = detail::ba_demand_stats(D);
616 if (method ==
"aba.upper") {
617 const T X = detail::ba_min(T(one / st.max), T(Nt / T(Z + st.sum)));
618 return detail::ba_fill_xc(L, V, N, X, T(Z + Nt * st.sum),
true);
620 if (method ==
"aba.lower") {
621 const T X = T(Nt / T(Z + Nt * st.sum));
622 return detail::ba_fill_xc(L, V, N, X, T(Z + st.sum),
false);
629 const T xup1 = detail::ba_min(T(one / st.max), T(Nm1 / T(Z + st.sum)));
630 const T xlo1 = T(Nm1 / T(Z + Nm1 * st.sum));
631 if (method ==
"bjb.upper") {
632 const T C = T(Z + st.sum + st.max * T(Nm1 - Z * xlo1));
633 const T X = detail::ba_min(
634 T(one / st.max), T(Nt / T(Z + st.sum + st.mean * T(Nm1 - Z * xup1))));
635 return detail::ba_fill_xc(L, V, N, X, C,
true);
637 if (method ==
"bjb.lower") {
638 const T C = T(Z + st.sum + st.mean * T(Nm1 - Z * xup1));
639 const T X = T(Nt / T(Z + st.sum + st.max * T(Nm1 - Z * xlo1)));
640 return detail::ba_fill_xc(L, V, N, X, C,
false);
642 if (method ==
"pb.upper" || method ==
"pb.lower") {
643 T d2 = zero, dN = zero, dNm1 = zero;
644 for (
const T& d : D) {
647 dNm1 +=
num_pow_int(d,
static_cast<unsigned>(N - 1));
649 const T Dpb2 = T(d2 / st.sum);
650 const T DpbN = T(dN / dNm1);
651 if (method ==
"pb.upper") {
652 const T C = T(Z + st.sum + DpbN * T(Nm1 - Z * xlo1));
653 const T X = detail::ba_min(
654 T(one / st.max), T(Nt / T(Z + st.sum + Dpb2 * T(Nm1 - Z * xup1))));
655 return detail::ba_fill_xc(L, V, N, X, C,
true);
657 const T C = T(Z + st.sum + Dpb2 * T(Nm1 - Z * xup1));
658 const T X = T(Nt / T(Z + st.sum + DpbN * T(Nm1 - Z * xlo1)));
659 return detail::ba_fill_xc(L, V, N, X, C,
false);
661 if (method ==
"sb.upper" || method ==
"sb.lower") {
665 for (std::size_t i = 0; i < L.
nstations; ++i)
666 if (detail::ba_is_delay(L, i))
668 "' does not support infinite-server stations");
669 T A1 = zero, A2 = zero, A3 = zero;
670 for (
const T& d : D) {
675 if (method ==
"sb.upper") {
676 const T C = T(Z + A1 + Nm1 * T(T(A1 * A2 + A3) / T(A1 * A1 + A2)));
677 const T X = detail::ba_min(T(one / st.max), T(Nt / C));
681 return detail::ba_fill_xc(L, V, N, X, C,
false);
686 "solver_ba_analyzer: method 'sb.lower' needs an (N-1)-st root and is "
687 "unavailable in exact arithmetic");
691 for (
const T& d : D) AN +=
num_pow_int(d,
static_cast<unsigned>(N));
694 const T root = pow(T(AN / A1), T(one / Nm1));
695 const T C = T(Z + A1 + Nm1 * root);
696 return detail::ba_fill_xc(L, V, N, T(Nt / C), C,
false);
701 throw UnsupportedError(
"solver_ba_analyzer: the geometric bounds '" + method +
702 "' solve a quadratic and are unavailable in exact arithmetic");
704 const bool up = (method ==
"gb.upper");
707 const T X = up ? detail::ba_min(T(one / st.max), Xup) : Xlo;
708 const T C = T(Nt / (up ? Xlo : Xup));
713 const T Xden = up ? Xlo : Xup;
720 for (std::size_t i = 0; i < L.
nstations; ++i) {
721 s.
Tp(i, 0) = T(V[i] * X);
722 if (detail::ba_is_delay(L, i)) {
723 s.
R(i, 0) = T(one / L.
rates(i, 0));
724 s.
Q(i, 0) = T(X * s.
R(i, 0));
727 s.
R(i, 0) = T(T(s.
Q(i, 0) / Xden) / V[i]);
730 s.
U(i, 0) = detail::ba_is_delay(L, i) ? s.
Q(i, 0) : T(s.
Tp(i, 0) / L.
rates(i, 0));
739 if (method ==
"lr.upper" || method ==
"lr.lower") {
743 const detail::ScDemands<T> sc = detail::ba_sc_demands(L);
744 for (std::size_t i = 0; i < L.
nstations; ++i)
745 if (detail::ba_is_delay(L, i))
747 "' does not support delay (infinite-server) stations");
748 const bool up = (method ==
"lr.upper");
750 const std::vector<T>& V = sc.V;
753 par.
M =
static_cast<int>(M);
756 par.
N =
static_cast<int>(sc.N);
759 for (std::size_t i = 0; i < M; ++i) {
767 if (totV == zero)
throw NumericError(
"solver_ba_analyzer: the visit ratios are all zero");
768 for (std::size_t i = 0; i < M; ++i)
769 for (std::size_t j = 0; j < M; ++j) par.
r(i, j) = T(V[j] / totV);
772 std::vector<T> U(M, zero);
773 for (std::size_t ti = 0; ti < M; ++ti) {
777 throw NumericError(
"solver_ba_analyzer: the '" + method +
778 "' linear program did not solve (" + r.
status +
")");
786 for (std::size_t i = 0; i < M; ++i) {
787 if (V[i] == zero)
continue;
788 const T cand = T(U[i] * L.
rates(i, 0) / V[i]);
789 if (!any || cand < X) {
798 BaSolution<T> s = detail::ba_fill(L, V, sc.N, X, zero, sc.D, up);
799 for (std::size_t i = 0; i < M; ++i) s.
U(i, 0) = U[i];
800 s.
lG = std::numeric_limits<double>::quiet_NaN();
804 if (method ==
"mapamva.upper" || method ==
"mapamva.lower") {
819 const detail::ScDemands<T> sc = detail::ba_sc_demands(L);
820 for (std::size_t i = 0; i < L.
nstations; ++i)
821 if (detail::ba_is_delay(L, i))
823 "solver_ba_analyzer: method '" + method +
824 "' does not support delay (infinite-server) stations: the MAP-AMVA program "
825 "of Casale-Smirni (DSN 2009) is written for a network of queues and the "
826 "paper names the delay extension as open work. Use a QRF method, which "
827 "carries the load-dependent rate law");
828 const bool up = (method ==
"mapamva.upper");
830 const std::vector<T>& V = sc.V;
835 std::vector<std::pair<Matrix<T>,
Matrix<T>>> MAPs(M);
836 std::vector<int> kph(M, 1);
837 for (std::size_t i = 0; i < M; ++i) {
839 if (m.
D0.rows() == 0) {
844 MAPs[i] = std::make_pair(m.
D0, m.
D1);
845 kph[i] =
static_cast<int>(m.
D0.rows());
848 std::vector<std::size_t> phased;
849 for (std::size_t i = 0; i < M; ++i)
850 if (kph[i] > 1) phased.push_back(i);
851 if (phased.size() > 1) {
853 for (std::size_t j = 0; j < phased.size(); ++j)
854 names += (j ?
", " :
"") + std::to_string(phased[j] + 1);
856 "solver_ba_analyzer: method '" + method +
857 "' carries phases at ONE station: the LP gives queue M the (D0,D1) pair and "
858 "every other queue a scalar rate. Stations " + names +
" are all "
859 "non-exponential. Use a QRF method, whose q carries a phase at every station");
864 const std::size_t map_idx = phased.empty() ? M - 1 : phased.front();
865 std::vector<std::size_t>
perm;
867 for (std::size_t i = 0; i < M; ++i)
868 if (i != map_idx)
perm.push_back(i);
869 perm.push_back(map_idx);
870 const int K = kph[map_idx];
871 const std::size_t Ks =
static_cast<std::size_t
>(K);
874 par.
M =
static_cast<int>(M);
875 par.
N =
static_cast<int>(sc.N);
877 par.
muM.resize(M - 1);
878 for (std::size_t a = 0; a + 1 < M; ++a) par.
muM[a] = L.
rates(
perm[a], 0);
886 for (std::size_t a = 0; a < Ks; ++a)
887 for (std::size_t b = 0; b < Ks; ++b) {
888 par.
muMAP(a, b) = MAPs[map_idx].second(a, b);
889 par.
v(a, b) = (a == b) ? zero : MAPs[map_idx].first(a, b);
892 for (std::size_t a = 0; a < M; ++a)
893 for (std::size_t b = 0; b < M; ++b) par.
r(a, b) = L.
rt(
perm[a],
perm[b]);
895 std::vector<T> Vp(M, zero), Sp(M, zero);
896 for (std::size_t a = 0; a < M; ++a) {
897 const std::size_t i =
perm[a];
900 mi.
D0 = MAPs[i].first;
901 mi.
D1 = MAPs[i].second;
911 std::vector<T> umax(M, zero), umin(M, zero), qbnd(M, zero);
912 for (std::size_t a = 0; a < M; ++a) {
913 const int ti =
static_cast<int>(a);
922 if (!ru.
ok || !rl.
ok || !rq.
ok)
923 throw NumericError(
"solver_ba_analyzer: the '" + method +
924 "' linear program did not solve (" +
936 T x_up = zero, x_lo = zero;
937 for (std::size_t a = 0; a < M; ++a) {
938 const T load = T(Vp[a] * Sp[a]);
939 if (load <= zero)
continue;
940 const T cu = T(umax[a] / load), cl = T(umin[a] / load);
946 if (cu < x_up) x_up = cu;
947 if (cl > x_lo) x_lo = cl;
951 throw NumericError(
"solver_ba_analyzer: method '" + method +
952 "' found no station with both a positive visit ratio and a "
953 "positive mean service time");
954 const T xb = up ? x_up : x_lo;
955 const T xopp = up ? x_lo : x_up;
963 for (std::size_t a = 0; a < M; ++a) {
964 const std::size_t i =
perm[a];
965 s.
U(i, 0) = up ? umax[a] : umin[a];
967 s.
Tp(i, 0) = T(Vp[a] * xb);
968 s.
R(i, 0) = (xopp > zero && Vp[a] > zero) ? T(qbnd[a] / (Vp[a] * xopp)) : zero;
974 s.
C.assign(1, xopp > zero
982 if (method ==
"mwba.upper" || method ==
"mwba.lower") {
986 const detail::ChainView<T> v = detail::ba_chain_view(L);
988 Matrix<T> Vq(Kq, C, zero), Sq(Kq, C, zero);
989 std::vector<pfqn::MwrbbSched> schedq(Kq);
990 for (std::size_t k = 0; k < Kq; ++k) {
991 const std::size_t i = v.qstat[k];
992 schedq[k] = detail::mwrbb_disc_code(L.
stations[i].sched);
993 for (std::size_t c = 0; c < C; ++c) {
994 Vq(k, c) = v.d.Vchain(i, c);
995 Sq(k, c) = v.d.STchain(i, c);
1000 std::vector<int> prioc(C, 0);
1001 for (std::size_t c = 0; c < C; ++c) {
1006 for (std::size_t r : L.
inchain[c]) p = std::min(p, L.
classes[r - 1].prio);
1011 const bool up = (method ==
"mwba.upper");
1012 const std::vector<T>& Xchain = up ? b.
Xup : b.
Xlo;
1013 Matrix<T> Tchain(M, C, zero), Uchain(M, C, zero), Qchain(M, C, zero);
1014 for (std::size_t c = 0; c < C; ++c)
1015 for (std::size_t i = 0; i < M; ++i) {
1016 Tchain(i, c) = T(Xchain[c] * v.d.Vchain(i, c));
1017 Uchain(i, c) = T(Xchain[c] * v.d.Lchain(i, c));
1019 Qchain = detail::ba_chain_qfill(Uchain, Xchain, v.d.Lchain, v.Nv, v.isdelay, up);
1021 L, v.d, Qchain, Uchain,
Matrix<T>(), Tchain, Xchain);
1029 s.
lG = std::numeric_limits<double>::quiet_NaN();
1036 if (method ==
"looping.upper" || method ==
"looping.lower") {
1037 const detail::ChainView<T> v = detail::ba_chain_view(L);
1040 for (std::size_t k = 0; k < Kq; ++k)
1041 for (std::size_t c = 0; c < C; ++c) Lq(k, c) = v.d.Lchain(v.qstat[k], c);
1043 const bool up = (method ==
"looping.upper");
1044 const std::vector<T>& Xchain = up ? b.
Xup : b.
Xlo;
1045 Matrix<T> Tchain(M, C, zero), Uchain(M, C, zero), Qchain(M, C, zero);
1046 for (std::size_t c = 0; c < C; ++c)
1047 for (std::size_t i = 0; i < M; ++i) {
1048 Tchain(i, c) = T(Xchain[c] * v.d.Vchain(i, c));
1049 Uchain(i, c) = T(Xchain[c] * v.d.Lchain(i, c));
1051 Qchain = detail::ba_chain_qfill(Uchain, Xchain, v.d.Lchain, v.Nv, v.isdelay, up);
1053 L, v.d, Qchain, Uchain,
Matrix<T>(), Tchain, Xchain);
1061 s.
lG = std::numeric_limits<double>::quiet_NaN();
1069 if (method ==
"bpt.lower") {
1078 if (method ==
"bgt.upper") {
1087 if (method ==
"snc.upper") {
1093 if (method ==
"cub.upper" || method ==
"mbjb.lower") {
1094 const detail::ChainView<T> v = detail::ba_chain_view(L);
1097 for (std::size_t k = 0; k < Kq; ++k)
1098 for (std::size_t c = 0; c < C; ++c) Lq(k, c) = v.d.Lchain(v.qstat[k], c);
1100 const bool up = (method ==
"cub.upper");
1101 const std::vector<T>& Xchain = up ? b.
Xub : b.
Xlb;
1102 Matrix<T> Tchain(M, C, zero), Uchain(M, C, zero), Qchain(M, C, zero);
1103 for (std::size_t c = 0; c < C; ++c)
1104 for (std::size_t i = 0; i < M; ++i) {
1105 Tchain(i, c) = T(Xchain[c] * v.d.Vchain(i, c));
1106 Uchain(i, c) = T(Xchain[c] * v.d.Lchain(i, c));
1108 Qchain = detail::ba_chain_qfill(Uchain, Xchain, v.d.Lchain, v.Nv, v.isdelay, up);
1110 L, v.d, Qchain, Uchain,
Matrix<T>(), Tchain, Xchain);
1118 s.
lG = std::numeric_limits<double>::quiet_NaN();
1125 if (method ==
"harel.upper" || method ==
"harel.lower") {
1126 const detail::ScDemands<T> sch = detail::ba_sc_demands(L);
1127 if (!(sch.Z == zero))
1129 "' does not support think times (infinite-server stations)");
1130 const int Nh =
static_cast<int>(sch.N);
1131 const int maxUB = Nh < 7 ? Nh : 7;
1133 const bool uph = method ==
"harel.upper";
1137 for (
const T& d : sch.D)
1138 if (d > dmax) dmax = d;
1140 const T ext = maxUB >= 2 ? b.
UB[
static_cast<std::size_t
>(maxUB)] : b.
TH[1];
1141 Xb = ext < cap ? ext : cap;
1143 return detail::ba_fill(L, sch.V, sch.N, Xb, sch.Z, sch.D, uph);
1147 const bool hier = method ==
"pbh.upper" || method ==
"pbh.lower" || method ==
"cbh.upper" ||
1148 method ==
"cbh.lower" || method ==
"pbk.upper" || method ==
"pbk.lower" ||
1149 method ==
"bjbk.upper" || method ==
"bjbk.lower" || method ==
"ssd.upper" ||
1150 method ==
"ssd.lower" || method ==
"sib.upper" || method ==
"sib.lower" ||
1151 method ==
"scb.upper" || method ==
"scb.lower" ||
1152 method ==
"ldbcmp.lower";
1154 throw UnsupportedError(
"solver_ba_analyzer: unknown bound method '" + method +
"'");
1156 const detail::ScDemands<T> sc = detail::ba_sc_demands(L);
1157 const bool up = method.size() > 6 && method.compare(method.size() - 6, 6,
".upper") == 0;
1159 const int lvl =
opt.level;
1164 if (method ==
"ssd.upper" || method ==
"ssd.lower") {
1165 std::vector<T> cvec;
1166 for (std::size_t i = 0; i < L.
nstations; ++i)
1167 if (!detail::ba_is_delay(L, i))
1170 return detail::ba_fill(L, sc.V, sc.N, up ? b.
Xhi : b.
Xlo, sc.Z, sc.D, up);
1172 if (method ==
"ldbcmp.lower") {
1176 const std::vector<T> cz(sc.D.size(), zero);
1181 "solver_ba_analyzer: method 'ldbcmp.lower' requires the asymptotic regime N >= "
1183 return detail::ba_fill(L, sc.V, sc.N, b.
Xlo, sc.Z, sc.D,
false);
1185 if (method ==
"scb.upper" || method ==
"scb.lower") {
1192 if (!(sc.Z == zero))
1194 "' supports Z=0 (no delay station) only; Theorem 3 rests on "
1195 "the delay-free balanced-network throughput");
1197 return detail::ba_fill(L, sc.V, sc.N, up ? b.
Xhi : b.
Xlo, sc.Z, sc.D, up);
1199 if (method ==
"pbh.upper" || method ==
"pbh.lower") {
1201 return detail::ba_fill(L, sc.V, sc.N, up ? b.
Xhi : b.
Xlo, sc.Z, sc.D, up);
1203 if (method ==
"pbk.upper" || method ==
"pbk.lower") {
1205 return detail::ba_fill(L, sc.V, sc.N, up ? b.
Xhi : b.
Xlo, sc.Z, sc.D, up);
1207 if (method ==
"bjbk.upper" || method ==
"bjbk.lower") {
1209 return detail::ba_fill(L, sc.V, sc.N, up ? b.
Xhi : b.
Xlo, sc.Z, sc.D, up);
1211 if (method ==
"cbh.upper" || method ==
"cbh.lower") {
1213 return detail::ba_fill(L, sc.V, sc.N, up ? b.
Xhi : b.
Xlo, sc.Z, sc.D, up);
1216 if (!(sc.Z == zero))
1218 "' supports Z=0 (no delay station) only; delay needs the SIB "
1219 "Section-3.2 extension");
1222 "' solves a quadratic and is unavailable in exact arithmetic");
1225 return detail::ba_fill(L, sc.V, sc.N, up ? b.
Xhi : b.
Xlo, sc.Z, sc.D, up);