LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
afterEvent.m
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)
3%
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.
8%
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.
15
16% Copyright (c) 2012-2026, Imperial College London
17% All rights reserved.
18
19if nargin < 9 || isempty(noPromote)
20 noPromote = false;
21end
22
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,:);
39 end
40 return
41 end
42else
43 key = NaN;
44 eventCache = [];
45end
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);
51 return
52end
53
54% Else continue to main body below
55usectx = nargin >= 8 && ~isempty(ctx);
56if usectx
57 % Loop-invariant setup precomputed once by State.afterEventInit
58 M = ctx.M;
59 R = ctx.R;
60 S = ctx.S;
61 phasessz = ctx.phasessz;
62 phaseshift = ctx.phaseshift;
63 pie = ctx.pie;
64 isf = sn.nodeToStateful(ind);
65 if sn.isstation(ind)
66 ismkvmod = ctx.ismkvmod(ind);
67 ismkvmodclass = ctx.ismkvmodclass{ind};
68 end
69 lldscaling = ctx.lldscaling;
70 lldlimit = ctx.lldlimit;
71 cdscaling = ctx.cdscaling;
72else
73 M = sn.nstations;
74 R = sn.nclasses;
75 S = sn.nservers;
76 phasessz = sn.phasessz;
77 phaseshift = sn.phaseshift;
78 pie = sn.pie;
79
80 % ind: node index
81 isf = sn.nodeToStateful(ind);
82
83 if sn.isstation(ind)
84 ismkvmod = any(sn.procid(sn.nodeToStation(ind),:)==ProcessType.MAP | sn.procid(sn.nodeToStation(ind),:)==ProcessType.MMPP2);
85 ismkvmodclass = zeros(R,1);
86 for r=1:R
87 ismkvmodclass(r) = any(sn.procid(sn.nodeToStation(ind),r)==ProcessType.MAP | sn.procid(sn.nodeToStation(ind),r)==ProcessType.MMPP2);
88 end
89 end
90
91 lldscaling = sn.lldscaling;
92 if isempty(lldscaling)
93 lldlimit = max(sum(sn.nclosedjobs),1);
94 lldscaling = ones(M,lldlimit);
95 else
96 lldlimit = size(lldscaling,2);
97 end
98
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);
108 end
109 neutral = @(ni) 1;
110 for i = 1:M
111 if i > numel(cdscaling) || isempty(cdscaling{i})
112 cdscaling{i} = neutral;
113 end
114 end
115 % Fold joint-dependence handles (sn.jdscaling, non-product-form eta_i) into
116 % the effective per-station handle, exactly as afterEventInit does.
117 jdscaling = sn.jdscaling;
118 if ~isempty(jdscaling)
119 for i = 1:M
120 if i <= numel(jdscaling) && ~isempty(jdscaling{i})
121 cdh = cdscaling{i};
122 jdh = jdscaling{i};
123 cdscaling{i} = @(ni) cdh(ni) .* jdh(ni);
124 end
125 end
126 end
127end
128
129hasOnlyExp = false; % true if all service processes are exponential
130if sn.isstation(ind)
131 ist = sn.nodeToStation(ind);
132 K = phasessz(ist,:);
133 Ks = phaseshift(ist,:);
134 if max(K)==1
135 hasOnlyExp = true;
136 end
137 if usectx
138 mu = ctx.mu;
139 phi = ctx.phi;
140 proc = ctx.proc;
141 capacity = ctx.capacity;
142 classcap = ctx.classcap;
143 else
144 mu = sn.mu;
145 phi = sn.phi;
146 proc = sn.proc;
147 capacity = sn.cap;
148 classcap = sn.classcap;
149 end
150 if K(class) == 0 % if this class is not accepted at the resource
151 eventCache(key) = {outprob, outspace,outrate};
152 return
153 end
154 V = sum(sn.nvars(ind,:));
155 % Place nodes: state format is [buffer(R), server(sum(K))] after ARV
156 if sn.nodetype(ind) == NodeType.Place
157 space_var = zeros(size(inspace,1), 0); % proper dimensions for concatenation
158 state_len = size(inspace, 2);
159 expected_len = R + sum(K);
160 if state_len == expected_len
161 % State already has [buffer, server] format
162 space_buf = inspace(:, 1:R);
163 space_srv = inspace(:, (R+1):end);
164 elseif state_len == R
165 % Initial state: just buffer counts
166 space_buf = inspace;
167 space_srv = zeros(size(inspace,1), sum(K));
168 else
169 % Fallback for unexpected formats
170 space_buf = inspace;
171 space_srv = zeros(size(inspace,1), sum(K));
172 end
173 else
174 if sn.sched(ist) == SchedStrategy.EXT
175 % Source state layout from fromMarginal: [Inf_buffer, phases...]
176 % plus, when the source uses round-robin routing, trailing local
177 % variable columns holding the outlink pointers. MAP arrival
178 % variables are not materialized (the phase lives in the service
179 % slots), so only the routing variables are sliced off.
180 V = sum(sn.nvars(ind,(R+1):(2*R)));
181 if V > 0
182 space_var = inspace(:,(end-V+1):end);
183 space_srv = inspace(:,(end-sum(K)-V+1):(end-V));
184 space_buf = inspace(:,1:(end-sum(K)-V));
185 else
186 space_buf = inspace(:, 1:(size(inspace,2)-sum(K)));
187 space_srv = inspace(:, (size(inspace,2)-sum(K)+1):end);
188 space_var = zeros(size(inspace,1), 0);
189 V = 0;
190 end
191 elseif sn.sched(ist) == SchedStrategy.PAS
192 % PAS/OI layout: [ordered-list(cap) | routing vars(V)]; no server split.
193 space_var = inspace(:,(end-V+1):end);
194 space_srv = zeros(size(inspace,1), 0);
195 space_buf = inspace(:,1:(end-V));
196 else
197 space_var = inspace(:,(end-V+1):end); % local state variables
198 space_srv = inspace(:,(end-sum(K)-V+1):(end-V)); % server state
199 space_buf = inspace(:,1:(end-sum(K)-V)); % buffer state
200 end
201 end
202elseif sn.isstateful(ind)
203 V = sum(sn.nvars(ind,:));
204 % in this case service is always immediate so sum(K)=1
205 space_var = inspace(:,(end-V+1):end); % local state variables
206 if sn.nodetype(ind) == NodeType.Transition
207 K = sn.nodeparam{ind}.firingphases;
208 nmodes = sn.nodeparam{ind}.nmodes;
209 % Handle NaN firingphases (non-phase-type distributions like Pareto)
210 % Infer phase count from D0 matrix size
211 if any(isnan(K))
212 K = zeros(1, nmodes);
213 for m = 1:nmodes
214 if iscell(sn.nodeparam{ind}.firingproc) && ~isempty(sn.nodeparam{ind}.firingproc{m})
215 K(m) = size(sn.nodeparam{ind}.firingproc{m}{1}, 1);
216 else
217 K(m) = 1;
218 end
219 end
220 end
221 Ks = [0,cumsum(K,2)];
222 space_buf = inspace(:,1:nmodes); % idle servers count put in buf
223 space_srv = inspace(:,(nmodes+1):(nmodes+sum(K))); % enabled servers' phases
224 % Handle both state formats: with and without fired component
225 expected_len_with_fired = 2*nmodes + sum(K);
226 expected_len_without_fired = nmodes + sum(K);
227 if size(inspace, 2) >= expected_len_with_fired
228 space_fired = inspace(:,(nmodes+sum(K)+1):(2*nmodes+sum(K))); % servers that just fired
229 elseif size(inspace, 2) == expected_len_without_fired
230 % Legacy format without fired component - initialize to zeros
231 space_fired = zeros(size(inspace,1), nmodes);
232 else
233 line_error(mfilename, 'Unexpected state vector length for Transition node');
234 end
235 else
236 space_buf = []; % buffer state
237 space_srv = inspace(:,(end-R-V+1):(end-V)); % server state
238 space_fired = []; % only for Transition nodes
239 end
240else % stateless node
241 space_var = [];
242 space_srv = [];
243 space_buf = [];
244end
245
246if sn.isstation(ind)
247 [outspace, outrate, outprob, eventCache] = State.afterEventStation(sn, ind, inspace, event, class, isSimulation, eventCache, ...
248 M, R, S, phasessz, phaseshift, pie, isf, ismkvmod, ismkvmodclass, lldscaling, lldlimit, cdscaling, ...
249 hasOnlyExp, ist, K, Ks, mu, phi, proc, capacity, classcap, V, space_buf, space_srv, space_var, key, noPromote);
250elseif sn.isstateful(ind)
251 switch sn.nodetype(ind)
252 case NodeType.Router
253 [outspace, outrate, outprob, eventCache] = State.afterEventRouter(sn, ind, event, class, isSimulation, eventCache, space_buf, space_srv, space_var, key);
254 case NodeType.Fork
255 [outspace, outrate, outprob, eventCache] = State.afterEventFork(sn, ind, event, class, isSimulation, eventCache, space_buf, space_srv, space_var, key);
256 case NodeType.Cache
257 [outspace, outrate, outprob, eventCache] = State.afterEventCache(sn, ind, event, class, isSimulation, eventCache, R, space_buf, space_srv, space_var, key);
258 case NodeType.Transition
259 [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);
260 end % switch nodeType
261end
262end
Definition fjtag.m:161