LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
afterEventStation.m
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)
11 noPromote = false;
12end
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,:)));
19outspace = [];
20outrate = [];
21outprob = 1;
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);
26 return;
27end
28switch event
29 case EventType.ARV %% passive
30 % A Place holds a marking, not a service facility: it has no servers and
31 % no service phases, so an arriving token only increments the class
32 % marking. The scheduling branches below would instead write an entering
33 % job into a phase slot that a Place state does not carry; MATLAB grows
34 % the row to fit, and the widened state can no longer be matched against
35 % the marking state space, so the successor hash fails and the arrival is
36 % dropped. Closed SPNs never notice, because there tokens reach a Place
37 % through FIRE (State.afterGlobalEvent) rather than through ARV.
38 if sn.nodetype(ind) == NodeType.Place
39 outspace = inspace;
40 outspace(:,class) = outspace(:,class) + 1;
41 % passive action: the rate is set by the active node
42 outrate = -1*ones(size(outspace,1),1);
43 outprob = ones(size(outspace,1),1);
44 return
45 end
46 % Signal / catastrophe arrival: a signal class is a G-network negative
47 % customer. Instead of joining, it removes job(s) of its target class
48 % from this station and is itself annihilated. CATASTROPHE empties the
49 % station of all jobs. The event stays passive (rate set by the active
50 % signal source); when there is no target job the signal simply vanishes
51 % (destination state unchanged).
52 if isfield(sn,'issignal') && ~isempty(sn.issignal) && sn.issignal(class)
53 [outspace, outrate, outprob] = State.afterEventStationSignal(sn, ind, ist, inspace, class, K, Ks, S, pie, space_buf, space_srv, space_var);
54 % Which job a signal removes is in general a random choice, so the
55 % generator needs every destination and its probability. A simulation
56 % instead walks one sample path, so it draws a single successor from
57 % outprob, as the balking branch below does for its passive action.
58 if isSimulation && size(outprob,1) > 1
59 cum_prob = cumsum(outprob) / sum(outprob);
60 firing_ctr = 1 + max([0,find( rand > cum_prob' )]); % select action
61 outspace = outspace(firing_ctr,:);
62 outrate = -1;
63 outprob = 1;
64 end
65 return;
66 end
67 % Stochastic Petri net Place (ordinary, non-queueing): the place state is
68 % a width-R token marking (one count per class); its full row is exactly
69 % inspace. A token arriving from a Source (open SPN) or routed from a
70 % Transition simply increments this place's marking for its class,
71 % subject to the per-class place capacity classcap: a finite-capacity
72 % place blocks/loses arrivals once full, which for a
73 % Source->Place->Transition->Sink net reproduces M/M/1/K exactly.
74 % Handle it here because the generic scheduling switch below splits the
75 % row into (buffer, server-phases) and, for an INF place, misassigns the
76 % marking column to the server section, writing the arriving token into a
77 % phase slot instead of the marking. The resulting row does not match any
78 % enumerated marking, so every Source->Place arrival is silently dropped
79 % and the generator degenerates to a pure-death chain. A queueing place
80 % carries a real embedded server (list-format buffer) and falls through.
81 isOrdinaryPlace = isfield(sn,'nodetype') && ~isempty(sn.nodetype) ...
82 && sn.nodetype(ind) == NodeType.Place;
83 if isOrdinaryPlace && isfield(sn,'isqueueingplace') && ~isempty(sn.isqueueingplace) ...
84 && ist <= numel(sn.isqueueingplace) && sn.isqueueingplace(ist)
85 isOrdinaryPlace = false;
86 end
87 if isOrdinaryPlace
88 outspace = inspace;
89 nrows = size(outspace,1);
90 if inspace(1,class) < classcap(ist,class)
91 outspace(:,class) = outspace(:,class) + 1;
92 outrate = -ones(nrows,1); % passive: rate set by active source
93 outprob = ones(nrows,1);
94 else
95 % place full: arrival is blocked and lost (no state change)
96 outrate = -ones(nrows,1);
97 outprob = zeros(nrows,1);
98 end
99 return;
100 end
101 % return if there is no space to accept the arrival
102 [ni,nir] = State.toMarginalAggr(sn,ind,inspace,K,Ks,space_buf,space_srv,space_var);
103 % otherwise check scheduling strategy
104 pentry = pie{ist}{class};
105 % For Place nodes (INF scheduling with NaN service), use uniform entry probability
106 if all(isnan(pentry))
107 pentry = ones(size(pentry)) / length(pentry);
108 end
109 outprob = [];
110 outprob_k = [];
111 for kentry = 1:K(class)
112 space_var_k = space_var;
113 space_srv_k = space_srv;
114 space_buf_k = space_buf;
115 switch sn.sched(ist)
116 case SchedStrategy.EXT % source, can receive any "virtual" arrival from the sink as long as it is from an open class
117 if isinf(sn.njobs(class))
118 outspace = inspace;
119 outrate = -1*zeros(size(outspace,1)); % passive action, rate is unspecified
120 outprob = ones(size(outspace,1));
121 break
122 end
123 case {SchedStrategy.PS, SchedStrategy.INF, SchedStrategy.DPS, SchedStrategy.GPS, SchedStrategy.PSPRIO, SchedStrategy.DPSPRIO, SchedStrategy.GPSPRIO, SchedStrategy.LPS}
124 % job enters service immediately
125 if space_srv_k(:,Ks(class)+kentry) < classcap(ist,class)
126 space_srv_k(:,Ks(class)+kentry) = space_srv_k(:,Ks(class)+kentry) + 1;
127 outprob_k = pentry(kentry)*ones(size(space_srv_k,1));
128 else
129 outprob_k = pentry(kentry)*zeros(size(space_srv_k,1));
130 end
131 case {SchedStrategy.SIRO, SchedStrategy.SEPT, SchedStrategy.LEPT}
132 % Idle-server test on server occupancy, not total count ni: these
133 % agree for work-conserving states but an immediate-feedback
134 % self-loop transiently yields an idle server with a non-empty
135 % buffer, where the fed-back job must re-enter the vacated server
136 % (mirrors the FCFS case below).
137 if sum(space_srv_k,2)<S(ist)
138 space_srv_k(:,Ks(class)+kentry) = space_srv_k(:,Ks(class)+kentry) + 1;
139 outprob_k = pentry(kentry)*ones(size(space_srv_k,1));
140 else
141 space_buf_k(:,class) = space_buf_k(:,class) + 1;
142 outprob_k = pentry(kentry)*ones(size(space_srv_k,1));
143 end
144 case SchedStrategy.POLLING
145 % The controller, not the arrival, decides who is served:
146 % an arriving job joins its class buffer and waits for the
147 % server to walk to it, even when the facility is idle,
148 % because the server is then in a switchover. The exception
149 % is a parked server, which State.pollingNext only ever
150 % produces with an empty station and immediate switchovers:
151 % it therefore reaches the arriving job in zero time and
152 % starts a visit on it at once.
153 pinfoA = State.pollingInfo(sn, ind);
154 srvclassA = 0;
155 for rA=1:R
156 if sum(space_srv_k(1,(Ks(rA)+1):(Ks(rA)+K(rA)))) > 0
157 srvclassA = rA;
158 break
159 end
160 end
161 [posA, swkA] = State.pollingGet(pinfoA, space_var_k, srvclassA);
162 if srvclassA == 0 && swkA == 0
163 % Parked. The arriving job is the only work present, so
164 % the walk necessarily resolves to a visit on its own
165 % class: it enters service here, in phase kentry, and
166 % never occupies the buffer.
167 nbufA = space_buf_k(1,1:R);
168 nbufA(class) = nbufA(class) + 1;
169 [qA, ~, budgetA] = State.pollingNext(pinfoA, posA, nbufA, R, true);
170 space_srv_k(:,Ks(class)+kentry) = space_srv_k(:,Ks(class)+kentry) + 1;
171 space_var_k = State.pollingSet(pinfoA, space_var_k, qA, 0, budgetA);
172 else
173 space_buf_k(:,class) = space_buf_k(:,class) + 1;
174 end
175 outprob_k = pentry(kentry)*ones(size(space_srv_k,1));
176 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.LCFS, SchedStrategy.LCFSPRIO}
177 % find states with all servers busy - this
178 % needs not to be moved
179
180 % if MAP service, when empty restart from the phase
181 % stored in space_var for this class
182 if ~ismkvmodclass(class) || (ismkvmodclass(class) && kentry == space_var(sum(sn.nvars(ind,1:class))))
183 if ismkvmodclass(class)
184 pentry = zeros(size(pentry));
185 pentry(kentry) = 1.0;
186 end
187 all_busy_srv = sum(space_srv_k,2) >= S(ist);
188
189 % find and modify states with an idle server
190 idle_srv = sum(space_srv_k,2) < S(ist);
191 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
192
193 % this section dynamically grows the number of
194 % elements in the buffer
195
196 if any(ni < capacity(ist))
197 if any(nir(:,class) < classcap(ist,class)) % if there is room
198 if ~any(space_buf_k(:)==0) % but the buffer has no empty slots
199 % append job slot
200 space_buf_k = [zeros(size(space_buf_k,1),1),space_buf_k];
201 end
202 end
203 end
204 %end
205 %get position of first empty slot
206 empty_slots = -1*ones(size(all_busy_srv,1),1);
207 if size(space_buf_k,2) == 0
208 empty_slots(all_busy_srv) = false;
209 elseif size(space_buf_k,2) == 1
210 empty_slots(all_busy_srv) = space_buf_k(all_busy_srv,:)==0;
211 else
212 empty_slots(all_busy_srv) = max(bsxfun(@times, space_buf_k(all_busy_srv,:)==0, [1:size(space_buf_k,2)]),[],2);
213 end
214
215 % ignore states where the buffer has no empty slots.
216 % A structurally free buffer column is NOT enough: the job may
217 % only be placed when the CAPACITY also permits it. Without this
218 % the job was written into the slot and the whole row was then
219 % vetoed by the capacity filter below (en_o), which returned an
220 % EMPTY outspace -- i.e. the arrival event never fired, so the
221 % upstream departure was never counted and the reported arrival
222 % rate collapsed from the OFFERED rate to the CARRIED one
223 % (BUG-85: M/M/1/1 reported ArvR 0.444444 instead of 0.800000,
224 % hiding the loss entirely, while M/M/1/2 -- whose buffer is
225 % full rather than absent -- correctly reported 0.800000).
226 % Leaving the state unchanged instead makes the arrival a
227 % self-loop: the event fires, ArvR counts the offered job, and
228 % the job is lost. A self-loop cancels on the generator diagonal,
229 % so the stationary distribution -- and hence QLen/Util/Tput --
230 % cannot move.
231 % Gate the placement on the capacity ONLY for a PHYSICAL
232 % finite capacity. When the bound is a state-space cutoff
233 % (open class, no physical cap; solver_ssa folds the cutoff
234 % into the capacity/classcap arguments), keep the pre-change
235 % behaviour: place the job structurally and let the en_o
236 % capacity filter below delete the beyond-cutoff row
237 % (truncation). Firing the capacity gate at a cutoff would
238 % send the arrival to the loss/block branch, turning a
239 % truncation into a self-loop. See State.isPhysicalCapacity.
240 if State.isPhysicalCapacity(sn, ist, class)
241 hasRoom = (ni < capacity(ist)) & (nir(:,class) < classcap(ist,class));
242 else
243 hasRoom = true(size(empty_slots));
244 end
245 wbuf_empty = empty_slots>0 & hasRoom;
246 if any(wbuf_empty)
247 space_srv_k = space_srv_k(wbuf_empty,:);
248 space_buf_k = space_buf_k(wbuf_empty,:);
249 space_var_k = space_var_k(wbuf_empty,:);
250 empty_slots = empty_slots(wbuf_empty);
251 space_buf_k(sub2ind(size(space_buf_k),1:size(space_buf_k,1),empty_slots')) = class;
252 %outspace(all_busy_srv(wbuf_empty),:) = [space_buf, space_srv, space_var];
253 elseif any(all_busy_srv) && ~State.arrivalIsLost(sn, ist, class)
254 % The arrival cannot be placed (all servers busy, no room).
255 % Whether that is a LOSS or a BLOCK is decided by the class
256 % type, not by the drop rule: a CLOSED job has nowhere to go,
257 % so it cannot be lost, and an explicit blocking rule
258 % (BAS/BBS/RSRD) asks for blocking too. Removing the rows
259 % returns an EMPTY outspace, which disables the upstream
260 % departure until space frees (and is what the CTMC
261 % become-blocked edge tests for, so true BAS fires). For a
262 % LOST arrival the rows are kept unchanged instead, so the
263 % event still fires and ArvR counts the offered job -- see
264 % State.arrivalIsLost and the hasRoom comment above.
265 space_srv_k = space_srv_k(idle_srv,:);
266 space_buf_k = space_buf_k(idle_srv,:);
267 space_var_k = space_var_k(idle_srv,:);
268 end
269 outprob_k = pentry(kentry)*ones(size(space_srv_k,1),1);
270 else
271 outprob_k = 0*ones(size(space_srv_k,1),1); % zero probability event
272 end
273 case {SchedStrategy.FCFSPR,SchedStrategy.FCFSPI,SchedStrategy.FCFSPRPRIO,SchedStrategy.FCFSPIPRIO,SchedStrategy.LCFSPR,SchedStrategy.LCFSPI,SchedStrategy.LCFSPRPRIO,SchedStrategy.LCFSPIPRIO}
274 % find states with all servers busy - this
275 % must not be moved
276 all_busy_srv = sum(space_srv_k,2) >= S(ist);
277 % find states with an idle server
278 idle_srv = sum(space_srv_k,2) < S(ist);
279
280 % reorder states so that idle ones come first
281 space_buf_k_reord = space_buf_k(idle_srv,:);
282 space_srv_k_reord = space_srv_k(idle_srv,:);
283 space_var_k_reord = space_var_k(idle_srv,:);
284
285 % if idle, the job enters service in phase kentry
286 if any(idle_srv)
287 space_srv_k_reord(:, end-sum(K)+Ks(class)+kentry) = space_srv_k_reord(:,end-sum(K)+Ks(class)+kentry) + 1;
288 outprob_k = pentry(kentry);
289 else
290 % if all busy, expand output states for all possible choices of job class to preempt
291 psentry = ones(size(space_buf_k_reord,1),1); % probability scaling due to preemption
292 isPrioAware = (sn.sched(ist) == SchedStrategy.FCFSPRPRIO || sn.sched(ist) == SchedStrategy.FCFSPIPRIO || sn.sched(ist) == SchedStrategy.LCFSPRPRIO || sn.sched(ist) == SchedStrategy.LCFSPIPRIO);
293 isLcfsPrioFamily = (sn.sched(ist) == SchedStrategy.LCFSPRPRIO || sn.sched(ist) == SchedStrategy.LCFSPIPRIO);
294 for classpreempt = 1:R
295 % For priority variants (or LCFSPR/FCFSPR when priorities differ),
296 % only higher-priority jobs can preempt
297 isPrioSched = (sn.sched(ist) == SchedStrategy.FCFSPRPRIO || sn.sched(ist) == SchedStrategy.FCFSPIPRIO || sn.sched(ist) == SchedStrategy.LCFSPRPRIO || sn.sched(ist) == SchedStrategy.LCFSPIPRIO);
298 % Priority-awareness is a property of the DECLARED policy, never of
299 % the data. LCFSPRPRIO/FCFSPRPRIO are the priority-aware variants;
300 % inferring the discipline from ~all(classprio==classprio(1)) turned
301 % plain LCFSPR/FCFSPR into something that is neither the base policy
302 % nor the PRIO variant. The FCFSPR case below states the rule outright
303 % ("FCFS preempt-resume (no priority)").
304 isPrioAware = isPrioSched;
305 if isPrioAware
306 % Across priority groups a strictly higher-priority arrival preempts.
307 % WITHIN a group the base discipline decides: LCFS-PR keeps the NEWEST
308 % job in service, so an equal-priority arrival preempts; FCFS-PR never
309 % lets an arrival preempt. Refusing on equality for both left
310 % LCFSPRPRIO unable to preempt anything when the priorities were flat,
311 % so no arrival could enter a busy station at all.
312 isLcfsPrioFamily = (sn.sched(ist) == SchedStrategy.LCFSPRPRIO || sn.sched(ist) == SchedStrategy.LCFSPIPRIO);
313 if isLcfsPrioFamily
314 cannotPreempt = sn.classprio(class) > sn.classprio(classpreempt);
315 else
316 cannotPreempt = sn.classprio(class) >= sn.classprio(classpreempt);
317 end
318 if cannotPreempt
319 continue;
320 end
321 end
322 for phasepreempt = 1:K(classpreempt) % phase of job to preempt
323 si_preempt = space_srv_k(:, (end-sum(K)+Ks(classpreempt)+phasepreempt));
324 busy_preempt = si_preempt > 0; % states where there is at least on class-r job in execution
325 if any(busy_preempt)
326 psentry = [psentry; si_preempt(busy_preempt) ./ sum(space_srv_k,2)];
327 space_srv_k_preempt = space_srv_k(busy_preempt,:);
328 space_buf_k_preempt = space_buf_k(busy_preempt,:);
329 space_var_k_preempt = space_var_k(busy_preempt,:);
330 space_srv_k_preempt(:, end-sum(K)+Ks(classpreempt)+phasepreempt) = space_srv_k_preempt(:,end-sum(K)+Ks(classpreempt)+phasepreempt) - 1; % remove preempted job
331 space_srv_k_preempt(:, end-sum(K)+Ks(class)+kentry) = space_srv_k_preempt(:,end-sum(K)+Ks(class)+kentry) + 1;
332
333 % dynamically grow buffer lenght in
334 % simulation
335 if isSimulation
336 if ni < capacity(ist) && nir(class) < classcap(ist,class) % if there is room
337 if ~any(space_buf_k_preempt(:)==0) % but the buffer has no empty slots
338 % append job slot
339 space_buf_k_preempt = [zeros(size(space_buf_k_preempt,1),2),space_buf_k]; % append two columns for (class, preempt-phase)
340 end
341 end
342 end
343
344 %get position of first empty slot
345 empty_slots = -1*ones(sum(busy_preempt),1);
346 if size(space_buf_k_preempt,2) == 0
347 empty_slots(busy_preempt) = false;
348 elseif size(space_buf_k_preempt,2) == 2 % 2 due to (class, preempt-phase) pairs
349 empty_slots(busy_preempt) = space_buf_k_preempt(busy_preempt,1:2:end)==0;
350 else
351 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
352 end
353
354 % ignore states where the buffer has no empty slots
355 wbuf_empty = empty_slots>0;
356 if any(wbuf_empty)
357 space_srv_k_preempt = space_srv_k_preempt(wbuf_empty,:);
358 space_buf_k_preempt = space_buf_k_preempt(wbuf_empty,:);
359 space_var_k_preempt = space_var_k_preempt(wbuf_empty,:);
360 empty_slots = empty_slots(wbuf_empty);
361 if sn.sched(ist) == SchedStrategy.LCFSPR || sn.sched(ist) == SchedStrategy.LCFSPRPRIO || sn.sched(ist) == SchedStrategy.FCFSPR || sn.sched(ist) == SchedStrategy.FCFSPRPRIO % preempt-resume
362 space_buf_k_preempt(sub2ind(size(space_buf_k_preempt),1:size(space_buf_k_preempt,1),empty_slots')+1) = phasepreempt;
363 elseif sn.sched(ist) == SchedStrategy.LCFSPI || sn.sched(ist) == SchedStrategy.LCFSPIPRIO || sn.sched(ist) == SchedStrategy.FCFSPI || sn.sched(ist) == SchedStrategy.FCFSPIPRIO % preempt-independent
364 space_buf_k_preempt(sub2ind(size(space_buf_k_preempt),1:size(space_buf_k_preempt,1),empty_slots')+1) = 1;
365 end
366 space_buf_k_preempt(sub2ind(size(space_buf_k_preempt),1:size(space_buf_k_preempt,1),empty_slots')) = classpreempt;
367 %outspace(all_busy_srv(wbuf_empty),:) = [space_buf, space_srv, space_var];
368 end
369 space_srv_k_reord = [space_srv_k_reord; space_srv_k_preempt];
370 space_buf_k_reord = [space_buf_k_reord; space_buf_k_preempt];
371 space_var_k_reord = [space_var_k_reord; space_var_k_preempt];
372 end
373 end
374 end
375
376 % Rows where the arrival can preempt nothing: every busy server holds a
377 % job it may not displace. The job cannot seize a server, so it WAITS in
378 % the buffer. Without this branch the loop above emits no state at all
379 % for those rows -- the arrival transition simply does not exist, so the
380 % class can never enter a busy station and its queue is silently
381 % understated. Python already carries this fallback (its BUG-70 fix);
382 % MATLAB never received it. The job is stored as a (class, entry-phase)
383 % pair exactly as a preempted job is, so on promotion it resumes from
384 % that phase (for exponential service resume == restart).
385 if isPrioAware
386 canPreempt = false(size(space_srv_k,1),1);
387 for cp = 1:R
388 if isLcfsPrioFamily
389 preemptable = sn.classprio(class) <= sn.classprio(cp);
390 else
391 preemptable = sn.classprio(class) < sn.classprio(cp);
392 end
393 if preemptable
394 for ph = 1:K(cp)
395 canPreempt = canPreempt | (space_srv_k(:, end-sum(K)+Ks(cp)+ph) > 0);
396 end
397 end
398 end
399 waitRows = all_busy_srv & ~canPreempt;
400 if any(waitRows)
401 space_srv_k_wait = space_srv_k(waitRows,:);
402 space_buf_k_wait = space_buf_k(waitRows,:);
403 space_var_k_wait = space_var_k(waitRows,:);
404 % rightmost empty (class,phase) pair, matching the preemption store
405 empty_wait = -1*ones(sum(waitRows),1);
406 if size(space_buf_k_wait,2) > 0
407 empty_wait = max(bsxfun(@times, space_buf_k_wait==0, 1:size(space_buf_k_wait,2)),[],2)-1;
408 end
409 keep_wait = empty_wait > 0;
410 if any(keep_wait)
411 space_srv_k_wait = space_srv_k_wait(keep_wait,:);
412 space_buf_k_wait = space_buf_k_wait(keep_wait,:);
413 space_var_k_wait = space_var_k_wait(keep_wait,:);
414 ew = empty_wait(keep_wait);
415 nw = size(space_buf_k_wait,1);
416 space_buf_k_wait(sub2ind(size(space_buf_k_wait),(1:nw)',ew)) = class;
417 space_buf_k_wait(sub2ind(size(space_buf_k_wait),(1:nw)',ew+1)) = kentry;
418 psentry = [psentry; ones(nw,1)];
419 space_srv_k_reord = [space_srv_k_reord; space_srv_k_wait];
420 space_buf_k_reord = [space_buf_k_reord; space_buf_k_wait];
421 space_var_k_reord = [space_var_k_reord; space_var_k_wait];
422 end
423 end
424 end
425 outprob_k = pentry(kentry) * psentry .* ones(size(space_srv_k_reord,1),1);
426 end
427 space_buf_k = space_buf_k_reord; % save reordered output states
428 space_srv_k = space_srv_k_reord; % save reordered output states
429 space_var_k = space_var_k_reord; % save reordered output states
430 end
431 % form the new state
432 outspace_k = [space_buf_k, space_srv_k, space_var_k];
433 % remove states where new arrival violates capacity or cutoff constraints
434 [oi,oir] = State.toMarginalAggr(sn,ind,outspace_k,K,Ks,space_buf_k,space_srv_k,space_var_k);
435 en_o = classcap(ist,class)>= oir(:,class) & capacity(ist)*ones(size(oi,1),1) >= oi;
436
437 if size(outspace,2)>size(outspace_k(en_o,:),2)
438 outspace = [outspace; zeros(1,size(outspace,2)-size(outspace_k(en_o,:),2)),outspace_k(en_o,:)];
439 elseif size(outspace,2)<size(outspace_k(en_o,:),2)
440 outspace = [zeros(size(outspace,1),size(outspace_k(en_o,:),2)-size(outspace,2)), outspace; outspace_k(en_o,:)];
441 else
442 outspace = [outspace; outspace_k(en_o,:)];
443 end
444 outrate = [outrate; -1*ones(size(outspace_k(en_o,:),1),1)]; % passive action, rate is unspecified
445 outprob = [outprob; outprob_k(en_o,:)];
446 end
447 % Balking (QUEUE_LENGTH strategy): with probability balkProb the
448 % arriving class-r job refuses to join, based on the pre-arrival total
449 % station population ni. A balked job is lost (destination state left
450 % unchanged); the admitted branches are scaled by (1-balkProb). Only
451 % the QUEUE_LENGTH strategy is a pure function of the state vector and
452 % is therefore admissible in state-space solvers; EXPECTED_WAIT /
453 % COMBINED depend on the mean wait and are rejected in the analyzers.
454 if isfield(sn,'balkingStrategy') && ~isempty(sn.balkingStrategy) ...
455 && sn.balkingStrategy(ist,class) == BalkingStrategy.QUEUE_LENGTH ...
456 && ~isempty(outspace)
457 balkProb = 0;
458 thresholds = sn.balkingThresholds{ist,class};
459 qlen = ni(1); % pre-arrival total station population
460 for ti = 1:numel(thresholds)
461 th = thresholds{ti};
462 if qlen >= th{1} && qlen <= th{2}
463 balkProb = th{3};
464 break
465 end
466 end
467 if balkProb > 0
468 outprob = outprob * (1 - balkProb);
469 % balked branch: job lost, destination state unchanged
470 inrow = inspace;
471 if size(outspace,2) > size(inrow,2)
472 inrow = [zeros(1,size(outspace,2)-size(inrow,2)), inrow];
473 end
474 outspace = [outspace; inrow];
475 outrate = [outrate; -1];
476 outprob = [outprob; balkProb];
477 end
478 end
479 if isSimulation
480 if size(outprob,1) > 1
481 cum_prob = cumsum(outprob) / sum(outprob);
482 firing_ctr = 1 + max([0,find( rand > cum_prob' )]); % select action
483 outspace = outspace(firing_ctr,:);
484 outrate = -1;
485 outprob = 1;
486 end
487 end
488 case EventType.DEP
489 % Marked (MMAP) source class: the shared modulating chain lives in the
490 % carrier's phase block (mark index 1); this class's departures fire
491 % from there using its per-mark matrix D1k (M3A cell index 2+mark).
492 markofclass = -1;
493 phclass = class;
494 if sn.sched(ist) == SchedStrategy.EXT && isfield(sn,'markidx') ...
495 && ~isempty(sn.markidx) && ist <= size(sn.markidx,1) ...
496 && sn.markidx(ist,class) > 0
497 markofclass = sn.markidx(ist,class);
498 phclass = find(sn.markidx(ist,:) == 1, 1);
499 end
500 if any(any(space_srv(:,(Ks(phclass)+1):(Ks(phclass)+K(phclass))))) % something is busy
501 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)
502 nir = space_srv;
503 ni = sum(nir,2);
504 sir = nir;
505 kir = sir;
506 else
507 [ni,nir,sir,kir] = State.toMarginal(sn,ind,inspace,K,Ks,space_buf,space_srv,space_var);
508 end
509 switch sn.routing(ind,class)
510 case RoutingStrategy.RROBIN
511 idx = find(space_var(sum(sn.nvars(ind,1:(R+class)))) == sn.nodeparam{ind}{class}.outlinks);
512 if idx < length(sn.nodeparam{ind}{class}.outlinks)
513 space_var(sum(sn.nvars(ind,1:(R+class)))) = sn.nodeparam{ind}{class}.outlinks(idx+1);
514 else
515 space_var(sum(sn.nvars(ind,1:(R+class)))) = sn.nodeparam{ind}{class}.outlinks(1);
516 end
517 case RoutingStrategy.WRROBIN
518 % WRR slot holds a POSITION in weighted_outlinks; advance it
519 % cyclically (mirrors afterEventRouter and sub_wrr). Without
520 % this advance the position stays fixed and only the initial
521 % destination is ever selected.
522 slot = sum(sn.nvars(ind,1:(R+class)));
523 if isfield(sn.nodeparam{ind}{class}, 'weighted_outlinks') ...
524 && ~isempty(sn.nodeparam{ind}{class}.weighted_outlinks)
525 cycle_len = length(sn.nodeparam{ind}{class}.weighted_outlinks);
526 else
527 cycle_len = length(sn.nodeparam{ind}{class}.outlinks);
528 end
529 pos = space_var(slot);
530 if pos < 1 || pos >= cycle_len
531 space_var(slot) = 1;
532 else
533 space_var(slot) = pos + 1;
534 end
535 end
536 if sir(phclass)>0 % is a job of class is in service
537 outprob = [];
538 for k=1:K(phclass)
539 space_srv = inspace(:,(end-sum(K)-V+1):(end-V)); % server state
540 space_buf = inspace(:,1:(end-sum(K)-V)); % buffer state
541 rate = zeros(size(space_srv,1),1);
542 en = space_srv(:,Ks(phclass)+k) > 0;
543 if any(en)
544 switch sn.sched(ist)
545 case SchedStrategy.EXT % source, can produce an arrival from phase-k as long as it is from an open class
546 if isinf(sn.njobs(class))
547 if markofclass > 0
548 % per-mark arrival matrix over the shared chain
549 D1_srv = proc{ist}{class}{2+markofclass};
550 else
551 D1_srv = proc{ist}{class}{2};
552 end
553 for kentry = 1:K(phclass)
554 arv_rate = D1_srv(k, kentry);
555 if arv_rate <= 0
556 continue
557 end
558 space_srv = inspace(:,(end-sum(K)-V+1):(end-V));
559 space_srv(en,Ks(phclass)+k) = space_srv(en,Ks(phclass)+k) - 1;
560 space_srv(en,Ks(phclass)+kentry) = space_srv(en,Ks(phclass)+kentry) + 1;
561 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
562 if isinf(ni)
563 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*arv_rate*ones(size(inspace(en,:),1),1)];
564 else
565 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*arv_rate*ones(size(inspace(en,:),1),1)];
566 end
567 outprob = [outprob; ones(size(space_buf(en,:),1),1)];
568 end
569 end
570 case SchedStrategy.INF % move first job in service
571 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
572 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(en,class,k); % assume active
573 % if state is unchanged, still add with rate 0
574 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
575 if isinf(ni) % hit limited load-dependence
576 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
577 else
578 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
579 end
580 outprob = [outprob; ones(size(rate(en,:),1),1)];
581 case {SchedStrategy.PS, SchedStrategy.LPS} % move first job in service
582 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
583 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*(kir(en,class,k)./ni(en)).*min(ni(en),S(ist)); % assume active
584 % if state is unchanged, still add with rate 0
585 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
586 if isinf(ni) % hit limited load-dependence
587 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
588 else
589 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
590 end
591 outprob = [outprob; ones(size(rate(en,:),1),1)];
592 % end
593 case SchedStrategy.PSPRIO
594 % unclear if LD scaling should be with
595 % ni or with niprio, for now left as ni
596 % for consistency with HOL multiserver
597 if all(ni(en) <= S(ist))
598 % n <= c: all jobs get service, priority doesn't matter
599 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
600 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*(kir(en,class,k)./ni(en)).*min(ni(en),S(ist)); % assume active
601 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
602 if isinf(ni) % hit limited load-dependence
603 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
604 else
605 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
606 end
607 outprob = [outprob; ones(size(rate(en,:),1),1)];
608 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)
609 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
610 niprio(en) = sum(nir(sn.classprio==sn.classprio(class))); % jobs at the same priority class
611 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*(kir(en,class,k)./niprio(en)).*min(niprio(en),S(ist)); % assume active
612 % if state is unchanged, still add with rate 0
613 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
614 if isinf(ni) % hit limited load-dependence
615 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
616 else
617 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(niprio(en),lldlimit)).*rate(en,:)];
618 end
619 outprob = [outprob; ones(size(rate(en,:),1),1)];
620 else % n > c and not highest priority: set rate to zero
621 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
622 outrate = [outrate; zeros(size(rate(en,:),1),1)];
623 outprob = [outprob; ones(size(rate(en,:),1),1)];
624 end
625 case SchedStrategy.DPS
626 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
627 if S(ist) > 1
628 line_error(mfilename,'Multi-server DPS stations are not supported yet.');
629 end
630 % in GPS, the scheduling parameter are the weights
631 w_i = sn.schedparam(ist,:);
632 w_i = w_i / sum(w_i);
633 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));
634 % if state is unchanged, still add with rate 0
635 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
636 if isinf(ni) % hit limited load-dependence
637 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
638 else
639 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
640 end
641 outprob = [outprob; ones(size(rate(en,:),1),1)];
642 case SchedStrategy.DPSPRIO
643 if all(ni(en) <= S(ist))
644 % n <= c: all jobs get service, behave like regular DPS
645 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
646 if S(ist) > 1
647 line_error(mfilename,'Multi-server DPS stations are not supported yet.');
648 end
649 w_i = sn.schedparam(ist,:);
650 w_i = w_i / sum(w_i);
651 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));
652 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
653 if isinf(ni) % hit limited load-dependence
654 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
655 else
656 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
657 end
658 outprob = [outprob; ones(size(rate(en,:),1),1)];
659 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)
660 nirprio = nir;
661 nirprio(sn.classprio~=sn.classprio(class)) = 0; % ignore jobs of lower priority
662 niprio = sum(nirprio);
663 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
664 if S(ist) > 1
665 line_error(mfilename,'Multi-server DPS stations are not supported yet.');
666 end
667 % in GPS, the scheduling parameter are the weights
668 w_i = sn.schedparam(ist,:);
669 w_i = w_i / sum(w_i);
670 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));
671 % if state is unchanged, still add with rate 0
672 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
673 if isinf(ni) % hit limited load-dependence
674 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nirprio,en,class).*lldscaling(ist,end).*rate(en,:)];
675 else
676 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nirprio,en,class).*lldscaling(ist,min(niprio(en),lldlimit)).*rate(en,:)];
677 end
678 outprob = [outprob; ones(size(rate(en,:),1),1)];
679 else % n > c and not most urgent priority: set rate to zero
680 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
681 outrate = [outrate; zeros(size(rate(en,:),1),1)];
682 outprob = [outprob; ones(size(rate(en,:),1),1)];
683 end
684 case SchedStrategy.GPS
685 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
686 if S(ist) > 1
687 line_error(mfilename,'Multi-server GPS stations are not supported yet.');
688 end
689 % in GPS, the scheduling parameter are the weights
690 w_i = sn.schedparam(ist,:);
691 w_i = w_i / sum(w_i);
692 cir = min(nir,ones(size(nir)));
693 rate = mu{ist}{class}(k)*(phi{ist}{class}(k))*(kir(en,class,k)/nir(class))*w_i(class)/(w_i*cir(:)); % assume active
694 % if state is unchanged, still add with rate 0
695 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
696 if isinf(ni) % hit limited load-dependence
697 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
698 else
699 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
700 end
701 outprob = [outprob; ones(size(rate(en,:),1),1)];
702 case SchedStrategy.GPSPRIO
703 if all(ni(en) <= S(ist))
704 % n <= c: all jobs get service, behave like regular GPS
705 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
706 if S(ist) > 1
707 line_error(mfilename,'Multi-server GPS stations are not supported yet.');
708 end
709 w_i = sn.schedparam(ist,:);
710 w_i = w_i / sum(w_i);
711 cir = min(nir,ones(size(nir)));
712 rate = mu{ist}{class}(k)*(phi{ist}{class}(k))*(kir(en,class,k)/nir(class))*w_i(class)/(w_i*cir(:)); % assume active
713 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
714 if isinf(ni) % hit limited load-dependence
715 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
716 else
717 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
718 end
719 outprob = [outprob; ones(size(rate(en,:),1),1)];
720 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)
721 nirprio = nir;
722 nirprio(sn.classprio~=sn.classprio(class)) = 0; % ignore jobs of lower priority
723 niprio = sum(nirprio);
724 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
725 if S(ist) > 1
726 line_error(mfilename,'Multi-server DPS stations are not supported yet.');
727 end
728 % in GPS, the scheduling parameter are the weights
729 w_i = sn.schedparam(ist,:);
730 w_i = w_i / sum(w_i);
731 cir = min(nirprio,ones(size(nirprio)));
732 rate = mu{ist}{class}(k)*(phi{ist}{class}(k))*(kir(en,class,k)/nirprio(class))*w_i(class)/(w_i*cir(:)); % assume active
733 % if state is unchanged, still add with rate 0
734 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
735 if isinf(ni) % hit limited load-dependence
736 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nirprio,en,class).*lldscaling(ist,end).*rate(en,:)];
737 else
738 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nirprio,en,class).*lldscaling(ist,min(niprio(en),lldlimit)).*rate(en,:)];
739 end
740 outprob = [outprob; ones(size(rate(en,:),1),1)];
741 else % n > c and not most urgent priority: set rate to zero
742 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
743 outrate = [outrate; zeros(size(rate(en,:),1),1)];
744 outprob = [outprob; ones(size(rate(en,:),1),1)];
745 end
746 case SchedStrategy.FCFS % move first job in service
747 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
748 en_wbuf = en & ni>S(ist); %states with jobs in buffer
749 if noPromote || isRetrialStation % immediate feedback / retrial orbit: hold server, do not promote a waiting job
750 en_wbuf(:) = false;
751 end
752 for kdest=1:K(class) % new phase
753 space_buf_kd = space_buf;
754 space_var_kd = space_var;
755 if ismkvmodclass(class)
756 space_var_kd(en,sum(sn.nvars(ind,1:class))) = kdest;
757 end
758 rate_kd = rate;
759 rate_kd(en) = proc{ist}{class}{2}(k,kdest).*kir(en,class,k); % assume active
760 % first process states without jobs in buffer
761 en_wobuf = ~en_wbuf;
762 if any(en_wobuf) %any state without jobs in buffer
763 outspace = [outspace; space_buf_kd(en_wobuf,:), space_srv(en_wobuf,:), space_var_kd(en_wobuf,:)];
764 if isinf(ni) % hit limited load-dependence
765 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,end).*rate_kd(en_wobuf,:)];
766 else
767 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_kd(en_wobuf,:)];
768 end
769 end
770 % now process states with jobs in buffer
771 outprob = [outprob; ones(size(rate_kd(en_wobuf,:),1),1)];
772 if any(en_wbuf) %any state with jobs in buffer
773 % get class of job at head
774 start_svc_class = space_buf_kd(en_wbuf,end);
775 if start_svc_class > 0 % redunant if?
776 % update input buffer
777 space_buf_kd(en_wbuf,:) = [zeros(sum(en_wbuf),1),space_buf_kd(en_wbuf,1:end-1)];
778 % probability vector for the next job of starting in phase kentry
779 if ismkvmodclass(start_svc_class) % if markov-modulated
780 if start_svc_class==class % if successive service from the same class
781 kentry_range = kdest; % new job enters in phase left by departing job
782 else % resume phase from local variables
783 kentry_range = space_var_kd(en,sum(sn.nvars(ind,1:start_svc_class)));
784 end
785 pentry_svc_class = 0*pie{ist}{start_svc_class};
786 pentry_svc_class(kentry_range) = 1.0;
787 else % if i.i.d.
788 pentry_svc_class = pie{ist}{start_svc_class};
789 kentry_range = 1:K(start_svc_class);
790 end
791 for kentry = kentry_range
792 space_srv(en_wbuf,Ks(start_svc_class)+kentry) = space_srv(en_wbuf,Ks(start_svc_class)+kentry) + 1;
793 outspace = [outspace; space_buf_kd(en,:), space_srv(en,:), space_var_kd(en,:)];
794 rate_k = rate_kd;
795 rate_k(en_wbuf,:) = rate_kd(en_wbuf,:)*pentry_svc_class(kentry);
796 if isinf(ni) % use limited load-dependence at the latest user-provided level
797 newrate = State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate_k(en,:);
798 else
799 newrate = State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en,:);
800 end
801 outrate = [outrate; newrate];
802 % Zero the probability of the branches added *by this kentry* when
803 % their rate is zero. The mask must index newrate, not the whole
804 % accumulated outrate: outrate grows by one entry per kentry while
805 % outprob_cur has one entry per enabled state, so masking on outrate
806 % silently GREW outprob_cur (MATLAB expands on out-of-bound
807 % assignment) once any earlier branch had rate 0 -- which happens
808 % whenever pentry_svc_class has zeros, i.e. a PH whose entry vector
809 % pie does not reach every phase. That misaligned outprob against
810 % outspace/outrate, so the sampled branch read a bogus (often 0)
811 % probability and depRatesSamples under-counted departures.
812 outprob_cur = ones(size(rate_kd(en,:),1),1);
813 outprob_cur(newrate==0.0) = 0;
814 outprob = [outprob; outprob_cur(:)];
815 space_srv(en_wbuf,Ks(start_svc_class)+kentry) = space_srv(en_wbuf,Ks(start_svc_class)+kentry) - 1;
816 end
817 end
818 end
819 end
820 % if state is unchanged, still add with rate 0
821 case SchedStrategy.HOL % FCFS priority
822 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % assume active
823 en_wbuf = en & ni>S(ist); %states with jobs in buffer
824 if noPromote || isRetrialStation % immediate feedback / retrial orbit: hold server, do not promote a waiting job
825 en_wbuf(:) = false;
826 end
827 en_wobuf = ~en_wbuf;
828 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
829 priogroup = [Inf,sn.classprio]; % Inf for empty positions (lower value = higher priority)
830 space_buf_groupg = arrayfun(@(x) priogroup(1+x), space_buf);
831 start_classprio = min(space_buf_groupg(en_wbuf,:),[],2); % min finds highest priority
832 isrowmax = space_buf_groupg == repmat(start_classprio, 1, size(space_buf_groupg,2));
833 [~,rightmostMaxPosFlipped]=max(fliplr(isrowmax),[],2);
834 rightmostMaxPos = size(isrowmax,2) - rightmostMaxPosFlipped + 1;
835 start_svc_class = space_buf(en_wbuf, rightmostMaxPos);
836 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
837 if isinf(ni) % hit limited load-dependence
838 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,end).*rate(en_wobuf,:)];
839 else
840 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
841 end
842 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
843 if start_svc_class > 0
844 pentry_svc_class = pie{ist}{start_svc_class};
845 for kentry = 1:K(start_svc_class)
846 space_srv_k = space_srv;
847 space_buf_k = space_buf;
848 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
849 for j=find(en_wbuf)'
850 space_buf_k(j,:) = [0, space_buf_k(j,1:rightmostMaxPos(j)-1), space_buf_k(j,(rightmostMaxPos(j)+1):end)];
851 end
852 % if state is unchanged, still add with rate 0
853 outspace = [outspace; space_buf_k(en_wbuf,:), space_srv_k(en_wbuf,:), space_var(en_wbuf,:)];
854 rate_k = rate;
855 rate_k(en_wbuf,:) = rate(en_wbuf,:) * pentry_svc_class(kentry);
856 if isinf(ni) % hit limited load-dependence
857 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,end).*rate_k(en_wbuf,:)];
858 else
859 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en_wbuf,:)];
860 end
861 outprob = [outprob; ones(size(rate_k(en_wbuf,:),1),1)];
862 end
863 end
864 case SchedStrategy.LCFSPRIO % LCFS priority - like HOL but LCFS order within priority groups
865 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % assume active
866 en_wbuf = en & ni>S(ist); %states with jobs in buffer
867 if noPromote || isRetrialStation % immediate feedback / retrial orbit: hold server, do not promote a waiting job
868 en_wbuf(:) = false;
869 end
870 en_wobuf = ~en_wbuf;
871 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
872 priogroup = [Inf,sn.classprio]; % Inf for empty positions (lower value = higher priority)
873 space_buf_groupg = arrayfun(@(x) priogroup(1+x), space_buf);
874 start_classprio = min(space_buf_groupg(en_wbuf,:),[],2); % min finds highest priority
875 isrowmax = space_buf_groupg == repmat(start_classprio, 1, size(space_buf_groupg,2));
876 % LCFS: Find leftmost (first) position instead of rightmost for LCFS order
877 [~,leftmostMaxPos]=max(isrowmax,[],2);
878 start_svc_class = space_buf(en_wbuf, leftmostMaxPos);
879 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
880 if isinf(ni) % hit limited load-dependence
881 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,end).*rate(en_wobuf,:)];
882 else
883 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
884 end
885 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
886 if start_svc_class > 0
887 pentry_svc_class = pie{ist}{start_svc_class};
888 for kentry = 1:K(start_svc_class)
889 space_srv_k = space_srv;
890 space_buf_k = space_buf;
891 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
892 for j=find(en_wbuf)'
893 % LCFS: Remove from leftmost position instead of rightmost
894 space_buf_k(j,:) = [0, space_buf_k(j,1:leftmostMaxPos(j)-1), space_buf_k(j,(leftmostMaxPos(j)+1):end)];
895 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,:)];
898 rate_k = rate;
899 rate_k(en_wbuf,:) = rate_k(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,:)];
902 else
903 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en_wbuf,:)];
904 end
905 outprob = [outprob; ones(size(rate_k(en_wbuf,:),1),1)];
906 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) - 1;
907 end
908 end
909 case SchedStrategy.LCFS % move last job in service
910 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
911 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % assume active
912 en_wbuf = en & ni>S(ist); %states with jobs in buffer
913 if noPromote || isRetrialStation % immediate feedback / retrial orbit: hold server, do not promote a waiting job
914 en_wbuf(:) = false;
915 end
916 % Plain LCFS is not priority-aware: it always promotes the most
917 % recent arrival. Priorities are honored by SchedStrategy.LCFSPRIO,
918 % which has its own case below ("LCFS order within priority
919 % groups"), exactly as FCFS relates to FCFSPRIO. Branching here on
920 % ~all(classprio == classprio(1)) silently turned every LCFS
921 % station with distinct class priorities into an LCFSPRIO one,
922 % contradicting the "Priorities will be ignored" warning raised by
923 % refreshStruct and, for three or more classes, yielding an
924 % all-zero queue length.
925 [~, colfirstnnz] = max( space_buf(en_wbuf,:) ~=0, [], 2 ); % find first nnz column
926 start_svc_class = space_buf(en_wbuf,colfirstnnz); % job entering service
927 space_buf(en_wbuf,colfirstnnz)=0;
928 if isempty(start_svc_class)
929 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
930 if isinf(ni) % hit limited load-dependence
931 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
932 else
933 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
934 end
935 outprob = [outprob; ones(size(rate(en,:),1),1)];
936 if isSimulation && nargin>=7 && isobject(eventCache)
937 eventCache(key) = {outprob, outspace,outrate};
938 end
939 return
940 end
941 for kentry = 1:K(start_svc_class)
942 pentry_svc_class = pie{ist}{start_svc_class};
943 space_srv(en_wbuf,Ks(start_svc_class)+kentry) = space_srv(en_wbuf,Ks(start_svc_class)+kentry) + 1;
944 % if state is unchanged, still add with rate 0
945 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
946 rate_k = rate;
947 rate_k(en_wbuf,:) = rate(en_wbuf,:)*pentry_svc_class(kentry);
948 if isinf(ni) % hit limited load-dependence
949 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate_k(en,:)];
950 else
951 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en,:)];
952 end
953 outprob = [outprob; ones(size(rate(en,:),1),1)];
954 space_srv(en_wbuf,Ks(start_svc_class)+kentry) = space_srv(en_wbuf,Ks(start_svc_class)+kentry) - 1;
955 end
956 case SchedStrategy.LCFSPR % move last job in service (preempt-resume)
957 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
958 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % assume active
959 en_wbuf = en & ni>S(ist); %states with jobs in buffer
960 % Plain LCFSPR resumes the most recently preempted job. It is not
961 % priority-aware: LCFSPRPRIO is, and it carries its own handling.
962 % Selecting by priority whenever the class priorities happened to
963 % differ made this case answer for a policy the user never declared
964 % (see the arrival path above for the same rule).
965 [~, colfirstnnz] = max( space_buf(en_wbuf,:) ~=0, [], 2 ); % find first nnz column
966 start_svc_class = space_buf(en_wbuf,colfirstnnz); % job entering service
967 kentry = space_buf(en_wbuf,colfirstnnz+1); % entry phase of job resuming service
968 space_buf(en_wbuf,colfirstnnz)=0;% zero popped job
969 space_buf(en_wbuf,colfirstnnz+1)=0; % zero popped phase
970 if isempty(start_svc_class)
971 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
972 if isinf(ni) % hit limited load-dependence
973 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
974 else
975 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
976 end
977 outprob = [outprob; ones(size(rate(en,:),1),1)];
978 if isSimulation && nargin>=7 && isobject(eventCache)
979 eventCache(key) = {outprob, outspace,outrate};
980 end
981 return
982 end
983 space_srv(en_wbuf,Ks(start_svc_class)+kentry) = space_srv(en_wbuf,Ks(start_svc_class)+kentry) + 1;
984 % if state is unchanged, still add with rate 0
985 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
986 if isinf(ni) % hit limited load-dependence
987 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
988 else
989 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
990 end
991 outprob = [outprob; ones(size(rate(en,:),1),1)];
992 case SchedStrategy.LCFSPI % move last job in service (preempt-independent)
993 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
994 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % assume active
995 en_wbuf = en & ni>S(ist); %states with jobs in buffer
996 [~, colfirstnnz] = max( space_buf(en_wbuf,:) ~=0, [], 2 ); % find first nnz column
997 start_svc_class = space_buf(en_wbuf,colfirstnnz); % job entering service
998 space_buf(en_wbuf,colfirstnnz)=0;% zero popped job
999 space_buf(en_wbuf,colfirstnnz+1)=0; % zero popped phase (ignored for LCFSPI)
1000 if isempty(start_svc_class)
1001 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1002 if isinf(ni) % hit limited load-dependence
1003 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
1004 else
1005 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
1006 end
1007 outprob = [outprob; ones(size(rate(en,:),1),1)];
1008 if isSimulation && nargin>=7 && isobject(eventCache)
1009 eventCache(key) = {outprob, outspace,outrate};
1010 end
1011 return
1012 end
1013 % For LCFSPI, jobs restart from pie distribution instead of stored phase
1014 pentry_svc_class = pie{ist}{start_svc_class};
1015 for kentry = 1:K(start_svc_class)
1016 space_srv_k = space_srv;
1017 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
1018 % if state is unchanged, still add with rate 0
1019 outspace = [outspace; space_buf(en,:), space_srv_k(en,:), space_var(en,:)];
1020 rate_k = rate;
1021 rate_k(en_wbuf,:) = rate_k(en_wbuf,:) * pentry_svc_class(kentry);
1022 if isinf(ni) % hit limited load-dependence
1023 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate_k(en,:)];
1024 else
1025 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en,:)];
1026 end
1027 outprob = [outprob; ones(size(rate_k(en,:),1),1)];
1028 end
1029 case SchedStrategy.FCFSPR % FCFS preempt-resume (no priority)
1030 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
1031 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % assume active
1032 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1033 % FCFS: Find rightmost (last) non-zero position (longest waiting job)
1034 [~, colLastNnz] = max(fliplr(space_buf(en_wbuf,:) ~= 0), [], 2);
1035 colLastNnz = size(space_buf,2) - colLastNnz; % convert to actual position (odd index for class)
1036 start_svc_class = space_buf(en_wbuf, colLastNnz); % job entering service
1037 kentry = space_buf(en_wbuf, colLastNnz+1); % entry phase of job resuming service
1038 space_buf(en_wbuf, colLastNnz) = 0; % zero popped job
1039 space_buf(en_wbuf, colLastNnz+1) = 0; % zero popped phase
1040 if isempty(start_svc_class)
1041 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1042 if isinf(ni)
1043 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
1044 else
1045 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
1046 end
1047 outprob = [outprob; ones(size(rate(en,:),1),1)];
1048 if isSimulation && nargin>=7 && isobject(eventCache)
1049 eventCache(key) = {outprob, outspace, outrate};
1050 end
1051 return
1052 end
1053 space_srv(en_wbuf, Ks(start_svc_class)+kentry) = space_srv(en_wbuf, Ks(start_svc_class)+kentry) + 1;
1054 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1055 if isinf(ni)
1056 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
1057 else
1058 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
1059 end
1060 outprob = [outprob; ones(size(rate(en,:),1),1)];
1061 case SchedStrategy.FCFSPI % FCFS preempt-independent (no priority)
1062 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
1063 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % assume active
1064 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1065 % FCFS: Find rightmost (last) non-zero position (longest waiting job)
1066 [~, colLastNnz] = max(fliplr(space_buf(en_wbuf,:) ~= 0), [], 2);
1067 colLastNnz = size(space_buf,2) - colLastNnz; % convert to actual position (odd index for class)
1068 start_svc_class = space_buf(en_wbuf, colLastNnz); % job entering service
1069 space_buf(en_wbuf, colLastNnz) = 0; % zero popped job
1070 space_buf(en_wbuf, colLastNnz+1) = 0; % zero popped phase (ignored for FCFSPI)
1071 if isempty(start_svc_class)
1072 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1073 if isinf(ni)
1074 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate(en,:)];
1075 else
1076 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en,:)];
1077 end
1078 outprob = [outprob; ones(size(rate(en,:),1),1)];
1079 if isSimulation && nargin>=7 && isobject(eventCache)
1080 eventCache(key) = {outprob, outspace, outrate};
1081 end
1082 return
1083 end
1084 % For FCFSPI, jobs restart from pie distribution instead of stored phase
1085 pentry_svc_class = pie{ist}{start_svc_class};
1086 for kentry = 1:K(start_svc_class)
1087 space_srv_k = space_srv;
1088 space_srv_k(en_wbuf, Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf, Ks(start_svc_class)+kentry) + 1;
1089 outspace = [outspace; space_buf(en,:), space_srv_k(en,:), space_var(en,:)];
1090 rate_k = rate;
1091 rate_k(en_wbuf,:) = rate_k(en_wbuf,:) * pentry_svc_class(kentry);
1092 if isinf(ni)
1093 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate_k(en,:)];
1094 else
1095 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en,:)];
1096 end
1097 outprob = [outprob; ones(size(rate_k(en,:),1),1)];
1098 end
1099 case SchedStrategy.FCFSPRPRIO % FCFS preempt-resume with priority groups
1100 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
1101 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % assume active
1102 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1103 en_wobuf = ~en_wbuf;
1104 priogroup = [Inf,sn.classprio]; % Inf for empty positions (lower value = higher priority)
1105 % Buffer stores [class,phase,class,phase,...] pairs;
1106 % only inspect class columns (odd: 1,3,5,...) for priority
1107 class_cols = 1:2:size(space_buf,2);
1108 space_buf_class = space_buf(:, class_cols);
1109 space_buf_class_groupg = arrayfun(@(x) priogroup(1+x), space_buf_class);
1110 start_classprio = min(space_buf_class_groupg(en_wbuf,:),[],2); % min finds highest priority
1111 isrowmax = space_buf_class_groupg == repmat(start_classprio, 1, size(space_buf_class_groupg,2));
1112 % FCFS: Find rightmost (last) class position for highest priority class (longest waiting)
1113 [~,rightmostClassPos]=max(fliplr(isrowmax),[],2);
1114 rightmostClassPos = size(space_buf_class_groupg,2) - rightmostClassPos + 1;
1115 rightmostMaxPos = 2*rightmostClassPos - 1; % convert to buffer column index
1116 start_svc_class = space_buf(en_wbuf, rightmostMaxPos); % job entering service
1117 kentry = space_buf(en_wbuf, rightmostMaxPos+1); % entry phase of job resuming service (preempt-resume)
1118
1119 % Handle states without buffer jobs
1120 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
1121 if isinf(ni)
1122 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,end).*rate(en_wobuf,:)];
1123 else
1124 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
1125 end
1126 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
1127
1128 % Handle states with buffer jobs
1129 if any(en_wbuf) && start_svc_class > 0
1130 space_srv_k = space_srv;
1131 space_buf_k = space_buf;
1132 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
1133 for j=find(en_wbuf)'
1134 % Remove both class and phase from rightmost position (preempt-resume)
1135 space_buf_k(j,:) = [0, space_buf_k(j,1:rightmostMaxPos(j)-1), space_buf_k(j,(rightmostMaxPos(j)+2):end), 0];
1136 end
1137 outspace = [outspace; space_buf_k(en_wbuf,:), space_srv_k(en_wbuf,:), space_var(en_wbuf,:)];
1138 if isinf(ni)
1139 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,end).*rate(en_wbuf,:)];
1140 else
1141 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wbuf,:)];
1142 end
1143 outprob = [outprob; ones(size(rate(en_wbuf,:),1),1)];
1144 end
1145 case SchedStrategy.LCFSPRPRIO % LCFS preempt-resume with priority groups
1146 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
1147 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % assume active
1148 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1149 en_wobuf = ~en_wbuf;
1150 priogroup = [Inf,sn.classprio]; % Inf for empty positions (lower value = higher priority)
1151 % Buffer stores [class,phase,class,phase,...] pairs;
1152 % only inspect class columns (odd: 1,3,5,...) for priority
1153 class_cols = 1:2:size(space_buf,2);
1154 space_buf_class = space_buf(:, class_cols);
1155 space_buf_class_groupg = arrayfun(@(x) priogroup(1+x), space_buf_class);
1156 start_classprio = min(space_buf_class_groupg(en_wbuf,:),[],2); % min finds highest priority
1157 isrowmax = space_buf_class_groupg == repmat(start_classprio, 1, size(space_buf_class_groupg,2));
1158 % LCFS: Find leftmost (first) class position for highest priority class (most recently preempted)
1159 [~,leftmostClassPos]=max(isrowmax,[],2);
1160 leftmostMaxPos = 2*leftmostClassPos - 1; % convert to buffer column index
1161 start_svc_class = space_buf(en_wbuf, leftmostMaxPos); % job entering service
1162 kentry = space_buf(en_wbuf, leftmostMaxPos+1); % entry phase of job resuming service (preempt-resume)
1163
1164 % Handle states without buffer jobs
1165 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
1166 if isinf(ni)
1167 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,end).*rate(en_wobuf,:)];
1168 else
1169 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
1170 end
1171 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
1172
1173 % Handle states with buffer jobs
1174 if any(en_wbuf) && start_svc_class > 0
1175 space_srv_k = space_srv;
1176 space_buf_k = space_buf;
1177 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
1178 for j=find(en_wbuf)'
1179 % Remove both class and phase from leftmost position (preempt-resume)
1180 % The (class,phase) pair buffer is RIGHT-aligned: occupied pairs sit at the
1181 % right, empty pairs pad the left. Priority can promote a pair from the
1182 % middle, so closing the hole must pad a whole empty PAIR on the left --
1183 % as State.afterEventStationSignal's dropWaiting does. Padding one slot
1184 % left and one right instead left a trailing empty slot, e.g.
1185 % [2 1 3 1] -> [0 3 1 0], a layout fromMarginal never enumerates: the
1186 % successor was unreachable, which is what made the generator reducible.
1187 space_buf_k(j,:) = [0, 0, space_buf_k(j,1:leftmostMaxPos(j)-1), space_buf_k(j,(leftmostMaxPos(j)+2):end)];
1188 end
1189 outspace = [outspace; space_buf_k(en_wbuf,:), space_srv_k(en_wbuf,:), space_var(en_wbuf,:)];
1190 if isinf(ni)
1191 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,end).*rate(en_wbuf,:)];
1192 else
1193 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wbuf,:)];
1194 end
1195 outprob = [outprob; ones(size(rate(en_wbuf,:),1),1)];
1196 end
1197 case SchedStrategy.FCFSPIPRIO % FCFS preempt-independent with priority groups
1198 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
1199 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % assume active
1200 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1201 en_wobuf = ~en_wbuf;
1202 priogroup = [Inf,sn.classprio]; % Inf for empty positions (lower value = higher priority)
1203 space_buf_groupg = arrayfun(@(x) priogroup(1+x), space_buf);
1204 start_classprio = min(space_buf_groupg(en_wbuf,:),[],2); % min finds highest priority
1205 isrowmax = space_buf_groupg == repmat(start_classprio, 1, size(space_buf_groupg,2));
1206 % FCFS: Find rightmost (last) position for highest priority class
1207 [~,rightmostMaxPos]=max(fliplr(isrowmax),[],2);
1208 rightmostMaxPos = size(space_buf_groupg,2) - rightmostMaxPos + 1;
1209 start_svc_class = space_buf(en_wbuf, rightmostMaxPos); % job entering service
1210
1211 % Handle states without buffer jobs
1212 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
1213 if isinf(ni)
1214 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,end).*rate(en_wobuf,:)];
1215 else
1216 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
1217 end
1218 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
1219
1220 % Handle states with buffer jobs
1221 if any(en_wbuf) && start_svc_class > 0
1222 % For FCFSPIPRIO, jobs restart from pie distribution instead of stored phase
1223 pentry_svc_class = pie{ist}{start_svc_class};
1224 for kentry = 1:K(start_svc_class)
1225 space_srv_k = space_srv;
1226 space_buf_k = space_buf;
1227 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
1228 for j=find(en_wbuf)'
1229 % Remove both class and phase from rightmost position (preempt-independent ignores stored phase)
1230 space_buf_k(j,:) = [0, space_buf_k(j,1:rightmostMaxPos(j)-1), space_buf_k(j,(rightmostMaxPos(j)+2):end), 0];
1231 end
1232 outspace = [outspace; space_buf_k(en_wbuf,:), space_srv_k(en_wbuf,:), space_var(en_wbuf,:)];
1233 rate_k = rate;
1234 rate_k(en_wbuf,:) = rate_k(en_wbuf,:) * pentry_svc_class(kentry);
1235 if isinf(ni)
1236 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,end).*rate_k(en_wbuf,:)];
1237 else
1238 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en_wbuf,:)];
1239 end
1240 outprob = [outprob; ones(size(rate_k(en_wbuf,:),1),1)];
1241 end
1242 end
1243 case SchedStrategy.LCFSPIPRIO % LCFS preempt-independent with priority groups
1244 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
1245 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % assume active
1246 en_wbuf = en & ni>S(ist); %states with jobs in buffer
1247 en_wobuf = ~en_wbuf;
1248 priogroup = [Inf,sn.classprio]; % Inf for empty positions (lower value = higher priority)
1249 space_buf_groupg = arrayfun(@(x) priogroup(1+x), space_buf);
1250 start_classprio = min(space_buf_groupg(en_wbuf,:),[],2); % min finds highest priority
1251 isrowmax = space_buf_groupg == repmat(start_classprio, 1, size(space_buf_groupg,2));
1252 % LCFS: Find leftmost (first) position for highest priority class
1253 [~,leftmostMaxPos]=max(isrowmax,[],2);
1254 start_svc_class = space_buf(en_wbuf, leftmostMaxPos); % job entering service
1255
1256 % Handle states without buffer jobs
1257 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
1258 if isinf(ni)
1259 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,end).*rate(en_wobuf,:)];
1260 else
1261 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
1262 end
1263 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
1264
1265 % Handle states with buffer jobs
1266 if any(en_wbuf) && start_svc_class > 0
1267 % For LCFSPIPRIO, jobs restart from pie distribution instead of stored phase
1268 pentry_svc_class = pie{ist}{start_svc_class};
1269 for kentry = 1:K(start_svc_class)
1270 space_srv_k = space_srv;
1271 space_buf_k = space_buf;
1272 space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) = space_srv_k(en_wbuf,Ks(start_svc_class)+kentry) + 1;
1273 for j=find(en_wbuf)'
1274 % Remove both class and phase from leftmost position (preempt-independent ignores stored phase)
1275 % The (class,phase) pair buffer is RIGHT-aligned: occupied pairs sit at the
1276 % right, empty pairs pad the left. Priority can promote a pair from the
1277 % middle, so closing the hole must pad a whole empty PAIR on the left --
1278 % as State.afterEventStationSignal's dropWaiting does. Padding one slot
1279 % left and one right instead left a trailing empty slot, e.g.
1280 % [2 1 3 1] -> [0 3 1 0], a layout fromMarginal never enumerates: the
1281 % successor was unreachable, which is what made the generator reducible.
1282 space_buf_k(j,:) = [0, 0, space_buf_k(j,1:leftmostMaxPos(j)-1), space_buf_k(j,(leftmostMaxPos(j)+2):end)];
1283 end
1284 outspace = [outspace; space_buf_k(en_wbuf,:), space_srv_k(en_wbuf,:), space_var(en_wbuf,:)];
1285 rate_k = rate;
1286 rate_k(en_wbuf,:) = rate_k(en_wbuf,:) * pentry_svc_class(kentry);
1287 if isinf(ni)
1288 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,end).*rate_k(en_wbuf,:)];
1289 else
1290 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en_wbuf,:)];
1291 end
1292 outprob = [outprob; ones(size(rate_k(en_wbuf,:),1),1)];
1293 end
1294 end
1295 case SchedStrategy.SIRO
1296 rate = zeros(size(space_srv,1),1);
1297 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % this is for states not in en_buf
1298 space_srv = inspace(:,(end-sum(K)-V+1):(end-V)); % server state (clear of the local vars)
1299 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
1300 % first record departure in states where the buffer is empty
1301 en_wobuf = en & sum(space_buf(en,:),2) == 0;
1302 if noPromote % immediate feedback: hold server, do not promote a waiting job
1303 en_wobuf = en;
1304 end
1305 outspace = [outspace; space_buf(en_wobuf,:), space_srv(en_wobuf,:), space_var(en_wobuf,:)];
1306 if isinf(ni) % hit limited load-dependence
1307 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,end).*rate(en_wobuf,:)];
1308 else
1309 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wobuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate(en_wobuf,:)];
1310 end
1311 outprob = [outprob; ones(size(rate(en_wobuf,:),1),1)];
1312 % let's go now to states where the buffer is non-empty
1313 % (promotion suppressed under immediate feedback: R*(~noPromote)=0)
1314 for r=1:(R*(~noPromote))
1315 rate_r = rate;
1316 space_buf = inspace(:,1:(end-sum(K)-V)); % buffer state (clear of the local vars)
1317 en_wbuf = en & space_buf(en,r) > 0; % states where the buffer is non-empty
1318 space_buf(en_wbuf,r) = space_buf(en_wbuf,r) - 1; % remove from buffer
1319 space_srv_r = space_srv;
1320 pentry_svc_class = pie{ist}{r};
1321 pick_prob = (nir(r)-sir(r)) / (ni-sum(sir));
1322 if pick_prob >= 0
1323 rate_r(en_wbuf,:) = rate_r(en_wbuf,:) * pick_prob;
1324 end
1325 for kentry=1:K(r)
1326 space_srv_r(en_wbuf,Ks(r)+kentry) = space_srv_r(en_wbuf,Ks(r)+kentry) + 1; % bring job in service
1327 outspace = [outspace; space_buf(en_wbuf,:), space_srv_r(en_wbuf,:), space_var(en_wbuf,:)];
1328 rate_k = rate_r;
1329 rate_k(en_wbuf,:) = rate_k(en_wbuf,:) * pentry_svc_class(kentry);
1330 if isinf(ni) % hit limited load-dependence
1331 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,end).*rate_k(en_wbuf,:)];
1332 else
1333 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en_wbuf,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en_wbuf,:)];
1334 end
1335 outprob = [outprob; ones(size(rate(en_wbuf,:),1),1)];
1336 space_srv_r(en_wbuf,Ks(r)+kentry) = space_srv_r(en_wbuf,Ks(r)+kentry) - 1; % bring job in service
1337 end
1338 end
1339 case SchedStrategy.POLLING
1340 % A completion ends the visit unless the
1341 % discipline still allows another job of the
1342 % same class to be taken; when it ends, the
1343 % server walks the cyclic order to wherever the
1344 % next tangible controller state lies. Which of
1345 % the two happens depends on the buffer of each
1346 % state row, so the rows are resolved one by one
1347 % rather than as a single vectorized promotion.
1348 % space_buf/space_srv are re-sliced from inspace by the
1349 % enclosing k-loop; space_var deliberately is NOT, since
1350 % it already carries the RROBIN/WRROBIN pointer advance
1351 % applied above and re-slicing would discard it.
1352 pinfoD = State.pollingInfo(sn, ind);
1353 for rowD = find(en(:))'
1354 rateD = mu{ist}{class}(k)*phi{ist}{class}(k)*kir(rowD,class,k);
1355 if rateD <= 0
1356 continue
1357 end
1358 [~, swkD, ctrD] = State.pollingGet(pinfoD, space_var(rowD,:), class);
1359 if swkD ~= 0
1360 continue % no job can complete while the server is walking
1361 end
1362 bufD = space_buf(rowD,:);
1363 nbufD = bufD(1,1:R);
1364 srvD = space_srv(rowD,:);
1365 srvD(1,Ks(class)+k) = srvD(1,Ks(class)+k) - 1; % record departure
1366 switch pinfoD.ptype
1367 case PollingType.EXHAUSTIVE
1368 ctrnextD = 0;
1369 goonD = nbufD(class) > 0;
1370 case PollingType.GATED
1371 ctrnextD = ctrD - 1; % one of the gated jobs completed
1372 goonD = ctrnextD > 0;
1373 case PollingType.KLIMITED
1374 ctrnextD = ctrD - 1; % one of the K permitted services used
1375 goonD = ctrnextD > 0 && nbufD(class) > 0;
1376 case PollingType.DECREMENTING
1377 ctrnextD = ctrD; % the target level is fixed for the visit
1378 goonD = nbufD(class) > ctrD;
1379 end
1380 if goonD
1381 qD = class; modeD = 1; budgetD = ctrnextD;
1382 else
1383 [qD, modeD, budgetD] = State.pollingNext(pinfoD, class, nbufD, R, false);
1384 end
1385 [rowsD, probsD] = State.pollingLand(pinfoD, qD, modeD, budgetD, ...
1386 bufD, srvD, space_var(rowD,:), K, Ks, pie{ist}, R);
1387 for jD = 1:size(rowsD,1)
1388 outspace = [outspace; rowsD(jD,:)];
1389 if isinf(ni) % hit limited load-dependence
1390 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,rowD,class).*lldscaling(ist,end).*rateD.*probsD(jD)];
1391 else
1392 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,rowD,class).*lldscaling(ist,min(ni(rowD),lldlimit)).*rateD.*probsD(jD)];
1393 end
1394 outprob = [outprob; 1];
1395 end
1396 end
1397 case {SchedStrategy.SEPT,SchedStrategy.LEPT} % move last job in service
1398 rate = zeros(size(space_srv,1),1);
1399 rate(en) = mu{ist}{class}(k)*(phi{ist}{class}(k)).*kir(:,class,k); % this is for states not in en_buf
1400 space_srv = inspace(:,(end-sum(K)-V+1):(end-V)); % server state (clear of the local vars)
1401 space_srv(en,Ks(class)+k) = space_srv(en,Ks(class)+k) - 1; % record departure
1402 space_buf = inspace(:,1:(end-sum(K)-V)); % buffer state (clear of the local vars)
1403 % in SEPT, the scheduling parameter is the priority order of the class means
1404 % en_wbuf: states where the buffer is non-empty
1405 % sept_class: class to pick in service
1406 % sn.schedparam(ist,r) holds the RANK of class r's mean service
1407 % time (ascending for SEPT, descending for LEPT, see
1408 % MNetwork.refreshScheduling). Scanning the buffer columns in
1409 % schedparam order visits the classes as P(1),P(2),..., which
1410 % coincides with rank order only when P is an involution -- true
1411 % of every 2-class model, but false in general, in which case the
1412 % wrong class is promoted. The discipline needs the inverse map
1413 % rank -> class, so invert the permutation first.
1414 [~, classByRank] = sort(sn.schedparam(ist,1:R));
1415 [en_wbuf, first_class_inrow] = max(space_buf(:,classByRank)~=0, [], 2);
1416 sept_class = classByRank(first_class_inrow); % this is different for sept and lept
1417 if noPromote % immediate feedback: hold server, do not promote a waiting job
1418 en_wbuf(:) = false;
1419 end
1420
1421 space_buf(en_wbuf,sept_class) = space_buf(en_wbuf,sept_class) - 1; % remove from buffer
1422 pentry = pie{ist}{sept_class};
1423 for kentry=1:K(sept_class)
1424 space_srv(en_wbuf,Ks(sept_class)+kentry) = space_srv(en_wbuf,Ks(sept_class)+kentry) + 1; % bring job in service
1425 if isSimulation
1426 % break the tie
1427 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1428 rate_k = rate;
1429 rate_k(en,:) = rate_k(en,:) * pentry(kentry);
1430 if isinf(ni) % hit limited load-dependence
1431 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate_k(en,:)];
1432 else
1433 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en,:)];
1434 end
1435 outprob = [outprob; ones(size(rate(en,:),1),1)];
1436 else
1437 outspace = [outspace; space_buf(en,:), space_srv(en,:), space_var(en,:)];
1438 rate_k = rate;
1439 rate_k(en,:) = rate_k(en,:) * pentry(kentry);
1440 if isinf(ni) % hit limited load-dependence
1441 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,end).*rate_k(en,:)];
1442 else
1443 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,en,class).*lldscaling(ist,min(ni(en),lldlimit)).*rate_k(en,:)];
1444 end
1445 outprob = [outprob; ones(size(rate(en,:),1),1)];
1446 end
1447 space_srv(en_wbuf,Ks(sept_class)+kentry) = space_srv(en_wbuf,Ks(sept_class)+kentry) - 1; % bring job in service
1448 end
1449 otherwise
1450 line_error(mfilename,sprintf('Scheduling strategy %s is not supported.', SchedStrategy.toText(sn.sched(ist))));
1451 end
1452 end
1453 end
1454 if isSimulation
1455 if nargin>=7 && isobject(eventCache)
1456 eventCache(key) = {outprob, outspace,outrate};
1457 end
1458
1459 if size(outspace,1) > 1
1460 tot_rate = sum(outrate);
1461 cum_rate = cumsum(outrate) / tot_rate;
1462 firing_ctr = 1 + max([0,find( rand > cum_rate' )]); % select action
1463 outspace = outspace(firing_ctr,:);
1464 outrate = sum(outrate);
1465 outprob = outprob(firing_ctr,:);
1466 end
1467 end
1468 end
1469 end
1470 case EventType.PHASE
1471 outspace = [];
1472 outrate = [];
1473 outprob = [];
1474 [ni,nir,~,kir] = State.toMarginal(sn,ind,inspace,K,Ks,space_buf,space_srv,space_var);
1475 if nir(class)>0
1476 for k=1:K(class)
1477 en = space_srv(:,Ks(class)+k) > 0;
1478 if any(en)
1479 for kdest=setdiff(1:K(class),k) % new phase
1480 rate = 0;
1481 space_srv_k = space_srv(en,:);
1482 space_buf_k = space_buf(en,:);
1483 space_var_k = space_var(en,:);
1484 if ismkvmodclass(class) && ~isempty(space_var_k)
1485 space_var_k(sum(sn.nvars(ind,1:class))) = kdest;
1486 end
1487 space_srv_k(:,Ks(class)+k) = space_srv_k(:,Ks(class)+k) - 1;
1488 space_srv_k(:,Ks(class)+kdest) = space_srv_k(:,Ks(class)+kdest) + 1;
1489 switch sn.sched(ist)
1490 case SchedStrategy.EXT
1491 rate = proc{ist}{class}{1}(k,kdest); % move next job forward
1492 case SchedStrategy.INF
1493 rate = proc{ist}{class}{1}(k,kdest)*kir(:,class,k); % assume active
1494 case {SchedStrategy.PS, SchedStrategy.LPS}
1495 rate = proc{ist}{class}{1}(k,kdest)*kir(:,class,k)./ni(:).*min(ni(:),S(ist)); % assume active
1496 case SchedStrategy.PSPRIO
1497 if all(ni <= S(ist)) || sn.classprio(class) == min(sn.classprio(nir>0))
1498 rate = proc{ist}{class}{1}(k,kdest)*kir(:,class,k)./ni(:).*min(ni(:),S(ist)); % assume active
1499 else
1500 rate = 0; % not in most urgent priority group
1501 end
1502 case SchedStrategy.DPSPRIO
1503 if all(ni <= S(ist)) || sn.classprio(class) == min(sn.classprio(nir>0))
1504 w_i = sn.schedparam(ist,:);
1505 w_i = w_i / sum(w_i);
1506 nirprio = nir;
1507 if ~all(ni <= S(ist))
1508 nirprio(sn.classprio~=sn.classprio(class)) = 0;
1509 end
1510 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
1511 else
1512 rate = 0; % not in most urgent priority group
1513 end
1514 case SchedStrategy.GPSPRIO
1515 if all(ni <= S(ist)) || sn.classprio(class) == min(sn.classprio(nir>0))
1516 w_i = sn.schedparam(ist,:);
1517 w_i = w_i / sum(w_i);
1518 nirprio = nir;
1519 if ~all(ni <= S(ist))
1520 nirprio(sn.classprio~=sn.classprio(class)) = 0;
1521 end
1522 cir = min(nirprio,ones(size(nirprio)));
1523 rate = proc{ist}{class}{1}(k,kdest)*kir(:,class,k)/nirprio(class)*w_i(class)/(w_i*cir(:)); % assume active
1524 else
1525 rate = 0; % not in most urgent priority group
1526 end
1527 case SchedStrategy.DPS
1528 if S(ist) > 1
1529 line_error(mfilename,'Multi-server DPS not supported yet');
1530 end
1531 w_i = sn.schedparam(ist,:);
1532 w_i = w_i / sum(w_i);
1533 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
1534 case SchedStrategy.GPS
1535 if S(ist) > 1
1536 line_error(mfilename,'Multi-server GPS not supported yet');
1537 end
1538 cir = min(nir,ones(size(nir)));
1539 w_i = sn.schedparam(ist,:); w_i = w_i / sum(w_i);
1540 rate = proc{ist}{class}{1}(k,kdest)*kir(:,class,k)/nir(class)*w_i(class)/(w_i*cir(:)); % assume active
1541
1542 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.LCFS, SchedStrategy.LCFSPR, SchedStrategy.LCFSPI, SchedStrategy.LCFSPRIO, SchedStrategy.SIRO, SchedStrategy.SEPT, SchedStrategy.LEPT, SchedStrategy.POLLING}
1543 rate = proc{ist}{class}{1}(k,kdest)*kir(:,class,k); % assume active
1544 end
1545 % if the class cannot be served locally,
1546 % then rate = NaN since mu{i,class}=NaN
1547 if isinf(ni) % hit limited load-dependence
1548 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,1:size(rate,1),class).*lldscaling(ist,end).*rate];
1549 else
1550 outrate = [outrate; State.cdclassfactor(cdscaling{ist},nir,1:size(rate,1),class).*lldscaling(ist,min(ni(en),lldlimit)).*rate];
1551 end
1552 outspace = [outspace; space_buf_k, space_srv_k, space_var_k];
1553 outprob = [outprob; ones(size(rate,1),1)];
1554 end
1555 end
1556 end
1557 if isSimulation
1558 if nargin>=7 && isobject(eventCache)
1559 eventCache(key) = {outprob, outspace,outrate};
1560 end
1561
1562 if size(outspace,1) > 1
1563 tot_rate = sum(outrate);
1564 cum_rate = cumsum(outrate) / tot_rate;
1565 firing_ctr = 1 + max([0,find( rand > cum_rate' )]); % select action
1566 outspace = outspace(firing_ctr,:);
1567 outrate = sum(outrate);
1568 outprob = outprob(firing_ctr,:);
1569 end
1570 end
1571 end
1572 case EventType.SWITCH
1573 % Switchover of a polling server walking towards buffer `class`. Unlike
1574 % PHASE, which carries only the internal transitions of a phase-type
1575 % and leaves the absorption to DEP, this event carries both: a
1576 % completed switchover moves no job and so has no departure to attach
1577 % the absorption to (see refreshSync). It is therefore also emitted for
1578 % a single-phase switchover, where it consists of the absorption alone.
1579 outspace = [];
1580 outrate = [];
1581 outprob = [];
1582 pinfoS = State.pollingInfo(sn, ind);
1583 if isempty(pinfoS) || ~pinfoS.hasSw(class)
1584 return
1585 end
1586 for rowS = 1:size(inspace,1)
1587 [posS, swkS] = State.pollingGet(pinfoS, space_var(rowS,:), 0);
1588 if posS ~= class || swkS == 0
1589 continue % the server is not inside the switchover into `class`
1590 end
1591 D0S = pinfoS.swD0{class};
1592 D1S = pinfoS.swD1{class};
1593 % internal transitions of the switchover phase-type
1594 for kdestS = setdiff(1:pinfoS.Ksw(class), swkS)
1595 if D0S(swkS,kdestS) <= 0
1596 continue
1597 end
1598 varS = State.pollingSet(pinfoS, space_var(rowS,:), class, kdestS, 0);
1599 outspace = [outspace; space_buf(rowS,:), space_srv(rowS,:), varS];
1600 outrate = [outrate; D0S(swkS,kdestS)];
1601 outprob = [outprob; 1];
1602 end
1603 % absorption: the server arrives at buffer `class` and either opens
1604 % a visit there or walks on
1605 rateS = sum(D1S(swkS,:));
1606 if rateS <= 0
1607 continue
1608 end
1609 nbufS = space_buf(rowS,1:R);
1610 [qS, modeS, budgetS] = State.pollingNext(pinfoS, class, nbufS, R, true);
1611 [rowsS, probsS] = State.pollingLand(pinfoS, qS, modeS, budgetS, ...
1612 space_buf(rowS,:), space_srv(rowS,:), space_var(rowS,:), K, Ks, pie{ist}, R);
1613 for jS = 1:size(rowsS,1)
1614 % A switchover that completes over an empty buffer starts the
1615 % next leg at once, and when that leg re-enters the same phase
1616 % of the same switchover (the single-buffer case, or any
1617 % memoryless switchover) the landing state is the departure
1618 % state. Such a self-loop is not a transition: emitting it would
1619 % add a spurious rate to the row and inflate the exit rate.
1620 if isequal(rowsS(jS,:), inspace(rowS,:))
1621 continue
1622 end
1623 outspace = [outspace; rowsS(jS,:)];
1624 outrate = [outrate; rateS*probsS(jS)];
1625 outprob = [outprob; 1];
1626 end
1627 end
1628 case EventType.RENEGE
1629 % Exponential-patience reneging: each waiting (queued, not-in-service)
1630 % class-r job abandons the queue at a memoryless rate impatienceMu, so
1631 % the aggregate rate out of this state is (waiting count) * mu. One
1632 % waiting job is removed from the buffer and leaves the system (the
1633 % passive half of the sync is LOCAL). Removing the newest waiting
1634 % class-r job and re-padding a zero keeps the buffer in the canonical
1635 % right-aligned form used by the arrival handler; for memoryless
1636 % patience all waiting jobs are exchangeable, so the choice does not
1637 % affect the marginal distribution.
1638 outspace = [];
1639 outrate = [];
1640 outprob = [];
1641 [~,nir,sir] = State.toMarginal(sn,ind,inspace,K,Ks,space_buf,space_srv,space_var);
1642 waiting_r = nir(class) - sir(class);
1643 if waiting_r > 0
1644 slot = find(space_buf == class, 1, 'first');
1645 if ~isempty(slot)
1646 space_buf_k = space_buf;
1647 space_buf_k(slot) = [];
1648 space_buf_k = [0, space_buf_k];
1649 outspace = [space_buf_k, space_srv, space_var];
1650 outrate = waiting_r * sn.impatienceMu(ist,class);
1651 outprob = 1;
1652 end
1653 end
1654 case EventType.RETRY
1655 % Exponential retrial: an orbiting (buffered) class-r job retries entry
1656 % into the station. The retry succeeds only when a server is free, in
1657 % which case one orbiting job enters service (in an entry phase drawn
1658 % from pie); otherwise the job stays in orbit and the event is a no-op
1659 % (not generated). The aggregate rate is (orbit size) * retrialMu.
1660 outspace = [];
1661 outrate = [];
1662 outprob = [];
1663 [~,nir,sir] = State.toMarginal(sn,ind,inspace,K,Ks,space_buf,space_srv,space_var);
1664 orbit_r = nir(class) - sir(class); % orbiting class-r jobs (held in buffer)
1665 if orbit_r > 0 && sum(space_srv,2) < S(ist)
1666 slot = find(space_buf == class, 1, 'first');
1667 if ~isempty(slot)
1668 space_buf_k = space_buf;
1669 space_buf_k(slot) = [];
1670 space_buf_k = [0, space_buf_k];
1671 pentry = pie{ist}{class};
1672 if all(isnan(pentry))
1673 pentry = ones(size(pentry)) / length(pentry);
1674 end
1675 for kentry = 1:K(class)
1676 if pentry(kentry) <= 0
1677 continue;
1678 end
1679 space_srv_k = space_srv;
1680 space_srv_k(:,Ks(class)+kentry) = space_srv_k(:,Ks(class)+kentry) + 1;
1681 outspace = [outspace; space_buf_k, space_srv_k, space_var];
1682 outrate = [outrate; orbit_r * sn.retrialMu(ist,class) * pentry(kentry)];
1683 outprob = [outprob; 1];
1684 end
1685 if isSimulation && size(outspace,1) > 1
1686 cr = cumsum(outrate) / sum(outrate);
1687 fc = 1 + max([0, find(rand > cr')]);
1688 outspace = outspace(fc,:);
1689 outrate = sum(outrate);
1690 outprob = 1;
1691 end
1692 end
1693 end
1694end
1695
1696% True BAS: when the front job is already blocked (completed, held at the server), this
1697% DEP is the *instant transfer* of that job downstream — fire at rate 1e7 (effectively
1698% instant) and clear the blocked marker in the successor. The complementary become-blocked
1699% transition (b:0->1 when the destination is full) is added by the CTMC generator / SSA
1700% engine, since only they can see the destination's occupancy. Blocked marker = last state
1701% column when this station declares it (nvars col 2*R+1 == 1).
1702% The blocked marker shares nvars col 2*R+1 with the polling controller, so gate on
1703% the dedicated sn.isbasblocking field (set for the blocking station under BOTH the
1704% upstream and destination declaration forms) rather than the station's own drop
1705% rule, which fails for a destination-declared BAS. See BUG-83.
1706if event == EventType.DEP && ~isempty(sn.isbasblocking) && numel(sn.isbasblocking) >= ind ...
1707 && sn.isbasblocking(ind) == 1 ...
1708 && ~isempty(inspace) && inspace(1,end) == 1 && ~isempty(outspace)
1709 outspace(:,end) = 0;
1710 outrate(:) = 1e7;
1711end
1712
1713end
Definition Station.m:287
Definition fjtag.m:157
Definition Station.m:245