LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
runAnalyzer.m
1function [runtime, tranSysState, tranSync] = runAnalyzer(self, options)
2% [RUNTIME, TRANSYSSTATE] = RUNANALYZER()
3%
4% Run the LDES solver on the queueing network model.
5%
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.
12
13T0 = tic;
14if nargin < 2
15 options = self.getOptions;
16end
17
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);
22end
23
24tranSysState = [];
25tranSync = [];
26
27if isa(self.model, 'LayeredNetwork')
28 self.obj.getAvg(); % runs the LN LDES analyzer (Java ensemble backend)
29 runtime = toc(T0);
30 return
31end
32
33self.runAnalyzerChecks(options);
34Solver.resetRandomGeneratorSeed(options.seed);
35
36line_debug('LDES solver starting: samples=%d, seed=%d', options.samples, options.seed);
37
38sn = self.model.getStruct;
39M = sn.nstations;
40R = sn.nclasses;
41[confintEnabled, ~] = Solver.parseConfInt(options.confint);
42
43isTransient = isfield(options, 'timespan') && numel(options.timespan) >= 2 && ...
44 isfinite(options.timespan(2));
45
46extraFlags = {};
47if isTransient
48 extraFlags = {'--timespan', ...
49 sprintf('%.10g,%.10g', options.timespan(1), options.timespan(2)), ...
50 '--trajectory'};
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.
57 reps = 0;
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;
63 end
64 if reps > 1
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));
71 end
72 end
73end
74
75data = self.solveCli(options, extraFlags);
76if ~isstruct(data) || ~isfield(data, 'metrics')
77 line_error(mfilename, 'LDES engine returned no metrics.');
78end
79
80if isTransient
81 parseTransient(self, data, M, R, options);
82else
83 parseSteady(self, data, sn, M, R, confintEnabled, options);
84end
85
86runtime = toc(T0);
87end
88
89% =========================================================================
90
91function parseSteady(self, data, sn, M, R, confintEnabled, options)
92% PARSESTEADY Marshal steady-state result JSON onto the solver.
93
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);
102if isempty(QN)
103 line_error('runAnalyzer', 'LDES result metrics could not be parsed.');
104end
105
106if isfield(data, 'runtime') && isscalar(data.runtime) && isnumeric(data.runtime)
107 runtime = data.runtime;
108else
109 runtime = 0;
110end
111
112% Finite capacity region (FCR) metrics: append region rows after the stations.
113F = sn.nregions;
114WeightNfcr = [];
115MemOccNfcr = [];
116if F > 0 && isfield(data, 'fcr')
117 fcr = 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);
123 if ~isempty(QNfcr)
124 ANfcr = NaN(F, R); % arrival rate not applicable to FCR rows
125 QN = [QN; QNfcr];
126 UN = [UN; UNfcr];
127 RN = [RN; RNfcr];
128 TN = [TN; TNfcr];
129 WN = [WN; WNfcr];
130 AN = [AN; ANfcr];
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
135 end
136end
137
138if options.verbose
139 line_printf('LDES samples: %8d\n', options.samples);
140end
141
142self.setAvgResults(QN, UN, RN, TN, AN, WN, CN, XN, runtime, options.method, options.samples);
143
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;
151end
152
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');
157for ind = 1:sn.nnodes
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([]));
164 if haveCacheMetrics
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
180 end
181 end
182 end
183end
184
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, [], []);
195end
196end
197
198% =========================================================================
199
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.
204
205runtime = 0;
206if isfield(data, 'runtime') && isscalar(data.runtime) && isnumeric(data.runtime)
207 runtime = data.runtime;
208end
209
210if ~isfield(data, 'transient') || isempty(data.transient) || ~isfield(data.transient, 't')
211 if options.verbose
212 line_printf('LDES transient analysis produced no trajectory.\n');
213 end
214 return;
215end
216tran = data.transient;
217
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
222CNt = cell(1, R);
223XNt = cell(1, R);
224% Empty cells become scalar NaN so downstream indexing (getTranAvg) is uniform.
225for ist = 1:M
226 for r = 1:R
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
230 RNt{ist, r} = NaN;
231 end
232end
233for r = 1:R
234 CNt{1, r} = NaN;
235 XNt{1, r} = NaN;
236end
237
238self.setTranAvgResults(QNt, UNt, RNt, TNt, CNt, XNt, runtime);
239
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);
243for ist = 1:M
244 for r = 1:R
245 if ~isscalar(QNt{ist, r}) && ~isnan(QNt{ist, r}(end, 1))
246 QN(ist, r) = QNt{ist, r}(end, 1);
247 end
248 if ~isscalar(UNt{ist, r}) && ~isnan(UNt{ist, r}(end, 1))
249 UN(ist, r) = UNt{ist, r}(end, 1);
250 end
251 if ~isscalar(TNt{ist, r}) && ~isnan(TNt{ist, r}(end, 1))
252 TN(ist, r) = TNt{ist, r}(end, 1);
253 end
254 end
255end
256self.setAvgResults(QN, UN, RN, TN, AN, WN, CN, XN, runtime, options.method, 0);
257
258if options.verbose
259 line_printf('LDES transient analysis complete, timespan = [%g, %g]\n', ...
260 options.timespan(1), options.timespan(2));
261end
262end
263
264% =========================================================================
Definition Station.m:245