LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
pfqn_qdamva_oi.m
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)
3%
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).
12%
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.
17%
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.
33%
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
43% state to n,
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.
56%
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.
66%
67% The system, solved by successive substitution as in eq. (11) of the reference,
68% is
69% W_{0,r} = Z_r
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
86% W_{i,r} = |m|/mu_i.
87%
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.
94%
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.
104%
105% Parameters:
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
123% unconverged return;
124% options.nodes Gauss-Legendre nodes for the Aumann-Shapley integral
125% (default 16);
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.
132%
133% Returns:
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.
139%
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]);
144%
145% See also PFQN_MVAOI, PFQN_MVAOI_MARG, PFQN_NCOI, PFQN_OI_INSVC, PFQN_BS.
146%
147% Copyright (c) 2012-2026, Imperial College London
148% All rights reserved.
149
150if nargin < 4
151 Dli = [];
152end
153if nargin < 5
154 options = struct();
155end
156if nargin < 3
157 mu = {};
158end
159if isa(mu, 'function_handle')
160 mu = {mu};
161end
162if isempty(mu)
163 mu = {};
164end
165if ~iscell(mu)
166 line_error(mfilename, 'mu must be a cell of OI rate handles.');
167end
168K = numel(mu);
169for i = 1:K
170 if ~isa(mu{i}, 'function_handle')
171 line_error(mfilename, 'each mu{i} must be a function handle mu_i(n).');
172 end
173end
174
175R = numel(N);
176N = round(N(:)');
177Z = Z(:)';
178if numel(Z) ~= R
179 line_error(mfilename, 'Z and N must have the same number of classes.');
180end
181if any(~isfinite(N))
182 line_error(mfilename, 'pfqn_qdamva_oi requires finite (closed) populations.');
183end
184if isempty(Dli)
185 Dli = zeros(0, R);
186end
187if size(Dli, 2) ~= R
188 line_error(mfilename, 'Dli must have one column per class.');
189end
190J = size(Dli, 1);
191
192tol = 1e-6;
193if isfield(options, 'tol') && ~isempty(options.tol)
194 tol = options.tol;
195end
196iter_max = 1000;
197if isfield(options, 'iter_max') && ~isempty(options.iter_max)
198 iter_max = options.iter_max;
199end
200nodes = 16;
201if isfield(options, 'nodes') && ~isempty(options.nodes)
202 nodes = round(options.nodes);
203end
204dmu = cell(1, K);
205if isfield(options, 'dmu') && ~isempty(options.dmu)
206 dmu = options.dmu;
207 if isa(dmu, 'function_handle')
208 dmu = {dmu};
209 end
210 if numel(dmu) ~= K
211 line_error(mfilename, 'options.dmu must have one gradient handle per OI station.');
212 end
213end
214
215X = zeros(1, R);
216Qoi = zeros(K, R);
217Qli = zeros(J, R);
218Qdelay = zeros(1, R);
219it = 0;
220if sum(N) == 0
221 return
222end
223
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.
226deltar = zeros(1, R);
227nz = N > 0;
228deltar(nz) = (N(nz) - 1) ./ N(nz);
229
230[qt, qw] = gauss_legendre01(nodes);
231
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.
234nq = K + J;
235if isfield(options, 'init') && ~isempty(options.init)
236 x = options.init;
237 if ~isequal(size(x), [1 + nq, R])
238 line_error(mfilename, 'options.init must be (1+K+J) x R.');
239 end
240else
241 x = zeros(1 + nq, R);
242 if nq > 0
243 for r = 1:R
244 x(2:end, r) = N(r) / nq;
245 end
246 else
247 x(1, :) = N;
248 end
249end
250
251W = zeros(1 + nq, R);
252W(1, :) = Z; % the delay node is population-independent
253for it = 1:iter_max
254 xtot = sum(x, 2).'; % 1 x (1+nq) aggregate queue-lengths
255
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.
258 for i = 1:K
259 xi = x(1 + i, :);
260 for r = 1:R
261 if N(r) == 0
262 W(1 + i, r) = 0;
263 continue
264 end
265 m = xi;
266 m(r) = 1 + deltar(r) * xi(r);
267 rate = mu{i}(m);
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));
271 end
272 f = aumann_shapley(mu{i}, dmu{i}, m, R, qt, qw);
273 W(1 + i, r) = m(r) / (rate * f(r));
274 end
275 end
276
277 % LI residence times: the standard arrival-theorem term under eq. (7).
278 for j = 1:J
279 for r = 1:R
280 if N(r) == 0
281 W(1 + K + j, r) = 0;
282 continue
283 end
284 W(1 + K + j, r) = Dli(j, r) * (1 + xtot(1 + K + j) - x(1 + K + j, r) / N(r));
285 end
286 end
287
288 % Throughputs by population conservation, then the updated queue-lengths.
289 xnew = zeros(1 + nq, R);
290 for r = 1:R
291 if N(r) == 0
292 continue
293 end
294 den = sum(W(:, r));
295 if ~(den > 0)
296 line_error(mfilename, sprintf('class %d has no positive residence time at any station.', r));
297 end
298 X(r) = N(r) / den;
299 xnew(:, r) = X(r) * W(:, r);
300 end
301
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));
306 x = xnew;
307 if err < tol
308 break
309 end
310end
311if err >= tol
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);
314end
315
316Qdelay = x(1, :);
317Qoi = x(2:1 + K, :);
318Qli = x(2 + K:end, :);
319end
320
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.
327w = zeros(1, R);
328for q = 1:numel(qt)
329 p = qt(q) * m;
330 if isempty(dmurate)
331 for r = 1:R
332 if m(r) <= 0
333 continue
334 end
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));
339 end
340 else
341 g = dmurate(p);
342 w = w + qw(q) * g(:).';
343 end
344end
345w = max(m .* w, 0);
346w(m <= 0) = 0;
347s = sum(w);
348if ~(s > 0)
349 % Rate constant in n: the shared-rate split m_r/|m| is the exact answer.
350 w = m;
351 w(m <= 0) = 0;
352 s = sum(w);
353end
354f = w / s;
355end
356
357% =========================================================================
358function [t, w] = gauss_legendre01(n)
359% Gauss-Legendre nodes and weights on [0,1] by the Golub-Welsch algorithm.
360n = max(2, n);
361k = 1:n-1;
362b = k ./ sqrt(4 * k.^2 - 1);
363T = diag(b, 1) + diag(b, -1);
364[V, D] = eig(T);
365[xs, ord] = sort(diag(D));
366ws = 2 * V(1, ord).^2;
367t = 0.5 * (xs(:).' + 1);
368w = 0.5 * ws(:).';
369end
Definition fjtag.m:161