LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_ctmc_analyzer.m
1function [QN,UN,RN,TN,CN,XN,InfGen,StateSpace,StateSpaceAggr,EventFiltration,runtime,fname,sncopy] = solver_ctmc_analyzer(sn, options)
2% [QN,UN,RN,TN,CN,XN,INFGEN,STATESPACE,STATESPACEAGGR,EVENTFILTRATION,RUNTIME,FNAME,sn] = SOLVER_CTMC_ANALYZER(sn, OPTIONS)
3%
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6
7%if options.remote
8% sn.rtfun = {};
9% sn.lst = {};
10% qn_json = jsonencode(sn);
11% sn = NetworkStruct.fromJSON(qn_json)
12%return
13%end
14
15M = sn.nstations; %number of stations
16K = sn.nclasses; %number of classes
17S = sn.nservers;
18NK = sn.njobs'; % initial population per class
19sched = sn.sched;
20
21Tstart = tic;
22PH = sn.proc;
23
24line_debug('CTMC analyzer starting: nstations=%d, nclasses=%d, njobs=%s', M, K, mat2str(NK));
25
26% Note: hide_immediate now selectively preserves Cache immediate transitions
27% in solver_ctmc.m, so we no longer need to disable it entirely for Cache nodes
28
29line_debug('Building state space and infinitesimal generator via solver_ctmc');
30[InfGen,StateSpace,StateSpaceAggr,EventFiltration,arvRates,depRates,sn] = solver_ctmc(sn, options); % sn is updated with the state space
31
32% if the initial state does not reflect the final size of the state
33% vectors, attempt to correct it
34for isf=1:sn.nstateful
35 if size(sn.state{isf},2) < size(sn.space{isf},2)
36 sn.state{isf} = [zeros(1,size(sn.space{isf},2)-size(sn.state{isf},2)),sn.state{isf}];
37 end
38end
39sncopy = sn;
40
41if options.keep
42 line_debug('Saving CTMC data to file (options.keep=true)');
43 fname = lineTempName;
44 save([fname,'.mat'],'InfGen','StateSpace','StateSpaceAggr','EventFiltration')
45 line_printf('CTMC infinitesimal generator and state space saved in: ');
46 line_printf(strrep(sprintf('%s.mat\n',fname),'\','\\'))
47else
48 fname = '';
49end
50
51wset = 1:length(InfGen);
52
53line_debug('State space built: %d states, solving CTMC', length(InfGen));
54
55use_ctmc_solve_stable = true;
56if use_ctmc_solve_stable
57 % stable version
58 % Note: solver_ctmc now selectively preserves Cache immediate transitions
59 % to enable hit/miss rate computation while hiding other immediate transitions
60 [probSysState, ~, nConnComp, connComp] = ctmc_solve(InfGen, options);
61
62 if nConnComp > 1
63 line_debug('CTMC is reducible: %d connected components', nConnComp);
64 % the matrix was reducible
65 initState = matchrow(StateSpace, cell2mat(sn.state'));
66 if initState <= 0
67 % Initial state may have been removed by stochcomp (e.g., SPN with
68 % immediate ENABLE states). Use the largest connected component.
69 compSizes = accumarray(connComp(:), 1);
70 [~, largestComp] = max(compSizes);
71 wset = find(connComp == largestComp);
72 else
73 % determine the weakly connected component associated to the initial state
74 wset = find(connComp == connComp(initState));
75 end
76 if initState > 0
77 line_debug('Using component %d with %d states (from initial state)', connComp(initState), length(wset));
78 else
79 line_debug('Using largest component with %d states (initial state removed by stochcomp)', length(wset));
80 end
81 probSysState = ctmc_solve(InfGen(wset, wset), options);
82 InfGen = InfGen(wset, wset);
83 % reduce all per-state arrays to the retained component and remap wset
84 % to local indices so matrix-form and loop-form estimators stay aligned
85 StateSpace = StateSpace(wset,:);
86 StateSpaceAggr = StateSpaceAggr(wset,:);
87 arvRates = arvRates(wset,:,:);
88 depRates = depRates(wset,:,:);
89 wset = 1:numel(wset);
90 else
91 line_debug('CTMC is irreducible, using full state space');
92 end
93else
94 % development version
95
96 % we now find the initial state and then solver the CTMC allowing for the
97 % case where it is reducible
98 initState = matchrow(StateSpace, cell2mat(sn.state'));
99 pi0 = zeros(1,length(InfGen)); pi0(initState) = 1.0;
100 [pi,pis,~,scc,~] = ctmc_solve_reducible(InfGen, pi0, options);
101
102 if size(pis,1)==1
103 probSysState = pi;
104 else
105 wset = scc == scc(initState);
106 InfGen = InfGen(wset, wset);
107 StateSpace = StateSpace(wset,:);
108 probSysState = pis(scc(initState),scc == scc(initState));
109 end
110end
111probSysState(probSysState<GlobalConstants.Zero)=0;
112probSysState = probSysState/sum(probSysState);
113
114XN = NaN*zeros(1,K);
115UN = NaN*zeros(M,K);
116QN = NaN*zeros(M,K);
117RN = NaN*zeros(M,K);
118TN = NaN*zeros(M,K);
119CN = NaN*zeros(1,K);
120
121istSpaceShift = zeros(1,M);
122for ist=1:M
123 if ist==1
124 istSpaceShift(ist) = 0;
125 else
126 istSpaceShift(ist) = istSpaceShift(ist-1) + size(sn.space{ist-1},2);
127 end
128end
129
130for k=1:K
131 refsf = sn.stationToStateful(sn.refstat(k));
132 XN(k) = probSysState*arvRates(wset,refsf,k);
133end
134
135% see _kb/06-solver-catalog.md (G-network signals) for rationale
136inDropRegion = false(1,M);
137if isfield(sn,'nregions') && sn.nregions > 0
138 for f=1:sn.nregions
139 if sn.regionrule(f) == DropStrategy.DROP
140 memb = any(sn.region{f} ~= -1, 2); % stations constrained by region f
141 memb = memb(:)';
142 inDropRegion(1:min(M,numel(memb))) = inDropRegion(1:min(M,numel(memb))) | memb(1:min(M,numel(memb)));
143 end
144 end
145end
146
147for ist=1:M
148 isf = sn.stationToStateful(ist);
149 ind = sn.stationToNode(ist);
150 for k=1:K
151 TN(ist,k) = probSysState*depRates(wset,isf,k);
152 QN(ist,k) = probSysState*StateSpaceAggr(wset,(ist-1)*K+k);
153 end
154 if sn.nodetype(ind) ~= NodeType.Source
155 % see _kb/06-solver-catalog.md (G-network signals) for rationale
156 canDropClass = isinf(sn.njobs(:)') & (isfinite(sn.cap(ist)) | isfinite(sn.classcap(ist,:)) | inDropRegion(ist));
157 signalLoss = ctmc_signal_lossy(sn, arvRates, probSysState, wset, isf);
158 canDropClass = canDropClass | signalLoss;
159 switch sched(ist)
160 case SchedStrategy.INF
161 for k=1:K
162 UN(ist,k) = QN(ist,k);
163 end
164 case {SchedStrategy.PS, SchedStrategy.DPS, SchedStrategy.GPS, SchedStrategy.LPS}
165 if isempty(sn.lldscaling) && isempty(sn.cdscaling) && isempty(sn.jdscaling)
166 for k=1:K
167 if ~isempty(PH{ist}{k})
168 % see _kb/06-solver-catalog.md (Utilization conventions) for rationale
169 UNdep_ik = TN(ist,k)*map_mean(PH{ist}{k})/S(ist); % this is valid because CS in LINE is in a separate node
170 if canDropClass(k)
171 UN(ist,k) = UNdep_ik;
172 else
173 UNarv_ik = probSysState*arvRates(wset,isf,k)*map_mean(PH{ist}{k})/S(ist);
174 UN(ist,k) = max(UNarv_ik,UNdep_ik);
175 end
176 end
177 end
178 else % lld/cd/ljd cases
179 % see _kb/06-solver-catalog.md (Utilization conventions) for rationale
180 ind = sn.stationToNode(ist);
181 ceff = S(ist);
182 if ~isempty(sn.lldscaling) && ist <= size(sn.lldscaling,1)
183 ceff = max(ceff, max(sn.lldscaling(ist,:)));
184 end
185 UN(ist,1:K) = 0;
186 for st = wset
187 [ni,nir] = State.toMarginal(sn, ind, StateSpace(st,(istSpaceShift(ist)+1):(istSpaceShift(ist)+size(sn.space{ist},2))));
188 if ni>0
189 lldnow = 1;
190 if ~isempty(sn.lldscaling) && ist <= size(sn.lldscaling,1)
191 lldnow = sn.lldscaling(ist, min(max(sum(ni),1), size(sn.lldscaling,2)));
192 end
193 for k=1:K
194 UN(ist,k) = UN(ist,k) + probSysState(st)*nir(k)*sn.schedparam(ist,k)/(nir*sn.schedparam(ist,:)')*lldnow/ceff;
195 end
196 end
197 end
198 end
199 case SchedStrategy.PAS
200 % see _kb/06-solver-catalog.md (Utilization conventions) for rationale
201 ind = sn.stationToNode(ist);
202 UN(ist,1:K) = 0;
203 for st = wset
204 [~,~,sir] = State.toMarginal(sn, ind, StateSpace(st,(istSpaceShift(ist)+1):(istSpaceShift(ist)+size(sn.space{ist},2))));
205 for k=1:K
206 UN(ist,k) = UN(ist,k) + probSysState(st)*sir(k)/S(ist);
207 end
208 end
209 otherwise
210 if isempty(sn.lldscaling) && isempty(sn.cdscaling) && isempty(sn.jdscaling)
211 for k=1:K
212 if ~isempty(PH{ist}{k})
213 % see _kb/06-solver-catalog.md (Utilization conventions) for rationale
214 UNdep_ik = TN(ist,k)*map_mean(PH{ist}{k})/S(ist); % this is valid because CS in LINE is in a separate node
215 if canDropClass(k)
216 UN(ist,k) = UNdep_ik;
217 else
218 UNarv_ik = probSysState*arvRates(wset,isf,k)*map_mean(PH{ist}{k})/S(ist);
219 UN(ist,k) = max(UNarv_ik,UNdep_ik);
220 end
221 end
222 end
223 else % lld/cd/ljd cases
224 % see _kb/06-solver-catalog.md (Utilization conventions) for rationale
225 ind = sn.stationToNode(ist);
226 ceff = S(ist);
227 if ~isempty(sn.lldscaling) && ist <= size(sn.lldscaling,1)
228 ceff = max(ceff, max(sn.lldscaling(ist,:)));
229 end
230 UN(ist,1:K) = 0;
231 for st = wset
232 [ni,~,sir] = State.toMarginal(sn, ind, StateSpace(st,(istSpaceShift(ist)+1):(istSpaceShift(ist)+size(sn.space{ist},2))));
233 if ni>0
234 lldnow = 1;
235 if ~isempty(sn.lldscaling) && ist <= size(sn.lldscaling,1)
236 lldnow = sn.lldscaling(ist, min(max(sum(ni),1), size(sn.lldscaling,2)));
237 end
238 sirtot = sum(sir);
239 for k=1:K
240 if sirtot > 0
241 UN(ist,k) = UN(ist,k) + probSysState(st)*(sir(k)/sirtot)*lldnow/ceff;
242 end
243 end
244 end
245 end
246 end
247 end
248 % see _kb/06-solver-catalog.md (G-network signals) for rationale
249 if any(signalLoss) && sched(ist) ~= SchedStrategy.INF && ...
250 isempty(sn.lldscaling) && isempty(sn.cdscaling) && isempty(sn.jdscaling)
251 UNb = ctmc_signal_busy(sn, ind, ist, sched(ist), S(ist), StateSpace, istSpaceShift, wset, probSysState);
252 UN(ist,signalLoss) = UNb(signalLoss);
253 end
254 end
255end
256
257% see _kb/06-solver-catalog.md (Utilization conventions) for rationale
258if ~isempty(sn.cdscaling) && ~any(isinf(sn.njobs))
259 for ist=1:M
260 if length(sn.cdscaling) >= ist && ~isempty(sn.cdscaling{ist})
261 for k=1:K
262 bmax = sn.cdscalingpeak(ist,k);
263 if isfinite(sn.rates(ist,k)) && sn.rates(ist,k) > 0 && bmax > 0
264 UN(ist,k) = TN(ist,k) / sn.rates(ist,k) / bmax;
265 else
266 UN(ist,k) = 0;
267 end
268 end
269 end
270 end
271end
272
273% Joint-dependence (non-product-form) utilization normalization: Util=T*S/peak
274% using the declared sn.jdscalingpeak, mirroring the class-dependence block.
275if ~isempty(sn.jdscaling) && ~any(isinf(sn.njobs))
276 for ist=1:M
277 if length(sn.jdscaling) >= ist && ~isempty(sn.jdscaling{ist})
278 for k=1:K
279 bmax = sn.jdscalingpeak(ist,k);
280 if isfinite(sn.rates(ist,k)) && sn.rates(ist,k) > 0 && bmax > 0
281 UN(ist,k) = TN(ist,k) / sn.rates(ist,k) / bmax;
282 else
283 UN(ist,k) = 0;
284 end
285 end
286 end
287 end
288end
289
290
291% see _kb/06-solver-catalog.md (True BAS blocking) for rationale
292if ~isempty(sn.isbasblocking)
293 for ist=1:M
294 ind = sn.stationToNode(ist);
295 % see _kb/06-solver-catalog.md (True BAS blocking) for rationale
296 if numel(sn.isbasblocking) < ind || sn.isbasblocking(ind) ~= 1
297 continue % no blocked marker at this station
298 end
299 % sn.isstation spans nnodes physical nodes plus one virtual entry per
300 % finite capacity region; restrict to physical nodes to conform to connmatrix
301 isstationNode = sn.isstation(1:sn.nnodes);
302 dests = find(sn.connmatrix(ind,:) == 1 & isstationNode(:)' == 1);
303 if numel(dests) ~= 1
304 continue % ambiguous destination: leave the job where it sits
305 end
306 jst = sn.nodeToStation(dests(1));
307 if isnan(jst) || jst < 1
308 continue
309 end
310 cols = (istSpaceShift(ist)+1):(istSpaceShift(ist)+size(sn.space{ist},2));
311 blocked = StateSpace(wset, cols(end)) == 1; % marker is the trailing column
312 for k=1:K
313 % Only the held job itself moves, not the whole queue at ist: a blocked
314 % state holds exactly one completed job, so cap the per-state count at 1.
315 shift = probSysState(blocked) * min(StateSpaceAggr(wset(blocked),(ist-1)*K+k), 1);
316 if shift > 0
317 QN(ist,k) = QN(ist,k) - shift;
318 QN(jst,k) = QN(jst,k) + shift;
319 end
320 end
321 end
322end
323
324for k=1:K
325 for ist=1:M
326 if TN(ist,k)>0
327 RN(ist,k) = QN(ist,k)./TN(ist,k);
328 else
329 RN(ist,k)=0;
330 end
331 end
332 CN(k) = NK(k)./XN(k);
333end
334
335QN(isnan(QN))=0;
336CN(isnan(CN))=0;
337RN(isnan(RN))=0;
338UN(isnan(UN))=0;
339XN(isnan(XN))=0;
340TN(isnan(TN))=0;
341
342runtime = toc(Tstart);
343
344% now update the routing probabilities in nodes with state-dependent routing
345TNcache = zeros(sn.nstateful,K);
346XNcache = zeros(sn.nstateful,K);
347for k=1:K
348 for isf=1:sn.nstateful
349 ind = sncopy.statefulToNode(isf);
350 if sncopy.nodetype(ind) == NodeType.Cache
351 TNcache(isf,k) = probSysState*depRates(wset,isf,k);
352 XNcache(isf,k) = probSysState*arvRates(wset,isf,k);
353 end
354 end
355end
356
357% updates cache actual hit and miss data + retrieval-system expected latency.
358% Hit / miss for class k are derived from departure rates of the configured
359% hitClass / missClass at the cache; for retrieval-aware caches the
360% miss-class departure rate equals the true miss rate because
361% afterEventCache fires the miss event on a retrieval-complete READ.
362retrievalLatencyWarned = false;
363for k=1:K
364 for isf=1:sncopy.nstateful
365 ind = sncopy.statefulToNode(isf);
366 if sncopy.nodetype(ind) == NodeType.Cache
367 np = sncopy.nodeparam{ind};
368 if length(np.hitclass)>=k
369 h = np.hitclass(k);
370 m = np.missclass(k);
371 if h>0 && m>0
372 sncopy.nodeparam{ind}.actualhitprob(k) = TNcache(isf,h)/sum(TNcache(isf,[h,m]));
373 sncopy.nodeparam{ind}.actualmissprob(k) = TNcache(isf,m)/sum(TNcache(isf,[h,m]));
374 else
375 sncopy.nodeparam{ind}.actualhitprob(k) = NaN;
376 sncopy.nodeparam{ind}.actualmissprob(k) = NaN;
377 end
378
379 % The Eq. 8 retrieval-system expected latency is not currently
380 % implemented; report NaN whenever a retrieval system is
381 % configured for this class.
382 expectedLatency = NaN;
383 if isfield(np, 'retrievalSystemQueueIndices') ...
384 && isKey(np.retrievalSystemQueueIndices, int32(k-1)) ...
385 && ~isempty(np.retrievalSystemQueueIndices(int32(k-1)))
386 if ~retrievalLatencyWarned
387 line_warning(mfilename, 'Retrieval-system expected latency is not currently implemented; reporting NaN.');
388 retrievalLatencyWarned = true;
389 end
390 end
391 sncopy.nodeparam{ind}.actualresidt(k) = expectedLatency;
392 end
393 end
394 end
395end
396end
Definition fjtag.m:161