1function [QN, UN, RN, TN, piAgg, GM] = solver_mam_qsys(arrival, service, options)
2% SOLVER_MAM_QSYS Unified solver
for MAP/BMAP queueing systems with single server
4% [QN, UN, RN, TN, PI, GM] = SOLVER_MAM_QSYS(ARRIVAL, SERVICE) solves a queueing
5% system with Markovian arrival and service processes. Automatically detects
6% whether arrivals or service are batched and applies the appropriate
7% matrix-analytic method:
8% - BMAP/MAP/1: M/G/1 type analysis (batch arrivals, single service)
9% - MAP/BMAP/1: GI/M/1 type analysis (single arrivals, batch service)
12% arrival - Arrival process: MAP object, BMAP object, or cell array
13% MAP: {D0, D1} or MAP
object
14% BMAP: {D0, D1, D2, ..., DK} or BMAP
object
15% service - Service process: MAP object, BMAP object, or cell array
16% MAP: {S0, S1} or MAP
object
17% BMAP: {S0, S1, S2, ..., SK} or BMAP
object
18% options - (optional) Options structure with fields:
19% .nMoments: number of queue length moments to compute (default: 3)
22% QN - Mean queue length E[N]
23% UN - Server utilization rho
24% RN - Mean response time E[R]
25% TN - Throughput (arrival rate)
26% piAgg - Aggregated stationary probabilities
27% GM - G matrix (M/G/1 type) or R matrix (GI/M/1 type)
30% BMAP/MAP/1 - Batch arrivals, single service (M/G/1 type)
31% Level increases by batch size (1, 2, 3, ...), decreases by 1
32% MAP/BMAP/1 - Single arrivals, batch service (GI/M/1 type)
33% Level increases by 1, decreases by batch size (1, 2, 3, ...)
36% Stathopoulos, Riska, Hua, Smirni: ETAQA algorithm
37% MAMSolver: www.cs.wm.edu/MAMSolver
41% Copyright (c) 2012-2026, Imperial College London
44%% Input validation and setup
48if ~isfield(options,
'nMoments')
52%% Determine arrival type (MAP or BMAP)
53[arrivalMatrices, arrivalIsBatch, ma] = parseProcess(arrival, 'arrival');
55%% Determine service type (MAP or BMAP)
56[serviceMatrices, serviceIsBatch, ms] = parseProcess(service, 'service');
58%% Route to appropriate solver based on queue type
59if arrivalIsBatch && ~serviceIsBatch
60 % BMAP/MAP/1 queue - M/G/1 type analysis
61 [QN, UN, RN, TN, piAgg, GM] = solveBMAPMAP1(arrivalMatrices, serviceMatrices, ma, ms, options);
62elseif ~arrivalIsBatch && serviceIsBatch
63 % MAP/BMAP/1 queue - GI/M/1 type analysis
64 [QN, UN, RN, TN, piAgg, GM] = solveMAPBMAP1(arrivalMatrices, serviceMatrices, ma, ms, options);
65elseif ~arrivalIsBatch && ~serviceIsBatch
66 % MAP/MAP/1 queue - treat as BMAP/MAP/1 with batch size 1
67 [QN, UN, RN, TN, piAgg, GM] = solveBMAPMAP1(arrivalMatrices, serviceMatrices, ma, ms, options);
69 % BMAP/BMAP/1 - both batched, use finite CTMC truncation
70 [QN, UN, RN, TN, piAgg, GM] = solveBMAPBMAP1(arrivalMatrices, serviceMatrices, ma, ms, options);
75%% Helper function to parse arrival/service process
76function [matrices, isBatch, nPhases] = parseProcess(proc, procType)
78 K = proc.getMaxBatchSize();
79 matrices = cell(1, K + 1);
80 matrices{1} = proc.D(0); % D0
82 matrices{k+1} = proc.D(1, k); % Dk (batch size k)
85 nPhases = size(matrices{1}, 1);
86 elseif isa(proc,
'MAP')
87 matrices = {proc.D(0), proc.D(1)};
89 nPhases = size(matrices{1}, 1);
92 isBatch = length(proc) > 2; % More than {D0, D1} means batch process
93 nPhases = size(proc{1}, 1);
95 error(
'%s must be a MAP object, BMAP object, or cell array', procType);
99 for k = 1:length(matrices)
100 if size(matrices{k}, 1) ~= nPhases || size(matrices{k}, 2) ~= nPhases
101 error(
'All %s matrices must be %dx%d', procType, nPhases, nPhases);
106%% BMAP/MAP/1 solver
using M/G/1 type analysis
107function [QN, UN, RN, TN, pi, G] = solveBMAPMAP1(D, S, ma, ms, options)
108% Solves BMAP/MAP/1
using M/G/1 type matrix-analytic methods
110% M/G/1 type structure (level can jump UP by any amount, DOWN by exactly 1):
111% A0 = I_ma \otimes S1 (service completion, level -1)
112% A1 = D0 \otimes I_ms + I_ma \otimes S0 (phase changes, level 0)
113% A_{k+1} = D_k \otimes I_ms (batch arrival size k, level +k)
115K = length(D) - 1; % max batch size
for arrivals
116m = ma * ms; % Combined phases per level
118% Extract MAP service matrices
122%% Construct M/G/1 type matrices
using Kronecker products
126A = zeros(m, m * (K + 2));
128% A0 = I_ma \otimes S1 (service completion)
132% A1 = D0 \otimes I_ms + I_ma \otimes S0 (phase changes only)
133A1 = kron(D{1}, I_ms) + kron(I_ma, S0);
136% A_{k+1} = D_k \otimes I_ms
for k >= 1 (batch arrivals)
138 Ak = kron(D{k+1}, I_ms);
139 A(:, (k+1)*m + 1 : (k+2)*m) = Ak;
142% B = [B0, B1, B2, ..., BK]
for level 0 (empty queue)
143B = zeros(m, m * (K + 1));
145% B0: At level 0, no service (add S1 back as self-loop)
146B0 = kron(D{1}, I_ms) + kron(I_ma, S0 + S1);
149% B_k = D_k \otimes I_ms
for k >= 1 (batch arrivals from empty queue)
151 Bk = kron(D{k+1}, I_ms);
152 B(:, k*m + 1 : (k+1)*m) = Bk;
155%% Verify stability condition
157D1_total = zeros(ma, ma);
159 D1_total = D1_total + D{k+1};
162% Stationary distribution of BMAP
163pi_bmap = map_prob({D0, D1_total});
166% Total customer arrival rate: sum_k (k * π * Dk * e)
169 rate_k = pi_bmap * D{k+1} * e_ma;
170 lambda_total = lambda_total + k * rate_k;
173% Compute service rate from MAP
174pi_map = map_prob({S0, S1});
176mu = pi_map * S1 * e_ms; % Service rate
179rho = lambda_total / mu;
182 warning(
'solver_mam_qsys:Unstable', ...
183 'System is unstable (rho = %.4f >= 1). Results may be invalid.', rho);
186%% Compute G matrix
using MG1_G_ETAQA
189%% Compute stationary probabilities
using MG1_pi_ETAQA
190pi = MG1_pi_ETAQA(B, A, G);
192%% Compute queue length moments
using MG1_qlen_ETAQA
193qlen_moments = zeros(1, options.nMoments);
194for n = 1:options.nMoments
195 qlen_moments(n) = MG1_qlen_ETAQA(B, A, pi, n);
198%% Compute performance metrics
199QN = qlen_moments(1); % Mean queue length (E[N])
200UN = rho; % Utilization
201TN = lambda_total; % Throughput (total arrival rate)
202RN = QN / TN; % Mean response time (Little
's law: E[R] = E[N] / λ)
206%% MAP/BMAP/1 solver using GI/M/1 type analysis
207function [QN, UN, RN, TN, piAgg, R] = solveMAPBMAP1(C, D, ma, ms, options)
208% Solves MAP/BMAP/1 using GI/M/1 type matrix-analytic methods
210% GI/M/1 type structure (level jumps UP by 1, DOWN by any amount):
211% A0 = C1 \otimes I_ms (MAP arrival, level +1)
212% A1 = C0 \otimes I_ms + I_ma \otimes D0 (phase changes, level 0)
213% A_{k+1} = I_ma \otimes D_k (batch size k service, level -k)
217K = length(D) - 1; % max batch size for service
218m = ma * ms; % Combined phases per level
221% Check MAP is valid generator
222rowSumsC = sum(C0 + C1, 2);
223if max(abs(rowSumsC)) > 1e-10
224 error('MAP matrices C0 + C1 must have zero row sums
');
227% Check BMAP is valid generator
230 DTotal = DTotal + D{k+1};
232rowSumsD = sum(DTotal, 2);
233if max(abs(rowSumsD)) > 1e-10
234 error('BMAP matrices D0 + D1 + ... + DK must have zero row sums
');
237%% Compute arrival and service rates
238% Arrival rate from MAP
239piC = map_prob({C0, C1});
240lambdaArr = piC * C1 * ones(ma, 1);
242% Service rate from BMAP (total customers served per unit time)
243piD = map_prob({D{1}, DTotal - D{1}}); % D0, sum of D1...DK
246 muTotal = muTotal + k * (piD * D{k+1} * ones(ms, 1));
250rho = lambdaArr / muTotal;
253 warning('solver_mam_qsys:Unstable
', ...
254 'System
is unstable (rho = %.4f >= 1). Results may be invalid.
', rho);
257%% Construct A matrix for GI/M/1-type (repeating part)
258A = zeros(m * (K + 2), m);
260% A0: MAP arrival (level +1)
261A0 = kron(C1, eye(ms));
264% A1: Phase changes only (level 0)
265A1 = kron(C0, eye(ms)) + kron(eye(ma), D{1});
268% A_{k+1}: Batch service of size k (level -k)
270 Ak = kron(eye(ma), D{k+1});
271 A((k+1)*m+1:(k+2)*m, :) = Ak;
274%% Construct B matrix for boundary levels
275B = zeros(m * (K + 1) + m, m);
277% B1 (level 0 -> level 0): Phase changes only, no service from empty
278B1 = kron(C0, eye(ms)) + kron(eye(ma), D{1});
279% Add service transitions that would go to negative levels back to level 0
281 B1 = B1 + kron(eye(ma), D{k+1});
285% B2, B3, ..., B_{K+1}: Transitions to level 0 from levels 1, 2, ..., K
289 Bj = Bj + kron(eye(ma), D{k+1});
291 B(m + (j-1)*m + 1 : m + j*m, :) = Bj;
294%% Compute R matrix using GI/M/1 ETAQA
297%% Compute stationary probabilities using GI/M/1 ETAQA
298piAgg = GIM1_pi_ETAQA(B, A, R, 'Boundary
', A0);
300%% Compute performance metrics
301% Mean queue length using ETAQA moments
302QN = GIM1_qlen_ETAQA(B, A, R, piAgg, 1, 'Boundary
', A0);
304% Throughput equals arrival rate (in steady state)
310% Mean response time via Little's Law
315%% BMAP/BMAP/1 solver
using finite CTMC truncation
316function [QN, UN, RN, TN, piAgg, G] = solveBMAPBMAP1(D, S, ma, ms, options)
317% Solves BMAP/BMAP/1
using finite CTMC truncation
319% When both arrivals and service are batched, the process
is neither
320% M/G/1 type nor GI/M/1 type. We construct a finite truncated CTMC
321% over the Kronecker product of arrival and service phases per level.
323% State: (level, arrival_phase, service_phase)
324% level = number of customers in system (0, 1, ..., L)
325% arrival_phase = 1, ..., ma
326% service_phase = 1, ..., ms
328Ka = length(D) - 1; % max batch size
for arrivals
329Ks = length(S) - 1; % max batch size
for service
330m = ma * ms; % combined phases per level
332%% Compute arrival and service rates
for stability check
333D1_total = zeros(ma, ma);
335 D1_total = D1_total + D{k+1};
337pi_bmap = map_prob({D{1}, D1_total});
341 lambda_total = lambda_total + k * (pi_bmap * D{k+1} * e_ma);
344S1_total = zeros(ms, ms);
346 S1_total = S1_total + S{k+1};
348pi_smap = map_prob({S{1}, S1_total});
352 mu_total = mu_total + k * (pi_smap * S{k+1} * e_ms);
355rho = lambda_total / mu_total;
358 warning(
'solver_mam_qsys:Unstable', ...
359 'System is unstable (rho = %.4f >= 1). Results may be invalid.', rho);
362%% Choose truncation level based on utilization
363L = max(100, ceil(20 / (1 - min(rho, 0.99))));
364L = min(L, 500); % cap
for memory
366%% Build generator matrix Q as sparse (L+1)*m x (L+1)*m
367N_states = (L + 1) * m;
371% Pre-compute Kronecker products
372arrKron = cell(1, Ka);
374 arrKron{k} = kron(sparse(D{k+1}), I_ms);
376svcKron = cell(1, Ks);
378 svcKron{k} = kron(I_ma, sparse(S{k+1}));
380phaseKron = kron(sparse(D{1}), I_ms) + kron(I_ma, sparse(S{1}));
382% Estimate non-zeros and pre-allocate
383nnz_est = (L+1) * m * m * (1 + Ka + Ks);
384ii = zeros(nnz_est, 1);
385jj = zeros(nnz_est, 1);
386vv = zeros(nnz_est, 1);
392 % Internal phase transitions: D_0 x I_ms + I_ma x S_0
393 [ri, ci, vi] = find(phaseKron);
394 n_entries = length(ri);
395 ii(cnt+1:cnt+n_entries) = base + ri;
396 jj(cnt+1:cnt+n_entries) = base + ci;
397 vv(cnt+1:cnt+n_entries) = vi;
398 cnt = cnt + n_entries;
400 % Arrivals: batch size k, level -> min(level+k, L)
402 new_lev = min(lev + k, L);
404 [ri, ci, vi] = find(arrKron{k});
405 n_entries = length(ri);
406 ii(cnt+1:cnt+n_entries) = base + ri;
407 jj(cnt+1:cnt+n_entries) = dest + ci;
408 vv(cnt+1:cnt+n_entries) = vi;
409 cnt = cnt + n_entries;
413 % Service: batch size j, level -> max(level-j, 0)
415 new_lev = max(lev - j, 0);
417 [ri, ci, vi] = find(svcKron{j});
418 n_entries = length(ri);
419 ii(cnt+1:cnt+n_entries) = base + ri;
420 jj(cnt+1:cnt+n_entries) = dest + ci;
421 vv(cnt+1:cnt+n_entries) = vi;
422 cnt = cnt + n_entries;
425 % Level 0: server idle, service completions stay at level 0
427 [ri, ci, vi] = find(svcKron{j});
428 n_entries = length(ri);
429 ii(cnt+1:cnt+n_entries) = base + ri;
430 jj(cnt+1:cnt+n_entries) = base + ci;
431 vv(cnt+1:cnt+n_entries) = vi;
432 cnt = cnt + n_entries;
437% Trim and assemble sparse Q
441Q = sparse(ii, jj, vv, N_states, N_states);
443% Fix diagonal: Q(i,i) = -sum of off-diagonal entries in row i
445Q = Q - spdiags(d, 0, N_states, N_states);
447%% Solve
for steady-state distribution
448% Solve pi * Q = 0 with pi * e = 1
449% Equivalent to Q
' * pi' = 0
451% Replace last equation with normalization constraint
453b = sparse(N_states, 1);
461%% Compute performance metrics
464QN = levels * reshape(sum(reshape(pi, m, L+1), 1), L+1, 1);
466% Utilization and throughput
470% Mean response time via Little
's law
473% Aggregate probabilities by level
474piAgg = sum(reshape(pi, m, L+1), 1);
476G = []; % No G matrix for truncation approach