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 for k=1:K
59 outputStrategy_k = outputStrategy{k};
60 if length(outputStrategy_k) >= 3 && ~isempty(outputStrategy_k{end})
61 fanout = length(outputStrategy_k{end});
62 else
63 fanout = sum(conn(ind,:));
64 end
65 switch sn.routing(ind,k)
66 case RoutingStrategy.PROB
67 % Use output strategy destinations (includes ClassSwitch nodes)
68 if ~isempty(outputStrategy_k) && length(outputStrategy_k) >= 3 && ~isempty(outputStrategy_k{end})
69 for t=1:length(outputStrategy_k{end})
70 jnd = outputStrategy_k{end}{t}{1}.index;
71 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = 1.0/fanout;
72 end
73 else
74 % Fallback: uniform to all connected nodes
75 for jnd=1:I
76 if conn(ind,jnd)>0
77 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = 1.0/fanout;
78 end
79 end
80 end
81 otherwise
82 % Non-PROB routing: uniform to all connected nodes
83 for jnd=1:I
84 if conn(ind,jnd)>0
85 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = 1.0/fanout;
86 end
87 end
88 end
89 end
90 otherwise
91 isSink_i = isa(node_i,'Sink');
92 for k=1:K
93 outputStrategy_k = outputStrategy{k};
94 switch sn.routing(ind,k)
95 case RoutingStrategy.PROB
96 if isinf(NK(k)) || ~isSink_i
97 for t=1:length(outputStrategy_k{end}) % for all outgoing links
98 jnd = outputStrategy_k{end}{t}{1}.index;
99 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = outputStrategy_k{end}{t}{2};
100 end
101 end
102 case RoutingStrategy.DISABLED
103 for jnd=1:I
104 if conn(ind,jnd)>0
105 if isa(self.classes{k},'SelfLoopingClass')
106 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = 0;
107 elseif isa(self.classes{k},'Signal') || isa(self.classes{k},'OpenSignal') || isa(self.classes{k},'ClosedSignal')
108 % Signal classes with DISABLED routing should not route
109 % (their routing is set explicitly via the P matrix)
110 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = 0;
111 else
112 % we set this to be non-zero as otherwise the
113 % classes that do not visit a classswitch are
114 % misconfigured in JMT
115 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = 1/sum(conn(ind,:));
116 end
117 end
118 end
119 case {RoutingStrategy.RAND, RoutingStrategy.RROBIN, RoutingStrategy.WRROBIN, RoutingStrategy.JSQ}
120 if isinf(NK(k)) % open class
121 for jnd=1:I
122 if conn(ind,jnd)>0
123 rtnodes((ind-1)*K+k,(jnd-1)*K+k)=1/sum(conn(ind,:));
124 end
125 end
126 elseif ~isa(node_i,'Source') && ~isSink_i % don't route closed classes out of source nodes
127 connectionsClosed = conn;
128 if connectionsClosed(ind,idxSink)
129 connectionsClosed(ind,idxSink) = 0;
130 end
131 for jnd=1:I
132 if connectionsClosed(ind,jnd)>0
133 rtnodes((ind-1)*K+k,(jnd-1)*K+k)=1/(sum(connectionsClosed(ind,:)));
134 end
135 end
136 end
137 otherwise
138 for jnd=1:I
139 if conn(ind,jnd)>0
140 rtnodes((ind-1)*K+k,(jnd-1)*K+k) = GlobalConstants.FineTol;
141 end
142 end
143 %line_error(mfilename,[outputStrategy_k{2},' routing policy is not yet supported.']);
144 end
145 end
146 end
147end
148
149% The second loop corrects the first one at nodes that change
150% the class of the job in the service section.
151for ind=1:I % source
152 if isa(nodes{ind}.server,'StatelessClassSwitcher')
153 Pi = rtnodes(((ind-1)*K+1):ind*K,:);
154 Pcs = nodes{ind}.server.csFun(1:K,1:K);
155 rtnodes(((ind-1)*K+1):ind*K,:) = 0;
156 for jnd=1:I % destination
157 Pij = Pi(:,((jnd-1)*K+1):jnd*K); %Pij(r,s)
158 % Find the routing probability section determined by the router section in the first loop
159 rtnodes(((ind-1)*K+1) : ((ind-1)*K+K),(jnd-1)*K+(1:K)) = Pcs.*repmat(diag(Pij)',K,1);
160 end
161 elseif isa(nodes{ind}.server,'StatefulClassSwitcher')
162 Pi = rtnodes(((ind-1)*K+1):ind*K,:);
163 for r=1:K
164 for s=1:K
165 Pcs(r,s) = nodes{ind}.server.csFun(r,s,[],[]); % get csmask
166 end
167 end
168 rtnodes(((ind-1)*K+1):ind*K,:) = 0;
169 if isa(nodes{ind}.server,'CacheClassSwitcher')
170 for r=1:K
171 if (isempty(find(r == nodes{ind}.server.hitClass)) && isempty(find(r == nodes{ind}.server.missClass)))
172 Pcs(r,:) = Pcs(r,:)/sum(Pcs(r,:));
173 end
174 end
175
176 for r=1:K
177 if (isempty(find(r == nodes{ind}.server.hitClass)) && isempty(find(r == nodes{ind}.server.missClass)))
178 for jnd=1:I % destination
179 for s=1:K
180 Pi((ind-1)*K+r,(jnd-1)*K+s) = 0;
181 end
182 end
183 end
184 end
185
186 for r=1:K
187 if length(nodes{ind}.server.actualHitProb)>=r && length(nodes{ind}.server.hitClass)>=r
188 ph = nodes{ind}.server.actualHitProb(r);
189 pm = nodes{ind}.server.actualMissProb(r);
190 h = nodes{ind}.server.hitClass(r);
191 m = nodes{ind}.server.missClass(r);
192 rtnodes((ind-1)*K+r,(ind-1)*K+h) = ph;
193 rtnodes((ind-1)*K+r,(ind-1)*K+m) = pm;
194 else
195 if length(nodes{ind}.server.hitClass)>=r
196 h = nodes{ind}.server.hitClass(r);
197 m = nodes{ind}.server.missClass(r);
198 if h > 0 && m > 0 % Only set NaN if hitClass/missClass are defined for this class
199 rtnodes((ind-1)*K+r,(ind-1)*K+h) = NaN;
200 rtnodes((ind-1)*K+r,(ind-1)*K+m) = NaN;
201 end
202 end
203 end
204 end
205 for jnd=1:I % destination
206 Pij = Pi(1:K,((jnd-1)*K+1):jnd*K); %Pij(r,s)
207 for r=1:K
208 if ~(isempty(find(r == nodes{ind}.server.hitClass)) && isempty(find(r == nodes{ind}.server.missClass)))
209 for s=1:K
210 % Find the routing probability section determined by the router section in the first loop
211 %Pnodes(((i-1)*K+1):i*K,((j-1)*K+1):j*K) = Pcs*Pij;
212 rtnodes((ind-1)*K+r,(jnd-1)*K+s) = Pcs(r,s)*Pij(s,s);
213 end
214 end
215 end
216 end
217 end
218 end
219end
220
221% Handle self-looping classes
222for r=1:K
223 if isa(self.classes{r},'SelfLoopingClass')
224 sl_refstat = self.classes{r}.refstat.index;
225 for ind=1:I % source
226 for jnd=1:I % destination
227 for s=1:K
228 rtnodes((ind-1)*K+r,(jnd-1)*K+s) = 0; % zero any prior setting
229 end
230 end
231 end
232 for s=1:K
233 if s==r
234 rtnodes((sl_refstat-1)*K+r,(sl_refstat-1)*K+s) = 1;
235 end
236 end
237 end
238end
239
240% ignore all chains containing a Pnodes column that sums to 0,
241% since these are classes that cannot arrive to the node
242% unless this column belongs to the source
243colsToIgnore = find(sum(rtnodes,1)==0);
244if hasOpen
245 colsToIgnore = setdiff(colsToIgnore,(idxSource-1)*K+(1:K));
246end
247
248% We route back from the sink to the source. Since open classes
249% have an infinite population, if there is a class switch QN
250% with the following chains
251% Source -> (A or B) -> C -> Sink
252% Source -> D -> Sink
253% We can re-route class C into the source either as A or B or C.
254% We here re-route back as C and leave for the chain analyzer
255% to detect that C is in a chain with A and B and change this
256% part.
257
258csmask = self.csMatrix;
259if isempty(csmask) % models not built with link(P)
260 % rtnodes+rtnodes' makes the matrix undirected, it helps grouping
261 % transient states with all the recurrent states their can reach
262 % into a single "chain" of the LINE model
263 [C,WCC]=weaklyconncomp(rtnodes+rtnodes');
264 WCC(colsToIgnore) = 0;
265 chainCandidates = cell(1,C);
266 for r=1:C
267 chainCandidates{r} = find(WCC==r);
268 end
269
270 chains = false(length(chainCandidates),K); % columns are classes, rows are chains
271 tmax = 0;
272 for t=1:length(chainCandidates)
273 if length(chainCandidates{t})>1
274 tmax = tmax+1;
275 chains(tmax,(mod(chainCandidates{t}-1,K)+1))=true;
276 end
277 end
278 chains(tmax+1:end,:)=[];
279else % this is faster since csmask is smaller than rtnodes
280 [C,WCC] = weaklyconncomp(csmask+csmask');
281
282 chainCandidates = cell(1,C);
283 for c=1:C
284 chainCandidates{c} = find(WCC==c);
285 end
286
287 chains = false(length(chainCandidates),K);
288 for t=1:length(chainCandidates)
289 chains(t,chainCandidates{t}) = true;
290 end
291end
292
293chains = unique(chains,'rows');
294try
295 chains = sortrows(chains,'descend');
296catch % old MATLABs
297 chains = sortrows(chains);
298end
299
300% overlapping chains can occur if a class that enters in a Cache
301% reappars downhill in the network in rtnodes in that case we merge
302% the two chains
303splitChains = [];
304for col=find(sum(chains,1)>1)
305 rows = find(chains(:,col));
306 for r=rows(2:end)'
307 chains(rows(1),:) = chains(rows(1),:) | chains(r,:);
308 splitChains(end+1)=r;
309 end
310end
311chains(splitChains,:)=[];
312
313% We now obtain the routing matrix P by ignoring the non-stateful
314% nodes and calculating by the stochastic complement method the
315% correct transition probabilities, that includes the effects
316% of the non-stateful nodes (e.g., ClassSwitch)
317statefulNodesClasses = [];
318for ind=statefulNodes %#ok<FXSET>
319 statefulNodesClasses(end+1:end+K)= ((ind-1)*K+1):(ind*K);
320end
321
322% this routes open classes back from the sink into the source
323% it will not work with non-renewal arrivals as it choses in which open
324% class to reroute a job with probability depending on the arrival rates
325if hasOpen
326 arvRates(isnan(arvRates)) = 0;
327 for s=indexOpenClasses
328 s_chain = find(chains(:,s));
329 others_in_chain = find(chains(s_chain,:)); %#ok<FNDSB>
330 rtnodes((idxSink-1)*K+others_in_chain,(idxSource-1)*K+others_in_chain) = repmat(arvRates(others_in_chain)/sum(arvRates(others_in_chain)),length(others_in_chain),1);
331 end
332end
333
334%% Hide the nodes that are not stateful
335rt = dtmc_stochcomp(rtnodes,statefulNodesClasses);
336
337% verify irreducibility
338% disabled as it casts warning in many models that are seemingly fine
339%try
340% eigen = eig(rt); % exception if rt has NaN or Inf
341% if sum(eigen>=1)>1
342% line_warning(mfilename, 'Solutions may be invalid, the routing matrix is reducible. The path of two or more classes form independent non-interacting models.');
343% end
344%end
345sn.rt = rt;
346sn.chains = chains;
347self.sn = sn;
348
349%% Compute optional outputs
350if nargout >= 5
351 rtNodesByClass = cellzeros(K,K,I,I);
352 for ind=1:I
353 for jnd=1:I
354 for r=1:K
355 for s=1:K
356 rtNodesByClass{s,r}(ind,jnd) = rtnodes((ind-1)*K+s,(jnd-1)*K+r);
357 end
358 end
359 end
360 end
361end
362
363if nargout >= 6
364 rtNodesByStation = cellzeros(I,I,K,K);
365 for ind=1:I %#ok<FXSET>
366 for jnd=1:I
367 for r=1:K
368 for s=1:K
369 rtNodesByStation{ind,jnd}(r,s) = rtnodes((ind-1)*K+s,(jnd-1)*K+r);
370 end
371 end
372 end
373 end
374end
375end
Definition mmt.m:92