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 % Carry the JLINE-side fork-join (MMT) iterate across the model rebuild so
33 % layers warm-start as in lang='matlab'. see _kb/05-solvers-overview.md
34 isMVA = isa(self,'SolverMVA');
35 if isMVA && self.options.config.fj_warmstart && ~isempty(self.fjForkLambda)
36 self.obj.setForkWarmStart(JLINE.from_line_matrix(self.fjForkLambda));
38 SolverResult = self.obj.getAvg();
39 if isMVA && self.model.hasFork
40 self.fjForkLambda = JLINE.from_jline_matrix(self.obj.getForkWarmStart());
42 QN = JLINE.from_jline_matrix(SolverResult.QN);
43 UN = JLINE.from_jline_matrix(SolverResult.UN);
44 RN = JLINE.from_jline_matrix(SolverResult.RN);
45 TN = JLINE.from_jline_matrix(SolverResult.TN);
46 AN = JLINE.from_jline_matrix(SolverResult.AN);
47 WN = JLINE.from_jline_matrix(SolverResult.WN);
48 runtime=SolverResult.runtime;
49 method=SolverResult.method;
50 % Extract cache hit/miss probabilities from Java model
52 if sn.nodetype(ind) == NodeType.Cache
53 jnode = self.model.obj.getNodeByIndex(ind-1);
54 hitRatioVec = JLINE.from_jline_matrix(jnode.getHitRatio());
55 missRatioVec = JLINE.from_jline_matrix(jnode.getMissRatio());
56 % Store per-class hit/miss probabilities matching MATLAB format:
57 % only parent classes with hitClass>0 have non-zero entries
58 hitClass = self.model.
nodes{ind}.getHitClass;
59 nk = length(hitClass);
60 hitprob = zeros(1, nk);
61 missprob = zeros(1, nk);
63 if hitClass(k) > 0 && k <= length(hitRatioVec)
64 hitprob(k) = hitRatioVec(k);
65 missprob(k) = missRatioVec(k);
68 self.model.nodes{ind}.setResultHitProb(hitprob);
69 self.model.nodes{ind}.setResultMissProb(missprob);
70 % Retrieval-cache extras (delayed-hit ratio, per-list hit ratio and
71 % expected latency) copied raw from the Java cache so getAvgCacheTable
72 % matches the native path. Empty
for plain caches.
73 self.model.nodes{ind}.setResultDelayedHitProb(JLINE.from_jline_matrix(jnode.getDelayedHitRatio()));
74 self.model.nodes{ind}.setResultHitProbList(JLINE.from_jline_matrix(jnode.getHitRatioByList()));
75 self.model.nodes{ind}.setResultItemProb(JLINE.from_jline_matrix(jnode.getItemProb()));
76 self.model.nodes{ind}.setResultResidT(JLINE.from_jline_matrix(jnode.getResidT()));
79 if any(sn.nodetype == NodeType.Cache)
80 self.model.refreshStruct(true);
82 self.setAvgResults(QN,UN,RN,TN,AN,WN,[],[],runtime,method,1);
83 % Finite Capacity Region (FCR) metrics appended as pseudo-stations
84 % M+1..M+F (JMT only). see _kb/06-solver-catalog.md for rationale
85 if sn.nregions > 0 && strcmp(self.name,'SolverJMT')
86 jnode = self.obj.getAvgNode();
87 Qn = JLINE.from_jline_matrix(jnode.QN);
88 Un = JLINE.from_jline_matrix(jnode.UN);
89 Rn = JLINE.from_jline_matrix(jnode.RN);
90 Wn = JLINE.from_jline_matrix(jnode.WN);
91 Tn = JLINE.from_jline_matrix(jnode.TN);
92 An = JLINE.from_jline_matrix(jnode.AN);
94 self.result.Avg.Q(M+f,:) = Qn(sn.nnodes+f,:);
95 self.result.Avg.U(M+f,:) = Un(sn.nnodes+f,:);
96 self.result.Avg.R(M+f,:) = Rn(sn.nnodes+f,:);
97 self.result.Avg.W(M+f,:) = Wn(sn.nnodes+f,:);
98 self.result.Avg.T(M+f,:) = Tn(sn.nnodes+f,:);
99 self.result.Avg.A(M+f,:) = An(sn.nnodes+f,:);
102 QNclass = reshape(QN,M,R);
103 UNclass = reshape(UN,M,R);
104 RNclass = reshape(RN,M,R);
105 TNclass = reshape(TN,M,R);
106 ANclass = reshape(AN,M,R);
107 WNclass = reshape(WN,M,R);
112if nargin == 1 % no parameter
113 if isempty(self.model.handles) || ~isfield(self.model.handles,'Q') || ...
114 ~isfield(self.model.handles,'U') || ~isfield(self.model.handles,'R') || ...
115 ~isfield(self.model.handles,'T') || ~isfield(self.model.handles,'A') || ...
116 ~isfield(self.model.handles,'W')
117 reset(self); % reset results in case there are partial results saved
119 [Q,U,R,T,A,W] = self.getAvgHandles;
122 [Q,U,R,T,A,W] = deal(handlers{:}); % set Q=handlers{1}, U=handlers{2}, ...
125if isfield(self.options,
'timespan')
126 if isfinite(self.options.timespan(2))
127 line_error(mfilename,'The getAvg method does not support the timespan option, use the getTranAvg method instead.');
130 self.options.timespan = [0,Inf];
133if ~self.hasAvgResults() || ~self.options.cache
135 % the next line
is required because getAvg can alter the chain
136 % structure in the presence of caches so we need to reload sn
137 sn = self.model.getStruct;
138 if ~self.hasAvgResults
139 line_error(mfilename,'Unable to return results for this model.');
141end % else return cached value
147% Check if this
is an SPN model (Places don't have response times)
148hasSPN = any(sn.nodetype == NodeType.Place) || any(sn.nodetype == NodeType.Transition);
151 RNclass = filterMetric(R, self.result.Avg.R, [], sn, K, M);
157 % For SPNs, don't zero Q based on R because Places don't have response times
161 zeroMaskQ = RNclass < 10 * GlobalConstants.FineTol;
163 QNclass = filterMetric(Q, self.result.Avg.Q, zeroMaskQ, sn, K, M);
169 % For SPNs, don't zero U based on R because Places don't have response times
173 zeroMaskU = RNclass < 10 * GlobalConstants.FineTol;
175 UNclass = filterMetric(U, self.result.Avg.U, zeroMaskU, sn, K, M);
181 TNclass = filterMetric(T, self.result.Avg.T, [], sn, K, M);
187 zeroMask = false(size(RNclass));
188 zeroMask(sn.nodeToStation(sn.nodetype==NodeType.Source),:) = true;
189 ANclass = filterMetric(A, self.result.Avg.A, zeroMask, sn, K, M);
195 WNclass = sn_get_residt_from_respt(sn, RNclass, W);
201 % Finite-server open station unstable when offered load rho >= 1: Util capped
202 % at 1.0, QLen/RespT set Inf. see _kb/06-solver-catalog.md for rationale
204 if any(isinf(sn.njobs)) && ~isempty(TNclass)
205 srcStations = sn.nodeToStation(sn.nodetype==NodeType.Source);
206 openCls = isinf(sn.njobs(:).');
207 for i = 1:size(UNclass,1)
209 if ~isfinite(c) || c <= 0 || any(srcStations==i)
210 continue % infinite-server / delay / source station
212 rho = zeros(1,size(UNclass,2));
213 for r = 1:size(UNclass,2)
214 if sn.rates(i,r) > 0 && TNclass(i,r) > 0
215 rho(r) = TNclass(i,r) / (c * sn.rates(i,r));
218 rhoOpen = sum(rho(openCls));
220 if rhoOpen >= 1 && rhoTot > 0
222 UNclass(i,:) = rho / rhoTot; % station total capped to 1.0
223 % Saturated station: QLen/RespT Inf for open classes.
224 % see _kb/06-solver-catalog.md for rationale
226 RNclass(i,openCls) = Inf;
229 QNclass(i,openCls) = Inf;
235 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')
240function outData = filterMetric(handle, metric, zeroMask, sn, K, M)
241% post-process Avg measure
242outData = zeros(M, K);
245 if ~handle{i,k}.disabled && ~isempty(metric)
246 outData(i,k) = metric(i,k);
253% NaN values indicate that a metric
is disabled
254outData(isnan(outData)) = 0;
255% set to zero entries associated to immediate transitions
256outData(zeroMask) = 0;
257% round to zero numerical perturbations
258outData(outData < GlobalConstants.FineTol) = 0;
260% Zero metrics
for unreachable classes, but trust the simulation
for
261% fork-join, cache, spawn-fed and SPN models where the routing-based visit
262% equations
do not apply. see _kb/06-solver-catalog.md
for rationale
263hasForkJoin = any(sn.nodetype == NodeType.Fork) && any(sn.nodetype == NodeType.Join);
264hasSPN = any(sn.nodetype == NodeType.Place) || any(sn.nodetype == NodeType.Transition);
265hasCache = any(sn.nodetype == NodeType.Cache);
267spawnFedChain =
false(sn.nchains, 1);
268if isfield(sn,
'classspawn') && ~isempty(sn.classspawn)
269 for k2 = 1:min(K, length(sn.classspawn))
270 sc2 = sn.classspawn(k2);
271 if sc2 >= 0 && sc2 < K
272 spawnFedChain(sn.chains(:, sc2+1) > 0) = true;
277if sn.nchains > 0 && ~hasSPN % Only check reachability if chains are defined and not SPN
279 c = sn.chains(:, k)>0;
280 if any(c) % Only check if class k belongs to a chain
283 % For fork-join models, don
't zero out if the metric has a non-zero value
284 % from simulation - the visits calculation doesn't capture fork-join semantics
285 % where Join outputs the parent
class
286 if (hasForkJoin || hasCache || any(spawnFedChain(c))) && ~isempty(metric) && metric(i,k) > GlobalConstants.FineTol
287 continue; % Trust the simulation result