LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_ba_qrf_analyzer.m
1function [QN,UN,RN,TN,CN,XN,runtime] = solver_ba_qrf_analyzer(sn, options)
2% SOLVER_BA_QRF_ANALYZER Adapter for QRF library functions within SolverBA
3%
4% [QN,UN,RN,TN,CN,XN,RUNTIME] = SOLVER_BA_QRF_ANALYZER(SN, OPTIONS)
5%
6% Bridges the LINE sn struct to QRF (Quadratic Reduction Framework) library
7% functions for approximating performance metrics of single-class closed
8% queueing networks with PH service.
9%
10% Copyright (c) 2012-2026, Imperial College London
11% All rights reserved.
12
13Tstart = tic;
14
15M = sn.nstations;
16K = sn.nclasses;
17N = sum(sn.njobs);
18S = sn.nservers;
19PH = sn.proc;
20
21% QRF only supports single-class closed networks
22if K ~= 1
23 line_error(mfilename, 'QRF methods only support single-class networks (found %d classes).', K);
24end
25if any(isinf(sn.njobs))
26 line_error(mfilename, 'QRF methods only support closed networks.');
27end
28
29% Extract MAPs as cell array: MAPs{i} = {D0, D1}
30MAPs = cell(M, 1);
31K_phases = zeros(M, 1);
32for i = 1:M
33 if ~isempty(PH{i}{1})
34 MAPs{i} = PH{i}{1};
35 K_phases(i) = size(MAPs{i}{1}, 1);
36 else
37 K_phases(i) = 1;
38 MAPs{i} = {-1, 1}; % fallback exponential rate 1
39 end
40end
41
42% Build routing matrix (M x M) from sn.rt (MK x MK)
43rt = zeros(M);
44for i = 1:M
45 for j = 1:M
46 rt(i,j) = sn.rt(i, j); % K=1, so indexing is direct
47 end
48end
49
50% Extract mu and v arrays from MAPs
51Kmax = max(K_phases);
52mu = zeros(M, Kmax, Kmax);
53v = zeros(M, Kmax, Kmax);
54for i = 1:M
55 D0 = MAPs{i}{1};
56 D1 = MAPs{i}{2};
57 for h = 1:K_phases(i)
58 for k = 1:K_phases(i)
59 % Both mu and v are indexed (from phase, to phase), matching
60 % qrf_noblo_*'s q{i,j}(k,h) = v{i}(k,h) + r(i,i)*mu{i}(k,h).
61 % mu{i}(k,h) is the completion rate from phase k leaving to phase
62 % h, i.e. D1(k,h); v{i}(k,h) is the background phase change k -> h,
63 % i.e. D0(k,h) off the diagonal. Writing v as D0(h,k) transposes
64 % it, which is invisible for a reversible D0 but reverses the phase
65 % order of an Erlang (upper bidiagonal D0) and silently changes the
66 % model.
67 mu(i, h, k) = D1(h, k);
68 if h == k
69 v(i, h, k) = 0;
70 else
71 v(i, h, k) = D0(h, k);
72 end
73 end
74 end
75end
76
77% Dispatch based on method
78switch options.method
79 case 'qrf.mmi'
80 MR = 1;
81 [UN_qrf, QN_qrf] = qrf_noblo_mmi(M, MR, K_phases(:)', N, mu, v, rt);
82
83 case 'qrf.mem'
84 [UN_qrf, QN_qrf] = qrf_noblo_mem(MAPs, N, rt);
85
86 case 'qrf.mmi.ld'
87 if isfield(options.config, 'qrf_alpha') && ~isempty(options.config.qrf_alpha)
88 alpha = options.config.qrf_alpha;
89 else
90 alpha = ones(M, N);
91 end
92 [UN_qrf, QN_qrf] = qrf_noblo_mmi_ld(MAPs, N, rt, alpha);
93
94 case 'qrf.mmi.linear'
95 if isfield(options.config, 'qrf_alpha') && ~isempty(options.config.qrf_alpha)
96 alpha = options.config.qrf_alpha;
97 else
98 alpha = ones(M, N);
99 end
100 [UN_qrf, QN_qrf] = qrf_noblo_mmi_linear(MAPs, N, rt, alpha);
101
102 case 'qrf.bas.mmi'
103 qp = options.config.qrf_params;
104 if isempty(qp)
105 line_error(mfilename, 'qrf.bas.mmi requires options.config.qrf_params with fields: f, MR, BB, F.');
106 end
107 f = qp.f;
108 MR = qp.MR;
109 BB = qp.BB;
110 F = qp.F;
111 [UN_qrf, QN_qrf] = qrf_bas_mmi_simple(f, M, MR, BB, K_phases(:)', F, N, mu, v, rt);
112
113 case 'qrf.bas.mem'
114 qp = options.config.qrf_params;
115 if isempty(qp)
116 line_error(mfilename, 'qrf.bas.mem requires options.config.qrf_params with fields: f, MR, MM, MM1, ZZ, ZM, BB, F.');
117 end
118 f = qp.f;
119 MR = qp.MR;
120 MM = qp.MM;
121 MM1 = qp.MM1;
122 ZZ = qp.ZZ;
123 ZM = qp.ZM;
124 BB = qp.BB;
125 F = qp.F;
126 [UN_qrf, QN_qrf] = qrf_bas_mem(f, M, MR, MM, MM1, ZZ, ZM, BB, K_phases(:)', F, N, mu, v, rt);
127
128 case 'qrf.bas'
129 params = sn_to_qrf_params(sn, MAPs, K_phases, N, mu, v, rt, options);
130 params.verbose = options.verbose > 0;
131 [result] = qrf_bas(params);
132 % qrf_bas returns utilization bounds; derive QN via visit ratios
133 [UN_qrf, QN_qrf] = derive_qn_from_bounds(result.U(:)', M, N, S, PH, sn);
134
135 case 'qrf.rsrd'
136 params = sn_to_qrf_params(sn, MAPs, K_phases, N, mu, v, rt, options);
137 params.verbose = options.verbose > 0;
138 [result] = qrf_rsrd(params);
139 [UN_qrf, QN_qrf] = derive_qn_from_bounds(result.U(:)', M, N, S, PH, sn);
140
141 otherwise
142 line_error(mfilename, 'Unknown QRF method: %s', options.method);
143end
144
145% Normalize QN to population constraint
146if sum(QN_qrf) > 0
147 QN_qrf = QN_qrf / sum(QN_qrf) * N;
148end
149
150% Map 1D QRF results to M x K matrices (K=1)
151UN = zeros(M, K);
152QN = zeros(M, K);
153TN = zeros(M, K);
154RN = zeros(M, K);
155XN = zeros(1, K);
156CN = zeros(1, K);
157
158QN(:, 1) = QN_qrf(:);
159
160% Derive all metrics from the QRF utilization using Little's law and visit
161% ratios.
162%
163% UN_qrf(i) is sum_{ni>=1, ki} p2(i,ni,ki,i,ni,ki), i.e. P(n_i >= 1)
164% marginalised over phase. That IS the utilization and it is bounded by 1
165% through the ONE constraint. An earlier comment here held that it "is the sum
166% of phase effective utilizations, which can exceed 1 for phase-type service"; that
167% is false, and what exceeds 1 is the sum ACROSS stations, which is expected.
168% Adjudicated against glpsol on the paper's AMPL model at K=2 (2-station
169% closed, Exp + Erlang-2): every UN_qrf entry lands inside the glpsol
170% [min,max] range for that station's utilization.
171%
172% Throughput therefore comes from a finite-server station via
173% U_i = X * V_i * stime_i, which is exact for a single server. The previous
174% inversion X = QN(refstat) / (V*stime_ref) instead assumed R == stime at the
175% reference station, which holds only for an infinite server; with a FCFS
176% reference station it returned X above the bottleneck capacity and hence
177% UN > 1. UN_qrf(i)/(V_i*stime_i) is consistent across stations, so any
178% finite-server station determines X.
179
180% Compute visit ratios
181if isfield(sn, 'visits') && ~isempty(sn.visits)
182 V = cellsum(sn.visits);
183else
184 [visits] = sn_refresh_visits(sn, sn.chains, sn.rt, sn.rtnodes);
185 V = cellsum(visits);
186end
187
188refstat = sn.refstat(1);
189
190% Per-station mean service times
191stimes = zeros(1, M);
192for i = 1:M
193 if ~isempty(PH{i}{1})
194 stimes(i) = map_mean(PH{i}{1});
195 end
196end
197
198% Find system throughput XN from the QRF utilization at a finite-server
199% station: U_i = XN * V(i,1) * stime_i / S(i), exact for a single server.
200UN_qrf = UN_qrf(:)';
201for i = 1:M
202 if ~isinf(S(i)) && stimes(i) > 0 && V(i, 1) > 0 && UN_qrf(i) > 0
203 XN(1) = UN_qrf(i) * S(i) / (V(i, 1) * stimes(i));
204 break;
205 end
206end
207if XN(1) == 0
208 % No finite-server station carries load: fall back to the reference
209 % station, where QN = XN * V * stime holds exactly for a delay.
210 if stimes(refstat) > 0 && V(refstat, 1) > 0
211 XN(1) = QN(refstat, 1) / (V(refstat, 1) * stimes(refstat));
212 end
213end
214
215% Derive per-station metrics from XN and visit ratios
216for i = 1:M
217 if ~isempty(PH{i}{1})
218 stime = stimes(i);
219 TN(i, 1) = XN(1) * V(i, 1);
220 if isinf(S(i))
221 % Delay (infinite server): UN = QN by LINE convention
222 UN(i, 1) = QN(i, 1);
223 else
224 % Finite server: the QRF utilization directly
225 if stime > 0
226 UN(i, 1) = UN_qrf(i);
227 end
228 end
229 % Response time via Little's law
230 if TN(i, 1) > 0
231 RN(i, 1) = QN(i, 1) / TN(i, 1);
232 end
233 end
234end
235
236% Cycle time
237if XN(1) > 0
238 CN(1) = N / XN(1);
239end
240
241% Clean NaN values
242QN(isnan(QN)) = 0;
243UN(isnan(UN)) = 0;
244RN(isnan(RN)) = 0;
245TN(isnan(TN)) = 0;
246XN(isnan(XN)) = 0;
247CN(isnan(CN)) = 0;
248
249runtime = toc(Tstart);
250end
251
252function params = sn_to_qrf_params(sn, MAPs, K_phases, N, mu, v, rt, options)
253% Build params struct for qrf_bas / qrf_rsrd from sn struct
254M = sn.nstations;
255params = struct();
256params.M = M;
257params.N = N;
258params.K = K_phases(:);
259params.r = rt;
260
261% Convert mu/v from 3D arrays to cell arrays expected by qrf_bas/qrf_rsrd
262% Each mu{i}, v{i} must be a [Ki x Ki] matrix (even for Ki=1)
263params.mu = cell(M, 1);
264params.v = cell(M, 1);
265for i = 1:M
266 Ki = K_phases(i);
267 params.mu{i} = reshape(mu(i, 1:Ki, 1:Ki), Ki, Ki);
268 params.v{i} = reshape(v(i, 1:Ki, 1:Ki), Ki, Ki);
269end
270
271% Capacity
272params.F = zeros(M, 1);
273for i = 1:M
274 if isfield(sn, 'cap') && ~isempty(sn.cap)
275 params.F(i) = sn.cap(i);
276 if isinf(params.F(i))
277 params.F(i) = N;
278 end
279 else
280 params.F(i) = N;
281 end
282end
283
284% Blocking parameters from options or auto-infer
285qp = options.config.qrf_params;
286if ~isempty(qp)
287 % Validate required fields for BAS blocking configuration
288 if strcmp(options.method, 'qrf.bas')
289 required_bas = {'f', 'MR', 'BB', 'MM', 'MM1', 'ZZ', 'ZM'};
290 for idx = 1:length(required_bas)
291 if ~isfield(qp, required_bas{idx})
292 line_error(mfilename, 'qrf_params must contain field ''%s'' for qrf.bas method.', required_bas{idx});
293 end
294 end
295 end
296 if isfield(qp, 'f'), params.f = qp.f; end
297 if isfield(qp, 'MR'), params.MR = qp.MR; end
298 if isfield(qp, 'BB'), params.BB = qp.BB; end
299 if isfield(qp, 'MM'), params.MM = qp.MM; end
300 if isfield(qp, 'ZZ'), params.ZZ = qp.ZZ; end
301 if isfield(qp, 'ZM'), params.ZM = qp.ZM; end
302 if isfield(qp, 'MM1'), params.MM1 = qp.MM1; end
303else
304 if strcmp(options.method, 'qrf.bas')
305 line_warning(mfilename, 'qrf_params not provided for qrf.bas; using no-blocking defaults (MR=1).');
306 end
307 % Default: no blocking
308 params.f = 1;
309 params.MR = 1;
310 params.BB = zeros(1, M);
311 params.MM = zeros(1, 2);
312 params.ZZ = 0;
313 params.ZM = 0;
314 params.MM1 = zeros(1, M);
315end
316
317% Load-dependent alpha
318if isfield(options.config, 'qrf_alpha') && ~isempty(options.config.qrf_alpha)
319 alpha_mat = options.config.qrf_alpha;
320 params.alpha = cell(M, 1);
321 for i = 1:M
322 params.alpha{i} = alpha_mat(i, :)';
323 end
324end
325end
326
327function [UN_qrf, QN_qrf] = derive_qn_from_bounds(U_bounds, M, N, S, PH, sn)
328% Derive QN from utilization bounds using visit ratios and Little's law.
329% For bounds methods (qrf.bas, qrf.rsrd), only utilization is returned.
330% We compute system throughput from the utilization at queue stations,
331% then derive QN at all stations via Little's law.
332UN_qrf = U_bounds;
333
334% Compute visit ratios
335if isfield(sn, 'visits') && ~isempty(sn.visits)
336 V = cellsum(sn.visits);
337else
338 [visits] = sn_refresh_visits(sn, sn.chains, sn.rt, sn.rtnodes);
339 V = cellsum(visits);
340end
341
342% Find XN from the first finite-server station with nonzero utilization
343XN_est = 0;
344for i = 1:M
345 if ~isinf(S(i)) && ~isempty(PH{i}{1}) && U_bounds(i) > 0 && V(i,1) > 0
346 stime = map_mean(PH{i}{1});
347 if stime > 0
348 % UN = XN * V(i) * stime / S(i), so XN = UN * S(i) / (V(i) * stime)
349 XN_est = U_bounds(i) * S(i) / (V(i, 1) * stime);
350 break;
351 end
352 end
353end
354
355% Derive QN at each station: QN(i) = XN * V(i) * RN(i)
356% For single-server queues: RN(i) >= stime(i) (at least one service time)
357% Use Little's law: QN(i) = TN(i) * RN(i) where TN(i) = XN * V(i)
358QN_qrf = zeros(1, M);
359for i = 1:M
360 if ~isempty(PH{i}{1})
361 stime = map_mean(PH{i}{1});
362 TN_i = XN_est * V(i, 1);
363 if isinf(S(i))
364 % Delay: QN = TN * stime
365 QN_qrf(i) = TN_i * stime;
366 UN_qrf(i) = QN_qrf(i); % LINE convention for INF server
367 else
368 % Queue: QN = TN * stime / (1 - U) for M/G/1-like estimate
369 if U_bounds(i) < 1
370 QN_qrf(i) = TN_i * stime / (1 - U_bounds(i));
371 else
372 QN_qrf(i) = N; % saturated
373 end
374 end
375 end
376end
377
378% Rescale to population constraint
379if sum(QN_qrf) > 0
380 QN_qrf = QN_qrf / sum(QN_qrf) * N;
381end
382end
Definition Station.m:245