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});
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.
59if sn.isstation(ind) && any(n>sn.classcap(ist,:))
60 exceeded = n>sn.classcap(ist,:);
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,:)));
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,:)));
70if sn.isstation(ind) && (sn.nservers(ist)>0 && sum(s) > sn.nservers(ist))
73% generate local-state space
74switch sn.nodetype(ind)
75 case {NodeType.Queue, NodeType.Delay, NodeType.Source}
77 case SchedStrategy.EXT
79 init = State.spaceClosedSingle(K(r),0);
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
84 % init
the job generation
88 state = State.cartesian(state,init);
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
95 init = State.spaceClosedSingle(K(r),0);
97 state = State.cartesian(state,init);
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
103 % build list of job classes in
the node, with repetition
106 init = State.spaceClosedSingle(K(r),0);
108 state = State.cartesian(state,init);
110 space = State.cartesian(space,[zeros(size(state,1),R),state]);
112 % si = multichoosecon(n,S(i)); % jobs of
class r that are running
114 mi_buf = repmat(n,size(si,1),1) - si; % jobs of
class r in buffer
116 % determine number of classes r jobs running in phase j
119 init = State.spaceClosedSingle(K(r),0);
121 kstate = State.cartesian(kstate,init);
123 state = [repmat(mi_buf(k,:),size(kstate,1),1), kstate];
124 space = [space; state];
127 case {SchedStrategy.FCFS, SchedStrategy.HOL,SchedStrategy.FCFSPRIO, SchedStrategy.LCFS,SchedStrategy.LCFSPRIO, SchedStrategy.EDD}
129 space = zeros(1,1+max(R,sum(K)));
132 % in these policies we track an ordered buffer and
135 % build list of job classes in
the buffer, with repetition
139 inbuf=[inbuf, r*ones(1,n(r)-s(r))];
143 sizeEstimator = multinomialln(n);
144 sizeEstimator = round(sizeEstimator/log(10));
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));
153 % gen permutation of their positions in
the fcfs buffer
154 mi = uniqueperms(inbuf);
156 mi_buf = zeros(1,max(1,sum(n)-S(ist)));
157 state = zeros(1,sum(K));
158 state = [mi_buf,state];
160 % mi_buf:
class of job in buffer position i (0=empty)
162 mi_buf = mi(:,1:(sum(n)-sum(s)));
163 else % set an empty buffer
167 % mi_srv:
class of jobs running in
the server of i
170 mi_srv = [mi_srv, r*ones(1,s(r))];
172 % si: number of
class r jobs that are running
174 %si = unique(si,
'rows');
175 for b=1:size(mi_buf,1)
177 % determine number of classs r jobs running in phase
178 % j in server state mi_srv(kjs,:) and build
182 % kstate = State.cartesian(kstate,State.spaceClosedSingle(K(r),si(k,r)));
183 init = State.spaceClosedSingle(K(r),0);
185 kstate = State.cartesian(kstate,init);
187 state = [state; repmat(mi_buf(b,:),size(kstate,1),1), kstate];
191 case {SchedStrategy.FCFSPR, SchedStrategy.FCFSPI, SchedStrategy.FCFSPRPRIO, SchedStrategy.FCFSPIPRIO, SchedStrategy.LCFSPR, SchedStrategy.LCFSPI, SchedStrategy.LCFSPRPRIO, SchedStrategy.LCFSPIPRIO, SchedStrategy.EDF}
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));
203 % in these policies we track an ordered buffer and
206 % build list of job classes in
the buffer, with repetition
210 inbuf=[inbuf, r*ones(1,n(r)-s(r))];
214 sizeEstimator = multinomialln(n);
215 sizeEstimator = round(sizeEstimator/log(10));
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));
224 % gen permutation of their positions in
the fcfs buffer
225 mi = uniqueperms(inbuf);
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)));
234 % mi_buf:
class of job in buffer position i (0=empty)
236 mi_buf = mi(:,1:(sum(n)-sum(s)));
237 else % set an empty buffer
242 % mi_srv:
class of jobs running in
the server of i
245 mi_srv = [mi_srv, r*ones(1,s(r))];
247 % si: number of
class r jobs that are running
249 %si = unique(si,
'rows');
250 for b=1:size(mi_buf,1)
252 % determine number of class r jobs running in phase
253 % j in server state mi_srv(kjs,:) and build
257 % kstate = State.cartesian(kstate,State.spaceClosedSingle(K(r),si(k,r)));
258 init = State.spaceClosedSingle(K(r),0);
260 kstate = State.cartesian(kstate,init);
264 for j=mi_buf(b,:) % for each job in
the buffer
266 bkstate = State.cartesian(bkstate,[1:K(j)]');
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);
274 bufstate_tmp = State.cartesian(mi_buf(b,:), bkstate);
275 % here interleave positions of class and phases in
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)];
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).
291 line_error(mfilename,'PAS stations require finite capacity for state-space generation.');
296 space = zeros(0, W); % infeasible: exceeds total capacity
301 vi=[vi, r*ones(1,n(r))];
304 mi = uniqueperms(vi);
305 space = [mi, zeros(size(mi,1), W - size(mi,2))];
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
313 init = State.spaceClosedSingle(K(r),0);
315 state = State.cartesian(state,init);
317 space = State.cartesian(space,state);
318 %
this is not casted as an error since
this function
is
321 % called to initial models with SJF and LJF
322 line_warning(mfilename,
'The scheduling policy does not admit a discrete state space.\n');
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);
339 case SchedStrategy.INF
340 % in this policies we only track the jobs in the servers
342 init = State.spaceClosedSingle(K(r),n(r));
343 state = State.cartesian(state,init);
345 space = State.cartesian(space,state);
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)
356 case NodeType.Transition
357 line_error(mfilename,
'fromMarginalAndStarted cannot be used on Petri net elements');
359 line_error(mfilename,
'fromMarginalAndStarted cannot be used on Petri net elements');
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