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 (e.g. PS/INF) cannot be represented in
47 % the ordered-buffer state used by exact Markovian solvers, but a valid
48 % marginal state is still constructed below (all jobs entered in the
49 % first service phase) so that getStruct/initDefault succeeds for
50 % simulation backends (JMT, LDES) and for solvers that do not rely on
51 % the MAP phase ordering. Analytical solvers that cannot handle a
52 % non-FCFS MAP reject the model through their feature set. This mirrors
53 % the JAR, where the simulators build the model without this guard.
54 end
55end
56
57state = [];
58space = [];
59if sn.isstation(ind) && any(n>sn.classcap(ist,:))
60 exceeded = n>sn.classcap(ist,:);
61 for r=find(exceeded)
62 if ~isempty(sn.proc) && ~isempty(sn.proc{ist}{r}) && any(any(isnan(sn.proc{ist}{r}{1})))
63 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,:)));
64 else
65 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,:)));
66 end
67 end
68 return
69end
70if sn.isstation(ind) && (sn.nservers(ist)>0 && sum(s) > sn.nservers(ist))
71 return
72end
73% generate local-state space
74switch sn.nodetype(ind)
75 case {NodeType.Queue, NodeType.Delay, NodeType.Source}
76 switch sn.sched(ist)
77 case SchedStrategy.EXT
78 for r=1:R
79 init = State.spaceClosedSingle(K(r),0);
80 if isinf(sn.njobs(r))
81 if ~isempty(sn.proc) && ~isempty(sn.proc{ist}{r}) && any(any(isnan(sn.proc{ist}{r}{1})))
82 init(1) = 0; % class is not processed at this source
83 else
84 % init the job generation
85 init(1) = 1;
86 end
87 end
88 state = State.cartesian(state,init);
89 end
90 space = State.cartesian(space,state);
91 space = [Inf*ones(size(space,1),1),space];
92 case {SchedStrategy.INF, SchedStrategy.PS, SchedStrategy.DPS, SchedStrategy.GPS, SchedStrategy.PSPRIO, SchedStrategy.DPSPRIO, SchedStrategy.GPSPRIO, SchedStrategy.LPS}
93 % in these policies we only track the jobs in the servers
94 for r=1:R
95 init = State.spaceClosedSingle(K(r),0);
96 init(1) = n(r);
97 state = State.cartesian(state,init);
98 end
99 space = State.cartesian(space,state);
100 case {SchedStrategy.SIRO, SchedStrategy.LEPT, SchedStrategy.SEPT, SchedStrategy.SRPT, SchedStrategy.SRPTPRIO, SchedStrategy.PSJF, SchedStrategy.FB, SchedStrategy.LRPT, SchedStrategy.POLLING, SchedStrategy.SETF, SchedStrategy.FSP}
101 % in these policies we track an un-ordered buffer and
102 % the jobs in the servers
103 % build list of job classes in the node, with repetition
104 if sum(n) <= S(ist)
105 for r=1:R
106 init = State.spaceClosedSingle(K(r),0);
107 init(1) = n(r);
108 state = State.cartesian(state,init);
109 end
110 space = State.cartesian(space,[zeros(size(state,1),R),state]);
111 else
112 % si = multichoosecon(n,S(i)); % jobs of class r that are running
113 si = s;
114 mi_buf = repmat(n,size(si,1),1) - si; % jobs of class r in buffer
115 for k=1:size(si,1)
116 % determine number of classes r jobs running in phase j
117 kstate=[];
118 for r=1:R
119 init = State.spaceClosedSingle(K(r),0);
120 init(1) = si(k,r);
121 kstate = State.cartesian(kstate,init);
122 end
123 state = [repmat(mi_buf(k,:),size(kstate,1),1), kstate];
124 space = [space; state];
125 end
126 end
127 case {SchedStrategy.FCFS, SchedStrategy.HOL,SchedStrategy.FCFSPRIO, SchedStrategy.LCFS,SchedStrategy.LCFSPRIO, SchedStrategy.EDD}
128 if sum(n) == 0
129 space = zeros(1,1+max(R,sum(K)));
130 return
131 end
132 % in these policies we track an ordered buffer and
133 % the jobs in the servers
134
135 % build list of job classes in the buffer, with repetition
136 inbuf = [];
137 for r=1:R
138 if n(r)>0
139 inbuf=[inbuf, r*ones(1,n(r)-s(r))];
140 end
141 end
142
143 sizeEstimator = multinomialln(n);
144 sizeEstimator = round(sizeEstimator/log(10));
145 if sizeEstimator > 2
146 if ~isfield(options,'force') || options.force == false
147 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));
148 state = inbuf;
149 return
150 end
151 end
152
153 % gen permutation of their positions in the fcfs buffer
154 mi = uniqueperms(inbuf);
155 if isempty(mi)
156 mi_buf = zeros(1,max(1,sum(n)-S(ist)));
157 state = zeros(1,sum(K));
158 state = [mi_buf,state];
159 else
160 % mi_buf: class of job in buffer position i (0=empty)
161 if sum(n)>sum(s)
162 mi_buf = mi(:,1:(sum(n)-sum(s)));
163 else % set an empty buffer
164 mi_buf = 0;
165 end
166 end
167 % mi_srv: class of jobs running in the server of i
168 mi_srv = [];
169 for r=1:R
170 mi_srv = [mi_srv, r*ones(1,s(r))];
171 end
172 % si: number of class r jobs that are running
173 si = s;
174 %si = unique(si,'rows');
175 for b=1:size(mi_buf,1)
176 for k=1:size(si,1)
177 % determine number of classs r jobs running in phase
178 % j in server state mi_srv(kjs,:) and build
179 % state
180 kstate=[];
181 for r=1:R
182 % kstate = State.cartesian(kstate,State.spaceClosedSingle(K(r),si(k,r)));
183 init = State.spaceClosedSingle(K(r),0);
184 init(1) = si(k,r);
185 kstate = State.cartesian(kstate,init);
186 end
187 state = [state; repmat(mi_buf(b,:),size(kstate,1),1), kstate];
188 end
189 end
190 space = state;
191 case {SchedStrategy.FCFSPR, SchedStrategy.FCFSPI, SchedStrategy.FCFSPRPRIO, SchedStrategy.FCFSPIPRIO, SchedStrategy.LCFSPR, SchedStrategy.LCFSPI, SchedStrategy.LCFSPRPRIO, SchedStrategy.LCFSPIPRIO, SchedStrategy.EDF}
192 %% TODO
193 if sum(n) == 0
194 % Preempt-resume/independent track the buffer as (class,phase)
195 % PAIRS (2 columns per waiting job), so the buffer must be
196 % even-width. A lone extra column is half a pair: on the first
197 % preemption the simulator cannot store the pair and drops the
198 % job (server left idle with a job "waiting"), losing jobs. Use
199 % one empty pair (width 2), matching JAR/native-Python.
200 space = zeros(1,2+sum(K));
201 return
202 end
203 % in these policies we track an ordered buffer and
204 % the jobs in the servers
205
206 % build list of job classes in the buffer, with repetition
207 inbuf = [];
208 for r=1:R
209 if n(r)>0
210 inbuf=[inbuf, r*ones(1,n(r)-s(r))];
211 end
212 end
213
214 sizeEstimator = multinomialln(n);
215 sizeEstimator = round(sizeEstimator/log(10));
216 if sizeEstimator > 2
217 if ~isfield(options,'force') || options.force == false
218 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));
219 state = inbuf;
220 return
221 end
222 end
223
224 % gen permutation of their positions in the fcfs buffer
225 mi = uniqueperms(inbuf);
226 if isempty(mi)
227 % buffer is empty (all present jobs are in service): a single
228 % placeholder slot is used, but the row is built by the loop
229 % below in the SAME interleaved (class,phase) encoding as the
230 % occupied case -- do NOT pre-seed a non-interleaved row here,
231 % otherwise vertcat at the end mixes widths L+sum(K) and 2L+sum(K).
232 mi_buf = zeros(1,max(1,sum(n)-S(ist)));
233 else
234 % mi_buf: class of job in buffer position i (0=empty)
235 if sum(n)>sum(s)
236 mi_buf = mi(:,1:(sum(n)-sum(s)));
237 else % set an empty buffer
238 mi_buf = 0;
239 end
240 end
241
242 % mi_srv: class of jobs running in the server of i
243 mi_srv = [];
244 for r=1:R
245 mi_srv = [mi_srv, r*ones(1,s(r))];
246 end
247 % si: number of class r jobs that are running
248 si = s;
249 %si = unique(si,'rows');
250 for b=1:size(mi_buf,1)
251 for k=1:size(si,1)
252 % determine number of class r jobs running in phase
253 % j in server state mi_srv(kjs,:) and build
254 % state
255 kstate=[];
256 for r=1:R
257 % kstate = State.cartesian(kstate,State.spaceClosedSingle(K(r),si(k,r)));
258 init = State.spaceClosedSingle(K(r),0);
259 init(1) = si(k,r);
260 kstate = State.cartesian(kstate,init);
261 end
262
263 bkstate = [];
264 for j=mi_buf(b,:) % for each job in the buffer
265 if j>0
266 bkstate = State.cartesian(bkstate,[1:K(j)]');
267 else
268 % empty buffer slot: phase placeholder 0, but keep
269 % accumulating (do not reset) so mixed buffers keep
270 % one phase column per slot.
271 bkstate = State.cartesian(bkstate,0);
272 end
273 end
274 bufstate_tmp = State.cartesian(mi_buf(b,:), bkstate);
275 % here interleave positions of class and phases in
276 % buf
277 bufstate = zeros(size(bufstate_tmp));
278 bufstate(:,1:2:end)=bufstate_tmp(:,1:size(mi_buf,2));
279 bufstate(:,2:2:end)=bufstate_tmp(:,(size(mi_buf,2)+1):end);
280 state = [state; State.cartesian(bufstate, kstate)];
281 end
282 end
283 space = state;
284 case SchedStrategy.PAS
285 % Pass-and-swap / order-independent queue: the local state is the
286 % full ordered list of class indices (oldest at column 1),
287 % left-aligned and right zero-padded to the station capacity.
288 % The "started" counts s are immaterial (no server/buffer split).
289 W = sn.cap(ist);
290 if isinf(W)
291 line_error(mfilename,'PAS stations require finite capacity for state-space generation.');
292 end
293 if sum(n) == 0
294 space = zeros(1, W);
295 elseif sum(n) > W
296 space = zeros(0, W); % infeasible: exceeds total capacity
297 else
298 vi = [];
299 for r=1:R
300 if n(r)>0
301 vi=[vi, r*ones(1,n(r))];
302 end
303 end
304 mi = uniqueperms(vi);
305 space = [mi, zeros(size(mi,1), W - size(mi,2))];
306 end
307 case {SchedStrategy.SJF, SchedStrategy.LJF}
308 % in these policies the state space includes continuous
309 % random variables for the service times
310 % in these policies we only track the jobs in the servers
311
312 for r=1:R
313 init = State.spaceClosedSingle(K(r),0);
314 init(1) = n(r);
315 state = State.cartesian(state,init);
316 end
317 space = State.cartesian(space,state);
318 % this is not casted as an error since this function is
319 % IS
320
321 % called to initial models with SJF and LJF
322 line_warning(mfilename,'The scheduling policy does not admit a discrete state space.\n');
323 end
324 % True BAS blocked marker (nvars col 2*R+1 == 1): pin blocked=0. This function
325 % returns an initial state, where no completed job is yet held at the server;
326 % State.fromMarginal enumerates {0,1} for the full state space. The column must
327 % be appended here too, otherwise the state is one column narrower than sn.nvars
328 % declares and State.toMarginal misreads the buffer, losing jobs.
329 % Col 2*R+1 is shared with the polling controller, so gate on the dedicated
330 % sn.isbasblocking field (set for the blocking station under BOTH declaration
331 % forms) rather than the station's own drop rule, which fails for a
332 % destination-declared BAS. Mirrors State.fromMarginal. See BUG-83.
333 if ~isempty(sn.isbasblocking) && numel(sn.isbasblocking) >= ind ...
334 && sn.isbasblocking(ind) == 1 && ~isempty(space)
335 space = State.cartesian(space, 0);
336 end
337 case NodeType.Cache
338 switch sn.sched(ist)
339 case SchedStrategy.INF
340 % in this policies we only track the jobs in the servers
341 for r=1:R
342 init = State.spaceClosedSingle(K(r),n(r));
343 state = State.cartesian(state,init);
344 end
345 space = State.cartesian(space,state);
346 end
347 case NodeType.Join
348 if isfield(sn,'isfjaugmented') && sn.isfjaugmented
349 % FJ-augmented struct: the join state is the per-class count
350 % vector of buffered jobs/siblings, deterministic given the
351 % marginals (the started counts s are immaterial, no service)
352 space = n(:)';
353 else
354 space = 0;
355 end
356 case NodeType.Transition
357 line_error(mfilename, 'fromMarginalAndStarted cannot be used on Petri net elements');
358 case NodeType.Place
359 line_error(mfilename, 'fromMarginalAndStarted cannot be used on Petri net elements');
360end
361space = unique(space,'rows'); % do not comment, required to sort empty state as first
362space = space(end:-1:1,:); % this ensures that states where jobs start in phase 1 are first, which is used eg in SSA
363end
Definition Station.m:245