5#ifndef LINE_API_PFQN_AB_AMVA_H
6#define LINE_API_PFQN_AB_AMVA_H
104Matrix<T> ab_weight_fun(
const std::vector<int>& population,
int lmax,
double alpha,
double beta) {
106 for (
int n : population)
107 if (n > maxPop) maxPop = n;
108 if (lmax > maxPop) maxPop = lmax;
109 if (maxPop < 0) maxPop = 0;
112 const std::size_t n1 =
static_cast<std::size_t
>(maxPop) + 1;
114 std::vector<T> scaling(n1, zero);
117 for (
int n = 2; n <= maxPop; ++n)
118 scaling[
static_cast<std::size_t
>(n)] =
124 for (
int l = 1; l <= maxPop; ++l) {
125 const std::size_t lu =
static_cast<std::size_t
>(l);
127 for (
int j = 0; j <= l - 1; ++j) {
128 const std::size_t ju =
static_cast<std::size_t
>(j);
129 w(lu, ju) = w(lu - 1, ju) - w(lu - 1, ju) * scaling[lu] / hundred;
132 w(lu, lu) = one -
sum;
139std::map<int, T> ab_marginal_probs(
const T& avgJobs,
int numServers,
140 const std::vector<int>& population, std::size_t classIdx,
145 const int floorVal =
static_cast<int>(std::floor(aj));
148 const int ceilVal = floorVal + 1;
154 const int ceiling = floorVal + 1;
155 const int maxVal = (2 * floorVal + 1) < (numServers - 2) ? (2 * floorVal + 1) : (numServers - 2);
156 const Matrix<T> w = ab_weight_fun<T>(population, floorVal < 0 ? 0 : floorVal, 45.0, 0.7);
157 const std::size_t wn = w.rows();
158 const auto wat = [&](
int l,
int j) -> T {
159 if (l < 0 || j < 0 ||
static_cast<std::size_t
>(l) >= wn ||
160 static_cast<std::size_t
>(j) >= wn)
161 return num_traits<T>::from_int(0);
162 return w(
static_cast<std::size_t
>(l),
static_cast<std::size_t
>(j));
164 const int popc = population[classIdx];
166 for (
int j = 0; j <= maxVal; ++j) {
168 const int lDist = floorVal - j;
169 const int lowerVal = floorVal - lDist;
170 const int upperVal = ceiling + lDist;
172 if (lDist <= 25 && floorVal < popc && upperVal != lowerVal)
173 prob = wat(floorVal, lDist) *
174 ((num_traits<T>::from_int(upperVal) - avgJobs) /
175 num_traits<T>::from_int(upperVal - lowerVal));
178 const int uDist = j - ceiling;
181 }
else if (j > popc - 1 && uDist < 25) {
184 const auto ite = mp.find(popc - 1);
185 const T existing = ite == mp.end() ? zero : ite->second;
186 const auto itf = mp.find(floorVal - uDist);
187 const T mfu = itf == mp.end() ? zero : itf->second;
188 mp[popc - 1] = existing + (wat(floorVal, uDist) - mfu);
190 const auto itf = mp.find(floorVal - uDist);
191 const T mfu = itf == mp.end() ? zero : itf->second;
192 mp[j] = wat(floorVal, uDist) - mfu;
201T ab_bcn(
const Matrix<T>& S, std::size_t i, std::size_t c,
const std::vector<int>& nvec,
int ns) {
204 for (
int t : nvec) nsum += t;
206 const T eps = num_traits<T>::from_double(1e-12);
207 T sumVal = num_traits<T>::from_int(0);
208 for (std::size_t t = 0; t < nvec.size(); ++t)
209 sumVal += num_traits<T>::from_int(nvec[t]) * S(i, t);
210 const T num = num_traits<T>::from_int(nsum - ns > 0 ? nsum - ns : 0);
211 const T den0 = num_traits<T>::from_int(ns * (nsum - 1));
212 const T den = den0 > eps ? den0 : eps;
213 bcn += num / den * (sumVal - S(i, c));
220T ab_binomial_prob(
const std::vector<int>& n,
const std::vector<int>& Kpop,
const T& Ljr) {
221 const T zero = num_traits<T>::from_int(0);
222 const T one = num_traits<T>::from_int(1);
224 for (std::size_t r = 0; r < Kpop.size(); ++r) {
225 if (Kpop[r] <= 0)
continue;
226 const T frac = Ljr / num_traits<T>::from_int(Kpop[r]);
227 if (frac == zero)
continue;
228 if (n[r] < 0 || n[r] > Kpop[r])
continue;
230 const T t2 =
num_pow_int(frac,
static_cast<unsigned>(n[r]));
231 const T t3 =
num_pow_int(T(one - frac),
static_cast<unsigned>(Kpop[r] - n[r]));
232 prob *= t1 * t2 * t3;
239AbAmvaResult<T> ab_core(
const std::vector<int>& population,
const std::vector<int>& nservers,
240 const std::vector<SchedStrategy>& type,
const Matrix<T>& v,
241 const Matrix<T>& S, std::size_t maxiter,
const std::vector<T>& Delta,
243 const std::size_t M = S.rows(), K = S.cols();
244 const T zero = num_traits<T>::from_int(0);
245 const T one = num_traits<T>::from_int(1);
246 const auto dat = [&](std::size_t i, std::size_t r, std::size_t t) ->
const T& {
247 return Delta[(i * K + r) * K + t];
251 for (
int n : population) Npop += n;
252 const T tol = one / num_traits<T>::from_int(4000 + 16 * Npop);
258 res.CN.assign(K, zero);
259 res.XN.assign(K, zero);
262 Matrix<T>& L = res.QN;
263 Matrix<T>& W = res.RN;
264 Matrix<T> F(M, K, zero);
265 std::vector<T> lWJ(M * K * K, zero);
266 const auto lwj = [&](std::size_t i, std::size_t r, std::size_t t) -> T& {
267 return lWJ[(i * K + r) * K + t];
269 const T milli = num_traits<T>::from_double(1e-3);
271 while (res.totiter < maxiter) {
272 for (std::size_t i = 0; i < M; ++i)
273 for (std::size_t r = 0; r < K; ++r)
274 F(i, r) = population[r] > 0 ? T(L(i, r) / num_traits<T>::from_int(population[r]))
276 for (std::size_t i = 0; i < M; ++i)
277 for (std::size_t r = 0; r < K; ++r)
278 for (std::size_t t = 0; t < K; ++t) {
279 const long scalar = r == t ? population[r] - 1 : population[r];
280 lwj(i, r, t) = num_traits<T>::from_int(scalar) * (F(i, r) + dat(i, r, t));
283 for (std::size_t i = 0; i < M; ++i)
284 for (std::size_t r = 0; r < K; ++r) {
287 }
else if (nservers[i] == 1) {
289 for (std::size_t c = 0; c < K; ++c) qtot += lwj(i, c, r);
290 W(i, r) = S(i, r) * (one + qtot);
293 std::vector<int> nvec(K, 0);
297 const T bcn = ab_bcn(S, i, r, nvec, nservers[i]);
298 std::vector<int> nm = nvec;
301 wait += bcn * ab_binomial_prob<T>(nm, population, lIn(i, r));
305 W(i, r) = wait <= milli ? zero : wait;
308 for (std::size_t j = 0; j < K; ++j) qtot += lwj(i, j, r);
309 const int c = nservers[i];
312 std::vector<int> popWithoutR = population;
314 const std::map<int, T> mp =
315 ab_marginal_probs<T>(qtot, c, popWithoutR, r, method);
316 for (
int j = 1; j <= c - 1; ++j) {
317 const auto it = mp.find(j);
319 corr += it->second * num_traits<T>::from_int(c - j);
322 W(i, r) = S(i, r) / num_traits<T>::from_int(c) * (one + qtot + corr);
326 for (std::size_t r = 0; r < K; ++r) {
328 for (std::size_t i = 0; i < M; ++i) cyc += v(i, r) * W(i, r);
332 Matrix<T> itQ(M, K, zero);
333 for (std::size_t i = 0; i < M; ++i)
334 for (std::size_t r = 0; r < K; ++r)
335 itQ(i, r) = res.CN[r] > zero
336 ? T(num_traits<T>::from_int(population[r]) *
337 (v(i, r) * W(i, r) / res.CN[r]))
341 for (std::size_t i = 0; i < M; ++i)
342 for (std::size_t r = 0; r < K; ++r) {
343 if (population[r] <= 0)
continue;
345 num_abs(T(L(i, r) - itQ(i, r))) / num_traits<T>::from_int(population[r]);
346 if (diff > maxDiff) maxDiff = diff;
351 if (maxDiff < tol)
break;
354 for (std::size_t r = 0; r < K; ++r)
355 res.XN[r] = W(0, r) > zero ? T(L(0, r) / W(0, r)) : zero;
356 for (std::size_t i = 0; i < M; ++i)
357 for (std::size_t r = 0; r < K; ++r) {
358 if (!(S(i, r) > zero))
continue;
360 ? T(res.XN[r] * S(i, r))
361 : T(res.XN[r] * S(i, r) / num_traits<T>::from_int(nservers[i]));
381 const std::vector<int>& nservers,
382 const std::vector<SchedStrategy>& sched,
bool fcfsSchmidt,
385 "pfqn_ab_amva requires transcendental arithmetic: its core is a "
386 "tolerance-stopped fixed point, so the answer depends on where the iteration "
387 "stops, and its marginal weights use floor and non-integer fractions");
389 const std::size_t M = S.
rows(), K = S.
cols();
390 if (N.size() != K)
throw InputError(
"pfqn_ab_amva: S and N disagree on the class count");
392 throw InputError(
"pfqn_ab_amva: visit-ratio matrix has the wrong shape");
393 if (nservers.size() != M)
throw InputError(
"pfqn_ab_amva: nservers has the wrong length");
394 if (sched.size() != M)
throw InputError(
"pfqn_ab_amva: sched has the wrong length");
395 for (std::size_t i = 0; i < M; ++i)
396 if (nservers[i] < 1)
throw InputError(
"pfqn_ab_amva: server count below one");
398 if (n < 0)
throw InputError(
"pfqn_ab_amva: negative population");
401 const std::size_t maxiter = 100;
405 for (std::size_t i = 0; i < M; ++i)
406 for (std::size_t r = 0; r < K; ++r)
409 std::vector<Matrix<T>> lWithoutR(K,
Matrix<T>(M, K, zero));
410 for (std::size_t r = 0; r < K; ++r)
411 for (std::size_t i = 0; i < M; ++i)
412 for (std::size_t t = 0; t < K; ++t)
418 std::vector<T> Delta(M * K * K, zero);
422 detail::ab_core(N, nservers, sched, v, S, maxiter, Delta, L, fcfsSchmidt, method);
426 for (std::size_t r = 0; r < K; ++r) {
427 std::vector<int> popWithout = N;
430 for (std::size_t j = 0; j < M; ++j)
431 for (std::size_t c = 0; c < K; ++c) lWithoutC(j, c) = lWithoutR[c](j, c);
432 const AbAmvaResult<T> ret = detail::ab_core(popWithout, nservers, sched, v, S, maxiter,
433 Delta, lWithoutC, fcfsSchmidt, method);
434 for (std::size_t j = 0; j < M; ++j)
435 for (std::size_t c = 0; c < K; ++c) lWithoutR[c](j, r) = ret.QN(j, c);
439 for (std::size_t i = 0; i < M; ++i)
440 for (std::size_t r = 0; r < K; ++r) {
442 for (std::size_t t = 0; t < K; ++t) {
443 const long divisor = r == t ? N[r] - 1 : N[r];
446 Delta[(i * K + r) * K + t] = F_irt - F_ir;
452 return detail::ab_core(N, nservers, sched, v, S, maxiter, Delta, LUpdated, fcfsSchmidt, method);
458 const std::vector<int>& nservers,
459 const std::vector<SchedStrategy>& sched) {
The exception types the port throws.
Dense matrix and non-owning view.
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.
AbMarginalMethod
Which marginal-probability rule the multiserver correction uses.
@ Ab
the Akyildiz-Bolch weight function
@ Scat
two-point scatter around floor(Qtot)
bool next_pop(std::vector< int > &n, const std::vector< int > &N)
Advance n to the next population vector in the lattice 0 <= n <= N, odometer order with the last clas...
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
T num_nck(int n, int k)
Binomial coefficient as a value of T, by the Pascal recurrence.
Number-type abstraction for the templated API port.
Scaffolding shared by the approximate-MVA family.
Population-vector enumeration and combinatorics.
Return value of pfqn_ab_amva, mirroring [QN,UN,RN,CN,XN,totiter].
Matrix< T > UN
(M x R) utilization
Matrix< T > RN
(M x R) residence (wait) time per visit
std::size_t totiter
iterations of the final core pass
std::vector< T > XN
(R) class throughput AT STATION 1
std::vector< T > CN
(R) cycle time
Matrix< T > QN
(M x R) mean queue length