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.KCHOICES
56 rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_kchoices(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_kchoices(ind, jnd, r, s, linksmat, state_before, state_after) %#ok<INUSD>
360 % P = SUB_KCHOICES(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER)
361 % Power-of-K choices: matches LDES semantics in
362 % Solver_ssj.kt:selectKChoicesDestinationWithClass.
363 %
364 % Without memory: enumerate the m^k ordered tuples obtained by sampling
365 % k destinations WITH replacement, break ties by first occurrence in
366 % the tuple, and return the marginal probability that jnd is chosen.
367 %
368 % With memory: the previously selected destination is forced as the
369 % LAST candidate (LDES order), and the remaining k-1 candidates are
370 % sampled iid with replacement. Memory is read from state_before; if
371 % it is -1 (no prior pick) or no longer eligible, the closure falls
372 % back to the memory-less form.
373 %
374 % Memoization: the marginal-probability vector p_dest(jnd) depends only
375 % on (n[1..m], k, memPos). Across (ind, r) we cache by the per-call
376 % queue-length signature so each unique (n, k, mem) is enumerated once
377 % and the per-jnd probabilities are reused.
378 persistent kchoicesCache
379 if isempty(kchoicesCache)
380 kchoicesCache = containers.Map('KeyType','char','ValueType','any');
381 end
382
383 isf = self.sn.nodeToStateful(ind);
384 if isempty(state_before{isf})
385 p = min(linksmat(ind,jnd),1);
386 return;
387 end
388 if r ~= s
389 p = 0; return;
390 end
391 eligible = find(linksmat(ind,:));
392 m = numel(eligible);
393 if m == 0 || ~any(eligible == jnd)
394 p = 0; return;
395 end
396 np = self.sn.nodeparam{ind}{r};
397 if isfield(np,'k') && ~isempty(np.k)
398 k = np.k;
399 else
400 k = 2;
401 end
402 k = max(1, min(k, m));
403 n = zeros(1, m);
404 for i = 1:m
405 knd = eligible(i);
406 ksf = self.sn.nodeToStateful(knd);
407 n(i) = State.toMarginal(self.sn, knd, state_before{ksf});
408 end
409 jnd_pos = find(eligible == jnd, 1);
410
411 % Read memorized destination from the Router's nvars slot. The slot
412 % stores the previously chosen destination as a node index (-1 = none).
413 memEnabled = isfield(np, 'withMemory') && np.withMemory;
414 memNode = -1;
415 if memEnabled
416 R = self.sn.nclasses;
417 sb = state_before{isf};
418 if numel(sb) >= R
419 memNode = sb(end - R + r);
420 end
421 end
422 memPos = -1;
423 if memEnabled && memNode > 0
424 tmpPos = find(eligible == memNode, 1);
425 if ~isempty(tmpPos)
426 memPos = tmpPos;
427 end
428 end
429
430 % Cache key: (per-class) k, m, n vector, memPos. Same vector reused
431 % across all jnd values for this (ind, r) pair.
432 cacheKey = sprintf('k%d|m%d|mp%d|n%s', k, m, memPos, mat2str(n));
433 if isKey(kchoicesCache, cacheKey)
434 pVec = kchoicesCache(cacheKey);
435 else
436 pVec = zeros(1, m);
437 if memPos > 0
438 n_tuples = m^(k-1);
439 tuple = zeros(1, k);
440 tuple(end) = memPos;
441 for ti = 0:(n_tuples-1)
442 rem = ti;
443 for c = 1:k-1
444 tuple(c) = mod(rem, m) + 1;
445 rem = floor(rem / m);
446 end
447 sub_n = n(tuple);
448 minval = min(sub_n);
449 winner_pos_in_tuple = find(sub_n == minval, 1);
450 winner = tuple(winner_pos_in_tuple);
451 pVec(winner) = pVec(winner) + 1;
452 end
453 pVec = pVec / n_tuples;
454 else
455 n_tuples = m^k;
456 for ti = 0:(n_tuples-1)
457 tuple = zeros(1, k);
458 rem = ti;
459 for c = 1:k
460 tuple(c) = mod(rem, m) + 1;
461 rem = floor(rem / m);
462 end
463 sub_n = n(tuple);
464 minval = min(sub_n);
465 winner_pos_in_tuple = find(sub_n == minval, 1);
466 winner = tuple(winner_pos_in_tuple);
467 pVec(winner) = pVec(winner) + 1;
468 end
469 pVec = pVec / n_tuples;
470 end
471 % Bound cache size to avoid unbounded growth in long simulations.
472 if kchoicesCache.Count > 50000
473 remove(kchoicesCache, kchoicesCache.keys);
474 end
475 kchoicesCache(cacheKey) = pVec;
476 end
477 p = pVec(jnd_pos);
478 end
479
480end
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515% function [rt, rtfun, rtnodes, sn] = refreshRoutingMatrix(self, rates)
516% % [RT, RTFUN, CSMASK, RTNODES, SN] = REFRESHROUTINGMATRIX(RATES)
517% %
518% % Copyright (c) 2012-2026, Imperial College London
519% % All rights reserved.
520%
521% sn = self.sn;
522% if nargin == 1
523% if isempty(sn)
524% line_error(mfilename,'refreshRoutingMatrix cannot retrieve station rates, pass them as an input parameters.');
525% else
526% rates = sn.rates;
527% end
528% end
529% M = sn.nnodes;
530% K = sn.nclasses;
531% arvRates = zeros(1,K);
532% stateful = find(sn.isstateful)';
533%
534% indSource = find(sn.nodetype == NodeType.Source);
535% indOpenClasses = find(sn.njobs == Inf);
536% for r = indOpenClasses
537% arvRates(r) = rates(sn.nodeToStation(indSource),r);
538% end
539%
540% [rt, rtnodes, linksmat, chains] = self.getRoutingMatrix(arvRates);
541% sn = self.sn;
542% sn.chains = chains;
543%
544% if self.enableChecks
545% for r=1:K
546% if all(sn.routing(:,r) == -1)
547% line_error(mfilename,sprintf('Routing strategy in class %d is unspecified at all nodes.',r));
548% end
549% end
550% end
551%
552% isStateDep = any(sn.isstatedep(:,3));
553%
554% rnodefuncell = cell(M*K,M*K);
555%
556% if isStateDep
557% for ind=1:M % from
558% for jnd=1:M % to
559% for r=1:K
560% for s=1:K
561% if sn.isstatedep(ind,3)
562% switch sn.routing(ind,r)
563% case RoutingStrategy.RROBIN
564% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_rr(ind, jnd, r, s, linksmat, state_before, state_after);
565% case RoutingStrategy.WRROBIN
566% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_wrr(ind, jnd, r, s, linksmat, state_before, state_after);
567% case RoutingStrategy.JSQ
568% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_jsq(ind, jnd, r, s, linksmat, state_before, state_after);
569% otherwise
570% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(~,~) rtnodes((ind-1)*K+r, (jnd-1)*K+s);
571% end
572% else
573% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(~,~) rtnodes((ind-1)*K+r, (jnd-1)*K+s);
574% end
575% end
576% end
577% end
578% end
579% end
580%
581% statefulNodesClasses = [];
582% for ind=getIndexStatefulNodes(self)
583% statefulNodesClasses(end+1:end+K)= ((ind-1)*K+1):(ind*K);
584% end
585%
586% % we now generate the node routing matrix for the given state and then
587% % lump the states for non-stateful nodes so that run gives the routing
588% % table for stateful nodes only
589% statefulNodesClasses = [];
590% for ind=stateful
591% statefulNodesClasses(end+1:end+K)= ((ind-1)*K+1):(ind*K);
592% end
593%
594% if isStateDep
595% rtfunraw = @(state_before, state_after) dtmc_stochcomp(cell2mat(cellfun(@(f) f(state_before, state_after), rnodefuncell,'UniformOutput',false)), statefulNodesClasses);
596% rtfun = rtfunraw;
597% %rtfun = memoize(rtfunraw); % memoize to reduce the number of stoch comp calls
598% %rtfun.CacheSize = 6000^2;
599% else
600% rtfun = @(state_before, state_after) dtmc_stochcomp(rtnodes, statefulNodesClasses);
601% end
602%
603% nchains = size(chains,1);
604% inchain = cell(1,nchains);
605% for c=1:nchains
606% inchain{c} = find(chains(c,:));
607% end
608%
609% sn.rt = rt;
610% sn.rtnodes = rtnodes;
611% sn.rtfun = rtfun;
612% sn.chains = chains;
613% sn.nchains = nchains;
614% sn.inchain = inchain;
615% for c=1:sn.nchains
616% if range(sn.refstat(inchain{c}))>0
617% line_error(mfilename,sprintf('Classes within chain %d (classes: %s) have different reference stations.',c,mat2str(find(sn.chains(c,:)))));
618% end
619% end
620% self.sn = sn;
621%
622% function p = sub_rr(ind, jnd, r, s, linksmat, state_before, state_after)
623% % P = SUB_RR(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER)
624%
625% R = sn.nclasses;
626% isf = sn.nodeToStateful(ind);
627% if isempty(state_before{isf})
628% p = min(linksmat(ind,jnd),1);
629% else
630% if r==s
631% p = double(state_after{isf}(end-R+r)==jnd);
632% else
633% p = 0;
634% end
635% end
636% end
637%
638% function p = sub_wrr(ind, jnd, r, s, linksmat, state_before, state_after)
639% % P = SUB_WRR(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER)
640%
641% R = sn.nclasses;
642% isf = sn.nodeToStateful(ind);
643% if isempty(state_before{isf})
644% p = min(linksmat(ind,jnd),1);
645% else
646% if r==s
647% p = double(state_after{isf}(end-R+r)==jnd);
648% else
649% p = 0;
650% end
651% end
652% end
653%
654% function p = sub_jsq(ind, jnd, r, s, linksmat, state_before, state_after) %#ok<INUSD>
655% % P = SUB_JSQ(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER) %#OK<INUSD>
656%
657% isf = sn.nodeToStateful(ind);
658% if isempty(state_before{isf})
659% p = min(linksmat(ind,jnd),1);
660% else
661% if r==s
662% n = Inf*ones(1,sn.nnodes);
663% for knd=1:sn.nnodes
664% if linksmat(ind,knd)
665% ksf = sn.nodeToStateful(knd);
666% n(knd) = State.toMarginal(sn, knd, state_before{ksf});
667% end
668% end
669% if n(jnd) == min(n)
670% p = 1 / sum(n == min(n));
671% else
672% p = 0;
673% end
674% else
675% p = 0;
676% end
677% end
678% end
679%
680% end
Definition fjtag.m:157
Definition Station.m:245