LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
runAnalyzer.m
1function runtime = runAnalyzer(self, options)
2% TSIM = RUN()
3
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6
7Tstart=tic;
8
9if nargin<2 %%~exist('options','var')
10 options = self.getOptions;
11end
12
13line_ack('JMT', options.verbose);
14
15line_debug(options, 'JMT analyzer starting: lang=%s, samples=%d, seed=%d', options.lang, options.samples, options.seed);
16
17self.runAnalyzerChecks(options);
18
19sn = self.getStruct();
20if isfield(sn,'immfeed') && ~isempty(sn.immfeed) && any(sn.immfeed(:))
21 line_warning(mfilename,'SolverJMT does not support immediate feedback (immfeed); no solution returned.\n');
22 runtime = toc(Tstart);
23 return
24end
25
26Solver.resetRandomGeneratorSeed(options.seed);
27
28switch options.lang
29 case 'python'
30 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.');
31 case 'java'
32 line_debug(options, 'JMT: using lang=java, delegating to JLINE SolverJMT');
33 jmodel = LINE2JLINE(self.model);
34 M = jmodel.getNumberOfStations;
35 R = jmodel.getNumberOfClasses;
36 jsolver = JLINE.SolverJMT(jmodel, options);
37 T0=tic;
38 [QN,UN,RN,WN,AN,TN] = JLINE.arrayListToResults(jsolver.getAvgTable);
39 runtime = toc(T0);
40 CN = [];
41 XN = [];
42 QN = reshape(QN',R,M)';
43 UN = reshape(UN',R,M)';
44 RN = reshape(RN',R,M)';
45 WN = reshape(WN',R,M)';
46 AN = reshape(AN',R,M)';
47 TN = reshape(TN',R,M)';
48 lG = NaN;
49 lastiter = NaN;
50 self.setAvgResults(QN,UN,RN,TN,AN,WN,CN,XN,runtime,options.method,lastiter);
51 self.result.Prob.logNormConstAggr = lG;
52 self.result.solverSpecific.sn = JLINE.from_jline_struct(jmodel);
53 self.result.Prob.logNormConstAggr = lG;
54 return
55 case 'matlab'
56 line_debug(options, 'JMT: using lang=matlab');
57
58 if ~isfield(options,'verbose')
59 options.verbose = 0;
60 end
61
62 if ~isfield(options,'force')
63 options.force = false;
64 end
65
66 if ~isfield(options,'keep')
67 options.keep = false;
68 end
69
70 if self.enableChecks && ~self.supports(self.model)
71 % if options.verbose
72 line_error(mfilename,'This model contains features not supported by the solver.');
73 % end
74 % runtime = toc(T0);
75 % return
76 end
77
78 if ~isfield(options,'samples')
79 options.samples = 1e4; % default: this is the samples / measure, not the total number of simulation events, which can be much larger.
80 elseif options.samples < 5e3
81 %if ~strcmpi(options.method,'jmva.ls')
82 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);
83 %end
84 options.samples = 5e3;
85 line_debug(options, 'JMT: sample size adjusted to minimum 5000');
86 end
87
88 if ~isfield(options,'verbose')
89 options.verbose = 0;
90 end
91
92 if ~isfield(options,'keep')
93 options.verbose = false;
94 end
95
96 if ~isfield(options,'seed')
97 options.seed = randi([1,1e6]);
98 end
99 self.seed = options.seed;
100
101 if ~isfield(options,'timespan')
102 options.timespan = [0,Inf];
103 else
104 self.maxSimulatedTime = options.timespan(2);
105 end
106
107 if ~self.model.hasInitState
108 self.model.initDefault;
109 end
110
111 self.maxSamples = options.samples;
112 % Sync xmlParser maxSamples with solver (ensures corrected sample count is used in XML)
113 self.xmlParser.maxSamples = options.samples;
114 sn = self.getStruct;
115
116 % Auto-detect transient mode: switch to 'replication' if finite timespan with default method
117 if strcmpi(options.method, 'default') && isfield(options, 'timespan') && isfinite(options.timespan(2))
118 options.method = 'replication';
119 line_debug(options, 'Finite timespan [%f,%f] detected, auto-switching to replication method', options.timespan(1), options.timespan(2));
120 end
121
122 switch options.method
123 case {'jsim','default'}
124 if strcmpi(options.method, 'default')
125 line_debug(options, 'JMT: default method resolved to JSIM');
126 end
127 line_debug(options, 'JMT: using JSIM method (discrete-event simulation), samples=%d, seed=%d', options.samples, options.seed);
128 fname = self.writeJSIM(sn);
129 cmd = ['java -cp "',getJMTJarPath(self),filesep,'JMT.jar" jmt.commandline.Jmt sim "',fname,'" -seed ',num2str(options.seed),' --illegal-access=permit'];
130 if options.verbose
131 line_printf('JMT model: %s\n',fname);
132 end
133 if options.verbose == VerboseLevel.DEBUG
134 line_printf('JMT command: %s\n',cmd);
135 end
136 [status, cmdoutput] = system(cmd);
137 runtime = toc(Tstart);
138 if status ~= 0 && options.verbose
139 line_printf('\nJMT command failed with status %d. Output:\n%s\n', status, cmdoutput);
140 end
141 self.getResults;
142 if ~options.keep && isfolder(getFilePath(self))
143 rmdir(getFilePath(self),'s');
144 end
145 %if options.verbose
146 % line_printf('\nJMT analysis (seed: %d) completed. Runtime: %f seconds.\n',options.seed,runtime);
147 %end
148 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);
149
150 % Set cache hit/miss ratios from JMT Cache Hit Rate metric
151 for ind = 1:sn.nnodes
152 if sn.nodetype(ind) == NodeType.Cache
153 hitclass = sn.nodeparam{ind}.hitclass;
154 nclasses = length(hitclass);
155 actualhitprob = zeros(1, nclasses);
156 actualmissprob = zeros(1, nclasses);
157
158 % Check if JMT returned Cache Hit Rate metrics
159 fieldName = sprintf('node%d', ind);
160 if isfield(self.result, 'CacheHitRate') && isfield(self.result.CacheHitRate, fieldName)
161 actualhitprob = self.result.CacheHitRate.(fieldName);
162 actualmissprob = 1 - actualhitprob;
163 end
164
165 % Set the results on the Cache node
166 self.model.nodes{ind}.setResultHitProb(actualhitprob);
167 self.model.nodes{ind}.setResultMissProb(actualmissprob);
168
169 % Also update the cached sn struct directly
170 sn.nodeparam{ind}.actualhitprob = actualhitprob;
171 sn.nodeparam{ind}.actualmissprob = actualmissprob;
172 self.model.sn.nodeparam{ind}.actualhitprob = actualhitprob;
173 self.model.sn.nodeparam{ind}.actualmissprob = actualmissprob;
174 end
175 end
176 case {'replication'}
177 line_debug(options, 'JMT: using replication method (transient simulation), iter_max=%d', options.iter_max);
178 options = self.getOptions;
179 initSeed = self.options.seed;
180 initTimeSpan = self.options.timespan;
181 self.options.timespan(1) = self.options.timespan(2);
182 if isfield(options,'timespan') && isfinite(options.timespan(2))
183 tu = [];
184 validReplications = 0;
185 for it=1:options.iter_max
186 self.options.seed = initSeed + it -1;
187 TranSysStateAggr{it} = sampleSysAggr(self);
188 % Skip replications with empty or invalid time vectors
189 if isempty(TranSysStateAggr{it}.t) || ~isvector(TranSysStateAggr{it}.t)
190 line_warning(mfilename, 'Replication %d produced empty/invalid time series, skipping.', it);
191 continue;
192 end
193 validReplications = validReplications + 1;
194 if isempty(tu)
195 tu = TranSysStateAggr{it}.t;
196 else
197 % we need to limit the time series at the minimum
198 % as otherwise the predictor of the state cannot
199 % take into account constraints that exist on the
200 % state space
201 tumax = min(max(tu),max(TranSysStateAggr{it}.t));
202 tu = union(tu, TranSysStateAggr{it}.t);
203 tu = tu(tu<=tumax);
204 end
205 end
206 if validReplications == 0
207 line_error(mfilename, 'No valid replications produced. Cannot compute transient averages.');
208 return;
209 end
210 QNt = cellzeros(sn.nstations, sn.nclasses, length(tu), 2);
211 UNt = cellzeros(sn.nstations, sn.nclasses, length(tu), 2);
212 TNt = cellzeros(sn.nstations, sn.nclasses, length(tu), 2);
213 M = sn.nstations;
214 K = sn.nclasses;
215 for jst=1:M
216 for r=1:K
217 QNt{jst,r}(:,2) = tu;
218 UNt{jst,r}(:,2) = tu;
219 TNt{jst,r}(:,2) = tu;
220 for it=1:options.iter_max
221 % Skip invalid replications
222 if isempty(TranSysStateAggr{it}.t) || ~isvector(TranSysStateAggr{it}.t)
223 continue;
224 end
225 qlenAt_t = interp1(TranSysStateAggr{it}.t, TranSysStateAggr{it}.state{jst}(:,r), tu,'previous');
226 avgQlenAt_t = qlenAt_t;
227 %avgQlenAt_t = cumsum(qlenAt_t .*[0;diff(tu)])./tu;
228 avgQlenAt_t(isnan(avgQlenAt_t))=0;
229 QNt{jst,r}(:,1) = QNt{jst,r}(:,1) + (1/validReplications) * avgQlenAt_t;
230 end
231 for it=1:options.iter_max
232 % Skip invalid replications
233 if isempty(TranSysStateAggr{it}.t) || ~isvector(TranSysStateAggr{it}.t)
234 continue;
235 end
236 if isfinite(sn.nservers(jst))
237 occupancyAt_t = interp1(TranSysStateAggr{it}.t, min(TranSysStateAggr{it}.state{jst}(:,r),sn.nservers(jst)), tu,'previous')/sn.nservers(jst);
238 else % if delay we use queue-length
239 occupancyAt_t = interp1(TranSysStateAggr{it}.t, TranSysStateAggr{it}.state{jst}(:,r), tu,'previous');
240 end
241 avgOccupancyAt_t = occupancyAt_t;
242 %avgOccupancyAt_t = cumsum(occupancyAt_t .*[0;diff(tu)])./tu;
243 avgOccupancyAt_t(isnan(avgOccupancyAt_t))=0;
244 UNt{jst,r}(:,1) = UNt{jst,r}(:,1) + (1/validReplications) * avgOccupancyAt_t;
245 end
246 % for it=1:options.iter_max
247 % departures = [0;diff(TranSysStateAggr{it}.state{j}(:,r))];
248 % departures(departures>0) = 0;
249 % departuresAt_t = abs(interp1(TranSysStateAggr{it}.t, cumsum(departures), tu, 'previous'));
250 % avgDeparturesAt_t = departuresAt_t./tu;
251 % avgDeparturesAt_t(isnan(avgDeparturesAt_t))=0;
252 % TNt{j,r}(:,1) = TNt{j,r}(:,1) + (1/options.iter_max) * avgDeparturesAt_t;
253 % end
254 if isfinite(sn.nservers(jst))
255 TNt{jst,r}(:,1) = UNt{jst,r}(:,1) * sn.nservers(jst) * sn.rates(jst,r);
256 else
257 TNt{jst,r}(:,1) = UNt{jst,r}(:,1) * sn.rates(jst,r);
258 end
259 end
260 end
261 runtime = toc(Tstart);
262 RNt = [];
263 CNt = [];
264 XNt = [];
265 self.setTranAvgResults(QNt,UNt,RNt,TNt,CNt,XNt,runtime);
266 self.result.Tran.Avg.U = UNt;
267 self.result.Tran.Avg.T = TNt;
268 self.result.Tran.Avg.Q = QNt;
269 end
270 self.options.seed = initSeed;
271 self.options.timespan = initTimeSpan;
272 self.result.('solver') = getName(self);
273 self.result.runtime = runtime;
274 %if options.verbose
275 % line_printf('\nJMT analysis (seed: %d) completed. Runtime: %f seconds.\n',options.seed,runtime);
276 %end
277 case {'jmva','jmva.amva','jmva.mva','jmva.recal','jmva.comom','jmva.chow','jmva.bs','jmva.aql','jmva.lin','jmva.dmlin','jmva.ls',...
278 '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'}
279 line_debug(options, 'JMT: using JMVA method: %s', options.method);
280 fname = self.writeJMVA(sn, getJMVATempPath(self), self.options);
281 cmd = ['java -cp "',getJMTJarPath(self),filesep,'JMT.jar" jmt.commandline.Jmt mva "',fname,'" -seed ',num2str(options.seed),' --illegal-access=permit'];
282 if options.verbose
283 line_printf('JMT model: %s\n',fname);
284 end
285 if options.verbose == VerboseLevel.DEBUG
286 line_printf('JMT command: %s\n',cmd);
287 end
288 [status, cmdoutput] = system(cmd);
289 runtime = toc(Tstart);
290 if status ~= 0 && options.verbose
291 line_printf('\nJMT command failed with status %d. Output:\n%s\n', status, cmdoutput);
292 end
293 self.getResults;
294 if ~options.keep && isfolder(getFilePath(self))
295 rmdir(getFilePath(self),'s');
296 end
297 %if options.verbose
298 % line_printf('\nJMT analysis (method: %d) completed. Runtime: %f seconds.\n',options.method,runtime);
299 %end
300 sn = self.model.getStruct();
301
302 % Compute average arrival rate at steady-state
303 TH = getAvgTputHandles(self);
304 AN = sn_get_arvr_from_tput(sn, self.result.Avg.T, TH);
305 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);
306 otherwise
307 line_warning(mfilename,'This solver does not support the specified method. Setting to default.\n');
308 self.options.method = 'default';
309 runAnalyzer(self);
310 end
311end
312end