1function [Q,U,R,T,C,X,lG,totiter] = solver_qna(sn, options)
2% [Q,U,R,T,C,X,lG,totiter] = SOLVER_QNA(QN, OPTIONS)
4% Copyright (c) 2012-2026, Imperial College London
7% Implementation as per Section 7.2.3 of N. Gautaum, Analysis of Queues, CRC Press, 2012.
8% Minor corrections applied in discussion with
the author.
10% Decomposition-aggregation structure: each sweep superposes
the flows into
11% every station (da_traffic_superpos aggregation), solves
the stations in
12% isolation, and splits
the departure flows;
the sweeps are driven to a
13% fixed point on
the queue lengths by da_fpi.
15config = options.config;
21scv = sn.scv; scv(isnan(scv))=0;
23%% immediate feedback elimination
24%
this is adapted
for class-switching, an alternative implementation would
25% rescale by sum(rt((i-1)*K+r,(i-1)*K+1:K)) rather than rt((i-1)*K+r,(i-1)*K+r)
31% rt((i-1)*K+r, (j-1)*K+s) = rt((i-1)*K+r, (j-1)*K+s) / (1-rt((i-1)*K+r,(i-1)*K+r));
35% S(i,r) = S(i,r) / (1-rt((i-1)*K+r,(i-1)*K+r));
36% scv(i,r) = rt((i-1)*K+r,(i-1)*K+r) + (1-rt((i-1)*K+r,(i-1)*K+r))*scv(i,r);
37% rt((i-1)*K+r,(i-1)*K+r) = 0;
41%% generate local state spaces
45V = cellsum(sn.visits);
55if any(isfinite(sn.njobs))
56 % line_error(mfilename,
'QNA does not support closed classes.');
59%isMixed = isOpen & isClosed;
61% treat open as having higher priority than closed
62%sn.classprio(~isfinite(sn.njobs)) = 1 + max(sn.classprio(isfinite(sn.njobs)));
65%% compute departure process at source
69f2 = zeros(M*K,M*K); % scv of each flow pair (i,r) -> (j,s)
75 if sn.nodetype(sn.stationToNode(jst)) ~= NodeType.Source
78 if rt((ist-1)*K+r, (jst-1)*K+s)>0
79 f2((ist-1)*K+r, (jst-1)*K+s) = 1; % C^2ij,r
86lambdas_inchain = cell(1,C);
87scvs_inchain = cell(1,C);
91 inchain = sn.inchain{c};
92 refStatIdx = sn.refstat(inchain(1));
93 if isfinite(sn.njobs(c))
94 Q(refStatIdx,:)=State.toMarginal(sn,sn.stationToNode(refStatIdx));
96 lambdas_inchain{c} = sn.rates(refStatIdx,inchain);
97 scvs_inchain{c} = scv(refStatIdx,inchain);
98 lambda(c) = sum(lambdas_inchain{c}(isfinite(lambdas_inchain{c})));
99 d2c(c) = da_traffic_superpos(lambdas_inchain{c},scvs_inchain{c});
100 if isinf(sum(sn.njobs(inchain))) %
if open chain
101 T(refStatIdx,inchain
') = lambdas_inchain{c};
106 inchain = sn.inchain{c};
107 refStatIdx = sn.refstat(inchain(1));
108 d2(refStatIdx)=d2c*lambda'/sum(lambda);
113fpopts.iter_max = options.iter_max + 1; % legacy
while-loop executed one extra sweep at
the cap
114fpopts.config.da_nanstop =
true; % legacy
while-loop exited on NaN convergence measure
115[Q, totiter] = da_fpi(@qna_sweep, Q, fpopts);
117 function [Qnew, Qref] = qna_sweep(Qin, itnum)
120 inchain = sn.inchain{c};
121 Q(:,c) = sn.njobs(c) .* Q(:,c) / sum(Q(:,c));
128 Q(sn.refstat(k),k) = sn.njobs(k);
132 % update throughputs at all stations
135 inchain = sn.inchain{c};
137 T(m,inchain) = V(m,inchain) .* lambda(c);
146 lambda_i = sum(T(ist,:));
150 a1(ist,r) = a1(ist,r) + T(jst,s)*rt((jst-1)*K+s, (ist-1)*K+r);
151 a2(ist,r) = a2(ist,r) + (1/lambda_i) * f2((jst-1)*K+s, (ist-1)*K+r)*T(jst,s)*rt((jst-1)*K+s, (ist-1)*K+r);
157 % update flow trhough queueing station
160 ist = sn.nodeToStation(ind);
161 switch sn.nodetype(ind)
165 % inchain = sn.inchain{c};
167 % fanin = nnz(sn.rtnodes(:, (ind-1)*K+k));
168 % TN(ist,k) = lambda(c)*V(ist,k)/fanin;
176 case SchedStrategy.INF
179 d2(ist,s) = a2(ist,s);
183 inchain = sn.inchain{c};
185 T(ist,k) = a1(ist,k);
186 Q(ist,k) = T(ist,k).*S(ist,k)*V(ist,k);
188 R(ist,k) = Q(ist,k)/T(ist,k);
191 case SchedStrategy.PS
193 inchain = sn.inchain{c};
195 T(ist,k) = lambda(c)*V(ist,k);
196 U(ist,k) = S(ist,k)*T(ist,k);
198 Nc = sum(sn.njobs(inchain)); % closed population
199 Uden = min([1-options.tol,sum(U(ist,:))]);
201 Q(ist,k) = (U(ist,k)-U(ist,k)^(Nc+1))/(1-Uden); % geometric bound type approximation
202 %Q(ist,k) = UN(ist,k)/(1-Uden);
203 R(ist,k) = Q(ist,k)/T(ist,k);
206 case {SchedStrategy.FCFS}
207 mu_ist = sn.rates(ist,1:K);
208 mu_ist(isnan(mu_ist))=0;
209 rho_ist_class = a1(ist,1:K)./(GlobalConstants.FineTol+sn.rates(ist,1:K));
210 rho_ist_class(isnan(rho_ist_class))=0;
211 lambda_ist = sum(a1(ist,:));
212 mi = sn.nservers(ist);
213 rho_ist = sum(rho_ist_class) / mi;
214 if rho_ist < 1-options.tol
217 alpha_mi = (rho_ist^mi+rho_ist) / 2;
219 alpha_mi = rho_ist^((mi+1)/2);
221 mubar(ist) = lambda_ist ./ rho_ist;
225 c2(ist) = c2(ist) + a1(ist,r)/lambda_ist * (mubar(ist)/mi/mu_ist(r))^2 * (scv(ist,r)+1 );
228 Wiq(ist) = (alpha_mi / mubar(ist)) * 1/ (1-rho_ist) * (sum(a2(ist,:))+c2(ist))/2;
229 Q(ist,k) = a1(ist,k) / mu_ist(k) + a1(ist,k)*Wiq(ist);
231 d2(ist) = 1 + rho_ist^2*(c2(ist)-1)/sqrt(mi) + (1 - rho_ist^2) *(sum(a2(ist,:))-1);
234 Q(ist,k) = sn.njobs(k);
239 T(ist,k) = a1(ist,k);
240 U(ist,k) = T(ist,k) * S(ist,k) /sn.nservers(ist);
241 R(ist,k) = Q(ist,k) ./ T(ist,k);
246 switch sn.nodetype(ind)
248 % Fork splits traffic; routing and SCV splitting
249 % are handled by
the rt matrix and
the splitting
250 % formula in
the main iteration loop. No additional
251 % processing needed here.
257 % splitting - update flow scvs
260 if sn.nodetype(sn.stationToNode(jst)) ~= NodeType.Source
263 if rt((ist-1)*K+r, (jst-1)*K+s)>0
264 f2((ist-1)*K+r, (jst-1)*K+s) = 1 + rt((ist-1)*K+r, (jst-1)*K+s) * (d2(ist)-1); % C^2ij,r
279 Q(ist,k) = sn.njobs(k);
280 T(ist,k) = sn.njobs(k)*sn.rates(ist,k);
281 R(ist,k) = Q(ist,k) ./ T(ist,k);
282 U(ist,k) = S(ist,k)*T(ist,k);
288 inchain = sn.inchain{c};
289 if isfinite(sn.njobs(c))
290 Q(:,c) = sn.njobs(c) .* Q(:,c) / sum(Q(:,c));
293for ist=1:sn.nstations
295 case SchedStrategy.INF