1classdef Queue < ServiceStation
2 % A service station with queueing
4 % Copyright (c) 2012-2026, Imperial College London
15 balkingStrategies; % Cell array: per-
class BalkingStrategy constant
16 balkingThresholds; % Cell array: per-
class balking thresholds (list of {minJobs, maxJobs, probability})
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
32 function self = Queue(model, name, schedStrategy)
33 % SELF = QUEUE(MODEL, NAME, SCHEDSTRATEGY)
35 self@ServiceStation(name);
37 if model.isMatlabNative()
40 self.output = Dispatcher(
classes);
41 self.schedPolicy = SchedStrategyType.PR;
42 self.schedStrategy = SchedStrategy.PS;
43 self.serviceProcess = {};
45 self.numberOfServers = 1;
46 self.schedStrategyPar = zeros(1,length(model.getClasses()));
48 self.model.addNode(self);
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 = {};
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;
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;
87 case SchedStrategy.POLLING
88 self.schedPolicy = SchedStrategyType.NP;
89 self.server = PollingServer(
classes);
91 line_error(mfilename,sprintf(
'The specified scheduling strategy (%s) is unsupported.',schedStrategy));
94 elseif model.isJavaNative()
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);
156 self.obj.setNumberOfServers(1);
157 self.index = model.obj.getNodeIndex(self.obj);
161 function setLoadDependence(self, alpha)
162 switch SchedStrategy.toId(self.schedStrategy)
163 case {SchedStrategy.PS, SchedStrategy.FCFS}
164 setLimitedLoadDependence(self, alpha);
166 line_error(mfilename,
'Load-dependence supported only for processor sharing (PS) and first-come first-serve (FCFS) stations.');
170 function setClassDependence(self, beta)
171 switch SchedStrategy.toId(self.schedStrategy)
172 case {SchedStrategy.PS, SchedStrategy.FCFS}
173 setLimitedClassDependence(self, beta);
175 line_error(mfilename,
'Class-dependence supported only for processor sharing (PS) and first-come first-serve (FCFS) stations.');
179 function setJointDependence(self, scalingTable, cutoffs)
180 % SETJOINTDEPENDENCE(SCALINGTABLE, CUTOFFS)
182 % Sets joint
class-dependent scaling
for service rates
using a
183 % lookup table indexed by per-
class population vector.
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)))
191 switch SchedStrategy.toId(self.schedStrategy)
192 case {SchedStrategy.PS, SchedStrategy.FCFS}
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);
201 % Convert multi-dimensional to linearized
if needed
202 if ~isvector(scalingTable)
203 scalingTable = scalingTable(:)
'; % linearize
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));
212 setLimitedJointDependence(self, scalingTable, cutoffs);
214 line_error(mfilename,'Joint-dependence supported only for processor sharing (PS) and first-come first-serve (FCFS) stations.
');
218 function setJointClassDependence(self, scalingTables, cutoffs)
219 % SETJOINTCLASSDEPENDENCE(SCALINGTABLES, CUTOFFS)
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.
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.
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]
235 switch SchedStrategy.toId(self.schedStrategy)
236 case {SchedStrategy.PS, SchedStrategy.FCFS}
238 expectedSize = prod(cutoffs + 1);
240 if ~iscell(scalingTables) || length(scalingTables) ~= K
245 % Convert multi-dimensional to linearized if needed
246 if ~isvector(scalingTables{c})
247 scalingTables{c} = scalingTables{c}(:)';
251 line_error(mfilename, sprintf(
'scalingTables{%d} size (%d) does not match expected size from cutoffs (%d).', c, length(
scalingTables{c}), expectedSize));
255 setLimitedJointClassDependence(self,
scalingTables, cutoffs);
257 line_error(mfilename,
'Joint-class-dependence supported only for processor sharing (PS) and first-come first-serve (FCFS) stations.');
261 function setNumberOfServers(self, value)
262 % SETNUMBEROFSERVERS(VALUE)
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.');
269 self.setNumServers(value);
272 self.obj.setNumberOfServers(value);
276 function setNumServers(self, value)
277 % SETNUMSERVERS(VALUE)
279 switch SchedStrategy.toId(self.schedStrategy)
280 case {SchedStrategy.DPS, SchedStrategy.GPS}
282 line_error(mfilename,sprintf(
'Cannot use multi-server stations with %s scheduling.', self.schedStrategy));
285 self.numberOfServers = value;
288 self.obj.setNumberOfServers(value);
292 function self = setStrategyParam(self,
class, weight)
293 % SELF = SETSTRATEGYPARAM(CLASS, WEIGHT)
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
305 self.schedStrategyPar(class.index) = weight;
308 function distribution = getService(self, class)
309 % DISTRIBUTION = GETSERVICE(CLASS)
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};
319 distribution = self.server.serviceProcess{1,
class.index}{3};
322 line_warning(mfilename,
'No distribution is available for the specified class.\n');
327 function setService(self,
class, distribution, weight)
328 % SETSERVICE(CLASS, DISTRIBUTION, WEIGHT)
329 % distribution can be a Distribution
object or a Workflow
object
331 if nargin<4 %~exist(
'weight',
'var')
335 % If Workflow, convert to PH distribution
336 if isa(distribution, 'Workflow')
337 distribution = distribution.toPH();
340 if distribution.isImmediate()
341 distribution = Immediate.getInstance();
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.');
347 server = self.server; % by reference
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
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
362 else %
if first configuration
363 if length(self.classCap) < c
364 self.classCap((length(self.classCap)+1):c) = Inf;
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;
371 self.dropRule(c) = DropStrategy.WAITQ;
373 server.serviceProcess{1, c}{2} = ServiceStrategy.LI;
375 server.serviceProcess{1, c}{3} = distribution;
376 self.serviceProcess{c} = distribution;
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
382 self.serviceProcess{c} = distribution;
386 function setDelayOff(self,
jobclass, setupTime, delayoffTime)
388 self.setupTime{1, c} = setupTime;
389 self.delayoffTime{1, c} = delayoffTime;
392 function dist = getSetupTime(self,
jobclass)
394 if c <= length(self.setupTime) && ~isempty(self.setupTime{1, c})
395 dist = self.setupTime{1, c};
401 function dist = getDelayOffTime(self,
jobclass)
403 if c <= length(self.delayoffTime) && ~isempty(self.delayoffTime{1, c})
404 dist = self.delayoffTime{1, c};
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()));
415 K = length(self.model.getClasses());
416 self.switchoverTime = cell(K,K);
419 self.switchoverTime{r,s} = Immediate();
424 if length(varargin)==2
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');
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;
443 function setPollingType(self, rule, par)
444 if PollingType.toId(rule) ~= PollingType.KLIMITED
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');
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');
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());
461 function setPatience(self,
class, varargin)
462 % SETPATIENCE(CLASS, DISTRIBUTION) - Backwards compatible
463 % SETPATIENCE(CLASS, PATIENCETYPE, DISTRIBUTION) - Explicit type
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.
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)
475 % Note: This setting takes precedence over the global
class patience.
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))
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};
492 line_error(mfilename,
'Invalid number of arguments. Use setPatience(class, distribution) or setPatience(class, impatienceType, distribution)');
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.');
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.');
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.');
509 if distribution.isImmediate()
510 distribution = Immediate.getInstance();
515 self.patienceDistributions{1, c} = distribution;
516 self.impatienceTypes{1, c} = impatienceType;
518 self.obj.setPatience(
class.obj, impatienceType, distribution.obj);
522 function distribution = getPatience(self,
class)
523 % DISTRIBUTION = GETPATIENCE(CLASS)
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.
530 %
class - JobClass object
533 % distribution - The patience distribution, or []
if not set
537 % Check queue-specific patience first
538 if c <= length(self.patienceDistributions) && ~isempty(self.patienceDistributions{1, c})
539 distribution = self.patienceDistributions{1, c};
541 % Fall back to global
class patience
542 distribution =
class.getPatience();
545 distObj = self.obj.getPatience(
class.obj);
549 distribution = Distribution.fromJavaObject(distObj);
554 function setLimit(self, limit)
555 % SETLIMIT(LIMIT) Sets the maximum number of jobs
for LPS scheduling
558 % limit - Maximum number of jobs in PS (processor sharing) mode
for LPS
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.');
566 % Store limit in schedStrategyPar (use index 0
for queue-level parameter)
567 if length(self.schedStrategyPar) < 1
568 self.schedStrategyPar = zeros(1, 1);
570 self.schedStrategyPar(1) = limit;
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.');
577 self.obj.setLimit(limit);
581 function limit = getLimit(self)
582 % LIMIT = GETLIMIT() Returns the maximum number of jobs for LPS scheduling
585 % limit - Maximum number of jobs in PS mode for LPS
588 % MATLAB native implementation
589 if length(self.schedStrategyPar) >= 1
590 limit = self.schedStrategyPar(1);
595 % JavaNative implementation
596 limit = self.obj.getLimit();
600 function impatienceType = getImpatienceType(self, class)
601 % IMPATIENCETYPE = GETIMPATIENCETYPE(CLASS)
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.
608 % class - JobClass
object
611 % impatienceType - The impatience type (ImpatienceType constant), or [] if not set
615 % Check queue-specific impatience type first
616 if c <= length(self.impatienceTypes) && ~isempty(self.impatienceTypes{1, c})
617 impatienceType = self.impatienceTypes{1, c};
619 % Fall back to global
class impatience type
620 impatienceType =
class.getImpatienceType();
623 impatienceTypeId = self.obj.getImpatienceType(
class.obj);
624 if isempty(impatienceTypeId)
627 impatienceType = ImpatienceType.fromId(impatienceTypeId.getID());
632 function tf = hasPatience(self,
class)
633 % TF = HASPATIENCE(CLASS)
635 % Returns
true if this class has patience configured at this queue
636 % (either locally or globally).
638 dist = self.getPatience(
class);
639 tf = ~isempty(dist) && ~isa(dist,
'Disabled');
642 function setBalking(self,
class, strategy, thresholds)
643 % SETBALKING(CLASS, STRATEGY, THRESHOLDS)
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.
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.
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}});
667 self.balkingStrategies{1, c} = strategy;
668 self.balkingThresholds{1, c} = thresholds;
670 % Java native - convert thresholds to Java format
671 jThresholds = jline.util.BalkingThresholdList();
672 for i = 1:length(thresholds)
677 maxJobs = java.lang.Integer.MAX_VALUE;
680 jThresholds.add(jline.lang.BalkingThreshold(minJobs, maxJobs, probability));
682 % Convert strategy to Java
enum
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;
691 self.obj.setBalking(
class.obj, jStrategy, jThresholds);
695 function [strategy, thresholds] = getBalking(self,
class)
696 % [STRATEGY, THRESHOLDS] = GETBALKING(CLASS)
698 % Returns the balking configuration
for a specific job
class.
701 %
class - JobClass object
704 % strategy - BalkingStrategy constant, or []
if not configured
705 % thresholds - Cell array of {minJobs, maxJobs, probability} tuples
709 if c <= length(self.balkingStrategies) && ~isempty(self.balkingStrategies{1, c})
710 strategy = self.balkingStrategies{1, c};
711 thresholds = self.balkingThresholds{1, c};
717 jStrategy = self.obj.getBalkingStrategy(
class.obj);
718 if isempty(jStrategy)
722 strategy = BalkingStrategy.fromId(jStrategy.getId());
723 jThresholds = self.obj.getBalkingThresholds(
class.obj);
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
732 thresholds{end+1} = {jTh.getMinJobs(), maxJobs, jTh.getProbability()};
739 function tf = hasBalking(self,
class)
740 % TF = HASBALKING(CLASS)
742 % Returns
true if this class has balking configured at this queue.
744 [strategy, ~] = self.getBalking(
class);
745 tf = ~isempty(strategy);
748 function setRetrial(self,
class, delayDistribution, maxAttempts)
749 % SETRETRIAL(CLASS, DELAYDISTRIBUTION, MAXATTEMPTS)
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.
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
763 % % Retry with exponential delay, unlimited attempts
764 % queue.setRetrial(
jobclass, Exp(0.5), -1);
766 % % Retry up to 3 times with Erlang delay
767 % queue.setRetrial(
jobclass, Erlang(2, 0.3), 3);
770 maxAttempts = -1; % Unlimited by
default
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.');
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;
784 self.retrialMaxAttempts(c) = maxAttempts;
785 % Also set drop rule to RETRIAL or RETRIAL_WITH_LIMIT
787 self.dropRule(c) = DropStrategy.RETRIAL;
789 self.dropRule(c) = DropStrategy.RETRIAL_WITH_LIMIT;
792 self.obj.setRetrial(
class.obj, delayDistribution.obj, maxAttempts);
796 function [delayDistribution, maxAttempts] = getRetrial(self,
class)
797 % [DELAYDISTRIBUTION, MAXATTEMPTS] = GETRETRIAL(CLASS)
799 % Returns the retrial configuration
for a specific job
class.
802 %
class - JobClass object
805 % delayDistribution - Retrial delay distribution, or []
if not configured
806 % maxAttempts - Maximum retrial attempts (-1 = unlimited)
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);
818 delayDistribution = [];
822 distObj = self.obj.getRetrialDelayDistribution(class.obj);
824 delayDistribution = [];
827 delayDistribution = Distribution.fromJavaObject(distObj);
828 maxAttempts = self.obj.getMaxRetrialAttempts(class.obj);
833 function tf = hasRetrial(self, class)
834 % TF = HASRETRIAL(CLASS)
836 % Returns true if this class has retrial configured at this queue.
838 [dist, ~] = self.getRetrial(class);
839 tf = ~isempty(dist) && ~isa(dist, 'Disabled');
842 function setOrbitImpatience(self, class, distribution)
843 % SETORBITIMPATIENCE(CLASS, DISTRIBUTION)
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.
851 % class - JobClass
object
852 % distribution - Distribution for orbit abandonment time (e.g., Exp(gamma))
855 % queue.setOrbitImpatience(
jobclass, Exp(0.008)); % gamma = 0.008
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.');
863 self.orbitImpatienceDistributions{1, c} = distribution;
865 self.obj.setOrbitImpatience(
class.obj, distribution.obj);
869 function distribution = getOrbitImpatience(self,
class)
870 % DISTRIBUTION = GETORBITIMPATIENCE(CLASS)
872 % Returns the orbit impatience distribution
for a specific job
class.
875 %
class - JobClass object
878 % distribution - The orbit impatience distribution, or []
if not set
882 if c <= length(self.orbitImpatienceDistributions) && ~isempty(self.orbitImpatienceDistributions{1, c})
883 distribution = self.orbitImpatienceDistributions{1, c};
888 distObj = self.obj.getOrbitImpatience(
class.obj);
892 distribution = Distribution.fromJavaObject(distObj);
897 function tf = hasOrbitImpatience(self,
class)
898 % TF = HASORBITORBITIMPATIENCE(CLASS)
900 % Returns
true if this class has orbit impatience configured at this queue.
902 dist = self.getOrbitImpatience(
class);
903 tf = ~isempty(dist) && ~isa(dist,
'Disabled');
906 function setBatchRejectProbability(self,
class, p)
907 % SETBATCHREJECTPROBABILITY(CLASS,
P)
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.
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
918 % class - JobClass object
919 % p - Probability [0,1] that batch
is rejected vs partially admitted
920 % Default
is 0 (partial admission allowed)
923 % queue.setBatchRejectProbability(
jobclass, 0.4);
926 line_error(mfilename,
'Batch reject probability must be in [0, 1].');
931 % Ensure array
is large enough
932 if length(self.batchRejectProb) < c
933 self.batchRejectProb(end+1:c) = 0;
935 self.batchRejectProb(c) = p;
937 self.obj.setBatchRejectProbability(class.obj, p);
941 function p = getBatchRejectProbability(self, class)
942 %
P = GETBATCHREJECTPROBABILITY(CLASS)
944 % Returns the batch reject probability for a specific job class.
947 % class - JobClass
object
950 % p - Batch reject probability [0,1], or 0 if not set
954 if c <= length(self.batchRejectProb) && self.batchRejectProb(c) > 0
955 p = self.batchRejectProb(c);
957 p = 0; % Default: partial admission allowed
960 p = self.obj.getBatchRejectProbability(class.obj);
964 % function distrib = getServiceProcess(self, oclass)
965 % distrib = self.serviceProcess{oclass};
968 % ==================== Heterogeneous Server Methods ====================
970 function self = addServerType(self, serverType)
971 % ADDSERVERTYPE Add a server type to
this queue
973 % self = ADDSERVERTYPE(serverType) adds a ServerType to
this queue
974 %
for heterogeneous multiserver configuration.
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.
980 % @param serverType The ServerType
object to add
982 if isempty(serverType)
983 line_error(mfilename,
'Server type cannot be empty');
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());
994 % MATLAB native implementation
995 serverType.setId(length(self.serverTypes));
996 serverType.setParentQueue(self);
997 self.serverTypes{end+1} = serverType;
999 % Initialize service distribution
map for this server type
1000 self.heteroServiceDistributions(serverType.getName()) = containers.Map();
1002 % Update total number of servers
1003 self.updateTotalServerCount();
1005 % Java native - delegate to Java
object
1006 self.obj.addServerType(serverType.obj);
1007 % Also store locally
1008 self.serverTypes{end+1} = serverType;
1012 function updateTotalServerCount(self)
1013 % UPDATETOTALSERVERCOUNT Update total server count from all types
1015 % Internal method to recalculate numberOfServers.
1017 if isempty(self.serverTypes)
1021 for i = 1:length(self.serverTypes)
1022 total = total + self.serverTypes{i}.getNumOfServers();
1024 self.numberOfServers = total;
1027 function types = getServerTypes(self)
1028 % GETSERVERTYPES Get the list of server types
1030 % types = GETSERVERTYPES() returns a cell array of ServerType objects.
1032 types = self.serverTypes;
1035 function n = getNumServerTypes(self)
1036 % GETNUMSERVERTYPES Get the number of server types
1038 % n = GETNUMSERVERTYPES() returns the number of server types,
1039 % or 0 if this
is a homogeneous queue.
1041 n = length(self.serverTypes);
1044 function result = isHeterogeneous(self)
1045 % ISHETEROGENEOUS Check if this
is a heterogeneous multiserver queue
1047 % result = ISHETEROGENEOUS() returns true if server types are defined.
1049 result = ~isempty(self.serverTypes);
1052 function self = setHeteroSchedPolicy(self, policy)
1053 % SETHETEROSCHEDPOLICY Set the heterogeneous server scheduling policy
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.
1059 % @param policy HeteroSchedPolicy constant (ORDER, ALIS, ALFS, FAIRNESS, FSF, RAIS)
1061 if isempty(self.obj)
1062 self.heteroSchedPolicy = policy;
1064 % Convert to Java enum
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;
1079 self.obj.setHeteroSchedPolicy(jPolicy);
1080 self.heteroSchedPolicy = policy;
1084 function policy = getHeteroSchedPolicy(self)
1085 % GETHETEROSCHEDPOLICY Get the heterogeneous server scheduling policy
1087 % policy = GETHETEROSCHEDPOLICY() returns the HeteroSchedPolicy.
1089 policy = self.heteroSchedPolicy;
1092 function setHeteroService(self, jobClass, serverType, distribution)
1093 % SETHETEROSERVICE Set service distribution for a job class and server type
1095 % SETHETEROSERVICE(jobClass, serverType, distribution) sets the
1096 % service time distribution for a specific job class when served
1097 % by a specific server type.
1099 % @param jobClass The JobClass
object
1100 % @param serverType The ServerType
object
1101 % @param distribution The service time Distribution
1103 if isempty(jobClass)
1104 line_error(mfilename, 'Job class cannot be empty');
1106 if isempty(serverType)
1107 line_error(mfilename, 'Server type cannot be empty');
1109 if isempty(distribution)
1110 line_error(mfilename, 'Distribution cannot be empty');
1113 % Check if server type
is in this queue
1115 for i = 1:length(self.serverTypes)
1116 if self.serverTypes{i} == serverType
1122 line_error(mfilename,
'Server type ''%s'' is not added to this queue. Call addServerType() first.', serverType.getName());
1125 if isempty(self.obj)
1126 % MATLAB native implementation
1127 if ~isKey(self.heteroServiceDistributions, serverType.getName())
1128 self.heteroServiceDistributions(serverType.getName()) = containers.Map();
1130 classMap = self.heteroServiceDistributions(serverType.getName());
1131 classMap(jobClass.getName()) = distribution;
1132 self.heteroServiceDistributions(serverType.getName()) = classMap;
1134 % Ensure compatibility
1135 if ~serverType.isCompatible(jobClass)
1136 serverType.addCompatible(jobClass);
1139 % Java native - delegate to Java
object
1140 self.obj.setService(jobClass.obj, serverType.obj, distribution.obj);
1144 function distribution = getHeteroService(self, jobClass, serverType)
1145 % GETHETEROSERVICE Get service distribution for a job class and server type
1147 % distribution = GETHETEROSERVICE(jobClass, serverType) returns the
1148 % service time distribution for a specific job class and server type.
1150 % @param jobClass The JobClass
object
1151 % @param serverType The ServerType
object
1152 % @return distribution The service time Distribution, or [] if not set
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());
1166 distObj = self.obj.getService(jobClass.obj, serverType.obj);
1170 distribution = Distribution.fromJavaObject(distObj);
1175 function st = getServerTypeById(self,
id)
1176 % GETSERVERTYPEBYID Get a server type by its ID
1178 % st = GETSERVERTYPEBYID(
id) returns the ServerType with the given ID,
1179 % or [] if not found.
1181 if
id >= 0 &&
id < length(self.serverTypes)
1182 st = self.serverTypes{
id + 1}; % MATLAB 1-indexed
1188 function st = getServerTypeByName(self, name)
1189 % GETSERVERTYPEBYNAME Get a server type by its name
1191 % st = GETSERVERTYPEBYNAME(name) returns the ServerType with the given
1192 % name, or []
if not found.
1195 for i = 1:length(self.serverTypes)
1196 if strcmp(self.serverTypes{i}.getName(), name)
1197 st = self.serverTypes{i};
1203 function result = validateCompatibility(self)
1204 % VALIDATECOMPATIBILITY Check all job
classes have compatible server types
1206 % result = VALIDATECOMPATIBILITY() returns true if all job
classes
1207 % in the model have at least one compatible server type at this queue.
1209 if ~self.isHeterogeneous()
1214 classes = self.model.getClasses();
1217 hasCompatible =
false;
1218 for s = 1:length(self.serverTypes)
1219 if self.serverTypes{s}.isCompatible(jobClass)
1220 hasCompatible =
true;
1232 % ==================== Immediate Feedback Methods ====================
1234 function setImmediateFeedback(self, varargin)
1235 % SETIMMEDIATEFEEDBACK Set immediate feedback
for self-loops
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
1242 % When enabled, a job that self-loops at
this station stays in service
1243 % instead of going back to the queue.
1245 if isempty(self.obj)
1246 % MATLAB native implementation
1249 if islogical(arg) || isnumeric(arg)
1252 self.immediateFeedback =
'all';
1255 self.immediateFeedback = {};
1257 elseif isa(arg,
'JobClass')
1259 if isempty(self.immediateFeedback) || ischar(self.immediateFeedback)
1260 self.immediateFeedback = {};
1262 if ~any(cellfun(@(x) x == arg.index, self.immediateFeedback))
1263 self.immediateFeedback{end+1} = arg.index;
1267 self.immediateFeedback = {};
1268 for i = 1:length(arg)
1269 if isa(arg{i},
'JobClass')
1270 self.immediateFeedback{end+1} = arg{i}.index;
1276 % Java native implementation
1279 if islogical(arg) || isnumeric(arg)
1280 self.obj.setImmediateFeedback(logical(arg));
1281 elseif isa(arg,
'JobClass')
1282 self.obj.setImmediateFeedback(arg.obj);
1284 classList = java.util.ArrayList();
1285 for i = 1:length(arg)
1286 if isa(arg{i},
'JobClass')
1287 classList.add(arg{i}.obj);
1290 self.obj.setImmediateFeedbackForClasses(classList);
1296 function tf = hasImmediateFeedback(self, varargin)
1297 % HASIMMEDIATEFEEDBACK Check
if immediate feedback
is enabled
1299 % TF = HASIMMEDIATEFEEDBACK() returns true if enabled for any class
1300 % TF = HASIMMEDIATEFEEDBACK(jobClass) returns true if enabled for specific class
1302 if isempty(self.obj)
1303 % MATLAB native implementation
1304 if isempty(self.immediateFeedback)
1306 elseif ischar(self.immediateFeedback) && strcmp(self.immediateFeedback, 'all')
1309 % No class specified - check if any class has it enabled
1310 tf = ~isempty(self.immediateFeedback);
1312 % Check specific class
1313 jobClass = varargin{1};
1314 if ischar(self.immediateFeedback) && strcmp(self.immediateFeedback,
'all')
1317 tf = any(cellfun(@(x) x == jobClass.index, self.immediateFeedback));
1321 % Java native implementation
1323 tf = self.obj.hasImmediateFeedback();
1325 jobClass = varargin{1};
1326 tf = self.obj.hasImmediateFeedback(jobClass.index - 1); % Java 0-indexed
1331 function
classes = getImmediateFeedbackClasses(self)
1332 % GETIMMEDIATEFEEDBACKCLASSES Get list of
class indices with immediate feedback
1334 % CLASSES = GETIMMEDIATEFEEDBACKCLASSES() returns cell array of class indices
1336 if isempty(self.obj)
1337 if isempty(self.immediateFeedback)
1339 elseif ischar(self.immediateFeedback) && strcmp(self.immediateFeedback,
'all')
1342 classes = self.immediateFeedback;
1345 jClasses = self.obj.getImmediateFeedbackClasses();
1346 if isempty(jClasses)
1348 elseif jClasses.equals(
"all")
1352 for i = 0:(jClasses.size()-1)
1353 classes{end+1} = jClasses.get(i) + 1; % Convert to MATLAB 1-indexed