1function [XN, QN] = pfqn_mvaoi_marg(D, N, isDelay, mu)
2% [XN, QN] = PFQN_MVAOI_MARG(D, N, ISDELAY, MU)
4% Exact marginal load-dependent MVA
for a closed product-form network of
5% infinite-server (delay) and load-independent (single-server, product-form)
6% stations plus ANY number of order-independent (OI) stations. An OI station
is
7% a
class-dependent load-dependent server whose total service rate mu_i(n)
is a
8% permutation-invariant function of
the per-
class count vector n. The network
is
9% product-form and
is solved exactly by
the load-dependent MVA recursion that
10% carries,
for EACH OI station i, its joint
count-vector marginal distribution
13% pM_i(n | k) = (1/mu_i(n)) * sum_r X_r(k) * pM_i(n - e_r | k - e_r), n ~= 0
14% pM_i(0 | k) = 1 - sum_{n ~= 0} pM_i(n | k)
16% All OI-station marginals share
the common per-class throughput X_r(k);
the
17% recursion
is exact per station because in product form
18% pM_i(n|k) = Phi_i(n) G_{-i}(k-n)/G(k) with X_r(k) = G(k-e_r)/G(k) and
the
19% balanced-fairness identity Phi_i(n) = (1/mu_i(n)) sum_r Phi_i(n-e_r).
21% Because
the OI rate
is class-dependent,
the mean-value response-time formula
is
22% not exact; instead
the per-
class throughput X_r(k)
is closed at each population
23% level by population conservation
25% X_r(k) * A_r(k) + sum_i QM_ir(k; X) = k_r, A_r(k) = sum_{i not OI} R_ir(k)
27% where QM_ir(k) = sum_n n_r pM_i(n | k)
is read off each OI station
's exact
28% marginal. This yields Q, X matching the exact CTMC / normalizing-constant (NC)
29% results for any number of OI stations. This is the marginal-distribution
30% counterpart of PFQN_MVAOI (the mean-value CMVA form).
33% D - (M x R) per-class service demand V_ir/rate_ir at every station.
34% Rows of OI stations are ignored (rate comes from MU).
35% N - (1 x R) closed population vector, finite.
36% isDelay - (1 x M) logical, true for infinite-server (delay) stations.
37% mu - (1 x M) cell; mu{i} is a function handle mu_i(n) returning the OI
38% total service rate for the per-class occupancy vector n (1 x R) at
39% OI station i, and [] for non-OI stations.
42% XN - (1 x R) per-class throughput X_r = G(N-e_r)/G(N).
43% QN - (M x R) per-class mean queue-length at every station.
46% Reiser, Lavenberg (1980). Mean-Value Analysis of Closed Multichain Queuing
47% Networks. JACM 27(2). Load-dependent extension: Bruell, Balbo, Afshari
48% (1984). OI stations: Casale, Comte, Dorsman (2026).
50% Copyright (c) 2012-2026, Imperial College London
56isDelay = logical(isDelay(:)
');
59 isOI(i) = ~isempty(mu{i});
64 line_error(mfilename, 'pfqn_mvaoi_marg requires at least one order-independent station.
');
67% OI rank rate mu_i(n) per OI station via the (1-based) canonical microstate.
70 svcRateFun = mu{oi_list(o)};
71 muM{o} = @(n) oi_rate(svcRateFun, n, R);
74% Caches keyed by population-vector string. The OI marginals are carried per
75% station: pM_keys{o}/pM_vals{o} hold station oi_list(o)'s
count-vector marginal.
76X_cache = containers.Map(
'KeyType',
'char',
'ValueType',
'any'); % X_r(k)
77Q_cache = containers.Map(
'KeyType',
'char',
'ValueType',
'any'); % Q_ir(k) (M x R)
78pM_keys = cell(1, nOI);
79pM_vals = cell(1, nOI);
81 pM_keys{o} = containers.Map(
'KeyType',
'char',
'ValueType',
'any');
82 pM_vals{o} = containers.Map(
'KeyType',
'char',
'ValueType',
'any');
85zeroKey = vec2key(zeros(1,R));
86X_cache(zeroKey) = zeros(1,R);
87Q_cache(zeroKey) = zeros(M,R);
89 pM_keys{o}(zeroKey) = zeros(1,R);
90 pM_vals{o}(zeroKey) = 1.0;
94[~, ord] = sortrows([sum(pops,2), pops]);
97for pidx = 1:size(pops,1)
104 % Fixed (non-OI) station response times via
the arrival theorem at k - e_r.
111 kr = k; kr(r) = kr(r) - 1;
112 Qkr = Q_cache(vec2key(kr));
120 Rfix(i,r) = D(i,r) * (1 + sum(Qkr(i,:)));
122 A(r) = A(r) + Rfix(i,r);
126 % Close X(k) by population conservation: X_r*A_r + sum_i QM_ir(k;X) = k_r.
130 Xk(r) = k(r) / (A(r) + 1);
133 nkAll = cell(1, nOI); pvAll = cell(1, nOI);
137 [nkAll{o}, pvAll{o}] = oi_marginal(k, Xk, muM{o}, R, pM_keys{o}, pM_vals{o});
138 QMtot = QMtot + sum(nkAll{o} .* pvAll{o}, 1);
142 if k(r) > 0 && A(r) > 0
143 Xnew(r) = max((k(r) - QMtot(r)) / A(r), 0);
146 if max(abs(Xnew - Xk)) < 1e-13
150 Xk = 0.5*Xk + 0.5*Xnew;
154 [nkAll{o}, pvAll{o}] = oi_marginal(k, Xk, muM{o}, R, pM_keys{o}, pM_vals{o});
159 QMi = sum(nkAll{o} .* pvAll{o}, 1);
160 Qk(oi_list(o), :) = QMi;
167 Qk(i,r) = Xk(r) * Rfix(i,r);
173 pM_keys{o}(kkey) = nkAll{o};
174 pM_vals{o}(kkey) = pvAll{o};
183% =========================================================================
185% =========================================================================
187function rate = oi_rate(svcRateFun, n, R)
188% OI total service rate at
count vector n via
the 1-based canonical microstate.
193micro = repelem(1:R, n);
194rate = svcRateFun(micro);
197function [nk, pv] = oi_marginal(k, Xk, muM, R, pM_keys, pM_vals)
198% Vector marginal pM(n | k) at
the OI station given throughput Xk. Returns
count
199% vectors nk (rows) and their probabilities pv (
column), including n = 0.
201nrows = size(vecs, 1);
212 pv(cnt) = 0; % filled after normalization
222 if n(r) >= 1 && k(r) >= 1
223 nr = n; nr(r) = nr(r) - 1;
224 kr = k; kr(r) = kr(r) - 1;
225 acc = acc + Xk(r) * lookup_prob(pM_keys, pM_vals, kr, nr, R);
230 pv(cnt) = acc / rate;
231 psum = psum + pv(cnt);
236 pv(zeroRow) = 1 - psum;
240function p = lookup_prob(pM_keys, pM_vals, kr, nr, R)
241% Probability pM(nr | kr) from
the cached marginal at population kr.
243if ~isKey(pM_keys, krkey)
247keysMat = pM_keys(krkey);
248valsVec = pM_vals(krkey);
249match = all(keysMat == repmat(nr, size(keysMat,1), 1), 2);
258function vecs = enum_vecs(bound)
259% All integer vectors v with 0 <= v <= bound (rows), in ndgrid order.
267 ranges{r} = 0:bound(r);
270[grids{:}] = ndgrid(ranges{:});
271vecs = zeros(numel(grids{1}), R);
273 vecs(:, r) = grids{r}(:);
277function key = vec2key(v)
278key = sprintf(
'%d_', round(v));