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);
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);
58% Set FCR U and A to NaN (JMT doesn't provide these for regions)
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;
66% Initialize CI storage (half-widths)
67[confintEnabled, ~] = Solver.parseConfInt(options.confint);
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);
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;
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'
92 if isfield(metric, 'nodeType') && strcmp(metric.nodeType, 'region')
94 if isfield(metric, 'station')
95 fcrStationName = metric.station;
97 elseif isfield(metric, 'station') && startsWith(metric.station, 'FCRegion')
98 % JMT may not echo nodeType, detect by station name pattern
100 fcrStationName = metric.station;
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;
116 for r = 1:sn.nclasses
117 result.Avg.R(fcrRowIdx, r) = metric.meanValue;
119 case 'Residence Time'
120 for r = 1:sn.nclasses
121 result.Avg.W(fcrRowIdx, r) = metric.meanValue;
124 for r = 1:sn.nclasses
125 result.Avg.T(fcrRowIdx, r) = metric.meanValue / sn.nclasses;
128 for r = 1:sn.nclasses
129 result.Avg.A(fcrRowIdx, r) = metric.meanValue / sn.nclasses;
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;
135 case 'FCR Memory' % JMT memory occupation
136 for r = 1:sn.nclasses
137 result.Avg.MemOcc(fcrRowIdx, r) = metric.meanValue / sn.nclasses;
141 continue; % Skip to next metric
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;
151 result.Avg.QCI(i,r) = ciHalfWidth;
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;
159 result.Avg.QCI(i,r) = ciHalfWidth;
162 result.Avg.Q(i,r) = 0;
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;
171 result.Avg.UCI(i,r) = ciHalfWidth;
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;
179 result.Avg.UCI(i,r) = ciHalfWidth;
182 result.Avg.U(i,r) = 0;
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;
191 result.Avg.RCI(i,r) = ciHalfWidth;
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;
199 result.Avg.RCI(i,r) = ciHalfWidth;
202 result.Avg.R(i,r) = 0;
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
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;
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;
220% result.Avg.W(i,r) = 0;
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;
229 result.Avg.ACI(i,r) = ciHalfWidth;
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;
237 result.Avg.ACI(i,r) = ciHalfWidth;
240 result.Avg.A(i,r) = 0;
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;
249 result.Avg.TCI(i,r) = ciHalfWidth;
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;
257 result.Avg.TCI(i,r) = ciHalfWidth;
260 result.Avg.T(i,r) = 0;
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;
269 result.Avg.TardCI(i,r) = ciHalfWidth;
273 chainIdx = find(sn.classnames == metric.class);
274 if metric.analyzedSamples > sum(sn.njobs(chainIdx))
275 result.Avg.Tard(i,r) = metric.meanValue;
277 result.Avg.TardCI(i,r) = ciHalfWidth;
280 result.Avg.Tard(i,r) = 0;
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;
288 result.Avg.SysTardCI(1,r) = ciHalfWidth;
292 chainIdx = find(sn.classnames == metric.class);
293 if metric.analyzedSamples > sum(sn.njobs(chainIdx))
294 result.Avg.SysTard(1,r) = metric.meanValue;
296 result.Avg.SysTardCI(1,r) = ciHalfWidth;
299 result.Avg.SysTard(1,r) = 0;
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();
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);
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;
330% Set self.result AFTER
the for loop completes, so FCR metrics are included