LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
getAvg.m
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)
3%
4% Compute steady-state average metrics (queue length, utilization, response time,
5% throughput, arrival rate, residence time) for all stations and job classes.
6%
7% Copyright (c) 2012-2026, Imperial College London
8% All rights reserved.
9
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);
16 return
17end
18
19sn = self.model.getStruct();
20
21if strcmp(self.options.lang,'java') && ~strcmp(self.name,'SolverLDES')
22 T0=tic;
23 M = sn.nstations;
24 R = sn.nclasses;
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()
28 self.model.obj = [];
29 self.setLang();
30 end
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));
39 end
40 SolverResult = self.obj.getAvg();
41 if isMVA && self.model.hasFork
42 self.fjForkLambda = JLINE.from_jline_matrix(self.obj.getForkWarmStart());
43 end
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
53 for ind = 1:sn.nnodes
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);
64 for k = 1:nk
65 if hitClass(k) > 0 && k <= length(hitRatioVec)
66 hitprob(k) = hitRatioVec(k);
67 missprob(k) = missRatioVec(k);
68 end
69 end
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()));
79 end
80 end
81 if any(sn.nodetype == NodeType.Cache)
82 self.model.refreshStruct(true);
83 end
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,:);
109 end
110 end
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);
117 return
118end
119
120%%
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
127 end
128 [Q,U,R,T,A,W] = self.getAvgHandles;
129elseif nargin == 2
130 handlers = Q;
131 [Q,U,R,T,A,W] = deal(handlers{:}); % set Q=handlers{1}, U=handlers{2}, ...
132end
133
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.');
137 end
138else
139 self.options.timespan = [0,Inf];
140end
141
142if ~self.hasAvgResults() || ~self.options.cache
143 runAnalyzer(self);
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.');
149 end
150end % else return cached value
151
152
153M = sn.nstations;
154K = sn.nclasses;
155
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);
158
159if ~isempty(R)
160 RNclass = filterMetric(R, self.result.Avg.R, [], sn, K, M);
161else
162 RNclass = [];
163end
164
165if ~isempty(Q)
166 % For SPNs, don't zero Q based on R because Places don't have response times
167 if hasSPN
168 zeroMaskQ = [];
169 else
170 zeroMaskQ = RNclass < 10 * GlobalConstants.FineTol;
171 end
172 QNclass = filterMetric(Q, self.result.Avg.Q, zeroMaskQ, sn, K, M);
173else
174 QNclass = [];
175end
176
177if ~isempty(U)
178 % For SPNs, don't zero U based on R because Places don't have response times
179 if hasSPN
180 zeroMaskU = [];
181 else
182 zeroMaskU = RNclass < 10 * GlobalConstants.FineTol;
183 end
184 UNclass = filterMetric(U, self.result.Avg.U, zeroMaskU, sn, K, M);
185else
186 UNclass = [];
187end
188
189if ~isempty(T)
190 TNclass = filterMetric(T, self.result.Avg.T, [], sn, K, M);
191else
192 TNclass = [];
193end
194
195if ~isempty(A)
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);
199else
200 ANclass = [];
201end
202
203if ~isempty(W)
204 WNclass = sn_get_residt_from_respt(sn, RNclass, W);
205else
206 WNclass = [];
207end
208
209if ~isempty(UNclass)
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.
217 anyUnstable = false;
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)
222 c = sn.nservers(i);
223 if ~isfinite(c) || c <= 0 || any(srcStations==i)
224 continue % infinite-server / delay / source station
225 end
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));
230 end
231 end
232 rhoOpen = sum(rho(openCls));
233 rhoTot = sum(rho);
234 if rhoOpen >= 1 && rhoTot > 0
235 anyUnstable = true;
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.
242 if ~isempty(RNclass)
243 RNclass(i,openCls) = Inf;
244 end
245 if ~isempty(QNclass)
246 QNclass(i,openCls) = Inf;
247 end
248 end
249 end
250 end
251 if anyUnstable
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')
253 end
254end
255end
256
257function outData = filterMetric(handle, metric, zeroMask, sn, K, M)
258% post-process Avg measure
259outData = zeros(M, K);
260for k = 1:K
261 for i = 1:M
262 if ~handle{i,k}.disabled && ~isempty(metric)
263 outData(i,k) = metric(i,k);
264 else
265 outData(i,k) = NaN;
266 end
267 end
268end
269
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;
276
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);
284
285if sn.nchains > 0 && ~hasSPN % Only check reachability if chains are defined and not SPN
286 for k = 1:K
287 c = sn.chains(:, k)>0;
288 if any(c) % Only check if class k belongs to a chain
289 for i = 1:M
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
296 end
297 outData(i,k) = 0;
298 end
299 end
300 end
301 end
302end
303end
Definition fjtag.m:157
Definition Station.m:245