1function [nodeStateSpace, sn, capacityc] = spaceGeneratorNodes(sn, cutoff, options)
3 options = Solver.defaultOptions;
7capacityc = zeros(sn.nnodes, sn.nclasses);
10% if isfield(sn.varsparam{n}, "capacityc")
11% capacityc(n) = sn.varsparam{n}.capacityc;
15 % Cooperative wall-clock budget checkpoint (options.timeout)
16 if nargin>=3 && lineTimeoutExceeded(options)
17 line_error(mfilename,'State space generation exceeded
the wall-clock time budget (options.timeout=%gs).
', options.timeout);
19 if sn.isstation(ind) % place jobs across stations
20 ist = sn.nodeToStation(ind);
21 isf = sn.nodeToStateful(ind);
22 for r=1:sn.nclasses %cut-off open classes to finite capacity
23 c = find(sn.chains(:,r));
24 if ~isempty(sn.visits{c}) && sn.visits{c}(isf,r) == 0
27 %elseif isfield(sn.varsparam{ind}, 'capacityc
')
28 % capacityc(ind,r) = min(cutoff(ist,r), sn.classcap(ist,r));
29 elseif sn.nodetype(ind) ~= NodeType.Place && ~isempty(sn.proc) && ~isempty(sn.proc{ist}{r}) && any(any(isnan(sn.proc{ist}{r}{1}))) % disabled (not Places - they hold tokens without service)
33 capacityc(ind,r) = min(cutoff(ist,r), sn.classcap(ist,r));
35 % closed classes: enumerate up to the chain population, but never
36 % beyond the class capacity at this station (finite-buffer stations)
37 capacityc(ind,r) = min(sum(sn.njobs(sn.chains(c,:))), sn.classcap(ist,r));
40 % Finite-capacity region bound: a station in a DROP/WAITQ region can
41 % never hold more than the region's per-
class cap (nor more than
the
42 % region-global cap) of
class r, so enumerating beyond it produces only
43 % states
the region filter later discards. Bounding capacityc here keeps
44 %
the generated space small (
the auto-cutoff
is region-blind and can be
45 % far larger than any reachable population), with an identical
final
46 % result. -1 = unbounded. sn.region{f}
is M x (K+1): cols 1..K per-class,
47 % col K+1
the region-global cap at each member station.
48 if isfield(sn,
'nregions') && sn.nregions > 0 && capacityc(ind,r) > 0
51 if ist <= size(regf,1)
53 gc = regf(ist, sn.nclasses+1);
55 capacityc(ind,r) = min(capacityc(ind,r), rc);
58 capacityc(ind,r) = min(capacityc(ind,r), gc);
65 % in this case,
the local variables are produced within
66 % fromMarginalBounds, e.g., for RROBIN routing
67 sn.space{isf} = State.fromMarginalBounds(sn, ind, [], capacityc(ind,:), sn.cap(ist), options);
70 state_bufsrv = State.fromMarginalBounds(sn, ind, [], capacityc(ind,:), sn.cap(ist), options);
71 state_var = State.spaceLocalVars(sn, ind);
72 sn.space{isf} = State.cartesian(state_bufsrv,state_var); % generate all possible states
for local variables
74 if isinf(sn.nservers(ist))
75 sn.nservers(ist) = sum(capacityc(ind,:));
77 elseif sn.isstateful(ind) % generate state space of other stateful
nodes that are not stations
78 %ist = sn.nodeToStation(ind);
79 isf = sn.nodeToStateful(ind);
80 switch sn.nodetype(ind)
82 for r=1:sn.nclasses % restrict state space generation to immediate events
86 % For Router
nodes, only allow capacity
for classes that have
87 % non-zero
nodevisits (classes that actually visit
the Router)
89 c = find(sn.chains(:,r));
90 if ~isempty(sn.nodevisits{c}) && sn.nodevisits{c}(ind,r) > 0
96 case NodeType.Transition
97 capacityc(ind,:) = 0; % Transitions don
't hold class-based jobs
98 % Generate per-mode state space (bypass fromMarginalBounds)
99 nmodes = sn.nodeparam{ind}.nmodes;
100 firingphases = sn.nodeparam{ind}.firingphases;
101 if any(isnan(firingphases))
102 firingphases = zeros(1, nmodes);
104 if iscell(sn.nodeparam{ind}.firingproc) && ~isempty(sn.nodeparam{ind}.firingproc{m})
105 firingphases(m) = size(sn.nodeparam{ind}.firingproc{m}{1}, 1);
112 nmodeservers = sn.nodeparam{ind}.nmodeservers;
113 max_jobs = sum(sn.njobs(~isinf(sn.njobs)));
114 if any(isinf(sn.njobs))
115 max_jobs = max_jobs + sum(cutoff(1,:));
117 mode_spaces = cell(1, nmodes);
119 max_srv_m = nmodeservers(m);
121 max_srv_m = max_jobs;
123 max_srv_m = min(max_srv_m, max_jobs);
125 for total = 0:max_srv_m
126 phase_combs = multichoose(fK(m), total);
127 buf_m = nmodeservers(m);
129 buf_m = GlobalConstants.MaxInt();
131 buf_m = buf_m - total;
132 mode_states = [mode_states; repmat(buf_m, size(phase_combs,1), 1), phase_combs]; %#ok<AGROW>
134 mode_spaces{m} = mode_states;
136 trans_space = mode_spaces{1};
138 trans_space = State.cartesian(trans_space, mode_spaces{m});
140 % State.cartesian interleaves the columns as
141 % [idle_1, phases_1, idle_2, phases_2, ...]. Reorder to the
142 % contiguous layout [idle(nmodes), phases(sum fK), fired(nmodes)]
143 % expected by afterGlobalEvent.m (space_buf = 1:nmodes,
144 % space_srv = nmodes+(1:sum fK)) and produced by the native-Python
145 % builder. The interleaved layout made afterGlobalEvent read the
146 % idle/phase counts of every mode after the first from the wrong
147 % columns, which collapsed the state space of any transition with
148 % more than one mode.
149 idle_cols = zeros(1, nmodes);
153 idle_cols(m) = off + 1;
154 phase_cols = [phase_cols, (off+2):(off+1+fK(m))]; %#ok<AGROW>
155 off = off + 1 + fK(m);
157 trans_space = trans_space(:, [idle_cols, phase_cols]);
158 trans_space = [trans_space, zeros(size(trans_space,1), nmodes)]; % append fired counts
159 state_var = State.spaceLocalVars(sn, ind);
160 sn.space{isf} = State.cartesian(trans_space, state_var);
161 continue; % skip fromMarginalBounds below
163 capacityc(ind,:) = 1; %
165 state_bufsrv = State.fromMarginalBounds(sn, ind, [], capacityc(ind,:), 1, options);
166 state_var = State.spaceLocalVars(sn, ind);
167 sn.space{isf} = State.cartesian(state_bufsrv,state_var); % generate all possible states for local variables
169 % Prune locally invalid cache states for retrieval-system caches. The
170 % unfiltered cartesian product enumerates states the dynamics can never
171 % reach -- an item simultaneously cached and being retrieved, more items
172 % in retrieval than the retrieval-system capacity, or more than one job
173 % in the cache server. Leaving them in pollutes the CTMC stationary
174 % distribution. The cross-node (cache <-> queue) consistency is enforced
175 % during global composition. Non-retrieval caches are left untouched.
176 if sn.nodetype(ind) == NodeType.Cache ...
177 && isfield(sn.nodeparam{ind},'retrievalSystemCapacity
') ...
178 && sn.nodeparam{ind}.retrievalSystemCapacity > 0
179 np = sn.nodeparam{ind};
181 tcc = np.totalCacheCapacity;
182 rsc = np.retrievalSystemCapacity;
183 lvs = size(state_bufsrv,2); % per-class server presence is columns 1..lvs
184 value = sn.space{isf};
185 keep = false(size(value,1),1);
186 for row = 1:size(value,1)
188 % at most one class job may sit in the cache server/buffer
189 validRow = sum(st(1:lvs)) <= 1;
192 cacheItems = st((lvs+1):(lvs+tcc));
193 for col = (lvs+tcc+1):size(st,2)
194 if st(col) == 0, continue; end
196 item = col - (lvs+tcc); % 1-based item id of the set bit
197 % a cached item cannot simultaneously be in the retrieval system
198 if any(cacheItems == item)
199 validRow = false; break;
202 % at most retrievalSystemCapacity items may be in retrieval at once
203 if validRow && nbits > rsc
207 keep(row) = validRow;
209 sn.space{isf} = value(keep,:);
213nodeStateSpace = sn.space;