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