1function [SS,SSh,sn,Adj,ST] = spaceGenerator(sn, cutoff, options)
2% SPACEGENERATOR Generate complete state space
for queueing network analysis
4% @brief Creates the complete state space including all possible network states
5% @param sn Network structure representing the queueing network
6% @param cutoff Population cutoff limits
for open classes (scalar or matrix)
7% @param options Optional configuration structure
for state generation
8% @
return SS Complete state space matrix
9% @
return SSh Hashed state space
for efficient lookups
10% @
return sn Updated network structure with state space information
11% @
return Adj Adjacency matrix
for state transitions (SPN support)
12% @
return ST State transition information (SPN support)
14% The state space generator creates all possible states
for the queueing
15% network, including those not reachable from the initial state. This
is
16% essential
for steady-state analysis and performance metric computation.
17% For open classes, a cutoff parameter limits the maximum population.
19% Copyright (c) 2012-2026, Imperial College London
29if ~exist('cutoff
','var
') && any(isinf(Np)) % if the model has open classes
30 line_error(mfilename,'Unspecified cutoff
for open classes in state space generator.
');
34 cutoff = cutoff * ones(sn.nstations, sn.nclasses);
37[~, sn, capacityc] = State.spaceGeneratorNodes(sn, cutoff, options);
40isOpenClass = isinf(Np);
41isClosedClass = ~isOpenClass;
42for r=1:sn.nclasses %cut-off open classes to finite capacity
44 Np(r) = max(capacityc(:,r)); % if replaced by sum stateMarg_i can exceed capacity
48nstatefulp = sn.nstateful - sum(sn.nodetype == NodeType.Source); % M without sources
50% see _kb/04-networkstruct.md (initDefault.m/spaceGenerator.m) for rationale
51if isfield(options,'ctmc_max_states
') && ~isempty(options.ctmc_max_states)
52 maxStates = options.ctmc_max_states;
60 % Cooperative wall-clock budget checkpoint (options.timeout): state-space
61 % enumeration can dominate the solve time, and a partial space would give
62 % silently wrong results, so exceeding the budget must abort the solve.
63 if nargin>=3 && lineTimeoutExceeded(options)
64 line_error(mfilename,'State space generation exceeded the wall-clock time budget (options.timeout=%gs).
', options.timeout);
66 % this is rather inefficient since n ignores chains and thus it can
67 % generate state spaces such as
89 % that are then in the need for a call to unique
90 if all(isOpenClass) | (Np(isClosedClass) == n(isClosedClass)) %#ok<OR2>
91 chainStationPos = [chainStationPos; State.spaceClosedMultiCS(nstatefulp,n,sn.chains)];
92 if size(chainStationPos,1) > maxStates
93 line_error(mfilename,'State space too large: population lattice exceeds ctmc_max_states=%g. Increase options.ctmc_max_states or use a different solver.
', maxStates);
99chainStationPos = unique(chainStationPos,'rows
');
101netstates = cell(size(chainStationPos,1), sn.nstateful);
102for j=1:size(chainStationPos,1)
103 % Cooperative wall-clock budget checkpoint: the per-marginal local state
104 % enumeration below (State.fromMarginal per node) dominates for large
105 % buffers, so the budget must also be enforced at this granularity.
106 if nargin>=3 && lineTimeoutExceeded(options)
107 line_error(mfilename,'State space generation exceeded the wall-clock time budget (options.timeout=%gs).
', options.timeout);
110 if sn.nodetype(ind) == NodeType.Source
111 isf = sn.nodeToStateful(ind);
112 state_i = State.fromMarginal(sn,ind,[]);
113 netstates{j,isf} = State.getHash(sn,ind,state_i);
114 elseif sn.isstation(ind)
115 isf = sn.nodeToStateful(ind);
116 stateMarg_i = chainStationPos(j,(isf-sum(sn.nodetype(1:ind-1) == NodeType.Source)):nstatefulp:end);
117 if any(stateMarg_i > capacityc(ind,:))
118 netstates{j,isf} = State.getHash(sn,ind,[]);
120 state_i = State.fromMarginal(sn,ind,stateMarg_i);
121 netstates{j,isf} = State.getHash(sn,ind,state_i);
123 elseif sn.isstateful(ind)
124 isf = sn.nodeToStateful(ind);
125 stateMarg_i = chainStationPos(j,(isf-sum(sn.nodetype(1:ind-1) == NodeType.Source)):nstatefulp:end);
126 state_i = sn.space{isf};
127 if any(stateMarg_i > capacityc(ind,:))
128 netstates{j,isf} = State.getHash(sn,ind,[]);
129 elseif sn.nodetype(ind) == NodeType.Cache
130 % For cache nodes, we need to handle states with jobs in multiple classes
131 % (InitClass, HitClass, MissClass) since cache nodes support class switching
132 state_i = state_i(findrows(state_i(:,1:length(stateMarg_i)),stateMarg_i),:);
133 np = sn.nodeparam{ind};
134 if isfield(np,'retrievalSystemCapacity
') && np.retrievalSystemCapacity > 0 ...
135 && isfield(np,'retrievalClasses
') && ~isempty(np.retrievalClasses)
136 % see _kb/04-networkstruct.md (initDefault.m/spaceGenerator.m) for rationale
137 rc = np.retrievalClasses;
138 tcc = np.totalCacheCapacity;
139 lvs = length(stateMarg_i); % per-class server presence width
140 rsqi = np.retrievalSystemQueueIndices;
141 itemsInQueue = []; % 1-based item ids currently at a queue
142 validMarginal = true;
145 for ak = 1:numel(aks)
146 arrivalClass = aks{ak}; % int32, 0-based arrival class
147 classCol = double(arrivalClass) + 1;
148 qNodes = rsqi(arrivalClass);
149 for qq = 1:numel(qNodes)
151 qOff = sn.nodeToStateful(qIdx) - sum(sn.nodetype(1:qIdx-1) == NodeType.Source);
152 for item = 1:size(rc,1)
153 if classCol > size(rc,2), continue; end
154 rClass = rc(item, classCol);
155 if rClass < 1, continue; end
156 col = qOff + (rClass-1)*nstatefulp;
157 if col > size(chainStationPos,2) || chainStationPos(j,col) == 0, continue; end
158 % item at a queue: no retrieval job may be at the cache server,
159 % and the item can be at only one queue
160 if stateMarg_i(rClass) > 0 || any(itemsInQueue == item)
161 validMarginal = false; break;
163 itemsInQueue(end+1) = item; %#ok<AGROW>
165 if ~validMarginal, break; end
167 if ~validMarginal, break; end
171 state_i = zeros(0, size(state_i,2));
173 keep = false(size(state_i,1),1);
174 for row = 1:size(state_i,1)
178 for col = (lvs+tcc+1):size(st,2)
179 if st(col) == 0, continue; end
180 item = col - (lvs+tcc);
181 for jac = 1:size(rc,2)
182 rClass = rc(item, jac);
183 if rClass < 1, continue; end
184 % item in the bitmap but not at a queue requires a
185 % retrieval job ready to depart/arrive at the cache server
186 if ~any(itemsInQueue == item) && st(rClass) == 0
187 validRow = false; break;
190 if ~validRow, break; end
191 itemsInRSstate(end+1) = item; %#ok<AGROW>
194 % every item at a queue must be recorded in the bitmap
195 for it = itemsInQueue
196 if ~any(itemsInRSstate == it)
197 validRow = false; break;
201 keep(row) = validRow;
203 state_i = state_i(keep,:);
206 netstates{j,isf} = State.getHash(sn,ind,state_i);
207 elseif sn.nodetype(ind) == NodeType.Transition
208 % Transition states are per-mode, not per-class; include all states
209 netstates{j,isf} = State.getHash(sn,ind,state_i);
211 state_i = state_i(findrows(state_i(:,1:length(stateMarg_i)),stateMarg_i),:);
212 netstates{j,isf} = State.getHash(sn,ind,state_i);
223for j=1:size(chainStationPos,1)
224 % for each network state
225 v = {netstates{j,:}};
227 vN = cellfun(@length,v)-1;
230 % Cooperative wall-clock budget checkpoint: this cartesian composition
231 % over the per-node state lattice is the combinatorial hot loop, so the
232 % budget is checked here too (amortized every 8192 iterations).
236 if nargin>=3 && lineTimeoutExceeded(options)
237 line_error(mfilename,'State space generation exceeded the wall-clock time budget (options.timeout=%gs).
', options.timeout);
243 h{isf} = v{isf}(1+n(isf));
248 u{isf} = sn.space{isf}(v{isf}(1+n(isf)),:);
251 ctr = ctr + 1; % do not move
253 line_error(mfilename,'State space too large: composed states exceed ctmc_max_states=%g. Increase options.ctmc_max_states or use a different solver.
', maxStates);
255 SS(ctr,:)=cell2mat(u);
256 SSh(ctr,:)=cell2mat(h);
261[SS,IA] = unique(SS,'rows
');