1function pinfo = pollingInfo(sn, ind)
2% PINFO = POLLINGINFO(SN, IND)
4% Derived description of
the polling controller at
the station of node IND.
5% Returns [] when
the node
is not a polling station.
7% The polling controller
is stored in
the trailing local-variable block of
the
8% station state, whose width
is sn.nvars(ind,2*R+1). The block holds, in order,
9%
the columns [pos, swk, ctr]; each
is materialized only when
the discipline
10% actually needs it (see
the widths below), so that a polling station never
11% carries state that its dynamics cannot distinguish.
13% pos index of
the buffer
the server
is currently at (serving) or heading to
14% (switching). It
is materialized only when at least one switchover
is
15% non-immediate: while a job
is in service pos always equals
the class of
16% that job, and while
the station
is empty and every switchover
is
17% immediate
the server position
is unobservable (see PARKED below).
18% swk 0 when
the server sits at pos, otherwise
the phase of
the switchover
19% PH into buffer pos. Materialized only when some switchover
is
21% ctr
the visit budget, materialized for every discipline except EXHAUSTIVE:
22% GATED jobs of class pos admitted at
the polling instant that
23% have not completed yet (
the job in service counts as
24% one of them), so
the visit ends when ctr reaches 0;
25% KLIMITED services still permitted in
this visit,
the one in
27% DECREMENTING
the target
class-pos population:
the visit ends once
28%
the population has dropped to ctr, i.e. one below
the
29% level found at
the polling instant (semi-exhaustive).
31% The three tangible controller configurations are therefore
32% SERVING(p) swk=0, one
class-p job in
the service facility;
33% SWITCHING(p) swk>0, service facility empty;
34% PARKED swk=0, service facility empty, station empty. Reachable only
35% when every switchover
is immediate, in which
case a server that
36% completes a full lap without finding work would otherwise cycle
37% in zero time forever. pos
is then unobservable and canonical.
39% Immediate() switchovers are NOT represented as states: Immediate.getProcess
40% returns an exponential of rate GlobalConstants.Immediate, and taking that
41% literally would put a ~1e8 rate in
the generator (stiff, and a spurious state
42% per buffer). They are instead folded into
the enclosing transition by
43% State.pollingNext, which walks
the cyclic order until a tangible state.
45% Copyright (c) 2012-2026, Imperial College London
52ist = sn.nodeToStation(ind);
53if sn.sched(ist) ~= SchedStrategy.POLLING
57% The description
is a pure function of
the model, but it
is read once per
58% state per synchronization when
the generator
is built, so it
is memoized on
59%
nodeparam by refreshLocalVars rather than rebuilt (map_pie and all) per call.
60if ~isempty(sn.
nodeparam{ind}) && iscell(sn.nodeparam{ind}) ...
61 && ~isempty(sn.nodeparam{ind}{1}) && isfield(sn.nodeparam{ind}{1},
'pollinfo')
62 pinfo = sn.nodeparam{ind}{1}.pollinfo;
67pinfo.ptype = PollingType.EXHAUSTIVE;
69pinfo.hasSw =
false(1,R);
70pinfo.Ksw = zeros(1,R);
71pinfo.swD0 = cell(1,R);
72pinfo.swD1 = cell(1,R);
73pinfo.swpie = cell(1,R);
75% Buffers
the server actually
visits. A
class disabled at this station can
76% never hold a job, and State.afterEvent
short-circuits every
event carrying
77% it (K(
class)==0), so it cannot be given a switchover to walk through: such a
78% buffer
is dropped from
the cyclic order rather than polled forever.
79pinfo.polled =
true(1,R);
81 if isempty(sn.proc{ist}{r}) || any(any(isnan(sn.proc{ist}{r}{1})))
82 pinfo.polled(r) = false;
86 line_error(mfilename, sprintf('Polling station %d has no class with an enabled service.', ist));
89% Which class buffer's switchover distribution governs
the leg INTO buffer q.
90% Queue.setSwitchover documents its argument as "time to switch from queue i to
92% LEAVING buffer i, which
is the same leg as entering
the next buffer
the
93% server
visits. The controller indexes its states by destination, so
the
94% lookup
is shifted onto
the previous polled buffer here, once, rather than at
95% every use. This
is also
the convention of Takagi
's r_i in the exact formulas
96% of api/polling (see polling_qsys_gated), against which the discipline agrees
97% to machine precision.
99polledlist = find(pinfo.polled);
100for jj=1:length(polledlist)
102 prevq = polledlist(mod(jj-2, length(polledlist)) + 1);
106np = sn.nodeparam{ind};
108 if isempty(np) || length(np) < r || isempty(np{r})
111 if isfield(np{r},'pollingType
') && ~isempty(np{r}.pollingType)
112 % setPollingType writes the same discipline into every class buffer
113 pinfo.ptype = PollingType.toId(np{r}.pollingType);
114 if isfield(np{r},'pollingPar
') && ~isempty(np{r}.pollingPar)
115 pinfo.pk = round(np{r}.pollingPar(1));
120% Switchover of the leg entering each polled buffer q, read off the buffer the
121% server leaves to get there.
124 if isempty(np) || length(np) < src || isempty(np{src}) ...
125 || ~isfield(np{src},'switchoverTime
') || isempty(np{src}.switchoverTime)
128 procid = np{src}.switchoverProcId;
129 proc = np{src}.switchoverTime;
130 if iscell(proc) && ~isempty(proc) && iscell(proc{1})
131 % from-to matrix form: polling only uses the per-buffer vector
135 if procid(1) == ProcessType.IMMEDIATE
136 continue % zero-time switchover: folded, never a state
138 pinfo.hasSw(q) = true;
139 pinfo.Ksw(q) = length(proc{1});
140 pinfo.swD0{q} = proc{1};
141 pinfo.swD1{q} = proc{2};
142 pinfo.swpie{q} = map_pie(proc);
145% Column widths. pos and swk exist only to encode SWITCHING(p); with no
146% non-immediate switchover the server is either serving (pos = class in
147% service) or parked (pos unobservable), so neither column carries
148% information. ctr exists for every discipline that bounds a visit.
149pinfo.wpos = double(any(pinfo.hasSw));
150pinfo.wswk = double(any(pinfo.hasSw));
151pinfo.wctr = double(pinfo.ptype ~= PollingType.EXHAUSTIVE);
152pinfo.width = pinfo.wpos + pinfo.wswk + pinfo.wctr;
154% Offset of the polling block inside space_var. The block is the 2*R+1 column
155% of nvars, hence it trails the per-class Markov-modulation slots (1..R) and
156% the routing slots (R+1..2*R).
157pinfo.off = sum(sn.nvars(ind,1:2*R));
159% Column indices inside space_var, 0 when the column is not materialized.
164if pinfo.wpos, c = c + 1; pinfo.ipos = c; end
165if pinfo.wswk, c = c + 1; pinfo.iswk = c; end
166if pinfo.wctr, c = c + 1; pinfo.ictr = c; end