LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_mva_qsys_analyzer.m
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)
3
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6
7T0=tic;
8M = sn.nstations;
9K = sn.nclasses;
10Q = zeros(M,K); U = zeros(M,K);
11R = zeros(M,K); T = zeros(M,K);
12C = zeros(1,K); X = zeros(M,K);
13totiter = 1;
14
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));
24
25line_debug('MVA qsys analyzer starting: method=%s, lambda=%g, mu=%g, k=%d', method, lambda, mu, k);
26
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'.
31if sn_is_mm1k_loss(sn)
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.');
34 end
35 Kcap = sn.cap(queue_ist);
36 rho = lambda/mu;
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
40 if abs(rho-1) < 1e-10
41 Lsys = Kcap/2; % L'Hopital limit at rho=1
42 else
43 Lsys = rho/(1-rho) - (Kcap+1)*rho^(Kcap+1)/(1-rho^(Kcap+1));
44 end
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;
48 U(queue_ist,1) = Uq;
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);
55 return
56end
57
58% Check for BMAP arrivals (batch Markovian)
59if sn.procid(source_ist) == ProcessType.BMAP
60 line_debug('BMAP arrival process detected');
61
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};
71 end
72 pie_b = ctmc_solve(proc_src{1} + proc_src{2});
73 nbatch = length(proc_src) - 2;
74 batch_rates = zeros(1, nbatch);
75 for bsize = 1:nbatch
76 batch_rates(bsize) = pie_b * proc_src{2+bsize} * ones(size(proc_src{1},1),1);
77 end
78 total_rate = sum(batch_rates);
79 lambda_batch = total_rate; % batch event rate
80 if total_rate > 0
81 E_X = sum((1:nbatch) .* batch_rates) / total_rate;
82 E_X2 = sum(((1:nbatch).^2) .* batch_rates) / total_rate;
83 else
84 E_X = 1;
85 E_X2 = 1;
86 end
87
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;
96 lG = 0;
97 runtime=toc(T0);
98 actualmethod = 'mxm1';
99 return;
100 end
101end
102
103if strcmpi(method,'exact')
104 if ca == 1 && cs == 1 && k==1
105 method = 'mm1';
106 line_debug('Exact method selected: M/M/1 (ca=1, cs=1, k=1)');
107 elseif ca == 1 && cs == 1 && k>1
108 method = 'mmk';
109 line_debug('Exact method selected: M/M/k (ca=1, cs=1, k=%d)', k);
110 elseif ca == 1 && k==1
111 method = 'mg1';
112 line_debug('Exact method selected: M/G/1 (ca=1, k=1)');
113 elseif cs == 1 && k==1
114 method = 'gm1';
115 line_debug('Exact method selected: G/M/1 (cs=1, k=1)');
116 else
117 line_error(mfilename,'MVA exact method unavailable for this model.');
118 end
119end
120
121switch method
122 case 'default'
123 if ca == 1 && cs == 1 && k == 1
124 method = 'mm1';
125 line_debug('Default method: using M/M/1 exact solution\n');
126 elseif ca == 1 && cs == 1 && k > 1
127 method = 'mmk';
128 line_debug('Default method: using M/M/k exact solution (k=%d)\n', k);
129 elseif ca == 1 && k == 1
130 method = 'mg1';
131 line_debug('Default method: using M/G/1 exact solution\n');
132 elseif cs == 1 && k == 1
133 method = 'gm1';
134 line_debug('Default method: using G/M/1 exact solution\n');
135 elseif k > 1
136 method = 'gigk';
137 line_debug('Default method: using G/G/k approximation (k=%d)\n', k);
138 else
139 method = 'gig1.klb';
140 line_debug('Default method: using G/G/1 KLB approximation\n');
141 end
142end
143
144switch method
145 case 'mm1'
146 line_debug('Using M/M/1 exact solution');
147 R = qsys_mm1(lambda,mu);
148 case 'mmk'
149 line_debug('Using M/M/k exact solution (k=%d)', k);
150 R = qsys_mmk(lambda,mu,k);
151 case {'rqna'}
152 line_debug('Using RQNA (robust queueing) single-queue solution');
153 arvMAP = sn.proc{source_ist}{1};
154 rho1 = lambda/mu;
155 IaFun1 = @(x) map_count_idc(arvMAP, x);
156 [~, W1] = qsys_gig1_rq(rho1, mu, cs^2, IaFun1);
157 R = W1 + 1/mu;
158 case {'mg1', 'mgi1'} % verified
159 line_debug('Using M/G/1 exact solution');
160 R = qsys_mg1(lambda,mu,cs);
161 case {'gigk'}
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);
170 case 'gig1.heyman'
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);
179 case 'gig1.klb'
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);
185 case {'gm1', 'gim1'}
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).
191 R = [];
192 if ProcessType.isMarkovian(sn.procid(source_ist))
193 try
194 phPair = sn.proc{source_ist}{1};
195 if iscell(phPair) && numel(phPair) >= 2
196 D0_ph = phPair{1};
197 D1_ph = 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;
202 end
203 end
204 catch
205 R = [];
206 end
207 end
208 if isempty(R)
209 try
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);
213 catch
214 R = qsys_gg1(lambda, mu, ca^2, 1);
215 end
216 end
217 otherwise
218 line_error(mfilename,'Unsupported method for a model with 1 station and 1 class.');
219end
220actualmethod = method;
221
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);
227R = zeros(M,K);
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)
235lG = 0;
236runtime=toc(T0);
237end