1function [QNclass,UNclass,RNclass,TNclass,ANclass,WNclass] = getAvg(self,Q,U,R,T,A,W)
2% [QNCLASS,UNCLASS,RNCLASS,TNCLASS,ANCLASS,WNCLASS] = GETAVG(SELF,Q,U,R,T,A,W)
4% Compute steady-state average metrics (queue length, utilization, response time,
5% throughput, arrival rate, residence time)
for all stations and job classes.
7% Copyright (c) 2012-2026, Imperial College London
10if isa(self.model,
'LayeredNetwork')
11 % Java-backed LQN simulation (SolverLDES): per-LQN-element vectors,
12 % same convention as SolverLN/SolverLQNS getAvg
13 self.obj.getAvg(); % runs
the LN LDES analyzer
14 avgTable = self.obj.getLNAvgTable();
15 [QNclass,UNclass,RNclass,WNclass,ANclass,TNclass] = JLINE.arrayListToResults(avgTable);
19sn = self.model.getStruct();
21if strcmp(self.options.lang,
'java') && ~strcmp(self.name,
'SolverLDES')
25 % Force fresh Java model conversion each time to ensure current state
26 % Only re-create
the Java model if
the model
is not already Java-native
27 if ~self.model.isJavaNative()
31 self.obj.getOptions.verbose = jline.VerboseLevel.STD;
32 % self.obj
is rebuilt on every call above, so
the JLINE-side fork-join (MMT)
33 % iterate cannot survive an outer iteration such as SolverLN on its own.
34 % Carry it across
the rebuild, exactly as
the lang='matlab' path does with
35 % SolverMVA.fjForkLambda, so layers warm-start identically in both languages.
36 isMVA = isa(self,'SolverMVA');
37 if isMVA && self.options.config.fj_warmstart && ~isempty(self.fjForkLambda)
38 self.obj.setForkWarmStart(JLINE.from_line_matrix(self.fjForkLambda));
40 SolverResult = self.obj.getAvg();
41 if isMVA && self.model.hasFork
42 self.fjForkLambda = JLINE.from_jline_matrix(self.obj.getForkWarmStart());
44 QN = JLINE.from_jline_matrix(SolverResult.QN);
45 UN = JLINE.from_jline_matrix(SolverResult.UN);
46 RN = JLINE.from_jline_matrix(SolverResult.RN);
47 TN = JLINE.from_jline_matrix(SolverResult.TN);
48 AN = JLINE.from_jline_matrix(SolverResult.AN);
49 WN = JLINE.from_jline_matrix(SolverResult.WN);
50 runtime=SolverResult.runtime;
51 method=SolverResult.method;
52 % Extract cache hit/miss probabilities from Java model
54 if sn.nodetype(ind) == NodeType.Cache
55 jnode = self.model.obj.getNodeByIndex(ind-1);
56 hitRatioVec = JLINE.from_jline_matrix(jnode.getHitRatio());
57 missRatioVec = JLINE.from_jline_matrix(jnode.getMissRatio());
58 % Store per-class hit/miss probabilities matching MATLAB format:
59 % only parent classes with hitClass>0 have non-zero entries
60 hitClass = self.model.
nodes{ind}.getHitClass;
61 nk = length(hitClass);
62 hitprob = zeros(1, nk);
63 missprob = zeros(1, nk);
65 if hitClass(k) > 0 && k <= length(hitRatioVec)
66 hitprob(k) = hitRatioVec(k);
67 missprob(k) = missRatioVec(k);
70 self.model.nodes{ind}.setResultHitProb(hitprob);
71 self.model.nodes{ind}.setResultMissProb(missprob);
72 % Retrieval-cache extras (delayed-hit ratio, per-list hit ratio and
73 % expected latency) copied raw from
the Java cache so getAvgCacheTable
74 % matches
the native path. Empty
for plain caches.
75 self.model.nodes{ind}.setResultDelayedHitProb(JLINE.from_jline_matrix(jnode.getDelayedHitRatio()));
76 self.model.nodes{ind}.setResultHitProbList(JLINE.from_jline_matrix(jnode.getHitRatioByList()));
77 self.model.nodes{ind}.setResultItemProb(JLINE.from_jline_matrix(jnode.getItemProb()));
78 self.model.nodes{ind}.setResultResidT(JLINE.from_jline_matrix(jnode.getResidT()));
81 if any(sn.nodetype == NodeType.Cache)
82 self.model.refreshStruct(true);
84 self.setAvgResults(QN,UN,RN,TN,AN,WN,[],[],runtime,method,1);
85 % Finite Capacity Region (FCR) metrics: self.obj.getAvg() returns only
the
86 % M station rows, but
the JAR's node-level getAvgNode() also carries
the
87 % region-aggregate metrics as FCR virtual-node rows (index nnodes+f).
88 % Append them to result.Avg as pseudo-stations M+1..M+F so getAvgNode /
89 % getAvgNodeTable surface
the region rows, matching native MATLAB (see
90 % @NetworkSolver/getAvgNode.m). getAvgNode() reuses
the JAR's cached result
91 % (options.cache), so no re-simulation
is triggered. Only SolverJMT produces
92 % region-aggregate metrics (QNfcr); other
solvers leave them empty, exactly
93 % as in native MATLAB, so restrict
the fetch to JMT.
94 if sn.nregions > 0 && strcmp(self.name,'SolverJMT')
95 jnode = self.obj.getAvgNode();
96 Qn = JLINE.from_jline_matrix(jnode.QN);
97 Un = JLINE.from_jline_matrix(jnode.UN);
98 Rn = JLINE.from_jline_matrix(jnode.RN);
99 Wn = JLINE.from_jline_matrix(jnode.WN);
100 Tn = JLINE.from_jline_matrix(jnode.TN);
101 An = JLINE.from_jline_matrix(jnode.AN);
102 for f = 1:sn.nregions
103 self.result.Avg.Q(M+f,:) = Qn(sn.nnodes+f,:);
104 self.result.Avg.U(M+f,:) = Un(sn.nnodes+f,:);
105 self.result.Avg.R(M+f,:) = Rn(sn.nnodes+f,:);
106 self.result.Avg.W(M+f,:) = Wn(sn.nnodes+f,:);
107 self.result.Avg.T(M+f,:) = Tn(sn.nnodes+f,:);
108 self.result.Avg.A(M+f,:) = An(sn.nnodes+f,:);
111 QNclass = reshape(QN,M,R);
112 UNclass = reshape(UN,M,R);
113 RNclass = reshape(RN,M,R);
114 TNclass = reshape(TN,M,R);
115 ANclass = reshape(AN,M,R);
116 WNclass = reshape(WN,M,R);
121if nargin == 1 % no parameter
122 if isempty(self.model.handles) || ~isfield(self.model.handles,'Q') || ...
123 ~isfield(self.model.handles,'U') || ~isfield(self.model.handles,'R') || ...
124 ~isfield(self.model.handles,'T') || ~isfield(self.model.handles,'A') || ...
125 ~isfield(self.model.handles,'W')
126 reset(self); % reset results in case there are partial results saved
128 [Q,U,R,T,A,W] = self.getAvgHandles;
131 [Q,U,R,T,A,W] = deal(handlers{:}); % set Q=handlers{1}, U=handlers{2}, ...
134if isfield(self.options,
'timespan')
135 if isfinite(self.options.timespan(2))
136 line_error(mfilename,'The getAvg method does not support
the timespan option, use
the getTranAvg method instead.');
139 self.options.timespan = [0,Inf];
142if ~self.hasAvgResults() || ~self.options.cache
144 %
the next line
is required because getAvg can alter
the chain
145 % structure in
the presence of caches so we need to reload sn
146 sn = self.model.getStruct;
147 if ~self.hasAvgResults
148 line_error(mfilename,'Unable to return results for this model.');
150end % else return cached value
156% Check if this
is an SPN model (Places don't have response times)
157hasSPN = any(sn.nodetype == NodeType.Place) || any(sn.nodetype == NodeType.Transition);
160 RNclass = filterMetric(R, self.result.Avg.R, [], sn, K, M);
166 % For SPNs, don't zero Q based on R because Places don't have response times
170 zeroMaskQ = RNclass < 10 * GlobalConstants.FineTol;
172 QNclass = filterMetric(Q, self.result.Avg.Q, zeroMaskQ, sn, K, M);
178 % For SPNs, don't zero U based on R because Places don't have response times
182 zeroMaskU = RNclass < 10 * GlobalConstants.FineTol;
184 UNclass = filterMetric(U, self.result.Avg.U, zeroMaskU, sn, K, M);
190 TNclass = filterMetric(T, self.result.Avg.T, [], sn, K, M);
196 zeroMask = false(size(RNclass));
197 zeroMask(sn.nodeToStation(sn.nodetype==NodeType.Source),:) = true;
198 ANclass = filterMetric(A, self.result.Avg.A, zeroMask, sn, K, M);
204 WNclass = sn_get_residt_from_respt(sn, RNclass, W);
210 % A finite-server open queueing station
is unstable when its offered load
211 % rho = sum_r T(i,r)/(nservers(i)*rate(i,r)) >= 1. Such a station
is fully
212 % saturated, so its utilization
is reported capped at 1.0 (split across
213 % classes by offered load) with a single instability warning. rho
is
214 % recomputed from throughput and service rate so
the cap
is independent of
215 % whatever value
the solver algorithm left in U. Source and infinite-server /
216 % delay stations are excluded.
218 if any(isinf(sn.njobs)) && ~isempty(TNclass)
219 srcStations = sn.nodeToStation(sn.nodetype==NodeType.Source);
220 openCls = isinf(sn.njobs(:).');
221 for i = 1:size(UNclass,1)
223 if ~isfinite(c) || c <= 0 || any(srcStations==i)
224 continue % infinite-server / delay / source station
226 rho = zeros(1,size(UNclass,2));
227 for r = 1:size(UNclass,2)
228 if sn.rates(i,r) > 0 && TNclass(i,r) > 0
229 rho(r) = TNclass(i,r) / (c * sn.rates(i,r));
232 rhoOpen = sum(rho(openCls));
234 if rhoOpen >= 1 && rhoTot > 0
236 UNclass(i,:) = rho / rhoTot; % station total capped to 1.0
237 % A saturated open station has unbounded backlog: report queue
238 % length and response time as Inf for its open classes (matching
239 %
the native-Python and JAR
solvers), so downstream consumers
240 % (e.g. SLA constraints in line-opt) treat it as infeasible
241 % rather than reading a spuriously finite response time.
243 RNclass(i,openCls) = Inf;
246 QNclass(i,openCls) = Inf;
252 line_warning(mfilename,'The model has unstable queues (utilization >= 1); station utilization
is reported capped at 1.0, queue length and response time as Inf.\n')
257function outData = filterMetric(handle, metric, zeroMask, sn, K, M)
258% post-process Avg measure
259outData = zeros(M, K);
262 if ~handle{i,k}.disabled && ~isempty(metric)
263 outData(i,k) = metric(i,k);
270% NaN values indicate that a metric
is disabled
271outData(isnan(outData)) = 0;
272% set to zero entries associated to immediate transitions
273outData(zeroMask) = 0;
274% round to zero numerical perturbations
275outData(outData < GlobalConstants.FineTol) = 0;
277% set to zero metrics
for classes that are unreachable
278% but skip
this check
for fork-join models where
the visits calculation
279% doesn
't correctly capture the parent class visiting Join and downstream nodes
280% also skip when no chains are defined (routing not specified)
281% also skip for SPN models where Places don't have traditional
visits
282hasForkJoin = any(sn.nodetype == NodeType.Fork) && any(sn.nodetype == NodeType.Join);
283hasSPN = any(sn.nodetype == NodeType.Place) || any(sn.nodetype == NodeType.Transition);
285if sn.nchains > 0 && ~hasSPN % Only check reachability
if chains are defined and not SPN
287 c = sn.chains(:, k)>0;
288 if any(c) % Only check
if class k belongs to a chain
290 if sn.visits{c}(i,k) == 0
291 % For fork-join models, don
't zero out if the metric has a non-zero value
292 % from simulation - the visits calculation doesn't capture fork-join semantics
293 % where Join outputs
the parent
class
294 if hasForkJoin && ~isempty(metric) && metric(i,k) > GlobalConstants.FineTol
295 continue; % Trust
the simulation result