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.
10config = options.config;
16scv = sn.scv; scv(isnan(scv))=0;
18%% immediate feedback elimination
19%
this is adapted
for class-switching, an alternative implementation would
20% rescale by sum(rt((i-1)*K+r,(i-1)*K+1:K)) rather than rt((i-1)*K+r,(i-1)*K+r)
26% 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));
30% S(i,r) = S(i,r) / (1-rt((i-1)*K+r,(i-1)*K+r));
31% 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);
32% rt((i-1)*K+r,(i-1)*K+r) = 0;
36%% generate local state spaces
40V = cellsum(sn.visits);
53if any(isfinite(sn.njobs))
54 % line_error(mfilename,
'QNA does not support closed classes.');
57%isMixed = isOpen & isClosed;
59% treat open as having higher priority than closed
60%sn.classprio(~isfinite(sn.njobs)) = 1 + max(sn.classprio(isfinite(sn.njobs)));
63%% compute departure process at source
67f2 = zeros(M*K,M*K); % scv of each flow pair (i,r) -> (j,s)
70 if sn.nodetype(sn.stationToNode(jst)) ~= NodeType.Source
73 if rt((ist-1)*K+r, (jst-1)*K+s)>0
74 f2((ist-1)*K+r, (jst-1)*K+s) = 1; % C^2ij,r
81lambdas_inchain = cell(1,C);
82scvs_inchain = cell(1,C);
86 inchain = sn.inchain{c};
87 refStatIdx = sn.refstat(inchain(1));
88 if isfinite(sn.njobs(c))
89 Q(refStatIdx,:)=State.toMarginal(sn,sn.stationToNode(refStatIdx));
91 lambdas_inchain{c} = sn.rates(refStatIdx,inchain);
92 scvs_inchain{c} = scv(refStatIdx,inchain);
93 lambda(c) = sum(lambdas_inchain{c}(isfinite(lambdas_inchain{c})));
94 d2c(c) = qna_superpos(lambdas_inchain{c},scvs_inchain{c});
95 if isinf(sum(sn.njobs(inchain))) %
if open chain
96 T(refStatIdx,inchain
') = lambdas_inchain{c};
101 inchain = sn.inchain{c};
102 refStatIdx = sn.refstat(inchain(1));
103 d2(refStatIdx)=d2c*lambda'/sum(lambda);
108while max(max(abs(Q-Q_1))) > options.iter_tol && it <= options.iter_max %#ok<max>
111 inchain = sn.inchain{c};
112 Q(:,c) = sn.njobs(c) .* Q(:,c) / sum(Q(:,c));
119 Q(sn.refstat(k),k) = sn.njobs(k);
123 % update throughputs at all stations
126 inchain = sn.inchain{c};
128 T(m,inchain) = V(m,inchain) .* lambda(c);
137 lambda_i = sum(T(ist,:));
141 a1(ist,r) = a1(ist,r) + T(jst,s)*rt((jst-1)*K+s, (ist-1)*K+r);
142 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);
148 % update flow trhough queueing station
151 ist = sn.nodeToStation(ind);
152 switch sn.nodetype(ind)
156 % inchain = sn.inchain{c};
158 % fanin = nnz(sn.rtnodes(:, (ind-1)*K+k));
159 % TN(ist,k) = lambda(c)*V(ist,k)/fanin;
167 case SchedStrategy.INF
170 d2(ist,s) = a2(ist,s);
174 inchain = sn.inchain{c};
176 T(ist,k) = a1(ist,k);
177 Q(ist,k) = T(ist,k).*S(ist,k)*V(ist,k);
179 R(ist,k) = Q(ist,k)/T(ist,k);
182 case SchedStrategy.PS
184 inchain = sn.inchain{c};
186 T(ist,k) = lambda(c)*V(ist,k);
187 U(ist,k) = S(ist,k)*T(ist,k);
189 Nc = sum(sn.njobs(inchain)); % closed population
190 Uden = min([1-options.tol,sum(U(ist,:))]);
192 Q(ist,k) = (U(ist,k)-U(ist,k)^(Nc+1))/(1-Uden); % geometric bound type approximation
193 %Q(ist,k) = UN(ist,k)/(1-Uden);
194 R(ist,k) = Q(ist,k)/T(ist,k);
197 case {SchedStrategy.FCFS}
198 mu_ist = sn.rates(ist,1:K);
199 mu_ist(isnan(mu_ist))=0;
200 rho_ist_class = a1(ist,1:K)./(GlobalConstants.FineTol+sn.rates(ist,1:K));
201 rho_ist_class(isnan(rho_ist_class))=0;
202 lambda_ist = sum(a1(ist,:));
203 mi = sn.nservers(ist);
204 rho_ist = sum(rho_ist_class) / mi;
205 if rho_ist < 1-options.tol
208 alpha_mi = (rho_ist^mi+rho_ist) / 2;
210 alpha_mi = rho_ist^((mi+1)/2);
212 mubar(ist) = lambda_ist ./ rho_ist;
216 c2(ist) = c2(ist) + a1(ist,r)/lambda_ist * (mubar(ist)/mi/mu_ist(r))^2 * (scv(ist,r)+1 );
219 Wiq(ist) = (alpha_mi / mubar(ist)) * 1/ (1-rho_ist) * (sum(a2(ist,:))+c2(ist))/2;
220 Q(ist,k) = a1(ist,k) / mu_ist(k) + a1(ist,k)*Wiq(ist);
222 d2(ist) = 1 + rho_ist^2*(c2(ist)-1)/sqrt(mi) + (1 - rho_ist^2) *(sum(a2(ist,:))-1);
225 Q(ist,k) = sn.njobs(k);
230 T(ist,k) = a1(ist,k);
231 U(ist,k) = T(ist,k) * S(ist,k) /sn.nservers(ist);
232 R(ist,k) = Q(ist,k) ./ T(ist,k);
237 switch sn.nodetype(ind)
239 line_error(mfilename,
'Fork nodes not supported yet by QNA solver.');
245 % splitting - update flow scvs
248 if sn.nodetype(sn.stationToNode(jst)) ~= NodeType.Source
251 if rt((ist-1)*K+r, (jst-1)*K+s)>0
252 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
266 Q(ist,k) = sn.njobs(k);
267 T(ist,k) = sn.njobs(k)*sn.rates(ist,k);
268 R(ist,k) = Q(ist,k) ./ T(ist,k);
269 U(ist,k) = S(ist,k)*T(ist,k);
275 inchain = sn.inchain{c};
276 if isfinite(sn.njobs(c))
277 Q(:,c) = sn.njobs(c) .* Q(:,c) / sum(Q(:,c));
280for ist=1:sn.nstations
282 case SchedStrategy.INF
298function [d2]=qna_superpos(lambda,a2)
299a2 = a2(isfinite(lambda));
300lambda = lambda(isfinite(lambda));
301d2 = a2(:)
'*lambda(:) / sum(lambda);