LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
fromMarginal.m
1function space = fromMarginal(sn, ind, n, options)
2% FROMMARGINAL Generate state space with specific marginal queue lengths
3%
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
10%
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.
14%
15% Copyright (c) 2012-2026, Imperial College London
16% All rights reserved.
17
18if nargin<4 %~exist('options','var')
19 options.force = false;
20end
21if isa(sn,'Network')
22 sn=sn.getStruct();
23end
24
25
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
28R = sn.nclasses;
29S = sn.nservers;
30state = [];
31space = [];
32
33% ind: node index
34ist = sn.nodeToStation(ind);
35%isf = sn.nodeToStateful(ind);
36
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.')
40 end
41end
42
43if sn.isstateful(ind) && ~sn.isstation(ind)
44 if sn.nodetype(ind) == NodeType.Transition
45 % Transition state format is per-mode, not per-class
46 isf = sn.nodeToStateful(ind);
47 if ~isempty(sn.space) && isf <= length(sn.space) && ~isempty(sn.space{isf})
48 space = sn.space{isf};
49 else
50 % Generate initial state only (all servers idle)
51 nmodes_t = sn.nodeparam{ind}.nmodes;
52 nmodeservers_t = sn.nodeparam{ind}.nmodeservers;
53 nmodeservers_t(isinf(nmodeservers_t)) = GlobalConstants.MaxInt();
54 firingphases_t = sn.nodeparam{ind}.firingphases;
55 firingphases_t(isnan(firingphases_t)) = 1;
56 space = [nmodeservers_t, zeros(1, sum(firingphases_t)), zeros(size(nmodeservers_t))];
57 end
58 return
59 end
60 for r=1:R
61 init_r = State.spaceClosedSingle(1,n(r));
62 state = State.cartesian(state,init_r);
63 end
64 space = State.cartesian(space,state);
65 return
66end
67
68phases = zeros(1,R);
69for r=1:R
70 if isempty(sn.proc{ist}{r})
71 phases(r) = 0;
72 else
73 phases(r) = length(sn.proc{ist}{r}{1});
74 end
75end
76if (sn.sched(ist) ~= SchedStrategy.EXT) && any(n>sn.classcap(ist,:))
77 return
78end
79
80% generate local-state space
81switch sn.nodetype(ind)
82 case {NodeType.Queue, NodeType.Delay, NodeType.Source, NodeType.Place}
83 switch sn.sched(ist)
84 case SchedStrategy.EXT
85 for r=1:R
86 if ~isempty(sn.proc) && ~isempty(sn.proc{ist}{r}) && any(any(isnan(sn.proc{ist}{r}{1}))) % disabled
87 init_r = 0*ones(1,phases(r));
88 else
89 init_r = State.spaceClosedSingle(phases(r),1);
90 end
91 state = State.cartesian(state,init_r);
92 end
93 space = State.cartesian(space,state); %server part
94 space = [Inf*ones(size(space,1),1),space]; % attach infinite buffer before servers
95 case {SchedStrategy.INF, SchedStrategy.PS, SchedStrategy.DPS, SchedStrategy.GPS, SchedStrategy.PSPRIO, SchedStrategy.DPSPRIO, SchedStrategy.GPSPRIO, SchedStrategy.LPS}
96 % in these policies we only track the jobs in the servers
97 for r=1:R
98 init_r = State.spaceClosedSingle(phases(r),n(r));
99 state = State.cartesian(state,init_r);
100 end
101 space = State.cartesian(space,state);
102 case {SchedStrategy.SIRO, SchedStrategy.LEPT, SchedStrategy.SEPT, SchedStrategy.SRPT, SchedStrategy.SRPTPRIO}
103 % in these policies we track an un-ordered buffer and
104 % the jobs in the servers
105 % build list of job classes in the node, with repetition
106 if sum(n) <= S(ist)
107 for r=1:R
108 init_r = State.spaceClosedSingle(phases(r),n(r));
109 state = State.cartesian(state,init_r);
110 end
111 space = State.cartesian(space,[zeros(size(state,1),R),state]);
112 else
113 si = multichoosecon(n,S(ist)); % jobs of class r that are running
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_r = State.spaceClosedSingle(phases(r),si(k,r));
120 kstate = State.cartesian(kstate,init_r);
121 end
122 state = [repmat(mi_buf(k,:),size(kstate,1),1), kstate];
123 space = [space; state];
124 end
125 end
126 case {SchedStrategy.FCFSPI, SchedStrategy.FCFSPR, SchedStrategy.FCFSPIPRIO, SchedStrategy.FCFSPRPRIO, SchedStrategy.LCFSPI, SchedStrategy.LCFSPR, SchedStrategy.LCFSPIPRIO, SchedStrategy.LCFSPRPRIO}
127 sizeEstimator = multinomialln(n) - gammaln(sum(n)) + gammaln(1+sn.cap(ist));
128 sizeEstimator = round(sizeEstimator/log(10));
129 if sizeEstimator > 3
130 if ~isfield(options,'force') || options.force == false
131 %line_warning(mfilename,sprintf('Marginal state space size is in the order of thousands of states. Computation may be slow.',sizeEstimator));
132 end
133 end
134
135 if sum(n) == 0
136 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
137 return
138 end
139 % in these policies we track an ordered buffer and
140 % the jobs in the servers
141
142 % build list of job classes in the node, with repetition
143 vi = [];
144 for r=1:R
145 if n(r)>0
146 vi=[vi, r*ones(1,n(r))];
147 end
148 end
149
150 % gen permutation of their positions in the waiting buffer
151 mi = uniqueperms(vi);
152 % now generate server states
153 if isempty(mi)
154 mi_buf = zeros(1,max(0,sum(n)-S(ist)));
155 state = zeros(1,R);
156 state = State.cartesian(state,[mi_buf,state]);
157 else
158 mi = mi(:,(end-min(sum(n),sn.cap(ist))+1):end); % n(r) may count more than once elements within the same chain
159 mi = unique(mi,'rows');
160 % mi_buf: class of job in buffer position i (0=empty)
161 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))];
162 if isempty(mi_buf)
163 mi_buf = zeros(size(mi,1),1);
164 end
165 mi_buf_kstate = [];
166 %if mi_buf(1)>0
167 % generate job phases for all buffer states
168 %for k=1:size(mi_buf,1)
169 % mi_buf_kstate(end+1:end+size(bkstate,1),1:size(bkstate,2)) = bkstate;
170 %end
171 %end
172 % mi_srv: class of job running in server i
173 mi_srv = mi(:,max(size(mi,2)-S(ist)+1,1):end);
174 % si: number of class r jobs that are running
175 si =[];
176 for k=1:size(mi_srv,1)
177 %si(k,1:R) = hist(mi_srv(k,:),1:R); % deprecated
178 si(k, 1:R) = histcounts(mi_srv(k, :), 1:(R+1));
179 end
180 %si = unique(si,'rows');
181 for k=1:size(si,1)
182 % determine number of class r jobs running in phase
183 % j in server state mi_srv(kjs,:) and build
184 % state
185 kstate=[];
186 for r=1:R
187 kstate = State.cartesian(kstate,State.spaceClosedSingle(phases(r),si(k,r)));
188 end
189 % generate job phases for all buffer states
190 bkstate = [];
191 for j=mi_buf(k,:) % for each job in the buffer
192 if j>0
193 bkstate = State.cartesian(bkstate,[1:phases(j)]');
194 else
195 bkstate = 0;
196 end
197 end
198 bufstate_tmp = State.cartesian(mi_buf(k,:), bkstate);
199 % here interleave positions of class and phases in
200 % buf
201 bufstate = zeros(size(bufstate_tmp));
202 bufstate(:,1:2:end)=bufstate_tmp(:,1:size(mi_buf,2));
203 bufstate(:,2:2:end)=bufstate_tmp(:,(size(mi_buf,2)+1):end);
204 state = [state; State.cartesian(bufstate, kstate)];
205 end
206 end
207 space = state;
208 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.FCFSPRIO, SchedStrategy.LCFS, SchedStrategy.LCFSPRIO}
209 sizeEstimator = multinomialln(n) - gammaln(sum(n)) + gammaln(1+sn.cap(ist));
210 sizeEstimator = round(sizeEstimator/log(10));
211 if sizeEstimator > 3
212 if ~isfield(options,'force') || options.force == false
213 %line_warning(mfilename,sprintf('Marginal state space size is in the order of thousands of states. Computation may be slow.',sizeEstimator));
214 end
215 end
216
217 if sum(n) == 0
218 space = zeros(1,1+sum(phases));
219 if sn.nodetype(ind) ~= NodeType.Source
220 for r=1:R
221 switch sn.procid(sn.nodeToStation(ind),r)
222 case {ProcessType.MAP, ProcessType.MMPP2}
223 space = State.cartesian(space, [1:sn.phases(ind,r)]');
224 end
225 end
226 end
227 return
228 end
229 % in these policies we track an ordered buffer and
230 % the jobs in the servers
231
232 % build list of job classes in the node, with repetition
233 vi = [];
234 for r=1:R
235 if n(r)>0
236 vi=[vi, r*ones(1,n(r))];
237 end
238 end
239
240 % gen permutation of their positions in the waiting buffer
241 mi = uniqueperms(vi);
242 % now generate server states
243 if isempty(mi)
244 mi_buf = zeros(1,max(0,sum(n)-S(ist)));
245 state = zeros(1,R);
246 state = State.cartesian(state,[mi_buf,state]);
247 else
248 mi = mi(:,(end-min(sum(n),sn.cap(ist))+1):end); % n(r) may count more than once elements within the same chain
249 mi = unique(mi,'rows');
250 % mi_buf: class of job in buffer position i (0=empty)
251 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))];
252 if isempty(mi_buf)
253 mi_buf = zeros(size(mi,1),1);
254 end
255 % mi_srv: class of job running in server i
256 mi_srv = mi(:,max(size(mi,2)-S(ist)+1,1):end);
257 % si: number of class r jobs that are running
258 si =[];
259 for k=1:size(mi_srv,1)
260 %si(k,1:R) = hist(mi_srv(k,:),1:R); % deprecated
261 si(k, 1:R) = histcounts(mi_srv(k, :), 1:(R+1));
262 end
263 %si = unique(si,'rows');
264 for k=1:size(si,1)
265 % determine number of class r jobs running in phase
266 % j in server state mi_srv(k,:) and build
267 % state
268 kstate=[];
269 map_cols = [];
270
271 for r=1:R
272 init_r = State.spaceClosedSingle(phases(r),si(k,r));
273 if sn.procid(sn.nodeToStation(ind),r) == ProcessType.MAP || sn.procid(sn.nodeToStation(ind),r) == ProcessType.MMPP2
274 if si(k,r) == 0
275 init_r = State.cartesian(init_r, [1:phases(r)]');
276 else
277 if S(ist) == 1
278 % Single server case (original logic)
279 init_r = State.cartesian(init_r, 0);
280 for i=1:size(init_r,1)
281 if init_r(i,end) == 0
282 init_r(i,end) = find(init_r(i,:));
283 end
284 end
285 else
286 % Multiserver case: FCFS-aware phase selection
287 % Use original approach but bias toward occupied phases
288 phase_list = [];
289
290 for i=1:size(init_r,1)
291 phase_dist = init_r(i, 1:phases(r));
292
293 % Find phases that have jobs (occupied phases)
294 occupied_phases = find(phase_dist > 0);
295
296 if ~isempty(occupied_phases)
297 % For FCFS, include occupied phases plus adjacent phases
298 % to account for phase transitions during service
299 extended_phases = [];
300 for op = occupied_phases
301 extended_phases = [extended_phases, op];
302 % Add adjacent phases for smooth transitions
303 if op > 1
304 extended_phases = [extended_phases, op-1];
305 end
306 if op < phases(r)
307 extended_phases = [extended_phases, op+1];
308 end
309 end
310 extended_phases = unique(extended_phases);
311 phase_list = [phase_list; extended_phases'];
312 else
313 % No jobs - include all phases (original behavior)
314 phase_list = [phase_list; [1:phases(r)]'];
315 end
316 end
317
318 % Remove duplicates and create cartesian product
319 phase_list = unique(phase_list);
320 init_r = State.cartesian(init_r, phase_list);
321 end
322 end
323 end
324 kstate = State.cartesian(kstate,init_r);
325 if sn.procid(sn.nodeToStation(ind),r) == ProcessType.MAP || sn.procid(sn.nodeToStation(ind),r) == ProcessType.MMPP2
326 map_cols(end+1) = size(kstate,2);
327 end
328 end
329 kstate = kstate(:,[setdiff(1:size(kstate,2),map_cols),map_cols]);
330 state = [state; repmat(mi_buf(k,:),size(kstate,1),1), kstate];
331 end
332 end
333 space = state;
334 case {SchedStrategy.SJF, SchedStrategy.LJF}
335 % in these policies the state space includes continuous
336 % random variables for the service times
337 line_error(mfilename,'The scheduling policy does not admit a discrete state space.\n');
338 end
339 for r=1:R
340 switch sn.routing(ind,r)
341 case {RoutingStrategy.RROBIN, RoutingStrategy.WRROBIN}
342 space = State.cartesian(space, sn.nodeparam{ind}{r}.outlinks(:));
343 end
344 end
345 case NodeType.Cache
346 switch sn.sched(ist)
347 case SchedStrategy.INF
348 % in this policies we only track the jobs in the servers
349 for r=1:R
350 init_r = State.spaceClosedSingle(phases(r),n(r));
351 state = State.cartesian(state,init_r);
352 end
353 space = State.cartesian(space,state);
354 end
355 for r=1:R
356 switch sn.routing(ind,r)
357 case RoutingStrategy.RROBIN
358 space = State.cartesian(space, sn.nodeparam{ind}{r}.outlinks(:));
359 end
360 end
361end
362space = unique(space,'rows'); % do not comment, required to sort empty state as first
363space = space(end:-1:1,:); % so that states with jobs in phase 1 comes earlier
364end