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% Check
for BMAP arrivals (batch Markovian)
28if sn.procid(source_ist) == ProcessType.BMAP
29 line_debug(
'BMAP arrival process detected');
31 % Check
if service
is exponential
32 if sn.procid(queue_ist) == ProcessType.EXP
33 line_debug(
'Service is exponential, using MX/M/1 queue model');
34 % sn.proc holds {D0, D1, D_batch1, ..., D_batchK} (JAR MatrixCell
35 % layout); batch rates are weighted by
the stationary vector of
36 %
the underlying CTMC, as in BMAP.getBatchRates
37 proc_src = sn.proc{source_ist};
38 if iscell(proc_src) && ~isempty(proc_src) && iscell(proc_src{1})
39 proc_src = proc_src{1};
41 pie_b = ctmc_solve(proc_src{1} + proc_src{2});
42 nbatch = length(proc_src) - 2;
43 batch_rates = zeros(1, nbatch);
45 batch_rates(bsize) = pie_b * proc_src{2+bsize} * ones(size(proc_src{1},1),1);
47 total_rate = sum(batch_rates);
48 lambda_batch = total_rate; % batch
event rate
50 E_X = sum((1:nbatch) .* batch_rates) / total_rate;
51 E_X2 = sum(((1:nbatch).^2) .* batch_rates) / total_rate;
57 [W, Wq, U_mxm1, Q_mxm1] = qsys_mxm1(lambda_batch, mu, E_X, E_X2);
58 R(queue_ist,1) = W * sn.visits{1}(sn.stationToStateful(queue_ist));
59 C(1,1) = R(queue_ist,1);
60 X(queue_ist,1) = lambda_batch * E_X; % Job arrival rate
61 U(queue_ist,1) = U_mxm1;
62 T(source_ist,1) = lambda_batch * E_X;
63 T(queue_ist,1) = lambda_batch * E_X;
64 Q(queue_ist,1) = Q_mxm1;
67 actualmethod =
'mxm1';
72if strcmpi(method,
'exact')
73 if ca == 1 && cs == 1 && k==1
75 line_debug('Exact method selected: M/M/1 (ca=1, cs=1, k=1)');
76 elseif ca == 1 && cs == 1 && k>1
78 line_debug('Exact method selected: M/M/k (ca=1, cs=1, k=%d)', k);
79 elseif ca == 1 && k==1
81 line_debug('Exact method selected: M/G/1 (ca=1, k=1)');
82 elseif cs == 1 && k==1
84 line_debug('Exact method selected: G/M/1 (cs=1, k=1)');
86 line_error(mfilename,'MVA exact method unavailable for this model.');
92 if ca == 1 && cs == 1 && k == 1
94 line_debug('Default method: using M/M/1 exact solution\n');
95 elseif ca == 1 && cs == 1 && k > 1
97 line_debug('Default method: using M/M/k exact solution (k=%d)\n', k);
98 elseif ca == 1 && k == 1
100 line_debug('Default method: using M/G/1 exact solution\n');
101 elseif cs == 1 && k == 1
103 line_debug('Default method: using G/M/1 exact solution\n');
106 line_debug('Default method: using G/G/k approximation (k=%d)\n', k);
109 line_debug('Default method: using G/G/1 KLB approximation\n');
115 line_debug('Using M/M/1 exact solution');
116 R = qsys_mm1(lambda,mu);
118 line_debug('Using M/M/k exact solution (k=%d)', k);
119 R = qsys_mmk(lambda,mu,k);
121 line_debug(
'Using RQNA (robust queueing) single-queue solution');
122 arvMAP = sn.proc{source_ist}{1};
124 IaFun1 = @(x) map_count_idc(arvMAP, x);
125 [~, W1] = qsys_gig1_rq(rho1, mu, cs^2, IaFun1);
127 case {
'mg1',
'mgi1'} % verified
128 line_debug(
'Using M/G/1 exact solution');
129 R = qsys_mg1(lambda,mu,cs);
131 line_debug(
'Using G/G/k approximation (k=%d)', k);
132 R = qsys_gigk_approx(lambda,mu,ca,cs,k);
133 case {
'gigk.kingman_approx'}
134 line_debug(
'Using G/G/k Kingman approximation (k=%d)', k);
135 R = qsys_gigk_approx_kingman(lambda,mu,ca,cs,k);
136 case 'gig1.kingman' % verified
137 line_debug(
'Using G/G/1 Kingman upper bound');
138 R = qsys_gig1_ubnd_kingman(lambda,mu,ca,cs);
140 line_debug(
'Using G/G/1 Heyman approximation');
141 R = qsys_gig1_approx_heyman(lambda,mu,ca,cs);
142 case {
'gig1',
'gig1.allen'}
143 line_debug(
'Using G/G/1 Allen-Cunneen approximation');
144 R = qsys_gig1_approx_allencunneen(lambda,mu,ca,cs);
145 case 'gig1.kobayashi'
146 line_debug(
'Using G/G/1 Kobayashi approximation');
147 R = qsys_gig1_approx_kobayashi(lambda,mu,ca,cs);
149 line_debug(
'Using G/G/1 KLB approximation');
150 R = qsys_gig1_approx_klb(lambda,mu,ca,cs);
151 case 'gig1.marchal' % verified
152 line_debug(
'Using G/G/1 Marchal approximation');
153 R = qsys_gig1_approx_marchal(lambda,mu,ca,cs);
155 line_debug(
'Using G/M/1 exact solution');
156 mu = sn.rates(queue_ist);
157 % Prefer
the exact PH/M/1 sigma-root via
the source
's PH representation.
158 % Falls back to the LST-based fzero if that's available,
else to
the
159 % two-moment sigma-root fit of qsys_gg1.
161 % A deterministic arrival has no exact finite PH -- sn.proc stores an
162 % Erlang-n moment approximation, so
the PH path returns an approximate
163 % sigma (D/M/1 0.64477 instead of
the exact 0.6275). Skip PH
for Det and
164 % use
the exact LST sigma-root below (Erlang/HyperExp/Coxian/APH keep PH,
165 % which
is exact
for them).
166 if sn.procid(source_ist) ~= ProcessType.DET
168 phPair = sn.proc{source_ist}{1};
169 if iscell(phPair) && numel(phPair) >= 2
172 if size(D0_ph, 1) == size(D0_ph, 2) && all(size(D0_ph) == size(D1_ph))
173 pie_src = map_pie({D0_ph, D1_ph});
174 res_phm1 = qsys_phm1(pie_src, D0_ph, mu);
175 R = res_phm1.meanSojournTime;
184 LA = @(s) sn.lst{source_ist}{1}(s);
185 sigma = fzero(@(x) LA(mu-mu*x)-x, 0.5);
186 R = qsys_gm1(sigma, mu);
188 R = qsys_gg1(lambda, mu, ca^2, 1);
192 line_error(mfilename,
'Unsupported method for a model with 1 station and 1 class.');
194actualmethod = method;
196Rscalar = R; % save scalar from qsys function
197% lambda
is the effective (per-visit) arrival rate to
the queue = external
198% source rate times
the queue visit ratio; Rscalar
is the per-visit sojourn
199% time. For feedback models
the queue visit ratio Vq > 1, so
the per-visit
200% and per-job (residence) quantities must be kept distinct: RespT/QLen are
201% per-visit,
while ResidT (=RespT*Vq downstream) and
the system response C
202% are per-job. Source throughput
is the external arrival rate, not lambda.
203Vq = sn.visits{1}(sn.stationToStateful(queue_ist));
204srcRate = sn.rates(source_ist);
206R(queue_ist,1) = Rscalar; % per-visit response time
207C(1,1) = Rscalar * Vq; % system (per-job) response time
208X(queue_ist,1) = srcRate; % system throughput = external arrival rate
209U(queue_ist,1) = lambda/mu/k;
210T(source_ist,1) = srcRate; % source throughput = external arrival rate
211T(queue_ist,1) = lambda; % queue throughput = effective arrival rate
212Q(queue_ist,1) = lambda * Rscalar; % Little
's law at the queue (per-visit)