1function [outspace, outrate, outprob, eventCache] = afterEvent(sn, ind, inspace, event,
class, isSimulation, eventCache, ctx, noPromote)
2% [OUTSPACE, OUTRATE, OUTPROB] = AFTEREVENT(QN, IND, INSPACE, EVENT, CLASS, ISSIMULATION, EVENTCACHE, CTX, NOPROMOTE)
4% CTX (optional): loop-invariant context precomputed by State.afterEventInit
5% on
the SAME sn passed here (after any caller-side rewrite of
6% nservers/cap/classcap, see solver_ssa preamble). Hot callers pass it to
7% skip
the per-call setup below; semantics are identical.
9% NOPROMOTE (optional,
default false): forwarded to State.afterEventStation.
10% When true, a DEP at an FCFS-family station does not promote a waiting job
11% into
the vacated server. Set only for
the departure half of an
12% immediate-feedback self-loop (sn.immfeed) so
the fed-back job holds
the
13% server rather than re-queueing. It
is part of
the cache key below so that
14% immediate-feedback and ordinary departures never collide in
the cache.
16% Copyright (c) 2012-2026, Imperial College London
19if nargin < 9 || isempty(noPromote)
23% Event cache
for faster simulation
24if isSimulation && nargin >= 7 && isobject(eventCache)
25 vector = [ind, event,
class, double(noPromote), inspace];
26 key = mat2str(vector);
27 if eventCache.isKey(key)
28 cachedResult = eventCache(key);
29 outprob = cachedResult{1};
30 outspace = cachedResult{2};
31 outrate = cachedResult{3};
32 if size(outspace,1) > 1
33 tot_rate = sum(outrate);
34 cum_rate = cumsum(outrate) / tot_rate;
35 firing_ctr = 1 + max([0,find( rand > cum_rate
' )]); % select action
36 outspace = outspace(firing_ctr,:);
37 outrate = sum(outrate);
38 outprob = outprob(firing_ctr,:);
46% Join synchronization stations on FJ-augmented structs carry a plain
47% per-class count state handled by a dedicated branch (they are stations,
48% but must bypass the buffer/phase slicing and afterEventStation below)
49if isfield(sn,'isfjaugmented
') && sn.isfjaugmented && sn.nodetype(ind) == NodeType.Join
50 [outspace, outrate, outprob, eventCache] = State.afterEventJoin(sn, ind, inspace, event, class, isSimulation, eventCache, key);
54% Else continue to main body below
55usectx = nargin >= 8 && ~isempty(ctx);
57 % Loop-invariant setup precomputed once by State.afterEventInit
61 phasessz = ctx.phasessz;
62 phaseshift = ctx.phaseshift;
64 isf = sn.nodeToStateful(ind);
66 ismkvmod = ctx.ismkvmod(ind);
67 ismkvmodclass = ctx.ismkvmodclass{ind};
69 lldscaling = ctx.lldscaling;
70 lldlimit = ctx.lldlimit;
71 cdscaling = ctx.cdscaling;
76 phasessz = sn.phasessz;
77 phaseshift = sn.phaseshift;
81 isf = sn.nodeToStateful(ind);
84 ismkvmod = any(sn.procid(sn.nodeToStation(ind),:)==ProcessType.MAP | sn.procid(sn.nodeToStation(ind),:)==ProcessType.MMPP2);
85 ismkvmodclass = zeros(R,1);
87 ismkvmodclass(r) = any(sn.procid(sn.nodeToStation(ind),r)==ProcessType.MAP | sn.procid(sn.nodeToStation(ind),r)==ProcessType.MMPP2);
91 lldscaling = sn.lldscaling;
92 if isempty(lldscaling)
93 lldlimit = max(sum(sn.nclosedjobs),1);
94 lldscaling = ones(M,lldlimit);
96 lldlimit = size(lldscaling,2);
99 % sn.cdscaling carries a class-dependence handle only for the stations that
100 % declare one; the others are left empty (see getLimitedClassDependence).
101 % afterEventStation indexes every station unconditionally, and an empty entry
102 % would be silently INDEXED rather than called -- cdscaling{ist}(nir) on []
103 % raises "Array indices must be positive integers" whenever some class has
104 % zero jobs. Fill the gaps with the neutral scaling, as afterEventInit does.
105 cdscaling = sn.cdscaling;
106 if isempty(cdscaling)
107 cdscaling = cell(M,1);
111 if i > numel(cdscaling) || isempty(cdscaling{i})
112 cdscaling{i} = neutral;
117hasOnlyExp = false; % true if all service processes are exponential
119 ist = sn.nodeToStation(ind);
121 Ks = phaseshift(ist,:);
129 capacity = ctx.capacity;
130 classcap = ctx.classcap;
136 classcap = sn.classcap;
138 if K(class) == 0 % if this class is not accepted at the resource
139 eventCache(key) = {outprob, outspace,outrate};
142 V = sum(sn.nvars(ind,:));
143 % Place nodes: state format is [buffer(R), server(sum(K))] after ARV
144 if sn.nodetype(ind) == NodeType.Place
145 space_var = zeros(size(inspace,1), 0); % proper dimensions for concatenation
146 state_len = size(inspace, 2);
147 expected_len = R + sum(K);
148 if state_len == expected_len
149 % State already has [buffer, server] format
150 space_buf = inspace(:, 1:R);
151 space_srv = inspace(:, (R+1):end);
152 elseif state_len == R
153 % Initial state: just buffer counts
155 space_srv = zeros(size(inspace,1), sum(K));
157 % Fallback for unexpected formats
159 space_srv = zeros(size(inspace,1), sum(K));
162 if sn.sched(ist) == SchedStrategy.EXT
163 % Source state layout from fromMarginal: [Inf_buffer, phases...]
164 % plus, when the source uses round-robin routing, trailing local
165 % variable columns holding the outlink pointers. MAP arrival
166 % variables are not materialized (the phase lives in the service
167 % slots), so only the routing variables are sliced off.
168 V = sum(sn.nvars(ind,(R+1):(2*R)));
170 space_var = inspace(:,(end-V+1):end);
171 space_srv = inspace(:,(end-sum(K)-V+1):(end-V));
172 space_buf = inspace(:,1:(end-sum(K)-V));
174 space_buf = inspace(:, 1:(size(inspace,2)-sum(K)));
175 space_srv = inspace(:, (size(inspace,2)-sum(K)+1):end);
176 space_var = zeros(size(inspace,1), 0);
179 elseif sn.sched(ist) == SchedStrategy.PAS
180 % PAS/OI layout: [ordered-list(cap) | routing vars(V)]; no server split.
181 space_var = inspace(:,(end-V+1):end);
182 space_srv = zeros(size(inspace,1), 0);
183 space_buf = inspace(:,1:(end-V));
185 space_var = inspace(:,(end-V+1):end); % local state variables
186 space_srv = inspace(:,(end-sum(K)-V+1):(end-V)); % server state
187 space_buf = inspace(:,1:(end-sum(K)-V)); % buffer state
190elseif sn.isstateful(ind)
191 V = sum(sn.nvars(ind,:));
192 % in this case service is always immediate so sum(K)=1
193 space_var = inspace(:,(end-V+1):end); % local state variables
194 if sn.nodetype(ind) == NodeType.Transition
195 K = sn.nodeparam{ind}.firingphases;
196 nmodes = sn.nodeparam{ind}.nmodes;
197 % Handle NaN firingphases (non-phase-type distributions like Pareto)
198 % Infer phase count from D0 matrix size
200 K = zeros(1, nmodes);
202 if iscell(sn.nodeparam{ind}.firingproc) && ~isempty(sn.nodeparam{ind}.firingproc{m})
203 K(m) = size(sn.nodeparam{ind}.firingproc{m}{1}, 1);
209 Ks = [0,cumsum(K,2)];
210 space_buf = inspace(:,1:nmodes); % idle servers count put in buf
211 space_srv = inspace(:,(nmodes+1):(nmodes+sum(K))); % enabled servers' phases
212 % Handle both state formats: with and without fired component
213 expected_len_with_fired = 2*nmodes + sum(K);
214 expected_len_without_fired = nmodes + sum(K);
215 if size(inspace, 2) >= expected_len_with_fired
216 space_fired = inspace(:,(nmodes+sum(K)+1):(2*nmodes+sum(K))); % servers that just fired
217 elseif size(inspace, 2) == expected_len_without_fired
218 % Legacy format without fired component - initialize to zeros
219 space_fired = zeros(size(inspace,1), nmodes);
221 line_error(mfilename,
'Unexpected state vector length for Transition node');
224 space_buf = []; % buffer state
225 space_srv = inspace(:,(end-R-V+1):(end-V)); % server state
226 space_fired = []; % only
for Transition
nodes
235 [outspace, outrate, outprob, eventCache] = State.afterEventStation(sn, ind, inspace, event,
class, isSimulation, eventCache, ...
236 M, R, S, phasessz, phaseshift, pie, isf, ismkvmod, ismkvmodclass, lldscaling, lldlimit, cdscaling, ...
237 hasOnlyExp, ist, K, Ks, mu, phi, proc, capacity, classcap, V, space_buf, space_srv, space_var, key, noPromote);
238elseif sn.isstateful(ind)
239 switch sn.nodetype(ind)
241 [outspace, outrate, outprob, eventCache] = State.afterEventRouter(sn, ind, event,
class, isSimulation, eventCache, space_buf, space_srv, space_var, key);
243 [outspace, outrate, outprob, eventCache] = State.afterEventFork(sn, ind, event,
class, isSimulation, eventCache, space_buf, space_srv, space_var, key);
245 [outspace, outrate, outprob, eventCache] = State.afterEventCache(sn, ind, event,
class, isSimulation, eventCache, R, space_buf, space_srv, space_var, key);
246 case NodeType.Transition
247 [outspace, outrate, outprob, eventCache] = State.afterEventTransition(sn, ind, inspace, K, Ks, event,
class, isSimulation, eventCache, R, space_buf, space_srv, space_fired, space_var, key);
248 end %
switch nodeType