1function [Q,U,R,T,C,X,lG,runtime,totiter,actualmethod] = solver_mva_qsys_analyzer(sn, options)
2% [Q,U,R,T,C,X,LG,RUNTIME,ITER,ACTUALMETHOD] = SOLVER_MVA_QSYS_ANALYZER(QN, OPTIONS)
4% Copyright (c) 2012-2026, Imperial College London
10Q = zeros(M,K); U = zeros(M,K);
11R = zeros(M,K); T = zeros(M,K);
12C = zeros(1,K); X = zeros(M,K);
15method = options.method;
16actualmethod = method; % resolved algorithm name, reported in the solver banner
17source_ist = sn.nodeToStation(sn.nodetype == NodeType.Source);
18queue_ist = sn.nodeToStation(sn.nodetype == NodeType.Queue);
19lambda = sn.rates(source_ist)*sn.visits{source_ist}(sn.stationToStateful(queue_ist));
20k = sn.nservers(queue_ist);
21mu = sn.rates(queue_ist);
22ca = sqrt(sn.scv(source_ist));
23cs = sqrt(sn.scv(queue_ist));
25line_debug(
'MVA qsys analyzer starting: method=%s, lambda=%g, mu=%g, k=%d', method, lambda, mu, k);
27% Finite-capacity loss branch (M/M/1/K with tail drop). Uses the moment-based
28% (MacGregor Smith) qsys_mg1k_loss_mgs, exact only at scv=1; queue-length
29% metrics come from the truncated M/M/1/K distribution. Being an approximation
30% in general, it
is not offered under method=
'exact'.
32 if strcmp(method,
'exact')
33 line_error(mfilename, 'M/M/1/K tail-drop
is solved by the approximate ''mg1k.mgs'' method (MacGregor Smith); it
is not available under method=''exact''. Use the default method, or SolverCTMC/SolverNC for an exact result.');
35 Kcap = sn.cap(queue_ist);
37 Ploss = qsys_mg1k_loss_mgs(lambda, mu, cs^2, Kcap);
38 Tq = lambda*(1-Ploss); % carried throughput
39 Uq = Tq/mu; % single-server utilization
41 Lsys = Kcap/2; % L'Hopital limit at rho=1
43 Lsys = rho/(1-rho) - (Kcap+1)*rho^(Kcap+1)/(1-rho^(Kcap+1));
45 Vq = sn.
visits{1}(sn.stationToStateful(queue_ist));
46 R(queue_ist,1) = Lsys/Tq; % per-visit response time (Little)
47 Q(queue_ist,1) = Lsys;
49 T(queue_ist,1) = Tq; % carried (effective) rate
50 T(source_ist,1) = lambda; % offered arrival rate
51 X(queue_ist,1) = Tq; % system throughput = carried rate
52 C(1,1) = R(queue_ist,1)*Vq;
53 actualmethod =
'mg1k.mgs';
54 lG = 0; totiter = 1; runtime = toc(T0);
58% Check
for BMAP arrivals (batch Markovian)
59if sn.procid(source_ist) == ProcessType.BMAP
60 line_debug(
'BMAP arrival process detected');
62 % Check
if service
is exponential
63 if sn.procid(queue_ist) == ProcessType.EXP
64 line_debug(
'Service is exponential, using MX/M/1 queue model');
65 % sn.proc holds {D0, D1, D_batch1, ..., D_batchK} (JAR MatrixCell
66 % layout); batch rates are weighted by the stationary vector of
67 % the underlying CTMC, as in BMAP.getBatchRates
68 proc_src = sn.proc{source_ist};
69 if iscell(proc_src) && ~isempty(proc_src) && iscell(proc_src{1})
70 proc_src = proc_src{1};
72 pie_b = ctmc_solve(proc_src{1} + proc_src{2});
73 nbatch = length(proc_src) - 2;
74 batch_rates = zeros(1, nbatch);
76 batch_rates(bsize) = pie_b * proc_src{2+bsize} * ones(size(proc_src{1},1),1);
78 total_rate = sum(batch_rates);
79 lambda_batch = total_rate; % batch
event rate
81 E_X = sum((1:nbatch) .* batch_rates) / total_rate;
82 E_X2 = sum(((1:nbatch).^2) .* batch_rates) / total_rate;
88 [W, Wq, U_mxm1, Q_mxm1] = qsys_mxm1(lambda_batch, mu, E_X, E_X2);
89 R(queue_ist,1) = W * sn.visits{1}(sn.stationToStateful(queue_ist));
90 C(1,1) = R(queue_ist,1);
91 X(queue_ist,1) = lambda_batch * E_X; % Job arrival rate
92 U(queue_ist,1) = U_mxm1;
93 T(source_ist,1) = lambda_batch * E_X;
94 T(queue_ist,1) = lambda_batch * E_X;
95 Q(queue_ist,1) = Q_mxm1;
98 actualmethod =
'mxm1';
103if strcmpi(method,
'exact')
104 if ca == 1 && cs == 1 && k==1
106 line_debug('Exact method selected: M/M/1 (ca=1, cs=1, k=1)');
107 elseif ca == 1 && cs == 1 && k>1
109 line_debug('Exact method selected: M/M/k (ca=1, cs=1, k=%d)', k);
110 elseif ca == 1 && k==1
112 line_debug('Exact method selected: M/G/1 (ca=1, k=1)');
113 elseif cs == 1 && k==1
115 line_debug('Exact method selected: G/M/1 (cs=1, k=1)');
117 line_error(mfilename,'MVA exact method unavailable for this model.');
123 if ca == 1 && cs == 1 && k == 1
125 line_debug('Default method: using M/M/1 exact solution\n');
126 elseif ca == 1 && cs == 1 && k > 1
128 line_debug('Default method: using M/M/k exact solution (k=%d)\n', k);
129 elseif ca == 1 && k == 1
131 line_debug('Default method: using M/G/1 exact solution\n');
132 elseif cs == 1 && k == 1
134 line_debug('Default method: using G/M/1 exact solution\n');
137 line_debug('Default method: using G/G/k approximation (k=%d)\n', k);
140 line_debug('Default method: using G/G/1 KLB approximation\n');
146 line_debug('Using M/M/1 exact solution');
147 R = qsys_mm1(lambda,mu);
149 line_debug('Using M/M/k exact solution (k=%d)', k);
150 R = qsys_mmk(lambda,mu,k);
152 line_debug(
'Using RQNA (robust queueing) single-queue solution');
153 arvMAP = sn.proc{source_ist}{1};
155 IaFun1 = @(x) map_count_idc(arvMAP, x);
156 [~, W1] = qsys_gig1_rq(rho1, mu, cs^2, IaFun1);
158 case {
'mg1',
'mgi1'} % verified
159 line_debug(
'Using M/G/1 exact solution');
160 R = qsys_mg1(lambda,mu,cs);
162 line_debug(
'Using G/G/k approximation (k=%d)', k);
163 R = qsys_gigk_approx(lambda,mu,ca,cs,k);
164 case {
'gigk.kingman_approx'}
165 line_debug(
'Using G/G/k Kingman approximation (k=%d)', k);
166 R = qsys_gigk_approx_kingman(lambda,mu,ca,cs,k);
167 case 'gig1.kingman' % verified
168 line_debug(
'Using G/G/1 Kingman upper bound');
169 R = qsys_gig1_ubnd_kingman(lambda,mu,ca,cs);
171 line_debug(
'Using G/G/1 Heyman approximation');
172 R = qsys_gig1_approx_heyman(lambda,mu,ca,cs);
173 case {
'gig1',
'gig1.allen'}
174 line_debug(
'Using G/G/1 Allen-Cunneen approximation');
175 R = qsys_gig1_approx_allencunneen(lambda,mu,ca,cs);
176 case 'gig1.kobayashi'
177 line_debug(
'Using G/G/1 Kobayashi approximation');
178 R = qsys_gig1_approx_kobayashi(lambda,mu,ca,cs);
180 line_debug(
'Using G/G/1 KLB approximation');
181 R = qsys_gig1_approx_klb(lambda,mu,ca,cs);
182 case 'gig1.marchal' % verified
183 line_debug(
'Using G/G/1 Marchal approximation');
184 R = qsys_gig1_approx_marchal(lambda,mu,ca,cs);
186 line_debug(
'Using G/M/1 exact solution');
187 mu = sn.rates(queue_ist);
188 % Prefer the exact PH/M/1 sigma-root, then LST-based fzero, then the
189 % two-moment qsys_gg1 fit. The PH path
is exact only
for a Markovian
190 % arrival law; see _kb/06-solver-catalog.md (MVA section, gm1 note).
192 if ProcessType.isMarkovian(sn.procid(source_ist))
194 phPair = sn.proc{source_ist}{1};
195 if iscell(phPair) && numel(phPair) >= 2
198 if size(D0_ph, 1) == size(D0_ph, 2) && all(size(D0_ph) == size(D1_ph))
199 pie_src = map_pie({D0_ph, D1_ph});
200 res_phm1 = qsys_phm1(pie_src, D0_ph, mu);
201 R = res_phm1.meanSojournTime;
210 LA = @(s) sn.lst{source_ist}{1}(s);
211 sigma = fzero(@(x) LA(mu-mu*x)-x, 0.5);
212 R = qsys_gm1(sigma, mu);
214 R = qsys_gg1(lambda, mu, ca^2, 1);
218 line_error(mfilename,
'Unsupported method for a model with 1 station and 1 class.');
220actualmethod = method;
222Rscalar = R; % save scalar from qsys function
223% per-visit vs per-job: RespT/QLen are per-visit, ResidT and system response C are
224% per-job (=per-visit * Vq); see _kb/06-solver-catalog.md (MVA section, gm1 note).
225Vq = sn.visits{1}(sn.stationToStateful(queue_ist));
226srcRate = sn.rates(source_ist);
228R(queue_ist,1) = Rscalar; % per-visit response time
229C(1,1) = Rscalar * Vq; % system (per-job) response time
230X(queue_ist,1) = srcRate; % system throughput = external arrival rate
231U(queue_ist,1) = lambda/mu/k;
232T(source_ist,1) = srcRate; % source throughput = external arrival rate
233T(queue_ist,1) = lambda; % queue throughput = effective arrival rate
234Q(queue_ist,1) = lambda * Rscalar; % Little
's law at the queue (per-visit)