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% see _kb/04-networkstruct.md (refreshGlobalSync/refreshSync) for rationale
91polledlist = find(pinfo.polled);
92for jj=1:length(polledlist)
94 prevq = polledlist(mod(jj-2, length(polledlist)) + 1);
98np = sn.nodeparam{ind};
100 if isempty(np) || length(np) < r || isempty(np{r})
103 if isfield(np{r},
'pollingType') && ~isempty(np{r}.pollingType)
104 % setPollingType writes the same discipline into every
class buffer
105 pinfo.ptype = PollingType.toId(np{r}.pollingType);
106 if isfield(np{r},
'pollingPar') && ~isempty(np{r}.pollingPar)
107 pinfo.pk = round(np{r}.pollingPar(1));
112% Switchover of the leg entering each polled buffer q, read off the buffer the
113% server leaves to get there.
116 if isempty(np) || length(np) < src || isempty(np{src}) ...
117 || ~isfield(np{src},
'switchoverTime') || isempty(np{src}.switchoverTime)
120 procid = np{src}.switchoverProcId;
121 proc = np{src}.switchoverTime;
122 if iscell(proc) && ~isempty(proc) && iscell(proc{1})
123 % from-to matrix
form: polling only uses the per-buffer vector
127 if procid(1) == ProcessType.IMMEDIATE
128 continue % zero-time switchover: folded, never a state
130 pinfo.hasSw(q) =
true;
131 pinfo.Ksw(q) = length(proc{1});
132 pinfo.swD0{q} = proc{1};
133 pinfo.swD1{q} = proc{2};
134 pinfo.swpie{q} = map_pie(proc);
137% Column widths. pos and swk exist only to encode SWITCHING(p); with no
138% non-immediate switchover the server
is either serving (pos =
class in
139% service) or parked (pos unobservable), so neither
column carries
140% information. ctr exists
for every discipline that bounds a visit.
141pinfo.wpos = double(any(pinfo.hasSw));
142pinfo.wswk = double(any(pinfo.hasSw));
143pinfo.wctr = double(pinfo.ptype ~= PollingType.EXHAUSTIVE);
144pinfo.width = pinfo.wpos + pinfo.wswk + pinfo.wctr;
146% Offset of the polling block inside space_var. The block
is the 2*R+1
column
147% of nvars, hence it trails the per-
class Markov-modulation slots (1..R) and
148% the routing slots (R+1..2*R).
149pinfo.off = sum(sn.nvars(ind,1:2*R));
151% Column indices inside space_var, 0 when the
column is not materialized.
156if pinfo.wpos, c = c + 1; pinfo.ipos = c; end
157if pinfo.wswk, c = c + 1; pinfo.iswk = c; end
158if pinfo.wctr, c = c + 1; pinfo.ictr = c; end