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
115end
116
117hasOnlyExp = false; % true if all service processes are exponential
118if sn.isstation(ind)
119 ist = sn.nodeToStation(ind);
120 K = phasessz(ist,:);
121 Ks = phaseshift(ist,:);
122 if max(K)==1
123 hasOnlyExp = true;
124 end
125 if usectx
126 mu = ctx.mu;
127 phi = ctx.phi;
128 proc = ctx.proc;
129 capacity = ctx.capacity;
130 classcap = ctx.classcap;
131 else
132 mu = sn.mu;
133 phi = sn.phi;
134 proc = sn.proc;
135 capacity = sn.cap;
136 classcap = sn.classcap;
137 end
138 if K(class) == 0 % if this class is not accepted at the resource
139 eventCache(key) = {outprob, outspace,outrate};
140 return
141 end
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
154 space_buf = inspace;
155 space_srv = zeros(size(inspace,1), sum(K));
156 else
157 % Fallback for unexpected formats
158 space_buf = inspace;
159 space_srv = zeros(size(inspace,1), sum(K));
160 end
161 else
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)));
169 if V > 0
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));
173 else
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);
177 V = 0;
178 end
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));
184 else
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
188 end
189 end
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
199 if any(isnan(K))
200 K = zeros(1, nmodes);
201 for m = 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);
204 else
205 K(m) = 1;
206 end
207 end
208 end
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);
220 else
221 line_error(mfilename, 'Unexpected state vector length for Transition node');
222 end
223 else
224 space_buf = []; % buffer state
225 space_srv = inspace(:,(end-R-V+1):(end-V)); % server state
226 space_fired = []; % only for Transition nodes
227 end
228else % stateless node
229 space_var = [];
230 space_srv = [];
231 space_buf = [];
232end
233
234if sn.isstation(ind)
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)
240 case NodeType.Router
241 [outspace, outrate, outprob, eventCache] = State.afterEventRouter(sn, ind, event, class, isSimulation, eventCache, space_buf, space_srv, space_var, key);
242 case NodeType.Fork
243 [outspace, outrate, outprob, eventCache] = State.afterEventFork(sn, ind, event, class, isSimulation, eventCache, space_buf, space_srv, space_var, key);
244 case NodeType.Cache
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
249end
250end
Definition fjtag.m:157
Definition Station.m:245