LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
getResults.m
1function [result, parsed] = getResults(self)
2% [RESULT, PARSED] = GETRESULTS()
3
4options = self.getOptions;
5
6% Check if result file exists, if not run the analyzer first
7% Need to handle case where filePath/fileName haven't been set yet
8needsAnalyzer = false;
9if isempty(self.filePath) || isempty(self.fileName)
10 needsAnalyzer = true;
11else
12 switch options.method
13 case {'jsim','default'}
14 fileName = [getFileName(self),'.jsim-result.jsim'];
15 otherwise
16 fileName = [getFileName(self),'.jmva-result.jmva'];
17 end
18 filePath = [getFilePath(self),filesep,fileName];
19 if ~exist(filePath,'file')
20 needsAnalyzer = true;
21 end
22end
23if needsAnalyzer
24 % Result file doesn't exist, need to run the simulation first
25 % runAnalyzer() calls getResults() internally and stores result in self.result
26 % It may also clean up the temp directory, so we return the cached result
27 runAnalyzer(self, options);
28 result = self.result;
29 parsed = struct(); % parsed data not available after cleanup
30 return;
31end
32
33switch options.method
34 case {'jsim','default'}
35 [result, parsed] = self.getResultsJSIM;
36 otherwise
37 [result, parsed] = self.getResultsJMVA;
38end
39
40sn = self.model.getStruct;
41
42% Extend matrices to include FCR rows at indices nstations+1 to nstations+nregions
43% This allows FCR metrics to be stored directly without custom fields
44totalRows = sn.nstations + sn.nregions;
45result.Avg.Q = zeros(totalRows, sn.nclasses);
46result.Avg.U = zeros(totalRows, sn.nclasses);
47result.Avg.R = zeros(totalRows, sn.nclasses);
48result.Avg.T = zeros(totalRows, sn.nclasses);
49result.Avg.A = zeros(totalRows, sn.nclasses);
50result.Avg.W = zeros(totalRows, sn.nclasses);
51result.Avg.Tard = zeros(totalRows, sn.nclasses);
52result.Avg.SysTard = zeros(1, sn.nclasses);
53% FCR-only metrics: weighted occupation ('FCR Capacity') and memory occupation
54% ('FCR Memory'). Populated at FCR rows only; NaN elsewhere.
55result.Avg.Weight = NaN(totalRows, sn.nclasses);
56result.Avg.MemOcc = NaN(totalRows, sn.nclasses);
57
58% Set FCR U and A to NaN (JMT doesn't provide these for regions)
59if sn.nregions > 0
60 fcrRowStart = sn.nstations + 1;
61 fcrRowEnd = sn.nstations + sn.nregions;
62 result.Avg.U(fcrRowStart:fcrRowEnd, :) = NaN;
63 result.Avg.A(fcrRowStart:fcrRowEnd, :) = NaN;
64end
65
66% Initialize CI storage (half-widths)
67[confintEnabled, ~] = Solver.parseConfInt(options.confint);
68if confintEnabled
69 result.Avg.QCI = zeros(sn.nstations, sn.nclasses);
70 result.Avg.UCI = zeros(sn.nstations, sn.nclasses);
71 result.Avg.RCI = zeros(sn.nstations, sn.nclasses);
72 result.Avg.TCI = zeros(sn.nstations, sn.nclasses);
73 result.Avg.ACI = zeros(sn.nstations, sn.nclasses);
74 result.Avg.WCI = zeros(sn.nstations, sn.nclasses);
75 result.Avg.TardCI = zeros(sn.nstations, sn.nclasses);
76 result.Avg.SysTardCI = zeros(1, sn.nclasses);
77end
78
79for m=1:length(result.metric)
80 metric = result.metric{m};
81 % Compute CI half-width from JMT bounds
82 if confintEnabled && isfield(metric, 'upperLimit') && isfield(metric, 'lowerLimit')
83 ciHalfWidth = (metric.upperLimit - metric.lowerLimit) / 2;
84 else
85 ciHalfWidth = 0;
86 end
87
88 % Check if this is a region (FCR) metric
89 % FCR metrics can be identified by either nodeType='region' or station name starting with 'FCRegion'
90 isFCRMetric = false;
91 fcrStationName = '';
92 if isfield(metric, 'nodeType') && strcmp(metric.nodeType, 'region')
93 isFCRMetric = true;
94 if isfield(metric, 'station')
95 fcrStationName = metric.station;
96 end
97 elseif isfield(metric, 'station') && startsWith(metric.station, 'FCRegion')
98 % JMT may not echo nodeType, detect by station name pattern
99 isFCRMetric = true;
100 fcrStationName = metric.station;
101 end
102
103 if isFCRMetric && ~isempty(fcrStationName) && startsWith(fcrStationName, 'FCRegion')
104 fcrIndexStr = fcrStationName(9:end); % Extract number after "FCRegion"
105 fcrIndex = str2double(fcrIndexStr);
106 if ~isnan(fcrIndex) && fcrIndex >= 1 && fcrIndex <= sn.nregions
107 % FCR row index in result matrices: nstations + fcrIndex
108 fcrRowIdx = sn.nstations + fcrIndex;
109 % FCR metrics from JMT are aggregate (not per-class), distribute across classes
110 switch metric.measureType
111 case 'Number of Customers'
112 for r = 1:sn.nclasses
113 result.Avg.Q(fcrRowIdx, r) = metric.meanValue / sn.nclasses;
114 end
115 case 'Response Time'
116 for r = 1:sn.nclasses
117 result.Avg.R(fcrRowIdx, r) = metric.meanValue;
118 end
119 case 'Residence Time'
120 for r = 1:sn.nclasses
121 result.Avg.W(fcrRowIdx, r) = metric.meanValue;
122 end
123 case 'Throughput'
124 for r = 1:sn.nclasses
125 result.Avg.T(fcrRowIdx, r) = metric.meanValue / sn.nclasses;
126 end
127 case 'Arrival Rate'
128 for r = 1:sn.nclasses
129 result.Avg.A(fcrRowIdx, r) = metric.meanValue / sn.nclasses;
130 end
131 case 'FCR Capacity' % JMT weighted occupation (Total Weight)
132 for r = 1:sn.nclasses
133 result.Avg.Weight(fcrRowIdx, r) = metric.meanValue / sn.nclasses;
134 end
135 case 'FCR Memory' % JMT memory occupation
136 for r = 1:sn.nclasses
137 result.Avg.MemOcc(fcrRowIdx, r) = metric.meanValue / sn.nclasses;
138 end
139 end
140 end
141 continue; % Skip to next metric
142 end
143
144 switch metric.measureType
145 case MetricType.toText(MetricType.QLen)
146 i = sn.nodeToStation(find(sn.nodenames == metric.station));
147 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
148 if isinf(sn.njobs(r))
149 result.Avg.Q(i,r) = metric.meanValue;
150 if confintEnabled
151 result.Avg.QCI(i,r) = ciHalfWidth;
152 end
153 else % 'closed'
154 N = sn.njobs;
155 chainIdx = find(sn.classnames == metric.class);
156 if metric.analyzedSamples > sum(sn.njobs(chainIdx)) % for a class to be considered recurrent we ask more samples than jobs in the corresponding closed chain
157 result.Avg.Q(i,r) = metric.meanValue;
158 if confintEnabled
159 result.Avg.QCI(i,r) = ciHalfWidth;
160 end
161 else
162 result.Avg.Q(i,r) = 0;
163 end
164 end
165 case MetricType.toText(MetricType.Util)
166 i = sn.nodeToStation(find(sn.nodenames == metric.station));
167 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
168 if isinf(sn.njobs(r))
169 result.Avg.U(i,r) = metric.meanValue;
170 if confintEnabled
171 result.Avg.UCI(i,r) = ciHalfWidth;
172 end
173 else % 'closed'
174 N = sn.njobs;
175 chainIdx = find(sn.classnames == metric.class);
176 if metric.analyzedSamples > sum(sn.njobs(chainIdx)) % for a class to be considered recurrent we ask more samples than jobs in the corresponding closed chain
177 result.Avg.U(i,r) = metric.meanValue;
178 if confintEnabled
179 result.Avg.UCI(i,r) = ciHalfWidth;
180 end
181 else
182 result.Avg.U(i,r) = 0;
183 end
184 end
185 case MetricType.toText(MetricType.RespT)
186 i = sn.nodeToStation(find(sn.nodenames == metric.station));
187 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
188 if isinf(sn.njobs(r))
189 result.Avg.R(i,r) = metric.meanValue;
190 if confintEnabled
191 result.Avg.RCI(i,r) = ciHalfWidth;
192 end
193 else % 'closed'
194 N = sn.njobs;
195 chainIdx = find(sn.classnames == metric.class);
196 if metric.analyzedSamples > sum(sn.njobs(chainIdx)) % for a class to be considered recurrent we ask more samples than jobs in the corresponding closed chain
197 result.Avg.R(i,r) = metric.meanValue;
198 if confintEnabled
199 result.Avg.RCI(i,r) = ciHalfWidth;
200 end
201 else
202 result.Avg.R(i,r) = 0;
203 end
204 end
205 case MetricType.toText(MetricType.ResidT)
206 % JMT ResidT is inconsistently defined with LINE's on some
207 % difficult class switching cases, hence we recompute it at the
208 % level of the NetworkSolver class to preserve consistency
209
210% i = sn.nodeToStation(find(sn.nodenames == metric.station));
211% r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
212% if isinf(sn.njobs(r))
213% result.Avg.W(i,r) = metric.meanValue;
214% else % 'closed'
215% N = sn.njobs;
216% chainIdx = find(sn.classnames == metric.class);
217% if metric.analyzedSamples > sum(sn.njobs(chainIdx)) % for a class to be considered recurrent we ask more samples than jobs in the corresponding closed chain
218% result.Avg.W(i,r) = metric.meanValue;
219% else
220% result.Avg.W(i,r) = 0;
221% end
222% end
223 case MetricType.toText(MetricType.ArvR)
224 i = sn.nodeToStation(find(sn.nodenames == metric.station));
225 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
226 if isinf(sn.njobs(r))
227 result.Avg.A(i,r) = metric.meanValue;
228 if confintEnabled
229 result.Avg.ACI(i,r) = ciHalfWidth;
230 end
231 else % 'closed'
232 N = sn.njobs;
233 chainIdx = find(sn.classnames == metric.class);
234 if metric.analyzedSamples > sum(sn.njobs(chainIdx)) % for a class to be considered recurrent we ask more samples than jobs in the corresponding closed chain
235 result.Avg.A(i,r) = metric.meanValue;
236 if confintEnabled
237 result.Avg.ACI(i,r) = ciHalfWidth;
238 end
239 else
240 result.Avg.A(i,r) = 0;
241 end
242 end
243 case MetricType.toText(MetricType.Tput)
244 i = sn.nodeToStation(find(sn.nodenames == metric.station));
245 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
246 if isinf(sn.njobs(r))
247 result.Avg.T(i,r) = metric.meanValue;
248 if confintEnabled
249 result.Avg.TCI(i,r) = ciHalfWidth;
250 end
251 else % 'closed'
252 N = sn.njobs;
253 chainIdx = find(sn.classnames == metric.class);
254 if metric.analyzedSamples > sum(sn.njobs(chainIdx)) % for a class to be considered recurrent we ask more samples than jobs in the corresponding closed chain
255 result.Avg.T(i,r) = metric.meanValue;
256 if confintEnabled
257 result.Avg.TCI(i,r) = ciHalfWidth;
258 end
259 else
260 result.Avg.T(i,r) = 0;
261 end
262 end
263 case MetricType.toText(MetricType.Tard)
264 i = sn.nodeToStation(find(sn.nodenames == metric.station));
265 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
266 if isinf(sn.njobs(r))
267 result.Avg.Tard(i,r) = metric.meanValue;
268 if confintEnabled
269 result.Avg.TardCI(i,r) = ciHalfWidth;
270 end
271 else % 'closed'
272 N = sn.njobs;
273 chainIdx = find(sn.classnames == metric.class);
274 if metric.analyzedSamples > sum(sn.njobs(chainIdx))
275 result.Avg.Tard(i,r) = metric.meanValue;
276 if confintEnabled
277 result.Avg.TardCI(i,r) = ciHalfWidth;
278 end
279 else
280 result.Avg.Tard(i,r) = 0;
281 end
282 end
283 case MetricType.toText(MetricType.SysTard)
284 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
285 if isinf(sn.njobs(r))
286 result.Avg.SysTard(1,r) = metric.meanValue;
287 if confintEnabled
288 result.Avg.SysTardCI(1,r) = ciHalfWidth;
289 end
290 else % 'closed'
291 N = sn.njobs;
292 chainIdx = find(sn.classnames == metric.class);
293 if metric.analyzedSamples > sum(sn.njobs(chainIdx))
294 result.Avg.SysTard(1,r) = metric.meanValue;
295 if confintEnabled
296 result.Avg.SysTardCI(1,r) = ciHalfWidth;
297 end
298 else
299 result.Avg.SysTard(1,r) = 0;
300 end
301 end
302
303 case 'Cache Hit Rate'
304 % Store cache hit rate for later processing
305 % Find the cache node by station name
306 cacheNodeIdx = find(cellfun(@(c) strcmp(c, metric.station), sn.nodenames));
307 hitClassIdx = find(cellfun(@(c) strcmp(c, metric.class), sn.classnames));
308 if ~isempty(cacheNodeIdx) && ~isempty(hitClassIdx) && sn.nodetype(cacheNodeIdx) == NodeType.Cache
309 % Initialize cache hit rate storage if not exists
310 if ~isfield(result, 'CacheHitRate')
311 result.CacheHitRate = struct();
312 end
313 % Store hit rate keyed by cache node index
314 fieldName = sprintf('node%d', cacheNodeIdx);
315 if ~isfield(result.CacheHitRate, fieldName)
316 result.CacheHitRate.(fieldName) = zeros(1, sn.nclasses);
317 end
318 % Find the original class (the one that maps to this hit class)
319 hitclass = sn.nodeparam{cacheNodeIdx}.hitclass;
320 for r = 1:length(hitclass)
321 if hitclass(r) == hitClassIdx
322 result.CacheHitRate.(fieldName)(r) = metric.meanValue;
323 break;
324 end
325 end
326 end
327
328 end
329end
330% Set self.result AFTER the for loop completes, so FCR metrics are included
331self.result = result;
332end
Definition Station.m:245