1function [X, Qoi, Qli, Qdelay, it] = pfqn_qdamva_oi(Z, N, mu, Dli, options)
2% [X, QOI, QLI, QDELAY, IT] = PFQN_QDAMVA_OI(Z, N, MU, DLI, OPTIONS)
4% Queue-dependent approximate mean value analysis (QD-AMVA) of a closed
5% product-
form network composed of an aggregated infinite-server (delay) node,
6% any number of load-independent (LI) single-server queues, and any number of
7% order-independent (OI) stations with empty swap graph. This
is the
8% approximate counterpart of PFQN_MVAOI: it returns the same quantities from a
9% fixed point of M*R equations whose cost
is INDEPENDENT of the population,
10% whereas PFQN_MVAOI
is exact but recurs over a lattice of size
11% prod_r nchoosek(N_r+K+1, K+1).
13% The algorithm specializes to OI stations the QD-AMVA method of Casale, Perez
14% and Wang,
"QD-AMVA: evaluating systems with queue-dependent service
15% requirements" (Performance Evaluation 91:80-98, 2015). Three steps take the
16% exact OI mean-value expression to the fixed point solved here.
18% 1) EXACT OI MEAN-VALUE FORM. Let Phi_i be the balanced-fairness balance
19% function of OI station i, the function tabulated by PFQN_NCOI, and let
20%
mu_{i,r}(n) = Phi_i(n - e_r) / Phi_i(n)
21% be the
class-r departure rate of station i in state n. Matching the BCMP
22% queue-dependent
template pi(n) = G^{-1} prod_i C_i(n_i) F_i(n_i) with the
23% multinomial C_i(n) = |n|!/prod_r n_r! gives the queue-dependent demand
24% D_{i,r}(n) = F_i(n)/F_i(n - e_r) = n_r / (|n|
mu_{i,r}(n)), and the exact
25% state-dependent MVA identity (eq. 4 of the reference)
26% x_{i,r}(N) = T_r sum_n |n| D_{i,r}(n) pi(n - e_r | N - e_r)
27% collapses, under n = m + e_r, to the OI arrival theorem
28% x_{i,r}(N) = T_r * m_r /
mu_{i,r}(m), m = e_r + x_i(N - e_r),
29% the |m| of the multinomial
factor cancelling the 1/|n| of D. The OI
30% residence time
is therefore Little
's law inside the station: the jobs of
31% class r found on arrival, the tagged one included, over the rate at which
32% the station clears class r.
34% 2) CLASS SPLIT OF THE RATE. The balanced-fairness recursion is equivalent to
35% sum_{r: n_r > 0} mu_{i,r}(n) = mu_i(n),
36% which holds exactly but is a single equation in R unknowns, so the split
37% of the station rate among the classes must be closed. Two solvable
38% extremes pin the closure down:
39% mu_i(n) = sum_r g_r(n_r) (separable) => Phi_i factorizes over the
40% classes and mu_{i,r} = g_r(n_r);
41% mu_i(n) = M(|n|) (shared) => mu_{i,r} = (n_r/|n|) M(|n|).
42% The Aumann-Shapley decomposition of the rate along the ray from the empty
44% mu_i(n) - mu_i(0) = sum_r n_r * int_0^1 (d mu_i / d n_r)(t n) dt,
45% reproduces BOTH exactly, is an exact additive decomposition of mu_i for
46% any differentiable rate, and is adopted here as the closure
47% mu_{i,r}(m) = mu_i(m) * w_r / sum_s w_s,
48% w_r = m_r * int_0^1 (d mu_i / d n_r)(t m) dt.
49% The integral is evaluated by Gauss-Legendre quadrature, so the cost is
50% independent of the population. When every w_r vanishes, which happens only
51% for a rate that is constant in n, the split falls back to w_r = m_r, the
52% exact shared-rate answer. Verified against exact lattice values, the
53% shares are correct to machine precision on separable rates, on
54% total-population rates including the multiserver rate min(c, |n|), and at
55% every state with a single job per class.
57% 3) SCHWEITZER INTERPOLATION (eq. 7 of the reference), applied componentwise
58% to the vector argument,
59% x_{i,r}(N - e_r) = delta_r x_{i,r}, delta_r = (N_r - 1)/N_r,
60% x_{i,s}(N - e_r) = x_{i,s}, s ~= r,
61% so that m_r = 1 + delta_r x_{i,r} and m_s = x_{i,s}. The reference also
62% offers the class-independent form of its eq. (8), adopted there to secure
63% uniqueness of the fixed point; on OI stations that form loses the per-class
64% arrival-instant correction and was measured to be three to ten times less
65% accurate, so eq. (7) is used instead.
67% The system, solved by successive substitution as in eq. (11) of the reference,
70% W_{j,r} = Dli(j,r) (1 + x_j - x_{j,r}/N_r) LI queue j
71% W_{i,r} = m_r / mu_{i,r}(m), m = e_r + x_i(N - e_r) OI station i
72% T_r = N_r / (W_{0,r} + sum_j W_{j,r} + sum_i W_{i,r})
73% x_{k,r} = T_r W_{k,r}
74% where x_k = sum_r x_{k,r}. A sweep costs O((J + K) R) residence times and
75% O(K R^2 q) rate evaluations with q quadrature nodes, independently of the
76% population. The number of sweeps is nearly so: 19 to 83 at the default
77% tolerance from |N| = 9 to |N| = 45000 on a saturating three-class station.
78% It does grow once an OI station saturates, because the fixed point is then
79% neutrally stable -- with mu_i(n) -> sum_r a_r and the shares -> a_r/sum_s a_s,
80% every x_i satisfies T_r = a_r -- and the trailing digits creep while the
81% throughputs are already settled. That is why the tolerance is relative and
82% defaults to 1e-6: tightening it to 1e-10 costs 16182 sweeps at |N| = 4500 and
83% moves the throughputs by 3e-9, far below the 1e-2 error of the approximation
84% itself. With every mu_i constant the OI equations reduce exactly to
85% Bard-Schweitzer AMVA, since then mu_{i,r}(m) = mu_i m_r/|m| and
88% ACCURACY. Against PFQN_MVAOI on a battery of separable, shared, multiserver,
89% mixed and multi-station OI models with two and three classes, the worst
90% relative error on X and on the per-station queue-lengths was 8.6e-2, reached
91% on a rate mixing a per-class and a shared term, with typical errors between
92% 1e-3 and 6e-2. The residual is dominated by the Schweitzer step, which alone
93% accounts for 1.5e-2 on a single-class queue-dependent station.
95% SMOOTHNESS. Theorem 1 of the reference requires the queue-dependent factors to
96% be differentiable on the real box, so MU IS ASSUMED TO BE SUPPLIED ALREADY
97% SMOOTHED BY THE CALLER: each mu{i} must accept a real, non-negative occupancy
98% vector and return a strictly positive rate on it. A rate written through the
99% support indicator 1{n_r > 0}, as accepted by PFQN_NCOI and PFQN_MVAOI, is a
100% step function of n and must be replaced by a smooth surrogate before being
101% passed here, for instance by substituting n_r/(1 + n_r) or 1 - exp(-n_r) for
102% the indicator. The same smoothed handle should then be passed to PFQN_MVAOI
103% when the two are compared, since it defines the reference model.
106% Z - (1 x R) think-time demand vector of the aggregated delay node.
107% N - (1 x R) closed population vector, finite.
108% mu - cell array {mu_1,...,mu_K} of function handles; mu_i(n) returns the OI
109% total service rate of station i at the REAL occupancy vector n
110% (1 x R). A bare function handle is accepted as the single-station
111% shorthand. May be empty when there is no OI station.
112% Dli - (J x R) per-class demand matrix of the LI single-server queues
113% (D_{j,r} = V_{j,r}/rate_{j,r}); empty or omitted when J = 0.
114% options - solver options (optional). Recognized fields:
115% options.tol successive-substitution tolerance (default 1e-6) on
116% the RELATIVE increment max |x^{n+1} - x^n| / |x^{n+1}|
117% over all (k,r). The criterion must be relative: the
118% queue-lengths scale with the population, so a fixed
119% absolute threshold demands more precision than the
120% iteration can deliver at large N;
121% options.iter_max maximum number of sweeps (default 1000). Reaching it
122% raises a warning naming the residual, never a silent
124% options.nodes Gauss-Legendre nodes for the Aumann-Shapley integral
126% options.dmu cell array of gradient handles, dmu{i}(n) returning
127% the (1 x R) gradient of mu_i at n. When omitted the
128% gradient is taken by central differences;
129% options.init (1+K+J) x R initial queue-length guess, rows ordered
130% as [delay; OI stations; LI queues]. Defaults to the
131% population spread evenly over the queueing stations.
134% X - (1 x R) per-class throughput.
135% Qoi - (K x R) per-class mean queue-length at each OI station (row i).
136% Qli - (J x R) per-class mean queue-length at each LI queue (row j).
137% Qdelay - (1 x R) per-class mean queue-length at the delay node (X.*Z).
138% it - number of successive-substitution sweeps performed.
140% Example (smoothed two-class OI station, one LI queue):
141% sm = @(v) v ./ (1 + v); % smoothed indicator
142% rate = @(n) 1.0*sm(n(1)) + 1.5*sm(n(2)); % smoothed OI rate
143% [X, Qoi, Qli] = pfqn_qdamva_oi([1 0.5], [8 6], {rate}, [0.3 0.2]);
145% See also PFQN_MVAOI, PFQN_MVAOI_MARG, PFQN_NCOI, PFQN_OI_INSVC, PFQN_BS.
147% Copyright (c) 2012-2026, Imperial College London
148% All rights reserved.
159if isa(mu, 'function_handle
')
166 line_error(mfilename, 'mu must be a cell of OI rate handles.
');
170 if ~isa(mu{i}, 'function_handle
')
171 line_error(mfilename, 'each mu{i} must be a function handle mu_i(n).
');
179 line_error(mfilename, 'Z and N must have the same number of classes.
');
182 line_error(mfilename, 'pfqn_qdamva_oi requires finite (closed) populations.
');
188 line_error(mfilename, 'Dli must have one
column per class.
');
193if isfield(options, 'tol
') && ~isempty(options.tol)
197if isfield(options, 'iter_max
') && ~isempty(options.iter_max)
198 iter_max = options.iter_max;
201if isfield(options, 'nodes') && ~isempty(options.nodes)
202 nodes = round(options.nodes);
205if isfield(options, 'dmu
') && ~isempty(options.dmu)
207 if isa(dmu, 'function_handle
')
211 line_error(mfilename, 'options.dmu must have one gradient handle per OI station.
');
224% Schweitzer per-class ratios of eq. (7). A class with a single job sees the
225% station empty of its own class on arrival, delta_r = 0.
228deltar(nz) = (N(nz) - 1) ./ N(nz);
230[qt, qw] = gauss_legendre01(nodes);
232% Queue-length state: row 1 is the delay node, rows 2:K+1 the OI stations, rows
233% K+2:K+J+1 the LI queues.
235if isfield(options, 'init
') && ~isempty(options.init)
237 if ~isequal(size(x), [1 + nq, R])
238 line_error(mfilename, 'options.init must be (1+K+J) x R.
');
241 x = zeros(1 + nq, R);
244 x(2:end, r) = N(r) / nq;
252W(1, :) = Z; % the delay node is population-independent
254 xtot = sum(x, 2).'; % 1 x (1+nq) aggregate queue-lengths
256 % OI residence times W_{i,r} = m_r /
mu_{i,r}(m), the
class rate being split
257 % by the Aumann-Shapley shares of mu_i at the arrival-instant occupancy m.
266 m(r) = 1 + deltar(r) * xi(r);
268 if ~(rate > 0) || ~isfinite(rate)
269 line_error(mfilename, sprintf([
'mu{%d} returned a non-positive or non-finite rate at a ' ...
270 'real occupancy vector; it must be a smooth, strictly positive extension.'], i));
272 f = aumann_shapley(mu{i}, dmu{i}, m, R, qt, qw);
273 W(1 + i, r) = m(r) / (rate * f(r));
277 % LI residence times: the standard arrival-theorem term under eq. (7).
284 W(1 + K + j, r) = Dli(j, r) * (1 + xtot(1 + K + j) - x(1 + K + j, r) / N(r));
288 % Throughputs by population conservation, then the updated queue-lengths.
289 xnew = zeros(1 + nq, R);
296 line_error(mfilename, sprintf(
'class %d has no positive residence time at any station.', r));
299 xnew(:, r) = X(r) * W(:, r);
302 % Relative criterion. An absolute one
is unusable here: the queue-lengths
303 % scale with the population, so a fixed absolute threshold silently demands
304 % more precision than the iteration can deliver at large N.
305 err = max(abs(xnew(:) - x(:)) ./ max(abs(xnew(:)), eps));
312 line_warning(mfilename, [
'successive substitution stopped at iter_max = %d with relative residual ' ...
313 '%g > tol = %g; the returned means are not converged.'], iter_max, err, tol);
318Qli = x(2 + K:end, :);
321% =========================================================================
322function f = aumann_shapley(murate, dmurate, m, R, qt, qw)
323% Aumann-Shapley shares of murate(m) among the R classes: the exact additive
324% decomposition murate(m) - murate(0) = sum_r m_r int_0^1 d murate/d n_r (t m) dt.
325% Exact
for separable and
for total-population rates, hence exact whenever the
326% balanced-fairness split itself
is available in closed
form.
335 h = 1e-6 * max(1, abs(p(r)));
336 pp = p; pp(r) = p(r) + h;
337 pm = p; pm(r) = max(0, p(r) - h);
338 w(r) = w(r) + qw(q) * (murate(pp) - murate(pm)) / (pp(r) - pm(r));
342 w = w + qw(q) * g(:).
';
349 % Rate constant in n: the shared-rate split m_r/|m| is the exact answer.
357% =========================================================================
358function [t, w] = gauss_legendre01(n)
359% Gauss-Legendre nodes and weights on [0,1] by the Golub-Welsch algorithm.
362b = k ./ sqrt(4 * k.^2 - 1);
363T = diag(b, 1) + diag(b, -1);
365[xs, ord] = sort(diag(D));
366ws = 2 * V(1, ord).^2;
367t = 0.5 * (xs(:).' + 1);