LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
pollingInfo.m
1function pinfo = pollingInfo(sn, ind)
2% PINFO = POLLINGINFO(SN, IND)
3%
4% Derived description of the polling controller at the station of node IND.
5% Returns [] when the node is not a polling station.
6%
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.
12%
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
20% non-immediate.
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
26% progress included;
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).
30%
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.
38%
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.
44
45% Copyright (c) 2012-2026, Imperial College London
46% All rights reserved.
47
48pinfo = [];
49if ~sn.isstation(ind)
50 return
51end
52ist = sn.nodeToStation(ind);
53if sn.sched(ist) ~= SchedStrategy.POLLING
54 return
55end
56
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;
63 return
64end
65
66R = sn.nclasses;
67pinfo.ptype = PollingType.EXHAUSTIVE;
68pinfo.pk = 1;
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);
74
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);
80for r=1:R
81 if isempty(sn.proc{ist}{r}) || any(any(isnan(sn.proc{ist}{r}{1})))
82 pinfo.polled(r) = false;
83 end
84end
85if ~any(pinfo.polled)
86 line_error(mfilename, sprintf('Polling station %d has no class with an enabled service.', ist));
87end
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
91% the next one", i.e. sn.nodeparam{ind}{i}.switchoverTime is the cost of
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.
98swof = zeros(1,R);
99polledlist = find(pinfo.polled);
100for jj=1:length(polledlist)
101 q = polledlist(jj);
102 prevq = polledlist(mod(jj-2, length(polledlist)) + 1);
103 swof(q) = prevq;
104end
105
106np = sn.nodeparam{ind};
107for r=1:R
108 if isempty(np) || length(np) < r || isempty(np{r})
109 continue
110 end
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));
116 end
117 end
118end
119
120% Switchover of the leg entering each polled buffer q, read off the buffer the
121% server leaves to get there.
122for q = polledlist
123 src = swof(q);
124 if isempty(np) || length(np) < src || isempty(np{src}) ...
125 || ~isfield(np{src},'switchoverTime') || isempty(np{src}.switchoverTime)
126 continue
127 end
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
132 proc = proc{1};
133 procid = procid(1);
134 end
135 if procid(1) == ProcessType.IMMEDIATE
136 continue % zero-time switchover: folded, never a state
137 end
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);
143end
144
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;
153
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));
158
159% Column indices inside space_var, 0 when the column is not materialized.
160pinfo.ipos = 0;
161pinfo.iswk = 0;
162pinfo.ictr = 0;
163c = pinfo.off;
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
167end
Definition Station.m:245