1function [QN, UN, RN, TN, CN, XN, totiter] = solver_mam_ldqbd(sn, options)
2% SOLVER_MAM_LDQBD Solve single-
class closed queueing networks using LD-QBD
4% Uses Level-Dependent Quasi-Birth-Death (LD-QBD) process to compute
5% exact performance metrics
for single-
class closed queueing networks
6% consisting of a Delay (infinite server) and a Queue (FCFS).
8% The LD-QBD approach models the system where:
9% - Level n = number of jobs at the queue (0 <= n <= N)
10% - Jobs at delay = N - n
11% - Transition rates depend on the current level
13% Supports PH-type service distributions (Exp, Erlang, HyperExp, etc.)
15% Copyright (c) 2012-2026, Imperial College London
18%% Validate model structure
23% Check: single-class closed network
25 line_error(mfilename, 'LDQBD method
requires a single-
class model.');
29 line_error(mfilename,
'LDQBD method requires a closed model.');
32% Check: must have exactly one delay and one queue
33nDelay = sum(sn.sched == SchedStrategy.INF);
34nQueue = sum(sn.sched == SchedStrategy.FCFS);
36if nDelay ~= 1 || nQueue ~= 1 || M ~= 2
37 line_error(mfilename,
'LDQBD method requires exactly one Delay and one Queue station.');
41delayIdx = find(sn.sched == SchedStrategy.INF);
42queueIdx = find(sn.sched == SchedStrategy.FCFS);
44%% Get service parameters
47nservers = sn.nservers;
49% Delay station rate (infinite server)
50lambda_d = rates(delayIdx, 1);
52% Get routing probability from Delay to Queue
53% sn.rt
is (M*K) x (M*K) routing table
55p_delay_to_queue = rt(delayIdx, queueIdx); % Since K=1, simple indexing works
57% Effective arrival rate to queue = delay rate * routing probability
58lambda_eff = lambda_d * p_delay_to_queue;
60% Queue service process
61PH_queue = PH{queueIdx}{1};
62nServers = nservers(queueIdx);
64% Check
if queue service
is exponential (1x1 matrix) or PH
65if numel(PH_queue{1}) == 1
67 mu = -PH_queue{1}; % Rate from D0 matrix
71 % PH-type service (Erlang, HyperExp, etc.)
72 nPhases = size(PH_queue{1}, 1);
74 D0 = PH_queue{1}; % Sub-generator (negative diag = rates out)
75 D1 = PH_queue{2}; % Absorption/completion transitions
76 alpha = map_pie(PH_queue); % Initial phase distribution
79%% Construct LD-QBD matrices
80% For a closed network with N jobs:
81% Level n = number of jobs at queue (0 <= n <= N)
82% Q0^(n): upward transitions (arrival from delay)
83% Q1^(n): local transitions (within level)
84% Q2^(n): downward transitions (departure from queue)
86Q0 = cell(N, 1); % {Q0^(0), Q0^(1), ..., Q0^(N-1)}
87Q1 = cell(N+1, 1); % {Q1^(0), Q1^(1), ..., Q1^(N)}
88Q2 = cell(N, 1); % {Q2^(1), Q2^(2), ..., Q2^(N)}
91 % Exponential service
case (scalar matrices)
93 % Arrival rate: (N-n) * lambda_eff (effective rate to queue)
94 Q0{n+1} = (N - n) * lambda_eff;
98 arrival_rate = (N - n) * lambda_eff;
100 % Service rate: min(n, nServers) * mu
101 departure_rate = min(n, nServers) * mu;
105 Q1{n+1} = -(arrival_rate + departure_rate);
109 % Service rate: min(n, nServers) * mu
110 Q2{n} = min(n, nServers) * mu;
113 % PH-type service
case (matrix-valued)
114 % State space at level n (n>0): nPhases phases
115 % State space at level 0: 1 state (empty queue)
117 % Level 0 -> 1: arrival starts service in some phase
118 % 1 x nPhases matrix: arrival rate * initial phase distribution
119 Q0{1} = N * lambda_eff * alpha;
121 % Level n -> n+1 (n >= 1): arrival, preserve phase
123 % nPhases x nPhases matrix: diagonal (preserve phase)
124 Q0{n+1} = (N - n) * lambda_eff * eye(nPhases);
127 % Level 0: 1x1 (no service, only arrivals)
128 Q1{1} = -(N * lambda_eff);
130 % Level n >= 1: phase transitions within level
132 arrival_rate = (N - n) * lambda_eff;
133 % Number of active servers at level n
134 c_n = min(n, nServers);
136 % Local transition matrix: D0 (phase transitions) - arrivals
137 % Scale D0 by number of active servers
for multi-server
138 Q1{n+1} = c_n * D0 - arrival_rate * eye(nPhases);
141 % Level 1 -> 0: service completion, go to empty state
142 % nPhases x 1 column vector (sum over destination phases)
143 c_1 = min(1, nServers);
144 Q2{1} = c_1 * D1 * ones(nPhases, 1);
146 % Level n -> n-1 (n >= 2): service completion, next job starts
147 % D1(i,j)
is rate of completing from phase i and next starting in phase j
149 c_n = min(n, nServers);
150 % nPhases x nPhases: D1 encodes completion and restart phase
155%% Solve LD-QBD
using the ldqbd function
156ldqbd_options =
struct(
'epsilon', options.tol,
'maxIter', options.iter_max,
'verbose',
false);
158[R, pi_ldqbd] = ldqbd(Q0, Q1, Q2, ldqbd_options);
160%% Compute performance metrics from steady-state distribution
161% Mean queue length at the queue station
162mean_queue = (0:N) * pi_ldqbd
';
164% Mean queue length at delay station (complementary)
165mean_delay = N - mean_queue;
167%% Compute throughput and utilization
168% For delay (infinite server): each job at delay generates arrivals to queue
169% at the effective rate lambda_eff = lambda_d * P(Delay->Queue)
170% System throughput X = mean_delay * lambda_eff (flow balance)
171X = mean_delay * lambda_eff;
173% Mean service time at queue
177 mean_service = map_mean(PH_queue);
180% Queue utilization: probability queue is busy (at least 1 job)
181% For multi-server: average fraction of server capacity in use
183 util_queue = 1 - pi_ldqbd(1);
185 % For c-server queue: U = sum_{n=1}^{N} min(n,c)/c * pi(n)
188 util_queue = util_queue + (min(n, nServers) / nServers) * pi_ldqbd(n+1);
192% Response time at queue (using Little's law: R = Q/X)
194 R_queue = mean_queue / X;
199% Response time at delay (constant
for infinite server)
200R_delay = 1 / lambda_d;
202%% Populate output matrices
210% Delay station metrics
211QN(delayIdx, 1) = mean_delay;
212UN(delayIdx, 1) = mean_delay; % For infinite server, U = Q
213RN(delayIdx, 1) = R_delay;
216% Queue station metrics
217QN(queueIdx, 1) = mean_queue;
218UN(queueIdx, 1) = util_queue / nServers; % Per-server utilization
219RN(queueIdx, 1) = R_queue;
222% System-level metrics
224CN(1) = R_delay + R_queue; % Cycle time
226totiter = 1; % LDQBD
is a direct method