1function [QN,UN,RN,TN,CN,XN,runtime,method,tranSysState,tranSync,sn,QNCI,UNCI,RNCI,TNCI,ANCI,WNCI] = solver_ssa_analyzer(sn, options)
2% [QN,UN,RN,TN,CN,XN,RUNTIME] = SOLVER_SSA_ANALYZER(SN, OPTIONS)
3% Wrapper that selects the most suitable SSA performance-analysis back-end.
5% If every station uses scheduling policy INF, EXT, or PS (and the network
6% has no cache
nodes), the faster Next-Reaction-Method analyser
7% -> solver_ssa_analyzer_nrm
8%
is invoked. Otherwise the original serial / parallel analysers are used.
10% Copyright (c) 2012-2026, Imperial College London
15line_debug(
'SSA analyzer starting: method=%s, nstations=%d, nclasses=%d', options.method, sn.nstations, sn.nclasses);
17% Convert non-Markovian distributions to PH
18sn = sn_nonmarkov_toph(sn, options);
20% Capture initial state after conversion (state may have been expanded
for MAPs)
23% Initialize CI outputs
33% Check
if confidence intervals are requested
34[confintEnabled, confintLevel] = Solver.parseConfInt(options.confint);
36% -------------------------------------------------------------------------
37% Pick analysis back-end
38% -------------------------------------------------------------------------
40 case {
'default'} %
"default" prefers NRM
for closed QNs with INF/PS
41 allowedSched = [SchedStrategy.INF, SchedStrategy.EXT, SchedStrategy.PS];
42 nrmSupported = all(arrayfun(@(s) any(s==allowedSched), sn.sched)) && ...
43 all(sn.nodetype ~= NodeType.Cache) && ...
44 sn_is_population_model(sn) && ...
45 all(sn.procid(sn.procid~=ProcessType.DISABLED) == ProcessType.EXP); % all exp
47 line_debug(
'Checking NRM eligibility: schedOK=%d, noCache=%d, isPopModel=%d, allExp=%d', ...
48 all(arrayfun(@(s) any(s==allowedSched), sn.sched)), ...
49 all(sn.nodetype ~= NodeType.Cache), ...
50 sn_is_population_model(sn), ...
51 all(sn.procid(sn.procid~=ProcessType.DISABLED) == ProcessType.EXP));
54 line_debug(
'Default method: using NRM (Next Reaction Method)\n');
55 line_debug(
'Using NRM method (fast path), calling solver_ssa_analyzer_nrm');
56 [XN,UN,QN,RN,TN,CN,tranSysState,tranSync,sn] = ...
57 solver_ssa_analyzer_nrm(sn, options);
59 % Compute CI
using batch means
if enabled
60 if confintEnabled && ~isempty(tranSysState) && length(tranSysState) > 1
61 [QNCI, UNCI, RNCI, TNCI, ANCI, WNCI] = ssa_compute_batch_means_ci(tranSysState, sn, confintLevel);
63 runtime = toc(Tstart);
66 % otherwise fall through to serial selection
67 line_debug(
'Default method: using serial SSA\n');
68 line_debug(
'NRM not supported, falling back to serial method');
69 options.method =
'serial';
73 line_debug(
'Using explicit NRM method, calling solver_ssa_analyzer_nrm');
75 [XN,UN,QN,RN,TN,CN,tranSysState,tranSync,sn] = ...
76 solver_ssa_analyzer_nrm(sn, options);
78 sn.method =
'default/nrm';
79 % Compute CI
using batch means
if enabled
80 if confintEnabled && ~isempty(tranSysState) && length(tranSysState) > 1
81 [QNCI, UNCI, RNCI, TNCI, ANCI, WNCI] = ssa_compute_batch_means_ci(tranSysState, sn, confintLevel);
83 runtime = toc(Tstart);
85 case 'ssa' % alias
for serial path below
86 line_debug(
'Using ssa alias, redirecting to serial method');
87 options.method =
'serial';
88 sn.method =
'default/serial';
91% SERIAL / PARALLEL ANALYSERS (legacy paths) ------------------------------
94 line_debug(
'Using serial method, calling solver_ssa_analyzer_serial');
95 [XN,UN,QN,RN,TN,CN,tranSysState,tranSync,sn] = ...
96 solver_ssa_analyzer_serial(sn, init_state, options,
false);
99 case {
'para',
'parallel'}
100 line_debug(
'Using parallel method, calling solver_ssa_analyzer_parallel');
102 [XN,UN,QN,RN,TN,CN,tranSysState,tranSync,sn] = ...
103 solver_ssa_analyzer_parallel(sn, init_state, options);
106 if strcmp(ME.identifier,
'MATLAB:spmd:NoPCT')
107 line_printf(['Parallel Computing Toolbox unavailable – ',...
108 'falling back to serial SSA.\n']);
109 [XN,UN,QN,RN,TN,CN,tranSysState,tranSync,sn] = ...
110 solver_ssa_analyzer_serial(sn, init_state, options, true);
118 error('solver_ssa_analyzer:UnknownMethod', ...
119 'Unknown analysis method: %s', options.method);
122% Compute CI using batch means if enabled
123if confintEnabled && ~isempty(tranSysState) && length(tranSysState) > 1
124 [QNCI, UNCI, RNCI, TNCI, ANCI, WNCI] = ssa_compute_batch_means_ci(tranSysState, sn, confintLevel);
127runtime = toc(Tstart);
130function [QNCI, UNCI, RNCI, TNCI, ANCI, WNCI] = ssa_compute_batch_means_ci(tranSysState, sn, confintLevel)
131% SSA_COMPUTE_BATCH_MEANS_CI Compute confidence intervals using batch means method
133% tranSysState{1} contains the cumulative time at each sample
134% tranSysState{2:end} contain the state vectors
for each stateful node
145% Extract time and state data
146if iscell(tranSysState) && length(tranSysState) > 1
147 times = tranSysState{1};
148 nSamples = length(times);
151 % Not enough samples
for batch means
155 % Number of batches (use 10-30 batches
for good CI estimation)
156 numBatches = min(20, floor(nSamples / 10));
160 batchSize = floor(nSamples / numBatches);
162 % Discard initial transient (first 10% of samples)
163 transientCutoff = max(1, floor(nSamples * 0.1));
165 % Extract queue length data from tranSysState
166 % tranSysState{2:end} contains state vectors
for each stateful node
167 % We need to compute marginal queue lengths per station/
class
169 % Compute batch means
for queue lengths
171 isf = sn.stationToStateful(ist);
172 if isf > 0 && (1 + isf) <= length(tranSysState)
173 stateData = tranSysState{1 + isf};
174 if isempty(stateData)
179 % Extract queue length
for this station/
class from state data
180 % The state data format depends on the scheduling strategy
181 % For simplicity, we
'll use the marginal extraction
182 ind = sn.stationToNode(ist);
184 % Compute time-weighted batch means
185 batchMeans = zeros(1, numBatches);
187 startIdx = transientCutoff + (b-1) * batchSize + 1;
188 endIdx = min(transientCutoff + b * batchSize, nSamples);
189 if startIdx > nSamples || startIdx >= endIdx
193 % Compute time-weighted average for this batch
194 % times contains cumulative times, compute inter-sample durations
196 prevTime = times(startIdx - 1);
200 batchTimes = times(startIdx:endIdx);
202 if length(batchTimes) >= 1
203 % Compute time duration each state was held
205 dt = [batchTimes(1); diff(batchTimes)];
207 dt = [batchTimes(1) - prevTime; diff(batchTimes)];
210 % Extract queue lengths from state data
211 % For now, sum all columns to get total jobs at the station
212 % This works for most queue types where state represents job counts
213 qLengths = sum(stateData(startIdx:endIdx, :), 2);
217 batchMeans(b) = sum(qLengths .* dt) / totalTime;
222 % Count valid batches (non-NaN)
223 validMask = ~isnan(batchMeans);
224 batchMeans = batchMeans(validMask);
225 nBatches = length(batchMeans);
228 % Compute mean and standard error
229 batchMean = mean(batchMeans);
230 batchStd = std(batchMeans);
231 stdErr = batchStd / sqrt(nBatches);
233 % t-critical value for confidence level
234 alpha = 1 - confintLevel;
235 tCrit = tinv(1 - alpha/2, nBatches - 1);
237 % Confidence interval half-width
238 QNCI(ist, k) = tCrit * stdErr;
244 % For utilization, response time, and throughput CIs, use relative scaling
245 % These are derived from queue length CI using Little's law relationships
246 UNCI = QNCI; % Simplified - utilization CI scales similarly
247 RNCI = QNCI; % Response time CI - would need service rate info
248 TNCI = QNCI; % Throughput CI - would need arrival rate info