1function runtime = runAnalyzer(self, options)
5 options = self.getOptions;
7% Wall-clock time-budget launch marker (see options.timeout / lineTimeoutExceeded)
8options.timeout_tic = tic;
10[pyHandled, options, pyRuntime] = self.runAnalyzerPreamble(options,
'NC');
17self.runAnalyzerChecks(options);
20if isfield(sn,
'immfeed') && ~isempty(sn.immfeed) && any(sn.immfeed(:))
21 line_warning(mfilename,
'SolverNC does not handle immediate feedback (immfeed); the solver will treat self-loops as class-switching with re-queueing.\n');
24Solver.resetRandomGeneratorSeed(options.seed);
27origmethod = options.method;
29% With enableChecks=
false (SolverLN layer backend) an unknown method silently
30% falls through to the
default analyzer; see _kb/06-solver-catalog.md (NC section)
31validMethods = [self.listValidMethods(), {
'comomld'}]; % comomld
is auto-selected internally from
'default'
32if ~any(strcmpi(origmethod, validMethods))
33 line_debug(options,
'NC: unrecognized method ''%s'', falling back to the default normalizing-constant analyzer (nc_analyzer/comom).', origmethod);
36%options.lang =
'java';
40 line_debug(options,
'NC: using lang=java, delegating to JLINE');
42 jmodel = LINE2JLINE(self.model);
43 %M = jmodel.getNumberOfStatefulNodes;
44 M = jmodel.getNumberOfStations;
45 R = jmodel.getNumberOfClasses;
46 jsolver = JLINE.SolverNC(jmodel, options);
47 [QN,UN,RN,WN,AN,TN] = JLINE.arrayListToResults(jsolver.getAvgTable);
48 runtime = jsolver.result.runtime;
51 QN = reshape(QN
',R,M)';
52 UN = reshape(UN
',R,M)';
53 RN = reshape(RN
',R,M)';
54 TN = reshape(TN
',R,M)';
55 WN = reshape(WN
',R,M)';
56 AN = reshape(AN
',R,M)';
60 if sn.nodetype(ind) == NodeType.Cache
61 jnode = jmodel.getNodeByIndex(ind-1);
62 self.model.nodes{ind}.setResultHitProb(JLINE.from_jline_matrix(jnode.getHitRatio()));
63 self.model.nodes{ind}.setResultMissProb(JLINE.from_jline_matrix(jnode.getMissRatio()));
64 % Retrieval-cache extras (delayed-hit ratio, per-list hit ratio
65 % and expected latency) so getAvgCacheTable matches the native path.
66 self.model.nodes{ind}.setResultDelayedHitProb(JLINE.from_jline_matrix(jnode.getDelayedHitRatio()));
67 self.model.nodes{ind}.setResultHitProbList(JLINE.from_jline_matrix(jnode.getHitRatioByList()));
68 self.model.nodes{ind}.setResultItemProb(JLINE.from_jline_matrix(jnode.getItemProb()));
69 self.model.nodes{ind}.setResultResidT(JLINE.from_jline_matrix(jnode.getResidT()));
72 %self.model.refreshChains();
73 self.model.refreshStruct(
true);
74 self.setAvgResults(QN,UN,RN,TN,AN,WN,CN,XN,runtime,options.method,lastiter);
75 self.result.Prob.logNormConstAggr = lG;
78 line_debug(options,
'NC: using lang=matlab');
79 sn = getStruct(self); % doesn
't need initial state
81 % Fork-join: solver-agnostic fjFixedPoint with ncDispatch as inner solve,
82 % intercepted first; see _kb/06-solver-catalog.md (MVA section)
84 fjres = self.fjFixedPoint(options, @(sn_, opt_) self.ncDispatch(sn_, opt_));
85 QN = fjres.QN; UN = fjres.UN; RN = fjres.RN; TN = fjres.TN;
86 CN = fjres.CN; XN = fjres.XN; lG = fjres.lG;
87 runtime = fjres.runtime; iter = fjres.iter;
88 actualmethod = fjres.actualmethod;
89 sn = self.model.getStruct();
90 AN = sn_get_arvr_from_tput(sn, TN, self.getAvgTputHandles());
91 if strcmp(origmethod,'default') && ~isempty(actualmethod) && ~strcmp(actualmethod,'default')
92 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,['default/
' actualmethod],iter);
94 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,options.method,iter);
96 self.result.Prob.logNormConstAggr = real(lG);
97 if lineTimeoutExceeded(options)
98 self.result.Avg.timedOut = true;
99 line_warning(mfilename,'Solver exceeded the wall-clock time budget (options.timeout=%gs).\n
', options.timeout);
104 % OI closed network -> solver_nc_oi_analyzer (exact pfqn_ncoi), intercepted
105 % before the multiserver->lldscaling conversion; see
106 % _kb/06-solver-catalog.md (NC section, analyzer routing)
107 if nc_is_oi_model(sn) && any(strcmpi(options.method,{'default','exact
'}))
108 line_debug(options, 'NC: order-independent closed network, routing to solver_nc_oi_analyzer
');
109 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_nc_oi_analyzer(sn, options);
110 AN = sn_get_arvr_from_tput(sn, TN, self.getAvgTputHandles());
111 if strcmp(origmethod,'default') && ~strcmp(actualmethod,'default')
112 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,['default/
' actualmethod],iter);
114 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,actualmethod,iter);
116 self.result.Prob.logNormConstAggr = real(lG);
120 switch options.method
122 if sn.nstations == 2 && ~any(sn.nodetype == NodeType.Cache) && any(sn.nodetype == NodeType.Delay) && any(sn.nservers(isfinite(sn.nservers))>1)
123 % 2-station Delay+multiserver (every SolverLN layer submodel):
124 % exact load-dependent CoMoM when product-form, else Seidmann
125 % (comom); see _kb/06-solver-catalog.md (NC section)
126 if self.model.hasProductFormSolution && isempty(sn.lldscaling)
129 sn.lldscaling = ones(sn.nstations,Nt);
131 if sn.nservers(i) > 1 && isfinite(sn.nservers(i))
132 % keep server count c for U normalization; see
133 % _kb/06-solver-catalog.md (NC section)
134 sn.lldscaling(i,:) = min(1:Nt,sn.nservers(i));
138 line_debug(options, 'Default method: 2-station Delay+multiserver product-
form network,
using exact load-dependent comomld
');
140 options.method = 'comom
'; % non-product-form (e.g. LN layer submodels): Seidmann approximation
141 line_debug(options, 'Default method: 2-station Delay+multiserver non-product-
form network,
using comom
');
145 % 'is' needs the same model as 'exact
' (same multiserver conversion),
146 % except OI/P&S which route to solver_nc_analyzer (pfqn_pas_is /
147 % pfqn_oi_is); see _kb/06-solver-catalog.md (NC section)
148 if strcmpi(options.method,'is') && nc_is_pas_model(sn)
149 % no-op: handled by solver_nc_analyzer (pfqn_pas_is)
150 elseif ~self.model.hasProductFormSolution
151 line_error(mfilename,'The %s method
requires the model to have a product-
form solution. This model does not have one. You can use Network.hasProductFormSolution() to check before running the solver.
', options.method);
152 elseif isempty(sn.lldscaling) && any(sn.nservers(isfinite(sn.nservers)) > 1)
153 % convert multiserver to lld ONLY when a genuine multiserver is
154 % present (forcing it on an all-single-server model mishandles a
155 % self-looping closed chain); see _kb/06-solver-catalog.md (NC section)
158 sn.lldscaling = ones(sn.nstations,Nt);
160 if sn.nservers(i) > 1 && isfinite(sn.nservers(i))
161 sn.lldscaling(i,:) = min(1:Nt,sn.nservers(i));
165 line_debug(options, 'Exact method: converted multiserver stations to load-dependent (Nt=%d)
', Nt);
169 % The feature gate ran in runAnalyzerChecks above, through
170 % supportsModelMethod, and reports which features are unsupported. A
171 % coarse supports(self.model) repeat here would only re-reject the same
172 % models with a message that names nothing.
174 Solver.resetRandomGeneratorSeed(options.seed);
176 ci_cache = find(sn.nodetype == NodeType.Cache, 1);
177 hasRetrieval = ~isempty(ci_cache) && isfield(sn.nodeparam{ci_cache}, 'retrievalSystemCapacity
') ...
178 && sn.nodeparam{ci_cache}.retrievalSystemCapacity > 0;
179 if hasRetrieval % delayed-hit (retrieval-system) cache
180 if any(sn.nodetype == NodeType.Source)
181 line_debug(options, 'Open delayed-hit retrieval cache, routing to nc_retrieval_analyzer
');
182 [QN,UN,RN,TN,CN,XN,lG,hitprob,missprob,delayedprob,hitproblist,itemprob,latency,runtime,actualmethod] = solver_nc_retrieval_analyzer(sn, options);
184 line_debug(options, 'Closed integrated delayed-hit retrieval cache, routing to nc_cacheqn_retrieval_analyzer
');
185 [QN,UN,RN,TN,CN,XN,lG,hitprob,missprob,delayedprob,hitproblist,itemprob,latency,runtime,actualmethod] = solver_nc_cacheqn_retrieval_analyzer(sn, options);
188 for ind = 1:sn.nnodes
189 if sn.nodetype(ind) == NodeType.Cache
190 self.model.nodes{ind}.setResultHitProb(hitprob);
191 self.model.nodes{ind}.setResultMissProb(missprob);
192 self.model.nodes{ind}.setResultDelayedHitProb(delayedprob);
193 self.model.nodes{ind}.setResultHitProbList(hitproblist);
194 self.model.nodes{ind}.setResultItemProb(itemprob);
195 self.model.nodes{ind}.setResultResidT(latency);
198 self.model.refreshStruct(true);
199 elseif sn.nclosedjobs == 0 && length(sn.nodetype)==3 && all(sort(sn.nodetype)' == sort([NodeType.Source,NodeType.Cache,NodeType.Sink])) %
is a non-rentrant cache
200 line_debug(options,
'Non-reentrant cache (Source-Cache-Sink), routing to nc_cache_analyzer');
201 % random initialization
202 for ind = 1:sn.nnodes
203 if sn.nodetype(ind) == NodeType.Cache
204 prob = self.model.nodes{ind}.server.hitClass;
206 self.model.nodes{ind}.setResultHitProb(prob);
207 self.model.nodes{ind}.setResultMissProb(1-prob);
210 self.model.refreshChains();
212 [QN,UN,RN,TN,CN,XN,lG,pij,runtime,actualmethod,hitproblist,itemprob] = solver_nc_cache_analyzer(sn, options);
213 self.result.Prob.itemProb = pij;
214 for ind = 1:sn.nnodes
215 if sn.nodetype(ind) == NodeType.Cache
216 self.model.nodes{ind}.setResultHitProbList(hitproblist);
217 self.model.nodes{ind}.setResultItemProb(itemprob);
218 %prob = self.model.nodes{ind}.server.hitClass;
220 hitClass = self.model.nodes{ind}.getHitClass;
221 missClass = self.model.nodes{ind}.getMissClass;
222 hitprob = zeros(1,length(hitClass));
223 for k=1:length(self.model.nodes{ind}.getHitClass)
224 %
for k=1:length(self.model.nodes{ind}.server.hitClass)
225 chain_k = sn.chains(:,k)>0;
226 inchain = sn.chains(chain_k,:)>0;
230 hitprob(k) = XN(h) / sum(XN(inchain),"omitnan");
233 self.model.
nodes{ind}.setResultHitProb(hitprob);
234 self.model.nodes{ind}.setResultMissProb(1-hitprob);
237 self.model.refreshChains;
238 else % queueing network
239 if any(sn.nodetype == NodeType.Cache) %
if integrated caching-queueing
240 line_debug(options,
'Integrated caching-queueing network, routing to nc_cacheqn_analyzer');
241 [QN,UN,RN,TN,CN,XN,lG,hitprob,missprob,runtime,iter,actualmethod] = solver_nc_cacheqn_analyzer(self, options);
242 for ind = 1:sn.nnodes
243 if sn.nodetype(ind) == NodeType.Cache
244 self.model.nodes{ind}.setResultHitProb(hitprob(ind,:));
245 self.model.nodes{ind}.setResultMissProb(missprob(ind,:));
248 self.model.refreshStruct(
true); % Force refresh to get updated actualhitprob/actualmissprob
250 elseif sn_is_mm1k_loss(sn) % single-station M/M/1/K with tail drop
251 % Exact probability-based loss analysis (M/M/1/K stationary
252 % distribution); see qsys_mm1k_loss.
253 queue_ist = sn.nodeToStation(sn.nodetype == NodeType.Queue);
254 source_ist = sn.nodeToStation(sn.nodetype == NodeType.Source);
255 Kcap = sn.cap(queue_ist);
256 lambda = sn.rates(source_ist)*sn.visits{source_ist}(sn.stationToStateful(queue_ist));
257 mu = sn.rates(queue_ist);
259 [Ploss, ~] = qsys_mm1k_loss(lambda, mu, Kcap);
260 Tq = lambda*(1-Ploss); % carried throughput
261 if abs(rho-1) < 1e-10
262 Lsys = Kcap/2; % L
'Hopital limit at rho=1
264 Lsys = rho/(1-rho) - (Kcap+1)*rho^(Kcap+1)/(1-rho^(Kcap+1));
266 Vq = sn.visits{1}(sn.stationToStateful(queue_ist));
268 QN = zeros(M,1); UN = QN; RN = QN; TN = QN; XN = QN; CN = 0;
269 RN(queue_ist) = Lsys/Tq; % per-visit response time (Little)
270 QN(queue_ist) = Lsys;
271 UN(queue_ist) = Tq/mu; % single-server utilization
272 TN(queue_ist) = Tq; % carried (effective) rate
273 TN(source_ist) = lambda; % offered arrival rate
274 XN(queue_ist) = Tq; % system throughput = carried rate
275 CN = RN(queue_ist)*Vq;
276 lG = 0; iter = 1; actualmethod = 'mm1k.loss
';
277 AN = sn_get_arvr_from_tput(sn, TN, self.getAvgTputHandles());
278 if strcmp(origmethod,'default')
279 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,0,['default/
' actualmethod],iter);
281 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,0,actualmethod,iter);
283 self.result.Prob.logNormConstAggr = real(lG);
285 else % ordinary queueing network
286 % Check for open model with single FCR containing single Delay (loss network)
287 if ~sn_has_closed_classes(sn) && sn.nregions == 1
288 regionMatrix = sn.region{1};
289 % Find stations in FCR (those with non-negative constraints)
290 stationsInFCR = find(any(regionMatrix(:,1:end-1) >= 0, 2) | regionMatrix(:,end) >= 0);
291 if length(stationsInFCR) == 1 && isinf(sn.nservers(stationsInFCR(1)))
292 % Single delay node in FCR - check drop rule
293 if all(sn.regionrule(1,:) == DropStrategy.DROP)
294 % Use loss network solver
295 line_debug(options, 'Open model with single FCR + Delay node (DROP), routing to nc_lossn_analyzer
');
296 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_nc_lossn_analyzer(sn, options);
297 AN = sn_get_arvr_from_tput(sn, TN, self.getAvgTputHandles());
298 if strcmp(origmethod,'default') && exist('actualmethod
','var
') && ~strcmp(actualmethod,'default')
299 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,['default/
' actualmethod],iter);
301 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,actualmethod,iter);
303 self.result.Prob.logNormConstAggr = real(lG);
306 % WAITQ (blocking) not supported - error and stop
307 line_error(mfilename, 'SolverNC does not support finite capacity regions with WAITQ (blocking) policy. Use DROP policy instead.
');
311 % Residual FCR (not the single-Delay loss-network case dispatched
312 % above): NC does not enforce the aggregate region limit; reject
313 % instead of silently returning the unconstrained answer.
314 if isfield(sn,'nregions
') && sn.nregions > 0
315 line_error(mfilename,'This model uses a Finite Capacity Region (addRegion) on queueing stations, which
is not supported by SolverNC. Use SolverJMT, or setCapacity
for a single-station limit.
');
317 if ~isempty(sn.lldscaling) || ~isempty(sn.cdscaling) || ~isempty(sn.jdscaling)
318 line_debug(options, 'Load-dependent scaling detected, routing to ncld_analyzer
');
319 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_ncld_analyzer(sn, options);
321 switch options.method
323 if ~sn_has_open_classes(sn)
324 % empty lldscaling here means an all-single-server closed
325 % model -> exact normalizing-constant path (ncld would
326 % mishandle a self-looping chain); see
327 % _kb/06-solver-catalog.md (NC section)
328 line_debug(options, 'NC method=exact, single-server closed model, routing to nc_analyzer
');
329 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_nc_analyzer(sn, options);
330 else%if ~snHasClosedClasses(sn)
331 line_debug(options, 'NC method=exact, open model, routing to nc_analyzer
');
332 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_nc_analyzer(sn, options);
334 case {'rd
','nrp
','nrl
','comomld
'}
335 line_debug(options, 'NC method=%s, routing to ncld_analyzer
', options.method);
336 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_ncld_analyzer(sn, options);
338 line_debug(options, 'NC method=%s, routing to nc_analyzer
', options.method);
339 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_nc_analyzer(sn, options);
344 % Compute average arrival rate at steady-state
345 AN = sn_get_arvr_from_tput(sn, TN, self.getAvgTputHandles());
347if strcmp(origmethod,'default') && exist('actualmethod
','var
') && ~strcmp(actualmethod,'default')
348 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,['default/
' actualmethod],iter);
350 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,actualmethod,iter);
353self.result.Prob.logNormConstAggr = real(lG);
354if lineTimeoutExceeded(options)
355 self.result.Avg.timedOut = true;
356 line_warning(mfilename,'Solver exceeded the wall-clock time budget (options.timeout=%gs).\n
', options.timeout);