3 % @file pfqn_sens_mom.m
4 % @brief Exact higher moments (up to order three) of
the per-station total
5 % queue lengths of a closed product-form queueing network, by
6 % second-order differentiation of
the MVA recursion.
10function mom = pfqn_sens_mom(L,N,Z,mi,groups)
13 % @brief Exact moments E[Q_i], E[Q_i^2], E[Q_i^3] and
the covariances
14 % Cov[Q_i,Q_j] of
the TOTAL queue lengths Q_i = sum_r n(i,r) of a closed
15 % product-form (BCMP) queueing network.
17 % The method
is the moment analysis of Strelen. Its Theorem 3.1 states
18 % that one further
factor Q_i in a moment costs one differentiation with
19 % respect to x_i,
the reciprocal of
the capacity of station i, i.e. a
20 % parameter that scales
the service times of ALL classes at station i:
22 % E[Q_i^j] = m_i E[Q_i^(j-1)] + x_i d/dx_i E[Q_i^(j-1)], Q_i^0 = 1
24 % Iterating from E[Q_i^0] = 1 gives, with m_i = E[Q_i] (equation (3.2)):
26 % Var[Q_i] = x_i dm_i/dx_i
27 % Cov[Q_i,Q_j]= x_j dm_i/dx_j = x_i dm_j/dx_i
28 % E[Q_i^2] = x_i dm_i/dx_i + m_i^2
29 % E[Q_i^3] = x_i^2 d^2m_i/dx_i^2 + (x_i + 3 x_i m_i) dm_i/dx_i + m_i^3
31 % so
the third moment requires
the SECOND derivative of
the MVA
32 % recursion, which
is what this routine adds over pfqn_sens_mva and
33 % pfqn_sens (both first order only). The derivatives are obtained by
34 % second-order forward-mode differentiation of
the Reiser-Lavenberg
35 % recursion, i.e. by carrying, for each parameter,
the value together
36 % with its first and second derivative along
the population lattice
37 % (Theorem 3.2 for one class, Theorem 3.5 for several).
39 % The parameter need not scale a whole
column. Theorem 1 of Akyildiz and
40 % Strelen states
the same recursion for a parameter that scales
the
41 % service times of an arbitrary class subset T at station i, and
the
42 % moments it generates are then those of Q_(i,T) = sum_(r in T) n(i,r).
43 % GROUPS supplies that subset structure: it partitions
the classes, and
44 %
the routine reports
the moments of each group
's queue length at each
45 % station. The three useful settings are
46 % groups = ones(1,R) the whole column: per-station TOTALS (default,
47 % this is Strelen's x_i);
48 % groups = 1:R one
class per group: PER-CLASS moments, so that
49 % even
the third moment
is per class;
50 % groups = chain(r) one group per chain: PER-CHAIN moments.
51 % Strelen states only
the first;
the generalization
is Akyildiz and
52 % Strelen's T. pfqn_sens_mom_validate checks
the per-class setting
53 % against brute force and against pfqn_sens_mva's second moments, which
54 % it must reproduce exactly.
56 % Reference: J. C. Strelen, "Moment Analysis for Closed Queuing Networks
57 % and its Linearizer", Performance Evaluation 11:127-142, 1990,
58 % Theorems 2.1, 3.1, 3.2, 3.5 and equation (3.2).
60 % @fn pfqn_sens_mom(L, N, Z, mi)
61 % @param L Service demand matrix (M x R), L(i,r) = visits_ir / rate_ir.
62 % @param N Population vector (1 x R).
63 % @param Z Think time vector (1 x R). Default: zeros.
64 % @param mi (Optional) Server multiplicity vector (1 x M). Default: ones.
65 % @param groups (Optional) Class-to-group
map (1 x R), a partition of
the
66 % classes into G = max(groups) groups labelled 1..G. The moments
67 % returned are those of each group's queue length at each station.
68 % Default: ones(1,R), i.e. one group holding every class, which
is the
70 % @return mom A struct with:
71 % .X (1 x R), .Q (M x R), .U (M x R), .R (M x R) base MVA measures,
72 % identical to pfqn_mva(L,N,Z,mi).
73 % .m (M x G) m(i,g) = E[Q_(i,g)],
the mean queue length of group g at
74 % station i. With
the default groups this
is (M x 1),
the station total.
75 % .d2m (M x G) d2m(i,g) =
the scaled pure second derivative with respect
76 % to
the parameter of (i,g). Only that entry
is needed by (3.2);
the
77 % mixed second derivatives are not required for moments of a single
78 % Q_(i,g) and would cost an extra
factor M*G to carry.
79 % .Var (M x G) Var[Q_(i,g)].
80 % .M2 (M x G) E[Q_(i,g)^2].
81 % .M3 (M x G) E[Q_(i,g)^3].
82 % .Skew (M x G) skewness of Q_(i,g). NaN where Var
is zero.
83 % .Cov, .dm Cov((i,g),(j,g')) = Cov[Q_(i,g),Q_(j,g')] and the scaled
84 % first derivative it comes from. Shaped (M x G x M x G) in general, but
85 % COLLAPSED to (M x M) in the default single-group case, where the group
86 % index carries no information and an (M x 1 x M x 1) array would only be
88 % .CovAsym (scalar) raw asymmetry of Cov before symmetrization. The two
89 % triangles are distinct expressions that must agree, so this is a live
90 % residual of the recursion; expect roundoff.
93 % - Restricted to closed populations, as is the moment analysis of the
94 % reference. Mixed and load-dependent second moments are in
96 % - Moments of the sojourn times at FCFS centers are built on top of these
97 % queue-length moments by pfqn_sens_respt, following Theorem 4.1.
98 % - The exact recursion costs O(prod(N+1)) lattice points; pfqn_sens_linearizer
99 % approximates the same quantities in polynomial time.
104if nargin < 3 || isempty(Z)
108if nargin < 4 || isempty(mi)
112if nargin < 5 || isempty(groups)
115groups = round(groups(:)');
117 line_error(mfilename,'demand matrix and population vector have different number of classes');
120 line_error(mfilename,'pfqn_sens_mom requires a closed population');
122if length(groups) ~= R || any(groups < 1)
123 line_error(mfilename,'groups must be a (1 x R) vector of group labels starting at 1');
126if ~isequal(unique(groups), 1:G)
127 line_error(mfilename,'groups must label the classes consecutively from 1 to max(groups), with no empty group');
130X = zeros(1,R); Q = zeros(M,R); U = zeros(M,R); C = zeros(M,R);
131m = zeros(M,G); dm = zeros(M,G,M,G); d2m = zeros(M,G);
134 mom = pack(X,Q,U,C,m,dm,d2m,G);
138% population-lattice odometer, identical to pfqn_mva
141 prods(w) = prod(ones(1,R-(w+1)+1) + N(w+1:R));
144while N(firstnonempty) == 0
145 firstnonempty = firstnonempty - 1;
149% Parameters are indexed by (station h, group g): y_(h,g) scales L(h,r) for
150% every class r in group g. At y = 1 the y-derivatives are exactly the scaled
151% x-derivatives that (3.2) asks for, since a pure rescaling x -> x*y gives
152% d/dy = x d/dx and d2/dy2 = x^2 d2/dx2.
153% Qtot(row,i) = sum_r Q(i,r) at population row; the recursion needs only the
154% station total, whatever the grouping.
155% D1(row,i,p) = d/dy_p Qtot(i), D2(row,i,p) = d^2/dy_p^2 Qtot(i)
156% The per-group queue lengths and their derivatives are accumulated for the
157% CURRENT population only and overwritten each step, so at the end of the walk
158% they hold the values at N, exactly as X, Q and C do.
163 pidx(h,g) = (h-1)*G + g;
166Qtot = zeros(totpop,M);
167D1 = zeros(totpop,M,P);
168D2 = zeros(totpop,M,P);
169Qg = zeros(M,G); D1Qg = zeros(M,G,P); D2Qg = zeros(M,G,P);
174Cs = zeros(M,1); dCs = zeros(M,P); d2Cs = zeros(M,P);
177 % the group accumulators describe one population only
178 Qg(:) = 0; D1Qg(:) = 0; D2Qg(:) = 0;
187 pos = pos + n(w)*prods(w);
194 % ---- residence times and their first two derivatives -----------
195 % C(i,s) = L(i,s)*y_(i,g(s))*(mi(i)+Qtot(i|n-e_s)) =: L(i,s)*y_(i,g(s))*A
196 % The parameter (h,g) touches C(i,s) directly only when i == h AND class
197 % s belongs to group g; otherwise it acts only through A.
198 % d/dy_p C = L(i,s)*( [i==h & g(s)==g]*A + dA/dy_p )
199 % d2/dy_p2 C = L(i,s)*( 2*[i==h & g(s)==g]*dA/dy_p + d2A/dy_p2 )
203 d2CNtot = zeros(1,P);
205 A = mi(i) + Qtot(row,i);
208 CNtot = CNtot + Cs(i);
213 dCs(i,p) = L(i,s) * (A + dA);
214 d2Cs(i,p) = L(i,s) * (2*dA + d2A);
216 dCs(i,p) = L(i,s) * dA;
217 d2Cs(i,p) = L(i,s) * d2A;
219 dCNtot(p) = dCNtot(p) + dCs(i,p);
220 d2CNtot(p) = d2CNtot(p) + d2Cs(i,p);
224 % ---- throughput and its first two derivatives -------------------
225 % X(s) = n(s)/den, den = Z(s) + sum_i C(i,s)
226 % dX = -n(s)*dden/den^2
227 % d2X = -n(s)*d2den/den^2 + 2*n(s)*dden^2/den^3
230 dX = zeros(1,P); d2X = zeros(1,P);
232 dX(p) = -n(s) * dCNtot(p) / den^2;
233 d2X(p) = -n(s) * d2CNtot(p) / den^2 + 2*n(s) * dCNtot(p)^2 / den^3;
236 % ---- queue lengths ---------------------------------------------
237 % Q = X*C, dQ = dX*C + X*dC, d2Q = d2X*C + 2*dX*dC + X*d2C
239 Q(i,s) = X(s) * Cs(i);
240 Qtot(currentpop,i) = Qtot(currentpop,i) + Q(i,s);
241 Qg(i,gs) = Qg(i,gs) + Q(i,s);
243 dQ = dX(p)*Cs(i) + X(s)*dCs(i,p);
244 d2Q = d2X(p)*Cs(i) + 2*dX(p)*dCs(i,p) + X(s)*d2Cs(i,p);
245 D1(currentpop,i,p) = D1(currentpop,i,p) + dQ;
246 D2(currentpop,i,p) = D2(currentpop,i,p) + d2Q;
247 D1Qg(i,gs,p) = D1Qg(i,gs,p) + dQ;
248 D2Qg(i,gs,p) = D2Qg(i,gs,p) + d2Q;
254 % ---- odometer advance ---------------------------------------------
256 while (s>0 && n(s)==N(s)) || s>firstnonempty
269 currentpop = currentpop + 1;
275 U(i,r) = X(r) * L(i,r);
279% moments at the full population: Qg, D1Qg and D2Qg were overwritten on every
280% population sweep, so they now hold the values at N.
286 dm(i,g,j,g2) = D1Qg(i,g,pidx(j,g2));
289 d2m(i,g) = D2Qg(i,g,pidx(i,g));
293mom = pack(X,Q,U,C,m,dm,d2m,G);
296% =========================================================================
297function mom = pack(X,Q,U,C,m,dm,d2m,G)
299mom.X = X; mom.Q = Q; mom.U = U; mom.R = C;
300mom.m = m; mom.d2m = d2m;
302% Cov((i,g),(j,g')) = the scaled derivative of m_(i,g) w.r.t. the parameter of
303% (j,g'), and the transposed entry, are distinct expressions for the same
304% quantity; report the raw disagreement, then symmetrize.
305flat = reshape(dm, M*G, M*G);
306mom.CovAsym = max(max(abs(flat - flat.')));
307if isempty(mom.CovAsym)
310flat = (flat + flat.') / 2;
312 % the group index carries no information here; an (M x 1 x M x 1) array
313 % would only be awkward to index
314 mom.Cov = reshape(flat, M, M);
315 mom.dm = reshape(dm, M, M);
317 mom.Cov = reshape(flat, M, G, M, G);
321Var = zeros(M,G); M2 = zeros(M,G); M3 = zeros(M,G); Skew = zeros(M,G);
325 Var(i,g) = d1; % (3.2)
326 M2(i,g) = d1 + m(i,g)^2; % (3.2)
327 M3(i,g) = d2m(i,g) + (1 + 3*m(i,g))*d1 + m(i,g)^3; % (3.2)
328 % third central moment mu3 = E[Q^3] - 3 m E[Q^2] + 2 m^3
329 mu3 = M3(i,g) - 3*m(i,g)*M2(i,g) + 2*m(i,g)^3;
331 Skew(i,g) = mu3 / Var(i,g)^1.5;
337mom.Var = Var; mom.M2 = M2; mom.M3 = M3; mom.Skew = Skew;