LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
afterEventStationSignal.m
1function [outspace, outrate, outprob] = afterEventStationSignal(sn, ind, ist, inspace, class, K, Ks, S, pie, space_buf, space_srv, space_var)
2% [OUTSPACE, OUTRATE, OUTPROB] = AFTEREVENTSTATIONSIGNAL(SN, IND, IST, INSPACE, CLASS, K, KS, S, PIE, SPACE_BUF, SPACE_SRV, SPACE_VAR)
3%
4% Passive arrival of a G-network signal class at station IST. A signal never
5% joins the station: it acts on the jobs already there and is annihilated.
6%
7% CATASTROPHE empties the station of every job, ignoring the removal count
8% distribution (a catastrophe removes all jobs by definition).
9%
10% NEGATIVE removes a batch of jobs:
11% - Victims. When the signal declares a target class (setmodel via
12% forJobClass, sn.signaltarget >= 1) only that class is eligible;
13% otherwise every non-signal class is eligible. The untargeted case is
14% the classic Gelenbe negative customer and agrees with SolverMAM and
15% SolverLDES, which are both class-agnostic.
16% - Count. sn.signalremdist gives the batch-size pmf. An oversized batch
17% empties the eligible jobs rather than driving the queue negative, so
18% the pmf tail P(B >= n) lumps onto "remove all n" (the min(B,n)
19% clipping used by LDES and the tail term used by MAM).
20% - Policy. sn.signalrempolicy selects the victim: FCFS removes the oldest
21% waiting job, LCFS the newest, RANDOM draws uniformly over waiting and
22% in-service jobs. FCFS/LCFS only touch an in-service job when no job is
23% waiting (two-tier, as in LDES).
24%
25% The station state carries no arrival-time order for in-service jobs, so
26% when a policy has to reach into the servers the victim is drawn uniformly
27% across the occupied phases (exact whenever at most one job of the class is
28% in service, which covers every single-server station).
29%
30% Copyright (c) 2012-2026, Imperial College London
31% All rights reserved.
32
33[~, nirm, sirm] = State.toMarginal(sn, ind, inspace, K, Ks, space_buf, space_srv, space_var);
34
35% CATASTROPHE: derived from signaltype as well as the iscatastrophe flag.
36if State.isCatastropheSignal(sn, class)
37 outspace = [zeros(1, size(space_buf, 2)), zeros(1, size(space_srv, 2)), space_var];
38 outrate = -1;
39 outprob = 1;
40 return
41end
42
43% Eligible victim classes.
44tgt = -1;
45if isfield(sn, 'signaltarget') && ~isempty(sn.signaltarget) && numel(sn.signaltarget) >= class
46 tgt = sn.signaltarget(class);
47end
48if tgt >= 1
49 tgtclasses = tgt;
50else
51 tgtclasses = find(~sn.issignal(:)');
52end
53tgtclasses = tgtclasses(nirm(tgtclasses) > 0);
54ntot = sum(nirm(tgtclasses));
55if isempty(tgtclasses) || ntot <= 0
56 outspace = [space_buf, space_srv, space_var]; % no victim: the signal vanishes
57 outrate = -1;
58 outprob = 1;
59 return
60end
61
62% Batch size pmf, clipped at the number of eligible jobs.
63[kvals, kprobs] = State.signalBatchPMF(sn, class, ntot);
64
65policy = RemovalPolicy.RANDOM;
66if isfield(sn, 'signalrempolicy') && ~isempty(sn.signalrempolicy) && numel(sn.signalrempolicy) >= class
67 policy = sn.signalrempolicy(class);
68end
69
70outspace = [];
71outprob = [];
72for ik = 1:numel(kvals)
73 if kprobs(ik) <= 0
74 continue
75 end
76 if kvals(ik) <= 0
77 outspace = [outspace; space_buf, space_srv, space_var]; %#ok<AGROW>
78 outprob = [outprob; kprobs(ik)]; %#ok<AGROW>
79 continue
80 end
81 [sp, pr] = removeBatch(sn, ist, space_buf, space_srv, space_var, ...
82 kvals(ik), tgtclasses, policy, K, Ks, S, pie);
83 outspace = [outspace; sp]; %#ok<AGROW>
84 outprob = [outprob; kprobs(ik) * pr]; %#ok<AGROW>
85end
86
87% Merge duplicate destination states so the generator sees one entry each.
88[outspace, outprob] = mergeStates(outspace, outprob);
89outrate = -1 * ones(size(outspace, 1), 1); % passive action
90end
91
92function [outspace, outprob] = removeBatch(sn, ist, buf, srv, var, k, tgtclasses, policy, K, Ks, S, pie)
93% Remove k jobs one at a time; sequential uniform draws without replacement
94% reproduce a uniform choice of the removed subset.
95outspace = [buf, srv, var];
96outprob = 1;
97for step = 1:k
98 nextspace = [];
99 nextprob = [];
100 for row = 1:size(outspace, 1)
101 b = outspace(row, 1:size(buf, 2));
102 s = outspace(row, size(buf, 2) + (1:size(srv, 2)));
103 v = outspace(row, size(buf, 2) + size(srv, 2) + 1:end);
104 [sp, pr] = removeOne(sn, ist, b, s, v, tgtclasses, policy, K, Ks, S, pie);
105 if isempty(sp)
106 % nothing left to remove: the state is already drained
107 nextspace = [nextspace; outspace(row, :)]; %#ok<AGROW>
108 nextprob = [nextprob; outprob(row)]; %#ok<AGROW>
109 else
110 nextspace = [nextspace; sp]; %#ok<AGROW>
111 nextprob = [nextprob; outprob(row) * pr]; %#ok<AGROW>
112 end
113 end
114 [outspace, outprob] = mergeStates(nextspace, nextprob);
115end
116end
117
118function [outspace, outprob] = removeOne(sn, ist, buf, srv, var, tgtclasses, policy, K, Ks, S, pie)
119% Enumerate the single-victim outcomes and their probabilities.
120outspace = [];
121outprob = [];
122
123[waitPos, waitClass, waitWeight, isOrdered, isPairBuf] = waitingVictims(sn, ist, buf, tgtclasses);
124[srvClass, srvPhase, srvCount] = inServiceVictims(srv, tgtclasses, K, Ks);
125nwait = sum(waitWeight);
126nsrv = sum(srvCount);
127if nwait == 0 && nsrv == 0
128 return
129end
130
131% FCFS/LCFS rank the waiting line by age, which only an ordered buffer
132% records; a per-class count buffer (SIRO/SEPT/LEPT) carries no age, so an
133% age-based policy degenerates to a uniform draw over the waiting jobs.
134ageOrdered = isOrdered && (policy == RemovalPolicy.FCFS || policy == RemovalPolicy.LCFS);
135if ageOrdered && nwait > 0
136 if policy == RemovalPolicy.FCFS
137 [~, pick] = max(waitPos); % head of line: the last occupied slot
138 else
139 [~, pick] = min(waitPos); % most recent arrival: the first occupied slot
140 end
141 [b2, s2] = dropWaiting(buf, srv, waitPos(pick), isOrdered, isPairBuf, waitClass(pick));
142 outspace = [b2, s2, var];
143 outprob = 1;
144 return
145end
146
147if policy == RemovalPolicy.RANDOM
148 total = nwait + nsrv; % uniform over waiting and in-service jobs alike
149else
150 total = nwait; % FCFS/LCFS drain the waiting line before the servers
151 if total == 0
152 total = nsrv;
153 end
154end
155
156if nwait > 0
157 for w = 1:numel(waitPos)
158 [b2, s2] = dropWaiting(buf, srv, waitPos(w), isOrdered, isPairBuf, waitClass(w));
159 outspace = [outspace; b2, s2, var]; %#ok<AGROW>
160 outprob = [outprob; waitWeight(w) / total]; %#ok<AGROW>
161 end
162end
163if policy == RemovalPolicy.RANDOM || nwait == 0
164 for j = 1:numel(srvClass)
165 if srvCount(j) <= 0
166 continue
167 end
168 [b2, s2] = dropInService(sn, ist, buf, srv, srvClass(j), srvPhase(j), K, Ks, S, pie, isOrdered, isPairBuf);
169 outspace = [outspace; b2, s2, var]; %#ok<AGROW>
170 outprob = [outprob; srvCount(j) / total]; %#ok<AGROW>
171 end
172end
173[outspace, outprob] = mergeStates(outspace, outprob);
174end
175
176function [pos, cls, weight, isOrdered, isPairBuf] = waitingVictims(sn, ist, buf, tgtclasses)
177% Eligible waiting jobs, as (position, class, multiplicity). Three buffer
178% layouts are in use: an ordered list of class ids (FCFS family), an ordered
179% list of (class, phase) pairs (preemptive family), and a per-class count
180% vector (SIRO/SEPT/LEPT). Stations that admit every job into service have
181% no waiting line at all. Only the ordered layouts record arrival order.
182pos = [];
183cls = [];
184weight = [];
185isOrdered = false;
186isPairBuf = false;
187if isempty(buf)
188 return
189end
190switch sn.sched(ist)
191 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.LCFS, SchedStrategy.LCFSPRIO}
192 isOrdered = true;
193 for c = 1:numel(buf)
194 if buf(c) > 0 && ismember(buf(c), tgtclasses)
195 pos(end+1) = c; %#ok<AGROW>
196 cls(end+1) = buf(c); %#ok<AGROW>
197 weight(end+1) = 1; %#ok<AGROW>
198 end
199 end
200 case {SchedStrategy.FCFSPR, SchedStrategy.FCFSPI, SchedStrategy.FCFSPRPRIO, SchedStrategy.FCFSPIPRIO, ...
201 SchedStrategy.LCFSPR, SchedStrategy.LCFSPI, SchedStrategy.LCFSPRPRIO, SchedStrategy.LCFSPIPRIO}
202 isOrdered = true;
203 isPairBuf = true;
204 for c = 1:2:numel(buf)
205 if buf(c) > 0 && ismember(buf(c), tgtclasses)
206 pos(end+1) = c; %#ok<AGROW>
207 cls(end+1) = buf(c); %#ok<AGROW>
208 weight(end+1) = 1; %#ok<AGROW>
209 end
210 end
211 case {SchedStrategy.SIRO, SchedStrategy.SEPT, SchedStrategy.LEPT}
212 % per-class counts: each eligible class is one victim kind carrying
213 % as many interchangeable jobs as its count
214 for r = tgtclasses
215 if r <= numel(buf) && buf(r) > 0
216 pos(end+1) = r; %#ok<AGROW>
217 cls(end+1) = r; %#ok<AGROW>
218 weight(end+1) = buf(r); %#ok<AGROW>
219 end
220 end
221 otherwise
222 % PS/INF/DPS/GPS/LPS and the source hold no waiting jobs
223end
224end
225
226function [cls, phase, count] = inServiceVictims(srv, tgtclasses, K, Ks)
227cls = [];
228phase = [];
229count = [];
230for r = tgtclasses
231 for p = 1:K(r)
232 n = srv(Ks(r) + p);
233 if n > 0
234 cls(end+1) = r; %#ok<AGROW>
235 phase(end+1) = p; %#ok<AGROW>
236 count(end+1) = n; %#ok<AGROW>
237 end
238 end
239end
240end
241
242function [buf, srv] = dropWaiting(buf, srv, pos, isOrdered, isPairBuf, cls)
243% Remove a waiting job, keeping the layout invariant (empty slots pad the
244% left, the head of line stays rightmost).
245if isPairBuf
246 buf([pos, pos+1]) = [];
247 buf = [0, 0, buf];
248elseif isOrdered
249 buf(pos) = [];
250 buf = [0, buf];
251else
252 buf(cls) = buf(cls) - 1; % per-class count buffer
253end
254end
255
256function [buf, srv] = dropInService(sn, ist, buf, srv, cls, phase, K, Ks, S, pie, isOrdered, isPairBuf)
257% Remove an in-service job and, at a station that keeps a waiting line, pull
258% the head of line into the freed server.
259srv(Ks(cls) + phase) = srv(Ks(cls) + phase) - 1;
260if isempty(buf) || sum(srv) >= S(ist)
261 return
262end
263switch sn.sched(ist)
264 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.LCFS, SchedStrategy.LCFSPRIO}
265 headpos = find(buf > 0, 1, 'last');
266 if ~isempty(headpos)
267 promo = buf(headpos);
268 % the slot is vacated, not blanked in place: the waiting line
269 % stays right-aligned with the empty slots padding the left
270 buf(headpos) = [];
271 buf = [0, buf];
272 srv(Ks(promo) + 1) = srv(Ks(promo) + 1) + 1;
273 end
274 case {SchedStrategy.FCFSPR, SchedStrategy.FCFSPI, SchedStrategy.FCFSPRPRIO, SchedStrategy.FCFSPIPRIO, ...
275 SchedStrategy.LCFSPR, SchedStrategy.LCFSPI, SchedStrategy.LCFSPRPRIO, SchedStrategy.LCFSPIPRIO}
276 headpos = [];
277 for c = 1:2:numel(buf)
278 if buf(c) > 0
279 headpos = c;
280 end
281 end
282 if ~isempty(headpos)
283 promo = buf(headpos);
284 promophase = buf(headpos+1);
285 if promophase < 1
286 promophase = 1;
287 end
288 buf([headpos, headpos+1]) = [];
289 buf = [0, 0, buf];
290 srv(Ks(promo) + promophase) = srv(Ks(promo) + promophase) + 1; % resumes at its stored phase
291 end
292 case {SchedStrategy.SIRO, SchedStrategy.SEPT, SchedStrategy.LEPT}
293 % the freed server takes a waiting job; the count buffer carries no
294 % order, so any waiting class is equally eligible. Promote the
295 % lowest-indexed waiting class to keep the map single-valued: the
296 % SIRO service order is itself resolved by the rate function.
297 promo = find(buf > 0, 1, 'first');
298 if ~isempty(promo)
299 buf(promo) = buf(promo) - 1;
300 srv(Ks(promo) + 1) = srv(Ks(promo) + 1) + 1;
301 end
302 otherwise
303 % no waiting line to promote from
304end
305end
306
307function [space, prob] = mergeStates(space, prob)
308if isempty(space)
309 return
310end
311[u, ~, ic] = unique(space, 'rows', 'stable');
312p = zeros(size(u, 1), 1);
313for i = 1:numel(ic)
314 p(ic(i)) = p(ic(i)) + prob(i);
315end
316space = u;
317prob = p;
318end
Definition Station.m:245