1function runtime = runAnalyzer(self, options)
8 options = self.getOptions;
10% Wall-clock time-budget launch marker (see options.timeout / lineTimeoutExceeded)
11options.timeout_tic = T0;
12% Session-level deadline
for budget checkpoints in deep utilities that have no
13% options argument (e.g. multichoose during state-space generation). Cleared
14% automatically when
this analyzer returns or errors.
15if isfield(options,
'timeout') && ~isempty(options.timeout) && isfinite(options.timeout) && options.timeout > 0
16 setappdata(0,
'LINEtimeoutDeadline',
struct(
'tic', T0,
'budget', options.timeout));
17 timeoutDeadlineCleanup = onCleanup(@() setappdata(0,
'LINEtimeoutDeadline', [])); %#ok<NASGU>
20if strcmp(options.lang,
'python')
21 line_debug(options, 'CTMC: using lang=python, delegating to native line_solver');
22 [QN,UN,RN,TN,AN,WN,runtime] = PYLINE.getAvg(self.name, self.model, options);
23 self.setAvgResults(QN,UN,RN,TN,AN,WN,[],[],runtime,options.method,NaN);
27% QRF (Quadratic/Linear Reduction) LP-based bounds moved to SolverBA.
28if startsWith(options.method, 'qrf')
29 line_error(mfilename, ['The QRF reduction bounds (method ''%s'') moved to SolverBA. ' ...
30 'Use SolverBA(model,''method'',''%s'') (or
the ''qr''/''lr'' aliases).'], ...
31 options.method, options.method);
34if ~isinf(options.timespan(1)) && (options.timespan(1) == options.timespan(2))
35 line_warning(mfilename,'%s: timespan
is a single point, spacing by options.tol (%e).\n',mfilename, options.tol);
36 options.timespan(2) = options.timespan(1) + options.tol;
40self.runAnalyzerChecks(options);
41% Finite Capacity Region: enforced in solver_ctmc.m by filtering
the state space
42% to states within
the aggregate per-region job/memory/linear caps (blocking-
43% before-entry). Per-station setCapacity
is also honored.
44Solver.resetRandomGeneratorSeed(options.seed);
46% Show library attribution if verbose and not yet shown
47if options.verbose ~= VerboseLevel.SILENT && ~GlobalConstants.isLibraryAttributionShown()
48 libs = SolverCTMC.getLibrariesUsed([], options);
50 line_printf('The solver will leverage %s.\n', strjoin(libs, ', '));
51 GlobalConstants.setLibraryAttributionShown(true);
55% The feature gate ran in runAnalyzerChecks above, through supportsModelMethod,
56% and reports which features are unsupported; a coarse supports(self.model)
57% repeat here would only re-reject
the same models naming nothing. The
58% LayeredNetwork redirect that used to sit here now lives in
the constructor,
59% which
is the only place it can run: NetworkSolver's constructor calls
60% getAvgHandles() and so never reaches this line for such a model.
62% Inform user about reducible routing handling
64 [isErg, ergInfo] = self.model.isRoutingErgodic();
65 if ~isErg && ~isempty(ergInfo.absorbingStations)
66 absNames = strjoin(ergInfo.absorbingStations, ', ');
68 'Note: Model has reducible routing with absorbing stations: %s\n' ...
69 ' Results represent limiting/absorption probabilities.\n' ...
70 ' Use model.getReducibilityInfo() for detailed analysis.\n'], ...
76line_debug(options, 'CTMC: using lang=matlab');
78% Native fork-join support: solve
the tag-augmented copy exactly and fold
79%
the auxiliary sibling classes back into
the original classes at
the end
80isFJ = any(sn.nodetype == NodeType.Fork) || any(sn.nodetype == NodeType.Join);
82 if ~isinf(options.timespan(1))
83 line_error(mfilename,'Transient analysis of fork-join models
is not supported by SolverCTMC.\n');
87 [~, fjsn, fjclassmap] = ModelAdapter.fjtag(self.model);
89 options.config.state_space_gen = 'reachable';
90 line_debug(options, 'CTMC: fork-join tag augmentation, %d classes (%d auxiliary), %d fork firings', sn.nclasses, sn.nclasses-Korig, length(sn.fjsync));
93% Convert non-Markovian distributions to PH
94sn = sn_nonmarkov_toph(sn, options);
95line_debug(options, 'CTMC: converted non-Markovian distributions to PH (nstations=%d, nclasses=%d)', sn.nstations, sn.nclasses);
102 sizeEstimator = sizeEstimator + gammaln(1+NK(k)+M-1) - gammaln(1+M-1) - gammaln(1+NK(k)); % worst-case estimate of
the state space
105if any(isinf(sn.njobs))
106 if isinf(options.cutoff)
107 line_warning(mfilename,sprintf('The model has open chains, it
is recommended to specify a finite cutoff value, e.g., SolverCTMC(model,''cutoff'',1).\n'));
108 self.options.cutoff= ceil(6000^(1/(M*K)));
109 options.cutoff= ceil(6000^(1/(M*K)));
110 line_debug(options, 'Open/mixed model: auto-setting cutoff=%d for %d stations, %d classes', options.cutoff, M, K);
111 line_warning(mfilename,sprintf('Setting cutoff=%d.\n',self.options.cutoff));
113 % Mandatory truncation warning for open/mixed models
114 line_printf('CTMC solver using state space cutoff = %d for open/mixed model.\n', options.cutoff);
115 line_warning(mfilename,'State space truncation may cause inaccurate results. Consider varying cutoff to assess sensitivity.\n');
118% Hardware-aware, profiling-calibrated memory pre-gate. Recompute
the
119% worst-case log state-space size using
the (now-resolved) cutoff for open
120% classes so it stays finite, then compare
the calibrated predicted footprint
121% against a fraction of
the memory actually available on this host.
126 if numel(nk) > 1; nk = max(nk(:)); end
130 logNstates = logNstates + gammaln(1+nk+M-1) - gammaln(1+M-1) - gammaln(1+nk);
132forceFlag = isfield(options,'force') && ~isempty(options.force) && options.force;
133if isfield(options,'memorySafetyFraction') && ~isempty(options.memorySafetyFraction)
134 safetyFraction = options.memorySafetyFraction;
136 safetyFraction = 0.6;
138line_debug(options, 'State space size estimate: exp(%f)', logNstates);
139gateVerbose = isfield(options,'verbose') && options.verbose == VerboseLevel.DEBUG;
140[gateOk, gateMsg] = ctmc_memory_gate(logNstates, forceFlag, gateVerbose, safetyFraction);
142 line_error(mfilename, sprintf('%s Stopping SolverCTMC.\n', gateMsg));
146% we compute all metrics anyway because CTMC has essentially
148if isinf(options.timespan(1))
149 line_debug(options, 'Using standard CTMC method for steady-state analysis');
151 s0prior = sn.stateprior;
153 if sn.isstateful(ind)
154 isf = sn.nodeToStateful(ind);
155 sn.state{isf} = s0{isf}(maxpos(s0prior{1}),:); % pick one particular initial state
158 [QN,UN,RN,TN,CN,XN,Q,SS,SSq,Dfilt,~,~,sn] = solver_ctmc_analyzer(sn, options);
160 % update initial state
if this has been corrected by
the state space
161 % generator (skipped on fork-join models:
the analyzed struct
is the
162 % tag-augmented copy, whose states do not fit
the original model)
163 for isf=1:sn.nstateful
164 ind = sn.statefulToNode(isf);
165 self.model.nodes{ind}.setState(sn.state{isf});
166 switch class(self.model.nodes{sn.statefulToNode(isf)})
168 self.model.nodes{sn.statefulToNode(isf)}.setResultHitProb(sn.nodeparam{ind}.actualhitprob);
169 self.model.nodes{sn.statefulToNode(isf)}.setResultMissProb(sn.nodeparam{ind}.actualmissprob);
170 if isfield(sn.nodeparam{ind},
'actualresidt')
171 self.model.
nodes{sn.statefulToNode(isf)}.setResultResidT(sn.nodeparam{ind}.actualresidt);
173 self.model.refreshChains();
178 self.result.infGen = Q;
179 self.result.space = SS;
180 self.result.spaceAggr = SSq;
181 self.result.nodeSpace = sn.space;
182 self.result.eventFilt = Dfilt;
185 T = getAvgTputHandles(self);
187 [QN,UN,RN,TN,CN,XN] = sn_fj_foldback(QN,UN,RN,TN,CN,XN,fjclassmap,Korig);
188 AN=sn_get_arvr_from_tput(sn_orig, TN, T);
189 % Join stations report
the per-sibling waiting time (JMT convention):
190 % QLen over
the sibling arrival rate rather than
the join firing rate
191 for ist=1:sn_orig.nstations
192 if sn_orig.nodetype(sn_orig.stationToNode(ist)) == NodeType.Join
195 RN(ist,r) = QN(ist,r)/AN(ist,r);
200 self.result.fjclassmap = fjclassmap;
202 AN=sn_get_arvr_from_tput(sn, TN, T);
204 self.setAvgResults(QN,UN,RN,TN,AN,[],CN,XN,runtime,options.method);
205 if lineTimeoutExceeded(options)
206 self.result.Avg.timedOut =
true;
207 line_warning(mfilename,
'Solver exceeded the wall-clock time budget (options.timeout=%gs).\n', options.timeout);
210 line_debug(options,
'Transient analysis: timespan=[%f,%f]', options.timespan(1), options.timespan(2));
213 s0prior = sn.stateprior;
215 s0_sz = cellfun(@(x) size(x,1), s0)
';
216 s0_id = pprod(s0_sz-1);
217 cur_state = sn.state;
218 while s0_id>=0 % for all possible initial states
221 if sn.isstateful(ind)
222 isf = sn.nodeToStateful(ind);
223 s0prior_val = s0prior_val * s0prior{isf}(1+s0_id(isf)); % update prior
224 sn.state{isf} = s0{isf}(1+s0_id(isf),:); % assign initial state to network
228 [t,pit,QNt,UNt,~,TNt,~,~,Q,SS,SSq,Dfilt,runtime_t] = solver_ctmc_transient_analyzer(sn, options);
229 self.result.space = SS;
230 self.result.spaceAggr = SSq;
231 self.result.infGen = Q;
232 self.result.eventFilt = Dfilt;
234 setTranProb(self,t,pit,SS,runtime_t);
235 if isempty(self.result) || ~isfield(self.result,'Tran
') || ~isfield(self.result.Tran,'Avg
') || ~isfield(self.result.Tran.Avg,'Q
')
236 self.result.Tran.Avg.Q = cell(M,K);
237 self.result.Tran.Avg.U = cell(M,K);
238 self.result.Tran.Avg.T = cell(M,K);
241 self.result.Tran.Avg.Q{ist,r} = [QNt{ist,r} * s0prior_val,t];
242 self.result.Tran.Avg.U{ist,r} = [UNt{ist,r} * s0prior_val,t];
243 self.result.Tran.Avg.T{ist,r} = [TNt{ist,r} * s0prior_val,t];
249 tunion = union(self.result.Tran.Avg.Q{ist,r}(:,2), t);
250 dataOld = interp1(self.result.Tran.Avg.Q{ist,r}(:,2),self.result.Tran.Avg.Q{ist,r}(:,1),tunion);
251 dataNew = interp1(t,QNt{ist,r},tunion);
252 self.result.Tran.Avg.Q{ist,r} = [dataOld+s0prior_val*dataNew,tunion];
253 dataOld = interp1(self.result.Tran.Avg.U{ist,r}(:,2),self.result.Tran.Avg.U{ist,r}(:,1),tunion);
254 dataNew = interp1(t,UNt{ist,r},tunion);
255 self.result.Tran.Avg.U{ist,r} = [dataOld+s0prior_val*dataNew,tunion];
257 dataOld = interp1(self.result.Tran.Avg.T{ist,r}(:,2),self.result.Tran.Avg.T{ist,r}(:,1),tunion);
258 dataNew = interp1(t,TNt{ist,r},tunion);
259 self.result.Tran.Avg.T{ist,r} = [dataOld+s0prior_val*dataNew,tunion];
264 s0_id=pprod(s0_id,s0_sz-1); % update initial state
266 % Now we restore the original state
268 if sn.isstateful(ind)
269 isf = sn.nodeToStateful(ind);
270 self.model.nodes{ind}.setState(cur_state{isf});
276 self.result.('solver
') = getName(self);
277 self.result.runtime = runtime;
278 self.result.solverSpecific = lastSol;