1function [runtime, tranSysState, tranSync] = runAnalyzer(self, options)
2% [RUNTIME, TRANSYSSTATE] = RUNANALYZER()
4% Run
the LDES solver on
the queueing network model.
6% Communication with
the LDES engine
is fully JSON-mediated:
the model
is
7% serialized to model.json (linemodel_save) and
the engine
is run as a
8% subprocess (
"solve model.json -o result.json");
the result JSON
is parsed
9% back here. No in-process Java
object marshalling (JPype/JLINE)
is used
for
10% regular Network models. LayeredNetwork (LQN) models are still simulated by
11%
the Java LDES ensemble backend (self.obj), which
is a separate path.
15 options = self.getOptions;
18% options.events (DES event budget) overrides options.samples when set;
19% samples remains accepted as a deprecated alias
for the event budget.
20if isfield(options,
'events') && ~isempty(options.events) && isfinite(options.events) && options.events > 0
21 options.samples = round(options.events);
27if isa(self.model,
'LayeredNetwork')
28 self.obj.getAvg(); % runs
the LN LDES analyzer (Java ensemble backend)
33self.runAnalyzerChecks(options);
34Solver.resetRandomGeneratorSeed(options.seed);
36line_debug('LDES solver starting: samples=%d, seed=%d', options.samples, options.seed);
38sn = self.model.getStruct;
41[confintEnabled, ~] = Solver.parseConfInt(options.confint);
43isTransient = isfield(options, 'timespan') && numel(options.timespan) >= 2 && ...
44 isfinite(options.timespan(2));
48 extraFlags = {
'--timespan', ...
49 sprintf(
'%.10g,%.10g', options.timespan(1), options.timespan(2)), ...
51 % Ensemble transient: a single realization
is not
the transient mean
52 % E[N](t) (no time-ergodicity at fixed t). When replications > 1
is
53 % requested, drive
the engine
's parallel analyzer via --replications so
54 % the per-bucket QNt/UNt/TNt are averaged across independent replications.
55 % Read from options.replications or options.config.replications; default
56 % (absent/<=1) keeps the single-path run.
58 if isfield(options, 'replications
') && ~isempty(options.replications)
59 reps = options.replications;
60 elseif isfield(options, 'config
') && isfield(options.config, 'replications
') ...
61 && ~isempty(options.config.replications)
62 reps = options.config.replications;
65 extraFlags{end+1} = '--replications
';
66 extraFlags{end+1} = sprintf('%d
', round(reps));
67 if isfield(options, 'config
') && isfield(options.config, 'numthreads
') ...
68 && ~isempty(options.config.numthreads) && options.config.numthreads > 0
69 extraFlags{end+1} = '--numthreads
';
70 extraFlags{end+1} = sprintf('%d
', round(options.config.numthreads));
75data = self.solveCli(options, extraFlags);
76if ~isstruct(data) || ~isfield(data, 'metrics
')
77 line_error(mfilename, 'LDES engine returned no metrics.
');
81 parseTransient(self, data, M, R, options);
83 parseSteady(self, data, sn, M, R, confintEnabled, options);
89% =========================================================================
91function parseSteady(self, data, sn, M, R, confintEnabled, options)
92% PARSESTEADY Marshal steady-state result JSON onto the solver.
94QN = ldesJson2mat(data.metrics.QN, M, R);
95UN = ldesJson2mat(data.metrics.UN, M, R);
96RN = ldesJson2mat(data.metrics.RN, M, R);
97TN = ldesJson2mat(data.metrics.TN, M, R);
98AN = ldesJson2mat(data.metrics.AN, M, R);
99WN = ldesJson2mat(data.metrics.WN, M, R);
100CN = ldesJson2mat(data.metrics.CN, 1, R);
101XN = ldesJson2mat(data.metrics.XN, 1, R);
103 line_error('runAnalyzer
', 'LDES result metrics could not be parsed.
');
106if isfield(data, 'runtime
') && isscalar(data.runtime) && isnumeric(data.runtime)
107 runtime = data.runtime;
112% Finite capacity region (FCR) metrics: append region rows after the stations.
116if F > 0 && isfield(data, 'fcr
')
118 QNfcr = ldesJson2mat(ldesGetField(fcr, 'QNfcr
', []), F, R);
119 UNfcr = ldesJson2mat(ldesGetField(fcr, 'UNfcr
', []), F, R);
120 RNfcr = ldesJson2mat(ldesGetField(fcr, 'RNfcr
', []), F, R);
121 TNfcr = ldesJson2mat(ldesGetField(fcr, 'TNfcr
', []), F, R);
122 WNfcr = ldesJson2mat(ldesGetField(fcr, 'WNfcr
', []), F, R);
124 ANfcr = NaN(F, R); % arrival rate not applicable to FCR rows
131 WeightNfcr = ldesJson2mat(ldesGetField(fcr, 'WeightNfcr
', []), F, R);
132 MemOccNfcr = ldesJson2mat(ldesGetField(fcr, 'MemOccNfcr
', []), F, R);
133 if isempty(WeightNfcr), WeightNfcr = zeros(F, R); end
134 if isempty(MemOccNfcr), MemOccNfcr = zeros(F, R); end
139 line_printf('LDES samples: %8d\n
', options.samples);
142self.setAvgResults(QN, UN, RN, TN, AN, WN, CN, XN, runtime, options.method, options.samples);
144% FCR-only weighted-occupation (Total Weight) and memory-occupation metrics:
145% NaN at station rows, populated at the FCR rows (M+1 .. M+F).
146self.result.Avg.Weight = NaN(M + F, R);
147self.result.Avg.MemOcc = NaN(M + F, R);
148if F > 0 && ~isempty(WeightNfcr)
149 self.result.Avg.Weight(M+1:M+F, :) = WeightNfcr;
150 self.result.Avg.MemOcc(M+1:M+F, :) = MemOccNfcr;
153% Marshal cache hit/miss/latency from the result JSON onto the Cache nodes.
154% Clear any stale split first, then set each field independently so a single
155% missing field does not discard the others (order-independence).
156haveCacheMetrics = isfield(data, 'cacheMetrics
');
158 if sn.nodetype(ind) == NodeType.Cache
159 mcache = self.model.nodes{ind};
160 mcache.setResultHitProb(sparse([]));
161 mcache.setResultMissProb(sparse([]));
162 mcache.setResultDelayedHitProb(sparse([]));
163 mcache.setResultResidT(sparse([]));
165 cname = matlab.lang.makeValidName(char(mcache.getName()));
166 if isfield(data.cacheMetrics, cname)
167 cm = data.cacheMetrics.(cname);
168 hp = ldesJson2mat(ldesGetField(cm, 'hit
', []), [], []);
169 mp = ldesJson2mat(ldesGetField(cm, 'miss
', []), [], []);
170 dhp = ldesJson2mat(ldesGetField(cm, 'delayed
', []), [], []);
171 lp = ldesJson2mat(ldesGetField(cm, 'latency
', []), [], []);
172 hpl = ldesJson2mat(ldesGetField(cm, 'hitList
', []), [], []);
173 ip = ldesJson2mat(ldesGetField(cm, 'itemProb
', []), [], []);
174 if ~isempty(hp), mcache.setResultHitProb(hp); end
175 if ~isempty(mp), mcache.setResultMissProb(mp); end
176 if ~isempty(dhp), mcache.setResultDelayedHitProb(dhp); end
177 if ~isempty(lp), mcache.setResultResidT(lp); end
178 if ~isempty(hpl), mcache.setResultHitProbList(hpl); end
179 if ~isempty(ip), mcache.setResultItemProb(ip); end
185% Confidence intervals (the result JSON always carries them when requested).
186if confintEnabled && isfield(data, 'confidenceIntervals
')
187 ci = data.confidenceIntervals;
188 QNCI = ldesJson2mat(ldesGetField(ci, 'QNCI
', []), M, R);
189 UNCI = ldesJson2mat(ldesGetField(ci, 'UNCI
', []), M, R);
190 RNCI = ldesJson2mat(ldesGetField(ci, 'RNCI
', []), M, R);
191 TNCI = ldesJson2mat(ldesGetField(ci, 'TNCI
', []), M, R);
192 ANCI = ldesJson2mat(ldesGetField(ci, 'ANCI
', []), M, R);
193 WNCI = ldesJson2mat(ldesGetField(ci, 'WNCI
', []), M, R);
194 self.setAvgResultsCI(QNCI, UNCI, RNCI, TNCI, ANCI, WNCI, [], []);
198% =========================================================================
200function parseTransient(self, data, M, R, options)
201% PARSETRANSIENT Marshal the transient trajectory JSON onto the solver.
202% data.transient carries t (time vector) and QNt/UNt/TNt as nested
203% [station][class] arrays, each an (nTimePoints x 2) [value, time] matrix.
206if isfield(data, 'runtime
') && isscalar(data.runtime) && isnumeric(data.runtime)
207 runtime = data.runtime;
210if ~isfield(data, 'transient
') || isempty(data.transient) || ~isfield(data.transient, 't
')
212 line_printf('LDES transient analysis produced no trajectory.\n
');
216tran = data.transient;
218QNt = ldesTrajCell(ldesGetField(tran, 'QNt
', []), M, R);
219UNt = ldesTrajCell(ldesGetField(tran, 'UNt
', []), M, R);
220TNt = ldesTrajCell(ldesGetField(tran, 'TNt
', []), M, R);
221RNt = cell(M, R); % response-time transient not computed by LDES
224% Empty cells become scalar NaN so downstream indexing (getTranAvg) is uniform.
227 if isempty(QNt{ist, r}), QNt{ist, r} = NaN; end
228 if isempty(UNt{ist, r}), UNt{ist, r} = NaN; end
229 if isempty(TNt{ist, r}), TNt{ist, r} = NaN; end
238self.setTranAvgResults(QNt, UNt, RNt, TNt, CNt, XNt, runtime);
240% Steady-state estimates from the final transient values.
241QN = NaN(M, R); UN = NaN(M, R); RN = NaN(M, R); TN = NaN(M, R);
242WN = NaN(M, R); AN = NaN(M, R); CN = NaN(1, R); XN = NaN(1, R);
245 if ~isscalar(QNt{ist, r}) && ~isnan(QNt{ist, r}(end, 1))
246 QN(ist, r) = QNt{ist, r}(end, 1);
248 if ~isscalar(UNt{ist, r}) && ~isnan(UNt{ist, r}(end, 1))
249 UN(ist, r) = UNt{ist, r}(end, 1);
251 if ~isscalar(TNt{ist, r}) && ~isnan(TNt{ist, r}(end, 1))
252 TN(ist, r) = TNt{ist, r}(end, 1);
256self.setAvgResults(QN, UN, RN, TN, AN, WN, CN, XN, runtime, options.method, 0);
259 line_printf('LDES transient analysis complete, timespan = [%g, %g]\n
', ...
260 options.timespan(1), options.timespan(2));
264% =========================================================================