LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
getRoutingMatrix.m
1function [rt,rtnodes,conn,chains,rtNodesByClass,rtNodesByStation] = getRoutingMatrix(self, arvRates)
2% [RT,RTNODES,CONNMATRIX,CHAINS,RTNODESBYCLASS,RTNODESBYSTATION] = GETROUTINGMATRIX(ARVRATES)
3
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6
7if self.hasStruct
8 sn = self.sn;
9 idxSource = find(sn.nodetype == NodeType.Source);
10 idxSink = find(sn.nodetype == NodeType.Sink);
11 indexOpenClasses = find(isinf(sn.njobs));
12 hasOpen = ~isempty(indexOpenClasses);
13 if nargin<2
14 arvRates = sn.rates(sn.nodeToStation(idxSource),:);
15 end
16 nodes = self.nodes;
17 conn = sn.connmatrix;
18 I = sn.nnodes;
19 K = sn.nclasses;
20 NK = sn.njobs;
21 statefulNodes = find(sn.isstateful)';
22else
23 sn = self.sn;
24 idxSource = self.getIndexSourceNode;
25 idxSink = self.getIndexSinkNode;
26 indexOpenClasses = self.getIndexOpenClasses;
27 nodes = self.nodes;
28
29 conn = self.getConnectionMatrix;
30 sn.connmatrix = conn;
31 hasOpen = hasOpenClasses(self);
32
33 I = self.getNumberOfNodes;
34 K = self.getNumberOfClasses;
35 NK = self.getNumberOfJobs;
36 for ind=1:I
37 for k=1:K
38 if isempty(self.nodes{ind}.output.outputStrategy{k})
39 sn.routing(ind,k) = RoutingStrategy.DISABLED;
40 else
41 sn.routing(ind,k) = RoutingStrategy.fromText(nodes{ind}.output.outputStrategy{k}{2});
42 end
43 end
44 end
45 statefulNodes = self.getIndexStatefulNodes;
46end
47
48rtnodes = zeros(I*K);
49rtNodesByClass = {};
50rtNodesByStation = {};
51% The first loop considers the class at which a job enters the
52% target station
53for ind=1:I
54 node_i = nodes{ind};
55 outputStrategy = node_i.output.outputStrategy;
56 switch class(node_i.output)
57 case 'Forker'
58 % Fork nodes send to ALL branches with probability 1.0 each
59 % (not 1.0/fanout). A Fork with arrival rate lambda sends
60 % lambda to EACH outgoing branch, not lambda/fanout.
61 for k=1:K
62 outputStrategy_k = outputStrategy{k};
63 switch sn.routing(ind,k)
64 case RoutingStrategy.PROB
65 % Use output strategy destinations (includes ClassSwitch nodes)
66 if ~isempty(outputStrategy_k) && length(outputStrategy_k) >= 3 && ~isempty(outputStrategy_k{end})
67 for t=1:length(outputStrategy_k{end})
68 jnd = outputStrategy_k{end}{t}{1}.index;
69 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = 1.0;
70 end
71 else
72 % Fallback: uniform to all connected nodes
73 for jnd=1:I
74 if conn(ind,jnd)>0
75 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = 1.0;
76 end
77 end
78 end
79 otherwise
80 % Non-PROB routing: route to all connected nodes
81 for jnd=1:I
82 if conn(ind,jnd)>0
83 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = 1.0;
84 end
85 end
86 end
87 end
88 otherwise
89 isSink_i = isa(node_i,'Sink');
90 for k=1:K
91 outputStrategy_k = outputStrategy{k};
92 switch sn.routing(ind,k)
93 case RoutingStrategy.PROB
94 if isinf(NK(k)) || ~isSink_i
95 for t=1:length(outputStrategy_k{end}) % for all outgoing links
96 jnd = outputStrategy_k{end}{t}{1}.index;
97 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = outputStrategy_k{end}{t}{2};
98 end
99 end
100 case RoutingStrategy.DISABLED
101 for jnd=1:I
102 if conn(ind,jnd)>0
103 if isa(self.classes{k},'SelfLoopingClass')
104 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = 0;
105 elseif isa(self.classes{k},'Signal') || isa(self.classes{k},'OpenSignal') || isa(self.classes{k},'ClosedSignal')
106 % Signal classes with DISABLED routing should not route
107 % (their routing is set explicitly via the P matrix)
108 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = 0;
109 else
110 % we set this to be non-zero as otherwise the
111 % classes that do not visit a classswitch are
112 % misconfigured in JMT
113 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = 1/sum(conn(ind,:));
114 end
115 end
116 end
117 case {RoutingStrategy.RAND, RoutingStrategy.RROBIN, RoutingStrategy.JSQ, RoutingStrategy.KCHOICES}
118 if isinf(NK(k)) % open class
119 for jnd=1:I
120 if conn(ind,jnd)>0
121 rtnodes((ind-1)*K+k,(jnd-1)*K+k)=1/sum(conn(ind,:));
122 end
123 end
124 elseif ~isa(node_i,'Source') && ~isSink_i % don't route closed classes out of source nodes
125 connectionsClosed = conn;
126 if connectionsClosed(ind,idxSink)
127 connectionsClosed(ind,idxSink) = 0;
128 end
129 for jnd=1:I
130 if connectionsClosed(ind,jnd)>0
131 rtnodes((ind-1)*K+k,(jnd-1)*K+k)=1/(sum(connectionsClosed(ind,:)));
132 end
133 end
134 end
135 case RoutingStrategy.WRROBIN
136 % Weighted round-robin: compute routing probabilities from weights
137 % Weights are stored in outputStrategy_k{3} as {destination, weight} pairs
138 if ~isempty(outputStrategy_k) && length(outputStrategy_k) >= 3 && ~isempty(outputStrategy_k{3})
139 % Compute total weight (excluding sink for closed classes)
140 totalWeight = 0;
141 for t=1:length(outputStrategy_k{3})
142 dest = outputStrategy_k{3}{t}{1};
143 destIdx = dest.index;
144 weight = outputStrategy_k{3}{t}{2};
145 % For closed classes, exclude sink
146 if ~isinf(NK(k)) && ~isempty(idxSink) && destIdx == idxSink
147 continue;
148 end
149 totalWeight = totalWeight + weight;
150 end
151 % Set routing probabilities based on weights
152 if totalWeight > 0
153 for t=1:length(outputStrategy_k{3})
154 dest = outputStrategy_k{3}{t}{1};
155 destIdx = dest.index;
156 weight = outputStrategy_k{3}{t}{2};
157 % For closed classes, exclude sink
158 if ~isinf(NK(k)) && ~isempty(idxSink) && destIdx == idxSink
159 continue;
160 end
161 rtnodes((ind-1)*K+k,(destIdx-1)*K+k) = weight / totalWeight;
162 end
163 else
164 % Fallback to uniform if no weights sum to positive
165 if isinf(NK(k))
166 for jnd=1:I
167 if conn(ind,jnd)>0
168 rtnodes((ind-1)*K+k,(jnd-1)*K+k)=1/sum(conn(ind,:));
169 end
170 end
171 elseif ~isa(node_i,'Source') && ~isSink_i
172 connectionsClosed = conn;
173 if ~isempty(idxSink) && connectionsClosed(ind,idxSink)
174 connectionsClosed(ind,idxSink) = 0;
175 end
176 for jnd=1:I
177 if connectionsClosed(ind,jnd)>0
178 rtnodes((ind-1)*K+k,(jnd-1)*K+k)=1/(sum(connectionsClosed(ind,:)));
179 end
180 end
181 end
182 end
183 else
184 % Fallback to uniform if no weight data available
185 if isinf(NK(k))
186 for jnd=1:I
187 if conn(ind,jnd)>0
188 rtnodes((ind-1)*K+k,(jnd-1)*K+k)=1/sum(conn(ind,:));
189 end
190 end
191 elseif ~isa(node_i,'Source') && ~isSink_i
192 connectionsClosed = conn;
193 if ~isempty(idxSink) && connectionsClosed(ind,idxSink)
194 connectionsClosed(ind,idxSink) = 0;
195 end
196 for jnd=1:I
197 if connectionsClosed(ind,jnd)>0
198 rtnodes((ind-1)*K+k,(jnd-1)*K+k)=1/(sum(connectionsClosed(ind,:)));
199 end
200 end
201 end
202 end
203 otherwise
204 for jnd=1:I
205 if conn(ind,jnd)>0
206 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = GlobalConstants.FineTol;
207 end
208 end
209 %line_error(mfilename,[outputStrategy_k{2},' routing policy is not yet supported.']);
210 end
211 end
212 end
213end
214
215% The second loop corrects the first one at nodes that change
216% the class of the job in the service section.
217for ind=1:I % source
218 if isa(nodes{ind}.server,'StatelessClassSwitcher')
219 Pi = rtnodes(((ind-1)*K+1):ind*K,:);
220 Pcs = nodes{ind}.server.csFun(1:K,1:K);
221 rtnodes(((ind-1)*K+1):ind*K,:) = 0;
222 for jnd=1:I % destination
223 Pij = Pi(:,((jnd-1)*K+1):jnd*K); %Pij(r,s)
224 % Find the routing probability section determined by the router section in the first loop
225 rtnodes(((ind-1)*K+1) : ((ind-1)*K+K),(jnd-1)*K+(1:K)) = Pcs.*repmat(diag(Pij)',K,1);
226 end
227 elseif isa(nodes{ind}.server,'StatefulClassSwitcher')
228 Pi = rtnodes(((ind-1)*K+1):ind*K,:);
229 for r=1:K
230 for s=1:K
231 Pcs(r,s) = nodes{ind}.server.csFun(r,s,[],[]); % get csmask
232 end
233 end
234 rtnodes(((ind-1)*K+1):ind*K,:) = 0;
235 if isa(nodes{ind}.server,'CacheClassSwitcher')
236 % A retrieval-pending class is an output class of the cache (like
237 % hitClass / missClass): its routing must be taken from the routing
238 % matrix directly rather than normalised/zeroed as a regular input
239 % class. isReceived(r) is true when r appears as a retrieval class;
240 % it is identically false for caches with no retrieval system.
241 if isprop(nodes{ind}.server, 'retrievalClasses')
242 rpClasses = nodes{ind}.server.retrievalClasses;
243 else
244 rpClasses = [];
245 end
246 isReceived = @(r) ~isempty(rpClasses) && any(rpClasses(:) == r);
247
248 for r=1:K
249 if (isempty(find(r == nodes{ind}.server.hitClass)) && isempty(find(r == nodes{ind}.server.missClass)) && ~isReceived(r))
250 Pcs(r,:) = Pcs(r,:)/sum(Pcs(r,:));
251 end
252 end
253
254 for r=1:K
255 if (isempty(find(r == nodes{ind}.server.hitClass)) && isempty(find(r == nodes{ind}.server.missClass)) && ~isReceived(r))
256 for jnd=1:I % destination
257 for s=1:K
258 Pi((ind-1)*K+r,(jnd-1)*K+s) = 0;
259 end
260 end
261 end
262 end
263
264 for r=1:K
265 if length(nodes{ind}.server.actualHitProb)>=r && length(nodes{ind}.server.hitClass)>=r
266 ph = nodes{ind}.server.actualHitProb(r);
267 pm = nodes{ind}.server.actualMissProb(r);
268 % Delayed hits (retrieval system) eventually depart as the
269 % hit class, so for flow conservation the routing split must
270 % send (true hit + delayed hit) to the hit class; otherwise
271 % ph+pm < 1 deflates the cache flow balance.
272 if isprop(nodes{ind}.server, 'actualDelayedHitProb') ...
273 && length(nodes{ind}.server.actualDelayedHitProb)>=r ...
274 && ~isnan(nodes{ind}.server.actualDelayedHitProb(r))
275 ph = ph + nodes{ind}.server.actualDelayedHitProb(r);
276 end
277 h = nodes{ind}.server.hitClass(r);
278 m = nodes{ind}.server.missClass(r);
279 rtnodes((ind-1)*K+r,(ind-1)*K+h) = ph;
280 rtnodes((ind-1)*K+r,(ind-1)*K+m) = pm;
281 else
282 if length(nodes{ind}.server.hitClass)>=r
283 h = nodes{ind}.server.hitClass(r);
284 m = nodes{ind}.server.missClass(r);
285 if h > 0 && m > 0 % Only set NaN if hitClass/missClass are defined for this class
286 rtnodes((ind-1)*K+r,(ind-1)*K+h) = NaN;
287 rtnodes((ind-1)*K+r,(ind-1)*K+m) = NaN;
288 end
289 end
290 end
291 end
292 for jnd=1:I % destination
293 Pij = Pi(1:K,((jnd-1)*K+1):jnd*K); %Pij(r,s)
294 for r=1:K
295 if ~(isempty(find(r == nodes{ind}.server.hitClass)) && isempty(find(r == nodes{ind}.server.missClass)) && ~isReceived(r))
296 for s=1:K
297 % Find the routing probability section determined by the router section in the first loop
298 %Pnodes(((i-1)*K+1):i*K,((j-1)*K+1):j*K) = Pcs*Pij;
299 rtnodes((ind-1)*K+r,(jnd-1)*K+s) = Pcs(r,s)*Pij(s,s);
300 end
301 end
302 end
303 end
304 end
305 end
306end
307
308% Handle self-looping classes
309for r=1:K
310 if isa(self.classes{r},'SelfLoopingClass')
311 sl_refstat = self.classes{r}.refstat.index;
312 for ind=1:I % source
313 for jnd=1:I % destination
314 for s=1:K
315 rtnodes((ind-1)*K+r,(jnd-1)*K+s) = 0; % zero any prior setting
316 end
317 end
318 end
319 for s=1:K
320 if s==r
321 rtnodes((sl_refstat-1)*K+r,(sl_refstat-1)*K+s) = 1;
322 end
323 end
324 end
325end
326
327% ignore all chains containing a Pnodes column that sums to 0,
328% since these are classes that cannot arrive to the node
329% unless this column belongs to the source
330colsToIgnore = find(sum(rtnodes,1)==0);
331if hasOpen
332 colsToIgnore = setdiff(colsToIgnore,(idxSource-1)*K+(1:K));
333end
334
335% We route back from the sink to the source. Since open classes
336% have an infinite population, if there is a class switch QN
337% with the following chains
338% Source -> (A or B) -> C -> Sink
339% Source -> D -> Sink
340% We can re-route class C into the source either as A or B or C.
341% We here re-route back as C and leave for the chain analyzer
342% to detect that C is in a chain with A and B and change this
343% part.
344
345csmask = self.csMatrix;
346if isempty(csmask) % models not built with link(P)
347 % rtnodes+rtnodes' makes the matrix undirected, it helps grouping
348 % transient states with all the recurrent states their can reach
349 % into a single "chain" of the LINE model
350 [C,WCC]=weaklyconncomp(rtnodes+rtnodes');
351 WCC(colsToIgnore) = 0;
352 chainCandidates = cell(1,C);
353 for r=1:C
354 chainCandidates{r} = find(WCC==r);
355 end
356
357 chains = false(length(chainCandidates),K); % columns are classes, rows are chains
358 tmax = 0;
359 for t=1:length(chainCandidates)
360 if length(chainCandidates{t})>1
361 tmax = tmax+1;
362 chains(tmax,(mod(chainCandidates{t}-1,K)+1))=true;
363 end
364 end
365 chains(tmax+1:end,:)=[];
366else % this is faster since csmask is smaller than rtnodes
367 [C,WCC] = weaklyconncomp(csmask+csmask');
368
369 chainCandidates = cell(1,C);
370 for c=1:C
371 chainCandidates{c} = find(WCC==c);
372 end
373
374 chains = false(length(chainCandidates),K);
375 for t=1:length(chainCandidates)
376 chains(t,chainCandidates{t}) = true;
377 end
378end
379
380chains = unique(chains,'rows');
381try
382 chains = sortrows(chains,'descend');
383catch % old MATLABs
384 chains = sortrows(chains);
385end
386
387% overlapping chains can occur if a class that enters in a Cache
388% reappars downhill in the network in rtnodes in that case we merge
389% the two chains
390splitChains = [];
391for col=find(sum(chains,1)>1)
392 rows = find(chains(:,col));
393 for r=rows(2:end)'
394 chains(rows(1),:) = chains(rows(1),:) | chains(r,:);
395 splitChains(end+1)=r;
396 end
397end
398chains(splitChains,:)=[];
399
400% We now obtain the routing matrix P by ignoring the non-stateful
401% nodes and calculating by the stochastic complement method the
402% correct transition probabilities, that includes the effects
403% of the non-stateful nodes (e.g., ClassSwitch)
404statefulNodesClasses = [];
405for ind=statefulNodes %#ok<FXSET>
406 statefulNodesClasses(end+1:end+K)= ((ind-1)*K+1):(ind*K);
407end
408
409% this routes open classes back from the sink into the source
410% it will not work with non-renewal arrivals as it choses in which open
411% class to reroute a job with probability depending on the arrival rates
412if hasOpen
413 arvRates(isnan(arvRates)) = 0;
414 for s=indexOpenClasses
415 s_chain = find(chains(:,s));
416 others_in_chain = find(chains(s_chain,:)); %#ok<FNDSB>
417 chainSum = sum(arvRates(others_in_chain));
418 if chainSum > 0
419 arvProbs = arvRates(others_in_chain) / chainSum;
420 else
421 % All rates are zero (e.g., all arrivals disabled): use equal probabilities
422 % to avoid NaN in Sink->Source routing
423 arvProbs = ones(size(others_in_chain)) / length(others_in_chain);
424 end
425 rtnodes((idxSink-1)*K+others_in_chain,(idxSource-1)*K+others_in_chain) = repmat(arvProbs,length(others_in_chain),1);
426 end
427end
428
429%% Hide the nodes that are not stateful
430rt = dtmc_stochcomp(rtnodes,statefulNodesClasses);
431
432% verify irreducibility
433% disabled as it casts warning in many models that are seemingly fine
434%try
435% eigen = eig(rt); % exception if rt has NaN or Inf
436% if sum(eigen>=1)>1
437% line_warning(mfilename, 'Solutions may be invalid, the routing matrix is reducible. The path of two or more classes form independent non-interacting models.');
438% end
439%end
440sn.rt = rt;
441sn.chains = chains;
442self.sn = sn;
443
444%% Compute optional outputs
445if nargout >= 5
446 rtNodesByClass = cellzeros(K,K,I,I);
447 for ind=1:I
448 for jnd=1:I
449 for r=1:K
450 for s=1:K
451 rtNodesByClass{s,r}(ind,jnd) = rtnodes((ind-1)*K+s,(jnd-1)*K+r);
452 end
453 end
454 end
455 end
456end
457
458if nargout >= 6
459 rtNodesByStation = cellzeros(I,I,K,K);
460 for ind=1:I %#ok<FXSET>
461 for jnd=1:I
462 for r=1:K
463 for s=1:K
464 rtNodesByStation{ind,jnd}(r,s) = rtnodes((ind-1)*K+s,(jnd-1)*K+r);
465 end
466 end
467 end
468 end
469end
470end
Definition mmt.m:124