LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mapqn_amva.h
Go to the documentation of this file.
1#pragma once
2/**
3 * @file mapqn_amva.h
4 * @ingroup api_mapqn
5 * @brief Horizontal-cut mean value analysis for a MAP server (SolverMVA method 'amva.mapqn').
6 *
7 * Closed multiclass network of an exponential infinite-server station (think rate mu_r for
8 * class r) and one FCFS single-server station whose class-r service is the MAP (D0_r, D1_r);
9 * the MAP of class r moves only while a class-r job is in service and is frozen otherwise,
10 * the convention of the CTMC solver. The recursion walks the population lattice n <= N in
11 * lexicographic order and solves ONE linear R x R system per point. Its unknowns are the
12 * per-phase means Q_r^k = E[n_r 1{k}] over the joint phase k = (k_1..k_R), the busy laws
13 * U_r^k = P[serving r, k], the phase law pi_k and the throughputs X_r.
14 *
15 * Exact relations: the joint phase balance, the class marginals U_r = X_r E[S_r] theta_r
16 * and the per-class horizontal cut (generator balance of n_r 1{k}) of Casale-Smirni,
17 * "MAP-AMVA: Approximate Mean Value Analysis of Bursty Systems", IEEE/IFIP DSN 2009.
18 * Closures: the product busy law theta_r(k_r) prod_{s != r} phi_s(k_s), phi the
19 * post-completion law of a frozen MAP, which solves the phase balance identically; the
20 * service-age closure of the cross term E[n_r 1{serving s} 1{k}] (class r accumulates at
21 * its throughput over the elapsed class-s service, whose mean given the phase is
22 * theta_s (-D0_s)^{-1} / theta_s); Little's law resolved by arrival phase with the exact
23 * FCFS response of the queue composition seen at n - e_r (the multiclass arrival
24 * theorem). K_r = 1 for every class reproduces multiclass FCFS MVA on class means.
25 *
26 * Port of matlab/src/api/mapqn/mapqn_amva.m; the arithmetic is done in double whatever T,
27 * since the recursion interpolates response tables at fractional populations.
28 */
29#include <algorithm>
30#include <cmath>
31#include <cstddef>
32#include <vector>
33
34#include "line/num/number.h"
35#include "line/util/error.h"
36#include "line/util/matrix.h"
37
38namespace line {
39namespace mapqn {
40
41/** X: class throughputs; Qq: mean queue lengths at the MAP station (job in service
42 * included); U: busy probability per class, X E[S]; ES: mean service times; pi: joint
43 * phase law at N (class R fastest). */
44template <class T>
46 std::vector<T> X, Qq, U, ES, pi;
47};
48
49namespace amva_detail {
50
51inline std::vector<double> solve_linear(std::vector<std::vector<double>> A, std::vector<double> b) {
52 const std::size_t n = b.size();
53 for (std::size_t c = 0; c < n; ++c) {
54 std::size_t p = c;
55 for (std::size_t i = c + 1; i < n; ++i)
56 if (std::fabs(A[i][c]) > std::fabs(A[p][c])) p = i;
57 std::swap(A[c], A[p]);
58 std::swap(b[c], b[p]);
59 const double piv = A[c][c];
60 if (piv == 0.0) throw InputError("mapqn_amva: singular linear system");
61 for (std::size_t i = c + 1; i < n; ++i) {
62 const double f = A[i][c] / piv;
63 if (f == 0.0) continue;
64 for (std::size_t j = c; j < n; ++j) A[i][j] -= f * A[c][j];
65 b[i] -= f * b[c];
66 }
67 }
68 std::vector<double> x(n, 0.0);
69 for (std::size_t ii = n; ii-- > 0;) {
70 double s = b[ii];
71 for (std::size_t j = ii + 1; j < n; ++j) s -= A[ii][j] * x[j];
72 x[ii] = s / A[ii][ii];
73 }
74 return x;
75}
76
77inline std::vector<std::vector<double>> inverse(const std::vector<std::vector<double>>& A) {
78 const std::size_t n = A.size();
79 std::vector<std::vector<double>> inv(n, std::vector<double>(n, 0.0));
80 for (std::size_t c = 0; c < n; ++c) {
81 std::vector<double> e(n, 0.0);
82 e[c] = 1.0;
83 const std::vector<double> col = solve_linear(A, e);
84 for (std::size_t i = 0; i < n; ++i) inv[i][c] = col[i];
85 }
86 return inv;
87}
88
89/** theta G = 0, theta 1 = 1: transpose G and replace the last equation by the normalization. */
90inline std::vector<double> stationary(const std::vector<std::vector<double>>& G) {
91 const std::size_t K = G.size();
92 std::vector<std::vector<double>> A(K, std::vector<double>(K, 0.0));
93 std::vector<double> rhs(K, 0.0);
94 for (std::size_t i = 0; i < K; ++i)
95 for (std::size_t j = 0; j < K; ++j) A[i][j] = G[j][i];
96 for (std::size_t j = 0; j < K; ++j) A[K - 1][j] = 1.0;
97 rhs[K - 1] = 1.0;
98 std::vector<double> th = solve_linear(A, rhs);
99 double s = 0.0;
100 for (double& v : th) { v = std::max(v, 0.0); s += v; }
101 for (double& v : th) v /= s;
102 return th;
103}
104
105} // namespace amva_detail
106
107template <class T>
108MapqnAmvaResult<T> mapqn_amva(const std::vector<T>& mu_in, const std::vector<Matrix<T>>& D0s,
109 const std::vector<Matrix<T>>& D1s, const std::vector<int>& N) {
110 using amva_detail::inverse;
111 using amva_detail::solve_linear;
112 using amva_detail::stationary;
113 using Mat = std::vector<std::vector<double>>;
114 const std::size_t R = N.size();
115 if (mu_in.size() != R || D0s.size() != R || D1s.size() != R)
116 throw InputError("mapqn_amva: mu, D0s, D1s and N must all have one entry per class");
117 std::vector<double> mu(R);
118 std::vector<std::size_t> Ks(R);
119 std::vector<Mat> D0(R), D1(R);
120 for (std::size_t r = 0; r < R; ++r) {
121 mu[r] = num_traits<T>::to_double(mu_in[r]);
122 Ks[r] = D0s[r].rows();
123 D0[r].assign(Ks[r], std::vector<double>(Ks[r], 0.0));
124 D1[r].assign(Ks[r], std::vector<double>(Ks[r], 0.0));
125 for (std::size_t i = 0; i < Ks[r]; ++i)
126 for (std::size_t j = 0; j < Ks[r]; ++j) {
127 D0[r][i][j] = num_traits<T>::to_double(D0s[r](i, j));
128 D1[r][i][j] = num_traits<T>::to_double(D1s[r](i, j));
129 }
130 }
131 std::size_t K = 1;
132 for (std::size_t r = 0; r < R; ++r) K *= Ks[r];
133 std::vector<std::size_t> stride(R, 1);
134 for (std::size_t r = 0; r < R; ++r)
135 for (std::size_t t = r + 1; t < R; ++t) stride[r] *= Ks[t]; // class R-1 is the fastest index
136 std::vector<std::vector<std::size_t>> krOf(K, std::vector<std::size_t>(R, 0));
137 for (std::size_t k = 0; k < K; ++k)
138 for (std::size_t r = 0; r < R; ++r) krOf[k][r] = (k / stride[r]) % Ks[r];
139 int Ntot = 0;
140 for (std::size_t r = 0; r < R; ++r) Ntot += N[r];
141
142 std::vector<Mat> G(R), Ainv(R), Tt(R);
143 std::vector<std::vector<double>> th(R), phi(R), age(R);
144 std::vector<double> ES(R, 0.0), abar(R, 0.0);
145 for (std::size_t r = 0; r < R; ++r) {
146 const std::size_t Kr = Ks[r];
147 G[r].assign(Kr, std::vector<double>(Kr, 0.0));
148 for (std::size_t i = 0; i < Kr; ++i)
149 for (std::size_t j = 0; j < Kr; ++j) G[r][i][j] = D0[r][i][j] + D1[r][i][j];
150 th[r] = stationary(G[r]);
151 double rate = 0.0;
152 for (std::size_t i = 0; i < Kr; ++i)
153 for (std::size_t j = 0; j < Kr; ++j) rate += th[r][i] * D1[r][i][j];
154 ES[r] = 1.0 / rate;
155 phi[r].assign(Kr, 0.0); // post-completion phase law
156 for (std::size_t j = 0; j < Kr; ++j) {
157 double a = 0.0;
158 for (std::size_t i = 0; i < Kr; ++i) a += th[r][i] * D1[r][i][j];
159 phi[r][j] = a * ES[r];
160 }
161 Mat negD0(Kr, std::vector<double>(Kr, 0.0));
162 for (std::size_t i = 0; i < Kr; ++i)
163 for (std::size_t j = 0; j < Kr; ++j) negD0[i][j] = -D0[r][i][j];
164 const Mat negD0inv = inverse(negD0);
165 std::vector<double> s(Kr, 0.0); // mean time to the next completion
166 for (std::size_t i = 0; i < Kr; ++i)
167 for (std::size_t j = 0; j < Kr; ++j) s[i] += negD0inv[i][j];
168 Mat P(Kr, std::vector<double>(Kr, 0.0)); // embedded phase transition
169 for (std::size_t i = 0; i < Kr; ++i)
170 for (std::size_t j = 0; j < Kr; ++j) {
171 double a = 0.0;
172 for (std::size_t m = 0; m < Kr; ++m) a += negD0inv[i][m] * D1[r][m][j];
173 P[i][j] = a;
174 }
175 Tt[r].assign(Ntot + 2, std::vector<double>(Kr, 0.0)); // T[j][k] = e_k'(I+P+..+P^(j-1)) s
176 std::vector<double> acc(Kr, 0.0), v = s;
177 for (int j = 1; j <= Ntot + 1; ++j) {
178 for (std::size_t i = 0; i < Kr; ++i) acc[i] += v[i];
179 Tt[r][j] = acc;
180 std::vector<double> nv(Kr, 0.0);
181 for (std::size_t i = 0; i < Kr; ++i)
182 for (std::size_t m = 0; m < Kr; ++m) nv[i] += P[i][m] * v[m];
183 v = nv;
184 }
185 std::vector<double> w(Kr, 0.0); // theta (-D0)^{-1}
186 for (std::size_t j = 0; j < Kr; ++j)
187 for (std::size_t i = 0; i < Kr; ++i) w[j] += th[r][i] * negD0inv[i][j];
188 age[r].assign(Kr, 0.0);
189 for (std::size_t j = 0; j < Kr; ++j) { age[r][j] = w[j] / th[r][j]; abar[r] += w[j]; }
190 Mat A(Kr, std::vector<double>(Kr, 0.0));
191 for (std::size_t i = 0; i < Kr; ++i)
192 for (std::size_t j = 0; j < Kr; ++j) A[i][j] = G[r][i][j] - (i == j ? mu[r] : 0.0);
193 Ainv[r] = inverse(A);
194 }
195 // joint-phase shapes: idle law F and class-r busy law u[r]
196 std::vector<double> F(K, 1.0);
197 std::vector<std::vector<double>> u(R, std::vector<double>(K, 1.0));
198 for (std::size_t k = 0; k < K; ++k) {
199 for (std::size_t r = 0; r < R; ++r) F[k] *= phi[r][krOf[k][r]];
200 for (std::size_t r = 0; r < R; ++r)
201 for (std::size_t s = 0; s < R; ++s) u[r][k] *= (s == r) ? th[s][krOf[k][s]] : phi[s][krOf[k][s]];
202 }
203 auto apply_axis = [&](const std::vector<double>& V, const Mat& M, std::size_t r) {
204 std::vector<double> out(K, 0.0);
205 for (std::size_t k = 0; k < K; ++k) {
206 const std::size_t kr = krOf[k][r];
207 const std::size_t base = k - kr * stride[r];
208 double a = 0.0;
209 for (std::size_t h = 0; h < Ks[r]; ++h) a += V[base + h * stride[r]] * M[h][kr];
210 out[k] = a;
211 }
212 return out;
213 };
214 auto t_at = [&](std::size_t r, double b, std::size_t kr) {
215 const Mat& Tr = Tt[r];
216 long j0 = static_cast<long>(std::floor(b));
217 j0 = std::max<long>(0, std::min<long>(j0, static_cast<long>(Tr.size()) - 2));
218 const double f = std::min(std::max(b - static_cast<double>(j0), 0.0), 1.0);
219 return (1.0 - f) * Tr[static_cast<std::size_t>(j0)][kr] + f * Tr[static_cast<std::size_t>(j0) + 1][kr];
220 };
221 // population lattice in lexicographic order: n - e_r always precedes n
222 std::vector<std::size_t> lstride(R, 1);
223 std::size_t L = 1;
224 for (std::size_t rr = R; rr-- > 0;) { lstride[rr] = L; L *= static_cast<std::size_t>(N[rr] + 1); }
225 std::vector<std::vector<std::vector<double>>> Qs(L, std::vector<std::vector<double>>(R, std::vector<double>(K, 0.0)));
226 std::vector<std::vector<double>> pis(L, std::vector<double>(K, 0.0)), Xs(L, std::vector<double>(R, 0.0));
227 pis[0] = F;
228 for (std::size_t l = 1; l < L; ++l) {
229 std::vector<int> n(R, 0);
230 for (std::size_t r = 0; r < R; ++r) n[r] = static_cast<int>((l / lstride[r]) % static_cast<std::size_t>(N[r] + 1));
231 std::vector<std::vector<std::vector<double>>> b(R), bN(R);
232 std::vector<std::vector<double>> Rk(R, std::vector<double>(K, 0.0));
233 for (std::size_t r = 0; r < R; ++r) {
234 if (n[r] < 1) continue;
235 const std::size_t lp = l - lstride[r];
236 b[r].assign(R, std::vector<double>(K, 0.0));
237 for (std::size_t t = 0; t < R; ++t)
238 for (std::size_t k = 0; k < K; ++k) b[r][t][k] = pis[lp][k] > 0 ? Qs[lp][t][k] / pis[lp][k] : 0.0;
239 for (std::size_t k = 0; k < K; ++k) {
240 double a = 0.0;
241 for (std::size_t t = 0; t < R; ++t) a += t_at(t, b[r][t][k] + (t == r ? 1.0 : 0.0), krOf[k][t]);
242 Rk[r][k] = a;
243 }
244 bN[r].assign(R, std::vector<double>(K, 0.0));
245 for (std::size_t t = 0; t < R; ++t) {
246 if (t == r || n[t] == 0) continue;
247 const double Xt = Xs[lp][t];
248 double Qt = 0.0;
249 for (std::size_t k = 0; k < K; ++k) Qt += Qs[lp][t][k];
250 if (Xt > 0) {
251 const double W = std::max(Qt / Xt - abar[r], 0.0);
252 for (std::size_t k = 0; k < K; ++k)
253 bN[r][t][k] = Xt * std::min(W + age[r][krOf[k][r]], static_cast<double>(n[t]) / Xt);
254 }
255 }
256 }
257 // the cut, linear in X: Q_r = c0[r] + sum_s X_s c1[r][s]
258 std::vector<std::vector<double>> c0(R);
259 std::vector<std::vector<std::vector<double>>> c1(R, std::vector<std::vector<double>>(R));
260 for (std::size_t r = 0; r < R; ++r) {
261 if (n[r] < 1) continue;
262 std::vector<double> v0(K, 0.0);
263 for (std::size_t k = 0; k < K; ++k) v0[k] = -mu[r] * n[r] * F[k];
264 c0[r] = apply_axis(v0, Ainv[r], r);
265 for (std::size_t s = 0; s < R; ++s) {
266 std::vector<double> term(K, 0.0);
267 for (std::size_t k = 0; k < K; ++k) term[k] = -mu[r] * n[r] * ES[s] * (u[s][k] - F[k]);
268 if (s == r) {
269 const std::vector<double> ud = apply_axis(u[r], D1[r], r);
270 for (std::size_t k = 0; k < K; ++k) term[k] += ES[r] * ud[k];
271 } else if (n[s] >= 1) {
272 std::vector<double> W(K, 0.0);
273 for (std::size_t k = 0; k < K; ++k) W[k] = ES[s] * u[s][k] * bN[s][r][k];
274 const std::vector<double> a1 = apply_axis(W, G[r], r), a2 = apply_axis(W, G[s], s);
275 for (std::size_t k = 0; k < K; ++k) term[k] += a1[k] - a2[k];
276 }
277 c1[r][s] = apply_axis(term, Ainv[r], r);
278 }
279 }
280 // Little's law by arrival phase: one R x R solve
281 Mat M(R, std::vector<double>(R, 0.0));
282 std::vector<double> v(R, 0.0);
283 for (std::size_t r = 0; r < R; ++r) M[r][r] = 1.0;
284 for (std::size_t r = 0; r < R; ++r) {
285 if (n[r] < 1) continue;
286 double a = 0.0;
287 for (std::size_t k = 0; k < K; ++k) a += (n[r] * F[k] - c0[r][k]) * Rk[r][k];
288 v[r] = n[r] - mu[r] * a;
289 M[r][r] = 1.0 / mu[r];
290 for (std::size_t s = 0; s < R; ++s) {
291 if (n[s] < 1) continue;
292 double e = 0.0;
293 for (std::size_t k = 0; k < K; ++k) e += (n[r] * ES[s] * (u[s][k] - F[k]) - c1[r][s][k]) * Rk[r][k];
294 M[r][s] += mu[r] * e;
295 }
296 }
297 const std::vector<double> X = solve_linear(M, v);
298 std::vector<double> pi(K, 0.0);
299 double psum = 0.0;
300 for (std::size_t k = 0; k < K; ++k) {
301 double p = F[k];
302 for (std::size_t s = 0; s < R; ++s) p += X[s] * ES[s] * (u[s][k] - F[k]);
303 pi[k] = std::max(p, 0.0);
304 psum += pi[k];
305 }
306 for (double& p : pi) p /= psum;
307 for (std::size_t r = 0; r < R; ++r) {
308 if (n[r] < 1) continue;
309 std::vector<double> Ur(K, 0.0), Qr(K, 0.0), Wr(K, 0.0);
310 double usum = 0.0, wsum = 0.0;
311 for (std::size_t k = 0; k < K; ++k) {
312 Ur[k] = X[r] * ES[r] * u[r][k];
313 Qr[k] = c0[r][k];
314 for (std::size_t s = 0; s < R; ++s) Qr[k] += X[s] * c1[r][s][k];
315 Wr[k] = std::max(Qr[k] - Ur[k], 0.0);
316 usum += Ur[k];
317 wsum += Wr[k];
318 }
319 // project onto Q >= U keeping the flow-balance total n_r - X_r/mu_r
320 const double tot = std::max(n[r] - X[r] / mu[r] - usum, 0.0);
321 for (std::size_t k = 0; k < K; ++k) Qs[l][r][k] = Ur[k] + (wsum > 0 ? Wr[k] * tot / wsum : 0.0);
322 }
323 pis[l] = pi;
324 Xs[l] = X;
325 }
327 out.X.resize(R); out.Qq.resize(R); out.U.resize(R); out.ES.resize(R); out.pi.resize(K);
328 for (std::size_t r = 0; r < R; ++r) {
329 double q = 0.0;
330 for (std::size_t k = 0; k < K; ++k) q += Qs[L - 1][r][k];
331 out.X[r] = num_traits<T>::from_double(Xs[L - 1][r]);
332 out.Qq[r] = num_traits<T>::from_double(q);
333 out.U[r] = num_traits<T>::from_double(Xs[L - 1][r] * ES[r]);
334 out.ES[r] = num_traits<T>::from_double(ES[r]);
335 }
336 for (std::size_t k = 0; k < K; ++k) out.pi[k] = num_traits<T>::from_double(pis[L - 1][k]);
337 return out;
338}
339
340} // namespace mapqn
341} // namespace line
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Dense matrix and non-owning view.
MapqnAmvaResult< T > mapqn_amva(const std::vector< T > &mu_in, const std::vector< Matrix< T > > &D0s, const std::vector< Matrix< T > > &D1s, const std::vector< int > &N)
Definition mapqn_amva.h:108
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Definition linalg.h:72
Number-type abstraction for the templated API port.
X: class throughputs; Qq: mean queue lengths at the MAP station (job in service included); U: busy pr...
Definition mapqn_amva.h:45