1function space = fromMarginal(sn, ind, n, options)
2% FROMMARGINAL Generate state space with specific marginal queue lengths
4% @brief Creates state space where a specific node has given marginal queue lengths
5% @param sn Network structure or Network
object
6% @param ind Node index
for which to set marginal queue lengths
7% @param n Vector of jobs per
class at the specified node
8% @param options Optional structure with configuration parameters
9% @
return space Generated state space satisfying marginal constraints
11% This function generates all possible network states where the specified
12% node (ind) has exactly n(r) jobs of
class r, for all
classes. It
is
13% essential
for state space analysis and marginal probability computations.
15% Copyright (c) 2012-2026, Imperial College London
18if nargin<4 %~exist(
'options',
'var')
19 options.force = false;
26% generate states such that the marginal queue-lengths are as in vector n
27% n(r): number of jobs at the station in class r
34ist = sn.nodeToStation(ind);
35%isf = sn.nodeToStateful(ind);
37if sn.isstation(ind) && any(sn.procid(ist,:)==ProcessType.MAP | sn.procid(sn.nodeToStation(ind),:)==ProcessType.MMPP2)
38 if sn.sched(ist) ~= SchedStrategy.FCFS & sn.nodetype ~= NodeType.Source
39 line_error(mfilename,'Non-FCFS MAP stations are not supported.')
43if sn.isstateful(ind) && ~sn.isstation(ind)
45 init_r = State.spaceClosedSingle(1,n(r));
46 state = State.cartesian(state,init_r);
48 space = State.cartesian(space,state);
54 if isempty(sn.proc{ist}{r})
57 phases(r) = length(sn.proc{ist}{r}{1});
60if (sn.sched(ist) ~= SchedStrategy.EXT) && any(n>sn.classcap(ist,:))
64% generate local-state space
65switch sn.nodetype(ind)
66 case {NodeType.Queue, NodeType.Delay, NodeType.Source, NodeType.Place}
68 case SchedStrategy.EXT
70 if ~isempty(sn.proc) && ~isempty(sn.proc{ist}{r}) && any(any(isnan(sn.proc{ist}{r}{1}))) % disabled
71 init_r = 0*ones(1,phases(r));
73 init_r = State.spaceClosedSingle(phases(r),1);
75 state = State.cartesian(state,init_r);
77 space = State.cartesian(space,state); %server part
78 space = [Inf*ones(size(space,1),1),space]; % attach infinite buffer before servers
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_r = State.spaceClosedSingle(phases(r),n(r));
83 state = State.cartesian(state,init_r);
85 space = State.cartesian(space,state);
86 case {SchedStrategy.SIRO, SchedStrategy.LEPT, SchedStrategy.SEPT, SchedStrategy.SRPT, SchedStrategy.SRPTPRIO}
87 % in these policies we track an un-ordered buffer and
88 % the jobs in the servers
89 % build list of job
classes in the node, with repetition
92 init_r = State.spaceClosedSingle(phases(r),n(r));
93 state = State.cartesian(state,init_r);
95 space = State.cartesian(space,[zeros(size(state,1),R),state]);
97 si = multichoosecon(n,S(ist)); % jobs of
class r that are running
98 mi_buf = repmat(n,size(si,1),1) - si; % jobs of
class r in buffer
100 % determine number of
classes r jobs running in phase j
103 init_r = State.spaceClosedSingle(phases(r),si(k,r));
104 kstate = State.cartesian(kstate,init_r);
106 state = [repmat(mi_buf(k,:),size(kstate,1),1), kstate];
107 space = [space; state];
110 case {SchedStrategy.LCFSPI, SchedStrategy.LCFSPR, SchedStrategy.LCFSPIPRIO, SchedStrategy.LCFSPRPRIO}
111 sizeEstimator = multinomialln(n) - gammaln(sum(n)) + gammaln(1+sn.cap(ist));
112 sizeEstimator = round(sizeEstimator/log(10));
114 if ~isfield(options,
'force') || options.force ==
false
115 %line_warning(mfilename,sprintf(
'Marginal state space size is in the order of thousands of states. Computation may be slow.',sizeEstimator));
120 space = zeros(1,1+sum(phases)); % unclear
if this should 1+sum(K), was sum(K) but State.fromMarginalAndStarted uses 1+sum(K) so was changed here as well
123 % in these policies we track an ordered buffer and
124 % the jobs in the servers
126 % build list of job
classes in the node, with repetition
130 vi=[vi, r*ones(1,n(r))];
134 % gen permutation of their positions in the waiting buffer
135 mi = uniqueperms(vi);
136 % now generate server states
138 mi_buf = zeros(1,max(0,sum(n)-S(ist)));
140 state = State.cartesian(state,[mi_buf,state]);
142 mi = mi(:,(end-min(sum(n),sn.cap(ist))+1):end); % n(r) may count more than once elements within the same chain
143 mi = unique(mi,
'rows');
144 % mi_buf:
class of job in buffer position i (0=empty)
145 mi_buf = [zeros(size(mi,1),min(sum(n),sn.cap(ist))-S(ist)-size(mi(:,1:end-S(ist)),2)), mi(:,1:end-S(ist))];
147 mi_buf = zeros(size(mi,1),1);
151 % generate job phases
for all buffer states
152 %
for k=1:size(mi_buf,1)
153 % mi_buf_kstate(end+1:end+size(bkstate,1),1:size(bkstate,2)) = bkstate;
156 % mi_srv:
class of job running in server i
157 mi_srv = mi(:,max(size(mi,2)-S(ist)+1,1):end);
158 % si: number of
class r jobs that are running
160 for k=1:size(mi_srv,1)
161 %si(k,1:R) = hist(mi_srv(k,:),1:R); % deprecated
162 si(k, 1:R) = histcounts(mi_srv(k, :), 1:(R+1));
164 %si = unique(si,
'rows');
166 % determine number of
class r jobs running in phase
167 % j in server state mi_srv(kjs,:) and build
171 kstate = State.cartesian(kstate,State.spaceClosedSingle(phases(r),si(k,r)));
173 % generate job phases
for all buffer states
175 for j=mi_buf(k,:) % for each job in the buffer
177 bkstate = State.cartesian(bkstate,[1:phases(j)]
');
182 bufstate_tmp = State.cartesian(mi_buf(k,:), bkstate);
183 % here interleave positions of class and phases in
185 bufstate = zeros(size(bufstate_tmp));
186 bufstate(:,1:2:end)=bufstate_tmp(:,1:size(mi_buf,2));
187 bufstate(:,2:2:end)=bufstate_tmp(:,(size(mi_buf,2)+1):end);
188 state = [state; State.cartesian(bufstate, kstate)];
192 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.FCFSPRIO, SchedStrategy.LCFS, SchedStrategy.LCFSPRIO}
193 sizeEstimator = multinomialln(n) - gammaln(sum(n)) + gammaln(1+sn.cap(ist));
194 sizeEstimator = round(sizeEstimator/log(10));
196 if ~isfield(options,'force
') || options.force == false
197 %line_warning(mfilename,sprintf('Marginal state space size
is in the order of thousands of states. Computation may be slow.
',sizeEstimator));
202 space = zeros(1,1+sum(phases));
203 if sn.nodetype(ind) ~= NodeType.Source
205 switch sn.procid(sn.nodeToStation(ind),r)
206 case {ProcessType.MAP, ProcessType.MMPP2}
207 space = State.cartesian(space, [1:sn.phases(ind,r)]');
213 % in these policies we track an ordered buffer and
214 % the jobs in the servers
216 % build list of job
classes in the node, with repetition
220 vi=[vi, r*ones(1,n(r))];
224 % gen permutation of their positions in the waiting buffer
225 mi = uniqueperms(vi);
226 % now generate server states
228 mi_buf = zeros(1,max(0,sum(n)-S(ist)));
230 state = State.cartesian(state,[mi_buf,state]);
232 mi = mi(:,(end-min(sum(n),sn.cap(ist))+1):end); % n(r) may count more than once elements within the same chain
233 mi = unique(mi,
'rows');
234 % mi_buf:
class of job in buffer position i (0=empty)
235 mi_buf = [zeros(size(mi,1),min(sum(n),sn.cap(ist))-S(ist)-size(mi(:,1:end-S(ist)),2)), mi(:,1:end-S(ist))];
237 mi_buf = zeros(size(mi,1),1);
239 % mi_srv:
class of job running in server i
240 mi_srv = mi(:,max(size(mi,2)-S(ist)+1,1):end);
241 % si: number of
class r jobs that are running
243 for k=1:size(mi_srv,1)
244 %si(k,1:R) = hist(mi_srv(k,:),1:R); % deprecated
245 si(k, 1:R) = histcounts(mi_srv(k, :), 1:(R+1));
247 %si = unique(si,
'rows');
249 % determine number of
class r jobs running in phase
250 % j in server state mi_srv(k,:) and build
256 init_r = State.spaceClosedSingle(phases(r),si(k,r));
257 if sn.procid(sn.nodeToStation(ind),r) == ProcessType.MAP || sn.procid(sn.nodeToStation(ind),r) == ProcessType.MMPP2
259 init_r = State.cartesian(init_r, [1:phases(r)]
');
262 % Single server case (original logic)
263 init_r = State.cartesian(init_r, 0);
264 for i=1:size(init_r,1)
265 if init_r(i,end) == 0
266 init_r(i,end) = find(init_r(i,:));
270 % Multiserver case: FCFS-aware phase selection
271 % Use original approach but bias toward occupied phases
274 for i=1:size(init_r,1)
275 phase_dist = init_r(i, 1:phases(r));
277 % Find phases that have jobs (occupied phases)
278 occupied_phases = find(phase_dist > 0);
280 if ~isempty(occupied_phases)
281 % For FCFS, include occupied phases plus adjacent phases
282 % to account for phase transitions during service
283 extended_phases = [];
284 for op = occupied_phases
285 extended_phases = [extended_phases, op];
286 % Add adjacent phases for smooth transitions
288 extended_phases = [extended_phases, op-1];
291 extended_phases = [extended_phases, op+1];
294 extended_phases = unique(extended_phases);
295 phase_list = [phase_list; extended_phases'];
297 % No jobs - include all phases (original behavior)
298 phase_list = [phase_list; [1:phases(r)]
'];
302 % Remove duplicates and create cartesian product
303 phase_list = unique(phase_list);
304 init_r = State.cartesian(init_r, phase_list);
308 kstate = State.cartesian(kstate,init_r);
309 if sn.procid(sn.nodeToStation(ind),r) == ProcessType.MAP || sn.procid(sn.nodeToStation(ind),r) == ProcessType.MMPP2
310 map_cols(end+1) = size(kstate,2);
313 kstate = kstate(:,[setdiff(1:size(kstate,2),map_cols),map_cols]);
314 state = [state; repmat(mi_buf(k,:),size(kstate,1),1), kstate];
318 case {SchedStrategy.SJF, SchedStrategy.LJF}
319 % in these policies the state space includes continuous
320 % random variables for the service times
321 line_error(mfilename,'The scheduling policy does not admit a discrete state space.\n
');
324 switch sn.routing(ind,r)
325 case {RoutingStrategy.RROBIN, RoutingStrategy.WRROBIN}
326 space = State.cartesian(space, sn.nodeparam{ind}{r}.outlinks(:));
331 case SchedStrategy.INF
332 % in this policies we only track the jobs in the servers
334 init_r = State.spaceClosedSingle(phases(r),n(r));
335 state = State.cartesian(state,init_r);
337 space = State.cartesian(space,state);
340 switch sn.routing(ind,r)
341 case RoutingStrategy.RROBIN
342 space = State.cartesian(space, sn.nodeparam{ind}{r}.outlinks(:));
346space = unique(space,'rows
'); % do not comment, required to sort empty state as first
347space = space(end:-1:1,:); % so that states with jobs in phase 1 comes earlier