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
4% [QN,UN,RN,TN,CN,XN,RUNTIME] = SOLVER_CTMC_QRF_ANALYZER(SN, OPTIONS)
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.
10% Copyright (c) 2012-2026, Imperial College London
21% QRF only supports single-
class closed networks
23 line_error(mfilename,
'QRF methods only support single-class networks (found %d classes).', K);
25if any(isinf(sn.njobs))
26 line_error(mfilename, 'QRF methods only support closed networks.');
29% Extract MAPs as cell array: MAPs{i} = {D0, D1}
31K_phases = zeros(M, 1);
35 K_phases(i) = size(MAPs{i}{1}, 1);
38 MAPs{i} = {-1, 1}; % fallback exponential rate 1
42% Build routing matrix (M x M) from sn.rt (MK x MK)
46 rt(i,j) = sn.rt(i, j); % K=1, so indexing
is direct
50% Extract mu and v arrays from MAPs
52mu = zeros(M, Kmax, Kmax);
53v = zeros(M, Kmax, Kmax);
59 mu(i, h, k) = D1(h, k);
63 v(i, k, h) = D0(h, k);
69% Dispatch based on method
73 [UN_qrf, QN_qrf] = qrf_noblo_mmi(M, MR, K_phases(:)
', N, mu, v, rt);
76 [UN_qrf, QN_qrf] = qrf_noblo_mem(MAPs, N, rt);
79 if isfield(options.config, 'qrf_alpha
') && ~isempty(options.config.qrf_alpha)
80 alpha = options.config.qrf_alpha;
84 [UN_qrf, QN_qrf] = qrf_noblo_mmi_ld(MAPs, N, rt, alpha);
87 if isfield(options.config, 'qrf_alpha
') && ~isempty(options.config.qrf_alpha)
88 alpha = options.config.qrf_alpha;
92 [UN_qrf, QN_qrf] = qrf_noblo_mmi_linear(MAPs, N, rt, alpha);
95 qp = options.config.qrf_params;
97 line_error(mfilename, 'qrf.bas.mmi requires options.config.qrf_params with fields: f, MR, BB, F.
');
103 [UN_qrf, QN_qrf] = qrf_bas_mmi_simple(f, M, MR, BB, K_phases(:)', F, N, mu, v, rt);
106 qp = options.config.qrf_params;
108 line_error(mfilename,
'qrf.bas.mem requires options.config.qrf_params with fields: f, MR, MM, MM1, ZZ, ZM, BB, F.');
118 [UN_qrf, QN_qrf] = qrf_bas_mem(f, M, MR, MM, MM1, ZZ, ZM, BB, K_phases(:)
', F, N, mu, v, rt);
121 params = sn_to_qrf_params(sn, MAPs, K_phases, N, mu, v, rt, options);
122 [result] = qrf_bas(params);
123 % qrf_bas returns utilization bounds; derive QN via visit ratios
124 [UN_qrf, QN_qrf] = derive_qn_from_bounds(result.U(:)', M, N, S, PH, sn);
127 params = sn_to_qrf_params(sn, MAPs, K_phases, N, mu, v, rt, options);
128 [result] = qrf_rsrd(params);
129 [UN_qrf, QN_qrf] = derive_qn_from_bounds(result.U(:)
', M, N, S, PH, sn);
132 line_error(mfilename, 'Unknown QRF method: %s
', options.method);
135% Normalize QN to population constraint
137 QN_qrf = QN_qrf / sum(QN_qrf) * N;
140% Map 1D QRF results to M x K matrices (K=1)
150% Derive all metrics from QN using Little's law and visit ratios.
151% QRF
's "UN" is the sum of phase effective utilizations, which can exceed 1
152% for multi-phase PH service; it does not match LINE's utilization definition.
153% Instead, we derive throughput from QN at a delay station (if present) or
154% from the visit-ratio weighted relationship, then compute UN = TN * stime / S.
156% Compute visit ratios
157if isfield(sn,
'visits') && ~isempty(sn.
visits)
160 [
visits] = sn_refresh_visits(sn, sn.chains, sn.rt, sn.rtnodes);
164refstat = sn.refstat(1);
166% Find system throughput XN from QN
using Little
's law at reference station
168if ~isempty(PH{refstat}{1})
169 stime_ref = map_mean(PH{refstat}{1});
171if stime_ref > 0 && V(refstat, 1) > 0
172 % QN(refstat) = XN * V(refstat,1) * stime_ref (for INF server at delay)
173 % QN(refstat) = TN(refstat) * RN(refstat) in general
174 % For delay: RN = stime, so QN = TN * stime = XN * V(refstat,1) * stime
175 XN(1) = QN(refstat, 1) / (V(refstat, 1) * stime_ref);
178% Derive per-station metrics from XN and visit ratios
180 if ~isempty(PH{i}{1})
181 stime = map_mean(PH{i}{1});
182 TN(i, 1) = XN(1) * V(i, 1);
184 % Delay (infinite server): UN = QN by LINE convention
187 % Finite server: UN = TN * stime / S
189 UN(i, 1) = TN(i, 1) * stime / S(i);
192 % Response time via Little's law
194 RN(i, 1) = QN(i, 1) / TN(i, 1);
212runtime = toc(Tstart);
215function params = sn_to_qrf_params(sn, MAPs, K_phases, N, mu, v, rt, options)
216% Build params
struct for qrf_bas / qrf_rsrd from sn struct
221params.K = K_phases(:);
224% Convert mu/v from 3D arrays to cell arrays expected by qrf_bas/qrf_rsrd
225% Each mu{i}, v{i} must be a [Ki x Ki] matrix (even
for Ki=1)
226params.mu = cell(M, 1);
227params.v = cell(M, 1);
230 params.mu{i} = reshape(mu(i, 1:Ki, 1:Ki), Ki, Ki);
231 params.v{i} = reshape(v(i, 1:Ki, 1:Ki), Ki, Ki);
235params.F = zeros(M, 1);
237 if isfield(sn,
'cap') && ~isempty(sn.cap)
238 params.F(i) = sn.cap(i);
239 if isinf(params.F(i))
247% Blocking parameters from options or auto-infer
248qp = options.config.qrf_params;
250 % Validate required fields for BAS blocking configuration
251 if strcmp(options.method, 'qrf.bas')
252 required_bas = {
'f',
'MR',
'BB',
'MM',
'MM1',
'ZZ',
'ZM'};
253 for idx = 1:length(required_bas)
254 if ~isfield(qp, required_bas{idx})
255 line_error(mfilename,
'qrf_params must contain field ''%s'' for qrf.bas method.', required_bas{idx});
259 if isfield(qp,
'f'), params.f = qp.f; end
260 if isfield(qp,
'MR'), params.MR = qp.MR; end
261 if isfield(qp,
'BB'), params.BB = qp.BB; end
262 if isfield(qp,
'MM'), params.MM = qp.MM; end
263 if isfield(qp,
'ZZ'), params.ZZ = qp.ZZ; end
264 if isfield(qp,
'ZM'), params.ZM = qp.ZM; end
265 if isfield(qp,
'MM1'), params.MM1 = qp.MM1; end
267 if strcmp(options.method,
'qrf.bas')
268 line_warning(mfilename, 'qrf_params not provided for qrf.bas; using no-blocking defaults (MR=1).');
270 % Default: no blocking
273 params.BB = zeros(1, M);
274 params.MM = zeros(1, 2);
277 params.MM1 = zeros(1, M);
280% Load-dependent alpha
281if isfield(options.config, 'qrf_alpha') && ~isempty(options.config.qrf_alpha)
282 alpha_mat = options.config.qrf_alpha;
283 params.alpha = cell(M, 1);
285 params.alpha{i} = alpha_mat(i, :)
';
290function [UN_qrf, QN_qrf] = derive_qn_from_bounds(U_bounds, M, N, S, PH, sn)
291% Derive QN from utilization bounds using visit ratios and Little's law.
292% For bounds methods (qrf.bas, qrf.rsrd), only utilization
is returned.
293% We compute system throughput from the utilization at queue stations,
294% then derive QN at all stations via Little
's law.
297% Compute visit ratios
298if isfield(sn, 'visits') && ~isempty(sn.visits)
299 V = cellsum(sn.visits);
301 [visits] = sn_refresh_visits(sn, sn.chains, sn.rt, sn.rtnodes);
305% Find XN from the first finite-server station with nonzero utilization
308 if ~isinf(S(i)) && ~isempty(PH{i}{1}) && U_bounds(i) > 0 && V(i,1) > 0
309 stime = map_mean(PH{i}{1});
311 % UN = XN * V(i) * stime / S(i), so XN = UN * S(i) / (V(i) * stime)
312 XN_est = U_bounds(i) * S(i) / (V(i, 1) * stime);
318% Derive QN at each station: QN(i) = XN * V(i) * RN(i)
319% For single-server queues: RN(i) >= stime(i) (at least one service time)
320% Use Little's law: QN(i) = TN(i) * RN(i) where TN(i) = XN * V(i)
323 if ~isempty(PH{i}{1})
324 stime = map_mean(PH{i}{1});
325 TN_i = XN_est * V(i, 1);
327 % Delay: QN = TN * stime
328 QN_qrf(i) = TN_i * stime;
329 UN_qrf(i) = QN_qrf(i); % LINE convention
for INF server
331 % Queue: QN = TN * stime / (1 - U)
for M/G/1-like estimate
333 QN_qrf(i) = TN_i * stime / (1 - U_bounds(i));
335 QN_qrf(i) = N; % saturated
341% Rescale to population constraint
343 QN_qrf = QN_qrf / sum(QN_qrf) * N;