1function runtime = runAnalyzer(self, options)
4% Copyright (c) 2012-2026, Imperial College London
9if nargin<2 %%~exist(
'options',
'var')
10 options = self.getOptions;
13line_debug(options, 'JMT analyzer starting: lang=%s, samples=%d, seed=%d', options.lang, options.samples, options.seed);
15self.runAnalyzerChecks(options);
18if isfield(sn,'immfeed') && ~isempty(sn.immfeed) && any(sn.immfeed(:))
19 line_warning(mfilename,'SolverJMT does not support immediate feedback (immfeed); no solution returned.\n');
20 runtime = toc(Tstart);
24Solver.resetRandomGeneratorSeed(options.seed);
28 line_error(mfilename, 'SolverJMT does not support lang=''python'' (no native-Python JMT backend). Use lang=''java'' or lang=''matlab'', or SolverSSA/SolverLDES for simulation.');
30 line_debug(options, 'JMT: using lang=java, delegating to JLINE SolverJMT');
31 jmodel = LINE2JLINE(self.model);
32 M = jmodel.getNumberOfStations;
33 R = jmodel.getNumberOfClasses;
34 jsolver = JLINE.SolverJMT(jmodel, options);
36 [QN,UN,RN,WN,AN,TN] = JLINE.arrayListToResults(jsolver.getAvgTable);
40 QN = reshape(QN',R,M)';
41 UN = reshape(UN',R,M)';
42 RN = reshape(RN',R,M)';
43 WN = reshape(WN',R,M)';
44 AN = reshape(AN',R,M)';
45 TN = reshape(TN',R,M)';
48 self.setAvgResults(QN,UN,RN,TN,AN,WN,CN,XN,runtime,options.method,lastiter);
49 self.result.Prob.logNormConstAggr = lG;
50 self.result.solverSpecific.sn = JLINE.from_jline_struct(jmodel);
51 self.result.Prob.logNormConstAggr = lG;
54 line_debug(options, 'JMT: using lang=matlab');
56 if ~isfield(options,'verbose')
60 if ~isfield(options,'force')
61 options.force = false;
64 if ~isfield(options,'keep')
68 if self.enableChecks && ~self.supports(self.model)
70 line_error(mfilename,'This model contains features not supported by
the solver.');
76 if ~isfield(options,'samples')
77 options.samples = 1e4; % default: this
is the samples / measure, not
the total number of simulation events, which can be much larger.
78 elseif options.samples < 5e3
79 %if ~strcmpi(options.method,'jmva.ls')
80 line_warning(mfilename,'JMT requires at least 5000 samples for each metric,
the current value
is %d. Starting
the simulation with 5000 samples.\n', options.samples);
82 options.samples = 5e3;
83 line_debug(options, 'JMT: sample size adjusted to minimum 5000');
86 if ~isfield(options,'verbose')
90 if ~isfield(options,'keep')
91 options.verbose = false;
94 if ~isfield(options,'seed')
95 options.seed = randi([1,1e6]);
97 self.seed = options.seed;
99 if ~isfield(options,'timespan')
100 options.timespan = [0,Inf];
102 self.maxSimulatedTime = options.timespan(2);
105 if ~self.model.hasInitState
106 self.model.initDefault;
109 self.maxSamples = options.samples;
110 % Sync xmlParser maxSamples with solver (ensures corrected sample
count is used in XML)
111 self.xmlParser.maxSamples = options.samples;
114 % Auto-detect transient mode: switch to 'replication' if finite timespan with default method
115 if strcmpi(options.method, 'default') && isfield(options, 'timespan') && isfinite(options.timespan(2))
116 options.method = 'replication';
117 line_debug(options, 'Finite timespan [%f,%f] detected, auto-switching to replication method', options.timespan(1), options.timespan(2));
120 switch options.method
121 case {
'jsim',
'default'}
122 if strcmpi(options.method,
'default')
123 line_debug(options, 'JMT: default method resolved to JSIM');
125 line_debug(options, 'JMT: using JSIM method (discrete-event simulation), samples=%d, seed=%d', options.samples, options.seed);
126 fname = self.writeJSIM(sn);
127 cmd = ['java -cp "',getJMTJarPath(self),filesep,'JMT.jar" jmt.commandline.Jmt sim "',fname,'" -seed ',num2str(options.seed),' --illegal-access=permit'];
129 line_printf('JMT model: %s\n',fname);
131 if options.verbose == VerboseLevel.DEBUG
132 line_printf('JMT command: %s\n',cmd);
134 [status, cmdoutput] = system(cmd);
135 runtime = toc(Tstart);
136 if status ~= 0 && options.verbose
137 line_printf('\nJMT command failed with status %d. Output:\n%s\n', status, cmdoutput);
140 if ~options.keep && isfolder(getFilePath(self))
141 rmdir(getFilePath(self),'s');
144 % line_printf('\nJMT analysis (seed: %d) completed. Runtime: %f seconds.\n',options.seed,runtime);
146 self.setAvgResults(self.result.Avg.Q,self.result.Avg.U,self.result.Avg.R,self.result.Avg.T,self.result.Avg.A,self.result.Avg.W,[],[],runtime,options.method,1);
148 % Set cache hit/miss ratios from JMT Cache Hit Rate metric
149 for ind = 1:sn.nnodes
150 if sn.nodetype(ind) == NodeType.Cache
152 nclasses = length(hitclass);
153 actualhitprob = zeros(1, nclasses);
154 actualmissprob = zeros(1, nclasses);
156 % Check
if JMT returned Cache Hit Rate metrics
157 fieldName = sprintf(
'node%d', ind);
158 if isfield(self.result,
'CacheHitRate') && isfield(self.result.CacheHitRate, fieldName)
159 actualhitprob = self.result.CacheHitRate.(fieldName);
160 actualmissprob = 1 - actualhitprob;
163 % Set
the results on
the Cache node
164 self.model.nodes{ind}.setResultHitProb(actualhitprob);
165 self.model.nodes{ind}.setResultMissProb(actualmissprob);
167 % Also update
the cached sn
struct directly
168 sn.
nodeparam{ind}.actualhitprob = actualhitprob;
169 sn.nodeparam{ind}.actualmissprob = actualmissprob;
170 self.model.sn.nodeparam{ind}.actualhitprob = actualhitprob;
171 self.model.sn.nodeparam{ind}.actualmissprob = actualmissprob;
175 line_debug(options,
'JMT: using replication method (transient simulation), iter_max=%d', options.iter_max);
176 options = self.getOptions;
177 initSeed = self.options.seed;
178 initTimeSpan = self.options.timespan;
179 self.options.timespan(1) = self.options.timespan(2);
180 if isfield(options,
'timespan') && isfinite(options.timespan(2))
182 validReplications = 0;
183 for it=1:options.iter_max
184 self.options.seed = initSeed + it -1;
185 TranSysStateAggr{it} = sampleSysAggr(self);
186 % Skip replications with empty or invalid time vectors
187 if isempty(TranSysStateAggr{it}.t) || ~isvector(TranSysStateAggr{it}.t)
188 line_warning(mfilename,
'Replication %d produced empty/invalid time series, skipping.', it);
191 validReplications = validReplications + 1;
193 tu = TranSysStateAggr{it}.t;
195 % we need to limit
the time series at
the minimum
196 % as otherwise
the predictor of
the state cannot
197 % take into account constraints that exist on
the
199 tumax = min(max(tu),max(TranSysStateAggr{it}.t));
200 tu =
union(tu, TranSysStateAggr{it}.t);
204 if validReplications == 0
205 line_error(mfilename,
'No valid replications produced. Cannot compute transient averages.');
208 QNt = cellzeros(sn.nstations, sn.nclasses, length(tu), 2);
209 UNt = cellzeros(sn.nstations, sn.nclasses, length(tu), 2);
210 TNt = cellzeros(sn.nstations, sn.nclasses, length(tu), 2);
215 QNt{jst,r}(:,2) = tu;
216 UNt{jst,r}(:,2) = tu;
217 TNt{jst,r}(:,2) = tu;
218 for it=1:options.iter_max
219 % Skip invalid replications
220 if isempty(TranSysStateAggr{it}.t) || ~isvector(TranSysStateAggr{it}.t)
223 qlenAt_t = interp1(TranSysStateAggr{it}.t, TranSysStateAggr{it}.state{jst}(:,r), tu,
'previous');
224 avgQlenAt_t = qlenAt_t;
225 %avgQlenAt_t = cumsum(qlenAt_t .*[0;diff(tu)])./tu;
226 avgQlenAt_t(isnan(avgQlenAt_t))=0;
227 QNt{jst,r}(:,1) = QNt{jst,r}(:,1) + (1/validReplications) * avgQlenAt_t;
229 for it=1:options.iter_max
230 % Skip invalid replications
231 if isempty(TranSysStateAggr{it}.t) || ~isvector(TranSysStateAggr{it}.t)
234 if isfinite(sn.nservers(jst))
235 occupancyAt_t = interp1(TranSysStateAggr{it}.t, min(TranSysStateAggr{it}.state{jst}(:,r),sn.nservers(jst)), tu,
'previous')/sn.nservers(jst);
236 else %
if delay we use queue-length
237 occupancyAt_t = interp1(TranSysStateAggr{it}.t, TranSysStateAggr{it}.state{jst}(:,r), tu,
'previous');
239 avgOccupancyAt_t = occupancyAt_t;
240 %avgOccupancyAt_t = cumsum(occupancyAt_t .*[0;diff(tu)])./tu;
241 avgOccupancyAt_t(isnan(avgOccupancyAt_t))=0;
242 UNt{jst,r}(:,1) = UNt{jst,r}(:,1) + (1/validReplications) * avgOccupancyAt_t;
244 %
for it=1:options.iter_max
245 % departures = [0;diff(TranSysStateAggr{it}.state{j}(:,r))];
246 % departures(departures>0) = 0;
247 % departuresAt_t = abs(interp1(TranSysStateAggr{it}.t, cumsum(departures), tu,
'previous'));
248 % avgDeparturesAt_t = departuresAt_t./tu;
249 % avgDeparturesAt_t(isnan(avgDeparturesAt_t))=0;
250 % TNt{j,r}(:,1) = TNt{j,r}(:,1) + (1/options.iter_max) * avgDeparturesAt_t;
252 if isfinite(sn.nservers(jst))
253 TNt{jst,r}(:,1) = UNt{jst,r}(:,1) * sn.nservers(jst) * sn.rates(jst,r);
255 TNt{jst,r}(:,1) = UNt{jst,r}(:,1) * sn.rates(jst,r);
259 runtime = toc(Tstart);
263 self.setTranAvgResults(QNt,UNt,RNt,TNt,CNt,XNt,runtime);
264 self.result.Tran.Avg.U = UNt;
265 self.result.Tran.Avg.T = TNt;
266 self.result.Tran.Avg.Q = QNt;
268 self.options.seed = initSeed;
269 self.options.timespan = initTimeSpan;
270 self.result.(
'solver') = getName(self);
271 self.result.runtime = runtime;
273 % line_printf(
'\nJMT analysis (seed: %d) completed. Runtime: %f seconds.\n',options.seed,runtime);
275 case {
'jmva',
'jmva.amva',
'jmva.mva',
'jmva.recal',
'jmva.comom',
'jmva.chow',
'jmva.bs',
'jmva.aql',
'jmva.lin',
'jmva.dmlin',
'jmva.ls',...
276 'jmt.jmva',
'jmt.jmva.mva',
'jmt.jmva.amva',
'jmt.jmva.recal',
'jmt.jmva.comom',
'jmt.jmva.chow',
'jmt.jmva.bs',
'jmt.jmva.aql',
'jmt.jmva.lin',
'jmt.jmva.dmlin',
'jmt.jmva.ls'}
277 line_debug(options,
'JMT: using JMVA method: %s', options.method);
278 fname = self.writeJMVA(sn, getJMVATempPath(self), self.options);
279 cmd = [
'java -cp "',getJMTJarPath(self),filesep,
'JMT.jar" jmt.commandline.Jmt mva "',fname,
'" -seed ',num2str(options.seed),
' --illegal-access=permit'];
281 line_printf(
'JMT model: %s\n',fname);
283 if options.verbose == VerboseLevel.DEBUG
284 line_printf(
'JMT command: %s\n',cmd);
286 [status, cmdoutput] = system(cmd);
287 runtime = toc(Tstart);
288 if status ~= 0 && options.verbose
289 line_printf(
'\nJMT command failed with status %d. Output:\n%s\n', status, cmdoutput);
292 if ~options.keep && isfolder(getFilePath(self))
293 rmdir(getFilePath(self),
's');
296 % line_printf(
'\nJMT analysis (method: %d) completed. Runtime: %f seconds.\n',options.method,runtime);
298 sn = self.model.getStruct();
300 % Compute average arrival rate at steady-state
301 TH = getAvgTputHandles(self);
302 AN = sn_get_arvr_from_tput(sn, self.result.Avg.T, TH);
303 self.setAvgResults(self.result.Avg.Q,self.result.Avg.U,self.result.Avg.R,self.result.Avg.T,AN,self.result.Avg.W,[],[],runtime,options.method,1);
305 line_warning(mfilename,
'This solver does not support the specified method. Setting to default.\n');
306 self.options.method =
'default';