LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
spaceGenerator.m
1function [SS,SSh,sn,Adj,ST] = spaceGenerator(sn, cutoff, options)
2% SPACEGENERATOR Generate complete state space for queueing network analysis
3%
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)
13%
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.
18%
19% Copyright (c) 2012-2026, Imperial College London
20% All rights reserved.
21N = sn.njobs';
22Np = N;
23
24% Draft SPN support
25Adj = [];
26ST = [];
27
28%%
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.');
31end
32
33if isscalar(cutoff)
34 cutoff = cutoff * ones(sn.nstations, sn.nclasses);
35end
36
37[~, sn, capacityc] = State.spaceGeneratorNodes(sn, cutoff, options);
38
39%%
40isOpenClass = isinf(Np);
41isClosedClass = ~isOpenClass;
42for r=1:sn.nclasses %cut-off open classes to finite capacity
43 if isOpenClass(r)
44 Np(r) = max(capacityc(:,r)); % if replaced by sum stateMarg_i can exceed capacity
45 end
46end
47
48nstatefulp = sn.nstateful - sum(sn.nodetype == NodeType.Source); % M without sources
49
50% Memory guard (analog of Python options.ctmc_max_states, default 3e6): an
51% unbounded state space (e.g. finite-capacity regions whose constraints are
52% applied only after composition) can enumerate to tens of GB and OOM-kill the
53% process. Abort with a recoverable error so an enclosing solver chain
54% (json_solve MVA->MAM->CTMC->LDES) falls through instead of crashing.
55if isfield(options,'ctmc_max_states') && ~isempty(options.ctmc_max_states)
56 maxStates = options.ctmc_max_states;
57else
58 maxStates = 3e6;
59end
60
61n = pprod(Np);
62chainStationPos=[];
63while n>=0
64 % Cooperative wall-clock budget checkpoint (options.timeout): state-space
65 % enumeration can dominate the solve time, and a partial space would give
66 % silently wrong results, so exceeding the budget must abort the solve.
67 if nargin>=3 && lineTimeoutExceeded(options)
68 line_error(mfilename,'State space generation exceeded the wall-clock time budget (options.timeout=%gs).', options.timeout);
69 end
70 % this is rather inefficient since n ignores chains and thus it can
71 % generate state spaces such as
72 % J =
73 %
74 % 0 0 0 0
75 % 0 0 0 1
76 % 0 0 1 0
77 % 0 1 0 0
78 % 1 0 0 0
79 % 0 0 0 1
80 % 0 0 1 0
81 % 0 1 0 0
82 % 1 0 0 0
83 % 0 0 0 2
84 % 0 0 1 1
85 % 0 0 2 0
86 % 0 1 0 1
87 % 1 0 0 1
88 % 0 1 1 0
89 % 1 0 1 0
90 % 0 2 0 0
91 % 1 1 0 0
92 % 2 0 0 0
93 % that are then in the need for a call to unique
94 if all(isOpenClass) | (Np(isClosedClass) == n(isClosedClass)) %#ok<OR2>
95 chainStationPos = [chainStationPos; State.spaceClosedMultiCS(nstatefulp,n,sn.chains)];
96 if size(chainStationPos,1) > maxStates
97 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);
98 end
99 end
100 n = pprod(n,Np);
101end
102
103chainStationPos = unique(chainStationPos,'rows');
104
105netstates = cell(size(chainStationPos,1), sn.nstateful);
106for j=1:size(chainStationPos,1)
107 % Cooperative wall-clock budget checkpoint: the per-marginal local state
108 % enumeration below (State.fromMarginal per node) dominates for large
109 % buffers, so the budget must also be enforced at this granularity.
110 if nargin>=3 && lineTimeoutExceeded(options)
111 line_error(mfilename,'State space generation exceeded the wall-clock time budget (options.timeout=%gs).', options.timeout);
112 end
113 for ind=1:sn.nnodes
114 if sn.nodetype(ind) == NodeType.Source
115 isf = sn.nodeToStateful(ind);
116 state_i = State.fromMarginal(sn,ind,[]);
117 netstates{j,isf} = State.getHash(sn,ind,state_i);
118 elseif sn.isstation(ind)
119 isf = sn.nodeToStateful(ind);
120 stateMarg_i = chainStationPos(j,(isf-sum(sn.nodetype(1:ind-1) == NodeType.Source)):nstatefulp:end);
121 if any(stateMarg_i > capacityc(ind,:))
122 netstates{j,isf} = State.getHash(sn,ind,[]);
123 else
124 state_i = State.fromMarginal(sn,ind,stateMarg_i);
125 netstates{j,isf} = State.getHash(sn,ind,state_i);
126 end
127 elseif sn.isstateful(ind)
128 isf = sn.nodeToStateful(ind);
129 stateMarg_i = chainStationPos(j,(isf-sum(sn.nodetype(1:ind-1) == NodeType.Source)):nstatefulp:end);
130 state_i = sn.space{isf};
131 if any(stateMarg_i > capacityc(ind,:))
132 netstates{j,isf} = State.getHash(sn,ind,[]);
133 elseif sn.nodetype(ind) == NodeType.Cache
134 % For cache nodes, we need to handle states with jobs in multiple classes
135 % (InitClass, HitClass, MissClass) since cache nodes support class switching
136 state_i = state_i(findrows(state_i(:,1:length(stateMarg_i)),stateMarg_i),:);
137 np = sn.nodeparam{ind};
138 if isfield(np,'retrievalSystemCapacity') && np.retrievalSystemCapacity > 0 ...
139 && isfield(np,'retrievalClasses') && ~isempty(np.retrievalClasses)
140 % Cross-node (cache <-> queue) consistency: an item recorded in the
141 % cache occupancy bitmap must correspond to a retrieval job that is
142 % either at a retrieval queue or ready to depart/arrive at the cache
143 % server, and an item being retrieved at a queue cannot also have a
144 % retrieval job sitting in the cache server. Enforced at the global
145 % composition (the local cache space alone cannot see the queues).
146 rc = np.retrievalClasses;
147 tcc = np.totalCacheCapacity;
148 lvs = length(stateMarg_i); % per-class server presence width
149 rsqi = np.retrievalSystemQueueIndices;
150 itemsInQueue = []; % 1-based item ids currently at a queue
151 validMarginal = true;
152 if ~isempty(rsqi)
153 aks = keys(rsqi);
154 for ak = 1:numel(aks)
155 arrivalClass = aks{ak}; % int32, 0-based arrival class
156 classCol = double(arrivalClass) + 1;
157 qNodes = rsqi(arrivalClass);
158 for qq = 1:numel(qNodes)
159 qIdx = qNodes(qq);
160 qOff = sn.nodeToStateful(qIdx) - sum(sn.nodetype(1:qIdx-1) == NodeType.Source);
161 for item = 1:size(rc,1)
162 if classCol > size(rc,2), continue; end
163 rClass = rc(item, classCol);
164 if rClass < 1, continue; end
165 col = qOff + (rClass-1)*nstatefulp;
166 if col > size(chainStationPos,2) || chainStationPos(j,col) == 0, continue; end
167 % item at a queue: no retrieval job may be at the cache server,
168 % and the item can be at only one queue
169 if stateMarg_i(rClass) > 0 || any(itemsInQueue == item)
170 validMarginal = false; break;
171 end
172 itemsInQueue(end+1) = item; %#ok<AGROW>
173 end
174 if ~validMarginal, break; end
175 end
176 if ~validMarginal, break; end
177 end
178 end
179 if ~validMarginal
180 state_i = zeros(0, size(state_i,2));
181 else
182 keep = false(size(state_i,1),1);
183 for row = 1:size(state_i,1)
184 st = state_i(row,:);
185 validRow = true;
186 itemsInRSstate = [];
187 for col = (lvs+tcc+1):size(st,2)
188 if st(col) == 0, continue; end
189 item = col - (lvs+tcc);
190 for jac = 1:size(rc,2)
191 rClass = rc(item, jac);
192 if rClass < 1, continue; end
193 % item in the bitmap but not at a queue requires a
194 % retrieval job ready to depart/arrive at the cache server
195 if ~any(itemsInQueue == item) && st(rClass) == 0
196 validRow = false; break;
197 end
198 end
199 if ~validRow, break; end
200 itemsInRSstate(end+1) = item; %#ok<AGROW>
201 end
202 if validRow
203 % every item at a queue must be recorded in the bitmap
204 for it = itemsInQueue
205 if ~any(itemsInRSstate == it)
206 validRow = false; break;
207 end
208 end
209 end
210 keep(row) = validRow;
211 end
212 state_i = state_i(keep,:);
213 end
214 end
215 netstates{j,isf} = State.getHash(sn,ind,state_i);
216 elseif sn.nodetype(ind) == NodeType.Transition
217 % Transition states are per-mode, not per-class; include all states
218 netstates{j,isf} = State.getHash(sn,ind,state_i);
219 else
220 state_i = state_i(findrows(state_i(:,1:length(stateMarg_i)),stateMarg_i),:);
221 netstates{j,isf} = State.getHash(sn,ind,state_i);
222 end
223 end
224 end
225end
226
227ctr = 0;
228%SS = sparse([]);
229SS = [];
230SSh = [];
231tochk = 0;
232for j=1:size(chainStationPos,1)
233 % for each network state
234 v = {netstates{j,:}};
235 % cycle over lattice
236 vN = cellfun(@length,v)-1;
237 n = pprod(vN);
238 while n >=0
239 % Cooperative wall-clock budget checkpoint: this cartesian composition
240 % over the per-node state lattice is the combinatorial hot loop, so the
241 % budget is checked here too (amortized every 8192 iterations).
242 tochk = tochk + 1;
243 if tochk >= 1024
244 tochk = 0;
245 if nargin>=3 && lineTimeoutExceeded(options)
246 line_error(mfilename,'State space generation exceeded the wall-clock time budget (options.timeout=%gs).', options.timeout);
247 end
248 end
249 u={}; h={};
250 skip = false;
251 for isf=1:length(n)
252 h{isf} = v{isf}(1+n(isf));
253 if h{isf} < 0
254 skip = true;
255 break
256 end
257 u{isf} = sn.space{isf}(v{isf}(1+n(isf)),:);
258 end
259 if skip == false
260 ctr = ctr + 1; % do not move
261 if ctr > maxStates
262 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);
263 end
264 SS(ctr,:)=cell2mat(u);
265 SSh(ctr,:)=cell2mat(h);
266 end
267 n = pprod(n,vN);
268 end
269end
270[SS,IA] = unique(SS,'rows');
271SSh = SSh(IA,:);
272end
Definition Station.m:245