LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
refreshRoutingMatrix.m
1function [rt, rtfun, rtnodes, sn] = refreshRoutingMatrix(self, rates)
2% [RT, RTFUN, CSMASK, RTNODES, SN] = REFRESHROUTINGMATRIX(RATES)
3%
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6
7sn = self.sn;
8if nargin == 1
9 if isempty(sn)
10 line_error(mfilename,'refreshRoutingMatrix cannot retrieve station rates, pass them as an input parameters.');
11 else
12 rates = sn.rates;
13 end
14end
15M = sn.nnodes;
16K = sn.nclasses;
17arvRates = zeros(1,K);
18stateful = find(sn.isstateful)';
19
20indSource = find(sn.nodetype == NodeType.Source);
21indOpenClasses = find(sn.njobs == Inf);
22for r = indOpenClasses
23 arvRates(r) = rates(sn.nodeToStation(indSource),r);
24end
25
26[rt, rtnodes, linksmat, chains] = self.getRoutingMatrix(arvRates);
27sn = self.sn;
28sn.chains = chains;
29
30if self.enableChecks
31 for r=1:K
32 if all(sn.routing(:,r) == -1)
33 line_error(mfilename,sprintf('Routing strategy in class %d is unspecified at all nodes.',r));
34 end
35 end
36end
37
38isStateDep = any(sn.isstatedep(:,3));
39
40rnodefuncell = cell(M*K,M*K);
41
42if isStateDep
43 for ind=1:M % from
44 for jnd=1:M % to
45 for r=1:K
46 for s=1:K
47 if sn.isstatedep(ind,3)
48 switch sn.routing(ind,r)
49 case RoutingStrategy.RROBIN
50 rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_rr(ind, jnd, r, s, linksmat, state_before, state_after);
51 case RoutingStrategy.WRROBIN
52 rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_wrr(ind, jnd, r, s, linksmat, state_before, state_after);
53 case RoutingStrategy.JSQ
54 rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_jsq(ind, jnd, r, s, linksmat, state_before, state_after);
55 case RoutingStrategy.SQ
56 rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_sq(ind, jnd, r, s, linksmat, state_before, state_after);
57 case RoutingStrategy.RL
58 rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_rl(ind, jnd, r, s, linksmat, state_before, state_after);
59 otherwise
60 rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(~,~) rtnodes((ind-1)*K+r, (jnd-1)*K+s);
61 end
62 else
63 rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(~,~) rtnodes((ind-1)*K+r, (jnd-1)*K+s);
64 end
65 end
66 end
67 end
68 end
69end
70
71statefulNodesClasses = [];
72for ind=getIndexStatefulNodes(self)
73 statefulNodesClasses(end+1:end+K)= ((ind-1)*K+1):(ind*K);
74end
75
76% we now generate the node routing matrix for the given state and then
77% lump the states for non-stateful nodes so that run gives the routing
78% table for stateful nodes only
79statefulNodesClasses = [];
80for ind=stateful
81 statefulNodesClasses(end+1:end+K)= ((ind-1)*K+1):(ind*K);
82end
83
84if isStateDep
85 rtfunraw = @(state_before, state_after) dtmc_stochcomp(cell2mat(cellfun(@(f) f(state_before, state_after), rnodefuncell,'UniformOutput',false)), statefulNodesClasses);
86 rtfun = rtfunraw;
87 %rtfun = memoize(rtfunraw); % memoize to reduce the number of stoch comp calls
88 %rtfun.CacheSize = 6000^2;
89else
90 rtfun = @(state_before, state_after) dtmc_stochcomp(rtnodes, statefulNodesClasses);
91end
92
93nchains = size(chains,1);
94inchain = cell(1,nchains);
95for c=1:nchains
96 inchain{c} = find(chains(c,:));
97end
98
99sn.rt = rt;
100sn.rtnodes = rtnodes;
101%sn.rtorig = RoutingMatrix.rtnodes2rtorig(sn); %% causes issues to
102%JLINE.from_line_links in convering example_closedModel_6
103sn.rtfun = rtfun;
104sn.chains = chains;
105sn.nchains = nchains;
106sn.inchain = inchain;
107for c=1:sn.nchains
108 if range(sn.refstat(inchain{c}))>0
109 line_error(mfilename,sprintf('Classes within chain %d (classes: %s) have different reference stations.',c,mat2str(find(sn.chains(c,:)))));
110 end
111end
112self.sn = sn;
113
114 function p = sub_rr(ind, jnd, r, s, linksmat, state_before, state_after)
115 % P = SUB_RR(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER)
116
117 isf = self.sn.nodeToStateful(ind);
118 if isempty(state_before{isf})
119 p = min(linksmat(ind,jnd),1);
120 else
121 if r==s
122 p = double(state_after{isf}(sub_routeslot(ind, r, state_after{isf}))==jnd);
123 else
124 p = 0;
125 end
126 end
127 end
128
129 function slot = sub_routeslot(ind, r, state_i)
130 % SLOT = SUB_ROUTESLOT(IND, R, STATE_I)
131 % Column of STATE_I holding the round-robin pointer of class R at node
132 % IND.
133 %
134 % The per-class routing pointers are appended in class order, and only
135 % for the classes that actually route round-robin, followed by the
136 % node-level block (nvars column 2R+1) if the node has one. The pointer
137 % is therefore located by counting back from the end of the state: over
138 % the node-level block, then over the pointers of the classes after r.
139 %
140 % Reading it as state_i(end-R+r) instead, as this did, assumes that the
141 % pointers are exactly the last R columns. That silently reads the wrong
142 % column whenever the node carries a trailing block of its own (the
143 % polling controller of a POLLING station), and also whenever only some
144 % of the classes route round-robin, in which case a pointer is confused
145 % with a service phase.
146 R = self.sn.nclasses;
147 nodeblock = self.sn.nvars(ind, 2*R+1);
148 after = sum(self.sn.nvars(ind, (R+r+1):(2*R)));
149 slot = numel(state_i) - nodeblock - after;
150 end
151
152 function p = sub_wrr(ind, jnd, r, s, linksmat, state_before, state_after)
153 % P = SUB_WRR(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER)
154 % WRR slot holds a POSITION in weighted_outlinks; map it to the
155 % destination node index before comparing.
156
157 isf = self.sn.nodeToStateful(ind);
158 if isempty(state_before{isf})
159 p = min(linksmat(ind,jnd),1);
160 else
161 if r==s
162 pos = state_after{isf}(sub_routeslot(ind, r, state_after{isf}));
163 np = self.sn.nodeparam{ind}{r};
164 if isfield(np,'weighted_outlinks') && ~isempty(np.weighted_outlinks) ...
165 && pos >= 1 && pos <= length(np.weighted_outlinks)
166 dest = np.weighted_outlinks(pos);
167 elseif pos >= 1 && pos <= length(np.outlinks)
168 dest = np.outlinks(pos);
169 else
170 p = 0; return;
171 end
172 p = double(dest == jnd);
173 else
174 p = 0;
175 end
176 end
177 end
178
179 function p = sub_jsq(ind, jnd, r, s, linksmat, state_before, state_after) %#ok<INUSD>
180 % P = SUB_JSQ(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER) %#OK<INUSD>
181
182 isf = self.sn.nodeToStateful(ind);
183 if isempty(state_before{isf})
184 p = min(linksmat(ind,jnd),1);
185 else
186 if r==s
187 n = Inf*ones(1,self.sn.nnodes);
188 for knd=1:self.sn.nnodes
189 if linksmat(ind,knd)
190 ksf = self.sn.nodeToStateful(knd);
191 n(knd) = State.toMarginal(self.sn, knd, state_before{ksf});
192 end
193 end
194 if n(jnd) == min(n)
195 p = 1 / sum(n == min(n));
196 else
197 p = 0;
198 end
199 else
200 p = 0;
201 end
202 end
203 end
204
205
206 function p = sub_rl(ind, jnd, r, s, linksmat, state_before, state_after) %#ok<INUSD>
207 % P = SUB_RL(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER) %#OK<INUSD>
208
209 isf = self.sn.nodeToStateful(ind);
210 if isempty(state_before{isf})
211 p = min(linksmat(ind,jnd),1);
212 else
213 if r==s
214 % ----- new added contents ----- %
215 if self.nodes{ind}.output.outputStrategy{1,r}{5}==0 % state_size=0, use tabular value fn
216 % Multi-class: each class supplies its own value function.
217 % State.toMarginal aggregates queue lengths across classes,
218 % so the per-class call works as long as outputStrategy{r}
219 % is configured per class.
220 value_function = self.nodes{ind}.output.outputStrategy{1,r}{3};
221 nodes_need_action = self.nodes{ind}.output.outputStrategy{1,r}{4};
222
223 if ~isempty(find(nodes_need_action==ind, 1))
224 indQueue = find(self.sn.nodetype == NodeType.Queue);
225 v = Inf*ones(1, self.sn.nnodes); % value fn
226 n = Inf*ones(1, self.sn.nnodes); % queue length
227 x = zeros(1, length(indQueue)); % current state
228 for knd_idx=1:length(indQueue)
229 knd = indQueue(knd_idx);
230 ksf = self.sn.nodeToStateful(knd); %% does the state removes the job from the departure node already?
231 x(knd_idx) = State.toMarginal(self.sn, knd, state_before{ksf});
232 end
233
234 for knd = 1:self.sn.nnodes
235 if linksmat(ind, knd)
236 tmp = x+1;
237 tmp(indQueue == knd) = tmp(indQueue == knd) + 1;
238 if max(tmp) <= size(value_function, 1)
239 ttmp = num2cell(tmp);
240 v(knd) = value_function(ttmp{:});
241 end
242 ksf = self.sn.nodeToStateful(knd);
243 n(knd) = State.toMarginal(self.sn, knd, state_before{ksf});
244 end
245 end
246
247 if min(v) < Inf && max(x+1) < size(value_function, 1) % in action space
248 if v(jnd) == min(v)
249 p = 1 / sum(v == min(v));
250 else
251 p = 0;
252 end
253 else % not in action space, use JSQ
254 if n(jnd) == min(n)
255 p = 1 / sum(n == min(n));
256 else
257 p = 0;
258 end
259 end
260
261 else % not in nodes_need_action: this node doesn't use RL results, use JSQ
262 n = Inf*ones(1,self.sn.nnodes);
263 for knd=1:self.sn.nnodes
264 if linksmat(ind,knd)
265 ksf = self.sn.nodeToStateful(knd);
266 n(knd) = State.toMarginal(self.sn, knd, state_before{ksf});
267 end
268 end
269 if n(jnd) == min(n)
270 p = 1 / sum(n == min(n));
271 else
272 p = 0;
273 end
274 end
275
276 elseif self.nodes{ind}.output.outputStrategy{1,r}{5}>0 % state_size>0, use fn approx for value fn
277 % Multi-class: class-specific coefficient row vector.
278 coeff = self.nodes{ind}.output.outputStrategy{1,r}{3};
279 nodes_need_action = self.nodes{ind}.output.outputStrategy{1,r}{4};
280 stateSize = self.nodes{ind}.output.outputStrategy{1,r}{5};
281
282 if ~isempty(find(nodes_need_action==ind, 1))
283 indQueue = find(self.sn.nodetype == NodeType.Queue);
284 v = Inf*ones(1, self.sn.nnodes); % value fn
285 n = Inf*ones(1, self.sn.nnodes); % queue length
286 x = zeros(1, length(indQueue)); % current state
287 for knd_idx=1:length(indQueue)
288 knd = indQueue(knd_idx);
289 ksf = self.sn.nodeToStateful(knd); %% does the state removes the job from the departure node already?
290 x(knd_idx) = State.toMarginal(self.sn, knd, state_before{ksf});
291 end
292 for knd = 1:self.sn.nnodes
293 if linksmat(ind, knd)
294 tmp = x;
295 tmp(indQueue == knd) = tmp(indQueue == knd) + 1;
296
297 tmp_vec = [1 tmp];
298 for i = 1:length(tmp)
299 for j = i:length(tmp)
300 tmp_vec(end+1) = tmp(i)* tmp(j);
301 end
302 end
303 v(knd) = tmp_vec * coeff.';
304
305 ksf = self.sn.nodeToStateful(knd);
306 n(knd) = State.toMarginal(self.sn, knd, state_before{ksf});
307 end
308 end
309 if min(v) < Inf && max(x+1) < stateSize % in action space
310 if v(jnd) == min(v)
311 p = 1 / sum(v == min(v));
312 else
313 p = 0;
314 end
315 else % not in action space, use JSQ
316 if n(jnd) == min(n)
317 p = 1 / sum(n == min(n));
318 else
319 p = 0;
320 end
321 end
322 else % not in nodes_need_action: this node doesn't use RL results, use JSQ
323 n = Inf*ones(1,self.sn.nnodes);
324 for knd=1:self.sn.nnodes
325 if linksmat(ind,knd)
326 ksf = self.sn.nodeToStateful(knd);
327 n(knd) = State.toMarginal(self.sn, knd, state_before{ksf});
328 end
329 end
330 if n(jnd) == min(n)
331 p = 1 / sum(n == min(n));
332 else
333 p = 0;
334 end
335 end
336
337 else % no value fn, use JSQ
338 % ----- end of new added contents ----- %
339
340 n = Inf*ones(1,self.sn.nnodes);
341 for knd=1:self.sn.nnodes
342 if linksmat(ind,knd)
343 ksf = self.sn.nodeToStateful(knd);
344 n(knd) = State.toMarginal(self.sn, knd, state_before{ksf});
345 end
346 end
347 if n(jnd) == min(n)
348 p = 1 / sum(n == min(n));
349 else
350 p = 0;
351 end
352 end
353 else
354 p = 0;
355 end
356 end
357 end
358
359 function p = sub_sq(ind, jnd, r, s, linksmat, state_before, state_after) %#ok<INUSD>
360 % P = SUB_SQ(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER)
361 % SQ(d), shortest queue of d: matches LDES semantics in
362 % Solver_ssj.kt:selectSQDestination.
363 %
364 % Enumerate the ndest^d ordered tuples obtained by sampling d
365 % destinations WITH replacement, break ties by first occurrence in the
366 % tuple, and return the marginal probability that jnd is chosen.
367 % Dispatcher memory is not supported, so the closure is a function of
368 % the queue lengths alone and carries no auxiliary state.
369 %
370 % Memoization: the marginal-probability vector p_dest(jnd) depends only
371 % on (n[1..ndest], d). Across (ind, r) we cache by the per-call
372 % queue-length signature so each unique (n, d) is enumerated once and
373 % the per-jnd probabilities are reused.
374 persistent sqCache
375 if isempty(sqCache)
376 sqCache = containers.Map('KeyType','char','ValueType','any');
377 end
378
379 isf = self.sn.nodeToStateful(ind);
380 if isempty(state_before{isf})
381 p = min(linksmat(ind,jnd),1);
382 return;
383 end
384 if r ~= s
385 p = 0; return;
386 end
387 eligible = find(linksmat(ind,:));
388 ndest = numel(eligible);
389 if ndest == 0 || ~any(eligible == jnd)
390 p = 0; return;
391 end
392 np = self.sn.nodeparam{ind}{r};
393 if isfield(np,'d') && ~isempty(np.d)
394 d = np.d;
395 else
396 d = 2;
397 end
398 d = max(1, min(d, ndest));
399 n = zeros(1, ndest);
400 for i = 1:ndest
401 knd = eligible(i);
402 ksf = self.sn.nodeToStateful(knd);
403 n(i) = State.toMarginal(self.sn, knd, state_before{ksf});
404 end
405 jnd_pos = find(eligible == jnd, 1);
406
407 % Cache key: (per-class) d, ndest, n vector. Same vector reused across
408 % all jnd values for this (ind, r) pair.
409 cacheKey = sprintf('d%d|nd%d|n%s', d, ndest, mat2str(n));
410 if isKey(sqCache, cacheKey)
411 pVec = sqCache(cacheKey);
412 else
413 pVec = zeros(1, ndest);
414 n_tuples = ndest^d;
415 for ti = 0:(n_tuples-1)
416 tuple = zeros(1, d);
417 rem = ti;
418 for c = 1:d
419 tuple(c) = mod(rem, ndest) + 1;
420 rem = floor(rem / ndest);
421 end
422 sub_n = n(tuple);
423 minval = min(sub_n);
424 winner_pos_in_tuple = find(sub_n == minval, 1);
425 winner = tuple(winner_pos_in_tuple);
426 pVec(winner) = pVec(winner) + 1;
427 end
428 pVec = pVec / n_tuples;
429 % Bound cache size to avoid unbounded growth in long simulations.
430 if sqCache.Count > 50000
431 remove(sqCache, sqCache.keys);
432 end
433 sqCache(cacheKey) = pVec;
434 end
435 p = pVec(jnd_pos);
436 end
437
438end
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473% function [rt, rtfun, rtnodes, sn] = refreshRoutingMatrix(self, rates)
474% % [RT, RTFUN, CSMASK, RTNODES, SN] = REFRESHROUTINGMATRIX(RATES)
475% %
476% % Copyright (c) 2012-2026, Imperial College London
477% % All rights reserved.
478%
479% sn = self.sn;
480% if nargin == 1
481% if isempty(sn)
482% line_error(mfilename,'refreshRoutingMatrix cannot retrieve station rates, pass them as an input parameters.');
483% else
484% rates = sn.rates;
485% end
486% end
487% M = sn.nnodes;
488% K = sn.nclasses;
489% arvRates = zeros(1,K);
490% stateful = find(sn.isstateful)';
491%
492% indSource = find(sn.nodetype == NodeType.Source);
493% indOpenClasses = find(sn.njobs == Inf);
494% for r = indOpenClasses
495% arvRates(r) = rates(sn.nodeToStation(indSource),r);
496% end
497%
498% [rt, rtnodes, linksmat, chains] = self.getRoutingMatrix(arvRates);
499% sn = self.sn;
500% sn.chains = chains;
501%
502% if self.enableChecks
503% for r=1:K
504% if all(sn.routing(:,r) == -1)
505% line_error(mfilename,sprintf('Routing strategy in class %d is unspecified at all nodes.',r));
506% end
507% end
508% end
509%
510% isStateDep = any(sn.isstatedep(:,3));
511%
512% rnodefuncell = cell(M*K,M*K);
513%
514% if isStateDep
515% for ind=1:M % from
516% for jnd=1:M % to
517% for r=1:K
518% for s=1:K
519% if sn.isstatedep(ind,3)
520% switch sn.routing(ind,r)
521% case RoutingStrategy.RROBIN
522% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_rr(ind, jnd, r, s, linksmat, state_before, state_after);
523% case RoutingStrategy.WRROBIN
524% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_wrr(ind, jnd, r, s, linksmat, state_before, state_after);
525% case RoutingStrategy.JSQ
526% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_jsq(ind, jnd, r, s, linksmat, state_before, state_after);
527% otherwise
528% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(~,~) rtnodes((ind-1)*K+r, (jnd-1)*K+s);
529% end
530% else
531% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(~,~) rtnodes((ind-1)*K+r, (jnd-1)*K+s);
532% end
533% end
534% end
535% end
536% end
537% end
538%
539% statefulNodesClasses = [];
540% for ind=getIndexStatefulNodes(self)
541% statefulNodesClasses(end+1:end+K)= ((ind-1)*K+1):(ind*K);
542% end
543%
544% % we now generate the node routing matrix for the given state and then
545% % lump the states for non-stateful nodes so that run gives the routing
546% % table for stateful nodes only
547% statefulNodesClasses = [];
548% for ind=stateful
549% statefulNodesClasses(end+1:end+K)= ((ind-1)*K+1):(ind*K);
550% end
551%
552% if isStateDep
553% rtfunraw = @(state_before, state_after) dtmc_stochcomp(cell2mat(cellfun(@(f) f(state_before, state_after), rnodefuncell,'UniformOutput',false)), statefulNodesClasses);
554% rtfun = rtfunraw;
555% %rtfun = memoize(rtfunraw); % memoize to reduce the number of stoch comp calls
556% %rtfun.CacheSize = 6000^2;
557% else
558% rtfun = @(state_before, state_after) dtmc_stochcomp(rtnodes, statefulNodesClasses);
559% end
560%
561% nchains = size(chains,1);
562% inchain = cell(1,nchains);
563% for c=1:nchains
564% inchain{c} = find(chains(c,:));
565% end
566%
567% sn.rt = rt;
568% sn.rtnodes = rtnodes;
569% sn.rtfun = rtfun;
570% sn.chains = chains;
571% sn.nchains = nchains;
572% sn.inchain = inchain;
573% for c=1:sn.nchains
574% if range(sn.refstat(inchain{c}))>0
575% line_error(mfilename,sprintf('Classes within chain %d (classes: %s) have different reference stations.',c,mat2str(find(sn.chains(c,:)))));
576% end
577% end
578% self.sn = sn;
579%
580% function p = sub_rr(ind, jnd, r, s, linksmat, state_before, state_after)
581% % P = SUB_RR(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER)
582%
583% R = sn.nclasses;
584% isf = sn.nodeToStateful(ind);
585% if isempty(state_before{isf})
586% p = min(linksmat(ind,jnd),1);
587% else
588% if r==s
589% p = double(state_after{isf}(end-R+r)==jnd);
590% else
591% p = 0;
592% end
593% end
594% end
595%
596% function p = sub_wrr(ind, jnd, r, s, linksmat, state_before, state_after)
597% % P = SUB_WRR(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER)
598%
599% R = sn.nclasses;
600% isf = sn.nodeToStateful(ind);
601% if isempty(state_before{isf})
602% p = min(linksmat(ind,jnd),1);
603% else
604% if r==s
605% p = double(state_after{isf}(end-R+r)==jnd);
606% else
607% p = 0;
608% end
609% end
610% end
611%
612% function p = sub_jsq(ind, jnd, r, s, linksmat, state_before, state_after) %#ok<INUSD>
613% % P = SUB_JSQ(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER) %#OK<INUSD>
614%
615% isf = sn.nodeToStateful(ind);
616% if isempty(state_before{isf})
617% p = min(linksmat(ind,jnd),1);
618% else
619% if r==s
620% n = Inf*ones(1,sn.nnodes);
621% for knd=1:sn.nnodes
622% if linksmat(ind,knd)
623% ksf = sn.nodeToStateful(knd);
624% n(knd) = State.toMarginal(sn, knd, state_before{ksf});
625% end
626% end
627% if n(jnd) == min(n)
628% p = 1 / sum(n == min(n));
629% else
630% p = 0;
631% end
632% else
633% p = 0;
634% end
635% end
636% end
637%
638% end
Definition fjtag.m:161