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
54% Set FCR U and A to NaN (JMT doesn't provide these for regions)
55if sn.nregions > 0
56 fcrRowStart = sn.nstations + 1;
57 fcrRowEnd = sn.nstations + sn.nregions;
58 result.Avg.U(fcrRowStart:fcrRowEnd, :) = NaN;
59 result.Avg.A(fcrRowStart:fcrRowEnd, :) = NaN;
60end
61
62% Initialize CI storage (half-widths)
63[confintEnabled, ~] = Solver.parseConfInt(options.confint);
64if confintEnabled
65 result.Avg.QCI = zeros(sn.nstations, sn.nclasses);
66 result.Avg.UCI = zeros(sn.nstations, sn.nclasses);
67 result.Avg.RCI = zeros(sn.nstations, sn.nclasses);
68 result.Avg.TCI = zeros(sn.nstations, sn.nclasses);
69 result.Avg.ACI = zeros(sn.nstations, sn.nclasses);
70 result.Avg.WCI = zeros(sn.nstations, sn.nclasses);
71 result.Avg.TardCI = zeros(sn.nstations, sn.nclasses);
72 result.Avg.SysTardCI = zeros(1, sn.nclasses);
73end
74
75for m=1:length(result.metric)
76 metric = result.metric{m};
77 % Compute CI half-width from JMT bounds
78 if confintEnabled && isfield(metric, 'upperLimit') && isfield(metric, 'lowerLimit')
79 ciHalfWidth = (metric.upperLimit - metric.lowerLimit) / 2;
80 else
81 ciHalfWidth = 0;
82 end
83
84 % Check if this is a region (FCR) metric
85 % FCR metrics can be identified by either nodeType='region' or station name starting with 'FCRegion'
86 isFCRMetric = false;
87 fcrStationName = '';
88 if isfield(metric, 'nodeType') && strcmp(metric.nodeType, 'region')
89 isFCRMetric = true;
90 if isfield(metric, 'station')
91 fcrStationName = metric.station;
92 end
93 elseif isfield(metric, 'station') && startsWith(metric.station, 'FCRegion')
94 % JMT may not echo nodeType, detect by station name pattern
95 isFCRMetric = true;
96 fcrStationName = metric.station;
97 end
98
99 if isFCRMetric && ~isempty(fcrStationName) && startsWith(fcrStationName, 'FCRegion')
100 fcrIndexStr = fcrStationName(9:end); % Extract number after "FCRegion"
101 fcrIndex = str2double(fcrIndexStr);
102 if ~isnan(fcrIndex) && fcrIndex >= 1 && fcrIndex <= sn.nregions
103 % FCR row index in result matrices: nstations + fcrIndex
104 fcrRowIdx = sn.nstations + fcrIndex;
105 % FCR metrics from JMT are aggregate (not per-class), distribute across classes
106 switch metric.measureType
107 case 'Number of Customers'
108 for r = 1:sn.nclasses
109 result.Avg.Q(fcrRowIdx, r) = metric.meanValue / sn.nclasses;
110 end
111 case 'Response Time'
112 for r = 1:sn.nclasses
113 result.Avg.R(fcrRowIdx, r) = metric.meanValue;
114 end
115 case 'Residence Time'
116 for r = 1:sn.nclasses
117 result.Avg.W(fcrRowIdx, r) = metric.meanValue;
118 end
119 case 'Throughput'
120 for r = 1:sn.nclasses
121 result.Avg.T(fcrRowIdx, r) = metric.meanValue / sn.nclasses;
122 end
123 case 'Arrival Rate'
124 for r = 1:sn.nclasses
125 result.Avg.A(fcrRowIdx, r) = metric.meanValue / sn.nclasses;
126 end
127 end
128 end
129 continue; % Skip to next metric
130 end
131
132 switch metric.measureType
133 case MetricType.toText(MetricType.QLen)
134 i = sn.nodeToStation(find(sn.nodenames == metric.station));
135 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
136 if isinf(sn.njobs(r))
137 result.Avg.Q(i,r) = metric.meanValue;
138 if confintEnabled
139 result.Avg.QCI(i,r) = ciHalfWidth;
140 end
141 else % 'closed'
142 N = sn.njobs;
143 chainIdx = find(sn.classnames == metric.class);
144 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
145 result.Avg.Q(i,r) = metric.meanValue;
146 if confintEnabled
147 result.Avg.QCI(i,r) = ciHalfWidth;
148 end
149 else
150 result.Avg.Q(i,r) = 0;
151 end
152 end
153 case MetricType.toText(MetricType.Util)
154 i = sn.nodeToStation(find(sn.nodenames == metric.station));
155 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
156 if isinf(sn.njobs(r))
157 result.Avg.U(i,r) = metric.meanValue;
158 if confintEnabled
159 result.Avg.UCI(i,r) = ciHalfWidth;
160 end
161 else % 'closed'
162 N = sn.njobs;
163 chainIdx = find(sn.classnames == metric.class);
164 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
165 result.Avg.U(i,r) = metric.meanValue;
166 if confintEnabled
167 result.Avg.UCI(i,r) = ciHalfWidth;
168 end
169 else
170 result.Avg.U(i,r) = 0;
171 end
172 end
173 case MetricType.toText(MetricType.RespT)
174 i = sn.nodeToStation(find(sn.nodenames == metric.station));
175 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
176 if isinf(sn.njobs(r))
177 result.Avg.R(i,r) = metric.meanValue;
178 if confintEnabled
179 result.Avg.RCI(i,r) = ciHalfWidth;
180 end
181 else % 'closed'
182 N = sn.njobs;
183 chainIdx = find(sn.classnames == metric.class);
184 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
185 result.Avg.R(i,r) = metric.meanValue;
186 if confintEnabled
187 result.Avg.RCI(i,r) = ciHalfWidth;
188 end
189 else
190 result.Avg.R(i,r) = 0;
191 end
192 end
193 case MetricType.toText(MetricType.ResidT)
194 % JMT ResidT is inconsistently defined with LINE's on some
195 % difficult class switching cases, hence we recompute it at the
196 % level of the NetworkSolver class to preserve consistency
197
198% i = sn.nodeToStation(find(sn.nodenames == metric.station));
199% r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
200% if isinf(sn.njobs(r))
201% result.Avg.W(i,r) = metric.meanValue;
202% else % 'closed'
203% N = sn.njobs;
204% chainIdx = find(sn.classnames == metric.class);
205% 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
206% result.Avg.W(i,r) = metric.meanValue;
207% else
208% result.Avg.W(i,r) = 0;
209% end
210% end
211 case MetricType.toText(MetricType.ArvR)
212 i = sn.nodeToStation(find(sn.nodenames == metric.station));
213 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
214 if isinf(sn.njobs(r))
215 result.Avg.A(i,r) = metric.meanValue;
216 if confintEnabled
217 result.Avg.ACI(i,r) = ciHalfWidth;
218 end
219 else % 'closed'
220 N = sn.njobs;
221 chainIdx = find(sn.classnames == metric.class);
222 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
223 result.Avg.A(i,r) = metric.meanValue;
224 if confintEnabled
225 result.Avg.ACI(i,r) = ciHalfWidth;
226 end
227 else
228 result.Avg.A(i,r) = 0;
229 end
230 end
231 case MetricType.toText(MetricType.Tput)
232 i = sn.nodeToStation(find(sn.nodenames == metric.station));
233 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
234 if isinf(sn.njobs(r))
235 result.Avg.T(i,r) = metric.meanValue;
236 if confintEnabled
237 result.Avg.TCI(i,r) = ciHalfWidth;
238 end
239 else % 'closed'
240 N = sn.njobs;
241 chainIdx = find(sn.classnames == metric.class);
242 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
243 result.Avg.T(i,r) = metric.meanValue;
244 if confintEnabled
245 result.Avg.TCI(i,r) = ciHalfWidth;
246 end
247 else
248 result.Avg.T(i,r) = 0;
249 end
250 end
251 case MetricType.toText(MetricType.Tard)
252 i = sn.nodeToStation(find(sn.nodenames == metric.station));
253 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
254 if isinf(sn.njobs(r))
255 result.Avg.Tard(i,r) = metric.meanValue;
256 if confintEnabled
257 result.Avg.TardCI(i,r) = ciHalfWidth;
258 end
259 else % 'closed'
260 N = sn.njobs;
261 chainIdx = find(sn.classnames == metric.class);
262 if metric.analyzedSamples > sum(sn.njobs(chainIdx))
263 result.Avg.Tard(i,r) = metric.meanValue;
264 if confintEnabled
265 result.Avg.TardCI(i,r) = ciHalfWidth;
266 end
267 else
268 result.Avg.Tard(i,r) = 0;
269 end
270 end
271 case MetricType.toText(MetricType.SysTard)
272 r = find(cellfun(@(c) strcmp(c,metric.class), sn.classnames));
273 if isinf(sn.njobs(r))
274 result.Avg.SysTard(1,r) = metric.meanValue;
275 if confintEnabled
276 result.Avg.SysTardCI(1,r) = ciHalfWidth;
277 end
278 else % 'closed'
279 N = sn.njobs;
280 chainIdx = find(sn.classnames == metric.class);
281 if metric.analyzedSamples > sum(sn.njobs(chainIdx))
282 result.Avg.SysTard(1,r) = metric.meanValue;
283 if confintEnabled
284 result.Avg.SysTardCI(1,r) = ciHalfWidth;
285 end
286 else
287 result.Avg.SysTard(1,r) = 0;
288 end
289 end
290
291 case 'Cache Hit Rate'
292 % Store cache hit rate for later processing
293 % Find the cache node by station name
294 cacheNodeIdx = find(cellfun(@(c) strcmp(c, metric.station), sn.nodenames));
295 hitClassIdx = find(cellfun(@(c) strcmp(c, metric.class), sn.classnames));
296 if ~isempty(cacheNodeIdx) && ~isempty(hitClassIdx) && sn.nodetype(cacheNodeIdx) == NodeType.Cache
297 % Initialize cache hit rate storage if not exists
298 if ~isfield(result, 'CacheHitRate')
299 result.CacheHitRate = struct();
300 end
301 % Store hit rate keyed by cache node index
302 fieldName = sprintf('node%d', cacheNodeIdx);
303 if ~isfield(result.CacheHitRate, fieldName)
304 result.CacheHitRate.(fieldName) = zeros(1, sn.nclasses);
305 end
306 % Find the original class (the one that maps to this hit class)
307 hitclass = sn.nodeparam{cacheNodeIdx}.hitclass;
308 for r = 1:length(hitclass)
309 if hitclass(r) == hitClassIdx
310 result.CacheHitRate.(fieldName)(r) = metric.meanValue;
311 break;
312 end
313 end
314 end
315
316 end
317end
318% Set self.result AFTER the for loop completes, so FCR metrics are included
319self.result = result;
320end