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
10[pyHandled, options, pyRuntime] = self.runAnalyzerPreamble(options, 'NC');
11if pyHandled
12 runtime = pyRuntime;
13 return
14end
15
16iter = NaN;
17self.runAnalyzerChecks(options);
18
19sn = self.getStruct();
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');
22end
23
24Solver.resetRandomGeneratorSeed(options.seed);
25
26
27origmethod = options.method;
28
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);
34end
35
36%options.lang = 'java';
37
38switch options.lang
39 case 'java'
40 line_debug(options, 'NC: using lang=java, delegating to JLINE');
41 sn = self.getStruct;
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;
49 CN = [];
50 XN = [];
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)';
57 lG = NaN;
58 lastiter = NaN;
59 for ind = 1:sn.nnodes
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()));
70 end
71 end
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;
76 return
77 case 'matlab'
78 line_debug(options, 'NC: using lang=matlab');
79 sn = getStruct(self); % doesn't need initial state
80
81 % Fork-join: solver-agnostic fjFixedPoint with ncDispatch as inner solve,
82 % intercepted first; see _kb/06-solver-catalog.md (MVA section)
83 if self.model.hasFork
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);
93 else
94 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,options.method,iter);
95 end
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);
100 end
101 return
102 end
103
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);
113 else
114 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,actualmethod,iter);
115 end
116 self.result.Prob.logNormConstAggr = real(lG);
117 return
118 end
119
120 switch options.method
121 case 'default'
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)
127 Nt = sum(sn.njobs);
128 if isfinite(Nt)
129 sn.lldscaling = ones(sn.nstations,Nt);
130 for i=1:sn.nstations
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));
135 end
136 end
137 end
138 line_debug(options, 'Default method: 2-station Delay+multiserver product-form network, using exact load-dependent comomld');
139 else
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');
142 end
143 end
144 case {'exact','is'}
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)
156 Nt = sum(sn.njobs);
157 if isfinite(Nt)
158 sn.lldscaling = ones(sn.nstations,Nt);
159 for i=1:sn.nstations
160 if sn.nservers(i) > 1 && isfinite(sn.nservers(i))
161 sn.lldscaling(i,:) = min(1:Nt,sn.nservers(i));
162 end
163 end
164 end
165 line_debug(options, 'Exact method: converted multiserver stations to load-dependent (Nt=%d)', Nt);
166 end
167 end
168
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.
173
174 Solver.resetRandomGeneratorSeed(options.seed);
175
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);
183 else
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);
186 end
187 iter = NaN;
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);
196 end
197 end
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;
205 prob(prob>0) = 0.5;
206 self.model.nodes{ind}.setResultHitProb(prob);
207 self.model.nodes{ind}.setResultMissProb(1-prob);
208 end
209 end
210 self.model.refreshChains();
211 % start iteration
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;
219 %prob(prob>0) = 0.5;
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;
227 h = hitClass(k);
228 m = missClass(k);
229 if h>0 && m>0
230 hitprob(k) = XN(h) / sum(XN(inchain),"omitnan");
231 end
232 end
233 self.model.nodes{ind}.setResultHitProb(hitprob);
234 self.model.nodes{ind}.setResultMissProb(1-hitprob);
235 end
236 end
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,:));
246 end
247 end
248 self.model.refreshStruct(true); % Force refresh to get updated actualhitprob/actualmissprob
249 sn = self.model.sn;
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);
258 rho = lambda/mu;
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
263 else
264 Lsys = rho/(1-rho) - (Kcap+1)*rho^(Kcap+1)/(1-rho^(Kcap+1));
265 end
266 Vq = sn.visits{1}(sn.stationToStateful(queue_ist));
267 M = sn.nstations;
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);
280 else
281 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,0,actualmethod,iter);
282 end
283 self.result.Prob.logNormConstAggr = real(lG);
284 return
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);
300 else
301 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,actualmethod,iter);
302 end
303 self.result.Prob.logNormConstAggr = real(lG);
304 return
305 else
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.');
308 end
309 end
310 end
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.');
316 end
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);
320 else
321 switch options.method
322 case 'exact'
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);
333 end
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);
337 otherwise
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);
340 end
341 end
342 end
343 end
344 % Compute average arrival rate at steady-state
345 AN = sn_get_arvr_from_tput(sn, TN, self.getAvgTputHandles());
346end
347if strcmp(origmethod,'default') && exist('actualmethod','var') && ~strcmp(actualmethod,'default')
348 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,['default/' actualmethod],iter);
349else
350 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,actualmethod,iter);
351end
352
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);
357end
358end
Definition fjtag.m:161