LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
runAnalyzer.m
1function runtime = runAnalyzer(self, options)
2% RUNTIME = RUNANALYZER(OPTIONS)
3% Run the solver
4
5if nargin<2
6 options = self.getOptions;
7end
8
9self.runAnalyzerChecks(options);
10
11Solver.resetRandomGeneratorSeed(options.seed);
12
13% Show library attribution if verbose and not yet shown
14if options.verbose ~= VerboseLevel.SILENT && ~GlobalConstants.isLibraryAttributionShown()
15 sn = self.getStruct();
16 libs = SolverMVA.getLibrariesUsed(sn, options);
17 if ~isempty(libs)
18 line_printf('The solver will leverage %s.\n', strjoin(libs, ', '));
19 GlobalConstants.setLibraryAttributionShown(true);
20 end
21end
22
23iter = 0;
24%options.lang='java';
25
26switch options.lang
27 case 'java'
28 sn = getStruct(self); % doesn't need initial state
29 jmodel = LINE2JLINE(self.model);
30 %M = jmodel.getNumberOfStatefulNodes;
31 M = jmodel.getNumberOfStations;
32 R = jmodel.getNumberOfClasses;
33 jsolver = JLINE.SolverMVA(jmodel, options);
34 [QN,UN,RN,WN,AN,TN] = JLINE.arrayListToResults(jsolver.getAvgTable);
35 runtime = jsolver.result.runtime;
36 CN = [];
37 XN = [];
38 QN = reshape(QN',R,M)';
39 UN = reshape(UN',R,M)';
40 RN = reshape(RN',R,M)';
41 TN = reshape(TN',R,M)';
42 WN = reshape(WN',R,M)';
43 AN = reshape(AN',R,M)';
44 lG = NaN;
45 lastiter = NaN;
46 for ind = 1:sn.nnodes
47 if sn.nodetype(ind) == NodeType.Cache
48 self.model.nodes{ind}.setResultHitProb(JLINE.from_jline_matrix(jmodel.getNodeByIndex(ind-1).getHitRatio()));
49 self.model.nodes{ind}.setResultMissProb(JLINE.from_jline_matrix(jmodel.getNodeByIndex(ind-1).getMissRatio()));
50 end
51 end
52 %self.model.refreshChains();
53 self.model.refreshStruct(true);
54 self.setAvgResults(QN,UN,RN,TN,AN,WN,CN,XN,runtime,options.method,lastiter);
55 self.result.Prob.logNormConstAggr = lG;
56 return
57 case 'matlab'
58 sn = getStruct(self); % doesn't need initial state
59 forkLoop = true;
60 forkIter = 0;
61 % create artificial classes arrival rates
62 forkLambda = GlobalConstants.FineTol * ones(1, 2*sn.nclasses*sum(sn.nodetype==NodeType.Fork));
63 QN = GlobalConstants.Immediate * ones(1, sn.nclasses);
64 QN_1 = 0*QN;
65 UN = 0*QN;
66 while (forkLoop && forkIter < options.iter_max)
67 if self.model.hasFork
68 forkIter = forkIter + 1;
69 if forkIter == 1
70 switch options.config.fork_join
71 case {'heidelberger-trivedi', 'ht'}
72 [nonfjmodel, fjclassmap, fjforkmap, fj_auxiliary_delays] = ModelAdapter.ht(self.model);
73 case {'mmt', 'default', 'fjt'}
74 [nonfjmodel, fjclassmap, fjforkmap, fanout] = ModelAdapter.mmt(self.model, forkLambda);
75 [outer_forks, parent_forks] = ModelAdapter.sortForks(sn, fjforkmap, fjclassmap, nonfjmodel);
76 end
77 elseif ~strcmp(options.config.fork_join, 'heidelberger-trivedi') & ~strcmp(options.config.fork_join, 'ht')
78 %line_printf('Fork-join iteration %d\n',forkIter);
79 nonfjSource = nonfjmodel.getSource;
80 for r=1:length(fjclassmap) % r is the auxiliary class
81 s = fjclassmap(r);
82 if s>0
83 if fanout(r)>0
84 if ~nonfjSource.arrivalProcess{r}.isDisabled
85 nonfjSource.arrivalProcess{r}.setRate((fanout(r)-1)*forkLambda(r));
86 end
87 end
88 end
89 end
90 nonfjmodel.refreshRates();
91 end
92 sn = nonfjmodel.getStruct(false); % this ensures that we solve nonfjmodel instead of the original model
93 % Convergence check: handle zero/NaN queue lengths from size mismatch and Immediate activities
94 qn_ratio = QN_1 ./ QN;
95 qn_ratio(isnan(qn_ratio)) = 1; % 0/0 = NaN → treat as converged (ratio=1)
96 qn_ratio(isinf(qn_ratio)) = 0; % x/0 = Inf → treat as not converged (ratio=0, error=1)
97 if isequal(size(QN_1), size(QN)) && max(abs(1 - qn_ratio(:))) < GlobalConstants.CoarseTol && (forkIter > 2)
98 forkLoop = false;
99 else
100 if self.model.hasOpenClasses
101 sourceIndex = self.model.getSource.index;
102 UNnosource = UN; UNnosource(sourceIndex,:) = 0;
103 if any(find(sum(UNnosource(:,isinf(sn.njobs(1:size(QN,2)))),2)>0.99 * sn.nservers))
104 line_warning(mfilename,'The model may be unstable: the utilization of station %i for open classes exceeds 99 percent.\n',maxpos(sum(UNnosource,2)));
105 end
106 end
107 QN_1 = QN;
108 end
109 else
110 forkLoop = false;
111 end
112 if strcmp(options.method,'exact') && ~self.model.hasProductFormSolution
113 line_error(mfilename,'The exact method requires the model to have a product-form solution. This model does not have one.\nYou can use Network.hasProductFormSolution() to check before running the solver.\n Run the ''mva'' method to obtain an approximation based on the exact MVA algorithm.\n');
114 end
115 if strcmp(options.method,'mva') && ~self.model.hasProductFormSolution
116 line_warning(mfilename,'The exact method requires the model to have a product-form solution. This model does not have one.\nYou can use Network.hasProductFormSolution() to check before running the solver.\nSolverMVA will return an approximation generated by an exact MVA algorithm.');
117 end
118
119 method = options.method;
120
121 % Check for size-based policies (SRPT, PSJF, FB, LRPT, SETF) in multiclass open systems
122 queueIdx = find(sn.nodetype == NodeType.Queue);
123 isSizeBasedPolicy = false;
124 if ~isempty(queueIdx)
125 schedType = sn.sched(sn.nodeToStation(queueIdx(1)));
126 isSizeBasedPolicy = ismember(schedType, [SchedStrategy.SRPT, SchedStrategy.PSJF, SchedStrategy.FB, SchedStrategy.LRPT, SchedStrategy.SETF]);
127 end
128
129 if sn.nclosedjobs == 0 && length(sn.nodetype)==3 && all(sort(sn.nodetype)' == sort([NodeType.Source,NodeType.Queue,NodeType.Sink])) && isSizeBasedPolicy
130 % Multiclass open system with size-based scheduling (SRPT, PSJF, FB, LRPT, SETF)
131 [QN,UN,RN,TN,CN,XN,lG,runtime,lastiter] = solver_mva_qsys_sizebased_analyzer(sn, options, schedType);
132 elseif sn.nclasses==1 && sn.nclosedjobs == 0 && length(sn.nodetype)==3 && all(sort(sn.nodetype)' == sort([NodeType.Source,NodeType.Queue,NodeType.Sink])) % is an open queueing system
133 [QN,UN,RN,TN,CN,XN,lG,runtime,lastiter] = solver_mva_qsys_analyzer(sn, options);
134 elseif sn.nclasses>1 && sn.nclosedjobs == 0 && length(sn.nodetype)==3 && all(sort(sn.nodetype)' == sort([NodeType.Source,NodeType.Queue,NodeType.Sink])) && sn.sched(find(sn.nodetype==NodeType.Queue)) == SchedStrategy.POLLING % is an open polling system
135 [QN,UN,RN,TN,CN,XN,lG,runtime,lastiter] = solver_mva_polling_analyzer(sn, options);
136 elseif sn.nclosedjobs == 0 && length(sn.nodetype)==3 && all(sort(sn.nodetype)' == sort([NodeType.Source,NodeType.Cache,NodeType.Sink])) % is a non-rentrant cache
137 % random initialization
138 for ind = 1:sn.nnodes
139 if sn.nodetype(ind) == NodeType.Cache
140 prob = self.model.nodes{ind}.server.hitClass;
141 prob(prob>0) = 0.5;
142 self.model.nodes{ind}.setResultHitProb(prob);
143 self.model.nodes{ind}.setResultMissProb(1-prob);
144 end
145 end
146 self.model.refreshChains();
147 % start iteration
148 [QN,UN,RN,TN,CN,XN,lG,runtime,lastiter,actualmethod] = solver_mva_cache_analyzer(sn, options);
149
150 for ind = 1:sn.nnodes
151 if sn.nodetype(ind) == NodeType.Cache
152 hitClass = self.model.nodes{ind}.getHitClass;
153 missClass = self.model.nodes{ind}.getMissClass;
154 hitprob = zeros(1,length(hitClass));
155 for k=1:length(self.model.nodes{ind}.getHitClass)
156 chain_k = sn.chains(:,k)>0;
157 inchain = sn.chains(chain_k,:)>0;
158 h = hitClass(k);
159 m = missClass(k);
160 if h>0 && m>0
161 hitprob(k) = XN(h) / sum(XN(inchain),"omitnan"); %#ok<NANSUM>
162 end
163 end
164 self.model.nodes{ind}.setResultHitProb(hitprob);
165 self.model.nodes{ind}.setResultMissProb(1-hitprob);
166 end
167 end
168 %self.model.refreshChains();
169 self.model.refreshStruct(true);
170 else % queueing network
171 if any(sn.nodetype == NodeType.Cache) % if integrated caching-queueing
172 [QN,UN,RN,TN,CN,XN,lG,hitprob,missprob,runtime,lastiter] = solver_mva_cacheqn_analyzer(self, options);
173 for ind = 1:sn.nnodes
174 if sn.nodetype(ind) == NodeType.Cache
175 self.model.nodes{ind}.setResultHitProb(hitprob(ind,:));
176 self.model.nodes{ind}.setResultMissProb(missprob(ind,:));
177 end
178 end
179 %self.model.refreshChains();
180 self.model.refreshStruct(true);
181 else % ordinary queueing network
182 switch method
183 case {'aba.upper', 'aba.lower', 'bjb.upper', 'bjb.lower', 'pb.upper', 'pb.lower', 'gb.upper', 'gb.lower', 'sb.upper', 'sb.lower'}
184 [QN,UN,RN,TN,CN,XN,lG,runtime,lastiter] = solver_mva_bound_analyzer(sn, options);
185 otherwise
186 if ~isempty(sn.lldscaling) || ~isempty(sn.cdscaling)
187 [QN,UN,RN,TN,CN,XN,lG,runtime,lastiter,actualmethod] = solver_mvald_analyzer(sn, options);
188 else
189 [QN,UN,RN,TN,CN,XN,lG,runtime,lastiter,actualmethod] = solver_mva_analyzer(sn, options);
190 end
191 end
192 end
193 end
194 if self.model.hasFork
195 nonfjstruct = sn;
196 sn = self.getStruct;
197 % Pre-compute linked routing matrix once (loop-invariant)
198 switch options.config.fork_join
199 case {'mmt', 'default', 'fjt'}
200 Pcs = cell2mat(nonfjmodel.getLinkedRoutingMatrix);
201 end
202 for f=find(sn.nodetype == NodeType.Fork)'
203 switch options.config.fork_join
204 case {'mmt', 'default', 'fjt'}
205 TNfork = zeros(1,sn.nclasses);
206 for c=1:sn.nchains
207 inchain = find(sn.chains(c,:));
208 for r=inchain(:)'
209 TNfork(r) = (sn.nodevisits{c}(parent_forks(f),r) / sum(sn.visits{c}(sn.stationToStateful(sn.refstat(r)),inchain))) * sum(TN(sn.refstat(r),inchain));
210 end
211 end
212 % find join associated to fork f
213 joinIdx = find(sn.fj(f,:));
214 forkauxclasses = find(fjforkmap==f);
215 for s=forkauxclasses(:)'
216 r = fjclassmap(s); % original class associated to auxiliary class s
217 if isempty(joinIdx)
218 forkLambda(s) = mean([forkLambda(s); TNfork(r)],1);
219 else
220 joinStat = sn.nodeToStation(joinIdx);
221 TN(sn.nodeToStation(joinIdx),r) = TN(sn.nodeToStation(joinIdx),r) + sum(TN(sn.nodeToStation(joinIdx), find(fjclassmap == r))) - TN(sn.nodeToStation(joinIdx), s);
222 forkLambda(s) = mean([forkLambda(s); TN(sn.nodeToStation(joinIdx),r)],1);
223 end
224 if isempty(joinIdx) || ~outer_forks(f, r)
225 % No join nodes for this fork, no synchronisation delay
226 continue;
227 end
228 % Find the parallel paths coming out of the fork
229 ri = ModelAdapter.findPathsCS(sn, Pcs, f, joinIdx, r, [r,s], QN, TN, 0, fjclassmap, fjforkmap, nonfjmodel);
230 if isempty(ri)
231 % No routing from fork for this class - set sync delay to 0 (Immediate)
232 % Matches JAR behavior where empty Matrix gives syncDelay = 0
233 syncDelay = 0;
234 else
235 lambdai = 1./ri;
236 d0 = 0;
237 parallel_branches = length(ri);
238 for pow=0:(parallel_branches - 1)
239 current_sum = sum(1./sum(nchoosek(lambdai, pow + 1),2));
240 d0 = d0 + (-1)^pow * current_sum;
241 end
242 syncDelay = d0*sn.nodeparam{f}.fanOut - mean(ri);
243 end
244 % Set the synchronisation delays
245 nonfjmodel.nodes{joinIdx}.setService(nonfjmodel.classes{s}, Exp.fitMean(syncDelay));
246 if outer_forks(f, r)
247 nonfjmodel.nodes{joinIdx}.setService(nonfjmodel.classes{r}, Exp.fitMean(syncDelay));
248 end
249 end
250 case {'heidelberger-trivedi', 'ht'}
251 joinIdx = find(sn.fj(f,:));
252 for c=1:sn.nchains
253 inchain = find(sn.chains(c,:));
254 for r=inchain(:)'
255 if sn.nodevisits{c}(f,r) == 0
256 continue;
257 end
258 % Obtain the response times on the parallel branches
259 ri = RN(:, find(fjclassmap == r));
260 ri(isnan(ri) | isinf(ri)) = 0;
261 ri = sum(ri, 1, "omitnan") - RN(nonfjstruct.nodeToStation(fj_auxiliary_delays{joinIdx}), find(fjclassmap == r)) - RN(nonfjstruct.nodeToStation(joinIdx), find(fjclassmap == r));
262 lambdai = 1./ri;
263 d0 = 0;
264 parallel_branches = length(self.model.nodes{f}.output.outputStrategy{r}{3});
265 for pow=0:(parallel_branches - 1)
266 current_sum = sum(1./sum(nchoosek(lambdai, pow + 1),2));
267 d0 = d0 + (-1)^pow * current_sum;
268 end
269 di = d0*sn.nodeparam{f}.fanOut - ri;
270 r0 = sum(RN(:, inchain), 2);
271 r0(isnan(r0) | isinf(r0)) = 0;
272 r0 = sum(r0, 1, "omitnan") - RN(nonfjstruct.nodeToStation(joinIdx), r);
273 % Update the delays at the join node and at the auxiliary delay
274 nonfjmodel.nodes{joinIdx}.setService(nonfjmodel.classes{r}, Exp.fitMean(d0*sn.nodeparam{f}.fanOut));
275 idx = 1;
276 for s=find(fjclassmap == r)
277 nonfjmodel.nodes{joinIdx}.setService(nonfjmodel.classes{s}, Exp.fitMean(di(idx)));
278 idx = idx + 1;
279 nonfjmodel.nodes{fj_auxiliary_delays{joinIdx}}.setService(nonfjmodel.classes{s}, Exp.fitMean(r0));
280 end
281
282 end
283 end
284 end
285 end
286 % Batch refreshRates after all sync delay updates (moved out of inner loops for performance)
287 switch options.config.fork_join
288 case {'mmt', 'default', 'fjt'}
289 nonfjmodel.refreshRates();
290 end
291 switch options.config.fork_join
292 case {'heidelberger-trivedi', 'ht'}
293 nonfjmodel.refreshStruct();
294 % Delete the queue lengths, response times, throughputs and utilizations of the original classes at the join nodes
295 QN(nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonzeros(fjclassmap)) = 0;
296 RN(nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonzeros(fjclassmap)) = 0;
297 % Save the throughputs of the original classes at the join node
298 TN_orig = TN(nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonzeros(fjclassmap));
299 TN(nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonzeros(fjclassmap)) = 0;
300 UN(nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonzeros(fjclassmap)) = 0;
301
302 % Remove the times at the auxiliary delay
303 QN(nonfjstruct.nodeToStation(cell2mat(fj_auxiliary_delays)),:) = [];
304 UN(nonfjstruct.nodeToStation(cell2mat(fj_auxiliary_delays)),:) = [];
305 RN(nonfjstruct.nodeToStation(cell2mat(fj_auxiliary_delays)),:) = [];
306 TN(nonfjstruct.nodeToStation(cell2mat(fj_auxiliary_delays)),:) = [];
307 % merge back artificial classes into their original classes
308 for r=1:length(fjclassmap)
309 s = fjclassmap(r);
310 if s>0
311 QN(:,s) = QN(:,s) + QN(:,r);
312 UN(:,s) = UN(:,s) + UN(:,r);
313 % Add all throughputs of the auxiliary classes to facilitate the computation of the response times
314 TN(:,s) = TN(:,s) + TN(:,r);
315 RN(:,s) = QN(:,s) ./ TN(:,s);
316 end
317 end
318 % Re-set the throughputs for the original classes
319 TN(nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonzeros(fjclassmap)) = TN_orig;
320 case {'mmt', 'default', 'fjt'}
321 TN_orig = TN([nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Source))], nonzeros(fjclassmap));
322 % merge back artificial classes into their original classes
323 for r=1:length(fjclassmap)
324 s = fjclassmap(r);
325 if s>0
326 QN(:,s) = QN(:,s) + QN(:,r);
327 UN(:,s) = UN(:,s) + UN(:,r);
328 TN(:,s) = TN(:,s) + TN(:,r);
329 %RN(:,s) = RN(:,s) + RN(:,r);
330 % for i=find(snorig.nodetype == NodeType.Delay | snorig.nodetype == NodeType.Queue)'
331 % TN(snorig.nodeToStation(i),s) = TN(snorig.nodeToStation(i),s) + TN(snorig.nodeToStation(i),r);
332 % end
333 RN(:,s) = QN(:,s) ./ TN(:,s);
334 %CN(:,s) = CN(:,s) + CN(:,r);
335 %XN(:,s) = XN(:,s) + XN(:,r);
336 end
337 end
338 TN([nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Source))], nonzeros(fjclassmap)) = TN_orig;
339 end
340 QN(:,fjclassmap>0) = [];
341 UN(:,fjclassmap>0) = [];
342 RN(:,fjclassmap>0) = [];
343 TN(:,fjclassmap>0) = [];
344 CN(:,fjclassmap>0) = [];
345 XN(:,fjclassmap>0) = [];
346 end
347 iter = iter + lastiter;
348 % Cap accumulated iterations for stability (Python parity)
349 if iter > 10000
350 iter = 10000;
351 break; % Exit forkLoop early
352 end
353 end
354
355 sn = self.model.getStruct();
356
357 % Compute average residence time at steady-state
358 AN = sn_get_arvr_from_tput(sn, TN, self.getAvgTputHandles());
359 WN = sn_get_residt_from_respt(sn, RN, self.getAvgResidTHandles());
360 if strcmp(method,'default') && exist('actualmethod','var')
361 self.setAvgResults(QN,UN,RN,TN,AN,WN,CN,XN,runtime,['default/' actualmethod],iter);
362 else
363 self.setAvgResults(QN,UN,RN,TN,AN,WN,CN,XN,runtime,method,iter);
364 end
365 self.result.Prob.logNormConstAggr = lG;
366end
367end
368
369
Definition mmt.m:93