LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
fromMarginalAndStarted.m
1function space = fromMarginalAndStarted(sn, ind, n, s, options)
2% SPACE = FROMMARGINALANDSTARTED(QN, IND, N, S, OPTIONS)
3
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6
7if nargin<5 %~exist('options','var')
8 options.force = true;
9end
10if isa(sn,'Network')
11 sn = sn.getStruct();
12end
13% generate one initial state such that the marginal queue-lengths are as in vector n
14% n(r): number of jobs at the station in class r
15% s(r): jobs of class r that are running
16R = sn.nclasses;
17S = sn.nservers;
18
19% ind: node index
20ist = sn.nodeToStation(ind);
21%isf = sn.nodeToStateful(ind);
22if ~isnan(ist)
23 K = zeros(1,R);
24 for r=1:R
25 if isempty(sn.proc{ist}{r})
26 K(r) = 0;
27 else
28 K(r) = length(sn.proc{ist}{r}{1});
29 end
30 end
31else % node or stateful
32 if sn.nodetype(ind) == NodeType.Transition
33 K = zeros(1,sn.nodeparam{ind}.nmodes);
34 for m=1:sn.nodeparam{ind}.nmodes
35 if isempty(sn.nodeparam{ind}.firingproc{m})
36 K(m) = 0;
37 else
38 K(m) = length(sn.nodeparam{ind}.firingproc{m}{1});
39 end
40 end
41 end
42end
43
44if sn.isstation(ind) && any(sn.procid(ist,:)==ProcessType.MAP | sn.procid(ist,:)==ProcessType.MMPP2)
45 if sn.sched(ist) ~= SchedStrategy.FCFS && sn.nodetype(ind) ~= NodeType.Source
46 % Non-FCFS MAP/MMPP2 stations still get a valid marginal state (jobs in first phase) for sim backends; analytical solvers reject via feature set
47 end
48end
49
50state = [];
51space = [];
52if sn.isstation(ind) && any(n>sn.classcap(ist,:))
53 exceeded = n>sn.classcap(ist,:);
54 for r=find(exceeded)
55 if ~isempty(sn.proc) && ~isempty(sn.proc{ist}{r}) && any(any(isnan(sn.proc{ist}{r}{1})))
56 line_warning(mfilename,'State vector at station %d (n=%s) exceeds the class capacity (classcap=%s). Some service classes are disabled.\n',ist,mat2str(n(ist,:)),mat2str(sn.classcap(ist,:)));
57 else
58 line_warning(mfilename,'State vector at station %d (n=%s) exceeds the class capacity (classcap=%s).\n',ist,mat2str(n(ist,:)),mat2str(sn.classcap(ist,:)));
59 end
60 end
61 return
62end
63if sn.isstation(ind) && (sn.nservers(ist)>0 && sum(s) > sn.nservers(ist))
64 return
65end
66% generate local-state space
67switch sn.nodetype(ind)
68 case {NodeType.Queue, NodeType.Delay, NodeType.Source}
69 switch sn.sched(ist)
70 case SchedStrategy.EXT
71 for r=1:R
72 init = State.spaceClosedSingle(K(r),0);
73 if isinf(sn.njobs(r))
74 if ~isempty(sn.proc) && ~isempty(sn.proc{ist}{r}) && any(any(isnan(sn.proc{ist}{r}{1})))
75 init(1) = 0; % class is not processed at this source
76 else
77 % init the job generation
78 init(1) = 1;
79 end
80 end
81 state = State.cartesian(state,init);
82 end
83 space = State.cartesian(space,state);
84 space = [Inf*ones(size(space,1),1),space];
85 case {SchedStrategy.INF, SchedStrategy.PS, SchedStrategy.DPS, SchedStrategy.GPS, SchedStrategy.PSPRIO, SchedStrategy.DPSPRIO, SchedStrategy.GPSPRIO, SchedStrategy.LPS}
86 % in these policies we only track the jobs in the servers
87 for r=1:R
88 init = State.spaceClosedSingle(K(r),0);
89 init(1) = n(r);
90 state = State.cartesian(state,init);
91 end
92 space = State.cartesian(space,state);
93 case {SchedStrategy.SIRO, SchedStrategy.LEPT, SchedStrategy.SEPT, SchedStrategy.SRPT, SchedStrategy.SRPTPRIO, SchedStrategy.PSJF, SchedStrategy.FB, SchedStrategy.LRPT, SchedStrategy.POLLING, SchedStrategy.SETF, SchedStrategy.FSP}
94 % Unordered buffer + in-server jobs -- see _kb/04-networkstruct.md
95 % build list of job classes in the node, with repetition
96 if sum(n) <= S(ist)
97 for r=1:R
98 init = State.spaceClosedSingle(K(r),0);
99 init(1) = n(r);
100 state = State.cartesian(state,init);
101 end
102 space = State.cartesian(space,[zeros(size(state,1),R),state]);
103 else
104 % si = multichoosecon(n,S(i)); % jobs of class r that are running
105 si = s;
106 mi_buf = repmat(n,size(si,1),1) - si; % jobs of class r in buffer
107 for k=1:size(si,1)
108 % determine number of classes r jobs running in phase j
109 kstate=[];
110 for r=1:R
111 init = State.spaceClosedSingle(K(r),0);
112 init(1) = si(k,r);
113 kstate = State.cartesian(kstate,init);
114 end
115 state = [repmat(mi_buf(k,:),size(kstate,1),1), kstate];
116 space = [space; state];
117 end
118 end
119 case {SchedStrategy.FCFS, SchedStrategy.HOL,SchedStrategy.FCFSPRIO, SchedStrategy.LCFS,SchedStrategy.LCFSPRIO, SchedStrategy.EDD}
120 if sum(n) == 0
121 space = zeros(1,1+max(R,sum(K)));
122 return
123 end
124 % Ordered buffer + in-server jobs -- see _kb/04-networkstruct.md
125
126 % build list of job classes in the buffer, with repetition
127 inbuf = [];
128 for r=1:R
129 if n(r)>0
130 inbuf=[inbuf, r*ones(1,n(r)-s(r))];
131 end
132 end
133
134 sizeEstimator = multinomialln(n);
135 sizeEstimator = round(sizeEstimator/log(10));
136 if sizeEstimator > 2
137 if ~isfield(options,'force') || options.force == false
138 line_warning(sprintf('State space size is very large: 1e%d states. Cannot generate valid state space. Initializing station $d from a default state.\n',sizeEstimator,ind));
139 state = inbuf;
140 return
141 end
142 end
143
144 % gen permutation of their positions in the fcfs buffer
145 mi = uniqueperms(inbuf);
146 if isempty(mi)
147 mi_buf = zeros(1,max(1,sum(n)-S(ist)));
148 state = zeros(1,sum(K));
149 state = [mi_buf,state];
150 else
151 % mi_buf: class of job in buffer position i (0=empty)
152 if sum(n)>sum(s)
153 mi_buf = mi(:,1:(sum(n)-sum(s)));
154 else % set an empty buffer
155 mi_buf = 0;
156 end
157 end
158 % mi_srv: class of jobs running in the server of i
159 mi_srv = [];
160 for r=1:R
161 mi_srv = [mi_srv, r*ones(1,s(r))];
162 end
163 % si: number of class r jobs that are running
164 si = s;
165 %si = unique(si,'rows');
166 for b=1:size(mi_buf,1)
167 for k=1:size(si,1)
168 % determine number of classs r jobs running in phase
169 % j in server state mi_srv(kjs,:) and build
170 % state
171 kstate=[];
172 for r=1:R
173 % kstate = State.cartesian(kstate,State.spaceClosedSingle(K(r),si(k,r)));
174 init = State.spaceClosedSingle(K(r),0);
175 init(1) = si(k,r);
176 kstate = State.cartesian(kstate,init);
177 end
178 state = [state; repmat(mi_buf(b,:),size(kstate,1),1), kstate];
179 end
180 end
181 space = state;
182 case {SchedStrategy.FCFSPR, SchedStrategy.FCFSPI, SchedStrategy.FCFSPRPRIO, SchedStrategy.FCFSPIPRIO, SchedStrategy.LCFSPR, SchedStrategy.LCFSPI, SchedStrategy.LCFSPRPRIO, SchedStrategy.LCFSPIPRIO, SchedStrategy.EDF}
183 %% TODO
184 if sum(n) == 0
185 % Preempt-resume buffer is (class,phase) pairs, must be even-width -- see _kb/04-networkstruct.md
186 space = zeros(1,2+sum(K));
187 return
188 end
189 % Ordered buffer + in-server jobs -- see _kb/04-networkstruct.md
190
191 % build list of job classes in the buffer, with repetition
192 inbuf = [];
193 for r=1:R
194 if n(r)>0
195 inbuf=[inbuf, r*ones(1,n(r)-s(r))];
196 end
197 end
198
199 sizeEstimator = multinomialln(n);
200 sizeEstimator = round(sizeEstimator/log(10));
201 if sizeEstimator > 2
202 if ~isfield(options,'force') || options.force == false
203 line_warning(sprintf('State space size is very large: 1e%d states. Cannot generate valid state space. Initializing station $d from a default state.\n',sizeEstimator,ind));
204 state = inbuf;
205 return
206 end
207 end
208
209 % gen permutation of their positions in the fcfs buffer
210 mi = uniqueperms(inbuf);
211 if isempty(mi)
212 % Empty buffer: single placeholder slot, built by the SAME interleaved (class,phase) loop below (do not pre-seed, mixes widths)
213 mi_buf = zeros(1,max(1,sum(n)-S(ist)));
214 else
215 % mi_buf: class of job in buffer position i (0=empty)
216 if sum(n)>sum(s)
217 mi_buf = mi(:,1:(sum(n)-sum(s)));
218 else % set an empty buffer
219 mi_buf = 0;
220 end
221 end
222
223 % mi_srv: class of jobs running in the server of i
224 mi_srv = [];
225 for r=1:R
226 mi_srv = [mi_srv, r*ones(1,s(r))];
227 end
228 % si: number of class r jobs that are running
229 si = s;
230 %si = unique(si,'rows');
231 for b=1:size(mi_buf,1)
232 for k=1:size(si,1)
233 % determine number of class r jobs running in phase
234 % j in server state mi_srv(kjs,:) and build
235 % state
236 kstate=[];
237 for r=1:R
238 % kstate = State.cartesian(kstate,State.spaceClosedSingle(K(r),si(k,r)));
239 init = State.spaceClosedSingle(K(r),0);
240 init(1) = si(k,r);
241 kstate = State.cartesian(kstate,init);
242 end
243
244 bkstate = [];
245 for j=mi_buf(b,:) % for each job in the buffer
246 if j>0
247 bkstate = State.cartesian(bkstate,[1:K(j)]');
248 else
249 % empty buffer slot: phase placeholder 0, but keep
250 % accumulating (do not reset) so mixed buffers keep
251 % one phase column per slot.
252 bkstate = State.cartesian(bkstate,0);
253 end
254 end
255 bufstate_tmp = State.cartesian(mi_buf(b,:), bkstate);
256 % here interleave positions of class and phases in
257 % buf
258 bufstate = zeros(size(bufstate_tmp));
259 bufstate(:,1:2:end)=bufstate_tmp(:,1:size(mi_buf,2));
260 bufstate(:,2:2:end)=bufstate_tmp(:,(size(mi_buf,2)+1):end);
261 state = [state; State.cartesian(bufstate, kstate)];
262 end
263 end
264 space = state;
265 case SchedStrategy.PAS
266 % PAS/OI: local state is the ordered class-index list; "started" counts s are immaterial -- see _kb/04-networkstruct.md
267 W = sn.cap(ist);
268 if isinf(W)
269 line_error(mfilename,'PAS stations require finite capacity for state-space generation.');
270 end
271 if sum(n) == 0
272 space = zeros(1, W);
273 elseif sum(n) > W
274 space = zeros(0, W); % infeasible: exceeds total capacity
275 else
276 vi = [];
277 for r=1:R
278 if n(r)>0
279 vi=[vi, r*ones(1,n(r))];
280 end
281 end
282 mi = uniqueperms(vi);
283 space = [mi, zeros(size(mi,1), W - size(mi,2))];
284 end
285 case {SchedStrategy.SJF, SchedStrategy.LJF}
286 % in these policies the state space includes continuous
287 % random variables for the service times
288 % in these policies we only track the jobs in the servers
289
290 for r=1:R
291 init = State.spaceClosedSingle(K(r),0);
292 init(1) = n(r);
293 state = State.cartesian(state,init);
294 end
295 space = State.cartesian(space,state);
296 % this is not casted as an error since this function is
297 % IS
298
299 % called to initial models with SJF and LJF
300 line_warning(mfilename,'The scheduling policy does not admit a discrete state space.\n');
301 end
302 % True BAS blocked marker: pin blocked=0 (an initial state has no held job); must still be appended -- see _kb/04-networkstruct.md
303 % Col 2*R+1 is shared with the polling controller, so gate on the dedicated
304 % sn.isbasblocking field (set for the blocking station under BOTH declaration
305 % forms) rather than the station's own drop rule, which fails for a
306 % destination-declared BAS. Mirrors State.fromMarginal. See BUG-83.
307 if ~isempty(sn.isbasblocking) && numel(sn.isbasblocking) >= ind ...
308 && sn.isbasblocking(ind) == 1 && ~isempty(space)
309 space = State.cartesian(space, 0);
310 end
311 case NodeType.Cache
312 switch sn.sched(ist)
313 case SchedStrategy.INF
314 % in this policies we only track the jobs in the servers
315 for r=1:R
316 init = State.spaceClosedSingle(K(r),n(r));
317 state = State.cartesian(state,init);
318 end
319 space = State.cartesian(space,state);
320 end
321 case NodeType.Join
322 if isfield(sn,'isfjaugmented') && sn.isfjaugmented
323 % FJ-augmented struct: join state is the per-class buffered-jobs count, deterministic given the marginals
324 space = n(:)';
325 else
326 space = 0;
327 end
328 case NodeType.Transition
329 line_error(mfilename, 'fromMarginalAndStarted cannot be used on Petri net elements');
330 case NodeType.Place
331 line_error(mfilename, 'fromMarginalAndStarted cannot be used on Petri net elements');
332end
333space = unique(space,'rows'); % do not comment, required to sort empty state as first
334space = space(end:-1:1,:); % this ensures that states where jobs start in phase 1 are first, which is used eg in SSA
335end