1function runtime = runAnalyzer(self, options)
8 options = self.getOptions;
10% Wall-clock time-budget launch marker (see options.timeout / lineTimeoutExceeded)
11options.timeout_tic = T0;
13if strcmp(options.lang,
'python')
14 line_debug(options, 'FLD: using lang=python, delegating to native line_solver');
15 [QN,UN,RN,TN,AN,WN,runtime] = PYLINE.getAvg(self.name, self.model, options);
16 self.setAvgResults(QN,UN,RN,TN,AN,WN,[],[],runtime,options.method,NaN);
20sn = getStruct(self); % this gets modified later on so pass by copy
22% Convert non-Markovian distributions to PH
23sn = sn_nonmarkov_toph(sn, options);
25orig_method = options.method;
26self.runAnalyzerChecks(options);
28% Finite Capacity Region:
the fluid ODEs do not enforce
the aggregate
29% per-region job limit and would silently return
the unconstrained answer.
30if isfield(sn,'nregions') && sn.nregions > 0
31 line_error(mfilename,'This model uses a Finite Capacity Region (addRegion), which
is not supported by SolverFLD. Use SolverCTMC, SolverJMT, SolverSSA or SolverLDES, or setCapacity for a single-station limit.');
34Solver.resetRandomGeneratorSeed(options.seed);
36% Show library attribution if verbose and not yet shown
37if options.verbose ~= VerboseLevel.SILENT && ~GlobalConstants.isLibraryAttributionShown()
38 libs = SolverFLD.getLibrariesUsed(sn, options);
40 line_printf('The solver will leverage %s.\n', strjoin(libs, ', '));
41 GlobalConstants.setLibraryAttributionShown(true);
45%options.lang = 'java';
47hasOpenClasses = sn_has_open_classes(sn);
50 line_debug(options,
'FLD: using lang=java, delegating to JLINE');
51 jmodel = LINE2JLINE(self.model);
52 %M = jmodel.getNumberOfStatefulNodes;
53 M = jmodel.getNumberOfStations;
54 R = jmodel.getNumberOfClasses;
56 case {
'default',
'closing',
'matrix',
'rmf'}
57 jsolver = JLINE.SolverFluid(jmodel, options);
59 line_warning(mfilename,
'This solver does not support the specified method. Setting to default.\n');
60 options.method =
'default';
61 jsolver = JLINE.SolverFluid(jmodel, options);
63 [QN,UN,RN,WN,AN,TN] = JLINE.arrayListToResults(jsolver.getAvgTable);
67 QN = reshape(QN
',R,M)';
68 UN = reshape(UN
',R,M)';
69 RN = reshape(RN
',R,M)';
70 WN = reshape(WN
',R,M)';
71 AN = reshape(AN
',R,M)';
72 TN = reshape(TN
',R,M)';
75 self.setAvgResults(QN,UN,RN,TN,AN,WN,CN,XN,runtime,options.method,lastiter);
76 self.result.Prob.logNormConstAggr = lG;
77 self.result.solverSpecific.sn = JLINE.from_jline_struct(jmodel);
78 self.result.solverSpecific.odeStateVec = JLINE.from_jline_matrix(jsolver.result.odeStateVec);
81 line_debug(options,
'FLD: using lang=matlab');
83 case {
'matrix',
'pnorm'}
84 line_debug(options,
'FLD method: matrix/pnorm requested');
85 % Matrix method now supports mixed/open models per Ruuskanen et al., PEVA 151 (2021)
87 line_error(mfilename,
'The matrix solver does not support DPS scheduling. Use options.method=''closing'' instead.');
90 options.method =
'closing';
91 line_debug(options,
'FLD method: closing');
93 % Use rmf
for cache models, closing
for DPS, matrix otherwise
94 if any(sn.nodetype == NodeType.Cache)
95 options.method = 'rmf';
97 options.method = 'closing';
99 options.method = 'matrix';
101 line_debug(options, 'FLD default method resolved to: %s', options.method);
102 case {
'statedep',
'softmin'}
103 line_debug(options,
'FLD method: %s', options.method);
105 case {
'tbi',
'fluid.tbi'}
106 options.method =
'tbi';
107 line_debug(options,
'FLD method: trajectory-based iteration');
108 % Trajectory-based iteration (Sheldon, Tuncer, Casale, IEEE T-ITS):
109 % cell-decomposed waveform relaxation of
the closing ODEs
111 line_error(mfilename,
'The tbi method supports closed models only.');
113 if any(sn.nodetype == NodeType.Cache)
114 line_error(mfilename,'The tbi method does not support caching stations. Use options.method=''rmf'' instead.');
116 case {
'diffusion',
'fluid.diffusion'}
117 line_debug(options,
'FLD method: diffusion approximation');
118 % Diffusion approximation - validated in solver_fluid_diffusion
119 %
do nothing here, validation happens in
the solver itself
120 case {
'mfq',
'fluid.mfq'}
121 line_debug(options,
'FLD method: Markovian fluid queue');
122 % Markovian fluid queue method
for single-queue analysis
123 % Validation and fallback happens in solver_fluid_analyzer
124 case {
'rmf',
'fluid.rmf'}
125 line_debug(options,
'FLD method: refined mean field (cache analysis)');
126 % Refined mean field method
for multi-list cache analysis
127 % Uses DDPP framework with 1/N correction
129 line_error(mfilename,sprintf(
'The ''%s'' method is unsupported by this solver.',options.method));
133if isinf(options.timespan(1))
134 if options.verbose == 2
135 line_warning(mfilename,'%s requires options.timespan(1) to be finite. Setting it to 0.\n',mfilename);
137 options.timespan(1) = 0;
140if options.timespan(1) == options.timespan(2)
141 line_warning(mfilename,'%s: timespan
is a single point, unsupported. Setting options.timespace(1) to 0.\n',mfilename);
142 options.timespan(1) = 0;
145if self.enableChecks && ~self.supports(self.model)
146 line_error(mfilename,'This model contains features not supported by
the solver.');
149self.setOptions(options);
156Q = zeros(M,K); R = zeros(M,K); T = zeros(M,K);
157U = zeros(M,K); C = zeros(1,K); X = zeros(1,K);
160s0prior = sn.stateprior;
161s0_sz = cellfun(@(x) size(x,1), s0)';
162s0_id = pprod(s0_sz-1);
164while s0_id>=0 % for all possible initial states
167 if sn.isstateful(ind)
168 isf = sn.nodeToStateful(ind);
169 s0prior_val = s0prior_val * s0prior{isf}(1+s0_id(isf)); % update prior
170 %sn.state{isf} = s0{isf}(1+s0_id(isf),:); % assign initial state to network
171 self.model.nodes{ind}.setState(s0{isf}(1+s0_id(isf),:));
174 sn = self.model.getStruct;
175 sn = sn_nonmarkov_toph(sn, options); % Re-apply conversion after fresh
struct
179 % [Qfull, Ufull, Rfull, Tfull, Cfull, Xfull, t, Qfull_t, Ufull_t, Tfull_t, lastSol] = JLINE.runFluidAnalyzer(self.model, options);
181 [Qfull, Ufull, Rfull, Tfull, Cfull, Xfull, t, Qfull_t, Ufull_t, Tfull_t, lastSol, iter, aoiResults] = solver_fluid_analyzer(sn, options);
184 [t,uniqueIdx] = unique(t);
185 if isempty(lastSol) %
if solution fails
186 Q = NaN*ones(M,K); R = NaN*ones(M,K);
187 T = NaN*ones(M,K); U = NaN*ones(M,K);
188 C = NaN*ones(1,K); X = NaN*ones(1,K);
189 Qt = cell(M,K); Ut = cell(M,K); Tt = cell(M,K);
192 Qt{ist,r} = [NaN,NaN];
193 Ut{ist,r} = [NaN,NaN];
194 Tt{ist,r} = [NaN,NaN];
198 if isempty(self.result) && max(size(Qt))==0 %~exist(
'Qt',
'var')
199 Q = Qfull*s0prior_val;
200 R = Rfull*s0prior_val;
201 T = Tfull*s0prior_val;
202 U = Ufull*s0prior_val;
203 C = Cfull*s0prior_val;
204 X = Xfull*s0prior_val;
210 if ~isempty(Qfull_t{ist,r}) && length(Qfull_t{ist,r}) >= max(uniqueIdx)
211 Qfull_t{ist,r} = Qfull_t{ist,r}(uniqueIdx);
212 Ufull_t{ist,r} = Ufull_t{ist,r}(uniqueIdx);
213 Tfull_t{ist,r} = Tfull_t{ist,r}(uniqueIdx);
214 Qt{ist,r} = [Qfull_t{ist,r} * s0prior_val,t];
215 Ut{ist,r} = [Ufull_t{ist,r} * s0prior_val,t];
216 Tt{ist,r} = [Tfull_t{ist,r} * s0prior_val,t];
218 Qt{ist,r} = [NaN,NaN];
219 Ut{ist,r} = [NaN,NaN];
220 Tt{ist,r} = [NaN,NaN];
225 Q = Q + Qfull*s0prior_val;
226 R = R + Rfull*s0prior_val;
227 T = T + Tfull*s0prior_val;
228 U = U + Ufull*s0prior_val;
229 C = C + Cfull*s0prior_val;
230 X = X + Xfull*s0prior_val;
233 [t,uniqueIdx] = unique(t);
234 Qfull_t{ist,r} = Qfull_t{ist,r}(uniqueIdx);
235 Ufull_t{ist,r} = Ufull_t{ist,r}(uniqueIdx);
236 % Tfull_t{i,r} = Tfull_t{i,r}(uniqueIdx);
238 tunion =
union(Qt{ist,r}(:,2), t);
239 dataOld = interp1(Qt{ist,r}(:,2),Qt{ist,r}(:,1),tunion);
240 dataNew = interp1(t,Qfull_t{ist,r},tunion);
241 Qt{ist,r} = [dataOld + s0prior_val * dataNew, tunion];
243 dataOld = interp1(Ut{ist,r}(:,2),Ut{ist,r}(:,1),tunion);
244 dataNew = interp1(t,Ufull_t{ist,r},tunion);
245 Ut{ist,r} = [dataOld + s0prior_val * dataNew, tunion];
247 % dataOld = interp1(Tt{i,r}(:,2),Tt{i,r}(:,1),tunion);
248 % dataNew = interp1(t,Tfull_t{i,r},tunion);
249 % Tt{i,r} = [dataOld + s0prior_val * dataNew, tunion];
255 s0_id=pprod(s0_id,s0_sz-1); % update initial state
257% Now we restore
the original state
259 if sn.isstateful(ind)
260 isf = sn.nodeToStateful(ind);
261 self.model.nodes{ind}.setState(cur_state{isf});
265self.result.solverSpecific = lastSol;
266% Store AoI results
if available
267if exist(
'aoiResults',
'var') && ~isempty(aoiResults)
268 self.result.solverSpecific.aoiResults = aoiResults;
270QN = Q; UN=U; RN=R; TN=T; CN=C; XN=X;
272% For cache models solved with rmf, update hit/miss probs in
the model
273if any(strcmp(options.method, {
'rmf',
'fluid.rmf'})) && any(sn.nodetype == NodeType.Cache)
274 caches = find(sn.nodetype == NodeType.Cache);
275 % Retrieve hitprob/missprob from
the cacheqn result stored in lastSol
276 if isfield(lastSol, 'cacheHitProb')
277 for cIdx = 1:length(caches)
279 self.model.
nodes{ind}.setResultHitProb(lastSol.cacheHitProb(cIdx,:));
280 self.model.nodes{ind}.setResultMissProb(lastSol.cacheMissProb(cIdx,:));
282 self.model.refreshStruct(
true);
283 sn = self.model.getStruct(
true);
287% Compute average arrival rate at steady-state
288AN = sn_get_arvr_from_tput(sn, TN, self.getAvgTputHandles());
289if strcmp(orig_method,
'default') && ~strcmp(options.method,
'default')
290 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,['default/',options.method],iter);
292 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,options.method,iter);
295self.setTranAvgResults(Qt,Ut,Rt,Tt,Ct,Xt,runtime);
296if lineTimeoutExceeded(options)
297 self.result.Avg.timedOut =
true;
298 line_warning(mfilename,
'Solver stopped after the wall-clock time budget (options.timeout=%gs) was exceeded; returning the interim solution.\n', options.timeout);