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 still get a valid marginal state (jobs in first phase) for sim backends; analytical solvers reject via feature set
52if sn.isstation(ind) && any(n>sn.classcap(ist,:))
53 exceeded = n>sn.classcap(ist,:);
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,:)));
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,:)));
63if sn.isstation(ind) && (sn.nservers(ist)>0 && sum(s) > sn.nservers(ist))
66% generate local-state space
67switch sn.nodetype(ind)
68 case {NodeType.Queue, NodeType.Delay, NodeType.Source}
70 case SchedStrategy.EXT
72 init = State.spaceClosedSingle(K(r),0);
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
77 % init the job generation
81 state = State.cartesian(state,init);
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
88 init = State.spaceClosedSingle(K(r),0);
90 state = State.cartesian(state,init);
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
98 init = State.spaceClosedSingle(K(r),0);
100 state = State.cartesian(state,init);
102 space = State.cartesian(space,[zeros(size(state,1),R),state]);
104 % si = multichoosecon(n,S(i)); % jobs of
class r that are running
106 mi_buf = repmat(n,size(si,1),1) - si; % jobs of
class r in buffer
108 % determine number of classes r jobs running in phase j
111 init = State.spaceClosedSingle(K(r),0);
113 kstate = State.cartesian(kstate,init);
115 state = [repmat(mi_buf(k,:),size(kstate,1),1), kstate];
116 space = [space; state];
119 case {SchedStrategy.FCFS, SchedStrategy.HOL,SchedStrategy.FCFSPRIO, SchedStrategy.LCFS,SchedStrategy.LCFSPRIO, SchedStrategy.EDD}
121 space = zeros(1,1+max(R,sum(K)));
124 % Ordered buffer + in-server jobs -- see _kb/04-networkstruct.md
126 % build list of job classes in the buffer, with repetition
130 inbuf=[inbuf, r*ones(1,n(r)-s(r))];
134 sizeEstimator = multinomialln(n);
135 sizeEstimator = round(sizeEstimator/log(10));
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));
144 % gen permutation of their positions in the fcfs buffer
145 mi = uniqueperms(inbuf);
147 mi_buf = zeros(1,max(1,sum(n)-S(ist)));
148 state = zeros(1,sum(K));
149 state = [mi_buf,state];
151 % mi_buf:
class of job in buffer position i (0=empty)
153 mi_buf = mi(:,1:(sum(n)-sum(s)));
154 else % set an empty buffer
158 % mi_srv:
class of jobs running in the server of i
161 mi_srv = [mi_srv, r*ones(1,s(r))];
163 % si: number of
class r jobs that are running
165 %si = unique(si,
'rows');
166 for b=1:size(mi_buf,1)
168 % determine number of classs r jobs running in phase
169 % j in server state mi_srv(kjs,:) and build
173 % kstate = State.cartesian(kstate,State.spaceClosedSingle(K(r),si(k,r)));
174 init = State.spaceClosedSingle(K(r),0);
176 kstate = State.cartesian(kstate,init);
178 state = [state; repmat(mi_buf(b,:),size(kstate,1),1), kstate];
182 case {SchedStrategy.FCFSPR, SchedStrategy.FCFSPI, SchedStrategy.FCFSPRPRIO, SchedStrategy.FCFSPIPRIO, SchedStrategy.LCFSPR, SchedStrategy.LCFSPI, SchedStrategy.LCFSPRPRIO, SchedStrategy.LCFSPIPRIO, SchedStrategy.EDF}
185 % Preempt-resume buffer
is (
class,phase) pairs, must be even-width -- see _kb/04-networkstruct.md
186 space = zeros(1,2+sum(K));
189 % Ordered buffer + in-server jobs -- see _kb/04-networkstruct.md
191 % build list of job classes in the buffer, with repetition
195 inbuf=[inbuf, r*ones(1,n(r)-s(r))];
199 sizeEstimator = multinomialln(n);
200 sizeEstimator = round(sizeEstimator/log(10));
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));
209 % gen permutation of their positions in the fcfs buffer
210 mi = uniqueperms(inbuf);
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)));
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 % PAS/OI: local state
is the ordered class-index list; "started" counts s are immaterial -- see _kb/04-networkstruct.md
269 line_error(mfilename,'PAS stations require finite capacity for state-space generation.');
274 space = zeros(0, W); % infeasible: exceeds total capacity
279 vi=[vi, r*ones(1,n(r))];
282 mi = uniqueperms(vi);
283 space = [mi, zeros(size(mi,1), W - size(mi,2))];
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
291 init = State.spaceClosedSingle(K(r),0);
293 state = State.cartesian(state,init);
295 space = State.cartesian(space,state);
296 %
this is not casted as an error since
this function
is
299 % called to initial models with SJF and LJF
300 line_warning(mfilename,
'The scheduling policy does not admit a discrete state space.\n');
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);
313 case SchedStrategy.INF
314 % in this policies we only track the jobs in the servers
316 init = State.spaceClosedSingle(K(r),n(r));
317 state = State.cartesian(state,init);
319 space = State.cartesian(space,state);
322 if isfield(sn,'isfjaugmented
') && sn.isfjaugmented
323 % FJ-augmented struct: join state is the per-class buffered-jobs count, deterministic given the marginals
328 case NodeType.Transition
329 line_error(mfilename,
'fromMarginalAndStarted cannot be used on Petri net elements');
331 line_error(mfilename,
'fromMarginalAndStarted cannot be used on Petri net elements');
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