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 % Note: We no longer invalidate hasStruct here as it causes severe performance
360 % issues in iterative solvers like LN. The refreshRates/refreshProcesses methods
361 % called during solver post-iteration phase handle updating procid appropriately.
362 self.state=[]; % reset the state vector
363 %end
364 else % if first configuration
365 if length(self.classCap) < c
366 self.classCap((length(self.classCap)+1):c) = Inf;
367 end
368 self.setStrategyParam(class, weight);
369 % Default to Drop for finite capacity, WaitingQueue otherwise
370 if self.cap < intmax && ~isinf(self.cap)
371 self.dropRule(c) = DropStrategy.DROP;
372 else
373 self.dropRule(c) = DropStrategy.WAITQ;
374 end
375 server.serviceProcess{1, c}{2} = ServiceStrategy.LI;
376 end
377 server.serviceProcess{1, c}{3} = distribution;
378 self.serviceProcess{c} = distribution;
379 % Update cached procid if struct exists to avoid stale values
380 % This is needed because we don't invalidate hasStruct for performance
381 if self.model.hasStruct && ~isempty(self.model.sn)
382 ist = self.model.getStationIndex(self);
383 procTypeId = ProcessType.toId(ProcessType.fromText(builtin('class', distribution)));
384 self.model.sn.procid(ist, c) = procTypeId;
385 end
386 else
387 self.obj.setService(class.obj, distribution.obj, weight);
388 % Also update MATLAB-side storage to keep in sync with Java object
389 % This ensures getService returns the correct distribution
390 c = class.index;
391 self.serviceProcess{c} = distribution;
392 end
393 end
394
395 function setDelayOff(self, jobclass, setupTime, delayoffTime)
396 c = jobclass.index;
397 self.setupTime{1, c} = setupTime;
398 self.delayoffTime{1, c} = delayoffTime;
399 end
400
401 function dist = getSetupTime(self, jobclass)
402 c = jobclass.index;
403 if c <= length(self.setupTime) && ~isempty(self.setupTime{1, c})
404 dist = self.setupTime{1, c};
405 else
406 dist = [];
407 end
408 end
409
410 function dist = getDelayOffTime(self, jobclass)
411 c = jobclass.index;
412 if c <= length(self.delayoffTime) && ~isempty(self.delayoffTime{1, c})
413 dist = self.delayoffTime{1, c};
414 else
415 dist = [];
416 end
417 end
418
419 function setSwitchover(self, varargin)
420 if isempty(self.switchoverTime)
421 if SchedStrategy.toId(self.schedStrategy) == SchedStrategy.POLLING
422 self.switchoverTime = cell(1,length(self.model.getClasses()));
423 else
424 K = length(self.model.getClasses());
425 self.switchoverTime = cell(K,K);
426 for r=1:K
427 for s=1:K
428 self.switchoverTime{r,s} = Immediate();
429 end
430 end
431 end
432 end
433 if length(varargin)==2
434 jobclass = varargin{1};
435 soTime = varargin{2};
436 % time to switch from queue i to the next one
437 if SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.POLLING
438 line_error(mfilename,'setSwitchover(jobclass, distrib) can only be invoked on queues with SchedStrategy.POLLING.\n');
439 end
440 c = jobclass.index;
441 self.switchoverTime{1,c} = soTime;
442 elseif length(varargin)==3
443 jobclass_from = varargin{1};
444 jobclass_to = varargin{2};
445 soTime = varargin{3};
446 f = jobclass_from.index;
447 t = jobclass_to.index;
448 self.switchoverTime{f,t} = soTime;
449 end
450 end
451
452 function setPollingType(self, rule, par)
453 if PollingType.toId(rule) ~= PollingType.KLIMITED
454 par = [];
455 elseif PollingType.toId(rule) == PollingType.KLIMITED && nargin<3
456 line_error(mfilename,'K-Limited polling requires to specify the parameter K, e.g., setPollingType(PollingType.KLIMITED, 2).\n');
457 end
458 % support only identical polling type at each class buffer
459 if SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.POLLING
460 line_error(mfilename,'setPollingType can only be invoked on queues with SchedStrategy.POLLING.\n');
461 end
462 for r=1:length(self.model.getClasses())
463 self.pollingType{1,r} = rule;
464 self.pollingPar = par;
465 classes = self.model.getClasses();
466 setSwitchover(self, classes{r}, Immediate());
467 end
468 end
469
470 function setPatience(self, class, varargin)
471 % SETPATIENCE(CLASS, DISTRIBUTION) - Backwards compatible
472 % SETPATIENCE(CLASS, PATIENCETYPE, DISTRIBUTION) - Explicit type
473 %
474 % Sets the patience type and distribution for a specific job class at this queue.
475 % Jobs that wait longer than their patience time will abandon the queue.
476 %
477 % Parameters:
478 % class - JobClass object
479 % impatienceType - (Optional) ImpatienceType constant (RENEGING or BALKING)
480 % If omitted, defaults to ImpatienceType.RENEGING
481 % distribution - Any LINE distribution (Exp, Erlang, HyperExp, etc.)
482 % excluding modulated processes (BMAP, MAP, MMPP2)
483 %
484 % Note: This setting takes precedence over the global class patience.
485 %
486 % Examples:
487 % queue.setPatience(jobclass, Exp(0.2)) % Defaults to RENEGING
488 % queue.setPatience(jobclass, ImpatienceType.RENEGING, Exp(0.2))
489 % queue.setPatience(jobclass, ImpatienceType.BALKING, Exp(0.5))
490
491 % Handle backwards compatibility: 2 or 3 arguments
492 if length(varargin) == 1
493 % Old signature: setPatience(class, distribution)
494 distribution = varargin{1};
495 impatienceType = ImpatienceType.RENEGING; % Default to RENEGING
496 elseif length(varargin) == 2
497 % New signature: setPatience(class, impatienceType, distribution)
498 impatienceType = varargin{1};
499 distribution = varargin{2};
500 else
501 line_error(mfilename, 'Invalid number of arguments. Use setPatience(class, distribution) or setPatience(class, impatienceType, distribution)');
502 end
503
504 if isa(distribution, 'BMAP') || isa(distribution, 'MAP') || isa(distribution, 'MMPP2')
505 line_error(mfilename, 'Modulated processes (BMAP, MAP, MMPP2) are not supported for patience distributions.');
506 end
507
508 % Validate impatience type
509 if impatienceType ~= ImpatienceType.RENEGING && impatienceType ~= ImpatienceType.BALKING
510 line_error(mfilename, 'Invalid impatience type. Use ImpatienceType.RENEGING or ImpatienceType.BALKING.');
511 end
512
513 % Only RENEGING is currently supported
514 if impatienceType == ImpatienceType.BALKING
515 line_error(mfilename, 'BALKING impatience type is not yet supported. Use ImpatienceType.RENEGING.');
516 end
517
518 if distribution.isImmediate()
519 distribution = Immediate.getInstance();
520 end
521
522 if isempty(self.obj)
523 c = class.index;
524 self.patienceDistributions{1, c} = distribution;
525 self.impatienceTypes{1, c} = impatienceType;
526 else
527 self.obj.setPatience(class.obj, impatienceType, distribution.obj);
528 end
529 end
530
531 function distribution = getPatience(self, class)
532 % DISTRIBUTION = GETPATIENCE(CLASS)
533 %
534 % Returns the patience distribution for a specific job class.
535 % Returns the queue-specific setting if available, otherwise
536 % falls back to the global class patience.
537 %
538 % Parameters:
539 % class - JobClass object
540 %
541 % Returns:
542 % distribution - The patience distribution, or [] if not set
543
544 if isempty(self.obj)
545 c = class.index;
546 % Check queue-specific patience first
547 if c <= length(self.patienceDistributions) && ~isempty(self.patienceDistributions{1, c})
548 distribution = self.patienceDistributions{1, c};
549 else
550 % Fall back to global class patience
551 distribution = class.getPatience();
552 end
553 else
554 distObj = self.obj.getPatience(class.obj);
555 if isempty(distObj)
556 distribution = [];
557 else
558 distribution = Distribution.fromJavaObject(distObj);
559 end
560 end
561 end
562
563 function setLimit(self, limit)
564 % SETLIMIT(LIMIT) Sets the maximum number of jobs for LPS scheduling
565 %
566 % Parameters:
567 % limit - Maximum number of jobs in PS (processor sharing) mode for LPS
568
569 if isempty(self.obj)
570 % MATLAB native implementation - store as a scheduling parameter
571 if SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.LPS
572 line_warning(mfilename, 'setLimit is only applicable to LPS (Least Progress Scheduling) queues.');
573 return;
574 end
575 % Store limit in schedStrategyPar (use index 0 for queue-level parameter)
576 if length(self.schedStrategyPar) < 1
577 self.schedStrategyPar = zeros(1, 1);
578 end
579 self.schedStrategyPar(1) = limit;
580 else
581 % JavaNative implementation
582 if SchedStrategy.toId(self.schedStrategy) ~= SchedStrategy.LPS
583 line_warning(mfilename, 'setLimit is only applicable to LPS (Least Progress Scheduling) queues.');
584 return;
585 end
586 self.obj.setLimit(limit);
587 end
588 end
589
590 function limit = getLimit(self)
591 % LIMIT = GETLIMIT() Returns the maximum number of jobs for LPS scheduling
592 %
593 % Returns:
594 % limit - Maximum number of jobs in PS mode for LPS
595
596 if isempty(self.obj)
597 % MATLAB native implementation
598 if length(self.schedStrategyPar) >= 1
599 limit = self.schedStrategyPar(1);
600 else
601 limit = [];
602 end
603 else
604 % JavaNative implementation
605 limit = self.obj.getLimit();
606 end
607 end
608
609 function impatienceType = getImpatienceType(self, class)
610 % IMPATIENCETYPE = GETIMPATIENCETYPE(CLASS)
611 %
612 % Returns the impatience type for a specific job class.
613 % Returns the queue-specific setting if available, otherwise
614 % falls back to the global class impatience type.
615 %
616 % Parameters:
617 % class - JobClass object
618 %
619 % Returns:
620 % impatienceType - The impatience type (ImpatienceType constant), or [] if not set
621
622 if isempty(self.obj)
623 c = class.index;
624 % Check queue-specific impatience type first
625 if c <= length(self.impatienceTypes) && ~isempty(self.impatienceTypes{1, c})
626 impatienceType = self.impatienceTypes{1, c};
627 else
628 % Fall back to global class impatience type
629 impatienceType = class.getImpatienceType();
630 end
631 else
632 impatienceTypeId = self.obj.getImpatienceType(class.obj);
633 if isempty(impatienceTypeId)
634 impatienceType = [];
635 else
636 impatienceType = ImpatienceType.fromId(impatienceTypeId.getID());
637 end
638 end
639 end
640
641 function tf = hasPatience(self, class)
642 % TF = HASPATIENCE(CLASS)
643 %
644 % Returns true if this class has patience configured at this queue
645 % (either locally or globally).
646
647 dist = self.getPatience(class);
648 tf = ~isempty(dist) && ~isa(dist, 'Disabled');
649 end
650
651 function setBalking(self, class, strategy, thresholds)
652 % SETBALKING(CLASS, STRATEGY, THRESHOLDS)
653 %
654 % Configures balking behavior for a specific job class at this queue.
655 % When a customer arrives, they may refuse to join based on queue length.
656 %
657 % Parameters:
658 % class - JobClass object
659 % strategy - BalkingStrategy constant:
660 % QUEUE_LENGTH - Balk based on current queue length
661 % EXPECTED_WAIT - Balk based on expected waiting time
662 % COMBINED - Both conditions (OR logic)
663 % thresholds - Cell array of balking thresholds, each element is:
664 % {minJobs, maxJobs, probability}
665 % where probability is the chance to balk when queue
666 % length is in [minJobs, maxJobs] range.
667 %
668 % Example:
669 % % Balk with 30% probability when 5-10 jobs in queue,
670 % % 80% when 11-20 jobs, 100% when >20 jobs
671 % queue.setBalking(jobclass, BalkingStrategy.QUEUE_LENGTH, ...
672 % {{5, 10, 0.3}, {11, 20, 0.8}, {21, Inf, 1.0}});
673
674 if isempty(self.obj)
675 c = class.index;
676 self.balkingStrategies{1, c} = strategy;
677 self.balkingThresholds{1, c} = thresholds;
678 else
679 % Java native - convert thresholds to Java format
680 jThresholds = jline.util.BalkingThresholdList();
681 for i = 1:length(thresholds)
682 th = thresholds{i};
683 minJobs = th{1};
684 maxJobs = th{2};
685 if isinf(maxJobs)
686 maxJobs = java.lang.Integer.MAX_VALUE;
687 end
688 probability = th{3};
689 jThresholds.add(jline.lang.BalkingThreshold(minJobs, maxJobs, probability));
690 end
691 % Convert strategy to Java enum
692 switch strategy
693 case BalkingStrategy.QUEUE_LENGTH
694 jStrategy = jline.lang.constant.BalkingStrategy.QUEUE_LENGTH;
695 case BalkingStrategy.EXPECTED_WAIT
696 jStrategy = jline.lang.constant.BalkingStrategy.EXPECTED_WAIT;
697 case BalkingStrategy.COMBINED
698 jStrategy = jline.lang.constant.BalkingStrategy.COMBINED;
699 end
700 self.obj.setBalking(class.obj, jStrategy, jThresholds);
701 end
702 end
703
704 function [strategy, thresholds] = getBalking(self, class)
705 % [STRATEGY, THRESHOLDS] = GETBALKING(CLASS)
706 %
707 % Returns the balking configuration for a specific job class.
708 %
709 % Parameters:
710 % class - JobClass object
711 %
712 % Returns:
713 % strategy - BalkingStrategy constant, or [] if not configured
714 % thresholds - Cell array of {minJobs, maxJobs, probability} tuples
715
716 if isempty(self.obj)
717 c = class.index;
718 if c <= length(self.balkingStrategies) && ~isempty(self.balkingStrategies{1, c})
719 strategy = self.balkingStrategies{1, c};
720 thresholds = self.balkingThresholds{1, c};
721 else
722 strategy = [];
723 thresholds = {};
724 end
725 else
726 jStrategy = self.obj.getBalkingStrategy(class.obj);
727 if isempty(jStrategy)
728 strategy = [];
729 thresholds = {};
730 else
731 strategy = BalkingStrategy.fromId(jStrategy.getId());
732 jThresholds = self.obj.getBalkingThresholds(class.obj);
733 thresholds = {};
734 if ~isempty(jThresholds)
735 for i = 0:(jThresholds.size()-1)
736 jTh = jThresholds.get(i);
737 maxJobs = jTh.getMaxJobs();
738 if maxJobs == java.lang.Integer.MAX_VALUE
739 maxJobs = Inf;
740 end
741 thresholds{end+1} = {jTh.getMinJobs(), maxJobs, jTh.getProbability()};
742 end
743 end
744 end
745 end
746 end
747
748 function tf = hasBalking(self, class)
749 % TF = HASBALKING(CLASS)
750 %
751 % Returns true if this class has balking configured at this queue.
752
753 [strategy, ~] = self.getBalking(class);
754 tf = ~isempty(strategy);
755 end
756
757 function setRetrial(self, class, delayDistribution, maxAttempts)
758 % SETRETRIAL(CLASS, DELAYDISTRIBUTION, MAXATTEMPTS)
759 %
760 % Configures retrial behavior for a specific job class at this queue.
761 % When a customer is rejected (queue full), they move to an orbit
762 % and retry after a random delay.
763 %
764 % Parameters:
765 % class - JobClass object
766 % delayDistribution - Distribution for retrial delay (e.g., Exp(0.5))
767 % maxAttempts - Maximum number of retrial attempts:
768 % -1 = unlimited retries (default)
769 % N = drop after N failed attempts
770 %
771 % Example:
772 % % Retry with exponential delay, unlimited attempts
773 % queue.setRetrial(jobclass, Exp(0.5), -1);
774 %
775 % % Retry up to 3 times with Erlang delay
776 % queue.setRetrial(jobclass, Erlang(2, 0.3), 3);
777
778 if nargin < 4
779 maxAttempts = -1; % Unlimited by default
780 end
781
782 if isa(delayDistribution, 'BMAP') || isa(delayDistribution, 'MAP') || isa(delayDistribution, 'MMPP2')
783 line_error(mfilename, 'Modulated processes (BMAP, MAP, MMPP2) are not supported for retrial delay distributions.');
784 end
785
786 if isempty(self.obj)
787 c = class.index;
788 self.retrialDelays{1, c} = delayDistribution;
789 % Ensure array is large enough
790 if length(self.retrialMaxAttempts) < c
791 self.retrialMaxAttempts(end+1:c) = -1;
792 end
793 self.retrialMaxAttempts(c) = maxAttempts;
794 % Also set drop rule to RETRIAL or RETRIAL_WITH_LIMIT
795 if maxAttempts < 0
796 self.dropRule(c) = DropStrategy.RETRIAL;
797 else
798 self.dropRule(c) = DropStrategy.RETRIAL_WITH_LIMIT;
799 end
800 else
801 self.obj.setRetrial(class.obj, delayDistribution.obj, maxAttempts);
802 end
803 end
804
805 function [delayDistribution, maxAttempts] = getRetrial(self, class)
806 % [DELAYDISTRIBUTION, MAXATTEMPTS] = GETRETRIAL(CLASS)
807 %
808 % Returns the retrial configuration for a specific job class.
809 %
810 % Parameters:
811 % class - JobClass object
812 %
813 % Returns:
814 % delayDistribution - Retrial delay distribution, or [] if not configured
815 % maxAttempts - Maximum retrial attempts (-1 = unlimited)
816
817 if isempty(self.obj)
818 c = class.index;
819 if c <= length(self.retrialDelays) && ~isempty(self.retrialDelays{1, c})
820 delayDistribution = self.retrialDelays{1, c};
821 if c <= length(self.retrialMaxAttempts)
822 maxAttempts = self.retrialMaxAttempts(c);
823 else
824 maxAttempts = -1;
825 end
826 else
827 delayDistribution = [];
828 maxAttempts = -1;
829 end
830 else
831 distObj = self.obj.getRetrialDelayDistribution(class.obj);
832 if isempty(distObj)
833 delayDistribution = [];
834 maxAttempts = -1;
835 else
836 delayDistribution = Distribution.fromJavaObject(distObj);
837 maxAttempts = self.obj.getMaxRetrialAttempts(class.obj);
838 end
839 end
840 end
841
842 function tf = hasRetrial(self, class)
843 % TF = HASRETRIAL(CLASS)
844 %
845 % Returns true if this class has retrial configured at this queue.
846
847 [dist, ~] = self.getRetrial(class);
848 tf = ~isempty(dist) && ~isa(dist, 'Disabled');
849 end
850
851 function setOrbitImpatience(self, class, distribution)
852 % SETORBITIMPATIENCE(CLASS, DISTRIBUTION)
853 %
854 % Sets the impatience (abandonment) rate for customers in the orbit.
855 % This is separate from queue patience (reneging from waiting queue).
856 % Used in BMAP/PH/N/N retrial queues where customers in the orbit
857 % may abandon before successfully retrying.
858 %
859 % Parameters:
860 % class - JobClass object
861 % distribution - Distribution for orbit abandonment time (e.g., Exp(gamma))
862 %
863 % Example:
864 % queue.setOrbitImpatience(jobclass, Exp(0.008)); % gamma = 0.008
865
866 if isa(distribution, 'BMAP') || isa(distribution, 'MAP') || isa(distribution, 'MMPP2')
867 line_error(mfilename, 'Modulated processes (BMAP, MAP, MMPP2) are not supported for orbit impatience distributions.');
868 end
869
870 if isempty(self.obj)
871 c = class.index;
872 self.orbitImpatienceDistributions{1, c} = distribution;
873 else
874 self.obj.setOrbitImpatience(class.obj, distribution.obj);
875 end
876 end
877
878 function distribution = getOrbitImpatience(self, class)
879 % DISTRIBUTION = GETORBITIMPATIENCE(CLASS)
880 %
881 % Returns the orbit impatience distribution for a specific job class.
882 %
883 % Parameters:
884 % class - JobClass object
885 %
886 % Returns:
887 % distribution - The orbit impatience distribution, or [] if not set
888
889 if isempty(self.obj)
890 c = class.index;
891 if c <= length(self.orbitImpatienceDistributions) && ~isempty(self.orbitImpatienceDistributions{1, c})
892 distribution = self.orbitImpatienceDistributions{1, c};
893 else
894 distribution = [];
895 end
896 else
897 distObj = self.obj.getOrbitImpatience(class.obj);
898 if isempty(distObj)
899 distribution = [];
900 else
901 distribution = Distribution.fromJavaObject(distObj);
902 end
903 end
904 end
905
906 function tf = hasOrbitImpatience(self, class)
907 % TF = HASORBITORBITIMPATIENCE(CLASS)
908 %
909 % Returns true if this class has orbit impatience configured at this queue.
910
911 dist = self.getOrbitImpatience(class);
912 tf = ~isempty(dist) && ~isa(dist, 'Disabled');
913 end
914
915 function setBatchRejectProbability(self, class, p)
916 % SETBATCHREJECTPROBABILITY(CLASS, P)
917 %
918 % Sets the probability that an entire batch is rejected when it
919 % cannot be fully admitted. Used in BMAP/PH/N/N retrial queues
920 % with batch arrivals.
921 %
922 % When a batch of size k arrives and only m < k servers are free:
923 % - With probability p: entire batch is rejected to orbit
924 % - With probability (1-p): m customers are admitted, k-m go to orbit
925 %
926 % Parameters:
927 % class - JobClass object
928 % p - Probability [0,1] that batch is rejected vs partially admitted
929 % Default is 0 (partial admission allowed)
930 %
931 % Example:
932 % queue.setBatchRejectProbability(jobclass, 0.4);
933
934 if p < 0 || p > 1
935 line_error(mfilename, 'Batch reject probability must be in [0, 1].');
936 end
937
938 if isempty(self.obj)
939 c = class.index;
940 % Ensure array is large enough
941 if length(self.batchRejectProb) < c
942 self.batchRejectProb(end+1:c) = 0;
943 end
944 self.batchRejectProb(c) = p;
945 else
946 self.obj.setBatchRejectProbability(class.obj, p);
947 end
948 end
949
950 function p = getBatchRejectProbability(self, class)
951 % P = GETBATCHREJECTPROBABILITY(CLASS)
952 %
953 % Returns the batch reject probability for a specific job class.
954 %
955 % Parameters:
956 % class - JobClass object
957 %
958 % Returns:
959 % p - Batch reject probability [0,1], or 0 if not set
960
961 if isempty(self.obj)
962 c = class.index;
963 if c <= length(self.batchRejectProb) && self.batchRejectProb(c) > 0
964 p = self.batchRejectProb(c);
965 else
966 p = 0; % Default: partial admission allowed
967 end
968 else
969 p = self.obj.getBatchRejectProbability(class.obj);
970 end
971 end
972
973 % function distrib = getServiceProcess(self, oclass)
974 % distrib = self.serviceProcess{oclass};
975 % end
976
977 % ==================== Heterogeneous Server Methods ====================
978
979 function self = addServerType(self, serverType)
980 % ADDSERVERTYPE Add a server type to this queue
981 %
982 % self = ADDSERVERTYPE(serverType) adds a ServerType to this queue
983 % for heterogeneous multiserver configuration.
984 %
985 % When server types are added, the queue becomes a heterogeneous
986 % multiserver queue where different server types can have different
987 % service rates and serve different subsets of job classes.
988 %
989 % @param serverType The ServerType object to add
990
991 if isempty(serverType)
992 line_error(mfilename, 'Server type cannot be empty');
993 end
994
995 % Check if already added
996 for i = 1:length(self.serverTypes)
997 if self.serverTypes{i} == serverType
998 line_error(mfilename, 'Server type ''%s'' is already added to this queue', serverType.getName());
999 end
1000 end
1001
1002 if isempty(self.obj)
1003 % MATLAB native implementation
1004 serverType.setId(length(self.serverTypes));
1005 serverType.setParentQueue(self);
1006 self.serverTypes{end+1} = serverType;
1007
1008 % Initialize service distribution map for this server type
1009 self.heteroServiceDistributions(serverType.getName()) = containers.Map();
1010
1011 % Update total number of servers
1012 self.updateTotalServerCount();
1013 else
1014 % Java native - delegate to Java object
1015 self.obj.addServerType(serverType.obj);
1016 % Also store locally
1017 self.serverTypes{end+1} = serverType;
1018 end
1019 end
1020
1021 function updateTotalServerCount(self)
1022 % UPDATETOTALSERVERCOUNT Update total server count from all types
1023 %
1024 % Internal method to recalculate numberOfServers.
1025
1026 if isempty(self.serverTypes)
1027 return;
1028 end
1029 total = 0;
1030 for i = 1:length(self.serverTypes)
1031 total = total + self.serverTypes{i}.getNumOfServers();
1032 end
1033 self.numberOfServers = total;
1034 end
1035
1036 function types = getServerTypes(self)
1037 % GETSERVERTYPES Get the list of server types
1038 %
1039 % types = GETSERVERTYPES() returns a cell array of ServerType objects.
1040
1041 types = self.serverTypes;
1042 end
1043
1044 function n = getNumServerTypes(self)
1045 % GETNUMSERVERTYPES Get the number of server types
1046 %
1047 % n = GETNUMSERVERTYPES() returns the number of server types,
1048 % or 0 if this is a homogeneous queue.
1049
1050 n = length(self.serverTypes);
1051 end
1052
1053 function result = isHeterogeneous(self)
1054 % ISHETEROGENEOUS Check if this is a heterogeneous multiserver queue
1055 %
1056 % result = ISHETEROGENEOUS() returns true if server types are defined.
1057
1058 result = ~isempty(self.serverTypes);
1059 end
1060
1061 function self = setHeteroSchedPolicy(self, policy)
1062 % SETHETEROSCHEDPOLICY Set the heterogeneous server scheduling policy
1063 %
1064 % self = SETHETEROSCHEDPOLICY(policy) sets the policy that determines
1065 % how jobs are assigned to server types when a job's class is
1066 % compatible with multiple server types.
1067 %
1068 % @param policy HeteroSchedPolicy constant (ORDER, ALIS, ALFS, FAIRNESS, FSF, RAIS)
1069
1070 if isempty(self.obj)
1071 self.heteroSchedPolicy = policy;
1072 else
1073 % Convert to Java enum
1074 switch policy
1075 case HeteroSchedPolicy.ORDER
1076 jPolicy = jline.lang.constant.HeteroSchedPolicy.ORDER;
1077 case HeteroSchedPolicy.ALIS
1078 jPolicy = jline.lang.constant.HeteroSchedPolicy.ALIS;
1079 case HeteroSchedPolicy.ALFS
1080 jPolicy = jline.lang.constant.HeteroSchedPolicy.ALFS;
1081 case HeteroSchedPolicy.FAIRNESS
1082 jPolicy = jline.lang.constant.HeteroSchedPolicy.FAIRNESS;
1083 case HeteroSchedPolicy.FSF
1084 jPolicy = jline.lang.constant.HeteroSchedPolicy.FSF;
1085 case HeteroSchedPolicy.RAIS
1086 jPolicy = jline.lang.constant.HeteroSchedPolicy.RAIS;
1087 end
1088 self.obj.setHeteroSchedPolicy(jPolicy);
1089 self.heteroSchedPolicy = policy;
1090 end
1091 end
1092
1093 function policy = getHeteroSchedPolicy(self)
1094 % GETHETEROSCHEDPOLICY Get the heterogeneous server scheduling policy
1095 %
1096 % policy = GETHETEROSCHEDPOLICY() returns the HeteroSchedPolicy.
1097
1098 policy = self.heteroSchedPolicy;
1099 end
1100
1101 function setHeteroService(self, jobClass, serverType, distribution)
1102 % SETHETEROSERVICE Set service distribution for a job class and server type
1103 %
1104 % SETHETEROSERVICE(jobClass, serverType, distribution) sets the
1105 % service time distribution for a specific job class when served
1106 % by a specific server type.
1107 %
1108 % @param jobClass The JobClass object
1109 % @param serverType The ServerType object
1110 % @param distribution The service time Distribution
1111
1112 if isempty(jobClass)
1113 line_error(mfilename, 'Job class cannot be empty');
1114 end
1115 if isempty(serverType)
1116 line_error(mfilename, 'Server type cannot be empty');
1117 end
1118 if isempty(distribution)
1119 line_error(mfilename, 'Distribution cannot be empty');
1120 end
1121
1122 % Check if server type is in this queue
1123 found = false;
1124 for i = 1:length(self.serverTypes)
1125 if self.serverTypes{i} == serverType
1126 found = true;
1127 break;
1128 end
1129 end
1130 if ~found
1131 line_error(mfilename, 'Server type ''%s'' is not added to this queue. Call addServerType() first.', serverType.getName());
1132 end
1133
1134 if isempty(self.obj)
1135 % MATLAB native implementation
1136 if ~isKey(self.heteroServiceDistributions, serverType.getName())
1137 self.heteroServiceDistributions(serverType.getName()) = containers.Map();
1138 end
1139 classMap = self.heteroServiceDistributions(serverType.getName());
1140 classMap(jobClass.getName()) = distribution;
1141 self.heteroServiceDistributions(serverType.getName()) = classMap;
1142
1143 % Ensure compatibility
1144 if ~serverType.isCompatible(jobClass)
1145 serverType.addCompatible(jobClass);
1146 end
1147 else
1148 % Java native - delegate to Java object
1149 self.obj.setService(jobClass.obj, serverType.obj, distribution.obj);
1150 end
1151 end
1152
1153 function distribution = getHeteroService(self, jobClass, serverType)
1154 % GETHETEROSERVICE Get service distribution for a job class and server type
1155 %
1156 % distribution = GETHETEROSERVICE(jobClass, serverType) returns the
1157 % service time distribution for a specific job class and server type.
1158 %
1159 % @param jobClass The JobClass object
1160 % @param serverType The ServerType object
1161 % @return distribution The service time Distribution, or [] if not set
1162
1163 if isempty(self.obj)
1164 if isKey(self.heteroServiceDistributions, serverType.getName())
1165 classMap = self.heteroServiceDistributions(serverType.getName());
1166 if isKey(classMap, jobClass.getName())
1167 distribution = classMap(jobClass.getName());
1168 else
1169 distribution = [];
1170 end
1171 else
1172 distribution = [];
1173 end
1174 else
1175 distObj = self.obj.getService(jobClass.obj, serverType.obj);
1176 if isempty(distObj)
1177 distribution = [];
1178 else
1179 distribution = Distribution.fromJavaObject(distObj);
1180 end
1181 end
1182 end
1183
1184 function st = getServerTypeById(self, id)
1185 % GETSERVERTYPEBYID Get a server type by its ID
1186 %
1187 % st = GETSERVERTYPEBYID(id) returns the ServerType with the given ID,
1188 % or [] if not found.
1189
1190 if id >= 0 && id < length(self.serverTypes)
1191 st = self.serverTypes{id + 1}; % MATLAB 1-indexed
1192 else
1193 st = [];
1194 end
1195 end
1196
1197 function st = getServerTypeByName(self, name)
1198 % GETSERVERTYPEBYNAME Get a server type by its name
1199 %
1200 % st = GETSERVERTYPEBYNAME(name) returns the ServerType with the given
1201 % name, or [] if not found.
1202
1203 st = [];
1204 for i = 1:length(self.serverTypes)
1205 if strcmp(self.serverTypes{i}.getName(), name)
1206 st = self.serverTypes{i};
1207 return;
1208 end
1209 end
1210 end
1211
1212 function result = validateCompatibility(self)
1213 % VALIDATECOMPATIBILITY Check all job classes have compatible server types
1214 %
1215 % result = VALIDATECOMPATIBILITY() returns true if all job classes
1216 % in the model have at least one compatible server type at this queue.
1217
1218 if ~self.isHeterogeneous()
1219 result = true;
1220 return;
1221 end
1222
1223 classes = self.model.getClasses();
1224 for c = 1:length(classes)
1225 jobClass = classes{c};
1226 hasCompatible = false;
1227 for s = 1:length(self.serverTypes)
1228 if self.serverTypes{s}.isCompatible(jobClass)
1229 hasCompatible = true;
1230 break;
1231 end
1232 end
1233 if ~hasCompatible
1234 result = false;
1235 return;
1236 end
1237 end
1238 result = true;
1239 end
1240
1241 % ==================== Immediate Feedback Methods ====================
1242
1243 function setImmediateFeedback(self, varargin)
1244 % SETIMMEDIATEFEEDBACK Set immediate feedback for self-loops
1245 %
1246 % SETIMMEDIATEFEEDBACK(true) enables immediate feedback for all classes
1247 % SETIMMEDIATEFEEDBACK(false) disables immediate feedback for all classes
1248 % SETIMMEDIATEFEEDBACK(jobClass) enables for a specific class
1249 % SETIMMEDIATEFEEDBACK({class1, class2}) enables for multiple classes
1250 %
1251 % When enabled, a job that self-loops at this station stays in service
1252 % instead of going back to the queue.
1253
1254 if isempty(self.obj)
1255 % MATLAB native implementation
1256 if nargin == 2
1257 arg = varargin{1};
1258 if islogical(arg) || isnumeric(arg)
1259 if arg
1260 % Enable for all classes
1261 self.immediateFeedback = 'all';
1262 else
1263 % Disable for all classes
1264 self.immediateFeedback = {};
1265 end
1266 elseif isa(arg, 'JobClass')
1267 % Single class
1268 if isempty(self.immediateFeedback) || ischar(self.immediateFeedback)
1269 self.immediateFeedback = {};
1270 end
1271 if ~any(cellfun(@(x) x == arg.index, self.immediateFeedback))
1272 self.immediateFeedback{end+1} = arg.index;
1273 end
1274 elseif iscell(arg)
1275 % Cell array of classes
1276 self.immediateFeedback = {};
1277 for i = 1:length(arg)
1278 if isa(arg{i}, 'JobClass')
1279 self.immediateFeedback{end+1} = arg{i}.index;
1280 end
1281 end
1282 end
1283 end
1284 else
1285 % Java native implementation
1286 if nargin == 2
1287 arg = varargin{1};
1288 if islogical(arg) || isnumeric(arg)
1289 self.obj.setImmediateFeedback(logical(arg));
1290 elseif isa(arg, 'JobClass')
1291 self.obj.setImmediateFeedback(arg.obj);
1292 elseif iscell(arg)
1293 classList = java.util.ArrayList();
1294 for i = 1:length(arg)
1295 if isa(arg{i}, 'JobClass')
1296 classList.add(arg{i}.obj);
1297 end
1298 end
1299 self.obj.setImmediateFeedbackForClasses(classList);
1300 end
1301 end
1302 end
1303 end
1304
1305 function tf = hasImmediateFeedback(self, varargin)
1306 % HASIMMEDIATEFEEDBACK Check if immediate feedback is enabled
1307 %
1308 % TF = HASIMMEDIATEFEEDBACK() returns true if enabled for any class
1309 % TF = HASIMMEDIATEFEEDBACK(jobClass) returns true if enabled for specific class
1310
1311 if isempty(self.obj)
1312 % MATLAB native implementation
1313 if isempty(self.immediateFeedback)
1314 tf = false;
1315 elseif ischar(self.immediateFeedback) && strcmp(self.immediateFeedback, 'all')
1316 tf = true;
1317 elseif nargin == 1
1318 % No class specified - check if any class has it enabled
1319 tf = ~isempty(self.immediateFeedback);
1320 else
1321 % Check specific class
1322 jobClass = varargin{1};
1323 if ischar(self.immediateFeedback) && strcmp(self.immediateFeedback, 'all')
1324 tf = true;
1325 else
1326 tf = any(cellfun(@(x) x == jobClass.index, self.immediateFeedback));
1327 end
1328 end
1329 else
1330 % Java native implementation
1331 if nargin == 1
1332 tf = self.obj.hasImmediateFeedback();
1333 else
1334 jobClass = varargin{1};
1335 tf = self.obj.hasImmediateFeedback(jobClass.index - 1); % Java 0-indexed
1336 end
1337 end
1338 end
1339
1340 function classes = getImmediateFeedbackClasses(self)
1341 % GETIMMEDIATEFEEDBACKCLASSES Get list of class indices with immediate feedback
1342 %
1343 % CLASSES = GETIMMEDIATEFEEDBACKCLASSES() returns cell array of class indices
1344
1345 if isempty(self.obj)
1346 if isempty(self.immediateFeedback)
1347 classes = {};
1348 elseif ischar(self.immediateFeedback) && strcmp(self.immediateFeedback, 'all')
1349 classes = 'all';
1350 else
1351 classes = self.immediateFeedback;
1352 end
1353 else
1354 jClasses = self.obj.getImmediateFeedbackClasses();
1355 if isempty(jClasses)
1356 classes = {};
1357 elseif jClasses.equals("all")
1358 classes = 'all';
1359 else
1360 classes = {};
1361 for i = 0:(jClasses.size()-1)
1362 classes{end+1} = jClasses.get(i) + 1; % Convert to MATLAB 1-indexed
1363 end
1364 end
1365 end
1366 end
1367
1368 end
1369end