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 %snorig = sn;
60 forkLoop = true;
61 forkIter = 0;
62 % create artificial classes arrival rates
63 forkLambda = GlobalConstants.FineTol * ones(1, 2*sn.nclasses*sum(sn.nodetype==NodeType.Fork));
64 QN = GlobalConstants.Immediate * ones(1, sn.nclasses);
65 QN_1 = 0*QN;
66 UN = 0*QN;
67 while (forkLoop && forkIter < options.iter_max)
68 if self.model.hasFork
69 forkIter = forkIter + 1;
70 if forkIter == 1
71 switch options.config.fork_join
72 case {'heidelberger-trivedi', 'ht'}
73 [nonfjmodel, fjclassmap, fjforkmap, fj_auxiliary_delays] = ModelAdapter.ht(self.model);
74 case {'mmt', 'default', 'fjt'}
75 [nonfjmodel, fjclassmap, fjforkmap, fanout] = ModelAdapter.mmt(self.model, forkLambda);
76 [outer_forks, parent_forks] = ModelAdapter.sortForks(sn, fjforkmap, fjclassmap, nonfjmodel);
77 end
78 elseif ~strcmp(options.config.fork_join, 'heidelberger-trivedi') & ~strcmp(options.config.fork_join, 'ht')
79 %line_printf('Fork-join iteration %d\n',forkIter);
80 for r=1:length(fjclassmap) % r is the auxiliary class
81 s = fjclassmap(r);
82 if s>0
83 nonfjSource = nonfjmodel.getSource;
84 if fanout(r)>0
85 if ~nonfjSource.arrivalProcess{r}.isDisabled
86 nonfjSource.arrivalProcess{r}.setRate((fanout(r)-1)*forkLambda(r));
87 end
88 end
89 end
90 % TODO: this was changed without too many checks into refreshRates in both MATLAB and Java,
91 % to be validated further especially for CacheTasks but the results seem unchanged.
92 % nonfjmodel.refreshStruct();
93 nonfjmodel.refreshRates();
94 end
95 end
96 sn = nonfjmodel.getStruct(false); % this ensures that we solve nonfjmodel instead of the original model
97 if max(abs(1-QN_1./ QN)) < GlobalConstants.CoarseTol & (forkIter > 2) %#ok<AND2>
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 for f=find(sn.nodetype == NodeType.Fork)'
198 switch options.config.fork_join
199 case {'mmt', 'default', 'fjt'}
200 TNfork = zeros(1,sn.nclasses);
201 for c=1:sn.nchains
202 inchain = find(sn.chains(c,:));
203 for r=inchain(:)'
204 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));
205 end
206 end
207 % find join associated to fork f
208 joinIdx = find(sn.fj(f,:));
209 forkauxclasses = find(fjforkmap==f);
210 for s=forkauxclasses(:)'
211 r = fjclassmap(s); % original class associated to auxiliary class s
212 if isempty(joinIdx)
213 forkLambda(s) = mean([forkLambda(s); TNfork(r)],1);
214 else
215 TN(sn.nodeToStation(joinIdx),r) = TN(sn.nodeToStation(joinIdx),r) + sum(TN(sn.nodeToStation(joinIdx), find(fjclassmap == r))) - TN(sn.nodeToStation(joinIdx), s);
216 forkLambda(s) = mean([forkLambda(s); TN(sn.nodeToStation(joinIdx),r)],1);
217 end
218 if isempty(joinIdx) || ~outer_forks(f, r)
219 % No join nodes for this fork, no synchronisation delay
220 continue;
221 end
222 % Find the parallel paths coming out of the fork
223 %ri = ModelAdapter.findPaths(sn, nonfjmodel.getLinkedRoutingMatrix{r,r}, f, joinIdx, r, [r,s], QN, TN, 0, fjclassmap, fjforkmap, nonfjmodel)
224 % The code below seems functional but it has not been tested
225 ri = ModelAdapter.findPathsCS(sn, cell2mat(nonfjmodel.getLinkedRoutingMatrix), f, joinIdx, r, [r,s], QN, TN, 0, fjclassmap, fjforkmap, nonfjmodel);
226 lambdai = 1./ri;
227 d0 = 0;
228 parallel_branches = length(ri);
229 for pow=0:(parallel_branches - 1)
230 current_sum = sum(1./sum(nchoosek(lambdai, pow + 1),2));
231 d0 = d0 + (-1)^pow * current_sum;
232 end
233 % Set the synchronisation delays
234 nonfjmodel.nodes{joinIdx}.setService(nonfjmodel.classes{s}, Exp.fitMean(d0*sn.nodeparam{f}.fanOut - mean(ri)));
235 nonfjmodel.nodes{joinIdx}.setService(nonfjmodel.classes{r}, Exp.fitMean(d0*sn.nodeparam{f}.fanOut - mean(ri)));
236 nonfjmodel.refreshRates(); % added by GC
237 end
238 case {'heidelberger-trivedi', 'ht'}
239 joinIdx = find(sn.fj(f,:));
240 for c=1:sn.nchains
241 inchain = find(sn.chains(c,:));
242 for r=inchain(:)'
243 if sn.nodevisits{c}(f,r) == 0
244 continue;
245 end
246 % Obtain the response times on the parallel branches
247 ri = RN(:, find(fjclassmap == r));
248 ri(isnan(ri) | isinf(ri)) = 0;
249 ri = sum(ri, 1, "omitnan") - RN(nonfjstruct.nodeToStation(fj_auxiliary_delays{joinIdx}), find(fjclassmap == r)) - RN(nonfjstruct.nodeToStation(joinIdx), find(fjclassmap == r));
250 lambdai = 1./ri;
251 d0 = 0;
252 parallel_branches = length(self.model.nodes{f}.output.outputStrategy{r}{3});
253 for pow=0:(parallel_branches - 1)
254 current_sum = sum(1./sum(nchoosek(lambdai, pow + 1),2));
255 d0 = d0 + (-1)^pow * current_sum;
256 end
257 di = d0*sn.nodeparam{f}.fanOut - ri;
258 r0 = sum(RN(:, inchain), 2);
259 r0(isnan(r0) | isinf(r0)) = 0;
260 r0 = sum(r0, 1, "omitnan") - RN(nonfjstruct.nodeToStation(joinIdx), r);
261 % Update the delays at the join node and at the auxiliary delay
262 nonfjmodel.nodes{joinIdx}.setService(nonfjmodel.classes{r}, Exp.fitMean(d0*sn.nodeparam{f}.fanOut));
263 idx = 1;
264 for s=find(fjclassmap == r)
265 nonfjmodel.nodes{joinIdx}.setService(nonfjmodel.classes{s}, Exp.fitMean(di(idx)));
266 idx = idx + 1;
267 nonfjmodel.nodes{fj_auxiliary_delays{joinIdx}}.setService(nonfjmodel.classes{s}, Exp.fitMean(r0));
268 end
269
270 end
271 end
272 end
273 end
274 switch options.config.fork_join
275 case {'heidelberger-trivedi', 'ht'}
276 nonfjmodel.refreshStruct();
277 % Delete the queue lengths, response times, throughputs and utilizations of the original classes at the join nodes
278 QN(nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonzeros(fjclassmap)) = 0;
279 RN(nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonzeros(fjclassmap)) = 0;
280 % Save the throughputs of the original classes at the join node
281 TN_orig = TN(nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonzeros(fjclassmap));
282 TN(nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonzeros(fjclassmap)) = 0;
283 UN(nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonzeros(fjclassmap)) = 0;
284
285 % Remove the times at the auxiliary delay
286 QN(nonfjstruct.nodeToStation(cell2mat(fj_auxiliary_delays)),:) = [];
287 UN(nonfjstruct.nodeToStation(cell2mat(fj_auxiliary_delays)),:) = [];
288 RN(nonfjstruct.nodeToStation(cell2mat(fj_auxiliary_delays)),:) = [];
289 TN(nonfjstruct.nodeToStation(cell2mat(fj_auxiliary_delays)),:) = [];
290 % merge back artificial classes into their original classes
291 for r=1:length(fjclassmap)
292 s = fjclassmap(r);
293 if s>0
294 QN(:,s) = QN(:,s) + QN(:,r);
295 UN(:,s) = UN(:,s) + UN(:,r);
296 % Add all throughputs of the auxiliary classes to facilitate the computation of the response times
297 TN(:,s) = TN(:,s) + TN(:,r);
298 RN(:,s) = QN(:,s) ./ TN(:,s);
299 end
300 end
301 % Re-set the throughputs for the original classes
302 TN(nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonzeros(fjclassmap)) = TN_orig;
303 case {'mmt', 'default', 'fjt'}
304 TN_orig = TN([nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Source))], nonzeros(fjclassmap));
305 % merge back artificial classes into their original classes
306 for r=1:length(fjclassmap)
307 s = fjclassmap(r);
308 if s>0
309 QN(:,s) = QN(:,s) + QN(:,r);
310 UN(:,s) = UN(:,s) + UN(:,r);
311 TN(:,s) = TN(:,s) + TN(:,r);
312 %RN(:,s) = RN(:,s) + RN(:,r);
313 % for i=find(snorig.nodetype == NodeType.Delay | snorig.nodetype == NodeType.Queue)'
314 % TN(snorig.nodeToStation(i),s) = TN(snorig.nodeToStation(i),s) + TN(snorig.nodeToStation(i),r);
315 % end
316 RN(:,s) = QN(:,s) ./ TN(:,s);
317 %CN(:,s) = CN(:,s) + CN(:,r);
318 %XN(:,s) = XN(:,s) + XN(:,r);
319 end
320 end
321 TN([nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Source))], nonzeros(fjclassmap)) = TN_orig;
322 end
323 QN(:,fjclassmap>0) = [];
324 UN(:,fjclassmap>0) = [];
325 RN(:,fjclassmap>0) = [];
326 TN(:,fjclassmap>0) = [];
327 CN(:,fjclassmap>0) = [];
328 XN(:,fjclassmap>0) = [];
329 end
330 iter = iter + lastiter;
331 % Cap accumulated iterations for stability (Python parity)
332 if iter > 10000
333 iter = 10000;
334 break; % Exit forkLoop early
335 end
336 end
337
338 sn = self.model.getStruct();
339
340 % Compute average residence time at steady-state
341 AN = sn_get_arvr_from_tput(sn, TN, self.getAvgTputHandles());
342 WN = sn_get_residt_from_respt(sn, RN, self.getAvgResidTHandles());
343 if strcmp(method,'default') && exist('actualmethod','var')
344 self.setAvgResults(QN,UN,RN,TN,AN,WN,CN,XN,runtime,['default/' actualmethod],iter);
345 else
346 self.setAvgResults(QN,UN,RN,TN,AN,WN,CN,XN,runtime,method,iter);
347 end
348 self.result.Prob.logNormConstAggr = lG;
349end
350end
351
352
Definition mmt.m:92