1function result = qsys_bmapm1(D, mu, varargin)
2% QSYS_BMAPM1 Analyzes a BMAP/M/1 queue by the matrix-analytic (M/G/1-type) method.
4% RESULT = QSYS_BMAPM1(D, MU) analyzes a single-server queue fed by a batch
5% Markovian arrival process and with exponential service of rate MU.
8% D - cell array {D0, D1, ..., DK} of BMAP matrices. D0 carries the hidden
9% transitions, Dk (k >= 1) the transitions that release a batch of k
11% MU - exponential service rate.
14% 'Uniformization' - uniformization constant q used to randomize the
15% generator into a discrete-time M/G/1-type chain. It must
16% dominate every total outflow rate; by default it
is
17% chosen as max_i(-D0(i,i)) + mu, rounded up.
18% 'MaxIter' - maximum functional iterations for G (default 10000)
19% 'Tolerance' - convergence tolerance for G (default 1e-12)
20% 'MaxLevel' - level truncation used for the queue-length distribution
21% (default: adaptive, see qsys_bmapphnn_retrial)
22% 'TailTolerance' - relative truncation target for the level distribution
25% Beyond the usual performance measures the result exposes the intermediate
26% matrix-analytic quantities themselves, so that the algorithm can be inspected
27% and taught rather than only its output:
29% theta - stationary vector of the BMAP phase process, sum_k D_k
30% lambda - mean arrival rate, theta * sum_k k*D_k * e
31% rho - offered load lambda/mu
32% q - uniformization constant actually used
33% A0, A1, Bk - randomized blocks: A0 = (mu/q)I
is a service completion
34% (level down by one), A1 = (1/q)(D0 - mu*I) + I keeps the
35% level, Bk{k} = (1/q)D_k raises it by k
36% B0 - boundary local block (1/q)D0 + I, used at level 0 where no
38% A - A0 + A1 + sum_k Bk{k}, the phase process of the chain
39% alpha - stationary vector of A
40% G - minimal non-negative solution of
41% G = A0 + A1*G + sum_k Bk{k}*G^(k+1)
42% drift - alpha*(sum_k k*Bk{k})*e - alpha*A0*e. The queue
is stable
43% iff
this is strictly negative
44% decayRate - geometric decay rate of the level probabilities, measured as
45% the limiting ratio pi_(n+1)/pi_n. Reported rather than
46% derived from a spectral convention so that it
is unambiguous
47% levelProb - level probabilities pi_n as rows (level 0 first)
48% pi0 - probability the system
is empty (equals 1-rho exactly)
51% % Example 6.4 of Bolch et al.
52% D0 = [-2, 1/2; 1/3, -3]; D1 = [1/4, 1/2; 1/3, 1]; D2 = [1/4, 1/2; 1, 1/3];
53% result = qsys_bmapm1({D0, D1, D2}, 11);
55% See also qsys_mapm1, qsys_mapph1, qsys_bmapphnn_retrial
57% Copyright (c) 2012-2026, Imperial College London
61addParameter(parser,
'Uniformization', [], @isnumeric);
62addParameter(parser,
'MaxIter', 10000, @isnumeric);
63addParameter(parser,
'Tolerance', 1e-12, @isnumeric);
64addParameter(parser,
'MaxLevel', [], @isnumeric);
65addParameter(parser,
'TailTolerance', 1e-10, @isnumeric);
66parse(parser, varargin{:});
68qParam = parser.Results.Uniformization;
69maxIter = parser.Results.MaxIter;
70tol = parser.Results.Tolerance;
71maxLevelParam = parser.Results.MaxLevel;
72tailTol = parser.Results.TailTolerance;
75if ~iscell(D) || numel(D) < 2
76 line_error(mfilename,
'The BMAP must be given as a cell array {D0,D1,...,DK} with at least D0 and D1.');
81 if ~isnumeric(Dk) || size(Dk,1) ~= V || size(Dk,2) ~= V || any(~isfinite(Dk(:)))
82 line_error(mfilename, sprintf(
'BMAP matrix D{%d} must be a finite %dx%d matrix.', k, V, V));
84 if k > 1 && any(Dk(:) < -GlobalConstants.FineTol)
85 line_error(mfilename, sprintf(
'BMAP arrival matrix D{%d} must be non-negative.', k));
92if any(abs(Dsum * ones(V,1)) > sqrt(GlobalConstants.FineTol))
93 line_error(mfilename, 'BMAP matrices are inconsistent: sum_k D_k must have zero row sums.');
95if ~isscalar(mu) || ~isfinite(mu) || mu <= 0
96 line_error(mfilename, 'The service rate mu must be a finite positive scalar.');
101%% Arrival characterization
102theta = ctmc_solve(Dsum);
106 sumKDk = sumKDk + k * D{k+1};
108lambda = theta * sumKDk * ones(V,1);
111%% Randomization (uniformization) into a discrete-time M/G/1-type chain
113 q = max(-diag(D{1})) + mu;
117if q < max(-diag(D{1})) + mu - GlobalConstants.FineTol
118 line_error(mfilename, sprintf([
'The uniformization constant q = %g does not dominate the total outflow rate ' ...
119 '%g; the randomized chain would have negative entries.'], q, max(-diag(D{1})) + mu));
122A0 = (mu/q) * eye(V); % level down by one: service completion
123A1 = (1/q) * (D{1} - mu*eye(V)) + eye(V); % level unchanged
124B0 = (1/q) * D{1} + eye(V); % level 0: no service can complete
127 Bk{k} = (1/q) * D{k+1}; % level up by k
134alpha = dtmc_solve(A);
137%% Matrix G: minimal non-negative solution of the M/G/1-type equation
144 Gpow = Gpow * G; % G^(k+1)
145 Gnew = Gnew + Bk{k} * Gpow;
147 if max(abs(Gnew(:) - G(:))) < tol
155 line_warning(mfilename, sprintf(['The functional iteration for G did not converge to %g in %d iterations
' ...
156 '(last change %g). The queue may be unstable.
'], tol, maxIter, max(abs(Gnew(:) - G(:)))));
162 upDrift = upDrift + k * Bk{k};
164drift = alpha * upDrift * ones(V,1) - alpha * A0 * ones(V,1);
166%% Level probabilities of the continuous-time chain
167% Built from the level-truncated generator: the level blocks are homogeneous
168% above the boundary, so a single truncated solve gives the whole distribution
169% up to a residual that is refined until negligible.
170if ~isempty(maxLevelParam) && maxLevelParam > 0
171 levelMax = round(maxLevelParam);
172 levelProb = solveLevels(D, mu, V, K, levelMax);
173 truncError = levelTailError(levelProb, levelMax);
175 levelMax = max(50, ceil(20 / max(1 - min(rho, 0.999), eps)));
178 levelProb = solveLevels(D, mu, V, K, levelMax);
179 truncError = levelTailError(levelProb, levelMax);
180 if truncError <= tailTol || (2*levelMax + 1) * V > 2e5
183 levelMax = 2 * levelMax;
185 if truncError > tailTol
186 line_warning(mfilename, sprintf(['The level distribution did not reach the requested accuracy: residual
' ...
187 '%.3e > TailTolerance %.3e at level %d.
'], truncError, tailTol, levelMax));
191levelMass = sum(levelProb, 2);
192% Measured decay rate: the ratio settles geometrically, so read it where the
193% mass is still numerically meaningful rather than at the truncation boundary.
194usable = find(levelMass > 1e-12, 1, 'last
');
195if isempty(usable) || usable < 3
198 ref = max(2, floor(usable/2));
199 decayRate = levelMass(ref+1) / levelMass(ref);
202meanQueueLength = (0:(size(levelProb,1)-1)) * levelMass;
207result.lambda = lambda;
218result.decayRate = decayRate;
219result.levelProb = levelProb;
220result.pi0 = levelMass(1);
221result.meanQueueLength = meanQueueLength;
222result.utilization = rho;
223result.throughput = lambda;
224result.truncLevel = size(levelProb,1) - 1;
225result.truncError = truncError;
226result.analyzer = 'LINE:qsys_bmapm1
';
229function err = levelTailError(levelProb, levelMax)
230% Relative contribution the truncated tail would add to the mean level.
231levelMass = sum(levelProb, 2);
232meanLevel = (0:levelMax) * levelMass;
233err = levelMax * levelMass(end) / max(meanLevel, realmin);
236function levelProb = solveLevels(D, mu, V, K, levelMax)
237% Level-truncated CTMC generator of the BMAP/M/1 queue and its stationary
238% distribution. Level n holds n customers in the system; the phase is the BMAP
239% state. Service fires only above level 0.
240totalDim = (levelMax + 1) * V;
241levels = (0:levelMax)';
243[r0, c0, v0] = find(D{1});
244[rs, cs, vs] = find(mu * eye(V));
246% Local blocks: D0 on every level, minus the service rate above level 0 (the
247% service outflow
is put back on the diagonal by the row-sum correction).
248[I, J, X] = tileBlock(r0, c0, v0, levels, levels, V);
249% Service: level n -> n-1
for n >= 1
250sub = levels(levels >= 1);
251[Is, Js, Xs] = tileBlock(rs, cs, vs, sub, sub - 1, V);
252I = [I; Is]; J = [J; Js]; X = [X; Xs];
253% Batch arrivals: level n -> n+k
255 [rk, ck, vk] = find(D{k+1});
259 up = levels(levels <= levelMax - k);
263 [Ik, Jk, Xk] = tileBlock(rk, ck, vk, up, up + k, V);
264 I = [I; Ik]; J = [J; Jk]; X = [X; Xk];
267Q = sparse(I, J, X, totalDim, totalDim);
268Q = Q - spdiags(full(sum(Q, 2)), 0, totalDim, totalDim);
270Q(:, end) = ones(totalDim, 1);
271b = zeros(1, totalDim);
279levelProb = reshape(pi, V, levelMax + 1)';
280levelProb(levelProb < 0) = 0;
281levelProb = levelProb / sum(levelProb(:));
284function [I, J, X] = tileBlock(r, c, v, rowLevels, colLevels, V)
285% Place a V x V block pattern at every (rowLevels, colLevels) pair.
286if isempty(r) || isempty(rowLevels)
287 I = zeros(0,1); J = zeros(0,1); X = zeros(0,1);
290r = r(:); c = c(:); v = v(:);
291rowLevels = rowLevels(:)
'; colLevels = colLevels(:)';
292I = reshape(r + rowLevels * V, [], 1);
293J = reshape(c + colLevels * V, [], 1);
294X = repmat(v, numel(rowLevels), 1);