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 for r=1:R
45 init_r = State.spaceClosedSingle(1,n(r));
46 state = State.cartesian(state,init_r);
47 end
48 space = State.cartesian(space,state);
49 return
50end
51
52phases = zeros(1,R);
53for r=1:R
54 if isempty(sn.proc{ist}{r})
55 phases(r) = 0;
56 else
57 phases(r) = length(sn.proc{ist}{r}{1});
58 end
59end
60if (sn.sched(ist) ~= SchedStrategy.EXT) && any(n>sn.classcap(ist,:))
61 return
62end
63
64% generate local-state space
65switch sn.nodetype(ind)
66 case {NodeType.Queue, NodeType.Delay, NodeType.Source, NodeType.Place}
67 switch sn.sched(ist)
68 case SchedStrategy.EXT
69 for r=1:R
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));
72 else
73 init_r = State.spaceClosedSingle(phases(r),1);
74 end
75 state = State.cartesian(state,init_r);
76 end
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
81 for r=1:R
82 init_r = State.spaceClosedSingle(phases(r),n(r));
83 state = State.cartesian(state,init_r);
84 end
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
90 if sum(n) <= S(ist)
91 for r=1:R
92 init_r = State.spaceClosedSingle(phases(r),n(r));
93 state = State.cartesian(state,init_r);
94 end
95 space = State.cartesian(space,[zeros(size(state,1),R),state]);
96 else
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
99 for k=1:size(si,1)
100 % determine number of classes r jobs running in phase j
101 kstate=[];
102 for r=1:R
103 init_r = State.spaceClosedSingle(phases(r),si(k,r));
104 kstate = State.cartesian(kstate,init_r);
105 end
106 state = [repmat(mi_buf(k,:),size(kstate,1),1), kstate];
107 space = [space; state];
108 end
109 end
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));
113 if sizeEstimator > 3
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));
116 end
117 end
118
119 if sum(n) == 0
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
121 return
122 end
123 % in these policies we track an ordered buffer and
124 % the jobs in the servers
125
126 % build list of job classes in the node, with repetition
127 vi = [];
128 for r=1:R
129 if n(r)>0
130 vi=[vi, r*ones(1,n(r))];
131 end
132 end
133
134 % gen permutation of their positions in the waiting buffer
135 mi = uniqueperms(vi);
136 % now generate server states
137 if isempty(mi)
138 mi_buf = zeros(1,max(0,sum(n)-S(ist)));
139 state = zeros(1,R);
140 state = State.cartesian(state,[mi_buf,state]);
141 else
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))];
146 if isempty(mi_buf)
147 mi_buf = zeros(size(mi,1),1);
148 end
149 mi_buf_kstate = [];
150 %if mi_buf(1)>0
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;
154 %end
155 %end
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
159 si =[];
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));
163 end
164 %si = unique(si,'rows');
165 for k=1:size(si,1)
166 % determine number of class r jobs running in phase
167 % j in server state mi_srv(kjs,:) and build
168 % state
169 kstate=[];
170 for r=1:R
171 kstate = State.cartesian(kstate,State.spaceClosedSingle(phases(r),si(k,r)));
172 end
173 % generate job phases for all buffer states
174 bkstate = [];
175 for j=mi_buf(k,:) % for each job in the buffer
176 if j>0
177 bkstate = State.cartesian(bkstate,[1:phases(j)]');
178 else
179 bkstate = 0;
180 end
181 end
182 bufstate_tmp = State.cartesian(mi_buf(k,:), bkstate);
183 % here interleave positions of class and phases in
184 % buf
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)];
189 end
190 end
191 space = state;
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));
195 if sizeEstimator > 3
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));
198 end
199 end
200
201 if sum(n) == 0
202 space = zeros(1,1+sum(phases));
203 if sn.nodetype(ind) ~= NodeType.Source
204 for r=1:R
205 switch sn.procid(sn.nodeToStation(ind),r)
206 case {ProcessType.MAP, ProcessType.MMPP2}
207 space = State.cartesian(space, [1:sn.phases(ind,r)]');
208 end
209 end
210 end
211 return
212 end
213 % in these policies we track an ordered buffer and
214 % the jobs in the servers
215
216 % build list of job classes in the node, with repetition
217 vi = [];
218 for r=1:R
219 if n(r)>0
220 vi=[vi, r*ones(1,n(r))];
221 end
222 end
223
224 % gen permutation of their positions in the waiting buffer
225 mi = uniqueperms(vi);
226 % now generate server states
227 if isempty(mi)
228 mi_buf = zeros(1,max(0,sum(n)-S(ist)));
229 state = zeros(1,R);
230 state = State.cartesian(state,[mi_buf,state]);
231 else
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))];
236 if isempty(mi_buf)
237 mi_buf = zeros(size(mi,1),1);
238 end
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
242 si =[];
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));
246 end
247 %si = unique(si,'rows');
248 for k=1:size(si,1)
249 % determine number of class r jobs running in phase
250 % j in server state mi_srv(k,:) and build
251 % state
252 kstate=[];
253 map_cols = [];
254
255 for r=1:R
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
258 if si(k,r) == 0
259 init_r = State.cartesian(init_r, [1:phases(r)]');
260 else
261 if S(ist) == 1
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,:));
267 end
268 end
269 else
270 % Multiserver case: FCFS-aware phase selection
271 % Use original approach but bias toward occupied phases
272 phase_list = [];
273
274 for i=1:size(init_r,1)
275 phase_dist = init_r(i, 1:phases(r));
276
277 % Find phases that have jobs (occupied phases)
278 occupied_phases = find(phase_dist > 0);
279
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
287 if op > 1
288 extended_phases = [extended_phases, op-1];
289 end
290 if op < phases(r)
291 extended_phases = [extended_phases, op+1];
292 end
293 end
294 extended_phases = unique(extended_phases);
295 phase_list = [phase_list; extended_phases'];
296 else
297 % No jobs - include all phases (original behavior)
298 phase_list = [phase_list; [1:phases(r)]'];
299 end
300 end
301
302 % Remove duplicates and create cartesian product
303 phase_list = unique(phase_list);
304 init_r = State.cartesian(init_r, phase_list);
305 end
306 end
307 end
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);
311 end
312 end
313 kstate = kstate(:,[setdiff(1:size(kstate,2),map_cols),map_cols]);
314 state = [state; repmat(mi_buf(k,:),size(kstate,1),1), kstate];
315 end
316 end
317 space = state;
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');
322 end
323 for r=1:R
324 switch sn.routing(ind,r)
325 case {RoutingStrategy.RROBIN, RoutingStrategy.WRROBIN}
326 space = State.cartesian(space, sn.nodeparam{ind}{r}.outlinks(:));
327 end
328 end
329 case NodeType.Cache
330 switch sn.sched(ist)
331 case SchedStrategy.INF
332 % in this policies we only track the jobs in the servers
333 for r=1:R
334 init_r = State.spaceClosedSingle(phases(r),n(r));
335 state = State.cartesian(state,init_r);
336 end
337 space = State.cartesian(space,state);
338 end
339 for r=1:R
340 switch sn.routing(ind,r)
341 case RoutingStrategy.RROBIN
342 space = State.cartesian(space, sn.nodeparam{ind}{r}.outlinks(:));
343 end
344 end
345end
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
348end