LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_ncld.m
1function [Q,U,R,T,C,X,lG,runtime,iter,method] = solver_ncld(sn, options)
2% [Q,U,R,T,C,X,LG,RUNTIME,ITER.METHOD] = SOLVER_NCLD(QN, OPTIONS)
3
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6M = sn.nstations; %number of stations
7K = sn.nclasses;
8nservers = sn.nservers;
9if nservers(isfinite(nservers))>1
10 if isempty(sn.lldscaling) && M==2 && all(isfinite(sn.njobs))
11 for ist=1:M
12 Nt = sum(sn.njobs);
13 sn.lldscaling(ist,1:Nt) = min(1:Nt,sn.nservers(ist));
14 end
15 elseif lld_encodes_multiserver(sn, nservers, M)
16 % The caller (SolverNC.runAnalyzer) already expressed every multiserver as
17 % mu(n)=min(n,c), which is what this guard asks for, so the model is
18 % supported. nservers is deliberately left at c: utilization is the
19 % fraction of the c servers busy and c cannot be read back from
20 % lldscaling once the population is below it (min(1:Nt,c) is then 1:Nt).
21 else
22 line_error(mfilename,'The load-dependent solver does not support multi-server stations yet. Specify multi-server stations via limited load-dependence.');
23 end
24end
25
26if ~isempty(sn.cdscaling)
27 % Class-dependent service rates beta_{i,r}(n): route to the convolution
28 % solver, which builds the station factor X_m(n) by Sauer's chain-dependent
29 % recurrence (Sauer 1983, Sec. 5.2, eq. (40)). This covers both the
30 % chain-independent case (beta_i(n), a scalar-valued handle) and the
31 % chain-specific one (a length-R vector), the latter being what the
32 % flow-equivalent-server aggregation installs.
33 %
34 % The routing is unconditional on options.method: the algorithms below read
35 % mu(n) from lldscaling only and have no way to apply beta, so gating this
36 % on method='exact' made every other method (including the default) return
37 % the UNSCALED network instead of erroring. Convolution is the only exact
38 % algorithm for beta, and it is what the native Python NC already does.
39 [Q,U,R,T,C,X,lG,runtime,iter,method] = solver_nc_conv(sn, options);
40 return
41end
42
43NK = sn.njobs'; % initial population per class
44
45% Mixed open/closed load-dependent networks are handled below through the exact
46% chain-level MVALDMX algorithm (Bruell-Balbo-Afshari effective capacity); the
47% closed-only convolution path is retained for purely closed models.
48
49sched = sn.sched;
50%chains = sn.chains;
51C = sn.nchains;
52SCV = sn.scv;
53gamma = zeros(M,1);
54V = cellsum(sn.visits);
55ST = 1 ./ sn.rates;
56ST(isnan(ST))=0;
57ST0=ST;
58lldscaling = sn.lldscaling;
59Nt = sum(NK(isfinite(NK)));
60if isempty(lldscaling)
61 lldscaling = ones(M,ceil(Nt));
62end
63
64[~,~,Vchain,alpha] = sn_get_demands_chain(sn);
65
66eta_1 = zeros(1,M);
67eta = ones(1,M);
68if all(sched~=SchedStrategy.FCFS) options.iter_max=1; end
69iter = 0;
70while max(abs(1-eta./eta_1)) > options.iter_tol & iter < options.iter_max
71 iter = iter + 1;
72 eta_1 = eta;
73 M = sn.nstations; %number of stations
74 K = sn.nclasses; %number of classes
75 C = sn.nchains;
76 Lchain = zeros(M,C);
77 STchain = zeros(M,C);
78
79 SCVchain = zeros(M,C);
80 Nchain = zeros(1,C);
81 refstatchain = zeros(C,1);
82 for c=1:C
83 inchain = sn.inchain{c};
84 isOpenChain = any(isinf(sn.njobs(inchain)));
85 for ist=1:M
86 % we assume that the visits in L(i,inchain) are equal to 1
87 Lchain(ist,c) = Vchain(ist,c) * ST(ist,inchain) * alpha(ist,inchain)';
88 STchain(ist,c) = ST(ist,inchain) * alpha(ist,inchain)';
89 if isOpenChain && ist == sn.refstat(inchain(1)) % if this is a source ST = 1 / arrival rates
90 STchain(ist,c) = sumfinite(ST(ist,inchain)); % ignore degenerate classes with zero arrival rates
91 else
92 STchain(ist,c) = ST(ist,inchain) * alpha(ist,inchain)';
93 end
94 SCVchain(ist,c) = SCV(ist,inchain) * alpha(ist,inchain)';
95 end
96 Nchain(c) = sum(NK(inchain));
97 refstatchain(c) = sn.refstat(inchain(1));
98 if any((sn.refstat(inchain(1))-refstatchain(c))~=0)
99 line_error(mfilename,sprintf('Classes in chain %d have different reference station.',c));
100 end
101 end
102 STchain(~isfinite(STchain))=0;
103 Lchain(~isfinite(Lchain))=0;
104 % chain-level arrival rates for open chains (source ST = 1/arrival rate)
105 lambda = zeros(1,C);
106 for c=1:C
107 inchain = sn.inchain{c};
108 if any(isinf(sn.njobs(inchain)))
109 rst = sn.refstat(inchain(1));
110 if STchain(rst,c) > 0
111 lambda(c) = 1 / STchain(rst,c);
112 end
113 end
114 end
115 Tstart = tic;
116 Nt = sum(Nchain(isfinite(Nchain)));
117
118 L = zeros(M,C);
119 mu = zeros(M,ceil(Nt));
120 infServers = [];
121 Z = zeros(M,C);
122 for ist=1:M
123 if isinf(nservers(ist)) % infinite server
124 %mu_chain(i,1:sum(Nchain)) = 1:sum(Nchain);
125 infServers(end+1) = ist;
126 L(ist,:) = Lchain(ist,:);
127 Z(ist,:) = Lchain(ist,:);
128 mu(ist,1:Nt) = 1:Nt;
129 else
130 % Only warn when the multiserver is not already represented by the
131 % load-dependent rates: SolverNC converts mu(n)=min(n,c) upfront, and
132 % that case is solved exactly, so it must not warn.
133 if strcmpi(options.method,'exact') && nservers(ist)>1 ...
134 && ~isequal(lldscaling(ist,1:Nt), min(1:Nt,nservers(ist)))
135 %options.method = 'default';
136 line_warning(mfilename,sprintf('%s does not support exact multiserver yet. Switching to approximate method.\n', 'SolverNC'));
137 end
138 L(ist,:) = Lchain(ist,:);
139 mu(ist,1:Nt) = lldscaling(ist,1:Nt);
140 end
141 end
142 openChains = find(isinf(Nchain));
143 if ~isempty(openChains)
144 % Mixed limited load-dependent network: exact chain-level MVALDMX. The open
145 % chains enter through arrival rates lambda; their reference (source)
146 % stations carry only the 1/lambda bookkeeping demand and are excluded from
147 % the queueing set. Infinite-server (delay) stations fold into the
148 % think-time vector; the remaining queueing stations carry the limited
149 % load-dependent rates lldscaling.
150 sourceStations = unique(refstatchain(openChains))';
151 delayStations = setdiff(infServers, sourceStations);
152 queueStations = setdiff(1:M, [delayStations, sourceStations]);
153 nq = numel(queueStations);
154 Ncl = sum(Nchain(isfinite(Nchain)));
155 Zvec = zeros(1,C);
156 if ~isempty(delayStations)
157 for c=1:C
158 Zvec(c) = sum(Lchain(delayStations,c));
159 end
160 end
161 Dq = Lchain(queueStations,:);
162 ncol = max(1,Ncl);
163 muq = ones(nq,ncol);
164 avail = min(ncol, size(lldscaling,2));
165 for qi=1:nq
166 if avail > 0
167 muq(qi,1:avail) = lldscaling(queueStations(qi),1:avail);
168 end
169 end
170 [Xchain,QN_mx,~,~,lG] = pfqn_mvaldmx(lambda, Dq, Nchain, Zvec, muq, ones(nq,1));
171 lG = real(lG);
172 Qchain = zeros(M,C);
173 Qchain(queueStations,:) = QN_mx;
174 for di=1:numel(delayStations)
175 ist = delayStations(di);
176 Qchain(ist,:) = Lchain(ist,:) .* Xchain;
177 end
178 method = 'ncldmx';
179 else
180 Qchain = zeros(M,C);
181 % Solve original system
182 [lG,~,method] = pfqn_ncld(L, Nchain, 0*Nchain, mu, options);
183 lG = real(lG);
184 Xchain=[];
185
186 % Solve systems with a job less
187 if isempty(Xchain)
188 for r=1:C
189 Nchain_r =oner(Nchain,r);
190 lGr(r) = pfqn_ncld(L,Nchain_r,0*Nchain,mu,options);
191 lGr = real(lGr);
192 Xchain(r) = exp(lGr(r) - lG);
193 for ist=1:M
194 Qchain(ist,r)=0;
195 end
196 CQchain_r = zeros(M,1);
197
198 if M==2 && any(isinf(sn.nservers)) % repairmen model
199 firstDelay = find(isinf(sn.nservers),1);
200 Qchain(firstDelay,r) = real(Lchain(firstDelay,r) * Xchain(r));
201 Qchain(setdiff(1:M,firstDelay),r) = Nchain(r) - real(Lchain(firstDelay,r) * Xchain(r));
202 else
203 % Add queue replicas for queue-length
204 for ist=1:M
205 Lms_i = L; Lms_i(ist,:) = [];
206 mu_i = mu; mu_i(ist,:) = [];
207 muhati = mu; muhati = pfqn_mushift(mu,ist); %#ok<NASGU>
208 [muhati_f,c] = pfqn_fnc(muhati(ist,:));
209 if Lchain(ist,r)>0
210 if isinf(nservers(ist)) % infinite server
211 Qchain(ist,r) = real(Lchain(ist,r) * Xchain(r));
212 else
213 if ist==M && sum(isfinite(nservers))==1 % normalize queue-lengths to Nchain(r)
214 Qchain(ist,r) = max(0,real(Nchain(r) - sum(Lchain(isinf(nservers),r)) * Xchain(r)) - sum(Qchain(setdiff(1:(M-1),find(isinf(nservers))),r)));
215 else
216 [lGhat_fnci(r)] = pfqn_ncld([L;L(ist,:)],Nchain_r, 0*Nchain, [muhati;muhati_f], options);
217 [lGhatir(r)] = pfqn_ncld(L,Nchain_r, 0*Nchain, muhati, options);
218 [lGr_i(r)] = pfqn_ncld(Lms_i,Nchain_r, 0*Nchain, mu_i, options);
219 [lGhati(r)] = pfqn_ncld(L,Nchain_r, 0*Nchain, muhati, options);
220 dlGa = real(lGhat_fnci(r)) - real(lGhatir(r));
221 dlG_i = real(lGr_i(r)) - real(lGhatir(r));
222 CQchain(ist) = (exp(dlGa) - 1) + c*(exp(dlG_i)-1); % conditional qlen
223 ldDemand(ist,r) = log(L(ist,r)) + real(lGhati(r)) - log(mu(ist,1)) - real(lGr(r));
224 Qchain(ist,r) = exp(ldDemand(ist,r)) * Xchain(r) * (1+CQchain(ist)); % conditional MVA formula
225 end
226 end
227 end
228 end
229 end
230 end
231
232 else
233 % just fill the delay servers
234 for r=1:C
235 for ist=1:M
236 if Lchain(ist,r)>0
237 if isinf(nservers(ist)) % infinite server
238 Qchain(ist,r) = Lchain(ist,r) * Xchain(r);
239 end
240 end
241 end
242 end
243 end
244
245 if isnan(Xchain)
246 line_warning(mfilename,'Normalizing constant computations produced a floating-point range exception. Model is likely too large.\n');
247 end
248 end % close open/closed dispatch
249
250 Z = sum(Z(1:M,:),1);
251
252 Rchain = Qchain ./ repmat(Xchain,M,1) ./ Vchain;
253 Rchain(infServers,:) = Lchain(infServers,:) ./ Vchain(infServers,:);
254 Tchain = repmat(Xchain,M,1) .* Vchain;
255 Uchain = Tchain .* Lchain;
256 Cchain = Nchain ./ Xchain - Z;
257
258 Xchain=real(Xchain);
259 Uchain=real(Uchain);
260 Qchain=real(Qchain);
261 Rchain=real(Rchain);
262
263 Xchain(~isfinite(Xchain))=0;
264 Uchain(~isfinite(Uchain))=0;
265 Qchain(~isfinite(Qchain))=0;
266 Rchain(~isfinite(Rchain))=0;
267
268 Xchain(Nchain==0)=0;
269 Uchain(:,Nchain==0)=0;
270 Qchain(:,Nchain==0)=0;
271 Rchain(:,Nchain==0)=0;
272 Tchain(:,Nchain==0)=0;
273
274 [Q,U,R,T,C,X] = sn_deaggregate_chain_results(sn, Lchain, ST, STchain, Vchain, alpha, [], [], Rchain, Tchain, [], Xchain);
275
276 [ST,gamma,~,~,~,~,eta] = npfqn_nonexp_approx(options.config.highvar,sn,ST0,V,SCV,T,U,gamma,nservers);
277end
278
279
280[lambda,L]= sn_get_product_form_params(sn);
281runtime = toc(Tstart);
282Q=abs(Q); R=abs(R); X=abs(X); U=abs(U);
283for ist=1:M
284 if sn.nservers(ist)>1 && sn.nservers(ist)<Inf
285 openClasses = find(isinf(NK));
286 closedClasses = setdiff(1:K, openClasses);
287 for r=closedClasses
288 c = find(sn.chains(:,r));
289 if X(r) > 0
290 U(ist,r) = X(r) * sn.visits{c}(ist,r) / sn.visits{c}(sn.refstat(r),r) * ST(ist,r)/sn.nservers(ist);
291 end
292 end
293 for r=openClasses
294 c = find(sn.chains(:,r));
295 if lambda(r)>0
296 U(ist,r) = lambda(r) * sn.visits{c}(ist,r) / sn.visits{c}(sn.refstat(r),r) * ST(ist,r)/sn.nservers(ist);
297 end
298 end
299 elseif isinf(sn.nservers(ist))
300 openClasses = find(isinf(NK));
301 closedClasses = setdiff(1:K, openClasses);
302 for r=closedClasses
303 if X(r) > 0
304 c = find(sn.chains(:,r));
305 U(ist,r) = X(r) * sn.visits{c}(ist,r) / sn.visits{c}(sn.refstat(r),r) * ST(ist,r);
306 end
307 end
308 for r=openClasses
309 if lambda(r)>0
310 c = find(sn.chains(:,r));
311 U(ist,r) = lambda(r) * sn.visits{c}(ist,r) / sn.visits{c}(sn.refstat(r),r) * ST(ist,r);
312 end
313 end
314 else
315 U(ist,:) = U(ist,:) / max(lldscaling(ist,:));
316 if sum(U(ist,:)) > 1
317 U(ist,:) = U(ist,:) / sum(U(ist,:),"omitnan");
318 end
319 end
320end
321
322X(~isfinite(X))=0; U(~isfinite(U))=0; Q(~isfinite(Q))=0; R(~isfinite(R))=0;
323
324% renormalize qlen and tput to correct for unforeseen population constraint deviations
325for c=1:sn.nchains
326 inchain = sn.inchain{c};
327 Nchain(c) = sum(NK(inchain)); %#ok<FNDSB>
328 if isfinite(Nchain(c))
329 q_den = sum(sum(Q(:,inchain)));
330 if q_den > 0
331 ratio = Nchain(c)/ q_den;
332 else
333 ratio = 0;
334 end
335 Q(:,inchain) = ratio * Q(:,inchain);
336 X(inchain) = ratio * X(inchain);
337 T(:,inchain) = ratio * T(:,inchain);
338 U(:,inchain) = ratio * U(:,inchain);
339 R(:,inchain) = Q(:,inchain) ./ T(:,inchain);
340 end
341end
342end
343
344function tf = lld_encodes_multiserver(sn, nservers, M)
345% True if every finite multi-server station already carries mu(n)=min(n,c) in
346% lldscaling, i.e. the multiserver is fully described by the load-dependent
347% rates and needs no further handling.
348tf = false;
349Ntot = sum(sn.njobs(isfinite(sn.njobs)));
350if isempty(sn.lldscaling) || ~isfinite(Ntot) || Ntot < 1
351 return
352end
353if size(sn.lldscaling,2) < Ntot
354 return
355end
356for ist=1:M
357 if isfinite(nservers(ist)) && nservers(ist) > 1
358 if ~isequal(sn.lldscaling(ist,1:Ntot), min(1:Ntot, nservers(ist)))
359 return
360 end
361 end
362end
363tf = true;
364end
Definition Station.m:245