1function runtime = runAnalyzer(self, options)
2% RUNTIME = RUNANALYZER(OPTIONS)
6 options = self.getOptions;
9self.runAnalyzerChecks(options);
11Solver.resetRandomGeneratorSeed(options.seed);
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);
18 line_printf(
'The solver will leverage %s.\n', strjoin(libs,
', '));
19 GlobalConstants.setLibraryAttributionShown(
true);
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;
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)
';
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()));
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;
58 sn = getStruct(self); % doesn't need initial state
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);
67 while (forkLoop && forkIter < options.iter_max)
69 forkIter = 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);
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
83 nonfjSource = nonfjmodel.getSource;
85 if ~nonfjSource.arrivalProcess{r}.isDisabled
86 nonfjSource.arrivalProcess{r}.setRate((fanout(r)-1)*forkLambda(r));
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();
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>
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)));
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');
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.');
119 method = options.method;
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]);
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;
142 self.model.nodes{ind}.setResultHitProb(prob);
143 self.model.nodes{ind}.setResultMissProb(1-prob);
146 self.model.refreshChains();
148 [QN,UN,RN,TN,CN,XN,lG,runtime,lastiter,actualmethod] = solver_mva_cache_analyzer(sn, options);
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;
161 hitprob(k) = XN(h) / sum(XN(inchain),"omitnan"); %
#ok<NANSUM>
164 self.model.nodes{ind}.setResultHitProb(hitprob);
165 self.model.nodes{ind}.setResultMissProb(1-hitprob);
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,:));
179 %self.model.refreshChains();
180 self.model.refreshStruct(
true);
181 else % ordinary queueing network
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);
186 if ~isempty(sn.lldscaling) || ~isempty(sn.cdscaling)
187 [QN,UN,RN,TN,CN,XN,lG,runtime,lastiter,actualmethod] = solver_mvald_analyzer(sn, options);
189 [QN,UN,RN,TN,CN,XN,lG,runtime,lastiter,actualmethod] = solver_mva_analyzer(sn, options);
194 if self.model.hasFork
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);
202 inchain = find(sn.chains(c,:));
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));
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
213 forkLambda(s) = mean([forkLambda(s); TNfork(r)],1);
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);
218 if isempty(joinIdx) || ~outer_forks(f, r)
219 % No join nodes for this fork, no synchronisation delay
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);
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;
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
238 case {'heidelberger-trivedi
', 'ht
'}
239 joinIdx = find(sn.fj(f,:));
241 inchain = find(sn.chains(c,:));
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));
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;
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));
264 for s=find(fjclassmap == r)
265 nonfjmodel.nodes{joinIdx}.setService(nonfjmodel.classes{s}, Exp.fitMean(di(idx)));
267 nonfjmodel.nodes{fj_auxiliary_delays{joinIdx}}.setService(nonfjmodel.classes{s}, Exp.fitMean(r0));
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;
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)),:) = [];
291 for r=1:length(fjclassmap)
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);
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));
306 for r=1:length(fjclassmap)
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);
316 RN(:,s) = QN(:,s) ./ TN(:,s);
317 %CN(:,s) = CN(:,s) + CN(:,r);
318 %XN(:,s) = XN(:,s) + XN(:,r);
321 TN([nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Join)), nonfjstruct.nodeToStation(find(sn.nodetype == NodeType.Source))], nonzeros(fjclassmap)) = TN_orig;
323 QN(:,fjclassmap>0) = [];
324 UN(:,fjclassmap>0) = [];
325 RN(:,fjclassmap>0) = [];
326 TN(:,fjclassmap>0) = [];
327 CN(:,fjclassmap>0) = [];
328 XN(:,fjclassmap>0) = [];
330 iter = iter + lastiter;
331 % Cap accumulated iterations for stability (Python parity)
334 break; % Exit forkLoop early
338 sn = self.model.getStruct();
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);
346 self.setAvgResults(QN,UN,RN,TN,AN,WN,CN,XN,runtime,method,iter);
348 self.result.Prob.logNormConstAggr = lG;