LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_mam_ldqbd_transient.m
1function [Qt, Ut, Tt] = solver_mam_ldqbd_transient(sn, options)
2% SOLVER_MAM_LDQBD_TRANSIENT Transient analysis of open queues via standard QBD
3%
4% Supports single-class open models (Source -> Queue -> Sink).
5% For M/M/c: scalar QBD levels (any number of servers).
6% For M/PH/1: m-phase QBD levels (single server only).
7%
8% Infinite capacity: libQBD adaptive Taylor series.
9% Finite capacity: direct matrix exponentiation (expm).
10%
11% Copyright (c) 2012-2026, Imperial College London
12% All rights reserved.
13
14%% Validate model structure
15M = sn.nstations;
16K = sn.nclasses;
17
18if K ~= 1
19 line_error(mfilename, 'Transient QBD method requires a single-class model.');
20end
21
22N = sn.njobs';
23if ~isinf(N)
24 line_error(mfilename, 'Transient QBD method requires an open model.');
25end
26
27%% Identify stations
28sourceIdx = find(sn.sched == SchedStrategy.EXT);
29queueIdx = find(sn.sched == SchedStrategy.FCFS);
30
31if numel(sourceIdx) ~= 1 || numel(queueIdx) ~= 1
32 line_error(mfilename, 'Transient QBD method requires exactly one Source and one FCFS Queue.');
33end
34
35%% Extract parameters
36lambda = sn.rates(sourceIdx, 1);
37PH_queue = sn.proc{queueIdx}{1};
38nServers = sn.nservers(queueIdx);
39bufCap = sn.cap(queueIdx);
40
41if numel(PH_queue{1}) == 1
42 mu = -PH_queue{1};
43 nPhases = 1;
44 isPH = false;
45else
46 nPhases = size(PH_queue{1}, 1);
47 isPH = true;
48 D0 = PH_queue{1};
49 D1 = PH_queue{2};
50 alpha = map_pie(PH_queue);
51 t_exit = -D0 * ones(nPhases, 1);
52end
53
54if isPH && nServers > 1
55 line_error(mfilename, 'Transient QBD with PH service supports single-server only.');
56end
57
58%% Time parameters
59T_start = options.timespan(1);
60T_end = options.timespan(2);
61T_duration = T_end - T_start;
62
63if ~isinf(bufCap)
64 %% Finite capacity: assemble full generator Q and use expm
65 Cap = bufCap;
66
67 if ~isPH
68 % M/M/c/N: scalar levels, (Cap+1) x (Cap+1) generator
69 dim = Cap + 1;
70 Q = zeros(dim, dim);
71 for n = 0:Cap
72 idx = n + 1;
73 dep = min(n, nServers) * mu;
74 arr = lambda * (n < Cap);
75 if n > 0
76 Q(idx, idx - 1) = dep;
77 end
78 if n < Cap
79 Q(idx, idx + 1) = arr;
80 end
81 Q(idx, idx) = -(dep + arr);
82 end
83 else
84 % M/PH/1/N: level 0 is 1-dim, levels 1..Cap are nPhases-dim
85 dim = 1 + Cap * nPhases;
86 Q = zeros(dim, dim);
87
88 % Level 0
89 Q(1, 1) = -lambda;
90 Q(1, 2:1+nPhases) = lambda * alpha;
91
92 for n = 1:Cap
93 rows = (1 + (n-1)*nPhases + 1):(1 + n*nPhases);
94
95 % Internal transitions (D0) and arrivals
96 if n < Cap
97 Q(rows, rows) = D0 - lambda * eye(nPhases);
98 else
99 Q(rows, rows) = D0;
100 end
101
102 % Arrivals: level n -> n+1
103 if n < Cap
104 next_rows = rows + nPhases;
105 Q(rows, next_rows) = lambda * eye(nPhases);
106 end
107
108 % Departures: level n -> n-1
109 if n == 1
110 Q(rows, 1) = t_exit;
111 else
112 prev_rows = rows - nPhases;
113 Q(rows, prev_rows) = D1;
114 end
115 end
116 end
117
118 % Time grid
119 nTimePoints = min(101, max(11, round(T_duration * 10)));
120 times = linspace(T_start, T_end, nTimePoints)';
121 dt = T_duration / (nTimePoints - 1);
122
123 % Initial distribution: empty queue
124 pi0 = zeros(1, dim);
125 pi0(1) = 1;
126
127 % Compute transient distributions via iterative expm
128 eQdt = expm(Q * dt);
129
130 queue_lengths = zeros(nTimePoints, 1);
131 util_values = zeros(nTimePoints, 1);
132 tput_values = zeros(nTimePoints, 1);
133
134 pi_t = pi0;
135 for t_idx = 1:nTimePoints
136 % Extract metrics from pi_t
137 q = 0; u = 0; tput = 0;
138 for n = 0:Cap
139 if ~isPH
140 p_n = pi_t(n + 1);
141 else
142 if n == 0
143 p_n = pi_t(1);
144 else
145 idx_s = 1 + (n-1)*nPhases + 1;
146 idx_e = 1 + n*nPhases;
147 p_n = sum(pi_t(idx_s:idx_e));
148 end
149 end
150 q = q + n * p_n;
151 if n >= 1
152 u = u + (min(n, nServers) / nServers) * p_n;
153 if ~isPH
154 tput = tput + min(n, nServers) * mu * p_n;
155 else
156 idx_s = 1 + (n-1)*nPhases + 1;
157 idx_e = 1 + n*nPhases;
158 tput = tput + pi_t(idx_s:idx_e) * t_exit;
159 end
160 end
161 end
162 queue_lengths(t_idx) = q;
163 util_values(t_idx) = u;
164 tput_values(t_idx) = tput;
165
166 % Advance to next time point
167 if t_idx < nTimePoints
168 pi_t = pi_t * eQdt;
169 end
170 end
171
172else
173 %% Infinite capacity: use libQBD adaptive Taylor series
174 proc = libqbd.QBD();
175
176 if ~isPH
177 % M/M/c: scalar QBD levels
178 % Level 0: boundary
179 proc.add_zero_level(-lambda, lambda);
180
181 % Levels 1..c-1: boundary (service rate = n*mu)
182 for n = 1:nServers-1
183 dep = n * mu;
184 proc.add_level(dep, -(lambda + dep), lambda);
185 end
186
187 % Repeating level c+: service rate = c*mu
188 dep = nServers * mu;
189 proc.add_final_level(dep, -(lambda + dep));
190 else
191 % M/PH/1: level 0 scalar, levels 1+ have nPhases phases
192 proc.add_zero_level(-lambda, lambda * alpha);
193
194 % Level 1: boundary (A_minus is m x 1, back to scalar level 0)
195 A1_minus = t_exit;
196 A1_0 = D0 - lambda * eye(nPhases);
197 A1_plus = lambda * eye(nPhases);
198 proc.add_level(A1_minus, A1_0, A1_plus);
199
200 % Repeating level 2+: A_minus = D1 (m x m)
201 proc.add_final_level(D1, D0 - lambda * eye(nPhases));
202 end
203
204 % Solve using libQBD adaptive Taylor series
205 tol = options.tol;
206 solver = libqbd.TaylorSeriesAdaptive(proc, {1}, tol, T_duration);
207
208 ref_times = solver.get_reference_times() + T_start;
209 ref_dists = solver.get_reference_dists();
210 nTimePoints = numel(ref_times);
211 times = ref_times(:);
212
213 queue_lengths = zeros(nTimePoints, 1);
214 util_values = zeros(nTimePoints, 1);
215 tput_values = zeros(nTimePoints, 1);
216
217 for t_idx = 1:nTimePoints
218 dist = ref_dists{t_idx};
219 nLevels = numel(dist);
220
221 q = 0; u = 0; tput = 0;
222 for n = 1:nLevels-1
223 p_n = sum(dist{n + 1});
224 q = q + n * p_n;
225 u = u + (min(n, nServers) / nServers) * p_n;
226 if ~isPH
227 tput = tput + min(n, nServers) * mu * p_n;
228 else
229 tput = tput + dist{n + 1} * t_exit;
230 end
231 end
232 queue_lengths(t_idx) = q;
233 util_values(t_idx) = u;
234 tput_values(t_idx) = tput;
235 end
236end
237
238%% Package results in [metric, time] format
239Qt = cell(M, K);
240Ut = cell(M, K);
241Tt = cell(M, K);
242
243% Queue station
244Qt{queueIdx, 1} = [queue_lengths, times];
245Ut{queueIdx, 1} = [util_values, times];
246Tt{queueIdx, 1} = [tput_values, times];
247
248end