1function space = fromMarginalAndStarted(sn, ind, n, s, options)
2% SPACE = FROMMARGINALANDSTARTED(QN, IND, N, S, OPTIONS)
4% Copyright (c) 2012-2026, Imperial College London
7if nargin<5 %~exist(
'options',
'var')
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
20ist = sn.nodeToStation(ind);
21%isf = sn.nodeToStateful(ind);
25 if isempty(sn.proc{ist}{r})
28 K(r) = length(sn.proc{ist}{r}{1});
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})
38 K(m) = length(sn.nodeparam{ind}.firingproc{m}{1});
46if sn.isstation(ind) && any(n>sn.classcap(ist,:))
47 exceeded = n>sn.classcap(ist,:);
49 if ~isempty(sn.proc) && ~isempty(sn.proc{ist}{r}) && any(any(isnan(sn.proc{ist}{r}{1})))
50 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,:)));
52 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,:)));
57if sn.isstation(ind) && (sn.nservers(ist)>0 && sum(s) > sn.nservers(ist))
60% generate local-state space
61switch sn.nodetype(ind)
62 case {NodeType.Queue, NodeType.Delay, NodeType.Source}
64 case SchedStrategy.EXT
66 init = State.spaceClosedSingle(K(r),0);
68 if ~isempty(sn.proc) && ~isempty(sn.proc{ist}{r}) && any(any(isnan(sn.proc{ist}{r}{1})))
69 init(1) = 0; % class
is not processed at this source
71 % init the job generation
75 state = State.cartesian(state,init);
77 space = State.cartesian(space,state);
78 space = [Inf*ones(size(space,1),1),space];
79 case {SchedStrategy.INF, SchedStrategy.PS, SchedStrategy.DPS, SchedStrategy.GPS, SchedStrategy.PSPRIO, SchedStrategy.DPSPRIO, SchedStrategy.GPSPRIO, SchedStrategy.LPS}
80 % in these policies we only track the jobs in the servers
82 init = State.spaceClosedSingle(K(r),0);
84 state = State.cartesian(state,init);
86 space = State.cartesian(space,state);
87 case {SchedStrategy.SIRO, SchedStrategy.LEPT, SchedStrategy.SEPT, SchedStrategy.SRPT, SchedStrategy.SRPTPRIO, SchedStrategy.PSJF, SchedStrategy.FB, SchedStrategy.LRPT, SchedStrategy.POLLING}
88 % in these policies we track an un-ordered buffer and
89 % the jobs in the servers
90 % build list of job
classes in the node, with repetition
93 init = State.spaceClosedSingle(K(r),0);
95 state = State.cartesian(state,init);
97 space = State.cartesian(space,[zeros(size(state,1),R),state]);
99 % si = multichoosecon(n,S(i)); % jobs of
class r that are running
101 mi_buf = repmat(n,size(si,1),1) - si; % jobs of
class r in buffer
103 % determine number of
classes r jobs running in phase j
106 init = State.spaceClosedSingle(K(r),0);
108 kstate = State.cartesian(kstate,init);
110 state = [repmat(mi_buf(k,:),size(kstate,1),1), kstate];
111 space = [space; state];
114 case {SchedStrategy.FCFS, SchedStrategy.HOL,SchedStrategy.FCFSPRIO, SchedStrategy.LCFS,SchedStrategy.LCFSPRIO}
116 space = zeros(1,1+max(R,sum(K)));
119 % in these policies we track an ordered buffer and
120 % the jobs in the servers
122 % build list of job
classes in the buffer, with repetition
126 inbuf=[inbuf, r*ones(1,n(r)-s(r))];
130 sizeEstimator = multinomialln(n);
131 sizeEstimator = round(sizeEstimator/log(10));
133 if ~isfield(options,
'force') || options.force ==
false
134 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));
140 % gen permutation of their positions in the fcfs buffer
141 mi = uniqueperms(inbuf);
143 mi_buf = zeros(1,max(1,sum(n)-S(ist)));
144 state = zeros(1,sum(K));
145 state = [mi_buf,state];
147 % mi_buf:
class of job in buffer position i (0=empty)
149 mi_buf = mi(:,1:(sum(n)-sum(s)));
150 else % set an empty buffer
154 % mi_srv:
class of jobs running in the server of i
157 mi_srv = [mi_srv, r*ones(1,s(r))];
159 % si: number of
class r jobs that are running
161 %si = unique(si,
'rows');
162 for b=1:size(mi_buf,1)
164 % determine number of classs r jobs running in phase
165 % j in server state mi_srv(kjs,:) and build
169 % kstate = State.cartesian(kstate,State.spaceClosedSingle(K(r),si(k,r)));
170 init = State.spaceClosedSingle(K(r),0);
172 kstate = State.cartesian(kstate,init);
174 state = [state; repmat(mi_buf(b,:),size(kstate,1),1), kstate];
178 case {SchedStrategy.FCFSPR, SchedStrategy.FCFSPI, SchedStrategy.FCFSPRPRIO, SchedStrategy.FCFSPIPRIO, SchedStrategy.LCFSPR, SchedStrategy.LCFSPI, SchedStrategy.LCFSPRPRIO, SchedStrategy.LCFSPIPRIO}
181 space = zeros(1,1+sum(K));
184 % in these policies we track an ordered buffer and
185 % the jobs in the servers
187 % build list of job
classes in the buffer, with repetition
191 inbuf=[inbuf, r*ones(1,n(r)-s(r))];
195 sizeEstimator = multinomialln(n);
196 sizeEstimator = round(sizeEstimator/log(10));
198 if ~isfield(options,
'force') || options.force ==
false
199 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));
205 % gen permutation of their positions in the fcfs buffer
206 mi = uniqueperms(inbuf);
208 % buffer
is empty (all present jobs are in service): a single
209 % placeholder slot
is used, but the row
is built by the loop
210 % below in the SAME interleaved (class,phase) encoding as the
211 % occupied case -- do NOT pre-seed a non-interleaved row here,
212 % otherwise vertcat at the end mixes widths L+sum(K) and 2L+sum(K).
213 mi_buf = zeros(1,max(1,sum(n)-S(ist)));
215 % mi_buf:
class of job in buffer position i (0=empty)
217 mi_buf = mi(:,1:(sum(n)-sum(s)));
218 else % set an empty buffer
223 % mi_srv:
class of jobs running in the server of i
226 mi_srv = [mi_srv, r*ones(1,s(r))];
228 % si: number of
class r jobs that are running
230 %si = unique(si,
'rows');
231 for b=1:size(mi_buf,1)
233 % determine number of class r jobs running in phase
234 % j in server state mi_srv(kjs,:) and build
238 % kstate = State.cartesian(kstate,State.spaceClosedSingle(K(r),si(k,r)));
239 init = State.spaceClosedSingle(K(r),0);
241 kstate = State.cartesian(kstate,init);
245 for j=mi_buf(b,:) % for each job in the buffer
247 bkstate = State.cartesian(bkstate,[1:K(j)]');
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);
255 bufstate_tmp = State.cartesian(mi_buf(b,:), bkstate);
256 % here interleave positions of class and phases in
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)];
265 case SchedStrategy.PAS
266 % Pass-and-swap / order-independent queue: the local state
is the
267 % full ordered list of class indices (oldest at column 1),
268 % left-aligned and right zero-padded to the station capacity.
269 % The "started" counts s are immaterial (no server/buffer split).
272 line_error(mfilename,'PAS stations require finite capacity for state-space generation.');
277 space = zeros(0, W); % infeasible: exceeds total capacity
282 vi=[vi, r*ones(1,n(r))];
285 mi = uniqueperms(vi);
286 space = [mi, zeros(size(mi,1), W - size(mi,2))];
288 case {SchedStrategy.SJF, SchedStrategy.LJF}
289 % in these policies the state space includes continuous
290 % random variables
for the service times
291 % in these policies we only track the jobs in the servers
294 init = State.spaceClosedSingle(K(r),0);
296 state = State.cartesian(state,init);
298 space = State.cartesian(space,state);
299 %
this is not casted as an error since
this function
is
302 % called to initial models with SJF and LJF
303 line_warning(mfilename,
'The scheduling policy does not admit a discrete state space.\n');
307 case SchedStrategy.INF
308 % in
this policies we only track the jobs in the servers
310 init = State.spaceClosedSingle(K(r),n(r));
311 state = State.cartesian(state,init);
313 space = State.cartesian(space,state);
317 case NodeType.Transition
318 line_error(mfilename,
'fromMarginalAndStarted cannot be used on Petri net elements');
320 line_error(mfilename,
'fromMarginalAndStarted cannot be used on Petri net elements');
322space = unique(space,
'rows'); %
do not comment, required to sort empty state as first
323space = space(end:-1:1,:); %
this ensures that states where jobs start in phase 1 are first, which
is used eg in SSA