1function tf = arrivalIsLost(sn, ist,
class)
2% TF = ARRIVALISLOST(SN, IST, CLASS)
4% True when an arrival of CLASS that finds no room at station IST
is LOST, and
5%
false when it must BLOCK the upstream instead. This
is the single predicate
6% that decides the refusal semantics; every refusal path must branch on it.
8% The rule
is the CLASS TYPE, not the drop rule:
10% OPEN
class -> LOST. The external arrival stream
is memoryless, so a job
11% that finds the station full simply never enters. The caller
12% must then leave the state UNCHANGED (a self-loop): the
13% arrival event still fires, so the offered rate reaches the
14% arrival-rate statistic and the loss shows up as
15% ArvR - Tput. A self-loop cancels on the generator diagonal
16% and therefore cannot perturb the stationary distribution,
17% so QLen/Util/Tput are unaffected.
18% CLOSED class -> BLOCKED. A closed network
's N jobs have nowhere to go;
19% population conservation is a defining invariant, so a
20% closed job can never be dropped. The caller must return an
21% EMPTY outspace, which disables the upstream departure until
24% An explicit blocking drop rule (BAS/BBS/RSRD) also asks for blocking, for any
25% class: the user has said the job must wait rather than be lost.
27% This is the same open/closed predicate as the CTMC analyzer's canDropClass
28% and the BUG-12 utilization guard. Note the conventions are complementary, not
29% contradictory: the arrival rate counts the OFFERED job (this predicate lets
30% the event fire), while Util/QLen/Tput count only the CARRIED one.
32% Copyright (c) 2012-2026, Imperial College London
35if ~isempty(sn.droprule) && size(sn.droprule,1) >= ist && size(sn.droprule,2) >= class
36 dr = sn.droprule(ist,class);
37 if dr == DropStrategy.BAS || dr == DropStrategy.BBS || dr == DropStrategy.RSRD
38 tf =
false; % the user asked
for blocking explicitly
42% The rule above sees only THIS station
's declaration. Under the upstream
43% declaration form (cqn_bas_blocking.m) the BAS rule sits on the blocking
44% station, not on the destination where the refusal happens, so the destination
45% side is recorded separately by refreshLocalVars. Without this branch an open
46% class refused here would be declared lost, the become-blocked edge would never
47% fire, and the blocking station would behave as if its destination were
48% unbounded. See BUG-83 and the R2 tandem-BAS fidelity fix.
49if isfield(sn,'isbasdestination
') && ~isempty(sn.isbasdestination) ...
50 && size(sn.isbasdestination,1) >= ist && size(sn.isbasdestination,2) >= class ...
51 && sn.isbasdestination(ist,class)
52 tf = false; % refusing here must block the upstream BAS station
55% Open -> lost (self-loop), closed -> blocked. This decides only what happens
56% once the arrival has already been refused (the state was not placed). The
57% physical-vs-cutoff distinction is NOT made here: an open arrival always
58% self-loops when refused, exactly as the pre-change producer did (a refused
59% WAITQ arrival was left unchanged). What DID change with the physical cap is
60% WHERE the refusal happens -- the hasRoom gate in afterEventStation stops
61% placing the job at a physical cap so that it reaches this self-loop, whereas
62% a cutoff-only class is still placed and truncated by the en_o filter. So the
63% cutoff case never relies on this branch, and open self-looping here is the
64% pre-change behaviour it must preserve.
65tf = isinf(sn.njobs(class)); % open -> lost, closed -> blocked