LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
Queue.m
1classdef Queue < ServiceStation
2 % A service station with queueing
3 %
4 % Copyright (c) 2012-2026, Imperial College London
5 % All rights reserved.
6
7 properties
8 setupTime;
9 delayoffTime;
10 switchoverTime;
11 pollingType;
12 pollingPar;
13 impatienceTypes;
14 % Balking properties
15 balkingStrategies; % Cell array: per-class BalkingStrategy constant
16 balkingThresholds; % Cell array: per-class balking thresholds (list of {minJobs, maxJobs, probability})
17 % Retrial properties
18 retrialDelays; % Cell array: per-class retrial delay distributions
19 retrialMaxAttempts; % Array: per-class max retrial attempts (-1 = unlimited)
20 retrialPolicies; % Array: per-class RetrialPolicy (LINEAR = per-customer rate, CONSTANT = orbit-wide rate)
21 orbitMaxJobs; % Array: per-class orbit capacity (-1 = unbounded)
22 orbitImpatienceDistributions; % Cell array: per-class orbit abandonment distributions
23 batchRejectProb; % Array: per-class batch rejection probability [0,1]
24 % Server breakdown / repair properties
25 breakdownFailure; % Distribution: time to failure of the server (runs while up, busy or idle)
26 breakdownRepair; % Distribution: repair time of the server
27 breakdownDownService; % Cell array: per-class service distribution used while the server is down ([] = no service)
28 % Heterogeneous server properties
29 serverTypes; % Cell array of ServerType objects
30 heteroSchedPolicy; % HeteroSchedPolicy for server assignment
31 heteroServiceDistributions; % containers.Map: ServerType -> (containers.Map: JobClass -> Distribution)
32 % Immediate feedback property
33 immediateFeedback; % Cell array of class indices, or 'all' for all classes
34 % Pass-and-swap properties
35 swapGraph; % (nclasses x nclasses) class-compatibility/swap graph for PAS scheduling
36 svcRateFun; % function handle mu(c): total service rate as a function of the ordered state vector c (PAS scheduling)
37 end
38
39 methods
40 %Constructor
41 function self = Queue(model, name, schedStrategy)
42 % SELF = QUEUE(MODEL, NAME, SCHEDSTRATEGY)
43
44 self@ServiceStation(name);
45
46 if model.isMatlabNative()
47 classes = model.getClasses();
48 self.input = Buffer(classes);
49 self.output = Dispatcher(classes);
50 self.schedPolicy = SchedStrategyType.PR;
51 self.schedStrategy = SchedStrategy.PS;
52 self.serviceProcess = {};
53 self.server = Server(classes);
54 self.numberOfServers = 1;
55 self.schedStrategyPar = zeros(1,length(model.getClasses()));
56 self.setModel(model);
57 self.model.addNode(self);
58 self.dropRule = [];
59 self.obj = [];
60 self.setupTime = {};
61 self.delayoffTime = {};
62 self.pollingType = {};
63 self.switchoverTime = {};
64 self.patienceDistributions = {};
65 self.impatienceTypes = {};
66 self.balkingStrategies = {};
67 self.balkingThresholds = {};
68 self.retrialDelays = {};
69 self.retrialMaxAttempts = [];
70 self.orbitImpatienceDistributions = {};
71 self.batchRejectProb = [];
72 self.serverTypes = {};
73 self.heteroSchedPolicy = HeteroSchedPolicy.ORDER;
74 self.heteroServiceDistributions = containers.Map();
75 self.immediateFeedback = {};
76 self.swapGraph = [];
77 self.svcRateFun = [];
78
79 if nargin>=3 %exist('schedStrategy','var')
80 self.schedStrategy = schedStrategy;
81 switch SchedStrategy.toId(self.schedStrategy)
82 case {SchedStrategy.PS, SchedStrategy.DPS,SchedStrategy.GPS, SchedStrategy.PSPRIO, SchedStrategy.DPSPRIO,SchedStrategy.GPSPRIO, SchedStrategy.LPS}
83 self.schedPolicy = SchedStrategyType.PR;
84 self.server = SharedServer(classes);
85 case {SchedStrategy.LCFSPR, SchedStrategy.LCFSPRPRIO, SchedStrategy.FCFSPR, SchedStrategy.FCFSPRPRIO, SchedStrategy.LCFSPI, SchedStrategy.LCFSPIPRIO, SchedStrategy.FCFSPI, SchedStrategy.FCFSPIPRIO, SchedStrategy.EDF}
86 self.schedPolicy = SchedStrategyType.PR;
87 self.server = PreemptiveServer(classes);
88 case {SchedStrategy.FCFS, SchedStrategy.LCFS, SchedStrategy.SIRO, SchedStrategy.SEPT, SchedStrategy.LEPT, SchedStrategy.SJF, SchedStrategy.LJF, SchedStrategy.EDD, SchedStrategy.SRPT, SchedStrategy.SRPTPRIO, SchedStrategy.PSJF, SchedStrategy.FB, SchedStrategy.LRPT, SchedStrategy.SETF, SchedStrategy.FSP}
89 self.schedPolicy = SchedStrategyType.NP;
90 self.server = Server(classes);
91 case SchedStrategy.PAS
92 % Pass-and-swap: non-preemptive order-independent queue whose class compatibility/swap graph defaults to complete (materialized at struct refresh) unless set via setSwapGraph.
93 self.schedPolicy = SchedStrategyType.NP;
94 self.server = Server(classes);
95 case SchedStrategy.OI
96 % Order-independent: pass-and-swap specialization whose swap graph is always zero (empty). Class order is preserved on completion; only the rank rate mu(supp(c)) matters.
97 self.schedPolicy = SchedStrategyType.NP;
98 self.server = Server(classes);
99 case SchedStrategy.INF
100 self.schedPolicy = SchedStrategyType.NP;
101 self.server = InfiniteServer(classes);
102 self.numberOfServers = Inf;
103 case {SchedStrategy.HOL, SchedStrategy.FCFSPRIO, SchedStrategy.LCFSPRIO}
104 self.schedPolicy = SchedStrategyType.NP;
105 self.server = Server(classes);
106 case SchedStrategy.POLLING
107 self.schedPolicy = SchedStrategyType.NP;
108 self.server = PollingServer(classes);
109 otherwise
110 line_error(mfilename,sprintf('The specified scheduling strategy (%s) is unsupported.',schedStrategy));
111 end
112 end
113 elseif model.isJavaNative()
114 self.setModel(model);
115 self.schedStrategy = schedStrategy; % keep MATLAB-side property in sync with the Java obj
116 switch SchedStrategy.toId(schedStrategy)
117 case SchedStrategy.INF
118 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.INF);
119 case SchedStrategy.FCFS
120 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.FCFS);
121 case SchedStrategy.LCFS
122 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.LCFS);
123 case SchedStrategy.SIRO
124 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.SIRO);
125 case SchedStrategy.SJF
126 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.SJF);
127 case SchedStrategy.LJF
128 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.LJF);
129 case SchedStrategy.PS
130 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.PS);
131 case SchedStrategy.PSPRIO
132 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.PSPRIO);
133 case SchedStrategy.DPS
134 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.DPS);
135 case SchedStrategy.DPSPRIO
136 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.DPSPRIO);
137 case SchedStrategy.GPS
138 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.GPS);
139 case SchedStrategy.GPSPRIO
140 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.GPSPRIO);
141 case SchedStrategy.SEPT
142 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.SEPT);
143 case SchedStrategy.LEPT
144 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.LEPT);
145 case SchedStrategy.SRPT
146 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.SRPT);
147 case SchedStrategy.SRPTPRIO
148 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.SRPTPRIO);
149 case SchedStrategy.PSJF
150 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.PSJF);
151 case SchedStrategy.FB
152 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.FB);
153 case SchedStrategy.LRPT
154 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.LRPT);
155 case SchedStrategy.HOL
156 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.HOL);
157 case SchedStrategy.FORK
158 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.FORK);
159 case SchedStrategy.EXT
160 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.EXT);
161 case SchedStrategy.REF
162 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.REF);
163 case SchedStrategy.LCFSPR
164 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.LCFSPR);
165 case SchedStrategy.FCFSPR
166 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.FCFSPR);
167 case SchedStrategy.FCFSPRPRIO
168 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.FCFSPRPRIO);
169 case SchedStrategy.EDD
170 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.EDD);
171 case SchedStrategy.EDF
172 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.EDF);
173 case SchedStrategy.LPS
174 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.LPS);
175 case SchedStrategy.SETF
176 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.SETF);
177 case SchedStrategy.FSP
178 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.FSP);
179 end
180 self.obj.setNumberOfServers(1);
181 self.index = model.obj.getNodeIndex(self.obj);
182 end
183 end
184
185 function setSwapGraph(self, graph)
186 % SETSWAPGRAPH(GRAPH)
187 %
188 % Sets the class compatibility/swap graph for a pass-and-swap (PAS)
189 % queue. GRAPH is an (nclasses x nclasses) matrix whose (r,s) entry
190 % is nonzero iff, upon completion of a class-r job, a waiting class-s
191 % job may swap into the freed position (order-independent service).
192 %
193 % Parameters:
194 % graph - (nclasses x nclasses) numeric/logical adjacency matrix
195
196 if SchedStrategy.toId(self.schedStrategy) == SchedStrategy.OI
197 % OI is PAS with an all-zero swap graph; a zero graph is a
198 % consistent no-op, only a nonzero graph is rejected.
199 if any(graph(:) ~= 0)
200 line_error(mfilename, 'setSwapGraph is not applicable to OI (order-independent) queues; their swap graph is always zero. Use SchedStrategy.PAS to configure a non-trivial swap graph.');
201 end
202 elseif SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.PAS
203 line_error(mfilename, 'setSwapGraph is only applicable to PAS (pass-and-swap) queues.');
204 end
205 K = length(self.model.getClasses());
206 if size(graph,1) ~= K || size(graph,2) ~= K
207 line_error(mfilename, sprintf('Swap graph must be a %dx%d matrix (nclasses x nclasses).', K, K));
208 end
209 self.swapGraph = double(graph);
210 end
211
212 function graph = getSwapGraph(self)
213 % GRAPH = GETSWAPGRAPH()
214 %
215 % Returns the (nclasses x nclasses) class compatibility/swap graph
216 % for a pass-and-swap (PAS) queue, or [] if not configured.
217
218 graph = self.swapGraph;
219 end
220
221 function setServiceRateFunction(self, muFun)
222 % SETSERVICERATEFUNCTION(MUFUNCTION)
223 %
224 % Sets the total service rate function mu(c) of a pass-and-swap (PAS)
225 % queue. MUFUNCTION is a function handle taking the ordered state
226 % vector c (a row vector of class indices, c(1)=oldest job) and
227 % returning the scalar total service rate mu(c). The rate allocated
228 % to the job in position i is the increment
229 % Delta_mu(c(1..i)) = mu(c(1..i)) - mu(c(1..i-1)).
230 %
231 % An order-independent/PAS queue is parameterized by mu(c) as a whole
232 % and does not support per-class service distributions.
233
234 if SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.PAS && SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.OI
235 line_error(mfilename, 'setServiceRateFunction is only applicable to PAS (pass-and-swap) and OI (order-independent) queues.');
236 end
237 if ~isa(muFun, 'function_handle')
238 line_error(mfilename, 'PAS queues require a service rate function handle mu(c); per-class service distributions are not supported. Use setService(@(c) ...).');
239 end
240 if ~isempty(self.obj)
241 line_error(mfilename, 'PAS scheduling is currently supported only in the MATLAB-native codebase.');
242 end
243 self.svcRateFun = muFun;
244 % Derive a representative per-class rate mu([r]) (single class-r job)
245 % so the standard rate/process machinery (refreshRates, procid) stays
246 % consistent; the authoritative service description remains mu(c).
247 classes = self.model.getClasses();
248 server = self.server;
249 for r = 1:length(classes)
250 rate_r = muFun(r);
251 if isfinite(rate_r) && rate_r > 0
252 dist = Exp(rate_r);
253 else
254 dist = Disabled.getInstance();
255 end
256 if length(self.classCap) < r
257 self.classCap((length(self.classCap)+1):r) = Inf;
258 end
259 self.setStrategyParam(classes{r}, 1.0);
260 % see _kb/04-networkstruct.md (node/process construction notes) for rationale
261 server.serviceProcess{1, r}{2} = ServiceStrategy.LI;
262 server.serviceProcess{1, r}{3} = dist;
263 self.serviceProcess{r} = dist;
264 end
265 self.model.setInitialized(false);
266 self.state = [];
267 end
268
269 function muFun = getServiceRateFunction(self)
270 % MUFUNCTION = GETSERVICERATEFUNCTION()
271 %
272 % Returns the total service rate function mu(c) of a pass-and-swap
273 % (PAS) queue, or [] if not configured.
274
275 muFun = self.svcRateFun;
276 end
277
278 function [ok, badc, partial] = checkPermInvariance(self, Nvec, cap)
279 % [OK, BADC, PARTIAL] = CHECKPERMINVARIANCE(NVEC, CAP)
280 %
281 % Checks the order-independence (OI) condition on the service rate
282 % mu(c): the rate of the job in position j must depend only on the
283 % jobs at or ahead of it (positions 1..j) and not on the jobs behind
284 % it. Since the position-j rate is the prefix increment
285 % Delta_mu(c1..cj) = mu(c1..cj) - mu(c1..c_{j-1}), tail-independence
286 % is structural; the substantive requirement is that this increment
287 % be independent of the ORDER of the jobs ahead, which (by induction
288 % on prefix length) is equivalent to mu(c) being permutation-
289 % invariant, i.e. a function of the multiset of present jobs only.
290 % This is what is verified, over the reachable microstates
291 % (per-class counts bounded by the population NVEC and total by the
292 % station capacity CAP). Returns OK=false and the offending sorted
293 % microstate BADC if a violation is found. When the reachable
294 % population is too large to enumerate exhaustively, only a subset of
295 % microstates is verified and PARTIAL is returned true.
296 ok = true; badc = []; partial = false;
297 muFun = self.svcRateFun;
298 if isempty(muFun), return, end
299 K = numel(Nvec);
300 tol = 1e-9;
301 PERM_ENUM = 5040; % enumerate all distinct permutations up to this
302 PERM_SAMPLE = 16; % permutations sampled per multiset above PERM_ENUM
303 LATTICE_BUDGET = 4096;
304 MAXEVAL = 50000; % total mu evaluations budget
305
306 % per-class count bound and total-length bound
307 ub = Nvec(:)';
308 hasOpen = any(~isfinite(ub));
309 if isfinite(cap) && cap >= 0 && cap < intmax
310 Lmax = cap;
311 else
312 Lmax = sum(ub(isfinite(ub)));
313 end
314 ub(~isfinite(ub)) = min(Lmax, 6); % open classes: sample bound
315 ub = min(ub, Lmax);
316 if ~isfinite(Lmax) || Lmax < 2, return, end
317
318 latSize = prod(ub + 1);
319 exhaustive = ~hasOpen && latSize <= LATTICE_BUDGET && isfinite(latSize);
320 neval = 0;
321 saved = rng; rng(0); % reproducible, no global side effect
322 try
323 if exhaustive
324 n = zeros(1, K); % odometer over count vectors
325 while true
326 if sum(n) >= 2 && nnz(n) >= 2 && sum(n) <= Lmax
327 [ok, badc, neval, partial] = local_test(n);
328 if ~ok, break, end
329 if neval >= MAXEVAL, partial = true; break, end
330 end
331 % increment odometer
332 d = 1;
333 while d <= K
334 n(d) = n(d) + 1;
335 if n(d) <= ub(d), break, end
336 n(d) = 0; d = d + 1;
337 end
338 if d > K, break, end
339 end
340 else
341 partial = true; % large / open population: sample
342 for trial = 1:400
343 len = 2 + randi(max(1, min(Lmax, 6) - 1)) - 1;
344 n = zeros(1, K);
345 for j = 1:len
346 r = randi(K);
347 if n(r) < ub(r), n(r) = n(r) + 1; end
348 end
349 if sum(n) >= 2 && nnz(n) >= 2
350 [ok, badc, neval] = local_test(n);
351 if ~ok, break, end
352 if neval >= MAXEVAL, break, end
353 end
354 end
355 end
356 catch ME
357 rng(saved); rethrow(ME);
358 end
359 rng(saved);
360
361 function [ok_, badc_, neval_, partial_] = local_test(nc)
362 % Test permutation invariance of mu over the multiset nc.
363 ok_ = true; badc_ = []; partial_ = partial;
364 c0 = repelem(1:K, nc); len_ = numel(c0);
365 dcount = round(exp(gammaln(len_ + 1) - sum(gammaln(nc(nc > 0) + 1))));
366 base = muFun(c0); neval_ = neval + 1;
367 if dcount <= PERM_ENUM
368 P = ms_perms(c0); % all distinct permutations
369 else
370 partial_ = true;
371 P = zeros(PERM_SAMPLE, len_);
372 P(1, :) = c0(len_:-1:1); % reversal
373 for t = 2:PERM_SAMPLE, P(t, :) = c0(randperm(len_)); end
374 end
375 for t = 1:size(P, 1)
376 v = muFun(P(t, :)); neval_ = neval_ + 1;
377 if abs(v - base) > tol * max(1, abs(base))
378 ok_ = false; badc_ = c0; return
379 end
380 end
381 end
382
383 function P = ms_perms(c0)
384 % Distinct permutations of the multiset c0 (dcount <= PERM_CAP).
385 if numel(c0) <= 1, P = c0; return, end
386 u = unique(c0); P = [];
387 for ii = 1:numel(u)
388 rest = c0; pos = find(rest == u(ii), 1); rest(pos) = [];
389 sub = ms_perms(rest);
390 P = [P; [repmat(u(ii), size(sub, 1), 1), sub]]; %#ok<AGROW>
391 end
392 end
393 end
394
395 function setLoadDependence(self, alpha)
396 switch SchedStrategy.toId(self.schedStrategy)
397 case {SchedStrategy.PS, SchedStrategy.FCFS}
398 setLimitedLoadDependence(self, alpha);
399 otherwise
400 line_error(mfilename,'Load-dependence supported only for processor sharing (PS) and first-come first-serve (FCFS) stations.');
401 end
402 end
403
404 function setClassDependence(self, beta, peakRatePerClass)
405 % SETCLASSDEPENDENCE(self, beta, peakRatePerClass)
406 % beta(ni) is the class-dependent service-rate scaling handle.
407 % peakRatePerClass (REQUIRED) is the peak rate scaling per class
408 % (scalar broadcast to all classes) used to normalize Util = T*S/peak.
409 if nargin < 3
410 peakRatePerClass = [];
411 end
412 switch SchedStrategy.toId(self.schedStrategy)
413 case {SchedStrategy.PS, SchedStrategy.FCFS}
414 setLimitedClassDependence(self, beta, peakRatePerClass);
415 otherwise
416 line_error(mfilename,'Class-dependence supported only for processor sharing (PS) and first-come first-serve (FCFS) stations.');
417 end
418 end
419
420 function setJointDependence(self, eta, peakRatePerClass)
421 % SETJOINTDEPENDENCE(self, eta, peakRatePerClass)
422 % eta(ni) is the joint-dependent service-rate scaling handle,
423 % where ni=[ni1,...,niR] is the joint per-class population at the
424 % station. It returns a scalar (shared across all classes) or a
425 % length-R per-class vector. This is the NON-product-form case
426 % (e.g. min(ni(1),c)); use setClassDependence for the product-form
427 % beta_{i,r}(n_{i,r}). peakRatePerClass (REQUIRED) normalizes
428 % Util = T*S/peak.
429 if nargin < 3
430 peakRatePerClass = [];
431 end
432 switch SchedStrategy.toId(self.schedStrategy)
433 case {SchedStrategy.PS, SchedStrategy.FCFS}
434 setLimitedJointDependence(self, eta, peakRatePerClass);
435 otherwise
436 line_error(mfilename,'Joint-dependence supported only for processor sharing (PS) and first-come first-serve (FCFS) stations.');
437 end
438 end
439
440
441
442 function setNumberOfServers(self, value)
443 % SETNUMBEROFSERVERS(VALUE)
444 if isempty(self.obj)
445 switch SchedStrategy.toId(self.schedStrategy)
446 case SchedStrategy.INF
447 %line_warning(mfilename,'A request to change the number of servers in an infinite server node has been ignored.');
448 %ignore
449 otherwise
450 self.setNumServers(value);
451 end
452 else
453 self.obj.setNumberOfServers(value);
454 end
455 end
456
457 function setNumServers(self, value)
458 % SETNUMSERVERS(VALUE)
459 if isempty(self.obj)
460 switch SchedStrategy.toId(self.schedStrategy)
461 case {SchedStrategy.DPS, SchedStrategy.GPS}
462 if value ~= 1
463 line_error(mfilename,sprintf('Cannot use multi-server stations with %s scheduling.', self.schedStrategy));
464 end
465 otherwise
466 self.numberOfServers = value;
467 end
468 else
469 self.obj.setNumberOfServers(value);
470 end
471 end
472
473 function self = setStrategyParam(self, class, weight)
474 % SELF = SETSTRATEGYPARAM(CLASS, WEIGHT)
475
476 % For LPS scheduling, schedStrategyPar(1) stores the limit set via setLimit()
477 % Don't overwrite it with the default weight
478 if SchedStrategy.toId(self.schedStrategy) == SchedStrategy.LPS
479 % For LPS, only set weight if explicitly provided (not default 1.0)
480 % or if this is not the first class (which would overwrite the limit)
481 if class.index == 1 && weight == 1.0 && ~isempty(self.schedStrategyPar) && self.schedStrategyPar(1) > 1
482 % Preserve the LPS limit, don't overwrite with default weight
483 return;
484 end
485 end
486 self.schedStrategyPar(class.index) = weight;
487 end
488
489 function distribution = getService(self, class)
490 % DISTRIBUTION = GETSERVICE(CLASS)
491
492 % return the service distribution assigned to the given class
493 if nargin<2 %~exist('class','var')
494 for s = 1:length(self.model.getClasses())
495 classes = self.model.getClasses();
496 distribution{s} = self.server.serviceProcess{1, classes{s}}{3};
497 end
498 else
499 try
500 distribution = self.server.serviceProcess{1, class.index}{3};
501 catch ME
502 distribution = [];
503 line_warning(mfilename,'No distribution is available for the specified class.\n');
504 end
505 end
506 end
507
508 function setService(self, class, distribution, weight)
509 % SETSERVICE(CLASS, DISTRIBUTION, WEIGHT)
510 % distribution can be a Distribution object or a Workflow object
511 %
512 % SETSERVICE(MUFUNCTION) on a pass-and-swap (PAS) queue
513 % An order-independent/PAS queue is parameterized by a single total
514 % service rate function mu(c) of the ordered state vector c (the row
515 % vector of class indices, c(1)=oldest job), not by per-class service
516 % distributions. The rate allocated to position i is the increment
517 % Delta_mu(c(1..i)) = mu(c(1..i)) - mu(c(1..i-1)).
518
519 if SchedStrategy.toId(self.schedStrategy) == SchedStrategy.PAS || SchedStrategy.toId(self.schedStrategy) == SchedStrategy.OI
520 self.setServiceRateFunction(class);
521 return;
522 end
523
524 if nargin<4 %~exist('weight','var')
525 weight=1.0;
526 end
527
528 % If Workflow, convert to PH distribution
529 if isa(distribution, 'Workflow')
530 distribution = distribution.toPH();
531 end
532
533 if distribution.isImmediate()
534 distribution = Immediate.getInstance();
535 end
536 if isa(class,'SelfLoopingClass') && class.refstat.index ~= self.index && ~isa(distribution,'Disabled')
537 line_error(mfilename, 'For a self-looping class, service cannot be set on stations other than the reference station of the class.');
538 end
539 if isempty(self.obj)
540 server = self.server; % by reference
541 c = class.index;
542 if length(server.serviceProcess) >= c && ~isempty(server.serviceProcess{1,c}) % if the distribution was already configured
543 % this is a forced state reset in case for example the number of phases changes
544 % appears to run faster without checks, probably due to
545 % isa being slow
546 %oldDistribution = server.serviceProcess{1, c}{3};
547 %isOldMarkovian = isa(oldDistribution,'Markovian');
548 %isNewMarkovian = isa(distribution,'Markovian');
549 %if distribution.getNumParams ~= oldDistribution.getNumParams
550 % %|| (isOldMarkovian && ~isNewMarkovian) || (~isOldMarkovian && isNewMarkovian) || (isOldMarkovian && isNewMarkovian && distribution.getNumberOfPhases ~= oldDistribution.getNumberOfPhases)
551 self.model.setInitialized(false); % this is a better way to invalidate to avoid that sequential calls to setService all trigger an initDefault
552 % Note: We no longer invalidate hasStruct here as it causes severe performance
553 % issues in iterative solvers like LN. The refreshRates/refreshProcesses methods
554 % called during solver post-iteration phase handle updating procid appropriately.
555 self.state=[]; % reset the state vector
556 %end
557 else % if first configuration
558 if length(self.classCap) < c
559 self.classCap((length(self.classCap)+1):c) = Inf;
560 end
561 self.setStrategyParam(class, weight);
562 % see _kb/04-networkstruct.md (node/process construction notes) for rationale
563 server.serviceProcess{1, c}{2} = ServiceStrategy.LI;
564 end
565 server.serviceProcess{1, c}{3} = distribution;
566 self.serviceProcess{c} = distribution;
567 % Update cached procid if struct exists to avoid stale values
568 % This is needed because we don't invalidate hasStruct for performance
569 if self.model.hasStruct && ~isempty(self.model.sn)
570 ist = self.model.getStationIndex(self);
571 procTypeId = ProcessType.toId(ProcessType.fromText(builtin('class', distribution)));
572 self.model.sn.procid(ist, c) = procTypeId;
573 end
574 else
575 self.obj.setService(class.obj, distribution.obj, weight);
576 % Also update MATLAB-side storage to keep in sync with Java object
577 % This ensures getService returns the correct distribution
578 c = class.index;
579 self.serviceProcess{c} = distribution;
580 end
581 end
582
583 function setItemServiceRate(self, cache, jobinClass, item, serviceRate)
584 % SETITEMSERVICERATE(cache, jobinClass, item, serviceRate)
585 % Override, at this queue, the retrieval service rate for a single
586 % item of the read class jobinClass in the given cache's retrieval
587 % system. The default (when not overridden) is the read class's own
588 % service distribution at this queue. item is 1-based.
589 rClassIdx = cache.server.retrievalClasses(item, jobinClass.index);
590 if rClassIdx <= 0
591 line_error(mfilename,'No retrieval class defined for the given class/item; call setRetrievalSystem first.');
592 end
593 rClass = self.model.classes{rClassIdx};
594 self.setService(rClass, Exp(serviceRate));
595 end
596
597 function setDelayOff(self, jobclass, setupTime, delayoffTime)
598 c = jobclass.index;
599 self.setupTime{1, c} = setupTime;
600 self.delayoffTime{1, c} = delayoffTime;
601 end
602
603 function dist = getSetupTime(self, jobclass)
604 c = jobclass.index;
605 if c <= length(self.setupTime) && ~isempty(self.setupTime{1, c})
606 dist = self.setupTime{1, c};
607 else
608 dist = [];
609 end
610 end
611
612 function dist = getDelayOffTime(self, jobclass)
613 c = jobclass.index;
614 if c <= length(self.delayoffTime) && ~isempty(self.delayoffTime{1, c})
615 dist = self.delayoffTime{1, c};
616 else
617 dist = [];
618 end
619 end
620
621 function setSwitchover(self, varargin)
622 if isempty(self.switchoverTime)
623 if SchedStrategy.toId(self.schedStrategy) == SchedStrategy.POLLING
624 self.switchoverTime = cell(1,length(self.model.getClasses()));
625 else
626 K = length(self.model.getClasses());
627 self.switchoverTime = cell(K,K);
628 for r=1:K
629 for s=1:K
630 self.switchoverTime{r,s} = Immediate();
631 end
632 end
633 end
634 end
635 if length(varargin)==2
636 jobclass = varargin{1};
637 soTime = varargin{2};
638 % time to switch from queue i to the next one
639 if SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.POLLING
640 line_error(mfilename,'setSwitchover(jobclass, distrib) can only be invoked on queues with SchedStrategy.POLLING.\n');
641 end
642 c = jobclass.index;
643 self.switchoverTime{1,c} = soTime;
644 elseif length(varargin)==3
645 jobclass_from = varargin{1};
646 jobclass_to = varargin{2};
647 soTime = varargin{3};
648 f = jobclass_from.index;
649 t = jobclass_to.index;
650 self.switchoverTime{f,t} = soTime;
651 end
652 end
653
654 function setPollingType(self, rule, par)
655 if PollingType.toId(rule) ~= PollingType.KLIMITED
656 par = [];
657 elseif PollingType.toId(rule) == PollingType.KLIMITED && nargin<3
658 line_error(mfilename,'K-Limited polling requires to specify the parameter K, e.g., setPollingType(PollingType.KLIMITED, 2).\n');
659 end
660 % support only identical polling type at each class buffer
661 if SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.POLLING
662 line_error(mfilename,'setPollingType can only be invoked on queues with SchedStrategy.POLLING.\n');
663 end
664 for r=1:length(self.model.getClasses())
665 self.pollingType{1,r} = rule;
666 self.pollingPar = par;
667 classes = self.model.getClasses();
668 setSwitchover(self, classes{r}, Immediate());
669 end
670 end
671
672 function setPatience(self, class, varargin)
673 % SETPATIENCE(CLASS, DISTRIBUTION) - Backwards compatible
674 % SETPATIENCE(CLASS, PATIENCETYPE, DISTRIBUTION) - Explicit type
675 %
676 % Sets the patience type and distribution for a specific job class at this queue.
677 % Jobs that wait longer than their patience time will abandon the queue.
678 %
679 % Parameters:
680 % class - JobClass object
681 % impatienceType - (Optional) ImpatienceType constant (RENEGING or BALKING)
682 % If omitted, defaults to ImpatienceType.RENEGING
683 % distribution - Any LINE distribution (Exp, Erlang, HyperExp, etc.)
684 % excluding modulated processes (BMAP, MAP, MMPP2)
685 %
686 % Note: This setting takes precedence over the global class patience.
687 %
688 % Examples:
689 % queue.setPatience(jobclass, Exp(0.2)) % Defaults to RENEGING
690 % queue.setPatience(jobclass, ImpatienceType.RENEGING, Exp(0.2))
691 % queue.setPatience(jobclass, ImpatienceType.BALKING, Exp(0.5))
692
693 % Handle backwards compatibility: 2 or 3 arguments
694 if length(varargin) == 1
695 % Old signature: setPatience(class, distribution)
696 distribution = varargin{1};
697 impatienceType = ImpatienceType.RENEGING; % Default to RENEGING
698 elseif length(varargin) == 2
699 % New signature: setPatience(class, impatienceType, distribution)
700 impatienceType = varargin{1};
701 distribution = varargin{2};
702 else
703 line_error(mfilename, 'Invalid number of arguments. Use setPatience(class, distribution) or setPatience(class, impatienceType, distribution)');
704 end
705
706 if isa(distribution, 'BMAP') || isa(distribution, 'MAP') || isa(distribution, 'DMAP') || isa(distribution, 'MMPP2')
707 line_error(mfilename, 'Modulated processes (BMAP, MAP, DMAP, MMPP2) are not supported for patience distributions.');
708 end
709
710 % Validate impatience type
711 if impatienceType ~= ImpatienceType.RENEGING && impatienceType ~= ImpatienceType.BALKING
712 line_error(mfilename, 'Invalid impatience type. Use ImpatienceType.RENEGING or ImpatienceType.BALKING.');
713 end
714
715 % Only RENEGING is currently supported
716 if impatienceType == ImpatienceType.BALKING
717 line_error(mfilename, 'BALKING impatience type is not yet supported. Use ImpatienceType.RENEGING.');
718 end
719
720 if distribution.isImmediate()
721 distribution = Immediate.getInstance();
722 end
723
724 if isempty(self.obj)
725 c = class.index;
726 self.patienceDistributions{1, c} = distribution;
727 self.impatienceTypes{1, c} = impatienceType;
728 else
729 self.obj.setPatience(class.obj, impatienceType, distribution.obj);
730 end
731 end
732
733 function distribution = getPatience(self, class)
734 % DISTRIBUTION = GETPATIENCE(CLASS)
735 %
736 % Returns the patience distribution for a specific job class.
737 % Returns the queue-specific setting if available, otherwise
738 % falls back to the global class patience.
739 %
740 % Parameters:
741 % class - JobClass object
742 %
743 % Returns:
744 % distribution - The patience distribution, or [] if not set
745
746 if isempty(self.obj)
747 c = class.index;
748 % Check queue-specific patience first
749 if c <= length(self.patienceDistributions) && ~isempty(self.patienceDistributions{1, c})
750 distribution = self.patienceDistributions{1, c};
751 else
752 % Fall back to global class patience
753 distribution = class.getPatience();
754 end
755 else
756 distObj = self.obj.getPatience(class.obj);
757 if isempty(distObj)
758 distribution = [];
759 else
760 distribution = Distribution.fromJavaObject(distObj);
761 end
762 end
763 end
764
765 function setLimit(self, limit)
766 % SETLIMIT(LIMIT) Sets the maximum number of jobs for LPS scheduling
767 %
768 % Parameters:
769 % limit - Maximum number of jobs in PS (processor sharing) mode for LPS
770
771 if isempty(self.obj)
772 % MATLAB native implementation - store as a scheduling parameter
773 if SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.LPS
774 line_warning(mfilename, 'setLimit is only applicable to LPS (Least Progress Scheduling) queues.');
775 return;
776 end
777 % Store limit in schedStrategyPar (use index 0 for queue-level parameter)
778 if length(self.schedStrategyPar) < 1
779 self.schedStrategyPar = zeros(1, 1);
780 end
781 self.schedStrategyPar(1) = limit;
782 else
783 % JavaNative implementation
784 if SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.LPS
785 line_warning(mfilename, 'setLimit is only applicable to LPS (Least Progress Scheduling) queues.');
786 return;
787 end
788 self.obj.setLimit(limit);
789 end
790 end
791
792 function limit = getLimit(self)
793 % LIMIT = GETLIMIT() Returns the maximum number of jobs for LPS scheduling
794 %
795 % Returns:
796 % limit - Maximum number of jobs in PS mode for LPS
797
798 if isempty(self.obj)
799 % MATLAB native implementation
800 if length(self.schedStrategyPar) >= 1
801 limit = self.schedStrategyPar(1);
802 else
803 limit = [];
804 end
805 else
806 % JavaNative implementation
807 limit = self.obj.getLimit();
808 end
809 end
810
811 function impatienceType = getImpatienceType(self, class)
812 % IMPATIENCETYPE = GETIMPATIENCETYPE(CLASS)
813 %
814 % Returns the impatience type for a specific job class.
815 % Returns the queue-specific setting if available, otherwise
816 % falls back to the global class impatience type.
817 %
818 % Parameters:
819 % class - JobClass object
820 %
821 % Returns:
822 % impatienceType - The impatience type (ImpatienceType constant), or [] if not set
823
824 if isempty(self.obj)
825 c = class.index;
826 % Check queue-specific impatience type first
827 if c <= length(self.impatienceTypes) && ~isempty(self.impatienceTypes{1, c})
828 impatienceType = self.impatienceTypes{1, c};
829 else
830 % Fall back to global class impatience type
831 impatienceType = class.getImpatienceType();
832 end
833 else
834 impatienceTypeId = self.obj.getImpatienceType(class.obj);
835 if isempty(impatienceTypeId)
836 impatienceType = [];
837 else
838 impatienceType = ImpatienceType.fromId(impatienceTypeId.getID());
839 end
840 end
841 end
842
843 function tf = hasPatience(self, class)
844 % TF = HASPATIENCE(CLASS)
845 %
846 % Returns true if this class has patience configured at this queue
847 % (either locally or globally).
848
849 dist = self.getPatience(class);
850 tf = ~isempty(dist) && ~isa(dist, 'Disabled');
851 end
852
853 function setBalking(self, class, strategy, thresholds)
854 % SETBALKING(CLASS, STRATEGY, THRESHOLDS)
855 %
856 % Configures balking behavior for a specific job class at this queue.
857 % When a customer arrives, they may refuse to join based on queue length.
858 %
859 % Parameters:
860 % class - JobClass object
861 % strategy - BalkingStrategy constant:
862 % QUEUE_LENGTH - Balk based on current queue length
863 % EXPECTED_WAIT - Balk based on expected waiting time
864 % COMBINED - Both conditions (OR logic)
865 % thresholds - Cell array of balking thresholds, each element is:
866 % {minJobs, maxJobs, probability}
867 % where probability is the chance to balk when queue
868 % length is in [minJobs, maxJobs] range.
869 %
870 % Example:
871 % % Balk with 30% probability when 5-10 jobs in queue,
872 % % 80% when 11-20 jobs, 100% when >20 jobs
873 % queue.setBalking(jobclass, BalkingStrategy.QUEUE_LENGTH, ...
874 % {{5, 10, 0.3}, {11, 20, 0.8}, {21, Inf, 1.0}});
875
876 if isempty(self.obj)
877 c = class.index;
878 self.balkingStrategies{1, c} = strategy;
879 self.balkingThresholds{1, c} = thresholds;
880 else
881 % Java native - convert thresholds to Java format
882 jThresholds = jline.util.BalkingThresholdList();
883 for i = 1:length(thresholds)
884 th = thresholds{i};
885 minJobs = th{1};
886 maxJobs = th{2};
887 if isinf(maxJobs)
888 maxJobs = java.lang.Integer.MAX_VALUE;
889 end
890 probability = th{3};
891 jThresholds.add(jline.lang.BalkingThreshold(minJobs, maxJobs, probability));
892 end
893 % Convert strategy to Java enum
894 switch strategy
895 case BalkingStrategy.QUEUE_LENGTH
896 jStrategy = jline.lang.constant.BalkingStrategy.QUEUE_LENGTH;
897 case BalkingStrategy.EXPECTED_WAIT
898 jStrategy = jline.lang.constant.BalkingStrategy.EXPECTED_WAIT;
899 case BalkingStrategy.COMBINED
900 jStrategy = jline.lang.constant.BalkingStrategy.COMBINED;
901 end
902 self.obj.setBalking(class.obj, jStrategy, jThresholds);
903 end
904 end
905
906 function [strategy, thresholds] = getBalking(self, class)
907 % [STRATEGY, THRESHOLDS] = GETBALKING(CLASS)
908 %
909 % Returns the balking configuration for a specific job class.
910 %
911 % Parameters:
912 % class - JobClass object
913 %
914 % Returns:
915 % strategy - BalkingStrategy constant, or [] if not configured
916 % thresholds - Cell array of {minJobs, maxJobs, probability} tuples
917
918 if isempty(self.obj)
919 c = class.index;
920 if c <= length(self.balkingStrategies) && ~isempty(self.balkingStrategies{1, c})
921 strategy = self.balkingStrategies{1, c};
922 thresholds = self.balkingThresholds{1, c};
923 else
924 strategy = [];
925 thresholds = {};
926 end
927 else
928 jStrategy = self.obj.getBalkingStrategy(class.obj);
929 if isempty(jStrategy)
930 strategy = [];
931 thresholds = {};
932 else
933 strategy = BalkingStrategy.fromId(jStrategy.getId());
934 jThresholds = self.obj.getBalkingThresholds(class.obj);
935 thresholds = {};
936 if ~isempty(jThresholds)
937 for i = 0:(jThresholds.size()-1)
938 jTh = jThresholds.get(i);
939 maxJobs = jTh.getMaxJobs();
940 if maxJobs == java.lang.Integer.MAX_VALUE
941 maxJobs = Inf;
942 end
943 thresholds{end+1} = {jTh.getMinJobs(), maxJobs, jTh.getProbability()};
944 end
945 end
946 end
947 end
948 end
949
950 function tf = hasBalking(self, class)
951 % TF = HASBALKING(CLASS)
952 %
953 % Returns true if this class has balking configured at this queue.
954
955 [strategy, ~] = self.getBalking(class);
956 tf = ~isempty(strategy);
957 end
958
959 function setRetrial(self, class, delayDistribution, maxAttempts)
960 % SETRETRIAL(CLASS, DELAYDISTRIBUTION, MAXATTEMPTS)
961 %
962 % Configures retrial behavior for a specific job class at this queue.
963 % When a customer is rejected (queue full), they move to an orbit
964 % and retry after a random delay.
965 %
966 % Parameters:
967 % class - JobClass object
968 % delayDistribution - Distribution for retrial delay (e.g., Exp(0.5))
969 % maxAttempts - Maximum number of retrial attempts:
970 % -1 = unlimited retries (default)
971 % N = drop after N failed attempts
972 %
973 % Example:
974 % % Retry with exponential delay, unlimited attempts
975 % queue.setRetrial(jobclass, Exp(0.5), -1);
976 %
977 % % Retry up to 3 times with Erlang delay
978 % queue.setRetrial(jobclass, Erlang(2, 0.3), 3);
979
980 if nargin < 4
981 maxAttempts = -1; % Unlimited by default
982 end
983
984 if isa(delayDistribution, 'BMAP') || isa(delayDistribution, 'MAP') || isa(delayDistribution, 'DMAP') || isa(delayDistribution, 'MMPP2')
985 line_error(mfilename, 'Modulated processes (BMAP, MAP, DMAP, MMPP2) are not supported for retrial delay distributions.');
986 end
987
988 if isempty(self.obj)
989 c = class.index;
990 self.retrialDelays{1, c} = delayDistribution;
991 % Ensure array is large enough
992 if length(self.retrialMaxAttempts) < c
993 self.retrialMaxAttempts(end+1:c) = -1;
994 end
995 self.retrialMaxAttempts(c) = maxAttempts;
996 % Also set drop rule to RETRIAL or RETRIAL_WITH_LIMIT
997 if maxAttempts < 0
998 self.dropRule(c) = DropStrategy.RETRIAL;
999 else
1000 self.dropRule(c) = DropStrategy.RETRIAL_WITH_LIMIT;
1001 end
1002 else
1003 self.obj.setRetrial(class.obj, delayDistribution.obj, maxAttempts);
1004 end
1005 end
1006
1007 function setOrbit(self, class, retrialDistribution, policy, maxOrbit)
1008 % SETORBIT(CLASS, RETRIALDISTRIBUTION)
1009 % SETORBIT(CLASS, RETRIALDISTRIBUTION, POLICY)
1010 % SETORBIT(CLASS, RETRIALDISTRIBUTION, POLICY, MAXORBIT)
1011 %
1012 % Declares this station to be a retrial queue for CLASS: a job that
1013 % finds every server busy joins an orbit and re-attempts entry after
1014 % a random delay, instead of waiting in a line.
1015 %
1016 % This is the first-class form of the retrial idiom. It removes the
1017 % waiting room itself (capacity = number of servers), which is what
1018 % makes the station bufferless, so the caller no longer has to know
1019 % that setCapacity(nservers) is the way to express "no waiting room,
1020 % blocked jobs orbit".
1021 %
1022 % Parameters:
1023 % class - JobClass object
1024 % retrialDistribution - retrial delay of an orbiting job
1025 % policy - RetrialPolicy.LINEAR (default): each
1026 % orbiting job retries at its own rate, so
1027 % the aggregate rate is (orbit size)*nu.
1028 % RetrialPolicy.CONSTANT: the orbit retries
1029 % as a whole at rate nu whenever non-empty.
1030 % maxOrbit - orbit capacity; -1 (default) leaves the
1031 % orbit unbounded. A job that finds the
1032 % orbit full is lost.
1033 %
1034 % The mean orbit length is reported by getAvgOrbit / the Orbit column
1035 % of the average table, so it need not be recovered as QLen - Util.
1036 %
1037 % Example:
1038 % % M/M/1 retrial queue with per-customer retrial rate 1.0
1039 % queue.setNumberOfServers(1);
1040 % queue.setOrbit(jobclass, Exp(1.0));
1041
1042 if nargin < 4 || isempty(policy)
1043 policy = RetrialPolicy.LINEAR;
1044 end
1045 if nargin < 5 || isempty(maxOrbit)
1046 maxOrbit = -1;
1047 end
1048 if ischar(policy) || isstring(policy)
1049 policy = RetrialPolicy.fromText(char(policy));
1050 end
1051 if policy ~= RetrialPolicy.LINEAR && policy ~= RetrialPolicy.CONSTANT
1052 line_error(mfilename, 'setOrbit requires a RetrialPolicy.LINEAR or RetrialPolicy.CONSTANT policy.');
1053 end
1054 if ~isnumeric(maxOrbit) || ~isscalar(maxOrbit) || (maxOrbit ~= -1 && (maxOrbit < 0 || maxOrbit ~= round(maxOrbit)))
1055 line_error(mfilename, 'setOrbit requires maxOrbit to be -1 (unbounded) or a non-negative integer.');
1056 end
1057
1058 % see _kb/04-networkstruct.md (node/process construction notes) for rationale
1059 nservers = self.getNumberOfServers();
1060 if isempty(nservers) || ~isfinite(nservers) || nservers < 1
1061 nservers = 1;
1062 self.setNumberOfServers(nservers);
1063 end
1064 if maxOrbit < 0
1065 self.setCapacity(Inf);
1066 else
1067 self.setCapacity(nservers + maxOrbit);
1068 end
1069
1070 self.setRetrial(class, retrialDistribution, -1);
1071
1072 if isempty(self.obj)
1073 c = class.index;
1074 if length(self.retrialPolicies) < c
1075 self.retrialPolicies(end+1:c) = RetrialPolicy.LINEAR;
1076 end
1077 self.retrialPolicies(c) = policy;
1078 if length(self.orbitMaxJobs) < c
1079 self.orbitMaxJobs(end+1:c) = -1;
1080 end
1081 self.orbitMaxJobs(c) = maxOrbit;
1082 elseif policy ~= RetrialPolicy.LINEAR || maxOrbit ~= -1
1083 line_error(mfilename, 'Retrial policies and finite orbits are not supported over the JLINE bridge.');
1084 end
1085 end
1086
1087 function [retrialDistribution, policy, maxOrbit] = getOrbit(self, class)
1088 % [RETRIALDISTRIBUTION, POLICY, MAXORBIT] = GETORBIT(CLASS)
1089 %
1090 % Returns the orbit configuration of CLASS at this station.
1091
1092 c = class.index;
1093 retrialDistribution = [];
1094 if c <= length(self.retrialDelays) && ~isempty(self.retrialDelays{1, c})
1095 retrialDistribution = self.retrialDelays{1, c};
1096 end
1097 policy = RetrialPolicy.LINEAR;
1098 if c <= length(self.retrialPolicies) && self.retrialPolicies(c) > 0
1099 policy = self.retrialPolicies(c);
1100 end
1101 maxOrbit = -1;
1102 if c <= length(self.orbitMaxJobs)
1103 maxOrbit = self.orbitMaxJobs(c);
1104 end
1105 end
1106
1107 function setBreakdown(self, failureDistribution, repairDistribution, downServiceDistribution)
1108 % SETBREAKDOWN(FAILUREDISTRIBUTION, REPAIRDISTRIBUTION)
1109 % SETBREAKDOWN(FAILUREDISTRIBUTION, REPAIRDISTRIBUTION, DOWNSERVICEDISTRIBUTION)
1110 %
1111 % Makes the server of this station subject to breakdowns. The server
1112 % alternates between an UP and a DOWN status: while up it fails after
1113 % FAILUREDISTRIBUTION, while down it is restored after
1114 % REPAIRDISTRIBUTION.
1115 %
1116 % The failure clock runs whenever the server is up, whether or not a
1117 % job is in service, so a station can fail while idle. Arrivals are
1118 % unaffected by the server status and keep queueing (subject to the
1119 % station capacity) while the server is down. A job that is in
1120 % service when the server fails is not lost: it stays at the station
1121 % and, service being memoryless in the supported case, resumes when
1122 % the server is repaired.
1123 %
1124 % Parameters:
1125 % failureDistribution - time to failure of an up server
1126 % repairDistribution - repair time of a down server
1127 % downServiceDistribution - optional service distribution used
1128 % while the server is down, either a
1129 % single Distribution applied to every
1130 % class or a cell array indexed by class.
1131 % Omitted or empty means the server does
1132 % not serve at all while down, which is
1133 % the usual breakdown model.
1134 %
1135 % Only exponential failure and repair distributions are currently
1136 % expanded into the joint (queue, server status) chain; anything else
1137 % is rejected here rather than silently approximated.
1138 %
1139 % Example:
1140 % % M/M/1/K whose server fails at rate 1e-4 and is repaired at rate 0.1
1141 % queue.setBreakdown(Exp(0.0001), Exp(0.1));
1142
1143 if nargin < 4
1144 downServiceDistribution = [];
1145 end
1146 if ~isa(failureDistribution, 'Distribution') || ~isa(repairDistribution, 'Distribution')
1147 line_error(mfilename, 'setBreakdown requires a failure and a repair Distribution.');
1148 end
1149 if ~isa(failureDistribution, 'Exp') || ~isa(repairDistribution, 'Exp')
1150 line_error(mfilename, sprintf(['Station ''%s'': only exponential failure and repair distributions are ' ...
1151 'supported by setBreakdown. A non-exponential failure or repair process needs its own phase in the ' ...
1152 'joint chain, which is not implemented; use an Environment ensemble for that case.'], self.getName()));
1153 end
1154 if failureDistribution.getMean() <= 0 || repairDistribution.getMean() <= 0
1155 line_error(mfilename, 'setBreakdown requires strictly positive failure and repair means.');
1156 end
1157
1158 if isempty(self.obj)
1159 self.breakdownFailure = failureDistribution;
1160 self.breakdownRepair = repairDistribution;
1161 if isempty(downServiceDistribution)
1162 self.breakdownDownService = {};
1163 elseif iscell(downServiceDistribution)
1164 self.breakdownDownService = downServiceDistribution;
1165 else
1166 self.breakdownDownService = {downServiceDistribution};
1167 end
1168 else
1169 if isempty(downServiceDistribution)
1170 self.obj.setBreakdown(failureDistribution.obj, repairDistribution.obj);
1171 elseif iscell(downServiceDistribution)
1172 line_error(mfilename, 'Per-class down-service distributions are not supported over the JLINE bridge.');
1173 else
1174 self.obj.setBreakdown(failureDistribution.obj, repairDistribution.obj, downServiceDistribution.obj);
1175 end
1176 end
1177 end
1178
1179 function [failureDistribution, repairDistribution, downServiceDistribution] = getBreakdown(self)
1180 % [FAILUREDISTRIBUTION, REPAIRDISTRIBUTION, DOWNSERVICEDISTRIBUTION] = GETBREAKDOWN()
1181 %
1182 % Returns the breakdown configuration of this station, or empty
1183 % values when the station is not subject to breakdowns.
1184
1185 failureDistribution = self.breakdownFailure;
1186 repairDistribution = self.breakdownRepair;
1187 downServiceDistribution = self.breakdownDownService;
1188 end
1189
1190 function [delayDistribution, maxAttempts] = getRetrial(self, class)
1191 % [DELAYDISTRIBUTION, MAXATTEMPTS] = GETRETRIAL(CLASS)
1192 %
1193 % Returns the retrial configuration for a specific job class.
1194 %
1195 % Parameters:
1196 % class - JobClass object
1197 %
1198 % Returns:
1199 % delayDistribution - Retrial delay distribution, or [] if not configured
1200 % maxAttempts - Maximum retrial attempts (-1 = unlimited)
1201
1202 if isempty(self.obj)
1203 c = class.index;
1204 if c <= length(self.retrialDelays) && ~isempty(self.retrialDelays{1, c})
1205 delayDistribution = self.retrialDelays{1, c};
1206 if c <= length(self.retrialMaxAttempts)
1207 maxAttempts = self.retrialMaxAttempts(c);
1208 else
1209 maxAttempts = -1;
1210 end
1211 else
1212 delayDistribution = [];
1213 maxAttempts = -1;
1214 end
1215 else
1216 distObj = self.obj.getRetrialDelayDistribution(class.obj);
1217 if isempty(distObj)
1218 delayDistribution = [];
1219 maxAttempts = -1;
1220 else
1221 delayDistribution = Distribution.fromJavaObject(distObj);
1222 maxAttempts = self.obj.getMaxRetrialAttempts(class.obj);
1223 end
1224 end
1225 end
1226
1227 function tf = hasRetrial(self, class)
1228 % TF = HASRETRIAL(CLASS)
1229 %
1230 % Returns true if this class has retrial configured at this queue.
1231
1232 [dist, ~] = self.getRetrial(class);
1233 tf = ~isempty(dist) && ~isa(dist, 'Disabled');
1234 end
1235
1236 function setOrbitImpatience(self, class, distribution)
1237 % SETORBITIMPATIENCE(CLASS, DISTRIBUTION)
1238 %
1239 % Sets the impatience (abandonment) rate for customers in the orbit.
1240 % This is separate from queue patience (reneging from waiting queue).
1241 % Used in BMAP/PH/N/N retrial queues where customers in the orbit
1242 % may abandon before successfully retrying.
1243 %
1244 % Parameters:
1245 % class - JobClass object
1246 % distribution - Distribution for orbit abandonment time (e.g., Exp(gamma))
1247 %
1248 % Example:
1249 % queue.setOrbitImpatience(jobclass, Exp(0.008)); % gamma = 0.008
1250
1251 if isa(distribution, 'BMAP') || isa(distribution, 'MAP') || isa(distribution, 'DMAP') || isa(distribution, 'MMPP2')
1252 line_error(mfilename, 'Modulated processes (BMAP, MAP, DMAP, MMPP2) are not supported for orbit impatience distributions.');
1253 end
1254
1255 if isempty(self.obj)
1256 c = class.index;
1257 self.orbitImpatienceDistributions{1, c} = distribution;
1258 else
1259 self.obj.setOrbitImpatience(class.obj, distribution.obj);
1260 end
1261 end
1262
1263 function distribution = getOrbitImpatience(self, class)
1264 % DISTRIBUTION = GETORBITIMPATIENCE(CLASS)
1265 %
1266 % Returns the orbit impatience distribution for a specific job class.
1267 %
1268 % Parameters:
1269 % class - JobClass object
1270 %
1271 % Returns:
1272 % distribution - The orbit impatience distribution, or [] if not set
1273
1274 if isempty(self.obj)
1275 c = class.index;
1276 if c <= length(self.orbitImpatienceDistributions) && ~isempty(self.orbitImpatienceDistributions{1, c})
1277 distribution = self.orbitImpatienceDistributions{1, c};
1278 else
1279 distribution = [];
1280 end
1281 else
1282 distObj = self.obj.getOrbitImpatience(class.obj);
1283 if isempty(distObj)
1284 distribution = [];
1285 else
1286 distribution = Distribution.fromJavaObject(distObj);
1287 end
1288 end
1289 end
1290
1291 function tf = hasOrbitImpatience(self, class)
1292 % TF = HASORBITORBITIMPATIENCE(CLASS)
1293 %
1294 % Returns true if this class has orbit impatience configured at this queue.
1295
1296 dist = self.getOrbitImpatience(class);
1297 tf = ~isempty(dist) && ~isa(dist, 'Disabled');
1298 end
1299
1300 function setBatchRejectProbability(self, class, p)
1301 % SETBATCHREJECTPROBABILITY(CLASS, P)
1302 %
1303 % Sets the probability that an entire batch is rejected when it
1304 % cannot be fully admitted. Used in BMAP/PH/N/N retrial queues
1305 % with batch arrivals.
1306 %
1307 % When a batch of size k arrives and only m < k servers are free:
1308 % - With probability p: entire batch is rejected to orbit
1309 % - With probability (1-p): m customers are admitted, k-m go to orbit
1310 %
1311 % Parameters:
1312 % class - JobClass object
1313 % p - Probability [0,1] that batch is rejected vs partially admitted
1314 % Default is 0 (partial admission allowed)
1315 %
1316 % Example:
1317 % queue.setBatchRejectProbability(jobclass, 0.4);
1318
1319 if p < 0 || p > 1
1320 line_error(mfilename, 'Batch reject probability must be in [0, 1].');
1321 end
1322
1323 if isempty(self.obj)
1324 c = class.index;
1325 % Ensure array is large enough
1326 if length(self.batchRejectProb) < c
1327 self.batchRejectProb(end+1:c) = 0;
1328 end
1329 self.batchRejectProb(c) = p;
1330 else
1331 self.obj.setBatchRejectProbability(class.obj, p);
1332 end
1333 end
1334
1335 function p = getBatchRejectProbability(self, class)
1336 % P = GETBATCHREJECTPROBABILITY(CLASS)
1337 %
1338 % Returns the batch reject probability for a specific job class.
1339 %
1340 % Parameters:
1341 % class - JobClass object
1342 %
1343 % Returns:
1344 % p - Batch reject probability [0,1], or 0 if not set
1345
1346 if isempty(self.obj)
1347 c = class.index;
1348 if c <= length(self.batchRejectProb) && self.batchRejectProb(c) > 0
1349 p = self.batchRejectProb(c);
1350 else
1351 p = 0; % Default: partial admission allowed
1352 end
1353 else
1354 p = self.obj.getBatchRejectProbability(class.obj);
1355 end
1356 end
1357
1358 % function distrib = getServiceProcess(self, oclass)
1359 % distrib = self.serviceProcess{oclass};
1360 % end
1361
1362 % ==================== Heterogeneous Server Methods ====================
1363
1364 function self = addServerType(self, serverType)
1365 % ADDSERVERTYPE Add a server type to this queue
1366 %
1367 % self = ADDSERVERTYPE(serverType) adds a ServerType to this queue
1368 % for heterogeneous multiserver configuration.
1369 %
1370 % When server types are added, the queue becomes a heterogeneous
1371 % multiserver queue where different server types can have different
1372 % service rates and serve different subsets of job classes.
1373 %
1374 % @param serverType The ServerType object to add
1375
1376 if isempty(serverType)
1377 line_error(mfilename, 'Server type cannot be empty');
1378 end
1379
1380 % Check if already added
1381 for i = 1:length(self.serverTypes)
1382 if self.serverTypes{i} == serverType
1383 line_error(mfilename, 'Server type ''%s'' is already added to this queue', serverType.getName());
1384 end
1385 end
1386
1387 if isempty(self.obj)
1388 % MATLAB native implementation
1389 serverType.setId(length(self.serverTypes));
1390 serverType.setParentQueue(self);
1391 self.serverTypes{end+1} = serverType;
1392
1393 % Initialize service distribution map for this server type
1394 self.heteroServiceDistributions(serverType.getName()) = containers.Map();
1395
1396 % Update total number of servers
1397 self.updateTotalServerCount();
1398 else
1399 % Java native - delegate to Java object
1400 self.obj.addServerType(serverType.obj);
1401 % Also store locally
1402 self.serverTypes{end+1} = serverType;
1403 end
1404 end
1405
1406 function updateTotalServerCount(self)
1407 % UPDATETOTALSERVERCOUNT Update total server count from all types
1408 %
1409 % Internal method to recalculate numberOfServers.
1410
1411 if isempty(self.serverTypes)
1412 return;
1413 end
1414 total = 0;
1415 for i = 1:length(self.serverTypes)
1416 total = total + self.serverTypes{i}.getNumOfServers();
1417 end
1418 self.numberOfServers = total;
1419 end
1420
1421 function types = getServerTypes(self)
1422 % GETSERVERTYPES Get the list of server types
1423 %
1424 % types = GETSERVERTYPES() returns a cell array of ServerType objects.
1425
1426 types = self.serverTypes;
1427 end
1428
1429 function n = getNumServerTypes(self)
1430 % GETNUMSERVERTYPES Get the number of server types
1431 %
1432 % n = GETNUMSERVERTYPES() returns the number of server types,
1433 % or 0 if this is a homogeneous queue.
1434
1435 n = length(self.serverTypes);
1436 end
1437
1438 function result = isHeterogeneous(self)
1439 % ISHETEROGENEOUS Check if this is a heterogeneous multiserver queue
1440 %
1441 % result = ISHETEROGENEOUS() returns true if server types are defined.
1442
1443 result = ~isempty(self.serverTypes);
1444 end
1445
1446 function self = setHeteroSchedPolicy(self, policy)
1447 % SETHETEROSCHEDPOLICY Set the heterogeneous server scheduling policy
1448 %
1449 % self = SETHETEROSCHEDPOLICY(policy) sets the policy that determines
1450 % how jobs are assigned to server types when a job's class is
1451 % compatible with multiple server types.
1452 %
1453 % @param policy HeteroSchedPolicy constant (ORDER, ALIS, ALFS, FAIRNESS, FSF, RAIS)
1454
1455 if isempty(self.obj)
1456 self.heteroSchedPolicy = policy;
1457 else
1458 % Convert to Java enum
1459 switch policy
1460 case HeteroSchedPolicy.ORDER
1461 jPolicy = jline.lang.constant.HeteroSchedPolicy.ORDER;
1462 case HeteroSchedPolicy.ALIS
1463 jPolicy = jline.lang.constant.HeteroSchedPolicy.ALIS;
1464 case HeteroSchedPolicy.ALFS
1465 jPolicy = jline.lang.constant.HeteroSchedPolicy.ALFS;
1466 case HeteroSchedPolicy.FAIRNESS
1467 jPolicy = jline.lang.constant.HeteroSchedPolicy.FAIRNESS;
1468 case HeteroSchedPolicy.FSF
1469 jPolicy = jline.lang.constant.HeteroSchedPolicy.FSF;
1470 case HeteroSchedPolicy.RAIS
1471 jPolicy = jline.lang.constant.HeteroSchedPolicy.RAIS;
1472 end
1473 self.obj.setHeteroSchedPolicy(jPolicy);
1474 self.heteroSchedPolicy = policy;
1475 end
1476 end
1477
1478 function policy = getHeteroSchedPolicy(self)
1479 % GETHETEROSCHEDPOLICY Get the heterogeneous server scheduling policy
1480 %
1481 % policy = GETHETEROSCHEDPOLICY() returns the HeteroSchedPolicy.
1482
1483 policy = self.heteroSchedPolicy;
1484 end
1485
1486 function setHeteroService(self, jobClass, serverType, distribution)
1487 % SETHETEROSERVICE Set service distribution for a job class and server type
1488 %
1489 % SETHETEROSERVICE(jobClass, serverType, distribution) sets the
1490 % service time distribution for a specific job class when served
1491 % by a specific server type.
1492 %
1493 % @param jobClass The JobClass object
1494 % @param serverType The ServerType object
1495 % @param distribution The service time Distribution
1496
1497 if isempty(jobClass)
1498 line_error(mfilename, 'Job class cannot be empty');
1499 end
1500 if isempty(serverType)
1501 line_error(mfilename, 'Server type cannot be empty');
1502 end
1503 if isempty(distribution)
1504 line_error(mfilename, 'Distribution cannot be empty');
1505 end
1506
1507 % Check if server type is in this queue
1508 found = false;
1509 for i = 1:length(self.serverTypes)
1510 if self.serverTypes{i} == serverType
1511 found = true;
1512 break;
1513 end
1514 end
1515 if ~found
1516 line_error(mfilename, 'Server type ''%s'' is not added to this queue. Call addServerType() first.', serverType.getName());
1517 end
1518
1519 if isempty(self.obj)
1520 % MATLAB native implementation
1521 if ~isKey(self.heteroServiceDistributions, serverType.getName())
1522 self.heteroServiceDistributions(serverType.getName()) = containers.Map();
1523 end
1524 classMap = self.heteroServiceDistributions(serverType.getName());
1525 classMap(jobClass.getName()) = distribution;
1526 self.heteroServiceDistributions(serverType.getName()) = classMap;
1527
1528 % Ensure compatibility
1529 if ~serverType.isCompatible(jobClass)
1530 serverType.addCompatible(jobClass);
1531 end
1532 else
1533 % Java native - delegate to Java object
1534 self.obj.setService(jobClass.obj, serverType.obj, distribution.obj);
1535 end
1536 end
1537
1538 function distribution = getHeteroService(self, jobClass, serverType)
1539 % GETHETEROSERVICE Get service distribution for a job class and server type
1540 %
1541 % distribution = GETHETEROSERVICE(jobClass, serverType) returns the
1542 % service time distribution for a specific job class and server type.
1543 %
1544 % @param jobClass The JobClass object
1545 % @param serverType The ServerType object
1546 % @return distribution The service time Distribution, or [] if not set
1547
1548 if isempty(self.obj)
1549 if isKey(self.heteroServiceDistributions, serverType.getName())
1550 classMap = self.heteroServiceDistributions(serverType.getName());
1551 if isKey(classMap, jobClass.getName())
1552 distribution = classMap(jobClass.getName());
1553 else
1554 distribution = [];
1555 end
1556 else
1557 distribution = [];
1558 end
1559 else
1560 distObj = self.obj.getService(jobClass.obj, serverType.obj);
1561 if isempty(distObj)
1562 distribution = [];
1563 else
1564 distribution = Distribution.fromJavaObject(distObj);
1565 end
1566 end
1567 end
1568
1569 function st = getServerTypeById(self, id)
1570 % GETSERVERTYPEBYID Get a server type by its ID
1571 %
1572 % st = GETSERVERTYPEBYID(id) returns the ServerType with the given ID,
1573 % or [] if not found.
1574
1575 if id >= 0 && id < length(self.serverTypes)
1576 st = self.serverTypes{id + 1}; % MATLAB 1-indexed
1577 else
1578 st = [];
1579 end
1580 end
1581
1582 function st = getServerTypeByName(self, name)
1583 % GETSERVERTYPEBYNAME Get a server type by its name
1584 %
1585 % st = GETSERVERTYPEBYNAME(name) returns the ServerType with the given
1586 % name, or [] if not found.
1587
1588 st = [];
1589 for i = 1:length(self.serverTypes)
1590 if strcmp(self.serverTypes{i}.getName(), name)
1591 st = self.serverTypes{i};
1592 return;
1593 end
1594 end
1595 end
1596
1597 function result = validateCompatibility(self)
1598 % VALIDATECOMPATIBILITY Check all job classes have compatible server types
1599 %
1600 % result = VALIDATECOMPATIBILITY() returns true if all job classes
1601 % in the model have at least one compatible server type at this queue.
1602
1603 if ~self.isHeterogeneous()
1604 result = true;
1605 return;
1606 end
1607
1608 classes = self.model.getClasses();
1609 for c = 1:length(classes)
1610 jobClass = classes{c};
1611 hasCompatible = false;
1612 for s = 1:length(self.serverTypes)
1613 if self.serverTypes{s}.isCompatible(jobClass)
1614 hasCompatible = true;
1615 break;
1616 end
1617 end
1618 if ~hasCompatible
1619 result = false;
1620 return;
1621 end
1622 end
1623 result = true;
1624 end
1625
1626 % ==================== Immediate Feedback Methods ====================
1627
1628 function setImmediateFeedback(self, varargin)
1629 % SETIMMEDIATEFEEDBACK Set immediate feedback for self-loops
1630 %
1631 % SETIMMEDIATEFEEDBACK(true) enables immediate feedback for all classes
1632 % SETIMMEDIATEFEEDBACK(false) disables immediate feedback for all classes
1633 % SETIMMEDIATEFEEDBACK(jobClass) enables for a specific class
1634 % SETIMMEDIATEFEEDBACK({class1, class2}) enables for multiple classes
1635 %
1636 % When enabled, a job that self-loops at this station stays in service
1637 % instead of going back to the queue.
1638
1639 if isempty(self.obj)
1640 % MATLAB native implementation
1641 if nargin == 2
1642 arg = varargin{1};
1643 if islogical(arg) || isnumeric(arg)
1644 if arg
1645 % Enable for all classes
1646 self.immediateFeedback = 'all';
1647 else
1648 % Disable for all classes
1649 self.immediateFeedback = {};
1650 end
1651 elseif isa(arg, 'JobClass')
1652 % Single class
1653 if isempty(self.immediateFeedback) || ischar(self.immediateFeedback)
1654 self.immediateFeedback = {};
1655 end
1656 if ~any(cellfun(@(x) x == arg.index, self.immediateFeedback))
1657 self.immediateFeedback{end+1} = arg.index;
1658 end
1659 elseif iscell(arg)
1660 % Cell array of classes
1661 self.immediateFeedback = {};
1662 for i = 1:length(arg)
1663 if isa(arg{i}, 'JobClass')
1664 self.immediateFeedback{end+1} = arg{i}.index;
1665 end
1666 end
1667 end
1668 end
1669 else
1670 % Java native implementation
1671 if nargin == 2
1672 arg = varargin{1};
1673 if islogical(arg) || isnumeric(arg)
1674 self.obj.setImmediateFeedback(logical(arg));
1675 elseif isa(arg, 'JobClass')
1676 self.obj.setImmediateFeedback(arg.obj);
1677 elseif iscell(arg)
1678 classList = java.util.ArrayList();
1679 for i = 1:length(arg)
1680 if isa(arg{i}, 'JobClass')
1681 classList.add(arg{i}.obj);
1682 end
1683 end
1684 self.obj.setImmediateFeedbackForClasses(classList);
1685 end
1686 end
1687 end
1688 end
1689
1690 function tf = hasImmediateFeedback(self, varargin)
1691 % HASIMMEDIATEFEEDBACK Check if immediate feedback is enabled
1692 %
1693 % TF = HASIMMEDIATEFEEDBACK() returns true if enabled for any class
1694 % TF = HASIMMEDIATEFEEDBACK(jobClass) returns true if enabled for specific class
1695
1696 if isempty(self.obj)
1697 % MATLAB native implementation
1698 if isempty(self.immediateFeedback)
1699 tf = false;
1700 elseif ischar(self.immediateFeedback) && strcmp(self.immediateFeedback, 'all')
1701 tf = true;
1702 elseif nargin == 1
1703 % No class specified - check if any class has it enabled
1704 tf = ~isempty(self.immediateFeedback);
1705 else
1706 % Check specific class
1707 jobClass = varargin{1};
1708 if ischar(self.immediateFeedback) && strcmp(self.immediateFeedback, 'all')
1709 tf = true;
1710 else
1711 tf = any(cellfun(@(x) x == jobClass.index, self.immediateFeedback));
1712 end
1713 end
1714 else
1715 % Java native implementation
1716 if nargin == 1
1717 tf = self.obj.hasImmediateFeedback();
1718 else
1719 jobClass = varargin{1};
1720 tf = self.obj.hasImmediateFeedback(jobClass.index - 1); % Java 0-indexed
1721 end
1722 end
1723 end
1724
1725 function classes = getImmediateFeedbackClasses(self)
1726 % GETIMMEDIATEFEEDBACKCLASSES Get list of class indices with immediate feedback
1727 %
1728 % CLASSES = GETIMMEDIATEFEEDBACKCLASSES() returns cell array of class indices
1729
1730 if isempty(self.obj)
1731 if isempty(self.immediateFeedback)
1732 classes = {};
1733 elseif ischar(self.immediateFeedback) && strcmp(self.immediateFeedback, 'all')
1734 classes = 'all';
1735 else
1736 classes = self.immediateFeedback;
1737 end
1738 else
1739 jClasses = self.obj.getImmediateFeedbackClasses();
1740 if isempty(jClasses)
1741 classes = {};
1742 elseif jClasses.equals("all")
1743 classes = 'all';
1744 else
1745 classes = {};
1746 for i = 0:(jClasses.size()-1)
1747 classes{end+1} = jClasses.get(i) + 1; % Convert to MATLAB 1-indexed
1748 end
1749 end
1750 end
1751 end
1752
1753 end
1754end