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 R = self.sn.nclasses;
118 isf = self.sn.nodeToStateful(ind);
119 if isempty(state_before{isf})
120 p = min(linksmat(ind,jnd),1);
121 else
122 if r==s
123 p = double(state_after{isf}(end-R+r)==jnd);
124 else
125 p = 0;
126 end
127 end
128 end
129
130 function p = sub_wrr(ind, jnd, r, s, linksmat, state_before, state_after)
131 % P = SUB_WRR(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER)
132 % WRR slot holds a POSITION in weighted_outlinks; map it to the
133 % destination node index before comparing.
134
135 R = self.sn.nclasses;
136 isf = self.sn.nodeToStateful(ind);
137 if isempty(state_before{isf})
138 p = min(linksmat(ind,jnd),1);
139 else
140 if r==s
141 pos = state_after{isf}(end-R+r);
142 np = self.sn.nodeparam{ind}{r};
143 if isfield(np,'weighted_outlinks') && ~isempty(np.weighted_outlinks) ...
144 && pos >= 1 && pos <= length(np.weighted_outlinks)
145 dest = np.weighted_outlinks(pos);
146 elseif pos >= 1 && pos <= length(np.outlinks)
147 dest = np.outlinks(pos);
148 else
149 p = 0; return;
150 end
151 p = double(dest == jnd);
152 else
153 p = 0;
154 end
155 end
156 end
157
158 function p = sub_jsq(ind, jnd, r, s, linksmat, state_before, state_after) %#ok<INUSD>
159 % P = SUB_JSQ(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER) %#OK<INUSD>
160
161 isf = self.sn.nodeToStateful(ind);
162 if isempty(state_before{isf})
163 p = min(linksmat(ind,jnd),1);
164 else
165 if r==s
166 n = Inf*ones(1,self.sn.nnodes);
167 for knd=1:self.sn.nnodes
168 if linksmat(ind,knd)
169 ksf = self.sn.nodeToStateful(knd);
170 n(knd) = State.toMarginal(self.sn, knd, state_before{ksf});
171 end
172 end
173 if n(jnd) == min(n)
174 p = 1 / sum(n == min(n));
175 else
176 p = 0;
177 end
178 else
179 p = 0;
180 end
181 end
182 end
183
184
185 function p = sub_rl(ind, jnd, r, s, linksmat, state_before, state_after) %#ok<INUSD>
186 % P = SUB_RL(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER) %#OK<INUSD>
187
188 isf = self.sn.nodeToStateful(ind);
189 if isempty(state_before{isf})
190 p = min(linksmat(ind,jnd),1);
191 else
192 if r==s
193 % ----- new added contents ----- %
194 if self.nodes{ind}.output.outputStrategy{1,r}{5}==0 % state_size=0, use tabular value fn
195 % Multi-class: each class supplies its own value function.
196 % State.toMarginal aggregates queue lengths across classes,
197 % so the per-class call works as long as outputStrategy{r}
198 % is configured per class.
199 value_function = self.nodes{ind}.output.outputStrategy{1,r}{3};
200 nodes_need_action = self.nodes{ind}.output.outputStrategy{1,r}{4};
201
202 if ~isempty(find(nodes_need_action==ind, 1))
203 indQueue = find(self.sn.nodetype == NodeType.Queue);
204 v = Inf*ones(1, self.sn.nnodes); % value fn
205 n = Inf*ones(1, self.sn.nnodes); % queue length
206 x = zeros(1, length(indQueue)); % current state
207 for knd_idx=1:length(indQueue)
208 knd = indQueue(knd_idx);
209 ksf = self.sn.nodeToStateful(knd); %% does the state removes the job from the departure node already?
210 x(knd_idx) = State.toMarginal(self.sn, knd, state_before{ksf});
211 end
212
213 for knd = 1:self.sn.nnodes
214 if linksmat(ind, knd)
215 tmp = x+1;
216 tmp(indQueue == knd) = tmp(indQueue == knd) + 1;
217 if max(tmp) <= size(value_function, 1)
218 ttmp = num2cell(tmp);
219 v(knd) = value_function(ttmp{:});
220 end
221 ksf = self.sn.nodeToStateful(knd);
222 n(knd) = State.toMarginal(self.sn, knd, state_before{ksf});
223 end
224 end
225
226 if min(v) < Inf && max(x+1) < size(value_function, 1) % in action space
227 if v(jnd) == min(v)
228 p = 1 / sum(v == min(v));
229 else
230 p = 0;
231 end
232 else % not in action space, use JSQ
233 if n(jnd) == min(n)
234 p = 1 / sum(n == min(n));
235 else
236 p = 0;
237 end
238 end
239
240 else % not in nodes_need_action: this node doesn't use RL results, use JSQ
241 n = Inf*ones(1,self.sn.nnodes);
242 for knd=1:self.sn.nnodes
243 if linksmat(ind,knd)
244 ksf = self.sn.nodeToStateful(knd);
245 n(knd) = State.toMarginal(self.sn, knd, state_before{ksf});
246 end
247 end
248 if n(jnd) == min(n)
249 p = 1 / sum(n == min(n));
250 else
251 p = 0;
252 end
253 end
254
255 elseif self.nodes{ind}.output.outputStrategy{1,r}{5}>0 % state_size>0, use fn approx for value fn
256 % Multi-class: class-specific coefficient row vector.
257 coeff = self.nodes{ind}.output.outputStrategy{1,r}{3};
258 nodes_need_action = self.nodes{ind}.output.outputStrategy{1,r}{4};
259 stateSize = self.nodes{ind}.output.outputStrategy{1,r}{5};
260
261 if ~isempty(find(nodes_need_action==ind, 1))
262 indQueue = find(self.sn.nodetype == NodeType.Queue);
263 v = Inf*ones(1, self.sn.nnodes); % value fn
264 n = Inf*ones(1, self.sn.nnodes); % queue length
265 x = zeros(1, length(indQueue)); % current state
266 for knd_idx=1:length(indQueue)
267 knd = indQueue(knd_idx);
268 ksf = self.sn.nodeToStateful(knd); %% does the state removes the job from the departure node already?
269 x(knd_idx) = State.toMarginal(self.sn, knd, state_before{ksf});
270 end
271 for knd = 1:self.sn.nnodes
272 if linksmat(ind, knd)
273 tmp = x;
274 tmp(indQueue == knd) = tmp(indQueue == knd) + 1;
275
276 tmp_vec = [1 tmp];
277 for i = 1:length(tmp)
278 for j = i:length(tmp)
279 tmp_vec(end+1) = tmp(i)* tmp(j);
280 end
281 end
282 v(knd) = tmp_vec * coeff.';
283
284 ksf = self.sn.nodeToStateful(knd);
285 n(knd) = State.toMarginal(self.sn, knd, state_before{ksf});
286 end
287 end
288 if min(v) < Inf && max(x+1) < stateSize % in action space
289 if v(jnd) == min(v)
290 p = 1 / sum(v == min(v));
291 else
292 p = 0;
293 end
294 else % not in action space, use JSQ
295 if n(jnd) == min(n)
296 p = 1 / sum(n == min(n));
297 else
298 p = 0;
299 end
300 end
301 else % not in nodes_need_action: this node doesn't use RL results, use JSQ
302 n = Inf*ones(1,self.sn.nnodes);
303 for knd=1:self.sn.nnodes
304 if linksmat(ind,knd)
305 ksf = self.sn.nodeToStateful(knd);
306 n(knd) = State.toMarginal(self.sn, knd, state_before{ksf});
307 end
308 end
309 if n(jnd) == min(n)
310 p = 1 / sum(n == min(n));
311 else
312 p = 0;
313 end
314 end
315
316 else % no value fn, use JSQ
317 % ----- end of new added contents ----- %
318
319 n = Inf*ones(1,self.sn.nnodes);
320 for knd=1:self.sn.nnodes
321 if linksmat(ind,knd)
322 ksf = self.sn.nodeToStateful(knd);
323 n(knd) = State.toMarginal(self.sn, knd, state_before{ksf});
324 end
325 end
326 if n(jnd) == min(n)
327 p = 1 / sum(n == min(n));
328 else
329 p = 0;
330 end
331 end
332 else
333 p = 0;
334 end
335 end
336 end
337
338 function p = sub_kchoices(ind, jnd, r, s, linksmat, state_before, state_after) %#ok<INUSD>
339 % P = SUB_KCHOICES(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER)
340 % Power-of-K choices: matches LDES semantics in
341 % Solver_ssj.kt:selectKChoicesDestinationWithClass.
342 %
343 % Without memory: enumerate the m^k ordered tuples obtained by sampling
344 % k destinations WITH replacement, break ties by first occurrence in
345 % the tuple, and return the marginal probability that jnd is chosen.
346 %
347 % With memory: the previously selected destination is forced as the
348 % LAST candidate (LDES order), and the remaining k-1 candidates are
349 % sampled iid with replacement. Memory is read from state_before; if
350 % it is -1 (no prior pick) or no longer eligible, the closure falls
351 % back to the memory-less form.
352 %
353 % Memoization: the marginal-probability vector p_dest(jnd) depends only
354 % on (n[1..m], k, memPos). Across (ind, r) we cache by the per-call
355 % queue-length signature so each unique (n, k, mem) is enumerated once
356 % and the per-jnd probabilities are reused.
357 persistent kchoicesCache
358 if isempty(kchoicesCache)
359 kchoicesCache = containers.Map('KeyType','char','ValueType','any');
360 end
361
362 isf = self.sn.nodeToStateful(ind);
363 if isempty(state_before{isf})
364 p = min(linksmat(ind,jnd),1);
365 return;
366 end
367 if r ~= s
368 p = 0; return;
369 end
370 eligible = find(linksmat(ind,:));
371 m = numel(eligible);
372 if m == 0 || ~any(eligible == jnd)
373 p = 0; return;
374 end
375 np = self.sn.nodeparam{ind}{r};
376 if isfield(np,'k') && ~isempty(np.k)
377 k = np.k;
378 else
379 k = 2;
380 end
381 k = max(1, min(k, m));
382 n = zeros(1, m);
383 for i = 1:m
384 knd = eligible(i);
385 ksf = self.sn.nodeToStateful(knd);
386 n(i) = State.toMarginal(self.sn, knd, state_before{ksf});
387 end
388 jnd_pos = find(eligible == jnd, 1);
389
390 % Read memorized destination from the Router's nvars slot. The slot
391 % stores the previously chosen destination as a node index (-1 = none).
392 memEnabled = isfield(np, 'withMemory') && np.withMemory;
393 memNode = -1;
394 if memEnabled
395 R = self.sn.nclasses;
396 sb = state_before{isf};
397 if numel(sb) >= R
398 memNode = sb(end - R + r);
399 end
400 end
401 memPos = -1;
402 if memEnabled && memNode > 0
403 tmpPos = find(eligible == memNode, 1);
404 if ~isempty(tmpPos)
405 memPos = tmpPos;
406 end
407 end
408
409 % Cache key: (per-class) k, m, n vector, memPos. Same vector reused
410 % across all jnd values for this (ind, r) pair.
411 cacheKey = sprintf('k%d|m%d|mp%d|n%s', k, m, memPos, mat2str(n));
412 if isKey(kchoicesCache, cacheKey)
413 pVec = kchoicesCache(cacheKey);
414 else
415 pVec = zeros(1, m);
416 if memPos > 0
417 n_tuples = m^(k-1);
418 tuple = zeros(1, k);
419 tuple(end) = memPos;
420 for ti = 0:(n_tuples-1)
421 rem = ti;
422 for c = 1:k-1
423 tuple(c) = mod(rem, m) + 1;
424 rem = floor(rem / m);
425 end
426 sub_n = n(tuple);
427 minval = min(sub_n);
428 winner_pos_in_tuple = find(sub_n == minval, 1);
429 winner = tuple(winner_pos_in_tuple);
430 pVec(winner) = pVec(winner) + 1;
431 end
432 pVec = pVec / n_tuples;
433 else
434 n_tuples = m^k;
435 for ti = 0:(n_tuples-1)
436 tuple = zeros(1, k);
437 rem = ti;
438 for c = 1:k
439 tuple(c) = mod(rem, m) + 1;
440 rem = floor(rem / m);
441 end
442 sub_n = n(tuple);
443 minval = min(sub_n);
444 winner_pos_in_tuple = find(sub_n == minval, 1);
445 winner = tuple(winner_pos_in_tuple);
446 pVec(winner) = pVec(winner) + 1;
447 end
448 pVec = pVec / n_tuples;
449 end
450 % Bound cache size to avoid unbounded growth in long simulations.
451 if kchoicesCache.Count > 50000
452 remove(kchoicesCache, kchoicesCache.keys);
453 end
454 kchoicesCache(cacheKey) = pVec;
455 end
456 p = pVec(jnd_pos);
457 end
458
459end
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494% function [rt, rtfun, rtnodes, sn] = refreshRoutingMatrix(self, rates)
495% % [RT, RTFUN, CSMASK, RTNODES, SN] = REFRESHROUTINGMATRIX(RATES)
496% %
497% % Copyright (c) 2012-2026, Imperial College London
498% % All rights reserved.
499%
500% sn = self.sn;
501% if nargin == 1
502% if isempty(sn)
503% line_error(mfilename,'refreshRoutingMatrix cannot retrieve station rates, pass them as an input parameters.');
504% else
505% rates = sn.rates;
506% end
507% end
508% M = sn.nnodes;
509% K = sn.nclasses;
510% arvRates = zeros(1,K);
511% stateful = find(sn.isstateful)';
512%
513% indSource = find(sn.nodetype == NodeType.Source);
514% indOpenClasses = find(sn.njobs == Inf);
515% for r = indOpenClasses
516% arvRates(r) = rates(sn.nodeToStation(indSource),r);
517% end
518%
519% [rt, rtnodes, linksmat, chains] = self.getRoutingMatrix(arvRates);
520% sn = self.sn;
521% sn.chains = chains;
522%
523% if self.enableChecks
524% for r=1:K
525% if all(sn.routing(:,r) == -1)
526% line_error(mfilename,sprintf('Routing strategy in class %d is unspecified at all nodes.',r));
527% end
528% end
529% end
530%
531% isStateDep = any(sn.isstatedep(:,3));
532%
533% rnodefuncell = cell(M*K,M*K);
534%
535% if isStateDep
536% for ind=1:M % from
537% for jnd=1:M % to
538% for r=1:K
539% for s=1:K
540% if sn.isstatedep(ind,3)
541% switch sn.routing(ind,r)
542% case RoutingStrategy.RROBIN
543% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_rr(ind, jnd, r, s, linksmat, state_before, state_after);
544% case RoutingStrategy.WRROBIN
545% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_wrr(ind, jnd, r, s, linksmat, state_before, state_after);
546% case RoutingStrategy.JSQ
547% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(state_before, state_after) sub_jsq(ind, jnd, r, s, linksmat, state_before, state_after);
548% otherwise
549% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(~,~) rtnodes((ind-1)*K+r, (jnd-1)*K+s);
550% end
551% else
552% rnodefuncell{(ind-1)*K+r, (jnd-1)*K+s} = @(~,~) rtnodes((ind-1)*K+r, (jnd-1)*K+s);
553% end
554% end
555% end
556% end
557% end
558% end
559%
560% statefulNodesClasses = [];
561% for ind=getIndexStatefulNodes(self)
562% statefulNodesClasses(end+1:end+K)= ((ind-1)*K+1):(ind*K);
563% end
564%
565% % we now generate the node routing matrix for the given state and then
566% % lump the states for non-stateful nodes so that run gives the routing
567% % table for stateful nodes only
568% statefulNodesClasses = [];
569% for ind=stateful
570% statefulNodesClasses(end+1:end+K)= ((ind-1)*K+1):(ind*K);
571% end
572%
573% if isStateDep
574% rtfunraw = @(state_before, state_after) dtmc_stochcomp(cell2mat(cellfun(@(f) f(state_before, state_after), rnodefuncell,'UniformOutput',false)), statefulNodesClasses);
575% rtfun = rtfunraw;
576% %rtfun = memoize(rtfunraw); % memoize to reduce the number of stoch comp calls
577% %rtfun.CacheSize = 6000^2;
578% else
579% rtfun = @(state_before, state_after) dtmc_stochcomp(rtnodes, statefulNodesClasses);
580% end
581%
582% nchains = size(chains,1);
583% inchain = cell(1,nchains);
584% for c=1:nchains
585% inchain{c} = find(chains(c,:));
586% end
587%
588% sn.rt = rt;
589% sn.rtnodes = rtnodes;
590% sn.rtfun = rtfun;
591% sn.chains = chains;
592% sn.nchains = nchains;
593% sn.inchain = inchain;
594% for c=1:sn.nchains
595% if range(sn.refstat(inchain{c}))>0
596% line_error(mfilename,sprintf('Classes within chain %d (classes: %s) have different reference stations.',c,mat2str(find(sn.chains(c,:)))));
597% end
598% end
599% self.sn = sn;
600%
601% function p = sub_rr(ind, jnd, r, s, linksmat, state_before, state_after)
602% % P = SUB_RR(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER)
603%
604% R = sn.nclasses;
605% isf = sn.nodeToStateful(ind);
606% if isempty(state_before{isf})
607% p = min(linksmat(ind,jnd),1);
608% else
609% if r==s
610% p = double(state_after{isf}(end-R+r)==jnd);
611% else
612% p = 0;
613% end
614% end
615% end
616%
617% function p = sub_wrr(ind, jnd, r, s, linksmat, state_before, state_after)
618% % P = SUB_WRR(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER)
619%
620% R = sn.nclasses;
621% isf = sn.nodeToStateful(ind);
622% if isempty(state_before{isf})
623% p = min(linksmat(ind,jnd),1);
624% else
625% if r==s
626% p = double(state_after{isf}(end-R+r)==jnd);
627% else
628% p = 0;
629% end
630% end
631% end
632%
633% function p = sub_jsq(ind, jnd, r, s, linksmat, state_before, state_after) %#ok<INUSD>
634% % P = SUB_JSQ(IND, JND, R, S, LINKSMAT, STATE_BEFORE, STATE_AFTER) %#OK<INUSD>
635%
636% isf = sn.nodeToStateful(ind);
637% if isempty(state_before{isf})
638% p = min(linksmat(ind,jnd),1);
639% else
640% if r==s
641% n = Inf*ones(1,sn.nnodes);
642% for knd=1:sn.nnodes
643% if linksmat(ind,knd)
644% ksf = sn.nodeToStateful(knd);
645% n(knd) = State.toMarginal(sn, knd, state_before{ksf});
646% end
647% end
648% if n(jnd) == min(n)
649% p = 1 / sum(n == min(n));
650% else
651% p = 0;
652% end
653% else
654% p = 0;
655% end
656% end
657% end
658%
659% end
Definition mmt.m:124