LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
pfqn_marie.m
1%{
2%{
3 % @file pfqn_marie.m
4 % @brief Marie's iterative aggregation for closed networks with FCFS Coxian
5 % (non-exponential) service. Single-class exact-reducing; multiclass via
6 % QD-AMVA with class-dependent (cd) scaling.
7%}
8%}
9
10function [X,Q,U,C,it,mu] = pfqn_marie(L,N,Z,scv,varargin)
11%{
12%{
13 % @brief Marie's method (Marie 1979/1980): approximate mean performance of a
14 % closed queueing network with FCFS general (Coxian) service, via
15 % iterative aggregation-decomposition. Each station is analyzed in
16 % isolation as a lambda(n)/Cox/1 queue; the resulting conditional
17 % throughputs mu_i(n) drive a load-dependent aggregate solve, iterated
18 % to a fixed point. Single class (R=1): the aggregate is the exact LD
19 % product-form solve pfqn_mvald, and the method reduces to exact product
20 % form for exponential service. Multiple classes (R>1): the aggregate is
21 % QD-AMVA with class-dependent (cd) scaling beta_{i,r}(nvec) supplied by
22 % a multiclass Cox/1 isolation sub-model; exact product-form service
23 % (scv==1 with class-independent means) is dispatched to exact MVA,
24 % otherwise the result is a decomposition approximation.
25 % @fn pfqn_marie(L, N, Z, scv, tol, maxiter, nservers)
26 % @param L Service demand matrix (M x R).
27 % @param N Population vector (1 x R).
28 % @param Z Think time vector (1 x R; total delay demand per class).
29 % @param scv Per-station per-class squared coefficient of variation (M x R).
30 % scv==1 exponential; scv<0.5 Erlang; scv>0.5 two-phase Coxian.
31 % @param tol Convergence tolerance (default 1e-8).
32 % @param maxiter Maximum iterations (default 1000).
33 % @param nservers Per-station server count (M x 1, default all 1); single-class
34 % only (multiserver multiclass isolation is not yet supported).
35 % @return X Throughput: single class M x 1 (per station); multiclass 1 x R
36 % (per-class chain throughput, visits folded into L).
37 % @return Q Mean queue length (M x 1 single class, M x R multiclass).
38 % @return U Utilization (same shape as Q).
39 % @return C Residence time (same shape as Q).
40 % @return it Iterations performed.
41 % @return mu Converged LD data (M x N single class; cell of cd-scalings R>1).
42%}
43%}
44
45R = size(L,2);
46if R > 1
47 [X,Q,U,C,it,mu] = marie_multi(L,N,Z,scv,varargin{:});
48 return
49end
50
51L = L(:);
52M = numel(L);
53if nargin < 3 || isempty(Z)
54 Z = 0;
55end
56Z = sum(Z(:));
57if nargin < 4 || isempty(scv)
58 scv = ones(M,1);
59end
60scv = scv(:);
61
62tol = 1e-8;
63maxiter = 1000;
64nservers = ones(M,1);
65if numel(varargin) >= 1 && ~isempty(varargin{1}), tol = varargin{1}; end
66if numel(varargin) >= 2 && ~isempty(varargin{2}), maxiter = varargin{2}; end
67if numel(varargin) >= 3 && ~isempty(varargin{3}), nservers = varargin{3}(:); end
68if isscalar(nservers), nservers = nservers*ones(M,1); end
69
70% Per-station Coxian phase representation of the service (mean = L(i), scv(i)).
71phRate = cell(M,1);
72phCompl = cell(M,1);
73for i = 1:M
74 [~, mu_i, phi_i] = Coxian.fitMeanAndSCV(L(i), scv(i));
75 phRate{i} = mu_i(:);
76 phCompl{i} = phi_i(:);
77end
78
79% Initial LD rate multipliers (relative to base rate 1/L(i)): exponential
80% single-server guess mu=1, multiserver mu=min(n,m). pfqn_mvald interprets mu
81% as a multiplier, so the absolute service rate at n jobs is mu(i,n)/L(i).
82mu = zeros(M,N);
83for i = 1:M
84 mu(i,:) = min(1:N, nservers(i));
85end
86
87X = zeros(M,1); Q = zeros(M,1); U = zeros(M,1); C = zeros(M,1);
88it = 0;
89while it < maxiter
90 it = it + 1;
91 [XN,QN,UN,CN,~,~,piglob] = pfqn_mvald(L,N,Z,mu);
92 % Marginal queue-length distribution at full population, per station.
93 Pg = piglob(:,:,end); % M x (sum(N)+1), Pg(i,k) = P(n_i = k-1)
94 mu_new = mu;
95 for i = 1:M
96 Pi = Pg(i,1:N+1);
97 % see _kb/03-api-layer.md (pfqn/ family: scaling, log-domain switches, dispatch gates)
98 lam = zeros(1,N); % lam(n+1) holds lambda_i(n)
99 for n = 0:N-1
100 pn = Pi(n+1);
101 if pn > 0
102 lam(n+1) = (mu(i,n+1)/L(i)) * Pi(n+2) / pn;
103 else
104 lam(n+1) = 0;
105 end
106 end
107 % Isolation returns absolute conditional throughput; convert to the
108 % multiplier pfqn_mvald expects (multiplier = abs_rate * L(i)).
109 muabs = isol_condtput(lam, phRate{i}, phCompl{i}, N, nservers(i));
110 mu_new(i,:) = muabs * L(i);
111 end
112 delta = max(max(abs(mu_new - mu)));
113 mu = mu_new;
114 X = XN(:); Q = QN(:); U = UN(:); C = CN(:);
115 if delta < tol
116 break
117 end
118end
119end
120
121function muvec = isol_condtput(lam, phRate, phCompl, N, m)
122% Stationary analysis of a lambda(n)/Cox/1(-m) queue in isolation, returning
123% the conditional throughput mu(n) = departure rate given n present, n=1..N.
124% lam(n+1) = arrival rate when n customers present (n=0..N-1); the customer in
125% service advances through Coxian phases (rate phRate(k); completes w.p.
126% phCompl(k), else advances to phase k+1). With m servers, the phase-completion
127% rate at population n is scaled by min(n,m).
128P = numel(phRate);
129% State layout: 1 = empty; for n=1..N, k=1..P -> index 1 + (n-1)*P + k.
130S = 1 + N*P;
131idx = @(n,k) 1 + (n-1)*P + k;
132Gq = zeros(S,S);
133
134% From empty: arrival starts a customer in phase 1.
135Gq(1, idx(1,1)) = Gq(1, idx(1,1)) + lam(1);
136
137for n = 1:N
138 sc = min(n,m); % multiserver rate scaling
139 for k = 1:P
140 r = idx(n,k);
141 % Arrival (queueing; in-service phase preserved).
142 if n < N
143 Gq(r, idx(n+1,k)) = Gq(r, idx(n+1,k)) + lam(n+1);
144 end
145 compl = phRate(k) * phCompl(k) * sc; % completion (departure)
146 adv = phRate(k) * (1-phCompl(k)) * sc; % advance to next phase
147 if adv > 0 && k < P
148 Gq(r, idx(n,k+1)) = Gq(r, idx(n,k+1)) + adv;
149 end
150 if compl > 0
151 if n > 1
152 Gq(r, idx(n-1,1)) = Gq(r, idx(n-1,1)) + compl;
153 else
154 Gq(r, 1) = Gq(r, 1) + compl;
155 end
156 end
157 end
158end
159Gq = Gq - diag(sum(Gq,2));
160
161% Stationary distribution: solve p*Gq = 0, sum(p) = 1.
162A = [Gq'; ones(1,S)];
163b = [zeros(S,1); 1];
164p = (A \ b)';
165
166muvec = zeros(1,N);
167for n = 1:N
168 Pn = 0; dep = 0;
169 for k = 1:P
170 pk = p(idx(n,k));
171 Pn = Pn + pk;
172 dep = dep + pk * phRate(k) * phCompl(k) * min(n,m);
173 end
174 if Pn > 0
175 muvec(n) = dep / Pn;
176 else
177 muvec(n) = min(n,m) / sum(1./phRate); % fallback: exponential-equiv rate
178 end
179end
180end
181
182% ========================= multiclass (R>1) path =========================
183function [X,Q,U,C,it,mu] = marie_multi(L,N,Z,scv,varargin)
184% Marie's method for multiclass FCFS Coxian closed networks. The aggregate is
185% QD-AMVA with class-dependent scaling beta_{i,r}(nvec) = (Coxian isolation
186% conditional throughput)/(exponential isolation conditional throughput), so
187% beta==1 recovers standard FCFS AMVA and the cd-scaling carries only the
188% non-exponential correction. beta is supplied by a multiclass Cox/1 isolation
189% sub-model fed the aggregate per-class throughput (Baynat-Dallery isolation),
190% iterated to a fixed point on X.
191[M,R] = size(L);
192N = N(:)';
193if nargin < 3 || isempty(Z), Z = zeros(1,R); end
194Z = Z(:)';
195if nargin < 4 || isempty(scv), scv = ones(M,R); end
196
197tol = 1e-8; maxiter = 1000;
198if numel(varargin) >= 1 && ~isempty(varargin{1}), tol = varargin{1}; end
199if numel(varargin) >= 2 && ~isempty(varargin{2}), maxiter = varargin{2}; end
200
201% Exact product-form dispatch: exponential service that is also class-
202% independent at every station is genuine BCMP FCFS -> exact MVA.
203isPF = all(scv(:) == 1);
204if isPF
205 for i = 1:M
206 if max(L(i,:)) - min(L(i,:)) > 1e-12
207 isPF = false; break
208 end
209 end
210end
211if isPF
212 [XN,QN,UN,CN] = pfqn_mva(L,N,Z);
213 X = XN(:)'; Q = QN; U = UN; C = CN; it = 0; mu = {}; return
214end
215
216% Per-station per-class Coxian phase representation, plus an exponential
217% reference (same means) used to normalize the cd-scaling.
218phR = cell(M,R); phP = cell(M,R);
219eR = cell(M,R); eP = cell(M,R);
220for i = 1:M
221 for r = 1:R
222 [~, mir, pir] = Coxian.fitMeanAndSCV(L(i,r), scv(i,r));
223 phR{i,r} = mir(:); phP{i,r} = pir(:);
224 eR{i,r} = 1/L(i,r); eP{i,r} = 1; % exponential reference
225 end
226end
227
228cds = cell(M,1);
229for i = 1:M, cds{i} = @(nv) ones(1,R); end % beta = 1 initially
230
231Xprev = inf(1,R); it = 0;
232X = zeros(1,R); Q = zeros(M,R); U = zeros(M,R); C = zeros(M,R);
233while it < maxiter
234 it = it + 1;
235 [X,Q,U,C] = amva_qd(L,N,Z,cds);
236 for i = 1:M
237 muCox = isol_mc(X, phR(i,:), phP(i,:), N);
238 muExp = isol_mc(X, eR(i,:), eP(i,:), N);
239 cds{i} = make_cdscale(muCox, muExp, N);
240 end
241 if max(abs(X - Xprev)) < tol, break, end
242 Xprev = X;
243end
244mu = cds;
245end
246
247function [X,Q,U,C] = amva_qd(L,N,Z,cds)
248% Multiclass Schweitzer AMVA with class-dependent service-rate scaling. The
249% effective class-r demand at station i is L(i,r)/beta_{i,r}(nvec_arrival),
250% beta supplied by cds{i} evaluated at the arrival-instant per-class population.
251[M,R] = size(L);
252Q = repmat(N,M,1) / max(M,1);
253W = zeros(M,R); X = zeros(1,R); U = zeros(M,R);
254Qprev = Q + 1; tol = 1e-9; it = 0;
255while max(abs(Q(:)-Qprev(:))) > tol && it < 5000
256 it = it + 1; Qprev = Q;
257 for r = 1:R
258 for i = 1:M
259 nv = Q(i,:);
260 if N(r) > 0
261 nv(r) = Q(i,r) * (N(r)-1) / N(r); % arrival instant, tagged class
262 end
263 be = cds{i}(nv);
264 % Work-based multiclass FCFS AMVA residence: the tagged class-r job's
265 % own effective service plus the effective work of the jobs found
266 % ahead (per-class, so unequal means are handled), with the cd
267 % scaling beta_{i,s} applied to each class's effective demand.
268 Leff = L(i,:) ./ be;
269 W(i,r) = Leff(r) + sum(Leff .* nv);
270 end
271 denom = Z(r) + sum(W(:,r));
272 if denom > 0, X(r) = N(r) / denom; else X(r) = 0; end
273 for i = 1:M
274 Q(i,r) = X(r) * W(i,r);
275 end
276 end
277end
278C = W;
279for r = 1:R
280 for i = 1:M
281 U(i,r) = X(r) * L(i,r); % busy fraction (true mean service)
282 end
283end
284end
285
286function mumat = isol_mc(lam, phRrow, phProw, Nvec)
287% Stationary analysis of a multiclass lambda_r/Cox/1 FCFS queue in isolation
288% over the joint per-class population box [0..Nvec], with the head-of-line job
289% tracked as (class, phase) and, on a departure, the next head class drawn in
290% random order (prob n_c/sum(n)). Returns mumat{r}, an ND array over the box
291% giving the conditional class-r throughput mu_r(nvec) = (class-r departure
292% rate in states with population nvec)/P(nvec).
293R = numel(lam);
294Pc = zeros(1,R);
295for r = 1:R, Pc(r) = numel(phRrow{r}); end
296boxsz = Nvec + 1;
297npops = prod(boxsz);
298
299% Enumerate states: id map keyed by (popLinear, head, phase).
300% state 1 reserved for the empty station.
301key2id = containers.Map('KeyType','char','ValueType','double');
302ids = {}; % ids{s} = [popLinear, head, phase]
303key2id('E') = 1; ids{1} = [1, 0, 0];
304nid = 1;
305popsubs = cell(1,R);
306for p = 1:npops
307 [popsubs{:}] = ind2sub(boxsz, p);
308 nvec = cell2mat(popsubs) - 1; % actual populations
309 if sum(nvec) == 0, continue, end
310 for c = 1:R
311 if nvec(c) > 0
312 for k = 1:Pc(c)
313 nid = nid + 1;
314 key2id(sprintf('%d_%d_%d', p, c, k)) = nid;
315 ids{nid} = [p, c, k];
316 end
317 end
318 end
319end
320S = nid;
321
322 function id = getid(p, c, k)
323 if c == 0
324 id = 1;
325 else
326 id = key2id(sprintf('%d_%d_%d', p, c, k));
327 end
328 end
329 function p = poplin(nvec)
330 sub = num2cell(nvec + 1);
331 p = sub2ind(boxsz, sub{:});
332 end
333
334I = zeros(0,1); J = zeros(0,1); V = zeros(0,1);
335 function addrate(a, b, rate)
336 I(end+1,1) = a; J(end+1,1) = b; V(end+1,1) = rate; %#ok<AGROW>
337 end
338
339for s = 1:S
340 info = ids{s};
341 p = info(1); c = info(2); k = info(3);
342 if c == 0
343 nvec = zeros(1,R);
344 else
345 [popsubs{:}] = ind2sub(boxsz, p);
346 nvec = cell2mat(popsubs) - 1;
347 end
348 % arrivals
349 for r = 1:R
350 if nvec(r) < Nvec(r) && lam(r) > 0
351 nnew = nvec; nnew(r) = nnew(r) + 1;
352 if c == 0
353 addrate(s, getid(poplin(nnew), r, 1), lam(r)); % start service
354 else
355 addrate(s, getid(poplin(nnew), c, k), lam(r)); % queue behind head
356 end
357 end
358 end
359 if c == 0, continue, end
360 rate = phRrow{c}(k);
361 compl = rate * phProw{c}(k);
362 adv = rate * (1 - phProw{c}(k));
363 if adv > 0 && k < Pc(c)
364 addrate(s, getid(p, c, k+1), adv);
365 end
366 if compl > 0
367 nnew = nvec; nnew(c) = nnew(c) - 1;
368 if sum(nnew) == 0
369 addrate(s, 1, compl);
370 else
371 tot = sum(nnew);
372 for cp = 1:R
373 if nnew(cp) > 0
374 addrate(s, getid(poplin(nnew), cp, 1), compl * nnew(cp)/tot);
375 end
376 end
377 end
378 end
379end
380
381Gq = sparse(I, J, V, S, S);
382Gq = Gq - spdiags(sum(Gq,2), 0, S, S);
383
384% Stationary distribution.
385A = [Gq'; ones(1,S)];
386b = [zeros(S,1); 1];
387pvec = A \ b;
388
389% Conditional class-r throughput on the population lattice.
390mumat = cell(1,R);
391for r = 1:R, mumat{r} = zeros(boxsz); end
392Ppop = zeros(boxsz);
393dep = cell(1,R);
394for r = 1:R, dep{r} = zeros(boxsz); end
395for s = 1:S
396 info = ids{s};
397 p = info(1); c = info(2); k = info(3);
398 if c == 0, continue, end
399 Ppop(p) = Ppop(p) + pvec(s);
400 dep{c}(p) = dep{c}(p) + pvec(s) * phRrow{c}(k) * phProw{c}(k);
401end
402for r = 1:R
403 idxpos = Ppop > 0;
404 mumat{r}(idxpos) = dep{r}(idxpos) ./ Ppop(idxpos);
405end
406end
407
408function f = make_cdscale(muCox, muExp, Nvec)
409% Class-dependent scaling beta_{i,r}(nv) = muCox_r(nv)/muExp_r(nv), so beta==1
410% for exponential service. Multilinear interpolation over the population
411% lattice; guarded and clamped.
412f = @(nv) cdscale_eval(nv, muCox, muExp, Nvec);
413end
414
415function be = cdscale_eval(nv, muCox, muExp, Nvec)
416R = numel(Nvec);
417be = ones(1,R);
418for r = 1:R
419 num = ndlininterp(muCox{r}, nv, Nvec);
420 den = ndlininterp(muExp{r}, nv, Nvec);
421 if den > 0 && num > 0 && isfinite(num) && isfinite(den)
422 be(r) = num / den;
423 else
424 be(r) = 1;
425 end
426end
427be = min(max(be, 1e-3), 1e3);
428end
429
430function v = ndlininterp(A, x, Nvec)
431% Multilinear interpolation of ND array A (size Nvec+1) at real point x,
432% clamped to the box [0, Nvec].
433R = numel(Nvec);
434sz = Nvec + 1;
435x = min(max(x(:)', 0), Nvec);
436lo = floor(x);
437hi = min(lo + 1, Nvec);
438fr = x - lo;
439v = 0;
440for mask = 0:(2^R - 1)
441 w = 1; sub = zeros(1,R);
442 for d = 1:R
443 if bitget(mask, d)
444 sub(d) = hi(d); w = w * fr(d);
445 else
446 sub(d) = lo(d); w = w * (1 - fr(d));
447 end
448 end
449 if w == 0, continue, end
450 subc = num2cell(sub + 1);
451 v = v + w * A(sub2ind(sz, subc{:}));
452end
453end