LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
runAnalyzer.m
1function runtime = runAnalyzer(self, options)
2% RUNTIME = RUN()
3% Run the solver
4if nargin<2
5 options = self.getOptions;
6end
7% Wall-clock time-budget launch marker (see options.timeout / lineTimeoutExceeded)
8options.timeout_tic = tic;
9
10if strcmp(options.lang,'python')
11 line_debug(options, 'NC: using lang=python, delegating to native line_solver');
12 [QN,UN,RN,TN,AN,WN,runtime] = PYLINE.getAvg(self.name, self.model, options);
13 self.setAvgResults(QN,UN,RN,TN,AN,WN,[],[],runtime,options.method,NaN);
14 return
15end
16
17iter = NaN;
18self.runAnalyzerChecks(options);
19
20sn = self.getStruct();
21if isfield(sn,'immfeed') && ~isempty(sn.immfeed) && any(sn.immfeed(:))
22 line_warning(mfilename,'SolverNC does not handle immediate feedback (immfeed); the solver will treat self-loops as class-switching with re-queueing.\n');
23end
24
25Solver.resetRandomGeneratorSeed(options.seed);
26
27% Show library attribution if verbose and not yet shown
28if options.verbose ~= VerboseLevel.SILENT && ~GlobalConstants.isLibraryAttributionShown()
29 sn = self.getStruct();
30 libs = SolverNC.getLibrariesUsed(sn, options);
31 if ~isempty(libs)
32 line_printf('The solver will leverage %s.\n', strjoin(libs, ', '));
33 GlobalConstants.setLibraryAttributionShown(true);
34 end
35end
36
37origmethod = options.method;
38
39% Warn (debug level) when the requested method is unrecognized. With solver
40% checks disabled (e.g. when SolverNC is used as a SolverLN layer backend,
41% where enableChecks=false bypasses runAnalyzerChecks) an unknown method name
42% silently falls through to the default normalizing-constant analyzer instead
43% of raising an error, which masks typos such as 'adaptive'.
44validMethods = [self.listValidMethods(), {'comomld'}]; % comomld is auto-selected internally from 'default'
45if ~any(strcmpi(origmethod, validMethods))
46 line_debug(options, 'NC: unrecognized method ''%s'', falling back to the default normalizing-constant analyzer (nc_analyzer/comom).', origmethod);
47end
48
49%options.lang = 'java';
50
51switch options.lang
52 case 'java'
53 line_debug(options, 'NC: using lang=java, delegating to JLINE');
54 sn = self.getStruct;
55 jmodel = LINE2JLINE(self.model);
56 %M = jmodel.getNumberOfStatefulNodes;
57 M = jmodel.getNumberOfStations;
58 R = jmodel.getNumberOfClasses;
59 jsolver = JLINE.SolverNC(jmodel, options);
60 [QN,UN,RN,WN,AN,TN] = JLINE.arrayListToResults(jsolver.getAvgTable);
61 runtime = jsolver.result.runtime;
62 CN = [];
63 XN = [];
64 QN = reshape(QN',R,M)';
65 UN = reshape(UN',R,M)';
66 RN = reshape(RN',R,M)';
67 TN = reshape(TN',R,M)';
68 WN = reshape(WN',R,M)';
69 AN = reshape(AN',R,M)';
70 lG = NaN;
71 lastiter = NaN;
72 for ind = 1:sn.nnodes
73 if sn.nodetype(ind) == NodeType.Cache
74 jnode = jmodel.getNodeByIndex(ind-1);
75 self.model.nodes{ind}.setResultHitProb(JLINE.from_jline_matrix(jnode.getHitRatio()));
76 self.model.nodes{ind}.setResultMissProb(JLINE.from_jline_matrix(jnode.getMissRatio()));
77 % Retrieval-cache extras (delayed-hit ratio, per-list hit ratio
78 % and expected latency) so getAvgCacheTable matches the native path.
79 self.model.nodes{ind}.setResultDelayedHitProb(JLINE.from_jline_matrix(jnode.getDelayedHitRatio()));
80 self.model.nodes{ind}.setResultHitProbList(JLINE.from_jline_matrix(jnode.getHitRatioByList()));
81 self.model.nodes{ind}.setResultItemProb(JLINE.from_jline_matrix(jnode.getItemProb()));
82 self.model.nodes{ind}.setResultResidT(JLINE.from_jline_matrix(jnode.getResidT()));
83 end
84 end
85 %self.model.refreshChains();
86 self.model.refreshStruct(true);
87 self.setAvgResults(QN,UN,RN,TN,AN,WN,CN,XN,runtime,options.method,lastiter);
88 self.result.Prob.logNormConstAggr = lG;
89 return
90 case 'matlab'
91 line_debug(options, 'NC: using lang=matlab');
92 sn = getStruct(self); % doesn't need initial state
93
94 % Order-independent (OI) closed network: solved exactly by the
95 % balanced-fairness normalizing constant (pfqn_oi_nc) with the OI
96 % functional-server (pfqn_oi_fnc) identity for the mean queue lengths.
97 % Intercept before the multiserver->lldscaling conversion below, since
98 % an OI station is multiserver but is not a plain min(n,c) load-
99 % dependent station.
100 if nc_is_oi_model(sn) && any(strcmpi(options.method,{'default','exact'}))
101 line_debug(options, 'NC: order-independent closed network, routing to solver_nc_oi_analyzer');
102 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_nc_oi_analyzer(sn, options);
103 AN = sn_get_arvr_from_tput(sn, TN, self.getAvgTputHandles());
104 if strcmp(origmethod,'default') && ~strcmp(actualmethod,'default')
105 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,['default/' actualmethod],iter);
106 else
107 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,actualmethod,iter);
108 end
109 self.result.Prob.logNormConstAggr = real(lG);
110 return
111 end
112
113 switch options.method
114 case 'default'
115 if sn.nstations == 2 && ~any(sn.nodetype == NodeType.Cache) && any(sn.nodetype == NodeType.Delay) && any(sn.nservers(isfinite(sn.nservers))>1)
116 % 2-station Delay+multiserver (the topology of every SolverLN
117 % layer submodel). When the model is product-form the
118 % multiserver can be solved exactly via load-dependent CoMoM
119 % (convert to mu(n)=min(n,c) and route to the ncld analyzer),
120 % which matches CTMC/MVA exactly. Non-product-form cases (e.g.
121 % LN layers with heterogeneous per-class FCFS rates) have no
122 % exact CoMoM-LD solution and fall back to Seidmann's
123 % approximation (comom).
124 if self.model.hasProductFormSolution && isempty(sn.lldscaling)
125 Nt = sum(sn.njobs);
126 if isfinite(Nt)
127 sn.lldscaling = ones(sn.nstations,Nt);
128 for i=1:sn.nstations
129 if sn.nservers(i) > 1 && isfinite(sn.nservers(i))
130 % The queueing solve reads mu(n) from lldscaling, so the
131 % server count must be kept: utilization is the fraction
132 % of the c servers busy, and c is not recoverable from
133 % lldscaling once Nt<c (min(1:Nt,c) is then just 1:Nt).
134 % Zeroing it to 1 made solver_ncld normalize U by
135 % max(lldscaling)=min(Nt,c) instead of c, overstating U
136 % by c/Nt whenever the population is below the servers.
137 sn.lldscaling(i,:) = min(1:Nt,sn.nservers(i));
138 end
139 end
140 end
141 line_debug(options, 'Default method: 2-station Delay+multiserver product-form network, using exact load-dependent comomld');
142 else
143 options.method = 'comom'; % non-product-form (e.g. LN layer submodels): Seidmann approximation
144 line_debug(options, 'Default method: 2-station Delay+multiserver non-product-form network, using comom');
145 end
146 end
147 case {'exact','is'}
148 % 'is' (importance sampling) on an ORDINARY product-form model is
149 % a normalizing-constant estimator and needs the same model as
150 % 'exact': convert multiserver stations to load-dependent here
151 % too, so it routes to solver_ncld_analyzer -> pfqn_ncld ->
152 % pfqn_ld_is rather than falling through to Seidmann's
153 % approximation, which would faithfully estimate a DIFFERENT
154 % (approximated) model.
155 %
156 % OI / pass-and-swap models are the exception: an OI station is
157 % multiserver but is not a plain min(n,c) load-dependent station,
158 % and a P&S tandem has only per-communicating-class product form
159 % (so hasProductFormSolution is false). Leave them untouched for
160 % solver_nc_analyzer to route to pfqn_pas_is / pfqn_oi_is.
161 if strcmpi(options.method,'is') && nc_is_pas_model(sn)
162 % no-op: handled by solver_nc_analyzer (pfqn_pas_is)
163 elseif ~self.model.hasProductFormSolution
164 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);
165 elseif isempty(sn.lldscaling) && any(sn.nservers(isfinite(sn.nservers)) > 1)
166 % if exact/is is requested and does not override a lldscaling assigment
167 % ONLY when a genuine multiserver station is present. Setting
168 % lldscaling=ones on an all-single-server model is a semantic
169 % no-op but forces the load-dependent path (solver_ncld_analyzer),
170 % whose recovery mishandles a single-station-confined
171 % (self-looping) closed chain -- it returned QLen=[1,0,0] for a
172 % Delay+2Queue model with self-looping classes where the exact
173 % answer (CTMC / solver_nc_analyzer) is [0.71,0.14,0.14]. With
174 % no multiserver there is nothing to convert, so leave
175 % lldscaling empty and route to the standard normalizing-constant
176 % path.
177 Nt = sum(sn.njobs);
178 if isfinite(Nt)
179 % trasform multi-server nodes into lld nodes; the server count is
180 % kept so that utilization stays normalized by c (see the
181 % 'default' branch above)
182 sn.lldscaling = ones(sn.nstations,Nt);
183 for i=1:sn.nstations
184 if sn.nservers(i) > 1 && isfinite(sn.nservers(i))
185 sn.lldscaling(i,:) = min(1:Nt,sn.nservers(i));
186 end
187 end
188 end
189 line_debug(options, 'Exact method: converted multiserver stations to load-dependent (Nt=%d)', Nt);
190 end
191 end
192
193 % The feature gate ran in runAnalyzerChecks above, through
194 % supportsModelMethod, and reports which features are unsupported. A
195 % coarse supports(self.model) repeat here would only re-reject the same
196 % models with a message that names nothing.
197
198 Solver.resetRandomGeneratorSeed(options.seed);
199
200 ci_cache = find(sn.nodetype == NodeType.Cache, 1);
201 hasRetrieval = ~isempty(ci_cache) && isfield(sn.nodeparam{ci_cache}, 'retrievalSystemCapacity') ...
202 && sn.nodeparam{ci_cache}.retrievalSystemCapacity > 0;
203 if hasRetrieval % delayed-hit (retrieval-system) cache
204 if any(sn.nodetype == NodeType.Source)
205 line_debug(options, 'Open delayed-hit retrieval cache, routing to nc_retrieval_analyzer');
206 [QN,UN,RN,TN,CN,XN,lG,hitprob,missprob,delayedprob,hitproblist,itemprob,latency,runtime,actualmethod] = solver_nc_retrieval_analyzer(sn, options);
207 else
208 line_debug(options, 'Closed integrated delayed-hit retrieval cache, routing to nc_cacheqn_retrieval_analyzer');
209 [QN,UN,RN,TN,CN,XN,lG,hitprob,missprob,delayedprob,hitproblist,itemprob,latency,runtime,actualmethod] = solver_nc_cacheqn_retrieval_analyzer(sn, options);
210 end
211 iter = NaN;
212 for ind = 1:sn.nnodes
213 if sn.nodetype(ind) == NodeType.Cache
214 self.model.nodes{ind}.setResultHitProb(hitprob);
215 self.model.nodes{ind}.setResultMissProb(missprob);
216 self.model.nodes{ind}.setResultDelayedHitProb(delayedprob);
217 self.model.nodes{ind}.setResultHitProbList(hitproblist);
218 self.model.nodes{ind}.setResultItemProb(itemprob);
219 self.model.nodes{ind}.setResultResidT(latency);
220 end
221 end
222 self.model.refreshStruct(true);
223 elseif sn.nclosedjobs == 0 && length(sn.nodetype)==3 && all(sort(sn.nodetype)' == sort([NodeType.Source,NodeType.Cache,NodeType.Sink])) % is a non-rentrant cache
224 line_debug(options, 'Non-reentrant cache (Source-Cache-Sink), routing to nc_cache_analyzer');
225 % random initialization
226 for ind = 1:sn.nnodes
227 if sn.nodetype(ind) == NodeType.Cache
228 prob = self.model.nodes{ind}.server.hitClass;
229 prob(prob>0) = 0.5;
230 self.model.nodes{ind}.setResultHitProb(prob);
231 self.model.nodes{ind}.setResultMissProb(1-prob);
232 end
233 end
234 self.model.refreshChains();
235 % start iteration
236 [QN,UN,RN,TN,CN,XN,lG,pij,runtime,actualmethod,hitproblist,itemprob] = solver_nc_cache_analyzer(sn, options);
237 self.result.Prob.itemProb = pij;
238 for ind = 1:sn.nnodes
239 if sn.nodetype(ind) == NodeType.Cache
240 self.model.nodes{ind}.setResultHitProbList(hitproblist);
241 self.model.nodes{ind}.setResultItemProb(itemprob);
242 %prob = self.model.nodes{ind}.server.hitClass;
243 %prob(prob>0) = 0.5;
244 hitClass = self.model.nodes{ind}.getHitClass;
245 missClass = self.model.nodes{ind}.getMissClass;
246 hitprob = zeros(1,length(hitClass));
247 for k=1:length(self.model.nodes{ind}.getHitClass)
248 % for k=1:length(self.model.nodes{ind}.server.hitClass)
249 chain_k = sn.chains(:,k)>0;
250 inchain = sn.chains(chain_k,:)>0;
251 h = hitClass(k);
252 m = missClass(k);
253 if h>0 && m>0
254 hitprob(k) = XN(h) / sum(XN(inchain),"omitnan");
255 end
256 end
257 self.model.nodes{ind}.setResultHitProb(hitprob);
258 self.model.nodes{ind}.setResultMissProb(1-hitprob);
259 end
260 end
261 self.model.refreshChains;
262 else % queueing network
263 if any(sn.nodetype == NodeType.Cache) % if integrated caching-queueing
264 line_debug(options, 'Integrated caching-queueing network, routing to nc_cacheqn_analyzer');
265 [QN,UN,RN,TN,CN,XN,lG,hitprob,missprob,runtime,iter,actualmethod] = solver_nc_cacheqn_analyzer(self, options);
266 for ind = 1:sn.nnodes
267 if sn.nodetype(ind) == NodeType.Cache
268 self.model.nodes{ind}.setResultHitProb(hitprob(ind,:));
269 self.model.nodes{ind}.setResultMissProb(missprob(ind,:));
270 end
271 end
272 self.model.refreshStruct(true); % Force refresh to get updated actualhitprob/actualmissprob
273 sn = self.model.sn;
274 else % ordinary queueing network
275 % Check for open model with single FCR containing single Delay (loss network)
276 if ~sn_has_closed_classes(sn) && sn.nregions == 1
277 regionMatrix = sn.region{1};
278 % Find stations in FCR (those with non-negative constraints)
279 stationsInFCR = find(any(regionMatrix(:,1:end-1) >= 0, 2) | regionMatrix(:,end) >= 0);
280 if length(stationsInFCR) == 1 && isinf(sn.nservers(stationsInFCR(1)))
281 % Single delay node in FCR - check drop rule
282 if all(sn.regionrule(1,:) == DropStrategy.DROP)
283 % Use loss network solver
284 line_debug(options, 'Open model with single FCR + Delay node (DROP), routing to nc_lossn_analyzer');
285 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_nc_lossn_analyzer(sn, options);
286 AN = sn_get_arvr_from_tput(sn, TN, self.getAvgTputHandles());
287 if strcmp(origmethod,'default') && exist('actualmethod','var') && ~strcmp(actualmethod,'default')
288 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,['default/' actualmethod],iter);
289 else
290 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,actualmethod,iter);
291 end
292 self.result.Prob.logNormConstAggr = real(lG);
293 return
294 else
295 % WAITQ (blocking) not supported - error and stop
296 line_error(mfilename, 'SolverNC does not support finite capacity regions with WAITQ (blocking) policy. Use DROP policy instead.');
297 end
298 end
299 end
300 % Residual FCR (not the single-Delay loss-network case dispatched
301 % above): NC does not enforce the aggregate region limit; reject
302 % instead of silently returning the unconstrained answer.
303 if isfield(sn,'nregions') && sn.nregions > 0
304 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.');
305 end
306 if ~isempty(sn.lldscaling) || ~isempty(sn.cdscaling)
307 line_debug(options, 'Load-dependent scaling detected, routing to ncld_analyzer');
308 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_ncld_analyzer(sn, options);
309 else
310 switch options.method
311 case 'exact'
312 if ~sn_has_open_classes(sn)
313 % This branch is reached only with EMPTY lldscaling
314 % (the outer test routes any load-dependent model to
315 % ncld). Genuine multiserver stations were converted
316 % to lldscaling above, so an empty lldscaling here
317 % means an all-single-server closed model, which the
318 % standard normalizing-constant path solves exactly.
319 % Routing it to solver_ncld_analyzer instead
320 % mishandles a single-station-confined (self-looping)
321 % closed chain -- returning QLen=[1,0,0] for a
322 % Delay+2Queue model with self-looping classes where
323 % the exact answer is [0.71,0.14,0.14].
324 line_debug(options, 'NC method=exact, single-server closed model, routing to nc_analyzer');
325 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_nc_analyzer(sn, options);
326 else%if ~snHasClosedClasses(sn)
327 line_debug(options, 'NC method=exact, open model, routing to nc_analyzer');
328 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_nc_analyzer(sn, options);
329 end
330 case {'rd','nrp','nrl','comomld'}
331 line_debug(options, 'NC method=%s, routing to ncld_analyzer', options.method);
332 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_ncld_analyzer(sn, options);
333 otherwise
334 line_debug(options, 'NC method=%s, routing to nc_analyzer', options.method);
335 [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_nc_analyzer(sn, options);
336 end
337 end
338 end
339 end
340 % Compute average arrival rate at steady-state
341 AN = sn_get_arvr_from_tput(sn, TN, self.getAvgTputHandles());
342end
343if strcmp(origmethod,'default') && exist('actualmethod','var') && ~strcmp(actualmethod,'default')
344 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,['default/' actualmethod],iter);
345else
346 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,actualmethod,iter);
347end
348
349self.result.Prob.logNormConstAggr = real(lG);
350if lineTimeoutExceeded(options)
351 self.result.Avg.timedOut = true;
352 line_warning(mfilename,'Solver exceeded the wall-clock time budget (options.timeout=%gs).\n', options.timeout);
353end
354end
Definition fjtag.m:157
Definition Station.m:245