LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_ctmc_qrf_analyzer.m
1function [QN,UN,RN,TN,CN,XN,runtime] = solver_ctmc_qrf_analyzer(sn, options)
2% SOLVER_CTMC_QRF_ANALYZER Adapter for QRF library functions within SolverCTMC
3%
4% [QN,UN,RN,TN,CN,XN,RUNTIME] = SOLVER_CTMC_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 mu(i, h, k) = D1(h, k);
60 if h == k
61 v(i, k, h) = 0;
62 else
63 v(i, k, h) = D0(h, k);
64 end
65 end
66 end
67end
68
69% Dispatch based on method
70switch options.method
71 case 'qrf.mmi'
72 MR = 1;
73 [UN_qrf, QN_qrf] = qrf_noblo_mmi(M, MR, K_phases(:)', N, mu, v, rt);
74
75 case 'qrf.mem'
76 [UN_qrf, QN_qrf] = qrf_noblo_mem(MAPs, N, rt);
77
78 case 'qrf.mmi.ld'
79 if isfield(options.config, 'qrf_alpha') && ~isempty(options.config.qrf_alpha)
80 alpha = options.config.qrf_alpha;
81 else
82 alpha = ones(M, N);
83 end
84 [UN_qrf, QN_qrf] = qrf_noblo_mmi_ld(MAPs, N, rt, alpha);
85
86 case 'qrf.mmi.linear'
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_linear(MAPs, N, rt, alpha);
93
94 case 'qrf.bas.mmi'
95 qp = options.config.qrf_params;
96 if isempty(qp)
97 line_error(mfilename, 'qrf.bas.mmi requires options.config.qrf_params with fields: f, MR, BB, F.');
98 end
99 f = qp.f;
100 MR = qp.MR;
101 BB = qp.BB;
102 F = qp.F;
103 [UN_qrf, QN_qrf] = qrf_bas_mmi_simple(f, M, MR, BB, K_phases(:)', F, N, mu, v, rt);
104
105 case 'qrf.bas.mem'
106 qp = options.config.qrf_params;
107 if isempty(qp)
108 line_error(mfilename, 'qrf.bas.mem requires options.config.qrf_params with fields: f, MR, MM, MM1, ZZ, ZM, BB, F.');
109 end
110 f = qp.f;
111 MR = qp.MR;
112 MM = qp.MM;
113 MM1 = qp.MM1;
114 ZZ = qp.ZZ;
115 ZM = qp.ZM;
116 BB = qp.BB;
117 F = qp.F;
118 [UN_qrf, QN_qrf] = qrf_bas_mem(f, M, MR, MM, MM1, ZZ, ZM, BB, K_phases(:)', F, N, mu, v, rt);
119
120 case 'qrf.bas'
121 params = sn_to_qrf_params(sn, MAPs, K_phases, N, mu, v, rt, options);
122 params.verbose = options.verbose > 0;
123 [result] = qrf_bas(params);
124 % qrf_bas returns utilization bounds; derive QN via visit ratios
125 [UN_qrf, QN_qrf] = derive_qn_from_bounds(result.U(:)', M, N, S, PH, sn);
126
127 case 'qrf.rsrd'
128 params = sn_to_qrf_params(sn, MAPs, K_phases, N, mu, v, rt, options);
129 params.verbose = options.verbose > 0;
130 [result] = qrf_rsrd(params);
131 [UN_qrf, QN_qrf] = derive_qn_from_bounds(result.U(:)', M, N, S, PH, sn);
132
133 otherwise
134 line_error(mfilename, 'Unknown QRF method: %s', options.method);
135end
136
137% Normalize QN to population constraint
138if sum(QN_qrf) > 0
139 QN_qrf = QN_qrf / sum(QN_qrf) * N;
140end
141
142% Map 1D QRF results to M x K matrices (K=1)
143UN = zeros(M, K);
144QN = zeros(M, K);
145TN = zeros(M, K);
146RN = zeros(M, K);
147XN = zeros(1, K);
148CN = zeros(1, K);
149
150QN(:, 1) = QN_qrf(:);
151
152% Derive all metrics from QN using Little's law and visit ratios.
153% QRF's "UN" is the sum of phase effective utilizations, which can exceed 1
154% for multi-phase PH service; it does not match LINE's utilization definition.
155% Instead, we derive throughput from QN at a delay station (if present) or
156% from the visit-ratio weighted relationship, then compute UN = TN * stime / S.
157
158% Compute visit ratios
159if isfield(sn, 'visits') && ~isempty(sn.visits)
160 V = cellsum(sn.visits);
161else
162 [visits] = sn_refresh_visits(sn, sn.chains, sn.rt, sn.rtnodes);
163 V = cellsum(visits);
164end
165
166refstat = sn.refstat(1);
167
168% Find system throughput XN from QN using Little's law at reference station
169stime_ref = 0;
170if ~isempty(PH{refstat}{1})
171 stime_ref = map_mean(PH{refstat}{1});
172end
173if stime_ref > 0 && V(refstat, 1) > 0
174 % QN(refstat) = XN * V(refstat,1) * stime_ref (for INF server at delay)
175 % QN(refstat) = TN(refstat) * RN(refstat) in general
176 % For delay: RN = stime, so QN = TN * stime = XN * V(refstat,1) * stime
177 XN(1) = QN(refstat, 1) / (V(refstat, 1) * stime_ref);
178end
179
180% Derive per-station metrics from XN and visit ratios
181for i = 1:M
182 if ~isempty(PH{i}{1})
183 stime = map_mean(PH{i}{1});
184 TN(i, 1) = XN(1) * V(i, 1);
185 if isinf(S(i))
186 % Delay (infinite server): UN = QN by LINE convention
187 UN(i, 1) = QN(i, 1);
188 else
189 % Finite server: UN = TN * stime / S
190 if stime > 0
191 UN(i, 1) = TN(i, 1) * stime / S(i);
192 end
193 end
194 % Response time via Little's law
195 if TN(i, 1) > 0
196 RN(i, 1) = QN(i, 1) / TN(i, 1);
197 end
198 end
199end
200
201% Cycle time
202if XN(1) > 0
203 CN(1) = N / XN(1);
204end
205
206% Clean NaN values
207QN(isnan(QN)) = 0;
208UN(isnan(UN)) = 0;
209RN(isnan(RN)) = 0;
210TN(isnan(TN)) = 0;
211XN(isnan(XN)) = 0;
212CN(isnan(CN)) = 0;
213
214runtime = toc(Tstart);
215end
216
217function params = sn_to_qrf_params(sn, MAPs, K_phases, N, mu, v, rt, options)
218% Build params struct for qrf_bas / qrf_rsrd from sn struct
219M = sn.nstations;
220params = struct();
221params.M = M;
222params.N = N;
223params.K = K_phases(:);
224params.r = rt;
225
226% Convert mu/v from 3D arrays to cell arrays expected by qrf_bas/qrf_rsrd
227% Each mu{i}, v{i} must be a [Ki x Ki] matrix (even for Ki=1)
228params.mu = cell(M, 1);
229params.v = cell(M, 1);
230for i = 1:M
231 Ki = K_phases(i);
232 params.mu{i} = reshape(mu(i, 1:Ki, 1:Ki), Ki, Ki);
233 params.v{i} = reshape(v(i, 1:Ki, 1:Ki), Ki, Ki);
234end
235
236% Capacity
237params.F = zeros(M, 1);
238for i = 1:M
239 if isfield(sn, 'cap') && ~isempty(sn.cap)
240 params.F(i) = sn.cap(i);
241 if isinf(params.F(i))
242 params.F(i) = N;
243 end
244 else
245 params.F(i) = N;
246 end
247end
248
249% Blocking parameters from options or auto-infer
250qp = options.config.qrf_params;
251if ~isempty(qp)
252 % Validate required fields for BAS blocking configuration
253 if strcmp(options.method, 'qrf.bas')
254 required_bas = {'f', 'MR', 'BB', 'MM', 'MM1', 'ZZ', 'ZM'};
255 for idx = 1:length(required_bas)
256 if ~isfield(qp, required_bas{idx})
257 line_error(mfilename, 'qrf_params must contain field ''%s'' for qrf.bas method.', required_bas{idx});
258 end
259 end
260 end
261 if isfield(qp, 'f'), params.f = qp.f; end
262 if isfield(qp, 'MR'), params.MR = qp.MR; end
263 if isfield(qp, 'BB'), params.BB = qp.BB; end
264 if isfield(qp, 'MM'), params.MM = qp.MM; end
265 if isfield(qp, 'ZZ'), params.ZZ = qp.ZZ; end
266 if isfield(qp, 'ZM'), params.ZM = qp.ZM; end
267 if isfield(qp, 'MM1'), params.MM1 = qp.MM1; end
268else
269 if strcmp(options.method, 'qrf.bas')
270 line_warning(mfilename, 'qrf_params not provided for qrf.bas; using no-blocking defaults (MR=1).');
271 end
272 % Default: no blocking
273 params.f = 1;
274 params.MR = 1;
275 params.BB = zeros(1, M);
276 params.MM = zeros(1, 2);
277 params.ZZ = 0;
278 params.ZM = 0;
279 params.MM1 = zeros(1, M);
280end
281
282% Load-dependent alpha
283if isfield(options.config, 'qrf_alpha') && ~isempty(options.config.qrf_alpha)
284 alpha_mat = options.config.qrf_alpha;
285 params.alpha = cell(M, 1);
286 for i = 1:M
287 params.alpha{i} = alpha_mat(i, :)';
288 end
289end
290end
291
292function [UN_qrf, QN_qrf] = derive_qn_from_bounds(U_bounds, M, N, S, PH, sn)
293% Derive QN from utilization bounds using visit ratios and Little's law.
294% For bounds methods (qrf.bas, qrf.rsrd), only utilization is returned.
295% We compute system throughput from the utilization at queue stations,
296% then derive QN at all stations via Little's law.
297UN_qrf = U_bounds;
298
299% Compute visit ratios
300if isfield(sn, 'visits') && ~isempty(sn.visits)
301 V = cellsum(sn.visits);
302else
303 [visits] = sn_refresh_visits(sn, sn.chains, sn.rt, sn.rtnodes);
304 V = cellsum(visits);
305end
306
307% Find XN from the first finite-server station with nonzero utilization
308XN_est = 0;
309for i = 1:M
310 if ~isinf(S(i)) && ~isempty(PH{i}{1}) && U_bounds(i) > 0 && V(i,1) > 0
311 stime = map_mean(PH{i}{1});
312 if stime > 0
313 % UN = XN * V(i) * stime / S(i), so XN = UN * S(i) / (V(i) * stime)
314 XN_est = U_bounds(i) * S(i) / (V(i, 1) * stime);
315 break;
316 end
317 end
318end
319
320% Derive QN at each station: QN(i) = XN * V(i) * RN(i)
321% For single-server queues: RN(i) >= stime(i) (at least one service time)
322% Use Little's law: QN(i) = TN(i) * RN(i) where TN(i) = XN * V(i)
323QN_qrf = zeros(1, M);
324for i = 1:M
325 if ~isempty(PH{i}{1})
326 stime = map_mean(PH{i}{1});
327 TN_i = XN_est * V(i, 1);
328 if isinf(S(i))
329 % Delay: QN = TN * stime
330 QN_qrf(i) = TN_i * stime;
331 UN_qrf(i) = QN_qrf(i); % LINE convention for INF server
332 else
333 % Queue: QN = TN * stime / (1 - U) for M/G/1-like estimate
334 if U_bounds(i) < 1
335 QN_qrf(i) = TN_i * stime / (1 - U_bounds(i));
336 else
337 QN_qrf(i) = N; % saturated
338 end
339 end
340 end
341end
342
343% Rescale to population constraint
344if sum(QN_qrf) > 0
345 QN_qrf = QN_qrf / sum(QN_qrf) * N;
346end
347end