LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
refreshLocalVars.m
1function nvars = refreshLocalVars(self)
2% NVARS = REFRESHLOCALVARS()
3
4R = self.getNumberOfClasses;
5nvars = zeros(self.getNumberOfNodes, 2*R+1);
6nodeparam = cell(self.getNumberOfNodes, 1);
7rtnodes = self.sn.rtnodes;
8% Draft SPN code:
9% isp = [];
10% ist = [];
11% nodeToPlace = zeros(1, self.getNumberOfNodes);
12% nodeToTransition = zeros(1, self.getNumberOfNodes);
13% for ind=1:self.getNumberOfNodes
14% node = self.getNodeByIndex(ind);
15% switch class(node)
16% case 'Place'
17% isp = [isp, ind];
18% nodeToPlace(ind) = length(isp);
19% case 'Transition'
20% ist = [ist, ind];
21% nodeToTransition(ind) = length(ist);
22% end
23% end
24sn = self.sn;
25
26for ind=1:self.getNumberOfNodes
27 node = self.getNodeByIndex(ind);
28 switch class(node)
29 case 'Cache'
30 nodeparam{ind} = struct();
31 nodeparam{ind}.nitems = 0;
32 nodeparam{ind}.accost = node.accessProb;
33 for r=1:self.getNumberOfClasses
34 if length(node.popularity) >= r && isa(node.popularity{r}, 'Distribution') && ~node.popularity{r}.isDisabled
35 nodeparam{ind}.nitems = max(nodeparam{ind}.nitems,node.popularity{r}.support(2));
36 end
37 end
38 % nvars cache width = contents + (retrieval system) per-item occupancy bitmap
39 if node.retrievalSystemCapacity > 0
40 retrievalBitmapWidth = nodeparam{ind}.nitems;
41 else
42 retrievalBitmapWidth = 0;
43 end
44 nvars(ind,2*R+1) = node.totalCacheCapacity + retrievalBitmapWidth;
45 nodeparam{ind}.itemcap = node.itemLevelCap;
46 nodeparam{ind}.totalCacheCapacity = node.totalCacheCapacity;
47 nodeparam{ind}.retrievalSystemCapacity = node.retrievalSystemCapacity;
48 nodeparam{ind}.pread = cell(1,self.getNumberOfClasses);
49 for r=1:self.getNumberOfClasses
50 if length(node.popularity) < r || ~isa(node.popularity{r}, 'Distribution') || node.popularity{r}.isDisabled
51 nodeparam{ind}.pread{r} = NaN;
52 else
53 nodeparam{ind}.pread{r} = node.popularity{r}.evalPMF(1:nodeparam{ind}.nitems);
54 end
55 end
56 nodeparam{ind}.replacestrat = node.replacestrategy;
57 if isprop(node,'admissionProb') && ~isempty(node.admissionProb)
58 nodeparam{ind}.qlru = node.admissionProb;
59 else
60 nodeparam{ind}.qlru = 1.0;
61 end
62 % CLIMB rewritten as FIFO on unit-capacity lists with chain promotion -- see _kb/09-ldes-and-cache.md
63 if node.replacestrategy == ReplacementStrategy.CLIMB
64 Cclimb = sum(nodeparam{ind}.itemcap);
65 nodeparam{ind}.itemcap = ones(1, Cclimb);
66 nodeparam{ind}.replacestrat = ReplacementStrategy.FIFO;
67 chainAc = diag(ones(1,Cclimb),1); chainAc(Cclimb+1,Cclimb+1) = 1;
68 Kclimb = self.getNumberOfClasses;
69 acClimb = cell(Kclimb, nodeparam{ind}.nitems);
70 for vClimb = 1:Kclimb
71 for kClimb = 1:nodeparam{ind}.nitems
72 acClimb{vClimb,kClimb} = chainAc;
73 end
74 end
75 nodeparam{ind}.accost = acClimb;
76 end
77 nodeparam{ind}.hitclass = zeros(1,self.getNumberOfClasses);
78 nodeparam{ind}.hitclass(1:length(node.server.hitClass)) = round(node.server.hitClass);
79 nodeparam{ind}.missclass = zeros(1,self.getNumberOfClasses);
80 nodeparam{ind}.missclass(1:length(node.server.missClass)) = round(node.server.missClass);
81
82 % retrieval-system class matrix: pad to current class count and -1 outside known entries
83 K = self.getNumberOfClasses;
84 if isprop(node.server, 'retrievalClasses') && ~isempty(node.server.retrievalClasses)
85 rc = -ones(nodeparam{ind}.nitems, K);
86 rc(1:size(node.server.retrievalClasses,1), 1:min(size(node.server.retrievalClasses,2),K)) = ...
87 node.server.retrievalClasses(1:size(node.server.retrievalClasses,1), 1:min(size(node.server.retrievalClasses,2),K));
88 nodeparam{ind}.retrievalClasses = rc;
89 else
90 nodeparam{ind}.retrievalClasses = -ones(max(nodeparam{ind}.nitems,1), K);
91 end
92 nodeparam{ind}.retrievalClassIndices = node.retrievalClassIndices;
93 nodeparam{ind}.retrievalSystemQueueIndices = containers.Map('KeyType','int32','ValueType','any');
94 keysList = keys(node.retrievalSystemQueueIndices);
95 for kk = 1:numel(keysList)
96 nodeparam{ind}.retrievalSystemQueueIndices(keysList{kk}) = node.retrievalSystemQueueIndices(keysList{kk});
97 end
98
99 % Store actual hit/miss/latency from solver results (if available)
100 if isprop(node.server, 'actualHitProb') && ~isempty(node.server.actualHitProb)
101 nodeparam{ind}.actualhitprob = full(node.server.actualHitProb);
102 nodeparam{ind}.actualmissprob = full(node.server.actualMissProb);
103 end
104 if isprop(node.server, 'actualDelayedHitProb') && ~isempty(node.server.actualDelayedHitProb)
105 nodeparam{ind}.actualdelayedhitprob = full(node.server.actualDelayedHitProb);
106 end
107 if isprop(node.server, 'actualResidT') && ~isempty(node.server.actualResidT)
108 nodeparam{ind}.actualresidt = full(node.server.actualResidT);
109 end
110 case 'Fork'
111 nodeparam{ind}.fanOut = node.output.tasksPerLink;
112 case 'Join'
113 nodeparam{ind}.joinStrategy = node.input.joinStrategy;
114 nodeparam{ind}.fanIn = cell(1,self.getNumberOfClasses);
115 for r=1:self.getNumberOfClasses
116 nodeparam{ind}.fanIn{r} = node.input.joinRequired{r};
117 end
118 case 'Logger'
119 nodeparam{ind}.fileName = node.fileName;
120 nodeparam{ind}.filePath = node.filePath;
121 nodeparam{ind}.startTime = node.getStartTime;
122 nodeparam{ind}.loggerName = node.getLoggerName;
123 nodeparam{ind}.timestamp = node.getTimestamp;
124 nodeparam{ind}.jobID = node.getJobID;
125 nodeparam{ind}.jobClass = node.getJobClass;
126 nodeparam{ind}.timeSameClass = node.getTimeSameClass;
127 nodeparam{ind}.timeAnyClass = node.getTimeAnyClass;
128 case {'Source'}
129 for r=1:self.getNumberOfClasses
130 if ~iscell(node.arrivalProcess) || length(node.arrivalProcess) < r || isempty(node.arrivalProcess{r})
131 continue;
132 end
133 switch class(node.arrivalProcess{r})
134 case {'MAP','MMPP2'} % Markov-modulated: track restart phase (see State.afterEvent ismkvmodclass)
135 nvars(ind,r) = nvars(ind,r) + 1;
136 case {'Replayer', 'Trace'}
137 if isempty(nodeparam{ind})
138 nodeparam{ind} = cell(1,self.getNumberOfClasses);
139 end
140 if isempty(nodeparam{ind}{r})
141 nodeparam{ind}{r} = struct();
142 end
143 nodeparam{ind}{r}.(node.arrivalProcess{r}.params{1}.paramName) = node.arrivalProcess{r}.params{1}.paramValue;
144 end
145 end
146 case {'Queue','QueueingStation','Delay','DelayStation','Transition'}
147 for r=1:self.getNumberOfClasses
148 switch class(node.server.serviceProcess{r}{3})
149 case {'MAP','MMPP2'} % Markov-modulated: track restart phase (see State.afterEvent ismkvmodclass)
150 nvars(ind,r) = nvars(ind,r) + 1;
151 case {'Replayer', 'Trace'}
152 if isempty(nodeparam{ind})
153 nodeparam{ind} = cell(1,self.getNumberOfClasses);
154 end
155 if isempty(nodeparam{ind}{r})
156 nodeparam{ind}{r} = struct();
157 end
158 nodeparam{ind}{r}.(node.server.serviceProcess{r}{3}.params{1}.paramName) = node.server.serviceProcess{r}{3}.params{1}.paramValue;
159 end
160 if ~isa(node,'Delay') && ~isa(node,'Transition') && ~isempty(node.setupTime) && ~isempty(node.setupTime{r})
161 if isempty(nodeparam{ind})
162 nodeparam{ind} = cell(1,self.getNumberOfClasses);
163 end
164 if isempty(nodeparam{ind}{r})
165 nodeparam{ind}{r} = struct();
166 end
167 nodeparam{ind}{r}.setupTime = node.setupTime{r}.getProcess();
168 nodeparam{ind}{r}.delayoffTime = node.delayoffTime{r}.getProcess();
169 end
170 if (isa(node,'Queue') || isa(node,'QueueingStation'))
171 if isempty(nodeparam{ind}) && (~isempty(node.pollingType) || ~isempty(node.switchoverTime))
172 nodeparam{ind} = cell(1,self.getNumberOfClasses);
173 for s=1:self.getNumberOfClasses
174 nodeparam{ind}{s} = struct();
175 end
176 end
177 if ~isempty(node.pollingType) && ~isempty(node.pollingType{r})
178 nodeparam{ind}{r}.pollingType = node.pollingType{r};
179 nodeparam{ind}{r}.pollingPar = node.pollingPar;
180 end
181 if ~isempty(node.switchoverTime) && ~isempty(node.switchoverTime{r})
182 if min(size(node.switchoverTime))==1
183 nodeparam{ind}{r}.switchoverTime = node.switchoverTime{r}.getProcess();
184 nodeparam{ind}{r}.switchoverProcId = ProcessType.toId(ProcessType.fromText(class(node.switchoverTime{r})));
185 else
186 for t=1:length(node.switchoverTime)
187 % Empty from-to switchover cells (diagonal, unset pairs) treated as Immediate, not dereferenced
188 so_rt = node.switchoverTime{r,t};
189 if isempty(so_rt)
190 so_rt = Immediate();
191 end
192 nodeparam{ind}{r}.switchoverTime{t} = so_rt.getProcess();
193 nodeparam{ind}{r}.switchoverProcId(t) = ProcessType.toId(ProcessType.fromText(class(so_rt)));
194 end
195 end
196 end
197 end
198 end
199 % Pass-and-swap (PAS) queues carry a node-level class
200 % compatibility/swap graph rather than per-class parameters.
201 if (isa(node,'Queue') || isa(node,'QueueingStation')) && ...
202 (SchedStrategy.toId(node.schedStrategy) == SchedStrategy.PAS || ...
203 SchedStrategy.toId(node.schedStrategy) == SchedStrategy.OI)
204 if isempty(nodeparam{ind})
205 nodeparam{ind} = struct();
206 end
207 if SchedStrategy.toId(node.schedStrategy) == SchedStrategy.OI
208 % Order-independent queue: swap graph is always zero (empty),
209 % so class order is preserved on completion (plain OI).
210 nodeparam{ind}.swapGraph = zeros(R,R);
211 elseif isempty(node.swapGraph)
212 % PAS default: complete compatibility graph (any class may
213 % swap with any other; no self-loops) when none was set.
214 nodeparam{ind}.swapGraph = ones(R,R) - eye(R);
215 else
216 nodeparam{ind}.swapGraph = node.swapGraph;
217 end
218 % Total service rate function mu(c) of the ordered state vector c.
219 nodeparam{ind}.svcRateFun = node.svcRateFun;
220 end
221 end
222
223 for r=1:R
224 switch sn.routing(ind,r)
225 case RoutingStrategy.SQ
226 nodeparam{ind}{r}.d = node.output.outputStrategy{r}{3}{1};
227 case RoutingStrategy.WRROBIN
228 nvars(ind,R+r) = nvars(ind,R+r) + 1;
229 % save indexes of outgoing links
230 if isempty(nodeparam) || isempty(nodeparam{ind}) % reinstantiate if not a cache
231 nodeparam{ind}{r} = struct();
232 end
233 nodeparam{ind}{r}.weights = zeros(1,self.sn.nnodes);
234 nodeparam{ind}{r}.outlinks = find(self.sn.connmatrix(ind,:));
235 for c=1:size(node.output.outputStrategy{1, r}{3},2)
236 destination = node.output.outputStrategy{1, r}{3}{c}{1};
237 weight = node.output.outputStrategy{1, r}{3}{c}{2};
238 nodeparam{ind}{r}.weights(destination.index) = weight;
239 end
240 % WRR cycle: replicate each outlink by its weight; afterEventRouter walks it one position per DEP (unit weights = plain RR)
241 cycle = [];
242 for ol = nodeparam{ind}{r}.outlinks
243 w = nodeparam{ind}{r}.weights(ol);
244 if w > 0
245 cycle = [cycle, repmat(ol, 1, max(1, round(w)))]; %#ok<AGROW>
246 else
247 cycle = [cycle, ol]; %#ok<AGROW>
248 end
249 end
250 nodeparam{ind}{r}.weighted_outlinks = cycle;
251 case RoutingStrategy.RROBIN
252 nvars(ind,R+r) = nvars(ind,R+r) + 1;
253 % save indexes of outgoing links
254 if isempty(nodeparam) || isempty(nodeparam{ind}) % reinstantiate if not a cache
255 nodeparam{ind}{r} = struct();
256 end
257 nodeparam{ind}{r}.outlinks = find(self.sn.connmatrix(ind,:));
258 end
259 end
260end
261
262% True BAS blocking: shared trailing nvars column, isbasblocking/isbasdestination
263% semantics and the BUG-83 upstream-vs-destination declaration forms -- see _kb/04-networkstruct.md (isbasblocking, isbasdestination fields)
264isbasblocking = zeros(self.getNumberOfNodes, 1);
265isbasdestnode = false(self.getNumberOfNodes, R);
266for ind=1:self.getNumberOfNodes
267 node = self.getNodeByIndex(ind);
268 if isa(node,'Station') && ~isa(node,'Source') && ~isa(node,'Cache')
269 [declares, destmask] = declaresBlockedMarker(self, ind, R);
270 isbasdestnode = isbasdestnode | destmask;
271 if declares
272 % Option B (BUG-83): polling + BAS-blocking share one column, so a polling+BAS station is rejected here, before state-space build
273 if self.sn.sched(self.sn.nodeToStation(ind)) == SchedStrategy.POLLING
274 line_error(mfilename, sprintf(['True BAS blocking is not supported at a polling station (%s): the ', ...
275 'polling controller and the BAS blocked marker share one local-state column. Use a non-polling ', ...
276 'scheduling strategy at the blocking station, or remove the BAS drop rule.'], self.sn.nodenames{ind}));
277 end
278 nvars(ind, 2*R+1) = 1;
279 isbasblocking(ind) = 1;
280 end
281 end
282end
283
284% Server breakdown status shares the trailing local-variable column with BAS/polling; combinations rejected before state-space build
285hasbreakdown = zeros(self.getNumberOfNodes, 1);
286if ~isempty(self.sn) && isfield(self.sn,'hasbreakdown') && ~isempty(self.sn.hasbreakdown)
287 for ind=1:self.getNumberOfNodes
288 if numel(self.sn.hasbreakdown) < ind || self.sn.hasbreakdown(ind) ~= 1
289 continue
290 end
291 if isbasblocking(ind) == 1
292 line_error(mfilename, sprintf(['Station ''%s'' combines server breakdowns with true-BAS blocking: the ' ...
293 'breakdown status and the BAS blocked marker share one local-state column. Remove the BAS drop rule ' ...
294 'or the breakdown.'], self.sn.nodenames{ind}));
295 end
296 if self.sn.sched(self.sn.nodeToStation(ind)) == SchedStrategy.POLLING
297 line_error(mfilename, sprintf(['Server breakdowns are not supported at a polling station (%s): the polling ' ...
298 'controller and the breakdown status share one local-state column.'], self.sn.nodenames{ind}));
299 end
300 nvars(ind, 2*R+1) = 1;
301 hasbreakdown(ind) = 1;
302 end
303end
304
305% Polling controller width depends on discipline/switchover immediacy; State.pollingInfo is the single definition of the block layout
306if ~isempty(self.sn)
307 self.sn.nvars = nvars;
308 self.sn.nodeparam = nodeparam;
309 for ind=1:self.getNumberOfNodes
310 if self.sn.isstation(ind) && self.sn.sched(self.sn.nodeToStation(ind)) == SchedStrategy.POLLING
311 pinfo = State.pollingInfo(self.sn, ind);
312 nvars(ind, 2*R+1) = pinfo.width;
313 % memoize: State.pollingInfo is read once per state per
314 % synchronization while the generator is built
315 nodeparam{ind}{1}.pollinfo = pinfo;
316 self.sn.nodeparam = nodeparam;
317 end
318 end
319 self.sn.nvars = nvars;
320 self.sn.isbasblocking = isbasblocking;
321 % Project the node-indexed destination mask onto the station index space
322 % used by sn.droprule, which is what State.arrivalIsLost indexes with.
323 isbasdestination = false(self.sn.nstations, R);
324 for ind=1:self.getNumberOfNodes
325 if ~self.sn.isstation(ind)
326 continue
327 end
328 jst = self.sn.nodeToStation(ind);
329 if isnan(jst) || jst < 1
330 continue
331 end
332 isbasdestination(jst,:) = isbasdestination(jst,:) | isbasdestnode(ind,:);
333 end
334 self.sn.isbasdestination = isbasdestination;
335end
336end
337
338function [tf, destmask] = declaresBlockedMarker(self, ind, R)
339% [TF, DESTMASK] = DECLARESBLOCKEDMARKER(SELF, IND, R)
340%
341% DESTMASK is an (nnodes x R) logical marking the (destination node, class)
342% pairs whose refusals must block IND rather than be lost.
343%
344% True when station IND is the BLOCKING (upstream) side of a true-BAS relation:
345% it has a directly reachable destination station with a finite capacity, and
346% BAS is declared EITHER on this station (upstream form, cqn_bas_blocking.m) OR
347% on that destination (destination form, the JMT/LDES convention used by
348% test_des_bas_closed.m; canonical since BUG-83). Both forms resolve to the same
349% blocking station -- the held job sits here and the become-blocked edge starts
350% here -- so the marker always lives on the upstream station. Whether the caller
351% keys enumeration on the station's own drop rule (which fails for the
352% destination form) or on the dedicated sn.isbasblocking field is what BUG-83
353% fixes. Mirrors Network.declaresBlockedMarker in the JAR.
354tf = false;
355destmask = false(self.getNumberOfNodes, R);
356node = self.getNodeByIndex(ind);
357if ~isa(node,'Station') || isa(node,'Source') || isa(node,'Cache')
358 return
359end
360dests = downstreamStations(self, ind);
361for r = 1:R
362 hereBAS = length(node.dropRule) >= r && node.dropRule(r) == DropStrategy.BAS;
363 for d = 1:length(dests)
364 dnode = self.getNodeByIndex(dests(d));
365 if ~isa(dnode,'Station') || isa(dnode,'Source')
366 continue
367 end
368 dcap = dnode.cap;
369 if isempty(dcap) || ~isfinite(dcap) || dcap <= 0
370 continue % the destination must be able to fill
371 end
372 thereBAS = length(dnode.dropRule) >= r && dnode.dropRule(r) == DropStrategy.BAS;
373 if hereBAS || thereBAS
374 % BAS declared upstream or on the reachable full destination. Do not
375 % return early: every such destination must be recorded, since a
376 % refusal at any of them has to block IND instead of dropping.
377 tf = true;
378 destmask(dests(d), r) = true;
379 end
380 end
381end
382end
383
384function out = downstreamStations(self, ind)
385% OUT = DOWNSTREAMSTATIONS(SELF, IND)
386%
387% Node indices of the stations directly downstream of IND, walking through
388% intermediate stateless nodes (Router, ClassSwitch, ...) but stopping at the first
389% station on each path, since a blocked job is held for its immediate destination.
390out = [];
391conn = self.sn.connmatrix;
392if isempty(conn)
393 return
394end
395n = self.getNumberOfNodes;
396seen = false(1,n);
397queue = ind;
398seen(ind) = true;
399while ~isempty(queue)
400 cur = queue(1); queue(1) = [];
401 for j = 1:min(n, size(conn,2))
402 if conn(cur,j) ~= 1 || seen(j)
403 continue
404 end
405 seen(j) = true;
406 if isa(self.getNodeByIndex(j),'Station')
407 out(end+1) = j; %#ok<AGROW>
408 else
409 queue(end+1) = j; %#ok<AGROW>
410 end
411 end
412end
413end
Definition fjtag.m:161