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 orbitImpatienceDistributions; % Cell array: per-class orbit abandonment distributions
21 batchRejectProb; % Array: per-class batch rejection probability [0,1]
22 % Heterogeneous server properties
23 serverTypes; % Cell array of ServerType objects
24 heteroSchedPolicy; % HeteroSchedPolicy for server assignment
25 heteroServiceDistributions; % containers.Map: ServerType -> (containers.Map: JobClass -> Distribution)
26 % Immediate feedback property
27 immediateFeedback; % Cell array of class indices, or 'all' for all classes
28 end
29
30 methods
31 %Constructor
32 function self = Queue(model, name, schedStrategy)
33 % SELF = QUEUE(MODEL, NAME, SCHEDSTRATEGY)
34
35 self@ServiceStation(name);
36
37 if model.isMatlabNative()
38 classes = model.getClasses();
39 self.input = Buffer(classes);
40 self.output = Dispatcher(classes);
41 self.schedPolicy = SchedStrategyType.PR;
42 self.schedStrategy = SchedStrategy.PS;
43 self.serviceProcess = {};
44 self.server = Server(classes);
45 self.numberOfServers = 1;
46 self.schedStrategyPar = zeros(1,length(model.getClasses()));
47 self.setModel(model);
48 self.model.addNode(self);
49 self.dropRule = [];
50 self.obj = [];
51 self.setupTime = {};
52 self.delayoffTime = {};
53 self.pollingType = {};
54 self.switchoverTime = {};
55 self.patienceDistributions = {};
56 self.impatienceTypes = {};
57 self.balkingStrategies = {};
58 self.balkingThresholds = {};
59 self.retrialDelays = {};
60 self.retrialMaxAttempts = [];
61 self.orbitImpatienceDistributions = {};
62 self.batchRejectProb = [];
63 self.serverTypes = {};
64 self.heteroSchedPolicy = HeteroSchedPolicy.ORDER;
65 self.heteroServiceDistributions = containers.Map();
66 self.immediateFeedback = {};
67
68 if nargin>=3 %exist('schedStrategy','var')
69 self.schedStrategy = schedStrategy;
70 switch SchedStrategy.toId(self.schedStrategy)
71 case {SchedStrategy.PS, SchedStrategy.DPS,SchedStrategy.GPS, SchedStrategy.PSPRIO, SchedStrategy.DPSPRIO,SchedStrategy.GPSPRIO, SchedStrategy.LPS}
72 self.schedPolicy = SchedStrategyType.PR;
73 self.server = SharedServer(classes);
74 case {SchedStrategy.LCFSPR, SchedStrategy.LCFSPRPRIO, SchedStrategy.FCFSPR, SchedStrategy.FCFSPRPRIO, SchedStrategy.LCFSPI, SchedStrategy.LCFSPIPRIO, SchedStrategy.FCFSPI, SchedStrategy.FCFSPIPRIO, SchedStrategy.EDF}
75 self.schedPolicy = SchedStrategyType.PR;
76 self.server = PreemptiveServer(classes);
77 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}
78 self.schedPolicy = SchedStrategyType.NP;
79 self.server = Server(classes);
80 case SchedStrategy.INF
81 self.schedPolicy = SchedStrategyType.NP;
82 self.server = InfiniteServer(classes);
83 self.numberOfServers = Inf;
84 case {SchedStrategy.HOL, SchedStrategy.FCFSPRIO, SchedStrategy.LCFSPRIO}
85 self.schedPolicy = SchedStrategyType.NP;
86 self.server = Server(classes);
87 case SchedStrategy.POLLING
88 self.schedPolicy = SchedStrategyType.NP;
89 self.server = PollingServer(classes);
90 otherwise
91 line_error(mfilename,sprintf('The specified scheduling strategy (%s) is unsupported.',schedStrategy));
92 end
93 end
94 elseif model.isJavaNative()
95 self.setModel(model);
96 switch SchedStrategy.toId(schedStrategy)
97 case SchedStrategy.INF
98 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.INF);
99 case SchedStrategy.FCFS
100 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.FCFS);
101 case SchedStrategy.LCFS
102 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.LCFS);
103 case SchedStrategy.SIRO
104 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.SIRO);
105 case SchedStrategy.SJF
106 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.SJF);
107 case SchedStrategy.LJF
108 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.LJF);
109 case SchedStrategy.PS
110 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.PS);
111 case SchedStrategy.PSPRIO
112 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.PSPRIO);
113 case SchedStrategy.DPS
114 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.DPS);
115 case SchedStrategy.DPSPRIO
116 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.DPSPRIO);
117 case SchedStrategy.GPS
118 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.GPS);
119 case SchedStrategy.GPSPRIO
120 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.GPSPRIO);
121 case SchedStrategy.SEPT
122 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.SEPT);
123 case SchedStrategy.LEPT
124 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.LEPT);
125 case SchedStrategy.SRPT
126 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.SRPT);
127 case SchedStrategy.SRPTPRIO
128 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.SRPTPRIO);
129 case SchedStrategy.PSJF
130 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.PSJF);
131 case SchedStrategy.FB
132 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.FB);
133 case SchedStrategy.LRPT
134 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.LRPT);
135 case SchedStrategy.HOL
136 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.HOL);
137 case SchedStrategy.FORK
138 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.FORK);
139 case SchedStrategy.EXT
140 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.EXT);
141 case SchedStrategy.REF
142 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.REF);
143 case SchedStrategy.LCFSPR
144 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.LCFSPR);
145 case SchedStrategy.FCFSPR
146 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.FCFSPR);
147 case SchedStrategy.FCFSPRPRIO
148 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.FCFSPRPRIO);
149 case SchedStrategy.EDD
150 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.EDD);
151 case SchedStrategy.EDF
152 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.EDF);
153 case SchedStrategy.LPS
154 self.obj = jline.lang.nodes.Queue(model.obj, name, jline.lang.constant.SchedStrategy.LPS);
155 end
156 self.obj.setNumberOfServers(1);
157 self.index = model.obj.getNodeIndex(self.obj);
158 end
159 end
160
161 function setLoadDependence(self, alpha)
162 switch SchedStrategy.toId(self.schedStrategy)
163 case {SchedStrategy.PS, SchedStrategy.FCFS}
164 setLimitedLoadDependence(self, alpha);
165 otherwise
166 line_error(mfilename,'Load-dependence supported only for processor sharing (PS) and first-come first-serve (FCFS) stations.');
167 end
168 end
169
170 function setClassDependence(self, beta)
171 switch SchedStrategy.toId(self.schedStrategy)
172 case {SchedStrategy.PS, SchedStrategy.FCFS}
173 setLimitedClassDependence(self, beta);
174 otherwise
175 line_error(mfilename,'Class-dependence supported only for processor sharing (PS) and first-come first-serve (FCFS) stations.');
176 end
177 end
178
179 function setJointDependence(self, scalingTable, cutoffs)
180 % SETJOINTDEPENDENCE(SCALINGTABLE, CUTOFFS)
181 %
182 % Sets joint class-dependent scaling for service rates using a
183 % lookup table indexed by per-class population vector.
184 %
185 % scalingTable: R-dimensional array or linearized 1D vector
186 % If R-dimensional: dimensions are [N1+1, N2+1, ..., NR+1]
187 % If linearized: index = 1 + n1 + n2*(N1+1) + n3*(N1+1)*(N2+1) + ...
188 % cutoffs: (optional) per-class cutoffs [N1, N2, ..., NR]
189 % If omitted, uses default: ceil(6000^(1/(M*K)))
190
191 switch SchedStrategy.toId(self.schedStrategy)
192 case {SchedStrategy.PS, SchedStrategy.FCFS}
193 if nargin < 3
194 % Auto-compute cutoffs
195 M = length(self.model.stations);
196 K = length(self.model.classes);
197 defaultCutoff = ceil(6000^(1/(M*K)));
198 cutoffs = defaultCutoff * ones(1, K);
199 end
200
201 % Convert multi-dimensional to linearized if needed
202 if ~isvector(scalingTable)
203 scalingTable = scalingTable(:)'; % linearize
204 end
205
206 % Validate table size matches cutoffs
207 expectedSize = prod(cutoffs + 1);
208 if length(scalingTable) ~= expectedSize
209 line_error(mfilename, sprintf('Scaling table size (%d) does not match expected size from cutoffs (%d).', length(scalingTable), expectedSize));
210 end
211
212 setLimitedJointDependence(self, scalingTable, cutoffs);
213 otherwise
214 line_error(mfilename,'Joint-dependence supported only for processor sharing (PS) and first-come first-serve (FCFS) stations.');
215 end
216 end
217
218 function setJointClassDependence(self, scalingTables, cutoffs)
219 % SETJOINTCLASSDEPENDENCE(SCALINGTABLES, CUTOFFS)
220 %
221 % Sets per-class joint class-dependent scaling for service rates.
222 % Unlike setJointDependence which uses a single scaling factor for
223 % all classes, this method allows each class to have its own
224 % scaling table indexed by per-class population vector.
225 %
226 % This is essential for Flow-Equivalent Server (FES) aggregation
227 % where the service rate for class c in state (n1,...,nK) equals
228 % the throughput of class c in an isolated subnetwork.
229 %
230 % scalingTables: cell array {1,K} of linearized scaling vectors
231 % Each scalingTables{c} has length prod(cutoffs + 1)
232 % Index: idx = 1 + n1 + n2*(N1+1) + n3*(N1+1)*(N2+1) + ...
233 % cutoffs: per-class cutoffs [N1, N2, ..., NR]
234
235 switch SchedStrategy.toId(self.schedStrategy)
236 case {SchedStrategy.PS, SchedStrategy.FCFS}
237 K = length(cutoffs);
238 expectedSize = prod(cutoffs + 1);
239
240 if ~iscell(scalingTables) || length(scalingTables) ~= K
241 line_error(mfilename, 'scalingTables must be cell array of length K (number of classes).');
242 end
243
244 for c = 1:K
245 % Convert multi-dimensional to linearized if needed
246 if ~isvector(scalingTables{c})
247 scalingTables{c} = scalingTables{c}(:)';
248 end
249
250 if length(scalingTables{c}) ~= expectedSize
251 line_error(mfilename, sprintf('scalingTables{%d} size (%d) does not match expected size from cutoffs (%d).', c, length(scalingTables{c}), expectedSize));
252 end
253 end
254
255 setLimitedJointClassDependence(self, scalingTables, cutoffs);
256 otherwise
257 line_error(mfilename,'Joint-class-dependence supported only for processor sharing (PS) and first-come first-serve (FCFS) stations.');
258 end
259 end
260
261 function setNumberOfServers(self, value)
262 % SETNUMBEROFSERVERS(VALUE)
263 if isempty(self.obj)
264 switch SchedStrategy.toId(self.schedStrategy)
265 case SchedStrategy.INF
266 %line_warning(mfilename,'A request to change the number of servers in an infinite server node has been ignored.');
267 %ignore
268 otherwise
269 self.setNumServers(value);
270 end
271 else
272 self.obj.setNumberOfServers(value);
273 end
274 end
275
276 function setNumServers(self, value)
277 % SETNUMSERVERS(VALUE)
278 if isempty(self.obj)
279 switch SchedStrategy.toId(self.schedStrategy)
280 case {SchedStrategy.DPS, SchedStrategy.GPS}
281 if value ~= 1
282 line_error(mfilename,sprintf('Cannot use multi-server stations with %s scheduling.', self.schedStrategy));
283 end
284 otherwise
285 self.numberOfServers = value;
286 end
287 else
288 self.obj.setNumberOfServers(value);
289 end
290 end
291
292 function self = setStrategyParam(self, class, weight)
293 % SELF = SETSTRATEGYPARAM(CLASS, WEIGHT)
294
295 % For LPS scheduling, schedStrategyPar(1) stores the limit set via setLimit()
296 % Don't overwrite it with the default weight
297 if SchedStrategy.toId(self.schedStrategy) == SchedStrategy.LPS
298 % For LPS, only set weight if explicitly provided (not default 1.0)
299 % or if this is not the first class (which would overwrite the limit)
300 if class.index == 1 && weight == 1.0 && ~isempty(self.schedStrategyPar) && self.schedStrategyPar(1) > 1
301 % Preserve the LPS limit, don't overwrite with default weight
302 return;
303 end
304 end
305 self.schedStrategyPar(class.index) = weight;
306 end
307
308 function distribution = getService(self, class)
309 % DISTRIBUTION = GETSERVICE(CLASS)
310
311 % return the service distribution assigned to the given class
312 if nargin<2 %~exist('class','var')
313 for s = 1:length(self.model.getClasses())
314 classes = self.model.getClasses();
315 distribution{s} = self.server.serviceProcess{1, classes{s}}{3};
316 end
317 else
318 try
319 distribution = self.server.serviceProcess{1, class.index}{3};
320 catch ME
321 distribution = [];
322 line_warning(mfilename,'No distribution is available for the specified class.\n');
323 end
324 end
325 end
326
327 function setService(self, class, distribution, weight)
328 % SETSERVICE(CLASS, DISTRIBUTION, WEIGHT)
329 % distribution can be a Distribution object or a Workflow object
330
331 if nargin<4 %~exist('weight','var')
332 weight=1.0;
333 end
334
335 % If Workflow, convert to PH distribution
336 if isa(distribution, 'Workflow')
337 distribution = distribution.toPH();
338 end
339
340 if distribution.isImmediate()
341 distribution = Immediate.getInstance();
342 end
343 if isa(class,'SelfLoopingClass') && class.refstat.index ~= self.index && ~isa(distribution,'Disabled')
344 line_error(mfilename, 'For a self-looping class, service cannot be set on stations other than the reference station of the class.');
345 end
346 if isempty(self.obj)
347 server = self.server; % by reference
348 c = class.index;
349 if length(server.serviceProcess) >= c && ~isempty(server.serviceProcess{1,c}) % if the distribution was already configured
350 % this is a forced state reset in case for example the number of phases changes
351 % appears to run faster without checks, probably due to
352 % isa being slow
353 %oldDistribution = server.serviceProcess{1, c}{3};
354 %isOldMarkovian = isa(oldDistribution,'Markovian');
355 %isNewMarkovian = isa(distribution,'Markovian');
356 %if distribution.getNumParams ~= oldDistribution.getNumParams
357 % %|| (isOldMarkovian && ~isNewMarkovian) || (~isOldMarkovian && isNewMarkovian) || (isOldMarkovian && isNewMarkovian && distribution.getNumberOfPhases ~= oldDistribution.getNumberOfPhases)
358 self.model.setInitialized(false); % this is a better way to invalidate to avoid that sequential calls to setService all trigger an initDefault
359 self.model.hasStruct = false; % invalidate struct so procid gets recomputed
360 self.state=[]; % reset the state vector
361 %end
362 else % if first configuration
363 if length(self.classCap) < c
364 self.classCap((length(self.classCap)+1):c) = Inf;
365 end
366 self.setStrategyParam(class, weight);
367 % Default to Drop for finite capacity, WaitingQueue otherwise
368 if self.cap < intmax && ~isinf(self.cap)
369 self.dropRule(c) = DropStrategy.DROP;
370 else
371 self.dropRule(c) = DropStrategy.WAITQ;
372 end
373 server.serviceProcess{1, c}{2} = ServiceStrategy.LI;
374 end
375 server.serviceProcess{1, c}{3} = distribution;
376 self.serviceProcess{c} = distribution;
377 else
378 self.obj.setService(class.obj, distribution.obj, weight);
379 % Also update MATLAB-side storage to keep in sync with Java object
380 % This ensures getService returns the correct distribution
381 c = class.index;
382 self.serviceProcess{c} = distribution;
383 end
384 end
385
386 function setDelayOff(self, jobclass, setupTime, delayoffTime)
387 c = jobclass.index;
388 self.setupTime{1, c} = setupTime;
389 self.delayoffTime{1, c} = delayoffTime;
390 end
391
392 function dist = getSetupTime(self, jobclass)
393 c = jobclass.index;
394 if c <= length(self.setupTime) && ~isempty(self.setupTime{1, c})
395 dist = self.setupTime{1, c};
396 else
397 dist = [];
398 end
399 end
400
401 function dist = getDelayOffTime(self, jobclass)
402 c = jobclass.index;
403 if c <= length(self.delayoffTime) && ~isempty(self.delayoffTime{1, c})
404 dist = self.delayoffTime{1, c};
405 else
406 dist = [];
407 end
408 end
409
410 function setSwitchover(self, varargin)
411 if isempty(self.switchoverTime)
412 if SchedStrategy.toId(self.schedStrategy) == SchedStrategy.POLLING
413 self.switchoverTime = cell(1,length(self.model.getClasses()));
414 else
415 K = length(self.model.getClasses());
416 self.switchoverTime = cell(K,K);
417 for r=1:K
418 for s=1:K
419 self.switchoverTime{r,s} = Immediate();
420 end
421 end
422 end
423 end
424 if length(varargin)==2
425 jobclass = varargin{1};
426 soTime = varargin{2};
427 % time to switch from queue i to the next one
428 if SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.POLLING
429 line_error(mfilename,'setSwitchover(jobclass, distrib) can only be invoked on queues with SchedStrategy.POLLING.\n');
430 end
431 c = jobclass.index;
432 self.switchoverTime{1,c} = soTime;
433 elseif length(varargin)==3
434 jobclass_from = varargin{1};
435 jobclass_to = varargin{2};
436 soTime = varargin{3};
437 f = jobclass_from.index;
438 t = jobclass_to.index;
439 self.switchoverTime{f,t} = soTime;
440 end
441 end
442
443 function setPollingType(self, rule, par)
444 if PollingType.toId(rule) ~= PollingType.KLIMITED
445 par = [];
446 elseif PollingType.toId(rule) == PollingType.KLIMITED && nargin<3
447 line_error(mfilename,'K-Limited polling requires to specify the parameter K, e.g., setPollingType(PollingType.KLIMITED, 2).\n');
448 end
449 % support only identical polling type at each class buffer
450 if SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.POLLING
451 line_error(mfilename,'setPollingType can only be invoked on queues with SchedStrategy.POLLING.\n');
452 end
453 for r=1:length(self.model.getClasses())
454 self.pollingType{1,r} = rule;
455 self.pollingPar = par;
456 classes = self.model.getClasses();
457 setSwitchover(self, classes{r}, Immediate());
458 end
459 end
460
461 function setPatience(self, class, varargin)
462 % SETPATIENCE(CLASS, DISTRIBUTION) - Backwards compatible
463 % SETPATIENCE(CLASS, PATIENCETYPE, DISTRIBUTION) - Explicit type
464 %
465 % Sets the patience type and distribution for a specific job class at this queue.
466 % Jobs that wait longer than their patience time will abandon the queue.
467 %
468 % Parameters:
469 % class - JobClass object
470 % impatienceType - (Optional) ImpatienceType constant (RENEGING or BALKING)
471 % If omitted, defaults to ImpatienceType.RENEGING
472 % distribution - Any LINE distribution (Exp, Erlang, HyperExp, etc.)
473 % excluding modulated processes (BMAP, MAP, MMPP2)
474 %
475 % Note: This setting takes precedence over the global class patience.
476 %
477 % Examples:
478 % queue.setPatience(jobclass, Exp(0.2)) % Defaults to RENEGING
479 % queue.setPatience(jobclass, ImpatienceType.RENEGING, Exp(0.2))
480 % queue.setPatience(jobclass, ImpatienceType.BALKING, Exp(0.5))
481
482 % Handle backwards compatibility: 2 or 3 arguments
483 if length(varargin) == 1
484 % Old signature: setPatience(class, distribution)
485 distribution = varargin{1};
486 impatienceType = ImpatienceType.RENEGING; % Default to RENEGING
487 elseif length(varargin) == 2
488 % New signature: setPatience(class, impatienceType, distribution)
489 impatienceType = varargin{1};
490 distribution = varargin{2};
491 else
492 line_error(mfilename, 'Invalid number of arguments. Use setPatience(class, distribution) or setPatience(class, impatienceType, distribution)');
493 end
494
495 if isa(distribution, 'BMAP') || isa(distribution, 'MAP') || isa(distribution, 'MMPP2')
496 line_error(mfilename, 'Modulated processes (BMAP, MAP, MMPP2) are not supported for patience distributions.');
497 end
498
499 % Validate impatience type
500 if impatienceType ~= ImpatienceType.RENEGING && impatienceType ~= ImpatienceType.BALKING
501 line_error(mfilename, 'Invalid impatience type. Use ImpatienceType.RENEGING or ImpatienceType.BALKING.');
502 end
503
504 % Only RENEGING is currently supported
505 if impatienceType == ImpatienceType.BALKING
506 line_error(mfilename, 'BALKING impatience type is not yet supported. Use ImpatienceType.RENEGING.');
507 end
508
509 if distribution.isImmediate()
510 distribution = Immediate.getInstance();
511 end
512
513 if isempty(self.obj)
514 c = class.index;
515 self.patienceDistributions{1, c} = distribution;
516 self.impatienceTypes{1, c} = impatienceType;
517 else
518 self.obj.setPatience(class.obj, impatienceType, distribution.obj);
519 end
520 end
521
522 function distribution = getPatience(self, class)
523 % DISTRIBUTION = GETPATIENCE(CLASS)
524 %
525 % Returns the patience distribution for a specific job class.
526 % Returns the queue-specific setting if available, otherwise
527 % falls back to the global class patience.
528 %
529 % Parameters:
530 % class - JobClass object
531 %
532 % Returns:
533 % distribution - The patience distribution, or [] if not set
534
535 if isempty(self.obj)
536 c = class.index;
537 % Check queue-specific patience first
538 if c <= length(self.patienceDistributions) && ~isempty(self.patienceDistributions{1, c})
539 distribution = self.patienceDistributions{1, c};
540 else
541 % Fall back to global class patience
542 distribution = class.getPatience();
543 end
544 else
545 distObj = self.obj.getPatience(class.obj);
546 if isempty(distObj)
547 distribution = [];
548 else
549 distribution = Distribution.fromJavaObject(distObj);
550 end
551 end
552 end
553
554 function setLimit(self, limit)
555 % SETLIMIT(LIMIT) Sets the maximum number of jobs for LPS scheduling
556 %
557 % Parameters:
558 % limit - Maximum number of jobs in PS (processor sharing) mode for LPS
559
560 if isempty(self.obj)
561 % MATLAB native implementation - store as a scheduling parameter
562 if SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.LPS
563 line_warning(mfilename, 'setLimit is only applicable to LPS (Least Progress Scheduling) queues.');
564 return;
565 end
566 % Store limit in schedStrategyPar (use index 0 for queue-level parameter)
567 if length(self.schedStrategyPar) < 1
568 self.schedStrategyPar = zeros(1, 1);
569 end
570 self.schedStrategyPar(1) = limit;
571 else
572 % JavaNative implementation
573 if SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.LPS
574 line_warning(mfilename, 'setLimit is only applicable to LPS (Least Progress Scheduling) queues.');
575 return;
576 end
577 self.obj.setLimit(limit);
578 end
579 end
580
581 function limit = getLimit(self)
582 % LIMIT = GETLIMIT() Returns the maximum number of jobs for LPS scheduling
583 %
584 % Returns:
585 % limit - Maximum number of jobs in PS mode for LPS
586
587 if isempty(self.obj)
588 % MATLAB native implementation
589 if length(self.schedStrategyPar) >= 1
590 limit = self.schedStrategyPar(1);
591 else
592 limit = [];
593 end
594 else
595 % JavaNative implementation
596 limit = self.obj.getLimit();
597 end
598 end
599
600 function impatienceType = getImpatienceType(self, class)
601 % IMPATIENCETYPE = GETIMPATIENCETYPE(CLASS)
602 %
603 % Returns the impatience type for a specific job class.
604 % Returns the queue-specific setting if available, otherwise
605 % falls back to the global class impatience type.
606 %
607 % Parameters:
608 % class - JobClass object
609 %
610 % Returns:
611 % impatienceType - The impatience type (ImpatienceType constant), or [] if not set
612
613 if isempty(self.obj)
614 c = class.index;
615 % Check queue-specific impatience type first
616 if c <= length(self.impatienceTypes) && ~isempty(self.impatienceTypes{1, c})
617 impatienceType = self.impatienceTypes{1, c};
618 else
619 % Fall back to global class impatience type
620 impatienceType = class.getImpatienceType();
621 end
622 else
623 impatienceTypeId = self.obj.getImpatienceType(class.obj);
624 if isempty(impatienceTypeId)
625 impatienceType = [];
626 else
627 impatienceType = ImpatienceType.fromId(impatienceTypeId.getID());
628 end
629 end
630 end
631
632 function tf = hasPatience(self, class)
633 % TF = HASPATIENCE(CLASS)
634 %
635 % Returns true if this class has patience configured at this queue
636 % (either locally or globally).
637
638 dist = self.getPatience(class);
639 tf = ~isempty(dist) && ~isa(dist, 'Disabled');
640 end
641
642 function setBalking(self, class, strategy, thresholds)
643 % SETBALKING(CLASS, STRATEGY, THRESHOLDS)
644 %
645 % Configures balking behavior for a specific job class at this queue.
646 % When a customer arrives, they may refuse to join based on queue length.
647 %
648 % Parameters:
649 % class - JobClass object
650 % strategy - BalkingStrategy constant:
651 % QUEUE_LENGTH - Balk based on current queue length
652 % EXPECTED_WAIT - Balk based on expected waiting time
653 % COMBINED - Both conditions (OR logic)
654 % thresholds - Cell array of balking thresholds, each element is:
655 % {minJobs, maxJobs, probability}
656 % where probability is the chance to balk when queue
657 % length is in [minJobs, maxJobs] range.
658 %
659 % Example:
660 % % Balk with 30% probability when 5-10 jobs in queue,
661 % % 80% when 11-20 jobs, 100% when >20 jobs
662 % queue.setBalking(jobclass, BalkingStrategy.QUEUE_LENGTH, ...
663 % {{5, 10, 0.3}, {11, 20, 0.8}, {21, Inf, 1.0}});
664
665 if isempty(self.obj)
666 c = class.index;
667 self.balkingStrategies{1, c} = strategy;
668 self.balkingThresholds{1, c} = thresholds;
669 else
670 % Java native - convert thresholds to Java format
671 jThresholds = jline.util.BalkingThresholdList();
672 for i = 1:length(thresholds)
673 th = thresholds{i};
674 minJobs = th{1};
675 maxJobs = th{2};
676 if isinf(maxJobs)
677 maxJobs = java.lang.Integer.MAX_VALUE;
678 end
679 probability = th{3};
680 jThresholds.add(jline.lang.BalkingThreshold(minJobs, maxJobs, probability));
681 end
682 % Convert strategy to Java enum
683 switch strategy
684 case BalkingStrategy.QUEUE_LENGTH
685 jStrategy = jline.lang.constant.BalkingStrategy.QUEUE_LENGTH;
686 case BalkingStrategy.EXPECTED_WAIT
687 jStrategy = jline.lang.constant.BalkingStrategy.EXPECTED_WAIT;
688 case BalkingStrategy.COMBINED
689 jStrategy = jline.lang.constant.BalkingStrategy.COMBINED;
690 end
691 self.obj.setBalking(class.obj, jStrategy, jThresholds);
692 end
693 end
694
695 function [strategy, thresholds] = getBalking(self, class)
696 % [STRATEGY, THRESHOLDS] = GETBALKING(CLASS)
697 %
698 % Returns the balking configuration for a specific job class.
699 %
700 % Parameters:
701 % class - JobClass object
702 %
703 % Returns:
704 % strategy - BalkingStrategy constant, or [] if not configured
705 % thresholds - Cell array of {minJobs, maxJobs, probability} tuples
706
707 if isempty(self.obj)
708 c = class.index;
709 if c <= length(self.balkingStrategies) && ~isempty(self.balkingStrategies{1, c})
710 strategy = self.balkingStrategies{1, c};
711 thresholds = self.balkingThresholds{1, c};
712 else
713 strategy = [];
714 thresholds = {};
715 end
716 else
717 jStrategy = self.obj.getBalkingStrategy(class.obj);
718 if isempty(jStrategy)
719 strategy = [];
720 thresholds = {};
721 else
722 strategy = BalkingStrategy.fromId(jStrategy.getId());
723 jThresholds = self.obj.getBalkingThresholds(class.obj);
724 thresholds = {};
725 if ~isempty(jThresholds)
726 for i = 0:(jThresholds.size()-1)
727 jTh = jThresholds.get(i);
728 maxJobs = jTh.getMaxJobs();
729 if maxJobs == java.lang.Integer.MAX_VALUE
730 maxJobs = Inf;
731 end
732 thresholds{end+1} = {jTh.getMinJobs(), maxJobs, jTh.getProbability()};
733 end
734 end
735 end
736 end
737 end
738
739 function tf = hasBalking(self, class)
740 % TF = HASBALKING(CLASS)
741 %
742 % Returns true if this class has balking configured at this queue.
743
744 [strategy, ~] = self.getBalking(class);
745 tf = ~isempty(strategy);
746 end
747
748 function setRetrial(self, class, delayDistribution, maxAttempts)
749 % SETRETRIAL(CLASS, DELAYDISTRIBUTION, MAXATTEMPTS)
750 %
751 % Configures retrial behavior for a specific job class at this queue.
752 % When a customer is rejected (queue full), they move to an orbit
753 % and retry after a random delay.
754 %
755 % Parameters:
756 % class - JobClass object
757 % delayDistribution - Distribution for retrial delay (e.g., Exp(0.5))
758 % maxAttempts - Maximum number of retrial attempts:
759 % -1 = unlimited retries (default)
760 % N = drop after N failed attempts
761 %
762 % Example:
763 % % Retry with exponential delay, unlimited attempts
764 % queue.setRetrial(jobclass, Exp(0.5), -1);
765 %
766 % % Retry up to 3 times with Erlang delay
767 % queue.setRetrial(jobclass, Erlang(2, 0.3), 3);
768
769 if nargin < 4
770 maxAttempts = -1; % Unlimited by default
771 end
772
773 if isa(delayDistribution, 'BMAP') || isa(delayDistribution, 'MAP') || isa(delayDistribution, 'MMPP2')
774 line_error(mfilename, 'Modulated processes (BMAP, MAP, MMPP2) are not supported for retrial delay distributions.');
775 end
776
777 if isempty(self.obj)
778 c = class.index;
779 self.retrialDelays{1, c} = delayDistribution;
780 % Ensure array is large enough
781 if length(self.retrialMaxAttempts) < c
782 self.retrialMaxAttempts(end+1:c) = -1;
783 end
784 self.retrialMaxAttempts(c) = maxAttempts;
785 % Also set drop rule to RETRIAL or RETRIAL_WITH_LIMIT
786 if maxAttempts < 0
787 self.dropRule(c) = DropStrategy.RETRIAL;
788 else
789 self.dropRule(c) = DropStrategy.RETRIAL_WITH_LIMIT;
790 end
791 else
792 self.obj.setRetrial(class.obj, delayDistribution.obj, maxAttempts);
793 end
794 end
795
796 function [delayDistribution, maxAttempts] = getRetrial(self, class)
797 % [DELAYDISTRIBUTION, MAXATTEMPTS] = GETRETRIAL(CLASS)
798 %
799 % Returns the retrial configuration for a specific job class.
800 %
801 % Parameters:
802 % class - JobClass object
803 %
804 % Returns:
805 % delayDistribution - Retrial delay distribution, or [] if not configured
806 % maxAttempts - Maximum retrial attempts (-1 = unlimited)
807
808 if isempty(self.obj)
809 c = class.index;
810 if c <= length(self.retrialDelays) && ~isempty(self.retrialDelays{1, c})
811 delayDistribution = self.retrialDelays{1, c};
812 if c <= length(self.retrialMaxAttempts)
813 maxAttempts = self.retrialMaxAttempts(c);
814 else
815 maxAttempts = -1;
816 end
817 else
818 delayDistribution = [];
819 maxAttempts = -1;
820 end
821 else
822 distObj = self.obj.getRetrialDelayDistribution(class.obj);
823 if isempty(distObj)
824 delayDistribution = [];
825 maxAttempts = -1;
826 else
827 delayDistribution = Distribution.fromJavaObject(distObj);
828 maxAttempts = self.obj.getMaxRetrialAttempts(class.obj);
829 end
830 end
831 end
832
833 function tf = hasRetrial(self, class)
834 % TF = HASRETRIAL(CLASS)
835 %
836 % Returns true if this class has retrial configured at this queue.
837
838 [dist, ~] = self.getRetrial(class);
839 tf = ~isempty(dist) && ~isa(dist, 'Disabled');
840 end
841
842 function setOrbitImpatience(self, class, distribution)
843 % SETORBITIMPATIENCE(CLASS, DISTRIBUTION)
844 %
845 % Sets the impatience (abandonment) rate for customers in the orbit.
846 % This is separate from queue patience (reneging from waiting queue).
847 % Used in BMAP/PH/N/N retrial queues where customers in the orbit
848 % may abandon before successfully retrying.
849 %
850 % Parameters:
851 % class - JobClass object
852 % distribution - Distribution for orbit abandonment time (e.g., Exp(gamma))
853 %
854 % Example:
855 % queue.setOrbitImpatience(jobclass, Exp(0.008)); % gamma = 0.008
856
857 if isa(distribution, 'BMAP') || isa(distribution, 'MAP') || isa(distribution, 'MMPP2')
858 line_error(mfilename, 'Modulated processes (BMAP, MAP, MMPP2) are not supported for orbit impatience distributions.');
859 end
860
861 if isempty(self.obj)
862 c = class.index;
863 self.orbitImpatienceDistributions{1, c} = distribution;
864 else
865 self.obj.setOrbitImpatience(class.obj, distribution.obj);
866 end
867 end
868
869 function distribution = getOrbitImpatience(self, class)
870 % DISTRIBUTION = GETORBITIMPATIENCE(CLASS)
871 %
872 % Returns the orbit impatience distribution for a specific job class.
873 %
874 % Parameters:
875 % class - JobClass object
876 %
877 % Returns:
878 % distribution - The orbit impatience distribution, or [] if not set
879
880 if isempty(self.obj)
881 c = class.index;
882 if c <= length(self.orbitImpatienceDistributions) && ~isempty(self.orbitImpatienceDistributions{1, c})
883 distribution = self.orbitImpatienceDistributions{1, c};
884 else
885 distribution = [];
886 end
887 else
888 distObj = self.obj.getOrbitImpatience(class.obj);
889 if isempty(distObj)
890 distribution = [];
891 else
892 distribution = Distribution.fromJavaObject(distObj);
893 end
894 end
895 end
896
897 function tf = hasOrbitImpatience(self, class)
898 % TF = HASORBITORBITIMPATIENCE(CLASS)
899 %
900 % Returns true if this class has orbit impatience configured at this queue.
901
902 dist = self.getOrbitImpatience(class);
903 tf = ~isempty(dist) && ~isa(dist, 'Disabled');
904 end
905
906 function setBatchRejectProbability(self, class, p)
907 % SETBATCHREJECTPROBABILITY(CLASS, P)
908 %
909 % Sets the probability that an entire batch is rejected when it
910 % cannot be fully admitted. Used in BMAP/PH/N/N retrial queues
911 % with batch arrivals.
912 %
913 % When a batch of size k arrives and only m < k servers are free:
914 % - With probability p: entire batch is rejected to orbit
915 % - With probability (1-p): m customers are admitted, k-m go to orbit
916 %
917 % Parameters:
918 % class - JobClass object
919 % p - Probability [0,1] that batch is rejected vs partially admitted
920 % Default is 0 (partial admission allowed)
921 %
922 % Example:
923 % queue.setBatchRejectProbability(jobclass, 0.4);
924
925 if p < 0 || p > 1
926 line_error(mfilename, 'Batch reject probability must be in [0, 1].');
927 end
928
929 if isempty(self.obj)
930 c = class.index;
931 % Ensure array is large enough
932 if length(self.batchRejectProb) < c
933 self.batchRejectProb(end+1:c) = 0;
934 end
935 self.batchRejectProb(c) = p;
936 else
937 self.obj.setBatchRejectProbability(class.obj, p);
938 end
939 end
940
941 function p = getBatchRejectProbability(self, class)
942 % P = GETBATCHREJECTPROBABILITY(CLASS)
943 %
944 % Returns the batch reject probability for a specific job class.
945 %
946 % Parameters:
947 % class - JobClass object
948 %
949 % Returns:
950 % p - Batch reject probability [0,1], or 0 if not set
951
952 if isempty(self.obj)
953 c = class.index;
954 if c <= length(self.batchRejectProb) && self.batchRejectProb(c) > 0
955 p = self.batchRejectProb(c);
956 else
957 p = 0; % Default: partial admission allowed
958 end
959 else
960 p = self.obj.getBatchRejectProbability(class.obj);
961 end
962 end
963
964 % function distrib = getServiceProcess(self, oclass)
965 % distrib = self.serviceProcess{oclass};
966 % end
967
968 % ==================== Heterogeneous Server Methods ====================
969
970 function self = addServerType(self, serverType)
971 % ADDSERVERTYPE Add a server type to this queue
972 %
973 % self = ADDSERVERTYPE(serverType) adds a ServerType to this queue
974 % for heterogeneous multiserver configuration.
975 %
976 % When server types are added, the queue becomes a heterogeneous
977 % multiserver queue where different server types can have different
978 % service rates and serve different subsets of job classes.
979 %
980 % @param serverType The ServerType object to add
981
982 if isempty(serverType)
983 line_error(mfilename, 'Server type cannot be empty');
984 end
985
986 % Check if already added
987 for i = 1:length(self.serverTypes)
988 if self.serverTypes{i} == serverType
989 line_error(mfilename, 'Server type ''%s'' is already added to this queue', serverType.getName());
990 end
991 end
992
993 if isempty(self.obj)
994 % MATLAB native implementation
995 serverType.setId(length(self.serverTypes));
996 serverType.setParentQueue(self);
997 self.serverTypes{end+1} = serverType;
998
999 % Initialize service distribution map for this server type
1000 self.heteroServiceDistributions(serverType.getName()) = containers.Map();
1001
1002 % Update total number of servers
1003 self.updateTotalServerCount();
1004 else
1005 % Java native - delegate to Java object
1006 self.obj.addServerType(serverType.obj);
1007 % Also store locally
1008 self.serverTypes{end+1} = serverType;
1009 end
1010 end
1011
1012 function updateTotalServerCount(self)
1013 % UPDATETOTALSERVERCOUNT Update total server count from all types
1014 %
1015 % Internal method to recalculate numberOfServers.
1016
1017 if isempty(self.serverTypes)
1018 return;
1019 end
1020 total = 0;
1021 for i = 1:length(self.serverTypes)
1022 total = total + self.serverTypes{i}.getNumOfServers();
1023 end
1024 self.numberOfServers = total;
1025 end
1026
1027 function types = getServerTypes(self)
1028 % GETSERVERTYPES Get the list of server types
1029 %
1030 % types = GETSERVERTYPES() returns a cell array of ServerType objects.
1031
1032 types = self.serverTypes;
1033 end
1034
1035 function n = getNumServerTypes(self)
1036 % GETNUMSERVERTYPES Get the number of server types
1037 %
1038 % n = GETNUMSERVERTYPES() returns the number of server types,
1039 % or 0 if this is a homogeneous queue.
1040
1041 n = length(self.serverTypes);
1042 end
1043
1044 function result = isHeterogeneous(self)
1045 % ISHETEROGENEOUS Check if this is a heterogeneous multiserver queue
1046 %
1047 % result = ISHETEROGENEOUS() returns true if server types are defined.
1048
1049 result = ~isempty(self.serverTypes);
1050 end
1051
1052 function self = setHeteroSchedPolicy(self, policy)
1053 % SETHETEROSCHEDPOLICY Set the heterogeneous server scheduling policy
1054 %
1055 % self = SETHETEROSCHEDPOLICY(policy) sets the policy that determines
1056 % how jobs are assigned to server types when a job's class is
1057 % compatible with multiple server types.
1058 %
1059 % @param policy HeteroSchedPolicy constant (ORDER, ALIS, ALFS, FAIRNESS, FSF, RAIS)
1060
1061 if isempty(self.obj)
1062 self.heteroSchedPolicy = policy;
1063 else
1064 % Convert to Java enum
1065 switch policy
1066 case HeteroSchedPolicy.ORDER
1067 jPolicy = jline.lang.constant.HeteroSchedPolicy.ORDER;
1068 case HeteroSchedPolicy.ALIS
1069 jPolicy = jline.lang.constant.HeteroSchedPolicy.ALIS;
1070 case HeteroSchedPolicy.ALFS
1071 jPolicy = jline.lang.constant.HeteroSchedPolicy.ALFS;
1072 case HeteroSchedPolicy.FAIRNESS
1073 jPolicy = jline.lang.constant.HeteroSchedPolicy.FAIRNESS;
1074 case HeteroSchedPolicy.FSF
1075 jPolicy = jline.lang.constant.HeteroSchedPolicy.FSF;
1076 case HeteroSchedPolicy.RAIS
1077 jPolicy = jline.lang.constant.HeteroSchedPolicy.RAIS;
1078 end
1079 self.obj.setHeteroSchedPolicy(jPolicy);
1080 self.heteroSchedPolicy = policy;
1081 end
1082 end
1083
1084 function policy = getHeteroSchedPolicy(self)
1085 % GETHETEROSCHEDPOLICY Get the heterogeneous server scheduling policy
1086 %
1087 % policy = GETHETEROSCHEDPOLICY() returns the HeteroSchedPolicy.
1088
1089 policy = self.heteroSchedPolicy;
1090 end
1091
1092 function setHeteroService(self, jobClass, serverType, distribution)
1093 % SETHETEROSERVICE Set service distribution for a job class and server type
1094 %
1095 % SETHETEROSERVICE(jobClass, serverType, distribution) sets the
1096 % service time distribution for a specific job class when served
1097 % by a specific server type.
1098 %
1099 % @param jobClass The JobClass object
1100 % @param serverType The ServerType object
1101 % @param distribution The service time Distribution
1102
1103 if isempty(jobClass)
1104 line_error(mfilename, 'Job class cannot be empty');
1105 end
1106 if isempty(serverType)
1107 line_error(mfilename, 'Server type cannot be empty');
1108 end
1109 if isempty(distribution)
1110 line_error(mfilename, 'Distribution cannot be empty');
1111 end
1112
1113 % Check if server type is in this queue
1114 found = false;
1115 for i = 1:length(self.serverTypes)
1116 if self.serverTypes{i} == serverType
1117 found = true;
1118 break;
1119 end
1120 end
1121 if ~found
1122 line_error(mfilename, 'Server type ''%s'' is not added to this queue. Call addServerType() first.', serverType.getName());
1123 end
1124
1125 if isempty(self.obj)
1126 % MATLAB native implementation
1127 if ~isKey(self.heteroServiceDistributions, serverType.getName())
1128 self.heteroServiceDistributions(serverType.getName()) = containers.Map();
1129 end
1130 classMap = self.heteroServiceDistributions(serverType.getName());
1131 classMap(jobClass.getName()) = distribution;
1132 self.heteroServiceDistributions(serverType.getName()) = classMap;
1133
1134 % Ensure compatibility
1135 if ~serverType.isCompatible(jobClass)
1136 serverType.addCompatible(jobClass);
1137 end
1138 else
1139 % Java native - delegate to Java object
1140 self.obj.setService(jobClass.obj, serverType.obj, distribution.obj);
1141 end
1142 end
1143
1144 function distribution = getHeteroService(self, jobClass, serverType)
1145 % GETHETEROSERVICE Get service distribution for a job class and server type
1146 %
1147 % distribution = GETHETEROSERVICE(jobClass, serverType) returns the
1148 % service time distribution for a specific job class and server type.
1149 %
1150 % @param jobClass The JobClass object
1151 % @param serverType The ServerType object
1152 % @return distribution The service time Distribution, or [] if not set
1153
1154 if isempty(self.obj)
1155 if isKey(self.heteroServiceDistributions, serverType.getName())
1156 classMap = self.heteroServiceDistributions(serverType.getName());
1157 if isKey(classMap, jobClass.getName())
1158 distribution = classMap(jobClass.getName());
1159 else
1160 distribution = [];
1161 end
1162 else
1163 distribution = [];
1164 end
1165 else
1166 distObj = self.obj.getService(jobClass.obj, serverType.obj);
1167 if isempty(distObj)
1168 distribution = [];
1169 else
1170 distribution = Distribution.fromJavaObject(distObj);
1171 end
1172 end
1173 end
1174
1175 function st = getServerTypeById(self, id)
1176 % GETSERVERTYPEBYID Get a server type by its ID
1177 %
1178 % st = GETSERVERTYPEBYID(id) returns the ServerType with the given ID,
1179 % or [] if not found.
1180
1181 if id >= 0 && id < length(self.serverTypes)
1182 st = self.serverTypes{id + 1}; % MATLAB 1-indexed
1183 else
1184 st = [];
1185 end
1186 end
1187
1188 function st = getServerTypeByName(self, name)
1189 % GETSERVERTYPEBYNAME Get a server type by its name
1190 %
1191 % st = GETSERVERTYPEBYNAME(name) returns the ServerType with the given
1192 % name, or [] if not found.
1193
1194 st = [];
1195 for i = 1:length(self.serverTypes)
1196 if strcmp(self.serverTypes{i}.getName(), name)
1197 st = self.serverTypes{i};
1198 return;
1199 end
1200 end
1201 end
1202
1203 function result = validateCompatibility(self)
1204 % VALIDATECOMPATIBILITY Check all job classes have compatible server types
1205 %
1206 % result = VALIDATECOMPATIBILITY() returns true if all job classes
1207 % in the model have at least one compatible server type at this queue.
1208
1209 if ~self.isHeterogeneous()
1210 result = true;
1211 return;
1212 end
1213
1214 classes = self.model.getClasses();
1215 for c = 1:length(classes)
1216 jobClass = classes{c};
1217 hasCompatible = false;
1218 for s = 1:length(self.serverTypes)
1219 if self.serverTypes{s}.isCompatible(jobClass)
1220 hasCompatible = true;
1221 break;
1222 end
1223 end
1224 if ~hasCompatible
1225 result = false;
1226 return;
1227 end
1228 end
1229 result = true;
1230 end
1231
1232 % ==================== Immediate Feedback Methods ====================
1233
1234 function setImmediateFeedback(self, varargin)
1235 % SETIMMEDIATEFEEDBACK Set immediate feedback for self-loops
1236 %
1237 % SETIMMEDIATEFEEDBACK(true) enables immediate feedback for all classes
1238 % SETIMMEDIATEFEEDBACK(false) disables immediate feedback for all classes
1239 % SETIMMEDIATEFEEDBACK(jobClass) enables for a specific class
1240 % SETIMMEDIATEFEEDBACK({class1, class2}) enables for multiple classes
1241 %
1242 % When enabled, a job that self-loops at this station stays in service
1243 % instead of going back to the queue.
1244
1245 if isempty(self.obj)
1246 % MATLAB native implementation
1247 if nargin == 2
1248 arg = varargin{1};
1249 if islogical(arg) || isnumeric(arg)
1250 if arg
1251 % Enable for all classes
1252 self.immediateFeedback = 'all';
1253 else
1254 % Disable for all classes
1255 self.immediateFeedback = {};
1256 end
1257 elseif isa(arg, 'JobClass')
1258 % Single class
1259 if isempty(self.immediateFeedback) || ischar(self.immediateFeedback)
1260 self.immediateFeedback = {};
1261 end
1262 if ~any(cellfun(@(x) x == arg.index, self.immediateFeedback))
1263 self.immediateFeedback{end+1} = arg.index;
1264 end
1265 elseif iscell(arg)
1266 % Cell array of classes
1267 self.immediateFeedback = {};
1268 for i = 1:length(arg)
1269 if isa(arg{i}, 'JobClass')
1270 self.immediateFeedback{end+1} = arg{i}.index;
1271 end
1272 end
1273 end
1274 end
1275 else
1276 % Java native implementation
1277 if nargin == 2
1278 arg = varargin{1};
1279 if islogical(arg) || isnumeric(arg)
1280 self.obj.setImmediateFeedback(logical(arg));
1281 elseif isa(arg, 'JobClass')
1282 self.obj.setImmediateFeedback(arg.obj);
1283 elseif iscell(arg)
1284 classList = java.util.ArrayList();
1285 for i = 1:length(arg)
1286 if isa(arg{i}, 'JobClass')
1287 classList.add(arg{i}.obj);
1288 end
1289 end
1290 self.obj.setImmediateFeedbackForClasses(classList);
1291 end
1292 end
1293 end
1294 end
1295
1296 function tf = hasImmediateFeedback(self, varargin)
1297 % HASIMMEDIATEFEEDBACK Check if immediate feedback is enabled
1298 %
1299 % TF = HASIMMEDIATEFEEDBACK() returns true if enabled for any class
1300 % TF = HASIMMEDIATEFEEDBACK(jobClass) returns true if enabled for specific class
1301
1302 if isempty(self.obj)
1303 % MATLAB native implementation
1304 if isempty(self.immediateFeedback)
1305 tf = false;
1306 elseif ischar(self.immediateFeedback) && strcmp(self.immediateFeedback, 'all')
1307 tf = true;
1308 elseif nargin == 1
1309 % No class specified - check if any class has it enabled
1310 tf = ~isempty(self.immediateFeedback);
1311 else
1312 % Check specific class
1313 jobClass = varargin{1};
1314 if ischar(self.immediateFeedback) && strcmp(self.immediateFeedback, 'all')
1315 tf = true;
1316 else
1317 tf = any(cellfun(@(x) x == jobClass.index, self.immediateFeedback));
1318 end
1319 end
1320 else
1321 % Java native implementation
1322 if nargin == 1
1323 tf = self.obj.hasImmediateFeedback();
1324 else
1325 jobClass = varargin{1};
1326 tf = self.obj.hasImmediateFeedback(jobClass.index - 1); % Java 0-indexed
1327 end
1328 end
1329 end
1330
1331 function classes = getImmediateFeedbackClasses(self)
1332 % GETIMMEDIATEFEEDBACKCLASSES Get list of class indices with immediate feedback
1333 %
1334 % CLASSES = GETIMMEDIATEFEEDBACKCLASSES() returns cell array of class indices
1335
1336 if isempty(self.obj)
1337 if isempty(self.immediateFeedback)
1338 classes = {};
1339 elseif ischar(self.immediateFeedback) && strcmp(self.immediateFeedback, 'all')
1340 classes = 'all';
1341 else
1342 classes = self.immediateFeedback;
1343 end
1344 else
1345 jClasses = self.obj.getImmediateFeedbackClasses();
1346 if isempty(jClasses)
1347 classes = {};
1348 elseif jClasses.equals("all")
1349 classes = 'all';
1350 else
1351 classes = {};
1352 for i = 0:(jClasses.size()-1)
1353 classes{end+1} = jClasses.get(i) + 1; % Convert to MATLAB 1-indexed
1354 end
1355 end
1356 end
1357 end
1358
1359 end
1360end