1function nvars = refreshLocalVars(self)
2% NVARS = REFRESHLOCALVARS()
4R = self.getNumberOfClasses;
5nvars = zeros(self.getNumberOfNodes, 2*R+1);
6nodeparam = cell(self.getNumberOfNodes, 1);
7rtnodes = self.sn.rtnodes;
11% nodeToPlace = zeros(1, self.getNumberOfNodes);
12% nodeToTransition = zeros(1, self.getNumberOfNodes);
13% for ind=1:self.getNumberOfNodes
14% node = self.getNodeByIndex(ind);
18% nodeToPlace(ind) = length(isp);
21% nodeToTransition(ind) = length(ist);
26for ind=1:self.getNumberOfNodes
27 node = self.getNodeByIndex(ind);
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));
38 % nvars cache width = contents + (retrieval system) per-item occupancy bitmap
39 if node.retrievalSystemCapacity > 0
40 retrievalBitmapWidth = nodeparam{ind}.nitems;
42 retrievalBitmapWidth = 0;
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;
53 nodeparam{ind}.pread{r} = node.popularity{r}.evalPMF(1:nodeparam{ind}.nitems);
56 nodeparam{ind}.replacestrat = node.replacestrategy;
57 if isprop(node,
'admissionProb') && ~isempty(node.admissionProb)
58 nodeparam{ind}.qlru = node.admissionProb;
60 nodeparam{ind}.qlru = 1.0;
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);
71 for kClimb = 1:nodeparam{ind}.nitems
72 acClimb{vClimb,kClimb} = chainAc;
75 nodeparam{ind}.accost = acClimb;
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);
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;
90 nodeparam{ind}.retrievalClasses = -ones(max(nodeparam{ind}.nitems,1), K);
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});
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);
104 if isprop(node.server,
'actualDelayedHitProb') && ~isempty(node.server.actualDelayedHitProb)
105 nodeparam{ind}.actualdelayedhitprob = full(node.server.actualDelayedHitProb);
107 if isprop(node.server,
'actualResidT') && ~isempty(node.server.actualResidT)
108 nodeparam{ind}.actualresidt = full(node.server.actualResidT);
111 nodeparam{ind}.fanOut = node.output.tasksPerLink;
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};
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;
129 for r=1:self.getNumberOfClasses
130 if ~iscell(node.arrivalProcess) || length(node.arrivalProcess) < r || isempty(node.arrivalProcess{r})
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);
140 if isempty(nodeparam{ind}{r})
141 nodeparam{ind}{r} =
struct();
143 nodeparam{ind}{r}.(node.arrivalProcess{r}.params{1}.paramName) = node.arrivalProcess{r}.params{1}.paramValue;
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);
155 if isempty(nodeparam{ind}{r})
156 nodeparam{ind}{r} =
struct();
158 nodeparam{ind}{r}.(node.server.serviceProcess{r}{3}.params{1}.paramName) = node.server.serviceProcess{r}{3}.params{1}.paramValue;
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);
164 if isempty(nodeparam{ind}{r})
165 nodeparam{ind}{r} =
struct();
167 nodeparam{ind}{r}.setupTime = node.setupTime{r}.getProcess();
168 nodeparam{ind}{r}.delayoffTime = node.delayoffTime{r}.getProcess();
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();
177 if ~isempty(node.pollingType) && ~isempty(node.pollingType{r})
178 nodeparam{ind}{r}.pollingType = node.pollingType{r};
179 nodeparam{ind}{r}.pollingPar = node.pollingPar;
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})));
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};
192 nodeparam{ind}{r}.switchoverTime{t} = so_rt.getProcess();
193 nodeparam{ind}{r}.switchoverProcId(t) = ProcessType.toId(ProcessType.fromText(
class(so_rt)));
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();
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);
216 nodeparam{ind}.swapGraph = node.swapGraph;
218 % Total service rate function mu(c) of the ordered state vector c.
219 nodeparam{ind}.svcRateFun = node.svcRateFun;
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();
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)
237 weight = node.output.outputStrategy{1, r}{3}{c}{2};
238 nodeparam{ind}{r}.weights(destination.index) = weight;
240 % WRR cycle: replicate each outlink by its weight; afterEventRouter walks it one position per DEP (unit weights = plain RR)
242 for ol = nodeparam{ind}{r}.outlinks
243 w = nodeparam{ind}{r}.weights(ol);
245 cycle = [cycle, repmat(ol, 1, max(1, round(w)))]; %#ok<AGROW>
247 cycle = [cycle, ol]; %#ok<AGROW>
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();
257 nodeparam{ind}{r}.outlinks = find(self.sn.connmatrix(ind,:));
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;
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}));
278 nvars(ind, 2*R+1) = 1;
279 isbasblocking(ind) = 1;
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
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}));
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}));
300 nvars(ind, 2*R+1) = 1;
301 hasbreakdown(ind) = 1;
305% Polling controller width depends on discipline/switchover immediacy; State.pollingInfo
is the single definition of the block layout
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;
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)
328 jst = self.sn.nodeToStation(ind);
329 if isnan(jst) || jst < 1
332 isbasdestination(jst,:) = isbasdestination(jst,:) | isbasdestnode(ind,:);
334 self.sn.isbasdestination = isbasdestination;
338function [tf, destmask] = declaresBlockedMarker(self, ind, R)
339% [TF, DESTMASK] = DECLARESBLOCKEDMARKER(SELF, IND, R)
341% DESTMASK
is an (nnodes x R) logical marking the (destination node,
class)
342% pairs whose refusals must block IND rather than be lost.
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.
355destmask = false(self.getNumberOfNodes, R);
356node = self.getNodeByIndex(ind);
357if ~isa(node,'
Station') || isa(node,'Source') || isa(node,'Cache')
360dests = downstreamStations(self, ind);
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')
369 if isempty(dcap) || ~isfinite(dcap) || dcap <= 0
370 continue % the destination must be able to fill
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.
378 destmask(dests(d), r) = true;
384function out = downstreamStations(self, ind)
385% OUT = DOWNSTREAMSTATIONS(SELF, IND)
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.
391conn = self.sn.connmatrix;
395n = self.getNumberOfNodes;
400 cur = queue(1); queue(1) = [];
401 for j = 1:min(n, size(conn,2))
402 if conn(cur,j) ~= 1 || seen(j)
406 if isa(self.getNodeByIndex(j),'
Station')
407 out(end+1) = j; %
#ok<AGROW>
409 queue(end+1) = j; %#ok<AGROW>