1function runtime = runAnalyzer(self, options)
8 options = self.getOptions;
10% Wall-clock time-budget launch marker (see options.timeout / lineTimeoutExceeded)
11options.timeout_tic = T0;
13[pyHandled, options, pyRuntime] = self.runAnalyzerPreamble(options,
'FLD');
19sn = getStruct(self); %
this gets modified later on so pass by copy
21% Convert non-Markovian distributions to PH
22sn = sn_nonmarkov_toph(sn, options);
24orig_method = options.method;
25self.runAnalyzerChecks(options);
27% Finite Capacity Region: the fluid ODEs
do not enforce the aggregate
28% per-region job limit and would silently
return the unconstrained answer.
29if isfield(sn,
'nregions') && sn.nregions > 0
30 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.');
33Solver.resetRandomGeneratorSeed(options.seed);
36%options.lang =
'java';
38hasOpenClasses = sn_has_open_classes(sn);
41 line_debug(options,
'FLD: using lang=java, delegating to JLINE');
42 jmodel = LINE2JLINE(self.model);
43 %M = jmodel.getNumberOfStatefulNodes;
44 M = jmodel.getNumberOfStations;
45 R = jmodel.getNumberOfClasses;
47 case {
'default',
'closing',
'matrix',
'rmf'}
48 jsolver = JLINE.SolverFluid(jmodel, options);
50 line_warning(mfilename,
'This solver does not support the specified method. Setting to default.\n');
51 options.method =
'default';
52 jsolver = JLINE.SolverFluid(jmodel, options);
54 [QN,UN,RN,WN,AN,TN] = JLINE.arrayListToResults(jsolver.getAvgTable);
58 QN = reshape(QN
',R,M)';
59 UN = reshape(UN
',R,M)';
60 RN = reshape(RN
',R,M)';
61 WN = reshape(WN
',R,M)';
62 AN = reshape(AN
',R,M)';
63 TN = reshape(TN
',R,M)';
66 self.setAvgResults(QN,UN,RN,TN,AN,WN,CN,XN,runtime,options.method,lastiter);
67 self.result.Prob.logNormConstAggr = lG;
68 self.result.solverSpecific.sn = JLINE.from_jline_struct(jmodel);
69 self.result.solverSpecific.odeStateVec = JLINE.from_jline_matrix(jsolver.result.odeStateVec);
72 line_debug(options,
'FLD: using lang=matlab');
74 case {
'matrix',
'pnorm'}
75 line_debug(options,
'FLD method: matrix/pnorm requested');
76 % Matrix method now supports mixed/open models per Ruuskanen et al., PEVA 151 (2021)
78 line_error(mfilename,
'The matrix solver does not support DPS scheduling. Use options.method=''closing'' instead.');
81 options.method =
'closing';
82 line_debug(options,
'FLD method: closing');
84 % Use rmf
for cache models, closing
for DPS, matrix otherwise
85 if any(sn.nodetype == NodeType.Cache)
86 options.method = 'rmf';
88 options.method = 'closing';
90 options.method = 'matrix';
92 line_debug(options, 'FLD default method resolved to: %s', options.method);
93 case {
'statedep',
'softmin'}
94 line_debug(options,
'FLD method: %s', options.method);
96 case {
'tbi',
'fluid.tbi'}
97 options.method =
'tbi';
98 line_debug(options,
'FLD method: trajectory-based iteration');
99 % Trajectory-based iteration (Sheldon, Tuncer, Casale, IEEE T-ITS):
100 % cell-decomposed waveform relaxation of the closing ODEs
102 line_error(mfilename,
'The tbi method supports closed models only.');
104 if any(sn.nodetype == NodeType.Cache)
105 line_error(mfilename,'The tbi method does not support caching stations. Use options.method=''rmf'' instead.');
107 case {
'diffusion',
'fluid.diffusion'}
108 line_debug(options,
'FLD method: diffusion approximation');
109 % Diffusion approximation - validated in solver_fluid_diffusion
110 %
do nothing here, validation happens in the solver itself
111 case {
'mfq',
'fluid.mfq'}
112 line_debug(options,
'FLD method: Markovian fluid queue');
113 % Markovian fluid queue method
for single-queue analysis
114 % Validation and fallback happens in solver_fluid_analyzer
115 case {
'rmf',
'fluid.rmf'}
116 line_debug(options,
'FLD method: refined mean field (cache analysis)');
117 % Refined mean field method
for multi-list cache analysis
118 % Uses DDPP framework with 1/N correction
120 line_error(mfilename,sprintf(
'The ''%s'' method is unsupported by this solver.',options.method));
124if isinf(options.timespan(1))
125 if options.verbose == 2
126 line_warning(mfilename,'%s requires options.timespan(1) to be finite. Setting it to 0.\n',mfilename);
128 options.timespan(1) = 0;
131if options.timespan(1) == options.timespan(2)
132 line_warning(mfilename,'%s: timespan
is a single point, unsupported. Setting options.timespace(1) to 0.\n',mfilename);
133 options.timespan(1) = 0;
136if self.enableChecks && ~self.supports(self.model)
137 line_error(mfilename,'This model contains features not supported by the solver.');
140self.setOptions(options);
147Q = zeros(M,K); R = zeros(M,K); T = zeros(M,K);
148U = zeros(M,K); C = zeros(1,K); X = zeros(1,K);
151s0prior = sn.stateprior;
152s0_sz = cellfun(@(x) size(x,1), s0)';
153s0_id = pprod(s0_sz-1);
155while s0_id>=0 % for all possible initial states
158 if sn.isstateful(ind)
159 isf = sn.nodeToStateful(ind);
160 s0prior_val = s0prior_val * s0prior{isf}(1+s0_id(isf)); % update prior
161 %sn.state{isf} = s0{isf}(1+s0_id(isf),:); % assign initial state to network
162 self.model.nodes{ind}.setState(s0{isf}(1+s0_id(isf),:));
165 sn = self.model.getStruct;
166 sn = sn_nonmarkov_toph(sn, options); % Re-apply conversion after fresh
struct
170 % [Qfull, Ufull, Rfull, Tfull, Cfull, Xfull, t, Qfull_t, Ufull_t, Tfull_t, lastSol] = JLINE.runFluidAnalyzer(self.model, options);
172 [Qfull, Ufull, Rfull, Tfull, Cfull, Xfull, t, Qfull_t, Ufull_t, Tfull_t, lastSol, iter, aoiResults] = solver_fluid_analyzer(sn, options);
175 [t,uniqueIdx] = unique(t);
176 if isempty(lastSol) %
if solution fails
177 Q = NaN*ones(M,K); R = NaN*ones(M,K);
178 T = NaN*ones(M,K); U = NaN*ones(M,K);
179 C = NaN*ones(1,K); X = NaN*ones(1,K);
180 Qt = cell(M,K); Ut = cell(M,K); Tt = cell(M,K);
183 Qt{ist,r} = [NaN,NaN];
184 Ut{ist,r} = [NaN,NaN];
185 Tt{ist,r} = [NaN,NaN];
189 if isempty(self.result) && max(size(Qt))==0 %~exist(
'Qt',
'var')
190 Q = Qfull*s0prior_val;
191 R = Rfull*s0prior_val;
192 T = Tfull*s0prior_val;
193 U = Ufull*s0prior_val;
194 C = Cfull*s0prior_val;
195 X = Xfull*s0prior_val;
201 if ~isempty(Qfull_t{ist,r}) && length(Qfull_t{ist,r}) >= max(uniqueIdx)
202 Qfull_t{ist,r} = Qfull_t{ist,r}(uniqueIdx);
203 Ufull_t{ist,r} = Ufull_t{ist,r}(uniqueIdx);
204 Tfull_t{ist,r} = Tfull_t{ist,r}(uniqueIdx);
205 Qt{ist,r} = [Qfull_t{ist,r} * s0prior_val,t];
206 Ut{ist,r} = [Ufull_t{ist,r} * s0prior_val,t];
207 Tt{ist,r} = [Tfull_t{ist,r} * s0prior_val,t];
209 Qt{ist,r} = [NaN,NaN];
210 Ut{ist,r} = [NaN,NaN];
211 Tt{ist,r} = [NaN,NaN];
216 Q = Q + Qfull*s0prior_val;
217 R = R + Rfull*s0prior_val;
218 T = T + Tfull*s0prior_val;
219 U = U + Ufull*s0prior_val;
220 C = C + Cfull*s0prior_val;
221 X = X + Xfull*s0prior_val;
224 [t,uniqueIdx] = unique(t);
225 Qfull_t{ist,r} = Qfull_t{ist,r}(uniqueIdx);
226 Ufull_t{ist,r} = Ufull_t{ist,r}(uniqueIdx);
227 % Tfull_t{i,r} = Tfull_t{i,r}(uniqueIdx);
229 tunion =
union(Qt{ist,r}(:,2), t);
230 dataOld = interp1(Qt{ist,r}(:,2),Qt{ist,r}(:,1),tunion);
231 dataNew = interp1(t,Qfull_t{ist,r},tunion);
232 Qt{ist,r} = [dataOld + s0prior_val * dataNew, tunion];
234 dataOld = interp1(Ut{ist,r}(:,2),Ut{ist,r}(:,1),tunion);
235 dataNew = interp1(t,Ufull_t{ist,r},tunion);
236 Ut{ist,r} = [dataOld + s0prior_val * dataNew, tunion];
238 % dataOld = interp1(Tt{i,r}(:,2),Tt{i,r}(:,1),tunion);
239 % dataNew = interp1(t,Tfull_t{i,r},tunion);
240 % Tt{i,r} = [dataOld + s0prior_val * dataNew, tunion];
246 s0_id=pprod(s0_id,s0_sz-1); % update initial state
248% Now we restore the original state
250 if sn.isstateful(ind)
251 isf = sn.nodeToStateful(ind);
252 self.model.nodes{ind}.setState(cur_state{isf});
256self.result.solverSpecific = lastSol;
257% Store AoI results
if available
258if exist(
'aoiResults',
'var') && ~isempty(aoiResults)
259 self.result.solverSpecific.aoiResults = aoiResults;
261QN = Q; UN=U; RN=R; TN=T; CN=C; XN=X;
263% For cache models solved with rmf, update hit/miss probs in the model
264if any(strcmp(options.method, {
'rmf',
'fluid.rmf'})) && any(sn.nodetype == NodeType.Cache)
265 caches = find(sn.nodetype == NodeType.Cache);
266 % Retrieve hitprob/missprob from the cacheqn result stored in lastSol
267 if isfield(lastSol, 'cacheHitProb')
268 for cIdx = 1:length(caches)
270 self.model.
nodes{ind}.setResultHitProb(lastSol.cacheHitProb(cIdx,:));
271 self.model.nodes{ind}.setResultMissProb(lastSol.cacheMissProb(cIdx,:));
273 self.model.refreshStruct(
true);
274 sn = self.model.getStruct(
true);
278% Compute average arrival rate at steady-state
279AN = sn_get_arvr_from_tput(sn, TN, self.getAvgTputHandles());
280if strcmp(orig_method,
'default') && ~strcmp(options.method,
'default')
281 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,['default/',options.method],iter);
283 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,options.method,iter);
286self.setTranAvgResults(Qt,Ut,Rt,Tt,Ct,Xt,runtime);
287if lineTimeoutExceeded(options)
288 self.result.Avg.timedOut =
true;
289 line_warning(mfilename,
'Solver stopped after the wall-clock time budget (options.timeout=%gs) was exceeded; returning the interim solution.\n', options.timeout);