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)
4% Copyright (c) 2012-2026, Imperial College London
6M = sn.nstations; %number of stations
9if nservers(isfinite(nservers))>1
10 if isempty(sn.lldscaling) && M==2 && all(isfinite(sn.njobs))
13 sn.lldscaling(ist,1:Nt) = min(1:Nt,sn.nservers(ist));
16 line_error(mfilename,'The load-dependent solver does not support multi-server stations yet. Specify multi-server stations via limited load-dependence.');
20if ~isempty(sn.cdscaling) && strcmpi(options.method, 'exact')
21 line_error(mfilename,'Exact class-dependent solver not yet available in NC.');
24if sn_has_joint_dependence(sn) && strcmpi(options.method, 'exact')
25 % Route to convolution-based solver for LJCD models (Sauer 1983, Sec. 5.2)
26 [Q,U,R,T,C,X,lG,runtime,iter,method] = solver_nc_conv(sn, options);
30NK = sn.njobs'; % initial population per class
33 line_error(mfilename,'The load-dependent solver does not support open
classes yet.');
45lldscaling = sn.lldscaling;
46Nt = sum(NK(isfinite(NK)));
48 lldscaling = ones(M,ceil(Nt));
51[~,~,Vchain,alpha] = sn_get_demands_chain(sn);
55if all(sched~=SchedStrategy.FCFS) options.iter_max=1; end
57while max(abs(1-eta./eta_1)) > options.iter_tol & iter < options.iter_max
60 M = sn.nstations; %number of stations
61 K = sn.nclasses; %number of
classes
66 SCVchain = zeros(M,C);
68 refstatchain = zeros(C,1);
70 inchain = sn.inchain{c};
71 isOpenChain = any(isinf(sn.njobs(inchain)));
73 % we assume that the
visits in L(i,inchain) are equal to 1
74 Lchain(ist,c) = Vchain(ist,c) * ST(ist,inchain) * alpha(ist,inchain)
';
75 STchain(ist,c) = ST(ist,inchain) * alpha(ist,inchain)';
76 if isOpenChain && ist == sn.refstat(inchain(1)) %
if this is a source ST = 1 / arrival rates
77 STchain(ist,c) = sumfinite(ST(ist,inchain)); % ignore degenerate
classes with zero arrival rates
79 STchain(ist,c) = ST(ist,inchain) * alpha(ist,inchain)
';
81 SCVchain(ist,c) = SCV(ist,inchain) * alpha(ist,inchain)';
83 Nchain(c) = sum(NK(inchain));
84 refstatchain(c) = sn.refstat(inchain(1));
85 if any((sn.refstat(inchain(1))-refstatchain(c))~=0)
86 line_error(mfilename,sprintf('Classes in chain %d have different reference station.',c));
89 STchain(~isfinite(STchain))=0;
90 Lchain(~isfinite(Lchain))=0;
92 Nt = sum(Nchain(isfinite(Nchain)));
95 mu = zeros(M,ceil(Nt));
99 if isinf(nservers(ist)) % infinite server
100 %mu_chain(i,1:sum(Nchain)) = 1:sum(Nchain);
101 infServers(end+1) = ist;
102 L(ist,:) = Lchain(ist,:);
103 Z(ist,:) = Lchain(ist,:);
106 if strcmpi(options.method,'exact') && nservers(ist)>1
107 %options.method = 'default';
108 line_warning(mfilename,sprintf('%s does not support exact multiserver yet. Switching to approximate method.\n', 'SolverNC'));
110 L(ist,:) = Lchain(ist,:);
111 mu(ist,1:Nt) = lldscaling(ist,1:Nt);
115 % Solve original system
116 [lG,~,method] = pfqn_ncld(L, Nchain, 0*Nchain, mu, options);
120 % Solve systems with a job less
123 Nchain_r =oner(Nchain,r);
124 lGr(r) = pfqn_ncld(L,Nchain_r,0*Nchain,mu,options);
126 Xchain(r) = exp(lGr(r) - lG);
130 CQchain_r = zeros(M,1);
132 if M==2 && any(isinf(sn.nservers)) % repairmen model
133 firstDelay = find(isinf(sn.nservers),1);
134 Qchain(firstDelay,r) = real(Lchain(firstDelay,r) * Xchain(r));
135 Qchain(setdiff(1:M,firstDelay),r) = Nchain(r) - real(Lchain(firstDelay,r) * Xchain(r));
137 % Add queue replicas for queue-length
139 Lms_i = L; Lms_i(ist,:) = [];
140 mu_i = mu; mu_i(ist,:) = [];
141 muhati = mu; muhati = pfqn_mushift(mu,ist); %
#ok<NASGU>
142 [muhati_f,c] = pfqn_fnc(muhati(ist,:));
144 if isinf(nservers(ist)) % infinite server
145 Qchain(ist,r) = real(Lchain(ist,r) * Xchain(r));
147 if ist==M && sum(isfinite(nservers))==1 % normalize queue-lengths to Nchain(r)
148 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)));
150 [lGhat_fnci(r)] = pfqn_ncld([L;L(ist,:)],Nchain_r, 0*Nchain, [muhati;muhati_f], options);
151 [lGhatir(r)] = pfqn_ncld(L,Nchain_r, 0*Nchain, muhati, options);
152 [lGr_i(r)] = pfqn_ncld(Lms_i,Nchain_r, 0*Nchain, mu_i, options);
153 [lGhati(r)] = pfqn_ncld(L,Nchain_r, 0*Nchain, muhati, options);
154 dlGa = real(lGhat_fnci(r)) - real(lGhatir(r));
155 dlG_i = real(lGr_i(r)) - real(lGhatir(r));
156 CQchain(ist) = (exp(dlGa) - 1) + c*(exp(dlG_i)-1); % conditional qlen
157 ldDemand(ist,r) = log(L(ist,r)) + real(lGhati(r)) - log(mu(ist,1)) - real(lGr(r));
158 Qchain(ist,r) = exp(ldDemand(ist,r)) * Xchain(r) * (1+CQchain(ist)); % conditional MVA formula
167 % just fill the delay servers
171 if isinf(nservers(ist)) % infinite server
172 Qchain(ist,r) = Lchain(ist,r) * Xchain(r);
180 line_warning(mfilename,
'Normalizing constant computations produced a floating-point range exception. Model is likely too large.\n');
185 Rchain = Qchain ./ repmat(Xchain,M,1) ./ Vchain;
186 Rchain(infServers,:) = Lchain(infServers,:) ./ Vchain(infServers,:);
187 Tchain = repmat(Xchain,M,1) .* Vchain;
188 Uchain = Tchain .* Lchain;
189 Cchain = Nchain ./ Xchain - Z;
196 Xchain(~isfinite(Xchain))=0;
197 Uchain(~isfinite(Uchain))=0;
198 Qchain(~isfinite(Qchain))=0;
199 Rchain(~isfinite(Rchain))=0;
202 Uchain(:,Nchain==0)=0;
203 Qchain(:,Nchain==0)=0;
204 Rchain(:,Nchain==0)=0;
205 Tchain(:,Nchain==0)=0;
207 [Q,U,R,T,C,X] = sn_deaggregate_chain_results(sn, Lchain, ST, STchain, Vchain, alpha, [], [], Rchain, Tchain, [], Xchain);
209 [ST,gamma,~,~,~,~,eta] = npfqn_nonexp_approx(options.config.highvar,sn,ST0,V,SCV,T,U,gamma,nservers);
213[lambda,L]= sn_get_product_form_params(sn);
214runtime = toc(Tstart);
215Q=abs(Q); R=abs(R); X=abs(X); U=abs(U);
217 if sn.nservers(ist)>1 && sn.nservers(ist)<Inf
218 openClasses = find(isinf(NK));
219 closedClasses = setdiff(1:K, openClasses);
221 c = find(sn.chains(:,r));
223 U(ist,r) = X(r) * sn.visits{c}(ist,r) / sn.visits{c}(sn.refstat(r),r) * ST(ist,r)/sn.nservers(ist);
227 c = find(sn.chains(:,r));
229 U(ist,r) = lambda(r) * sn.visits{c}(ist,r) / sn.visits{c}(sn.refstat(r),r) * ST(ist,r)/sn.nservers(ist);
232 elseif isinf(sn.nservers(ist))
233 openClasses = find(isinf(NK));
234 closedClasses = setdiff(1:K, openClasses);
237 c = find(sn.chains(:,r));
238 U(ist,r) = X(r) * sn.visits{c}(ist,r) / sn.visits{c}(sn.refstat(r),r) * ST(ist,r);
243 c = find(sn.chains(:,r));
244 U(ist,r) = lambda(r) * sn.visits{c}(ist,r) / sn.visits{c}(sn.refstat(r),r) * ST(ist,r);
248 U(ist,:) = U(ist,:) / max(lldscaling(ist,:));
250 U(ist,:) = U(ist,:) / sum(U(ist,:),
"omitnan");
255X(~isfinite(X))=0; U(~isfinite(U))=0; Q(~isfinite(Q))=0; R(~isfinite(R))=0;
257% renormalize qlen and tput to correct
for unforeseen population constraint deviations
259 inchain = sn.inchain{c};
260 Nchain(c) = sum(NK(inchain)); %#ok<FNDSB>
261 if isfinite(Nchain(c))
262 q_den = sum(sum(Q(:,inchain)));
264 ratio = Nchain(c)/ q_den;
268 Q(:,inchain) = ratio * Q(:,inchain);
269 X(inchain) = ratio * X(inchain);
270 T(:,inchain) = ratio * T(:,inchain);
271 U(:,inchain) = ratio * U(:,inchain);
272 R(:,inchain) = Q(:,inchain) ./ T(:,inchain);