1function [result, parsed] = getResults(self)
2% [RESULT, PARSED] = GETRESULTS()
4options = self.getOptions;
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
9if isempty(self.filePath) || isempty(self.fileName)
13 case {
'jsim',
'default'}
14 fileName = [getFileName(self),
'.jsim-result.jsim'];
16 fileName = [getFileName(self),
'.jmva-result.jmva'];
18 filePath = [getFilePath(self),filesep,fileName];
19 if ~exist(filePath,
'file')
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);
29 parsed = struct(); % parsed data not available after cleanup
34 case {
'jsim',
'default'}
35 [result, parsed] = self.getResultsJSIM;
37 [result, parsed] = self.getResultsJMVA;
40sn = self.model.getStruct;
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);
54% Set FCR U and A to NaN (JMT doesn
't provide these for regions)
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;
62% Initialize CI storage (half-widths)
63[confintEnabled, ~] = Solver.parseConfInt(options.confint);
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);
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;
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
'
88 if isfield(metric, 'nodeType
') && strcmp(metric.nodeType, 'region
')
90 if isfield(metric, 'station
')
91 fcrStationName = metric.station;
93 elseif isfield(metric, 'station
') && startsWith(metric.station, 'FCRegion
')
94 % JMT may not echo nodeType, detect by station name pattern
96 fcrStationName = metric.station;
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;
112 for r = 1:sn.nclasses
113 result.Avg.R(fcrRowIdx, r) = metric.meanValue;
115 case 'Residence Time
'
116 for r = 1:sn.nclasses
117 result.Avg.W(fcrRowIdx, r) = metric.meanValue;
120 for r = 1:sn.nclasses
121 result.Avg.T(fcrRowIdx, r) = metric.meanValue / sn.nclasses;
124 for r = 1:sn.nclasses
125 result.Avg.A(fcrRowIdx, r) = metric.meanValue / sn.nclasses;
129 continue; % Skip to next metric
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;
139 result.Avg.QCI(i,r) = ciHalfWidth;
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;
147 result.Avg.QCI(i,r) = ciHalfWidth;
150 result.Avg.Q(i,r) = 0;
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;
159 result.Avg.UCI(i,r) = ciHalfWidth;
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;
167 result.Avg.UCI(i,r) = ciHalfWidth;
170 result.Avg.U(i,r) = 0;
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;
179 result.Avg.RCI(i,r) = ciHalfWidth;
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;
187 result.Avg.RCI(i,r) = ciHalfWidth;
190 result.Avg.R(i,r) = 0;
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
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;
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;
208% result.Avg.W(i,r) = 0;
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;
217 result.Avg.ACI(i,r) = ciHalfWidth;
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;
225 result.Avg.ACI(i,r) = ciHalfWidth;
228 result.Avg.A(i,r) = 0;
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;
237 result.Avg.TCI(i,r) = ciHalfWidth;
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;
245 result.Avg.TCI(i,r) = ciHalfWidth;
248 result.Avg.T(i,r) = 0;
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;
257 result.Avg.TardCI(i,r) = ciHalfWidth;
261 chainIdx = find(sn.classnames == metric.class);
262 if metric.analyzedSamples > sum(sn.njobs(chainIdx))
263 result.Avg.Tard(i,r) = metric.meanValue;
265 result.Avg.TardCI(i,r) = ciHalfWidth;
268 result.Avg.Tard(i,r) = 0;
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;
276 result.Avg.SysTardCI(1,r) = ciHalfWidth;
280 chainIdx = find(sn.classnames == metric.class);
281 if metric.analyzedSamples > sum(sn.njobs(chainIdx))
282 result.Avg.SysTard(1,r) = metric.meanValue;
284 result.Avg.SysTardCI(1,r) = ciHalfWidth;
287 result.Avg.SysTard(1,r) = 0;
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();
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);
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;
318% Set self.result AFTER the
for loop completes, so FCR metrics are included