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