LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
qsys_bmapm1.m
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.
3%
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.
6%
7% Inputs:
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
10% customers.
11% MU - exponential service rate.
12%
13% Optional parameters:
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
23% (default 1e-10)
24%
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:
28%
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
37% service can complete
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)
49%
50% Example:
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);
54%
55% See also qsys_mapm1, qsys_mapph1, qsys_bmapphnn_retrial
56%
57% Copyright (c) 2012-2026, Imperial College London
58% All rights reserved.
59
60parser = inputParser;
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{:});
67
68qParam = parser.Results.Uniformization;
69maxIter = parser.Results.MaxIter;
70tol = parser.Results.Tolerance;
71maxLevelParam = parser.Results.MaxLevel;
72tailTol = parser.Results.TailTolerance;
73
74%% Validate inputs
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.');
77end
78V = size(D{1}, 1);
79for k = 1:numel(D)
80 Dk = D{k};
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));
83 end
84 if k > 1 && any(Dk(:) < -GlobalConstants.FineTol)
85 line_error(mfilename, sprintf('BMAP arrival matrix D{%d} must be non-negative.', k));
86 end
87end
88Dsum = zeros(V);
89for k = 1:numel(D)
90 Dsum = Dsum + D{k};
91end
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.');
94end
95if ~isscalar(mu) || ~isfinite(mu) || mu <= 0
96 line_error(mfilename, 'The service rate mu must be a finite positive scalar.');
97end
98
99K = numel(D) - 1;
100
101%% Arrival characterization
102theta = ctmc_solve(Dsum);
103theta = theta(:)';
104sumKDk = zeros(V);
105for k = 1:K
106 sumKDk = sumKDk + k * D{k+1};
107end
108lambda = theta * sumKDk * ones(V,1);
109rho = lambda / mu;
110
111%% Randomization (uniformization) into a discrete-time M/G/1-type chain
112if isempty(qParam)
113 q = max(-diag(D{1})) + mu;
114else
115 q = qParam;
116end
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));
120end
121
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
125Bk = cell(1, K);
126for k = 1:K
127 Bk{k} = (1/q) * D{k+1}; % level up by k
128end
129
130A = A0 + A1;
131for k = 1:K
132 A = A + Bk{k};
133end
134alpha = dtmc_solve(A);
135alpha = alpha(:)';
136
137%% Matrix G: minimal non-negative solution of the M/G/1-type equation
138G = zeros(V);
139converged = false;
140for iter = 1:maxIter
141 Gpow = G;
142 Gnew = A0 + A1*G;
143 for k = 1:K
144 Gpow = Gpow * G; % G^(k+1)
145 Gnew = Gnew + Bk{k} * Gpow;
146 end
147 if max(abs(Gnew(:) - G(:))) < tol
148 G = Gnew;
149 converged = true;
150 break;
151 end
152 G = Gnew;
153end
154if ~converged
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(:)))));
157end
158
159%% Stability drift
160upDrift = zeros(V);
161for k = 1:K
162 upDrift = upDrift + k * Bk{k};
163end
164drift = alpha * upDrift * ones(V,1) - alpha * A0 * ones(V,1);
165
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);
174else
175 levelMax = max(50, ceil(20 / max(1 - min(rho, 0.999), eps)));
176 truncError = Inf;
177 while true
178 levelProb = solveLevels(D, mu, V, K, levelMax);
179 truncError = levelTailError(levelProb, levelMax);
180 if truncError <= tailTol || (2*levelMax + 1) * V > 2e5
181 break;
182 end
183 levelMax = 2 * levelMax;
184 end
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));
188 end
189end
190
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
196 decayRate = NaN;
197else
198 ref = max(2, floor(usable/2));
199 decayRate = levelMass(ref+1) / levelMass(ref);
200end
201
202meanQueueLength = (0:(size(levelProb,1)-1)) * levelMass;
203
204%% Result
205result = struct();
206result.theta = theta;
207result.lambda = lambda;
208result.rho = rho;
209result.q = q;
210result.A0 = A0;
211result.A1 = A1;
212result.B0 = B0;
213result.Bk = {Bk};
214result.A = A;
215result.alpha = alpha;
216result.G = G;
217result.drift = drift;
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';
227end
228
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);
234end
235
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)';
242
243[r0, c0, v0] = find(D{1});
244[rs, cs, vs] = find(mu * eye(V));
245
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
254for k = 1:K
255 [rk, ck, vk] = find(D{k+1});
256 if isempty(rk)
257 continue
258 end
259 up = levels(levels <= levelMax - k);
260 if isempty(up)
261 continue
262 end
263 [Ik, Jk, Xk] = tileBlock(rk, ck, vk, up, up + k, V);
264 I = [I; Ik]; J = [J; Jk]; X = [X; Xk];
265end
266
267Q = sparse(I, J, X, totalDim, totalDim);
268Q = Q - spdiags(full(sum(Q, 2)), 0, totalDim, totalDim);
269
270Q(:, end) = ones(totalDim, 1);
271b = zeros(1, totalDim);
272b(end) = 1;
273if totalDim > 5000
274 pi = (Q' \ b')';
275else
276 pi = b / full(Q);
277end
278
279levelProb = reshape(pi, V, levelMax + 1)';
280levelProb(levelProb < 0) = 0;
281levelProb = levelProb / sum(levelProb(:));
282end
283
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);
288 return
289end
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);
295end