1function [outspace, outrate, outprob, eventCache] = afterEventStation(sn, ind, inspace, event,
class, isSimulation, eventCache, ...
2 M, R, S, phasessz, phaseshift, pie, isf, ismkvmod, ismkvmodclass, lldscaling, lldlimit, cdscaling, ...
3 hasOnlyExp, ist, K, Ks, mu, phi, proc, capacity, classcap, V, space_buf, space_srv, space_var, key, noPromote)
4% NOPROMOTE (optional,
default false): when true, a DEP at an FCFS-family
5% station does not promote the head-of-buffer job into the vacated server.
6% It
is set only for the active (departure) half of an immediate-feedback
7% self-loop (sn.immfeed), so the job that self-loops holds the server and the
8% subsequent passive arrival re-enters service instead of re-queueing behind
9% the waiting jobs. See State.afterEvent and solver_ssa.
10if nargin < 34 || isempty(noPromote)
13% A retrial station keeps blocked arrivals in an orbit (represented by the
14% buffer slots) instead of an ordered waiting line: on service completion the
15% freed server
is NOT filled from the orbit (no promotion); orbiting jobs
16% re-enter only through RETRY events at the memoryless retrial rate.
17isRetrialStation = isfield(sn,
'retrialProc') && ~isempty(sn.retrialProc) ...
18 && ist > 0 && any(~cellfun(@isempty, sn.retrialProc(ist,:)));
22% Pass-and-swap / order-independent stations use a dedicated ordered-list
23% representation and rate function mu(c); handle them separately.
24if sn.sched(ist) == SchedStrategy.PAS
25 [outspace, outrate, outprob, eventCache] = State.afterEventStationPAS(sn, ind, ist, inspace, event,
class, isSimulation, eventCache, R, V, key);
28% Server breakdown. The status
is the trailing local-variable
column (0 = down,
29% 1 = up), exclusive with the BAS marker and the polling controller. A down
30% server does not serve, so DEP and PHASE are suppressed unless a degraded
31% down-server rate was configured, in which
case the completion rate
is rescaled
32% by downRate/upRate at the end of the handler. Gating here keeps every
33% scheduling branch below unaware of the server status.
34isBreakdownStation = isfield(sn,
'hasbreakdown') && ~isempty(sn.hasbreakdown) ...
35 && numel(sn.hasbreakdown) >= ind && sn.hasbreakdown(ind) == 1;
37if isBreakdownStation && ~isempty(inspace) && (
event == EventType.DEP ||
event == EventType.PHASE)
38 if inspace(1,end) == 0 % server down
40 if ~isempty(sn.downServiceRates) && size(sn.downServiceRates,1) >= ist ...
41 && size(sn.downServiceRates,2) >=
class
42 downRate = sn.downServiceRates(ist,
class);
50 upRate = sn.rates(ist,
class);
51 if ~isfinite(upRate) || upRate <= 0
52 line_error(mfilename, sprintf([
'Station ''%s'' declares a down-server service rate for class ''%s'' but has ' ...
53 'no finite up-server service rate to rescale.'], sn.nodenames{ind}, sn.classnames{class}));
55 downRateScale = downRate / upRate;
59 case EventType.FAILURE
60 % An up server fails at the memoryless rate breakdownMu, whether or not
61 % it
is serving. Only the status
column changes: jobs in service are not
62 % lost and, service being memoryless here, they resume on repair. The
63 % passive half of the synchronization
is LOCAL, so no job moves.
67 if isBreakdownStation && ~isempty(inspace) && inspace(1,end) == 1
70 outrate = sn.breakdownMu(ist);
74 % A down server
is restored at the memoryless rate repairMu.
78 if isBreakdownStation && ~isempty(inspace) && inspace(1,end) == 0
81 outrate = sn.repairMu(ist);
84 case EventType.ARV %% passive
85 % A Place holds a marking, not a service facility: it has no servers and
86 % no service phases, so an arriving token only increments the
class
87 % marking. The scheduling branches below would instead write an entering
88 % job into a phase slot that a Place state does not carry; MATLAB grows
89 % the row to fit, and the widened state can no longer be matched against
90 % the marking state space, so the successor hash fails and the arrival
is
91 % dropped. Closed SPNs never notice, because there tokens reach a Place
92 % through FIRE (State.afterGlobalEvent) rather than through ARV.
93 if sn.nodetype(ind) == NodeType.Place
95 outspace(:,class) = outspace(:,class) + 1;
96 % passive action: the rate
is set by the active node
97 outrate = -1*ones(size(outspace,1),1);
98 outprob = ones(size(outspace,1),1);
101 % Signal / catastrophe arrival: a signal class
is a G-network negative
102 % customer. Instead of joining, it removes job(s) of its target class
103 % from this station and
is itself annihilated. CATASTROPHE empties the
104 % station of all jobs. The event stays passive (rate set by the active
105 % signal source); when there
is no target job the signal simply vanishes
106 % (destination state unchanged).
107 if isfield(sn,'issignal') && ~isempty(sn.issignal) && sn.issignal(class)
108 [outspace, outrate, outprob] = State.afterEventStationSignal(sn, ind, ist, inspace, class, K, Ks, S, pie, space_buf, space_srv, space_var);
109 % Which job a signal removes
is in general a random choice, so the
110 % generator needs every destination and its probability. A simulation
111 % instead walks one sample path, so it draws a single successor from
112 % outprob, as the balking branch below does for its passive action.
113 if isSimulation && size(outprob,1) > 1
114 cum_prob = cumsum(outprob) / sum(outprob);
115 firing_ctr = 1 + max([0,find( rand > cum_prob' )]); % select action
116 outspace = outspace(firing_ctr,:);
122 % Ordinary SPN Place arrival: see _kb/11-conventions-and-gotchas.md
123 % ("An ordinary Place must be special-cased before the generic
124 % scheduling switch") for the rationale.
125 isOrdinaryPlace = isfield(sn,'nodetype') && ~isempty(sn.nodetype) ...
126 && sn.nodetype(ind) == NodeType.Place;
127 if isOrdinaryPlace && isfield(sn,'isqueueingplace') && ~isempty(sn.isqueueingplace) ...
128 && ist <= numel(sn.isqueueingplace) && sn.isqueueingplace(ist)
129 isOrdinaryPlace = false;
133 nrows = size(outspace,1);
134 if inspace(1,class) < classcap(ist,class)
135 outspace(:,class) = outspace(:,class) + 1;
136 outrate = -ones(nrows,1); % passive: rate set by active source
137 outprob = ones(nrows,1);
139 % place full: arrival
is blocked and lost (no state change)
140 outrate = -ones(nrows,1);
141 outprob = zeros(nrows,1);
145 % return if there
is no space to accept the arrival
146 [ni,nir] = State.toMarginalAggr(sn,ind,inspace,K,Ks,space_buf,space_srv,space_var);
147 % otherwise check scheduling strategy
148 pentry = pie{ist}{
class};
149 % For Place
nodes (INF scheduling with NaN service), use uniform entry probability
150 if all(isnan(pentry))
151 pentry = ones(size(pentry)) / length(pentry);
155 for kentry = 1:K(
class)
156 space_var_k = space_var;
157 space_srv_k = space_srv;
158 space_buf_k = space_buf;
160 case SchedStrategy.EXT % source, can receive any "virtual" arrival from the sink as
long as it
is from an open class
161 if isinf(sn.njobs(class))
163 outrate = -1*zeros(size(outspace,1)); % passive action, rate
is unspecified
164 outprob = ones(size(outspace,1));
167 case {SchedStrategy.PS, SchedStrategy.INF, SchedStrategy.DPS, SchedStrategy.GPS, SchedStrategy.PSPRIO, SchedStrategy.DPSPRIO, SchedStrategy.GPSPRIO, SchedStrategy.LPS}
168 % job enters service immediately
169 if space_srv_k(:,Ks(class)+kentry) < classcap(ist,class)
170 space_srv_k(:,Ks(class)+kentry) = space_srv_k(:,Ks(class)+kentry) + 1;
171 outprob_k = pentry(kentry)*ones(size(space_srv_k,1));
173 outprob_k = pentry(kentry)*zeros(size(space_srv_k,1));
175 case {SchedStrategy.SIRO, SchedStrategy.SEPT, SchedStrategy.LEPT}
176 % Idle-server test on server occupancy, not total count ni: these
177 % agree
for work-conserving states but an immediate-feedback
178 % self-loop transiently yields an idle server with a non-empty
179 % buffer, where the fed-back job must re-enter the vacated server
180 % (mirrors the FCFS
case below).
181 if sum(space_srv_k,2)<S(ist)
182 space_srv_k(:,Ks(class)+kentry) = space_srv_k(:,Ks(class)+kentry) + 1;
183 outprob_k = pentry(kentry)*ones(size(space_srv_k,1));
185 space_buf_k(:,class) = space_buf_k(:,class) + 1;
186 outprob_k = pentry(kentry)*ones(size(space_srv_k,1));
188 case SchedStrategy.POLLING
189 % The controller, not the arrival, decides who
is served:
190 % an arriving job joins its
class buffer and waits for the
191 % server to walk to it, even when the facility
is idle,
192 % because the server
is then in a switchover. The exception
193 %
is a parked server, which State.pollingNext only ever
194 % produces with an empty station and immediate switchovers:
195 % it therefore reaches the arriving job in zero time and
196 % starts a visit on it at once.
197 pinfoA = State.pollingInfo(sn, ind);
200 if sum(space_srv_k(1,(Ks(rA)+1):(Ks(rA)+K(rA)))) > 0
205 [posA, swkA] = State.pollingGet(pinfoA, space_var_k, srvclassA);
206 if srvclassA == 0 && swkA == 0
207 % Parked. The arriving job
is the only work present, so
208 % the walk necessarily resolves to a visit on its own
209 %
class: it enters service here, in phase kentry, and
210 % never occupies the buffer.
211 nbufA = space_buf_k(1,1:R);
212 nbufA(
class) = nbufA(
class) + 1;
213 [qA, ~, budgetA] = State.pollingNext(pinfoA, posA, nbufA, R,
true);
214 space_srv_k(:,Ks(class)+kentry) = space_srv_k(:,Ks(class)+kentry) + 1;
215 space_var_k = State.pollingSet(pinfoA, space_var_k, qA, 0, budgetA);
217 space_buf_k(:,class) = space_buf_k(:,class) + 1;
219 outprob_k = pentry(kentry)*ones(size(space_srv_k,1));
220 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.LCFS, SchedStrategy.LCFSPRIO}
221 % find states with all servers busy -
this
222 % needs not to be moved
224 %
if MAP service, when empty restart from the phase
225 % stored in space_var
for this class
226 if ~ismkvmodclass(
class) || (ismkvmodclass(
class) && kentry == space_var(sum(sn.nvars(ind,1:class))))
227 if ismkvmodclass(class)
228 pentry = zeros(size(pentry));
229 pentry(kentry) = 1.0;
231 all_busy_srv = sum(space_srv_k,2) >= S(ist);
233 % find and modify states with an idle server
234 idle_srv = sum(space_srv_k,2) < S(ist);
235 space_srv_k(idle_srv, end-sum(K)+Ks(
class)+kentry) = space_srv_k(idle_srv,end-sum(K)+Ks(
class)+kentry) + 1; % job enters service
237 %
this section dynamically grows the number of
238 % elements in the buffer
240 if any(ni < capacity(ist))
241 if any(nir(:,class) < classcap(ist,class)) % if there
is room
242 if ~any(space_buf_k(:)==0) % but the buffer has no empty slots
244 space_buf_k = [zeros(size(space_buf_k,1),1),space_buf_k];
249 %get position of first empty slot
250 empty_slots = -1*ones(size(all_busy_srv,1),1);
251 if size(space_buf_k,2) == 0
252 empty_slots(all_busy_srv) =
false;
253 elseif size(space_buf_k,2) == 1
254 empty_slots(all_busy_srv) = space_buf_k(all_busy_srv,:)==0;
256 empty_slots(all_busy_srv) = max(bsxfun(@times, space_buf_k(all_busy_srv,:)==0, [1:size(space_buf_k,2)]),[],2);
259 % ignore states where the buffer has no empty slots.
260 % A structurally free buffer
column is NOT enough: the job may
261 % only be placed when the CAPACITY also permits it. Without
this
262 % the job was written into the slot and the whole row was then
263 % vetoed by the capacity filter below (en_o), which returned an
264 % EMPTY outspace -- i.e. the arrival
event never fired, so the
265 % upstream departure was never counted and the reported arrival
266 % rate collapsed from the OFFERED rate to the CARRIED one
267 % (BUG-85: M/M/1/1 reported ArvR 0.444444 instead of 0.800000,
268 % hiding the loss entirely,
while M/M/1/2 -- whose buffer
is
269 % full rather than absent -- correctly reported 0.800000).
270 % Leaving the state unchanged instead makes the arrival a
271 % self-loop: the event fires, ArvR counts the offered job, and
272 % the job
is lost. A self-loop cancels on the generator diagonal,
273 % so the stationary distribution -- and hence QLen/Util/Tput --
275 % Gate the placement on the capacity ONLY for a PHYSICAL
276 % finite capacity. When the bound
is a state-space cutoff
277 % (open class, no physical cap; solver_ssa folds the cutoff
278 % into the capacity/classcap arguments), keep the pre-change
279 % behaviour: place the job structurally and let the en_o
280 % capacity filter below delete the beyond-cutoff row
281 % (truncation). Firing the capacity gate at a cutoff would
282 % send the arrival to the loss/block branch, turning a
283 % truncation into a self-loop. See State.isPhysicalCapacity.
284 if State.isPhysicalCapacity(sn, ist, class)
285 hasRoom = (ni < capacity(ist)) & (nir(:,class) < classcap(ist,class));
287 hasRoom =
true(size(empty_slots));
289 wbuf_empty = empty_slots>0 & hasRoom;
291 space_srv_k = space_srv_k(wbuf_empty,:);
292 space_buf_k = space_buf_k(wbuf_empty,:);
293 space_var_k = space_var_k(wbuf_empty,:);
294 empty_slots = empty_slots(wbuf_empty);
295 space_buf_k(sub2ind(size(space_buf_k),1:size(space_buf_k,1),empty_slots
')) = class;
296 %outspace(all_busy_srv(wbuf_empty),:) = [space_buf, space_srv, space_var];
297 elseif any(all_busy_srv) && ~State.arrivalIsLost(sn, ist, class)
298 % The arrival cannot be placed (all servers busy, no room).
299 % Whether that is a LOSS or a BLOCK is decided by the class
300 % type, not by the drop rule: a CLOSED job has nowhere to go,
301 % so it cannot be lost, and an explicit blocking rule
302 % (BAS/BBS/RSRD) asks for blocking too. Removing the rows
303 % returns an EMPTY outspace, which disables the upstream
304 % departure until space frees (and is what the CTMC
305 % become-blocked edge tests for, so true BAS fires). For a
306 % LOST arrival the rows are kept unchanged instead, so the
307 % event still fires and ArvR counts the offered job -- see
308 % State.arrivalIsLost and the hasRoom comment above.
309 space_srv_k = space_srv_k(idle_srv,:);
310 space_buf_k = space_buf_k(idle_srv,:);
311 space_var_k = space_var_k(idle_srv,:);
313 outprob_k = pentry(kentry)*ones(size(space_srv_k,1),1);
315 outprob_k = 0*ones(size(space_srv_k,1),1); % zero probability event
317 case {SchedStrategy.FCFSPR,SchedStrategy.FCFSPI,SchedStrategy.FCFSPRPRIO,SchedStrategy.FCFSPIPRIO,SchedStrategy.LCFSPR,SchedStrategy.LCFSPI,SchedStrategy.LCFSPRPRIO,SchedStrategy.LCFSPIPRIO}
318 % find states with all servers busy - this
320 all_busy_srv = sum(space_srv_k,2) >= S(ist);
321 % find states with an idle server
322 idle_srv = sum(space_srv_k,2) < S(ist);
324 % reorder states so that idle ones come first
325 space_buf_k_reord = space_buf_k(idle_srv,:);
326 space_srv_k_reord = space_srv_k(idle_srv,:);
327 space_var_k_reord = space_var_k(idle_srv,:);
329 % if idle, the job enters service in phase kentry
331 space_srv_k_reord(:, end-sum(K)+Ks(class)+kentry) = space_srv_k_reord(:,end-sum(K)+Ks(class)+kentry) + 1;
332 outprob_k = pentry(kentry);
334 % if all busy, expand output states for all possible choices of job class to preempt
335 psentry = ones(size(space_buf_k_reord,1),1); % probability scaling due to preemption
336 isPrioAware = (sn.sched(ist) == SchedStrategy.FCFSPRPRIO || sn.sched(ist) == SchedStrategy.FCFSPIPRIO || sn.sched(ist) == SchedStrategy.LCFSPRPRIO || sn.sched(ist) == SchedStrategy.LCFSPIPRIO);
337 isLcfsPrioFamily = (sn.sched(ist) == SchedStrategy.LCFSPRPRIO || sn.sched(ist) == SchedStrategy.LCFSPIPRIO);
338 for classpreempt = 1:R
339 % For priority variants (or LCFSPR/FCFSPR when priorities differ),
340 % only higher-priority jobs can preempt
341 isPrioSched = (sn.sched(ist) == SchedStrategy.FCFSPRPRIO || sn.sched(ist) == SchedStrategy.FCFSPIPRIO || sn.sched(ist) == SchedStrategy.LCFSPRPRIO || sn.sched(ist) == SchedStrategy.LCFSPIPRIO);
342 % Priority-awareness is a property of the DECLARED policy, never of
343 % the data. LCFSPRPRIO/FCFSPRPRIO are the priority-aware variants;
344 % inferring the discipline from ~all(classprio==classprio(1)) turned
345 % plain LCFSPR/FCFSPR into something that is neither the base policy
346 % nor the PRIO variant. The FCFSPR case below states the rule outright
347 % ("FCFS preempt-resume (no priority)").
348 isPrioAware = isPrioSched;
350 % Across priority groups a strictly higher-priority arrival preempts.
351 % WITHIN a group the base discipline decides: LCFS-PR keeps the NEWEST
352 % job in service, so an equal-priority arrival preempts; FCFS-PR never
353 % lets an arrival preempt. Refusing on equality for both left
354 % LCFSPRPRIO unable to preempt anything when the priorities were flat,
355 % so no arrival could enter a busy station at all.
356 isLcfsPrioFamily = (sn.sched(ist) == SchedStrategy.LCFSPRPRIO || sn.sched(ist) == SchedStrategy.LCFSPIPRIO);
358 cannotPreempt = sn.classprio(class) > sn.classprio(classpreempt);
360 cannotPreempt = sn.classprio(class) >= sn.classprio(classpreempt);
366 for phasepreempt = 1:K(classpreempt) % phase of job to preempt
367 si_preempt = space_srv_k(:, (end-sum(K)+Ks(classpreempt)+phasepreempt));
368 busy_preempt = si_preempt > 0; % states where there is at least on class-r job in execution
370 psentry = [psentry; si_preempt(busy_preempt) ./ sum(space_srv_k,2)];
371 space_srv_k_preempt = space_srv_k(busy_preempt,:);
372 space_buf_k_preempt = space_buf_k(busy_preempt,:);
373 space_var_k_preempt = space_var_k(busy_preempt,:);
374 space_srv_k_preempt(:, end-sum(K)+Ks(classpreempt)+phasepreempt) = space_srv_k_preempt(:,end-sum(K)+Ks(classpreempt)+phasepreempt) - 1; % remove preempted job
375 space_srv_k_preempt(:, end-sum(K)+Ks(class)+kentry) = space_srv_k_preempt(:,end-sum(K)+Ks(class)+kentry) + 1;
377 % dynamically grow buffer lenght in
380 if ni < capacity(ist) && nir(class) < classcap(ist,class) % if there is room
381 if ~any(space_buf_k_preempt(:)==0) % but the buffer has no empty slots
383 space_buf_k_preempt = [zeros(size(space_buf_k_preempt,1),2),space_buf_k]; % append two columns for (class, preempt-phase)
388 %get position of first empty slot
389 empty_slots = -1*ones(sum(busy_preempt),1);
390 if size(space_buf_k_preempt,2) == 0
391 empty_slots(busy_preempt) = false;
392 elseif size(space_buf_k_preempt,2) == 2 % 2 due to (class, preempt-phase) pairs
393 empty_slots(busy_preempt) = space_buf_k_preempt(busy_preempt,1:2:end)==0;
395 empty_slots(busy_preempt) = max(bsxfun(@times, space_buf_k_preempt(busy_preempt,:)==0, [1:size(space_buf_k_preempt,2)]),[],2)-1; %-1 due to (class, preempt-phase) pairs
398 % ignore states where the buffer has no empty slots
399 wbuf_empty = empty_slots>0;
401 space_srv_k_preempt = space_srv_k_preempt(wbuf_empty,:);
402 space_buf_k_preempt = space_buf_k_preempt(wbuf_empty,:);
403 space_var_k_preempt = space_var_k_preempt(wbuf_empty,:);
404 empty_slots = empty_slots(wbuf_empty);
405 if sn.sched(ist) == SchedStrategy.LCFSPR || sn.sched(ist) == SchedStrategy.LCFSPRPRIO || sn.sched(ist) == SchedStrategy.FCFSPR || sn.sched(ist) == SchedStrategy.FCFSPRPRIO % preempt-resume
406 space_buf_k_preempt(sub2ind(size(space_buf_k_preempt),1:size(space_buf_k_preempt,1),empty_slots')+1) = phasepreempt;
407 elseif sn.sched(ist) == SchedStrategy.LCFSPI || sn.sched(ist) == SchedStrategy.LCFSPIPRIO || sn.sched(ist) == SchedStrategy.FCFSPI || sn.sched(ist) == SchedStrategy.FCFSPIPRIO % preempt-independent
408 space_buf_k_preempt(sub2ind(size(space_buf_k_preempt),1:size(space_buf_k_preempt,1),empty_slots
')+1) = 1;
410 space_buf_k_preempt(sub2ind(size(space_buf_k_preempt),1:size(space_buf_k_preempt,1),empty_slots')) = classpreempt;
411 %outspace(all_busy_srv(wbuf_empty),:) = [space_buf, space_srv, space_var];
413 space_srv_k_reord = [space_srv_k_reord; space_srv_k_preempt];
414 space_buf_k_reord = [space_buf_k_reord; space_buf_k_preempt];
415 space_var_k_reord = [space_var_k_reord; space_var_k_preempt];
420 % Rows where the arrival can preempt nothing: every busy server holds a
421 % job it may not displace. The job cannot seize a server, so it WAITS in
422 % the buffer. Without
this branch the loop above emits no state at all
423 %
for those rows -- the arrival transition simply does not exist, so the
424 %
class can never enter a busy station and its queue
is silently
425 % understated. Python already carries
this fallback (its BUG-70 fix);
426 % MATLAB never received it. The job
is stored as a (
class, entry-phase)
427 % pair exactly as a preempted job
is, so on promotion it resumes from
428 % that phase (
for exponential service resume == restart).
430 canPreempt =
false(size(space_srv_k,1),1);
433 preemptable = sn.classprio(
class) <= sn.classprio(cp);
435 preemptable = sn.classprio(
class) < sn.classprio(cp);
439 canPreempt = canPreempt | (space_srv_k(:, end-sum(K)+Ks(cp)+ph) > 0);
443 waitRows = all_busy_srv & ~canPreempt;
445 space_srv_k_wait = space_srv_k(waitRows,:);
446 space_buf_k_wait = space_buf_k(waitRows,:);
447 space_var_k_wait = space_var_k(waitRows,:);
448 % rightmost empty (
class,phase) pair, matching the preemption store
449 empty_wait = -1*ones(sum(waitRows),1);
450 if size(space_buf_k_wait,2) > 0
451 empty_wait = max(bsxfun(@times, space_buf_k_wait==0, 1:size(space_buf_k_wait,2)),[],2)-1;
453 keep_wait = empty_wait > 0;
455 space_srv_k_wait = space_srv_k_wait(keep_wait,:);
456 space_buf_k_wait = space_buf_k_wait(keep_wait,:);
457 space_var_k_wait = space_var_k_wait(keep_wait,:);
458 ew = empty_wait(keep_wait);
459 nw = size(space_buf_k_wait,1);
460 space_buf_k_wait(sub2ind(size(space_buf_k_wait),(1:nw)
',ew)) = class;
461 space_buf_k_wait(sub2ind(size(space_buf_k_wait),(1:nw)',ew+1)) = kentry;
462 psentry = [psentry; ones(nw,1)];
463 space_srv_k_reord = [space_srv_k_reord; space_srv_k_wait];
464 space_buf_k_reord = [space_buf_k_reord; space_buf_k_wait];
465 space_var_k_reord = [space_var_k_reord; space_var_k_wait];
469 outprob_k = pentry(kentry) * psentry .* ones(size(space_srv_k_reord,1),1);
471 space_buf_k = space_buf_k_reord; % save reordered output states
472 space_srv_k = space_srv_k_reord; % save reordered output states
473 space_var_k = space_var_k_reord; % save reordered output states
476 outspace_k = [space_buf_k, space_srv_k, space_var_k];
477 % remove states where
new arrival violates capacity or cutoff constraints
478 [oi,oir] = State.toMarginalAggr(sn,ind,outspace_k,K,Ks,space_buf_k,space_srv_k,space_var_k);
479 en_o = classcap(ist,
class)>= oir(:,class) & capacity(ist)*ones(size(oi,1),1) >= oi;
481 if size(outspace,2)>size(outspace_k(en_o,:),2)
482 outspace = [outspace; zeros(1,size(outspace,2)-size(outspace_k(en_o,:),2)),outspace_k(en_o,:)];
483 elseif size(outspace,2)<size(outspace_k(en_o,:),2)
484 outspace = [zeros(size(outspace,1),size(outspace_k(en_o,:),2)-size(outspace,2)), outspace; outspace_k(en_o,:)];
486 outspace = [outspace; outspace_k(en_o,:)];
488 outrate = [outrate; -1*ones(size(outspace_k(en_o,:),1),1)]; % passive action, rate
is unspecified
489 outprob = [outprob; outprob_k(en_o,:)];
491 % Balking (QUEUE_LENGTH strategy): with probability balkProb the
492 % arriving class-r job refuses to join, based on the pre-arrival total
493 % station population ni. A balked job
is lost (destination state left
494 % unchanged); the admitted branches are scaled by (1-balkProb). Only
495 % the QUEUE_LENGTH strategy
is a pure function of the state vector and
496 %
is therefore admissible in state-space solvers; EXPECTED_WAIT /
497 % COMBINED depend on the mean wait and are rejected in the analyzers.
498 if isfield(sn,
'balkingStrategy') && ~isempty(sn.balkingStrategy) ...
499 && sn.balkingStrategy(ist,
class) == BalkingStrategy.QUEUE_LENGTH ...
500 && ~isempty(outspace)
502 thresholds = sn.balkingThresholds{ist,
class};
503 qlen = ni(1); % pre-arrival total station population
504 for ti = 1:numel(thresholds)
506 if qlen >= th{1} && qlen <= th{2}
512 outprob = outprob * (1 - balkProb);
513 % balked branch: job lost, destination state unchanged
515 if size(outspace,2) > size(inrow,2)
516 inrow = [zeros(1,size(outspace,2)-size(inrow,2)), inrow];
518 outspace = [outspace; inrow];
519 outrate = [outrate; -1];
520 outprob = [outprob; balkProb];
524 if size(outprob,1) > 1
525 cum_prob = cumsum(outprob) / sum(outprob);
526 firing_ctr = 1 + max([0,find( rand > cum_prob' )]); % select action
527 outspace = outspace(firing_ctr,:);
533 % Marked (
MMAP) source class: the shared modulating chain lives in the
534 % carrier's phase block (mark index 1); this class's departures fire
535 % from there using its per-mark matrix D1k (M3A cell index 2+mark).
538 if sn.sched(ist) == SchedStrategy.EXT && isfield(sn,'markidx') ...
539 && ~isempty(sn.markidx) && ist <= size(sn.markidx,1) ...
540 && sn.markidx(ist,class) > 0
541 markofclass = sn.markidx(ist,class);
542 phclass = find(sn.markidx(ist,:) == 1, 1);
544 if any(any(space_srv(:,(Ks(phclass)+1):(Ks(phclass)+K(phclass))))) % something
is busy
545 if hasOnlyExp && (sn.sched(ist) == SchedStrategy.PS || sn.sched(ist) == SchedStrategy.DPS || sn.sched(ist) == SchedStrategy.GPS || sn.sched(ist) == SchedStrategy.INF || sn.sched(ist) == SchedStrategy.PSPRIO || sn.sched(ist) == SchedStrategy.DPSPRIO || sn.sched(ist) == SchedStrategy.GPSPRIO || sn.sched(ist) == SchedStrategy.LPS)
551 [ni,nir,sir,kir] = State.toMarginal(sn,ind,inspace,K,Ks,space_buf,space_srv,space_var);
553 switch sn.routing(ind,class)
554 case RoutingStrategy.RROBIN
555 idx = find(space_var(sum(sn.nvars(ind,1:(R+class)))) == sn.nodeparam{ind}{
class}.outlinks);
556 if idx < length(sn.nodeparam{ind}{class}.outlinks)
557 space_var(sum(sn.nvars(ind,1:(R+class)))) = sn.nodeparam{ind}{
class}.outlinks(idx+1);
559 space_var(sum(sn.nvars(ind,1:(R+class)))) = sn.nodeparam{ind}{
class}.outlinks(1);
561 case RoutingStrategy.WRROBIN
562 % WRR slot holds a POSITION in weighted_outlinks; advance it
563 % cyclically (mirrors afterEventRouter and sub_wrr). Without
564 %
this advance the position stays fixed and only the initial
565 % destination
is ever selected.
566 slot = sum(sn.nvars(ind,1:(R+class)));
567 if isfield(sn.nodeparam{ind}{class},
'weighted_outlinks') ...
568 && ~isempty(sn.nodeparam{ind}{class}.weighted_outlinks)
569 cycle_len = length(sn.nodeparam{ind}{
class}.weighted_outlinks);
571 cycle_len = length(sn.nodeparam{ind}{class}.outlinks);
573 pos = space_var(slot);
574 if pos < 1 || pos >= cycle_len
577 space_var(slot) = pos + 1;
580 if sir(phclass)>0 %
is a job of
class is in service
583 space_srv = inspace(:,(end-sum(K)-V+1):(end-V)); % server state
584 space_buf = inspace(:,1:(end-sum(K)-V)); % buffer state
585 rate = zeros(size(space_srv,1),1);
586 en = space_srv(:,Ks(phclass)+k) > 0;
589 case SchedStrategy.EXT % source, can produce an arrival from phase-k as
long as it
is from an open
class
590 if isinf(sn.njobs(
class))
592 % per-mark arrival matrix over the shared chain
593 D1_srv = proc{ist}{
class}{2+markofclass};
595 D1_srv = proc{ist}{
class}{2};
597 for kentry = 1:K(phclass)
598 arv_rate = D1_srv(k, kentry);
602 space_srv = inspace(:,(end-sum(K)-V+1):(end-V));
603 space_srv(en,Ks(phclass)+k) = space_srv(en,Ks(phclass)+k) - 1;
604 space_srv(en,Ks(phclass)+kentry) = space_srv(en,Ks(phclass)+kentry) + 1;
605 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
607 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*arv_rate*ones(size(inspace(en,:),1),1)];
609 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*arv_rate*ones(size(inspace(en,:),1),1)];
611 outprob = [outprob; ones(size(space_buf(en,:),1),1)];
614 case SchedStrategy.INF % move first job in service
615 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
616 rate(en) = mu{ist}{
class}(k)*(phi{ist}{
class}(k)).*kir(en,
class,k); % assume active
617 %
if state
is unchanged, still add with rate 0
618 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
619 if isinf(ni) % hit limited load-dependence
620 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate(en,:)];
622 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
624 outprob = [outprob; ones(size(rate(en,:),1),1)];
625 case {SchedStrategy.PS, SchedStrategy.LPS} % move first job in service
626 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
627 rate(en) = mu{ist}{
class}(k)*(phi{ist}{
class}(k)).*(kir(en,
class,k)./ni(en)).*min(ni(en),S(ist)); % assume active
628 %
if state
is unchanged, still add with rate 0
629 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
630 if isinf(ni) % hit limited load-dependence
631 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate(en,:)];
633 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
635 outprob = [outprob; ones(size(rate(en,:),1),1)];
637 case SchedStrategy.PSPRIO
638 % unclear
if LD scaling should be with
639 % ni or with niprio,
for now left as ni
640 %
for consistency with HOL multiserver
641 if all(ni(en) <= S(ist))
642 % n <= c: all jobs get service, priority doesn
't matter
643 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
644 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*(kir(en,class,k)./ni(en)).*min(ni(en),S(ist)); % assume active
645 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
646 if isinf(ni) % hit limited load-dependence
647 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
649 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
651 outprob = [outprob; ones(size(rate(en,:),1),1)];
652 elseif sn.classprio(class) == min(sn.classprio(nir>0)) % if this class is in the most urgent priority group (lower value = higher priority in LINE)
653 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
654 niprio(en) = sum(nir(sn.classprio==sn.classprio(class))); % jobs at the same priority class
655 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*(kir(en,class,k)./niprio(en)).*min(niprio(en),S(ist)); % assume active
656 % if state is unchanged, still add with rate 0
657 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
658 if isinf(ni) % hit limited load-dependence
659 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
661 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(niprio(en),lldlimit)).*rate(en,:)];
663 outprob = [outprob; ones(size(rate(en,:),1),1)];
664 else % n > c and not highest priority: set rate to zero
665 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
666 outrate = [outrate; zeros(size(rate(en,:),1),1)];
667 outprob = [outprob; ones(size(rate(en,:),1),1)];
669 case SchedStrategy.DPS
670 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
672 line_error(mfilename,'Multi-server DPS stations are not supported yet.
');
674 % in GPS, the scheduling parameter are the weights
675 w_i = sn.schedparam(ist,:);
676 w_i = w_i / sum(w_i);
677 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k))*(kir(en,class,k)/nir(class))*w_i(class)*nir(class)./(sum(repmat(w_i,sum(en),1)*nir',2));
678 %
if state
is unchanged, still add with rate 0
679 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
680 if isinf(ni) % hit limited load-dependence
681 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate(en,:)];
683 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
685 outprob = [outprob; ones(size(rate(en,:),1),1)];
686 case SchedStrategy.DPSPRIO
687 if all(ni(en) <= S(ist))
688 % n <= c: all jobs get service, behave like regular DPS
689 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
691 line_error(mfilename,
'Multi-server DPS stations are not supported yet.');
693 w_i = sn.schedparam(ist,:);
694 w_i = w_i / sum(w_i);
695 rate(en) = mu{ist}{
class}(k)*(phi{ist}{
class}(k))*(kir(en,
class,k)/nir(
class))*w_i(
class)*nir(
class)./(sum(repmat(w_i,sum(en),1)*nir
',2));
696 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
697 if isinf(ni) % hit limited load-dependence
698 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
700 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
702 outprob = [outprob; ones(size(rate(en,:),1),1)];
703 elseif sn.classprio(class) == min(sn.classprio(nir>0)) % if this class is in the most urgent priority group (lower value = higher priority in LINE)
705 nirprio(sn.classprio~=sn.classprio(class)) = 0; % ignore jobs of lower priority
706 niprio = sum(nirprio);
707 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
709 line_error(mfilename,'Multi-server DPS stations are not supported yet.
');
711 % in GPS, the scheduling parameter are the weights
712 w_i = sn.schedparam(ist,:);
713 w_i = w_i / sum(w_i);
714 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k))*(kir(en,class,k)/nirprio(class))*w_i(class)*nirprio(class)./(sum(repmat(w_i,sum(en),1)*nirprio',2));
715 %
if state
is unchanged, still add with rate 0
716 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
717 if isinf(ni) % hit limited load-dependence
718 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nirprio,en,
class).*lldscaling(ist,end).*rate(en,:)];
720 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nirprio,en,
class).*lldscaling(ist,min(niprio(en),lldlimit)).*rate(en,:)];
722 outprob = [outprob; ones(size(rate(en,:),1),1)];
723 else % n > c and not most urgent priority: set rate to zero
724 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
725 outrate = [outrate; zeros(size(rate(en,:),1),1)];
726 outprob = [outprob; ones(size(rate(en,:),1),1)];
728 case SchedStrategy.GPS
729 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
731 line_error(mfilename,
'Multi-server GPS stations are not supported yet.');
733 % in GPS, the scheduling parameter are the weights
734 w_i = sn.schedparam(ist,:);
735 w_i = w_i / sum(w_i);
736 cir = min(nir,ones(size(nir)));
737 rate = mu{ist}{
class}(k)*(phi{ist}{
class}(k))*(kir(en,
class,k)/nir(
class))*w_i(
class)/(w_i*cir(:)); % assume active
738 %
if state
is unchanged, still add with rate 0
739 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
740 if isinf(ni) % hit limited load-dependence
741 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate(en,:)];
743 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
745 outprob = [outprob; ones(size(rate(en,:),1),1)];
746 case SchedStrategy.GPSPRIO
747 if all(ni(en) <= S(ist))
748 % n <= c: all jobs get service, behave like regular GPS
749 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
751 line_error(mfilename,
'Multi-server GPS stations are not supported yet.');
753 w_i = sn.schedparam(ist,:);
754 w_i = w_i / sum(w_i);
755 cir = min(nir,ones(size(nir)));
756 rate = mu{ist}{
class}(k)*(phi{ist}{
class}(k))*(kir(en,
class,k)/nir(
class))*w_i(
class)/(w_i*cir(:)); % assume active
757 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
758 if isinf(ni) % hit limited load-dependence
759 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate(en,:)];
761 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
763 outprob = [outprob; ones(size(rate(en,:),1),1)];
764 elseif sn.classprio(
class) == min(sn.classprio(nir>0)) %
if this class is in the most urgent priority group (lower value = higher priority in LINE)
766 nirprio(sn.classprio~=sn.classprio(
class)) = 0; % ignore jobs of lower priority
767 niprio = sum(nirprio);
768 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
770 line_error(mfilename,
'Multi-server DPS stations are not supported yet.');
772 % in GPS, the scheduling parameter are the weights
773 w_i = sn.schedparam(ist,:);
774 w_i = w_i / sum(w_i);
775 cir = min(nirprio,ones(size(nirprio)));
776 rate = mu{ist}{
class}(k)*(phi{ist}{
class}(k))*(kir(en,
class,k)/nirprio(
class))*w_i(
class)/(w_i*cir(:)); % assume active
777 %
if state
is unchanged, still add with rate 0
778 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
779 if isinf(ni) % hit limited load-dependence
780 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nirprio,en,
class).*lldscaling(ist,end).*rate(en,:)];
782 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nirprio,en,
class).*lldscaling(ist,min(niprio(en),lldlimit)).*rate(en,:)];
784 outprob = [outprob; ones(size(rate(en,:),1),1)];
785 else % n > c and not most urgent priority: set rate to zero
786 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
787 outrate = [outrate; zeros(size(rate(en,:),1),1)];
788 outprob = [outprob; ones(size(rate(en,:),1),1)];
790 case SchedStrategy.FCFS % move first job in service
791 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
792 en_wbuf = en & ni>S(ist); %states with jobs in buffer
793 if noPromote || isRetrialStation % immediate feedback / retrial orbit: hold server,
do not promote a waiting job
796 for kdest=1:K(
class) %
new phase
797 space_buf_kd = space_buf;
798 space_var_kd = space_var;
799 if ismkvmodclass(
class)
800 space_var_kd(en,sum(sn.nvars(ind,1:class))) = kdest;
803 rate_kd(en) = proc{ist}{
class}{2}(k,kdest).*kir(en,
class,k); % assume active
804 % first process states without jobs in buffer
806 if any(en_wobuf) %any state without jobs in buffer
807 outspace = [outspace; space_buf_kd(en_wobuf,:), space_srv(en_wobuf,:), space_var_kd(en_wobuf,:)];
808 if isinf(ni) % hit limited load-dependence
809 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,
class).*lldscaling(ist,end).*rate_kd(en_wobuf,:)];
811 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_kd(en_wobuf,:)];
814 % now process states with jobs in buffer
815 outprob = [outprob; ones(size(rate_kd(en_wobuf,:),1),1)];
816 if any(en_wbuf) %any state with jobs in buffer
817 % get
class of job at head
818 start_svc_class = space_buf_kd(en_wbuf,end);
819 if start_svc_class > 0 % redunant
if?
820 % update input buffer
821 space_buf_kd(en_wbuf,:) = [zeros(sum(en_wbuf),1),space_buf_kd(en_wbuf,1:end-1)];
822 % probability vector
for the next job of starting in phase kentry
823 if ismkvmodclass(start_svc_class) %
if markov-modulated
824 if start_svc_class==
class %
if successive service from the same
class
825 kentry_range = kdest; %
new job enters in phase left by departing job
826 else % resume phase from local variables
827 kentry_range = space_var_kd(en,sum(sn.nvars(ind,1:start_svc_class)));
829 pentry_svc_class = 0*pie{ist}{start_svc_class};
830 pentry_svc_class(kentry_range) = 1.0;
832 pentry_svc_class = pie{ist}{start_svc_class};
833 kentry_range = 1:K(start_svc_class);
835 for kentry = kentry_range
836 space_srv(en_wbuf,Ks(start_svc_class)+kentry) = space_srv(en_wbuf,Ks(start_svc_class)+kentry) + 1;
837 outspace = [outspace; space_buf_kd(en,:), space_srv(en,:), space_var_kd(en,:)];
839 rate_k(en_wbuf,:) = rate_kd(en_wbuf,:)*pentry_svc_class(kentry);
840 if isinf(ni) % use limited load-dependence at the latest user-provided level
841 newrate = State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate_k(en,:);
843 newrate = State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en,:);
845 outrate = [outrate; newrate];
846 % Zero the probability of the branches added *by
this kentry* when
847 % their rate
is zero. The mask must index newrate, not the whole
848 % accumulated outrate: outrate grows by one entry per kentry
while
849 % outprob_cur has one entry per enabled state, so masking on outrate
850 % silently GREW outprob_cur (MATLAB expands on out-of-bound
851 % assignment) once any earlier branch had rate 0 -- which happens
852 % whenever pentry_svc_class has zeros, i.e. a PH whose entry vector
853 % pie does not reach every phase. That misaligned outprob against
854 % outspace/outrate, so the sampled branch read a bogus (often 0)
855 % probability and depRatesSamples under-counted departures.
856 outprob_cur = ones(size(rate_kd(en,:),1),1);
857 outprob_cur(newrate==0.0) = 0;
858 outprob = [outprob; outprob_cur(:)];
859 space_srv(en_wbuf,Ks(start_svc_class)+kentry) = space_srv(en_wbuf,Ks(start_svc_class)+kentry) - 1;
864 %
if state
is unchanged, still add with rate 0
865 case SchedStrategy.HOL % FCFS priority
866 rate(en) = mu{ist}{
class}(k)*(phi{ist}{
class}(k)).*kir(:,class,k); % assume active
867 en_wbuf = en & ni>S(ist); %states with jobs in buffer
868 if noPromote || isRetrialStation % immediate feedback / retrial orbit: hold server,
do not promote a waiting job
872 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
873 priogroup = [Inf,sn.classprio]; % Inf
for empty positions (lower value = higher priority)
874 space_buf_groupg = arrayfun(@(x) priogroup(1+x), space_buf);
875 start_classprio = min(space_buf_groupg(en_wbuf,:),[],2); % min finds highest priority
876 isrowmax = space_buf_groupg == repmat(start_classprio, 1, size(space_buf_groupg,2));
877 [~,rightmostMaxPosFlipped]=max(fliplr(isrowmax),[],2);
878 rightmostMaxPos = size(isrowmax,2) - rightmostMaxPosFlipped + 1;
879 start_svc_class = space_buf(en_wbuf, rightmostMaxPos);
880 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
881 if isinf(ni) % hit limited load-dependence
882 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,
class).*lldscaling(ist,end).*rate(en_wobuf,:)];
884 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
886 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
887 if start_svc_class > 0
888 pentry_svc_class = pie{ist}{start_svc_class};
889 for kentry = 1:K(start_svc_class)
890 space_srv_k = space_srv;
891 space_buf_k = space_buf;
892 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
894 space_buf_k(j,:) = [0, space_buf_k(j,1:rightmostMaxPos(j)-1), space_buf_k(j,(rightmostMaxPos(j)+1):end)];
896 % if state is unchanged, still add with rate 0
897 outspace = [outspace; space_buf_k(en_wbuf,:), space_srv_k(en_wbuf,:), space_var(en_wbuf,:)];
899 rate_k(en_wbuf,:) = rate(en_wbuf,:) * pentry_svc_class(kentry);
900 if isinf(ni) % hit limited load-dependence
901 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,end).*rate_k(en_wbuf,:)];
903 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en_wbuf,:)];
905 outprob = [outprob; ones(size(rate_k(en_wbuf,:),1),1)];
908 case SchedStrategy.LCFSPRIO % LCFS priority - like HOL but LCFS order within priority groups
909 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % assume active
910 en_wbuf = en & ni>S(ist); %states with jobs in buffer
911 if noPromote || isRetrialStation % immediate feedback / retrial orbit: hold server, do not promote a waiting job
915 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
916 priogroup = [Inf,sn.classprio]; % Inf for empty positions (lower value = higher priority)
917 space_buf_groupg = arrayfun(@(x) priogroup(1+x), space_buf);
918 start_classprio = min(space_buf_groupg(en_wbuf,:),[],2); % min finds highest priority
919 isrowmax = space_buf_groupg == repmat(start_classprio, 1, size(space_buf_groupg,2));
920 % LCFS: Find leftmost (first) position instead of rightmost for LCFS order
921 [~,leftmostMaxPos]=max(isrowmax,[],2);
922 start_svc_class = space_buf(en_wbuf, leftmostMaxPos);
923 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
924 if isinf(ni) % hit limited load-dependence
925 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,end).*rate(en_wobuf,:)];
927 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
929 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
930 if start_svc_class > 0
931 pentry_svc_class = pie{ist}{start_svc_class};
932 for kentry = 1:K(start_svc_class)
933 space_srv_k = space_srv;
934 space_buf_k = space_buf;
935 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
937 % LCFS: Remove from leftmost position instead of rightmost
938 space_buf_k(j,:) = [0, space_buf_k(j,1:leftmostMaxPos(j)-1), space_buf_k(j,(leftmostMaxPos(j)+1):end)];
940 %
if state
is unchanged, still add with rate 0
941 outspace = [outspace; space_buf_k(en_wbuf,:), space_srv_k(en_wbuf,:), space_var(en_wbuf,:)];
943 rate_k(en_wbuf,:) = rate_k(en_wbuf,:)*pentry_svc_class(kentry);
944 if isinf(ni) % hit limited load-dependence
945 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,
class).*lldscaling(ist,end).*rate_k(en_wbuf,:)];
947 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en_wbuf,:)];
949 outprob = [outprob; ones(size(rate_k(en_wbuf,:),1),1)];
950 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) - 1;
953 case SchedStrategy.LCFS % move last job in service
954 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
955 rate(en) = mu{ist}{
class}(k)*(phi{ist}{
class}(k)).*kir(:,class,k); % assume active
956 en_wbuf = en & ni>S(ist); %states with jobs in buffer
957 if noPromote || isRetrialStation % immediate feedback / retrial orbit: hold server,
do not promote a waiting job
960 % Plain LCFS
is not priority-aware: it always promotes the most
961 % recent arrival. Priorities are honored by SchedStrategy.LCFSPRIO,
962 % which has its own
case below (
"LCFS order within priority
963 % groups"), exactly as FCFS relates to FCFSPRIO. Branching here on
964 % ~all(classprio == classprio(1)) silently turned every LCFS
965 % station with distinct class priorities into an LCFSPRIO one,
966 % contradicting the "Priorities will be ignored" warning raised by
967 % refreshStruct and, for three or more classes, yielding an
968 % all-zero queue length.
969 [~, colfirstnnz] = max( space_buf(en_wbuf,:) ~=0, [], 2 ); % find first nnz
column
970 start_svc_class = space_buf(en_wbuf,colfirstnnz); % job entering service
971 space_buf(en_wbuf,colfirstnnz)=0;
972 if isempty(start_svc_class)
973 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
974 if isinf(ni) % hit limited load-dependence
975 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate(en,:)];
977 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
979 outprob = [outprob; ones(size(rate(en,:),1),1)];
980 if isSimulation && nargin>=7 && isobject(eventCache)
981 eventCache(key) = {outprob, outspace,outrate};
985 for kentry = 1:K(start_svc_class)
986 pentry_svc_class = pie{ist}{start_svc_class};
987 space_srv(en_wbuf,Ks(start_svc_class)+kentry) = space_srv(en_wbuf,Ks(start_svc_class)+kentry) + 1;
988 %
if state
is unchanged, still add with rate 0
989 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
991 rate_k(en_wbuf,:) = rate(en_wbuf,:)*pentry_svc_class(kentry);
992 if isinf(ni) % hit limited load-dependence
993 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate_k(en,:)];
995 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en,:)];
997 outprob = [outprob; ones(size(rate(en,:),1),1)];
998 space_srv(en_wbuf,Ks(start_svc_class)+kentry) = space_srv(en_wbuf,Ks(start_svc_class)+kentry) - 1;
1000 case SchedStrategy.LCFSPR % move last job in service (preempt-resume)
1001 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
1002 rate(en) = mu{ist}{
class}(k)*(phi{ist}{
class}(k)).*kir(:,class,k); % assume active
1003 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1004 % Plain LCFSPR resumes the most recently preempted job. It
is not
1005 % priority-aware: LCFSPRPRIO
is, and it carries its own handling.
1006 % Selecting by priority whenever the
class priorities happened to
1007 % differ made
this case answer
for a policy the user never declared
1008 % (see the arrival path above
for the same rule).
1009 [~, colfirstnnz] = max( space_buf(en_wbuf,:) ~=0, [], 2 ); % find first nnz
column
1010 start_svc_class = space_buf(en_wbuf,colfirstnnz); % job entering service
1011 kentry = space_buf(en_wbuf,colfirstnnz+1); % entry phase of job resuming service
1012 space_buf(en_wbuf,colfirstnnz)=0;% zero popped job
1013 space_buf(en_wbuf,colfirstnnz+1)=0; % zero popped phase
1014 if isempty(start_svc_class)
1015 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1016 if isinf(ni) % hit limited load-dependence
1017 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate(en,:)];
1019 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
1021 outprob = [outprob; ones(size(rate(en,:),1),1)];
1022 if isSimulation && nargin>=7 && isobject(eventCache)
1023 eventCache(key) = {outprob, outspace,outrate};
1027 space_srv(en_wbuf,Ks(start_svc_class)+kentry) = space_srv(en_wbuf,Ks(start_svc_class)+kentry) + 1;
1028 %
if state
is unchanged, still add with rate 0
1029 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1030 if isinf(ni) % hit limited load-dependence
1031 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate(en,:)];
1033 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
1035 outprob = [outprob; ones(size(rate(en,:),1),1)];
1036 case SchedStrategy.LCFSPI % move last job in service (preempt-independent)
1037 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
1038 rate(en) = mu{ist}{
class}(k)*(phi{ist}{
class}(k)).*kir(:,class,k); % assume active
1039 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1040 [~, colfirstnnz] = max( space_buf(en_wbuf,:) ~=0, [], 2 ); % find first nnz
column
1041 start_svc_class = space_buf(en_wbuf,colfirstnnz); % job entering service
1042 space_buf(en_wbuf,colfirstnnz)=0;% zero popped job
1043 space_buf(en_wbuf,colfirstnnz+1)=0; % zero popped phase (ignored
for LCFSPI)
1044 if isempty(start_svc_class)
1045 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1046 if isinf(ni) % hit limited load-dependence
1047 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate(en,:)];
1049 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
1051 outprob = [outprob; ones(size(rate(en,:),1),1)];
1052 if isSimulation && nargin>=7 && isobject(eventCache)
1053 eventCache(key) = {outprob, outspace,outrate};
1057 % For LCFSPI, jobs restart from pie distribution instead of stored phase
1058 pentry_svc_class = pie{ist}{start_svc_class};
1059 for kentry = 1:K(start_svc_class)
1060 space_srv_k = space_srv;
1061 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
1062 %
if state
is unchanged, still add with rate 0
1063 outspace = [outspace; space_buf(en,:), space_srv_k(en,:), space_var(en,:)];
1065 rate_k(en_wbuf,:) = rate_k(en_wbuf,:) * pentry_svc_class(kentry);
1066 if isinf(ni) % hit limited load-dependence
1067 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate_k(en,:)];
1069 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en,:)];
1071 outprob = [outprob; ones(size(rate_k(en,:),1),1)];
1073 case SchedStrategy.FCFSPR % FCFS preempt-resume (no priority)
1074 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
1075 rate(en) = mu{ist}{
class}(k)*(phi{ist}{
class}(k)).*kir(:,class,k); % assume active
1076 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1077 % FCFS: Find rightmost (last) non-zero position (longest waiting job)
1078 [~, colLastNnz] = max(fliplr(space_buf(en_wbuf,:) ~= 0), [], 2);
1079 colLastNnz = size(space_buf,2) - colLastNnz; % convert to actual position (odd index
for class)
1080 start_svc_class = space_buf(en_wbuf, colLastNnz); % job entering service
1081 kentry = space_buf(en_wbuf, colLastNnz+1); % entry phase of job resuming service
1082 space_buf(en_wbuf, colLastNnz) = 0; % zero popped job
1083 space_buf(en_wbuf, colLastNnz+1) = 0; % zero popped phase
1084 if isempty(start_svc_class)
1085 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1087 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate(en,:)];
1089 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
1091 outprob = [outprob; ones(size(rate(en,:),1),1)];
1092 if isSimulation && nargin>=7 && isobject(eventCache)
1093 eventCache(key) = {outprob, outspace, outrate};
1097 space_srv(en_wbuf, Ks(start_svc_class)+kentry) = space_srv(en_wbuf, Ks(start_svc_class)+kentry) + 1;
1098 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1100 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate(en,:)];
1102 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
1104 outprob = [outprob; ones(size(rate(en,:),1),1)];
1105 case SchedStrategy.FCFSPI % FCFS preempt-independent (no priority)
1106 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
1107 rate(en) = mu{ist}{
class}(k)*(phi{ist}{
class}(k)).*kir(:,class,k); % assume active
1108 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1109 % FCFS: Find rightmost (last) non-zero position (longest waiting job)
1110 [~, colLastNnz] = max(fliplr(space_buf(en_wbuf,:) ~= 0), [], 2);
1111 colLastNnz = size(space_buf,2) - colLastNnz; % convert to actual position (odd index
for class)
1112 start_svc_class = space_buf(en_wbuf, colLastNnz); % job entering service
1113 space_buf(en_wbuf, colLastNnz) = 0; % zero popped job
1114 space_buf(en_wbuf, colLastNnz+1) = 0; % zero popped phase (ignored
for FCFSPI)
1115 if isempty(start_svc_class)
1116 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1118 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate(en,:)];
1120 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
1122 outprob = [outprob; ones(size(rate(en,:),1),1)];
1123 if isSimulation && nargin>=7 && isobject(eventCache)
1124 eventCache(key) = {outprob, outspace, outrate};
1128 % For FCFSPI, jobs restart from pie distribution instead of stored phase
1129 pentry_svc_class = pie{ist}{start_svc_class};
1130 for kentry = 1:K(start_svc_class)
1131 space_srv_k = space_srv;
1132 space_srv_k(en_wbuf, Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf, Ks(start_svc_class)+kentry) + 1;
1133 outspace = [outspace; space_buf(en,:), space_srv_k(en,:), space_var(en,:)];
1135 rate_k(en_wbuf,:) = rate_k(en_wbuf,:) * pentry_svc_class(kentry);
1137 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate_k(en,:)];
1139 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en,:)];
1141 outprob = [outprob; ones(size(rate_k(en,:),1),1)];
1143 case SchedStrategy.FCFSPRPRIO % FCFS preempt-resume with priority groups
1144 space_srv(en,Ks(
class)+k) = space_srv(en,Ks(
class)+k) - 1; % record departure
1145 rate(en) = mu{ist}{
class}(k)*(phi{ist}{
class}(k)).*kir(:,class,k); % assume active
1146 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1147 en_wobuf = ~en_wbuf;
1148 priogroup = [Inf,sn.classprio]; % Inf
for empty positions (lower value = higher priority)
1149 % Buffer stores [
class,phase,
class,phase,...] pairs;
1150 % only inspect
class columns (odd: 1,3,5,...) for priority
1151 class_cols = 1:2:size(space_buf,2);
1152 space_buf_class = space_buf(:, class_cols);
1153 space_buf_class_groupg = arrayfun(@(x) priogroup(1+x), space_buf_class);
1154 start_classprio = min(space_buf_class_groupg(en_wbuf,:),[],2); % min finds highest priority
1155 isrowmax = space_buf_class_groupg == repmat(start_classprio, 1, size(space_buf_class_groupg,2));
1156 % FCFS: Find rightmost (last) class position for highest priority class (longest waiting)
1157 [~,rightmostClassPos]=max(fliplr(isrowmax),[],2);
1158 rightmostClassPos = size(space_buf_class_groupg,2) - rightmostClassPos + 1;
1159 rightmostMaxPos = 2*rightmostClassPos - 1; % convert to buffer
column index
1160 start_svc_class = space_buf(en_wbuf, rightmostMaxPos); % job entering service
1161 kentry = space_buf(en_wbuf, rightmostMaxPos+1); % entry phase of job resuming service (preempt-resume)
1163 % Handle states without buffer jobs
1164 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
1166 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,end).*rate(en_wobuf,:)];
1168 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
1170 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
1172 % Handle states with buffer jobs
1173 if any(en_wbuf) && start_svc_class > 0
1174 space_srv_k = space_srv;
1175 space_buf_k = space_buf;
1176 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
1177 for j=find(en_wbuf)'
1178 % Remove both class and phase from rightmost position (preempt-resume)
1179 space_buf_k(j,:) = [0, space_buf_k(j,1:rightmostMaxPos(j)-1), space_buf_k(j,(rightmostMaxPos(j)+2):end), 0];
1181 outspace = [outspace; space_buf_k(en_wbuf,:), space_srv_k(en_wbuf,:), space_var(en_wbuf,:)];
1183 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,end).*rate(en_wbuf,:)];
1185 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wbuf,:)];
1187 outprob = [outprob; ones(size(rate(en_wbuf,:),1),1)];
1189 case SchedStrategy.LCFSPRPRIO % LCFS preempt-resume with priority groups
1190 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
1191 rate(en) = mu{ist}{
class}(k)*(phi{ist}{
class}(k)).*kir(:,class,k); % assume active
1192 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1193 en_wobuf = ~en_wbuf;
1194 priogroup = [Inf,sn.classprio]; % Inf
for empty positions (lower value = higher priority)
1195 % Buffer stores [
class,phase,
class,phase,...] pairs;
1196 % only inspect
class columns (odd: 1,3,5,...) for priority
1197 class_cols = 1:2:size(space_buf,2);
1198 space_buf_class = space_buf(:, class_cols);
1199 space_buf_class_groupg = arrayfun(@(x) priogroup(1+x), space_buf_class);
1200 start_classprio = min(space_buf_class_groupg(en_wbuf,:),[],2); % min finds highest priority
1201 isrowmax = space_buf_class_groupg == repmat(start_classprio, 1, size(space_buf_class_groupg,2));
1202 % LCFS: Find leftmost (first) class position for highest priority class (most recently preempted)
1203 [~,leftmostClassPos]=max(isrowmax,[],2);
1204 leftmostMaxPos = 2*leftmostClassPos - 1; % convert to buffer
column index
1205 start_svc_class = space_buf(en_wbuf, leftmostMaxPos); % job entering service
1206 kentry = space_buf(en_wbuf, leftmostMaxPos+1); % entry phase of job resuming service (preempt-resume)
1208 % Handle states without buffer jobs
1209 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
1211 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,end).*rate(en_wobuf,:)];
1213 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
1215 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
1217 % Handle states with buffer jobs
1218 if any(en_wbuf) && start_svc_class > 0
1219 space_srv_k = space_srv;
1220 space_buf_k = space_buf;
1221 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
1222 for j=find(en_wbuf)'
1223 % Remove both class and phase from leftmost position (preempt-resume)
1224 % The (class,phase) pair buffer
is RIGHT-aligned: occupied pairs sit at the
1225 % right, empty pairs pad the left. Priority can promote a pair from the
1226 % middle, so closing the hole must pad a whole empty PAIR on the left --
1227 % as State.afterEventStationSignal's dropWaiting does. Padding one slot
1228 % left and one right instead left a trailing empty slot, e.g.
1229 % [2 1 3 1] -> [0 3 1 0], a layout fromMarginal never enumerates: the
1230 % successor was unreachable, which
is what made the generator reducible.
1231 space_buf_k(j,:) = [0, 0, space_buf_k(j,1:leftmostMaxPos(j)-1), space_buf_k(j,(leftmostMaxPos(j)+2):end)];
1233 outspace = [outspace; space_buf_k(en_wbuf,:), space_srv_k(en_wbuf,:), space_var(en_wbuf,:)];
1235 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,end).*rate(en_wbuf,:)];
1237 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wbuf,:)];
1239 outprob = [outprob; ones(size(rate(en_wbuf,:),1),1)];
1241 case SchedStrategy.FCFSPIPRIO % FCFS preempt-independent with priority groups
1242 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
1243 rate(en) = mu{ist}{
class}(k)*(phi{ist}{
class}(k)).*kir(:,class,k); % assume active
1244 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1245 en_wobuf = ~en_wbuf;
1246 priogroup = [Inf,sn.classprio]; % Inf
for empty positions (lower value = higher priority)
1247 space_buf_groupg = arrayfun(@(x) priogroup(1+x), space_buf);
1248 start_classprio = min(space_buf_groupg(en_wbuf,:),[],2); % min finds highest priority
1249 isrowmax = space_buf_groupg == repmat(start_classprio, 1, size(space_buf_groupg,2));
1250 % FCFS: Find rightmost (last) position
for highest priority
class
1251 [~,rightmostMaxPos]=max(fliplr(isrowmax),[],2);
1252 rightmostMaxPos = size(space_buf_groupg,2) - rightmostMaxPos + 1;
1253 start_svc_class = space_buf(en_wbuf, rightmostMaxPos); % job entering service
1255 % Handle states without buffer jobs
1256 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
1258 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,
class).*lldscaling(ist,end).*rate(en_wobuf,:)];
1260 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
1262 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
1264 % Handle states with buffer jobs
1265 if any(en_wbuf) && start_svc_class > 0
1266 % For FCFSPIPRIO, jobs restart from pie distribution instead of stored phase
1267 pentry_svc_class = pie{ist}{start_svc_class};
1268 for kentry = 1:K(start_svc_class)
1269 space_srv_k = space_srv;
1270 space_buf_k = space_buf;
1271 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
1272 for j=find(en_wbuf)
'
1273 % Remove both class and phase from rightmost position (preempt-independent ignores stored phase)
1274 space_buf_k(j,:) = [0, space_buf_k(j,1:rightmostMaxPos(j)-1), space_buf_k(j,(rightmostMaxPos(j)+2):end), 0];
1276 outspace = [outspace; space_buf_k(en_wbuf,:), space_srv_k(en_wbuf,:), space_var(en_wbuf,:)];
1278 rate_k(en_wbuf,:) = rate_k(en_wbuf,:) * pentry_svc_class(kentry);
1280 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,end).*rate_k(en_wbuf,:)];
1282 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en_wbuf,:)];
1284 outprob = [outprob; ones(size(rate_k(en_wbuf,:),1),1)];
1287 case SchedStrategy.LCFSPIPRIO % LCFS preempt-independent with priority groups
1288 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
1289 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % assume active
1290 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1291 en_wobuf = ~en_wbuf;
1292 priogroup = [Inf,sn.classprio]; % Inf for empty positions (lower value = higher priority)
1293 space_buf_groupg = arrayfun(@(x) priogroup(1+x), space_buf);
1294 start_classprio = min(space_buf_groupg(en_wbuf,:),[],2); % min finds highest priority
1295 isrowmax = space_buf_groupg == repmat(start_classprio, 1, size(space_buf_groupg,2));
1296 % LCFS: Find leftmost (first) position for highest priority class
1297 [~,leftmostMaxPos]=max(isrowmax,[],2);
1298 start_svc_class = space_buf(en_wbuf, leftmostMaxPos); % job entering service
1300 % Handle states without buffer jobs
1301 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
1303 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,end).*rate(en_wobuf,:)];
1305 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
1307 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
1309 % Handle states with buffer jobs
1310 if any(en_wbuf) && start_svc_class > 0
1311 % For LCFSPIPRIO, jobs restart from pie distribution instead of stored phase
1312 pentry_svc_class = pie{ist}{start_svc_class};
1313 for kentry = 1:K(start_svc_class)
1314 space_srv_k = space_srv;
1315 space_buf_k = space_buf;
1316 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
1317 for j=find(en_wbuf)'
1318 % Remove both
class and phase from leftmost position (preempt-independent ignores stored phase)
1319 % The (
class,phase) pair buffer
is RIGHT-aligned: occupied pairs sit at the
1320 % right, empty pairs pad the left. Priority can promote a pair from the
1321 % middle, so closing the hole must pad a whole empty PAIR on the left --
1322 % as State.afterEventStationSignal
's dropWaiting does. Padding one slot
1323 % left and one right instead left a trailing empty slot, e.g.
1324 % [2 1 3 1] -> [0 3 1 0], a layout fromMarginal never enumerates: the
1325 % successor was unreachable, which is what made the generator reducible.
1326 space_buf_k(j,:) = [0, 0, space_buf_k(j,1:leftmostMaxPos(j)-1), space_buf_k(j,(leftmostMaxPos(j)+2):end)];
1328 outspace = [outspace; space_buf_k(en_wbuf,:), space_srv_k(en_wbuf,:), space_var(en_wbuf,:)];
1330 rate_k(en_wbuf,:) = rate_k(en_wbuf,:) * pentry_svc_class(kentry);
1332 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,end).*rate_k(en_wbuf,:)];
1334 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en_wbuf,:)];
1336 outprob = [outprob; ones(size(rate_k(en_wbuf,:),1),1)];
1339 case SchedStrategy.SIRO
1340 rate = zeros(size(space_srv,1),1);
1341 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % this is for states not in en_buf
1342 space_srv = inspace(:,(end-sum(K)-V+1):(end-V)); % server state (clear of the local vars)
1343 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
1344 % first record departure in states where the buffer is empty
1345 en_wobuf = en & sum(space_buf(en,:),2) == 0;
1346 if noPromote % immediate feedback: hold server, do not promote a waiting job
1349 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
1350 if isinf(ni) % hit limited load-dependence
1351 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,end).*rate(en_wobuf,:)];
1353 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
1355 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
1356 % let's go now to states where the buffer
is non-empty
1357 % (promotion suppressed under immediate feedback: R*(~noPromote)=0)
1358 for r=1:(R*(~noPromote))
1360 space_buf = inspace(:,1:(end-sum(K)-V)); % buffer state (clear of the local vars)
1361 en_wbuf = en & space_buf(en,r) > 0; % states where the buffer
is non-empty
1362 space_buf(en_wbuf,r) = space_buf(en_wbuf,r) - 1; % remove from buffer
1363 space_srv_r = space_srv;
1364 pentry_svc_class = pie{ist}{r};
1365 pick_prob = (nir(r)-sir(r)) / (ni-sum(sir));
1367 rate_r(en_wbuf,:) = rate_r(en_wbuf,:) * pick_prob;
1370 space_srv_r(en_wbuf,Ks(r)+kentry) = space_srv_r(en_wbuf,Ks(r)+kentry) + 1; % bring job in service
1371 outspace = [outspace; space_buf(en_wbuf,:), space_srv_r(en_wbuf,:), space_var(en_wbuf,:)];
1373 rate_k(en_wbuf,:) = rate_k(en_wbuf,:) * pentry_svc_class(kentry);
1374 if isinf(ni) % hit limited load-dependence
1375 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,
class).*lldscaling(ist,end).*rate_k(en_wbuf,:)];
1377 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en_wbuf,:)];
1379 outprob = [outprob; ones(size(rate(en_wbuf,:),1),1)];
1380 space_srv_r(en_wbuf,Ks(r)+kentry) = space_srv_r(en_wbuf,Ks(r)+kentry) - 1; % bring job in service
1383 case SchedStrategy.POLLING
1384 % A completion ends the visit unless the
1385 % discipline still allows another job of the
1386 % same
class to be taken; when it ends, the
1387 % server walks the cyclic order to wherever the
1388 % next tangible controller state lies. Which of
1389 % the two happens depends on the buffer of each
1390 % state row, so the rows are resolved one by one
1391 % rather than as a single vectorized promotion.
1392 % space_buf/space_srv are re-sliced from inspace by the
1393 % enclosing k-loop; space_var deliberately
is NOT, since
1394 % it already carries the RROBIN/WRROBIN pointer advance
1395 % applied above and re-slicing would discard it.
1396 pinfoD = State.pollingInfo(sn, ind);
1397 for rowD = find(en(:))
'
1398 rateD = mu{ist}{class}(k)*phi{ist}{class}(k)*kir(rowD,class,k);
1402 [~, swkD, ctrD] = State.pollingGet(pinfoD, space_var(rowD,:), class);
1404 continue % no job can complete while the server is walking
1406 bufD = space_buf(rowD,:);
1407 nbufD = bufD(1,1:R);
1408 srvD = space_srv(rowD,:);
1409 srvD(1,Ks(class)+k) = srvD(1,Ks(class)+k) - 1; % record departure
1411 case PollingType.EXHAUSTIVE
1413 goonD = nbufD(class) > 0;
1414 case PollingType.GATED
1415 ctrnextD = ctrD - 1; % one of the gated jobs completed
1416 goonD = ctrnextD > 0;
1417 case PollingType.KLIMITED
1418 ctrnextD = ctrD - 1; % one of the K permitted services used
1419 goonD = ctrnextD > 0 && nbufD(class) > 0;
1420 case PollingType.DECREMENTING
1421 ctrnextD = ctrD; % the target level is fixed for the visit
1422 goonD = nbufD(class) > ctrD;
1425 qD = class; modeD = 1; budgetD = ctrnextD;
1427 [qD, modeD, budgetD] = State.pollingNext(pinfoD, class, nbufD, R, false);
1429 [rowsD, probsD] = State.pollingLand(pinfoD, qD, modeD, budgetD, ...
1430 bufD, srvD, space_var(rowD,:), K, Ks, pie{ist}, R);
1431 for jD = 1:size(rowsD,1)
1432 outspace = [outspace; rowsD(jD,:)];
1433 if isinf(ni) % hit limited load-dependence
1434 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,rowD,class).*lldscaling(ist,end).*rateD.*probsD(jD)];
1436 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,rowD,class).*lldscaling(ist,min(ni(rowD),lldlimit)).*rateD.*probsD(jD)];
1438 outprob = [outprob; 1];
1441 case {SchedStrategy.SEPT,SchedStrategy.LEPT} % move last job in service
1442 rate = zeros(size(space_srv,1),1);
1443 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % this is for states not in en_buf
1444 space_srv = inspace(:,(end-sum(K)-V+1):(end-V)); % server state (clear of the local vars)
1445 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
1446 space_buf = inspace(:,1:(end-sum(K)-V)); % buffer state (clear of the local vars)
1447 % in SEPT, the scheduling parameter is the priority order of the class means
1448 % en_wbuf: states where the buffer is non-empty
1449 % sept_class: class to pick in service
1450 % sn.schedparam(ist,r) holds the RANK of class r's mean service
1451 % time (ascending for SEPT, descending for LEPT, see
1452 % MNetwork.refreshScheduling). Scanning the buffer columns in
1453 % schedparam order
visits the classes as
P(1),
P(2),..., which
1454 % coincides with rank order only when
P is an involution -- true
1455 % of every 2-class model, but false in general, in which case the
1456 % wrong class
is promoted. The discipline needs the inverse
map
1457 % rank -> class, so invert the permutation first.
1458 [~, classByRank] = sort(sn.schedparam(ist,1:R));
1459 [en_wbuf, first_class_inrow] = max(space_buf(:,classByRank)~=0, [], 2);
1460 sept_class = classByRank(first_class_inrow); %
this is different
for sept and lept
1461 if noPromote % immediate feedback: hold server,
do not promote a waiting job
1465 space_buf(en_wbuf,sept_class) = space_buf(en_wbuf,sept_class) - 1; % remove from buffer
1466 pentry = pie{ist}{sept_class};
1467 for kentry=1:K(sept_class)
1468 space_srv(en_wbuf,Ks(sept_class)+kentry) = space_srv(en_wbuf,Ks(sept_class)+kentry) + 1; % bring job in service
1471 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1473 rate_k(en,:) = rate_k(en,:) * pentry(kentry);
1474 if isinf(ni) % hit limited load-dependence
1475 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate_k(en,:)];
1477 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en,:)];
1479 outprob = [outprob; ones(size(rate(en,:),1),1)];
1481 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1483 rate_k(en,:) = rate_k(en,:) * pentry(kentry);
1484 if isinf(ni) % hit limited load-dependence
1485 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,end).*rate_k(en,:)];
1487 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,
class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en,:)];
1489 outprob = [outprob; ones(size(rate(en,:),1),1)];
1491 space_srv(en_wbuf,Ks(sept_class)+kentry) = space_srv(en_wbuf,Ks(sept_class)+kentry) - 1; % bring job in service
1494 line_error(mfilename,sprintf(
'Scheduling strategy %s is not supported.', SchedStrategy.toText(sn.sched(ist))));
1499 if nargin>=7 && isobject(eventCache)
1500 eventCache(key) = {outprob, outspace,outrate};
1503 if size(outspace,1) > 1
1504 tot_rate = sum(outrate);
1505 cum_rate = cumsum(outrate) / tot_rate;
1506 firing_ctr = 1 + max([0,find( rand > cum_rate
' )]); % select action
1507 outspace = outspace(firing_ctr,:);
1508 outrate = sum(outrate);
1509 outprob = outprob(firing_ctr,:);
1514 case EventType.PHASE
1518 [ni,nir,~,kir] = State.toMarginal(sn,ind,inspace,K,Ks,space_buf,space_srv,space_var);
1521 en = space_srv(:,Ks(class)+k) > 0;
1523 for kdest=setdiff(1:K(class),k) % new phase
1525 space_srv_k = space_srv(en,:);
1526 space_buf_k = space_buf(en,:);
1527 space_var_k = space_var(en,:);
1528 if ismkvmodclass(class) && ~isempty(space_var_k)
1529 space_var_k(sum(sn.nvars(ind,1:class))) = kdest;
1531 space_srv_k(:,Ks(class)+k) = space_srv_k(:,Ks(class)+k) - 1;
1532 space_srv_k(:,Ks(class)+kdest) = space_srv_k(:,Ks(class)+kdest) + 1;
1533 switch sn.sched(ist)
1534 case SchedStrategy.EXT
1535 rate = proc{ist}{class}{1}(k,kdest); % move next job forward
1536 case SchedStrategy.INF
1537 rate = proc{ist}{class}{1}(k,kdest)*kir(:,class,k); % assume active
1538 case {SchedStrategy.PS, SchedStrategy.LPS}
1539 rate = proc{ist}{class}{1}(k,kdest)*kir(:,class,k)./ni(:).*min(ni(:),S(ist)); % assume active
1540 case SchedStrategy.PSPRIO
1541 if all(ni <= S(ist)) || sn.classprio(class) == min(sn.classprio(nir>0))
1542 rate = proc{ist}{class}{1}(k,kdest)*kir(:,class,k)./ni(:).*min(ni(:),S(ist)); % assume active
1544 rate = 0; % not in most urgent priority group
1546 case SchedStrategy.DPSPRIO
1547 if all(ni <= S(ist)) || sn.classprio(class) == min(sn.classprio(nir>0))
1548 w_i = sn.schedparam(ist,:);
1549 w_i = w_i / sum(w_i);
1551 if ~all(ni <= S(ist))
1552 nirprio(sn.classprio~=sn.classprio(class)) = 0;
1554 rate = proc{ist}{class}{1}(k,kdest)*kir(:,class,k)*w_i(class)./(sum(repmat(w_i,size(nirprio,1),1)*nirprio',2)); % assume active
1556 rate = 0; % not in most urgent priority group
1558 case SchedStrategy.GPSPRIO
1559 if all(ni <= S(ist)) || sn.classprio(
class) == min(sn.classprio(nir>0))
1560 w_i = sn.schedparam(ist,:);
1561 w_i = w_i / sum(w_i);
1563 if ~all(ni <= S(ist))
1564 nirprio(sn.classprio~=sn.classprio(class)) = 0;
1566 cir = min(nirprio,ones(size(nirprio)));
1567 rate = proc{ist}{
class}{1}(k,kdest)*kir(:,class,k)/nirprio(class)*w_i(class)/(w_i*cir(:)); % assume active
1569 rate = 0; % not in most urgent priority group
1571 case SchedStrategy.DPS
1573 line_error(mfilename,
'Multi-server DPS not supported yet');
1575 w_i = sn.schedparam(ist,:);
1576 w_i = w_i / sum(w_i);
1577 rate = proc{ist}{
class}{1}(k,kdest)*kir(:,class,k)*w_i(class)./(sum(repmat(w_i,size(nir,1),1)*nir
',2)); % assume active
1578 case SchedStrategy.GPS
1580 line_error(mfilename,'Multi-server GPS not supported yet
');
1582 cir = min(nir,ones(size(nir)));
1583 w_i = sn.schedparam(ist,:); w_i = w_i / sum(w_i);
1584 rate = proc{ist}{class}{1}(k,kdest)*kir(:,class,k)/nir(class)*w_i(class)/(w_i*cir(:)); % assume active
1586 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.LCFS, SchedStrategy.LCFSPR, SchedStrategy.LCFSPI, SchedStrategy.LCFSPRIO, SchedStrategy.SIRO, SchedStrategy.SEPT, SchedStrategy.LEPT, SchedStrategy.POLLING}
1587 rate = proc{ist}{class}{1}(k,kdest)*kir(:,class,k); % assume active
1589 % if the class cannot be served locally,
1590 % then rate = NaN since mu{i,class}=NaN
1591 if isinf(ni) % hit limited load-dependence
1592 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,1:size(rate,1),class).*lldscaling(ist,end).*rate];
1594 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,1:size(rate,1),class).*lldscaling(ist,min(ni(en),lldlimit)).*rate];
1596 outspace = [outspace; space_buf_k, space_srv_k, space_var_k];
1597 outprob = [outprob; ones(size(rate,1),1)];
1602 if nargin>=7 && isobject(eventCache)
1603 eventCache(key) = {outprob, outspace,outrate};
1606 if size(outspace,1) > 1
1607 tot_rate = sum(outrate);
1608 cum_rate = cumsum(outrate) / tot_rate;
1609 firing_ctr = 1 + max([0,find( rand > cum_rate' )]); % select action
1610 outspace = outspace(firing_ctr,:);
1611 outrate = sum(outrate);
1612 outprob = outprob(firing_ctr,:);
1616 case EventType.SWITCH
1617 % Switchover of a polling server walking towards buffer `
class`. Unlike
1618 % PHASE, which carries only the internal transitions of a phase-type
1619 % and leaves the absorption to DEP,
this event carries both: a
1620 % completed switchover moves no job and so has no departure to attach
1621 % the absorption to (see refreshSync). It
is therefore also emitted
for
1622 % a single-phase switchover, where it consists of the absorption alone.
1626 pinfoS = State.pollingInfo(sn, ind);
1627 if isempty(pinfoS) || ~pinfoS.hasSw(
class)
1630 for rowS = 1:size(inspace,1)
1631 [posS, swkS] = State.pollingGet(pinfoS, space_var(rowS,:), 0);
1632 if posS ~=
class || swkS == 0
1633 continue % the server
is not inside the switchover into `
class`
1635 D0S = pinfoS.swD0{
class};
1636 D1S = pinfoS.swD1{
class};
1637 % internal transitions of the switchover phase-type
1638 for kdestS = setdiff(1:pinfoS.Ksw(class), swkS)
1639 if D0S(swkS,kdestS) <= 0
1642 varS = State.pollingSet(pinfoS, space_var(rowS,:), class, kdestS, 0);
1643 outspace = [outspace; space_buf(rowS,:), space_srv(rowS,:), varS];
1644 outrate = [outrate; D0S(swkS,kdestS)];
1645 outprob = [outprob; 1];
1647 % absorption: the server arrives at buffer `
class` and either opens
1648 % a visit there or walks on
1649 rateS = sum(D1S(swkS,:));
1653 nbufS = space_buf(rowS,1:R);
1654 [qS, modeS, budgetS] = State.pollingNext(pinfoS,
class, nbufS, R,
true);
1655 [rowsS, probsS] = State.pollingLand(pinfoS, qS, modeS, budgetS, ...
1656 space_buf(rowS,:), space_srv(rowS,:), space_var(rowS,:), K, Ks, pie{ist}, R);
1657 for jS = 1:size(rowsS,1)
1658 % A switchover that completes over an empty buffer starts the
1659 % next leg at once, and when that leg re-enters the same phase
1660 % of the same switchover (the single-buffer
case, or any
1661 % memoryless switchover) the landing state
is the departure
1662 % state. Such a self-loop
is not a transition: emitting it would
1663 % add a spurious rate to the row and inflate the exit rate.
1664 if isequal(rowsS(jS,:), inspace(rowS,:))
1667 outspace = [outspace; rowsS(jS,:)];
1668 outrate = [outrate; rateS*probsS(jS)];
1669 outprob = [outprob; 1];
1672 case EventType.RENEGE
1673 % Exponential-patience reneging: each waiting (queued, not-in-service)
1674 %
class-r job abandons the queue at a memoryless rate impatienceMu, so
1675 % the aggregate rate out of
this state
is (waiting count) * mu. One
1676 % waiting job
is removed from the buffer and leaves the system (the
1677 % passive half of the sync
is LOCAL). Removing the newest waiting
1678 %
class-r job and re-padding a zero keeps the buffer in the canonical
1679 % right-aligned
form used by the arrival handler;
for memoryless
1680 % patience all waiting jobs are exchangeable, so the choice does not
1681 % affect the marginal distribution.
1685 [~,nir,sir] = State.toMarginal(sn,ind,inspace,K,Ks,space_buf,space_srv,space_var);
1686 waiting_r = nir(
class) - sir(
class);
1688 slot = find(space_buf ==
class, 1,
'first');
1690 space_buf_k = space_buf;
1691 space_buf_k(slot) = [];
1692 space_buf_k = [0, space_buf_k];
1693 outspace = [space_buf_k, space_srv, space_var];
1694 outrate = waiting_r * sn.impatienceMu(ist,
class);
1698 case EventType.RETRY
1699 % Exponential retrial: an orbiting (buffered)
class-r job retries entry
1700 % into the station. The retry succeeds only when a server
is free, in
1701 % which
case one orbiting job enters service (in an entry phase drawn
1702 % from pie); otherwise the job stays in orbit and the
event is a no-op
1703 % (not generated). The aggregate rate
is (orbit size) * retrialMu.
1707 [~,nir,sir] = State.toMarginal(sn,ind,inspace,K,Ks,space_buf,space_srv,space_var);
1708 orbit_r = nir(
class) - sir(
class); % orbiting
class-r jobs (held in buffer)
1709 if orbit_r > 0 && sum(space_srv,2) < S(ist)
1710 slot = find(space_buf ==
class, 1,
'first');
1712 space_buf_k = space_buf;
1713 space_buf_k(slot) = [];
1714 space_buf_k = [0, space_buf_k];
1715 pentry = pie{ist}{
class};
1716 if all(isnan(pentry))
1717 pentry = ones(size(pentry)) / length(pentry);
1719 for kentry = 1:K(
class)
1720 if pentry(kentry) <= 0
1723 space_srv_k = space_srv;
1724 space_srv_k(:,Ks(class)+kentry) = space_srv_k(:,Ks(class)+kentry) + 1;
1725 % LINEAR policy: every orbiting job carries its own timer, so
1726 % the aggregate retrial rate scales with the orbit size.
1727 % CONSTANT policy: one controller retries on behalf of the
1728 % whole orbit, so the rate does not depend on the orbit size.
1729 retrialRate = orbit_r * sn.retrialMu(ist,class);
1730 if isfield(sn,'retrialPolicy') && ~isempty(sn.retrialPolicy) ...
1731 && size(sn.retrialPolicy,1) >= ist && size(sn.retrialPolicy,2) >= class ...
1732 && sn.retrialPolicy(ist,class) == RetrialPolicy.CONSTANT
1733 retrialRate = sn.retrialMu(ist,class);
1735 outspace = [outspace; space_buf_k, space_srv_k, space_var];
1736 outrate = [outrate; retrialRate * pentry(kentry)];
1737 outprob = [outprob; 1];
1739 if isSimulation && size(outspace,1) > 1
1740 cr = cumsum(outrate) / sum(outrate);
1741 fc = 1 + max([0, find(rand > cr')]);
1742 outspace = outspace(fc,:);
1743 outrate = sum(outrate);
1750% True BAS: when the front job
is already blocked (completed, held at the server), this
1751% DEP
is the *instant transfer* of that job downstream — fire at rate 1e7 (effectively
1752% instant) and clear the blocked marker in the successor. The complementary become-blocked
1753% transition (b:0->1 when the destination
is full)
is added by the CTMC generator / SSA
1754% engine, since only they can see the destination's occupancy. Blocked marker = last state
1755%
column when this station declares it (nvars col 2*R+1 == 1).
1756% The blocked marker shares nvars col 2*R+1 with the polling controller, so gate on
1757% the dedicated sn.isbasblocking field (set for the blocking station under BOTH the
1758% upstream and destination declaration forms) rather than the station's own drop
1759% rule, which fails for a destination-declared BAS. See BUG-83.
1760if event == EventType.DEP && ~isempty(sn.isbasblocking) && numel(sn.isbasblocking) >= ind ...
1761 && sn.isbasblocking(ind) == 1 ...
1762 && ~isempty(inspace) && inspace(1,end) == 1 && ~isempty(outspace)
1763 outspace(:,end) = 0;
1767% Degraded service while the server
is down: the scheduling handlers computed the
1768% completion rate from the up-server service process, so rescale it to the
1769% configured down-server rate. downRateScale
is 1 whenever the server
is up or no
1770% degraded rate was configured, so this
is a no-op in every other model.
1771if downRateScale ~= 1 && ~isempty(outrate)
1772 outrate = outrate * downRateScale;