LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
pfqn_sens_mom.m
1%{
2%{
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.
7%}
8%}
9
10function mom = pfqn_sens_mom(L,N,Z,mi,groups)
11%{
12%{
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.
16 %
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:
21 %
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
23 %
24 % Iterating from E[Q_i^0] = 1 gives, with m_i = E[Q_i] (equation (3.2)):
25 %
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
30 %
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).
38 %
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.
55 %
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).
59 %
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
69 % per-station total.
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
87 % awkward to index.
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.
91 %
92 % Notes:
93 % - Restricted to closed populations, as is the moment analysis of the
94 % reference. Mixed and load-dependent second moments are in
95 % pfqn_sens_mvaldmx.
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.
100%}
101%}
102[M,R] = size(L);
103N = ceil(N(:)');
104if nargin < 3 || isempty(Z)
105 Z = zeros(1,R);
106end
107Z = Z(:)';
108if nargin < 4 || isempty(mi)
109 mi = ones(1,M);
110end
111mi = mi(:)';
112if nargin < 5 || isempty(groups)
113 groups = ones(1,R);
114end
115groups = round(groups(:)');
116if length(N) ~= R
117 line_error(mfilename,'demand matrix and population vector have different number of classes');
118end
119if any(isinf(N))
120 line_error(mfilename,'pfqn_sens_mom requires a closed population');
121end
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');
124end
125G = max(groups);
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');
128end
129
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);
132
133if ~any(N > 0)
134 mom = pack(X,Q,U,C,m,dm,d2m,G);
135 return;
136end
137
138% population-lattice odometer, identical to pfqn_mva
139prods = zeros(1,R-1);
140for w = 1:R-1
141 prods(w) = prod(ones(1,R-(w+1)+1) + N(w+1:R));
142end
143firstnonempty = R;
144while N(firstnonempty) == 0
145 firstnonempty = firstnonempty - 1;
146end
147totpop = prod(N+1);
148ctr = totpop;
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.
159P = M*G;
160pidx = zeros(M,G);
161for h = 1:M
162 for g = 1:G
163 pidx(h,g) = (h-1)*G + g;
164 end
165end
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);
170currentpop = 2;
171n = zeros(1,R);
172n(firstnonempty) = 1;
173
174Cs = zeros(M,1); dCs = zeros(M,P); d2Cs = zeros(M,P);
175
176while ctr
177 % the group accumulators describe one population only
178 Qg(:) = 0; D1Qg(:) = 0; D2Qg(:) = 0;
179 s = 1;
180 while s <= R
181 pos = 0;
182 if n(s) > 0
183 n(s) = n(s) - 1;
184 pos = n(R);
185 w = 1;
186 while w <= R-1
187 pos = pos + n(w)*prods(w);
188 w = w + 1;
189 end
190 n(s) = n(s) + 1;
191 end
192 row = 1 + pos;
193
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 )
200 gs = groups(s);
201 CNtot = 0;
202 dCNtot = zeros(1,P);
203 d2CNtot = zeros(1,P);
204 for i = 1:M
205 A = mi(i) + Qtot(row,i);
206 Cs(i) = L(i,s) * A;
207 C(i,s) = Cs(i);
208 CNtot = CNtot + Cs(i);
209 for p = 1:P
210 dA = D1(row,i,p);
211 d2A = D2(row,i,p);
212 if p == pidx(i,gs)
213 dCs(i,p) = L(i,s) * (A + dA);
214 d2Cs(i,p) = L(i,s) * (2*dA + d2A);
215 else
216 dCs(i,p) = L(i,s) * dA;
217 d2Cs(i,p) = L(i,s) * d2A;
218 end
219 dCNtot(p) = dCNtot(p) + dCs(i,p);
220 d2CNtot(p) = d2CNtot(p) + d2Cs(i,p);
221 end
222 end
223
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
228 den = Z(s) + CNtot;
229 X(s) = n(s) / den;
230 dX = zeros(1,P); d2X = zeros(1,P);
231 for p = 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;
234 end
235
236 % ---- queue lengths ---------------------------------------------
237 % Q = X*C, dQ = dX*C + X*dC, d2Q = d2X*C + 2*dX*dC + X*d2C
238 for i = 1:M
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);
242 for p = 1:P
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;
249 end
250 end
251 s = s + 1;
252 end
253
254 % ---- odometer advance ---------------------------------------------
255 s = R;
256 while (s>0 && n(s)==N(s)) || s>firstnonempty
257 s = s - 1;
258 end
259 if s == 0
260 break;
261 end
262 n(s) = n(s) + 1;
263 s = s + 1;
264 while s <= R
265 n(s) = 0;
266 s = s + 1;
267 end
268 ctr = ctr - 1;
269 currentpop = currentpop + 1;
270end
271
272% utilization
273for i = 1:M
274 for r = 1:R
275 U(i,r) = X(r) * L(i,r);
276 end
277end
278
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.
281for i = 1:M
282 for g = 1:G
283 m(i,g) = Qg(i,g);
284 for j = 1:M
285 for g2 = 1:G
286 dm(i,g,j,g2) = D1Qg(i,g,pidx(j,g2));
287 end
288 end
289 d2m(i,g) = D2Qg(i,g,pidx(i,g));
290 end
291end
292
293mom = pack(X,Q,U,C,m,dm,d2m,G);
294end
295
296% =========================================================================
297function mom = pack(X,Q,U,C,m,dm,d2m,G)
298M = size(Q,1);
299mom.X = X; mom.Q = Q; mom.U = U; mom.R = C;
300mom.m = m; mom.d2m = d2m;
301
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)
308 mom.CovAsym = 0;
309end
310flat = (flat + flat.') / 2;
311if G == 1
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);
316else
317 mom.Cov = reshape(flat, M, G, M, G);
318 mom.dm = dm;
319end
320
321Var = zeros(M,G); M2 = zeros(M,G); M3 = zeros(M,G); Skew = zeros(M,G);
322for i = 1:M
323 for g = 1:G
324 d1 = dm(i,g,i,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;
330 if Var(i,g) > 0
331 Skew(i,g) = mu3 / Var(i,g)^1.5;
332 else
333 Skew(i,g) = NaN;
334 end
335 end
336end
337mom.Var = Var; mom.M2 = M2; mom.M3 = M3; mom.Skew = Skew;
338end
Definition Station.m:287
Definition Station.m:245