1function [X, Qoi, Qli, Qdelay, Soi] = pfqn_mvaoi(Z, N, mu, Dli,
visits, options)
2% [X, QOI, QLI, QDELAY, SOI] = PFQN_MVAOI(Z, N, MU, DLI, OPTIONS)
4% Mean-value analysis of a closed product-
form queueing network composed of an
5% aggregated infinite-server (delay) node, any number of load-independent (LI)
6% single-server product-
form queues, and any number of order-independent (OI) /
7% pass-and-swap stations with empty swap graph. This
is the mean-value
8% counterpart of PFQN_NCOI and the marginal-distribution
form PFQN_MVAOI_MARG:
9% it returns the same exact per-
class throughput and queue-lengths but WITHOUT
10% computing any normalizing constant or joint marginal,
using only mean
11% quantities (throughputs, demands, queue-lengths) evaluated on shifted models.
12% It
is the composition-dependent generalization of the Conditional MVA (CMVA)
13% of Casale,
"A Note on Stable Flow-Equivalent Aggregation in Closed Networks"
14% (QUESTA 2009), whose
"third form" (rate depending on the full per-
class
15% occupancy vector)
is realized here, extended to MULTIPLE OI stations by
16% carrying one rate-shift vector s_i per OI station i.
18% Throughout, r and s index job classes; i indexes OI stations; j indexes LI
19% queues. For a single OI station and no LI queue the analysis recurs on the
20% shift vector s_i (the OI occupancy already committed at the bottom of station
21% i), Nn = N - s_i the jobs still to distribute:
22% Q^{(S)}(Nn) = sum_r U_r^{(S)}(Nn) ( e_r + Q^{(S+e_r@i)}(Nn - e_r) ),
23% U_r^{(S)}(Nn) = D_r^{(S)}(Nn) X_r^{(S)}(Nn) (bottom-job utilization),
24% with the
class-r OI demand and throughput satisfying
25% D_r^{(S)}(Nn) = (1/mu_i(s_i+e_r)) rho_{i,r}^{(S)}(Nn-e_r), Nn_r = 1,
26% D_r^{(S)}(Nn) = [X_r^{(S)}(Nn-e_r)/X_r^{(S+e_r@i)}(Nn-e_r)] D_r^{(S)}(Nn-e_r), Nn_r >= 2,
27% rho_{i,r}^{(S)}(M) = rho_{i,r}^{(S)}(M-e_s) X_s^{(S)}(M)/X_s^{(S+e_r@i)}(M), rho(0)=1, s ~= r,
28% and X_r^{(S)}(Nn) closed by population conservation. With K OI stations the
29% shift becomes a K x R matrix S (row i = s_i); each OI station keeps its own
30% D^i, rho^i and Q^i recursions driven by the common throughput X^{(S)}(Nn), and
31% the conservation identity aggregates every station
's contribution:
32% Nn_r = X_r Z_r + sum_j Q^{(j)}_r + sum_i Q^{(i)}_r,
33% where the LI queue Q^{(j)}_r = X_r D_{j,r} (1 + sum_s Q^{(j)}_s(Nn - e_r)) is
34% the standard arrival-theorem term. States (S, Nn) are processed by increasing
35% sum(Nn) so every reference lands at a strictly smaller free population.
38% Z - (1 x R) think-time demand vector of the aggregated delay node.
39% N - (1 x R) closed population vector, finite.
40% mu - cell array {mu_1,...,mu_K} of function handles; mu_i(n) returns the OI
41% total service rate of station i for per-class occupancy n (1 x R). A
42% bare function handle is accepted as the single-station shorthand.
43% Dli - (J x R) per-class demand matrix of the LI single-server queues
44% (D_{j,r} = V_{j,r}/rate_{j,r}); empty or omitted when J = 0.
45% options - solver options (optional, currently unused).
48% X - (1 x R) per-class throughput X_r = G(N-e_r)/G(N).
49% Qoi - (K x R) per-class mean queue-length at each OI station (row i).
50% Qli - (J x R) per-class mean queue-length at each LI queue (row j).
51% Qdelay - (1 x R) per-class mean queue-length at the delay node (X.*Z).
52% Soi - (K x R) per-class mean number of IN-SERVICE jobs at each OI station,
53% i.e. E[sir_r] with sir_r the count of class-r jobs receiving a
54% strictly positive rank rate (see PFQN_OI_INSVC); the utilization of
55% OI station i is Soi(i,r)/c_i. Unlike X/Qoi/Qli, which are pure
56% mean-value quantities, Soi is a distributional statistic and is
57% therefore obtained from the OI count marginal
58% pM_i(n|k) = (1/mu_i(n)) sum_r X_r(k) pM_i(n-e_r|k-e_r),
59% pM_i(0|k) = 1 - sum_{n ~= 0} pM_i(n|k),
60% which is assembled here from the zero-shift throughputs X^{(0)}(k)
61% already cached by the mean-value recursion above (no normalizing
62% constant is formed). It is only computed when requested.
64% Copyright (c) 2012-2026, Imperial College London
71 options = struct(); %#ok<NASGU>
73if isa(mu, 'function_handle
')
76if ~iscell(mu) || isempty(mu)
77 line_error(mfilename, 'mu must be a (nonempty) cell of OI rate handles.
');
80% Per-OI-station class visit ratios v_{i,r}. They enter the class-r demand base
81% case theta_{i,r}(N_r=1)=v_{i,r}/mu_i(...) (eq mvaoi-D); the N_r>=2 ratio case
82% cancels visits. Default unit visits. ms-promoted OI stations pass ones here,
83% their visits already folded into the rate handle by the analyzer.
84if nargin < 5 || isempty(visits)
85 visits = repmat({ones(1, numel(N))}, 1, K);
88 if ~isa(mu{i}, 'function_handle
')
89 line_error(mfilename, 'each mu{i} must be a function handle mu_i(n).
');
97 line_error(mfilename, 'pfqn_mvaoi requires finite (closed) populations.
');
105% Caches keyed by the string of [S(:).' , Nn]. Reachable states only.
106Xc = containers.Map(
'KeyType',
'char',
'ValueType',
'any'); % X^{(S)}(Nn) 1xR
107Qlc = containers.Map(
'KeyType',
'char',
'ValueType',
'any'); % Qli^{(S)}(Nn) JxR
108Dc = cell(1, K); % Dc{i}: D_i^{(S)}(Nn) 1xR
109Qc = cell(1, K); % Qc{i}: Q_i^{(S)}(Nn) 1xR
110Rc = cell(1, K); % Rc{i}: rho_i^{(S)}(M) 1xR (NaN until computed)
112 Dc{i} = containers.Map(
'KeyType',
'char',
'ValueType',
'any');
113 Qc{i} = containers.Map(
'KeyType',
'char',
'ValueType',
'any');
114 Rc{i} = containers.Map(
'KeyType',
'char',
'ValueType',
'any');
118k0 = statekey(zeroS, zeros(1,R));
120Qlc(k0) = zeros(J, R);
122 Dc{i}(k0) = zeros(1, R);
123 Qc{i}(k0) = zeros(1, R);
126% see _kb/03-api-layer.md (pfqn/ family: scaling, log-domain switches, dispatch gates)
128[si, nmat, SS, DD] = sprod(K+2, N);
130 states{end+1} = nmat(1:K+1, :); %#ok<AGROW>
131 [si, nmat] = sprod(si, SS, DD);
133sums = cellfun(@(nm) sum(nm(K+1, :)), states);
134[~, ord] = sort(sums);
137for p = 1:numel(states)
141 key = statekey(S, Nn);
143 % No free jobs: throughput and all queue-ahead terms vanish. This must
144 % be stored
for EVERY shift S (not only S = 0) because Qsub references
145 % (S + e_s@i, Nn - e_s) which reaches such states when Nn = e_s.
146 Xc(key) = zeros(1, R);
147 Qlc(key) = zeros(J, R);
149 Dc{i}(key) = zeros(1, R);
150 Qc{i}(key) = zeros(1, R);
155 % Per-OI-station
class demands D_i and queue-ahead vectors Qsub_i.
156 Dt = zeros(K, R); % Dt(i,r)
157 Qsub = cell(1, K); % Qsub{i}(s,r) = Q_i^{(S+e_s@i)}(Nn - e_s), comp r
159 Qsub{i} = zeros(R, R);
165 mur = mu{i}(shiftrow(S, i, I(r,:)));
167 Dt(i,r) = (
visits{i}(r)/mur) * rho_fn(i, r, S, oner(Nn,r), mu, Xc, Rc, I, R);
170 Sp = S; Sp(i,:) = Sp(i,:) + I(r,:);
171 xs = Xc(statekey(S, oner(Nn,r)));
172 xs2 = Xc(statekey(Sp, oner(Nn,r)));
174 Dprev = Dc{i}(statekey(S, oner(Nn,r)));
175 Dt(i,r) = (xs(r)/xs2(r)) * Dprev(r);
181 Ss = S; Ss(i,:) = Ss(i,:) + I(s,:);
182 Qsub{i}(s,:) = Qc{i}(statekey(Ss, oner(Nn,s)));
187 % LI-queue arrival-theorem coefficients beta_j,r and aggregate per
class.
188 betaLI = zeros(J, R); % beta_j,r = D_j,r (1 + sum_s Qli_j,s(Nn - e_r))
193 Qsub_li = Qlc(statekey(S, oner(Nn,r))); % J x R
195 betaLI(j,r) = Dli(j,r) * (1 + sum(Qsub_li(j,:)));
199 % Population conservation A X = Nn over classes with Nn_r > 0.
208 val = Z(r) + sum(betaLI(:,r));
210 val = val + Dt(i,r) * (1 + Qsub{i}(r,r));
216 val = val + Dt(i,s) * Qsub{i}(s,r);
222 % see _kb/03-api-layer.md (pfqn/ family: scaling, log-domain switches, dispatch gates)
230 Xner = Xc(statekey(S, oner(Nn,r))); % X(S, Nn-e_r)
231 Xnes = Xc(statekey(S, oner(Nn,s))); % X(S, Nn-e_s)
233 denom = denom + A(a,b) * (Xner(s) / Xnes(r));
238 Xk(r) = Nn(r) / denom;
242 % Assemble OI-station queues Q_i and LI-queue queues Qli.
248 Qk_li(:,r) = Xk(r) * betaLI(:,r);
251 U = Dt(i,:) .* Xk; % 1 x R bottom-job utilization
254 Qi(r) = U(r) + U * Qsub{i}(:,r);
257 Dc{i}(key) = Dt(i,:);
263keyN = statekey(zeroS, N);
267 Qoi(i,:) = Qc{i}(keyN);
273 Soi = oi_insvc_means(Z, N, mu, Xc, zeroS, K, R);
277% =========================================================================
278function Soi = oi_insvc_means(Z, N, mu, Xc, zeroS, K, R)
279% Mean number of in-service jobs per
class at each OI station, E[sir_r], from
280% the OI count marginal pM_i(n|k) built on the cached zero-shift throughputs
281% X^{(0)}(k). Exact because in product
form
282% pM_i(n|k) = Phi_i(n) G_{-i}(k-n)/G(k) and X_r(k) = G(k-e_r)/G(k), so the
283% recursion below reproduces the balanced-fairness identity
for Phi_i.
287 stride(d) = stride(d-1) * shp(d-1);
290subs = zeros(total, R);
294 subs(i, d) = mod(li, shp(d));
295 li = floor(li / shp(d));
298[~, ord] = sort(sum(subs, 2)); % process populations by increasing size
300% Cache X^{(0)}(k) over the lattice.
303 Xk(i,:) = Xc(statekey(zeroS, subs(i,:)));
308 gm = pfqn_oi_insvc(mu{m}, N); % E[sir_r | n] over the lattice
309 muv = zeros(total, 1);
311 if sum(subs(i,:)) > 0
312 muv(i) = mu{m}(subs(i,:));
315 % pMv(a,b) = pM_m(n_a | k_b), filled
for n_a <= k_b by increasing sum(k).
316 pMv = zeros(total, total);
317 pMv(1,1) = 1; % pM(0|0) = 1
328 if sum(n) == 0 || any(n > k)
337 acc = acc + Xk(b, r) * pMv(a - stride(r), b - stride(r));
340 pMv(a, b) = acc / muv(a);
341 acc0 = acc0 + pMv(a, b);
343 pMv(1, b) = 1 - acc0; % empty-state probability by complement
345 idxN = 1 + sum(N .* stride);
347 Soi(m, r) = pMv(:, idxN)
' * gm(:, r);
352% =========================================================================
354% =========================================================================
356function rv = rho_fn(i, r, S, M, mu, Xc, Rc, I, R)
357% rho_{i,r}^{(S)}(M): recursion on M (class r held fixed, M_r stays 0). NOTE all
358% local names are r-prefixed to avoid corrupting the recursion via shared
359% storage in nested calls.
360rkey = statekey(S, M);
378 if rss ~= r && M(rss) > 0
383rxu = Xc(statekey(S, M));
384rSp = S; rSp(i,:) = rSp(i,:) + I(r,:);
385rxu2 = Xc(statekey(rSp, M));
388 rratio = rxu(rs) / rxu2(rs);
390rv = rho_fn(i, r, S, oner(M, rs), mu, Xc, Rc, I, R) * rratio;
395function row = shiftrow(S, i, e)
396% Return the per-class occupancy row s_i + e for OI station i.
400function v = oner(v, r)
401% Decrement entry r of v by one.
405function key = statekey(S, Nn)
406% String key for the lattice state (S, Nn); S is K x R, Nn is 1 x R.
407key = sprintf('%d_
', [reshape(S.', 1, []), Nn]);