LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_mam_basic_mmap_inner.m
1function [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_basic_mmap_inner(sn, options, lambda)
2% [QN,UN,RN,TN,CN,XN,TOTITER] = SOLVER_MAM_BASIC_MMAP_INNER(SN, OPTIONS, LAMBDA)
3%
4% MAM/MMAP fork-join decomposition kernel parameterised by per-class arrival
5% rates LAMBDA. Performs the departure-process refinement loop only;
6% population enforcement (closed networks) is the wrapper's responsibility.
7%
8% Copyright (c) 2012-2026, Imperial College London
9% All rights reserved.
10
11config = options.config;
12if ~isfield(config, 'fj_sync_q_len')
13 config.fj_sync_q_len = 2;
14end
15if ~isfield(config, 'etaqa_trunc')
16 config.etaqa_trunc = 8;
17end
18
19PH = sn.proc;
20I = sn.nnodes;
21M = sn.nstations;
22K = sn.nclasses;
23V = cellsum(sn.visits);
24S = 1./sn.rates;
25
26QN = zeros(M,K);
27UN = zeros(M,K);
28RN = zeros(M,K);
29TN = zeros(M,K);
30CN = zeros(1,K);
31XN = zeros(1,K);
32
33% Build FJ synchronization map
34fjSyncMap = sn_build_fj_sync_map(sn);
35
36% Prepare PH service distributions
37pie = {};
38D0 = {};
39for ist=1:M
40 switch sn.sched(ist)
41 case SchedStrategy.EXT
42 TN(ist,:) = sn.rates(ist,:);
43 TN(ist,isnan(TN(ist,:))) = 0;
44 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.FCFSPRPRIO}
45 for k=1:K
46 PH{ist}{k} = map_scale(PH{ist}{k}, map_mean(PH{ist}{k})/sn.nservers(ist));
47 pie{ist}{k} = map_pie(PH{ist}{k});
48 D0{ist,k} = PH{ist}{k}{1};
49 if any(isnan(D0{ist,k}))
50 D0{ist,k} = -GlobalConstants.Immediate;
51 pie{ist}{k} = 1;
52 PH{ist}{k} = map_exponential(GlobalConstants.Immediate);
53 end
54 end
55 case SchedStrategy.INF
56 for k=1:K
57 pie{ist}{k} = map_pie(PH{ist}{k});
58 D0{ist,k} = PH{ist}{k}{1};
59 if any(isnan(D0{ist,k}))
60 D0{ist,k} = -GlobalConstants.Immediate;
61 pie{ist}{k} = 1;
62 PH{ist}{k} = map_exponential(GlobalConstants.Immediate);
63 end
64 end
65 case SchedStrategy.PS
66 for k=1:K
67 PH{ist}{k} = map_scale(PH{ist}{k}, map_mean(PH{ist}{k})/sn.nservers(ist));
68 pie{ist}{k} = map_pie(PH{ist}{k});
69 D0{ist,k} = PH{ist}{k}{1};
70 if any(isnan(D0{ist,k}))
71 D0{ist,k} = -GlobalConstants.Immediate;
72 pie{ist}{k} = 1;
73 PH{ist}{k} = map_exponential(GlobalConstants.Immediate);
74 end
75 end
76 end
77end
78
79it_max = options.iter_max;
80for it=1:it_max
81 % Initialize departure processes (node-indexed: DEP{ind,r})
82 if it == 1
83 DEP = cell(I,K);
84 for ind=1:I
85 isForkJoin = (sn.nodetype(ind) == NodeType.Fork || sn.nodetype(ind) == NodeType.Join);
86 if sn.isstation(ind) && ~isForkJoin
87 ist = sn.nodeToStation(ind);
88 for r=1:K
89 if V(ist,r) > 0 && lambda(r) > 0
90 DEP{ind,r} = map_scale(PH{ist}{r}, 1 / (lambda(r) * V(ist,r)));
91 elseif sn.isslc(r)
92 % Self-looping classes are handled separately by the
93 % closed-network wrapper (their metrics are pinned at the
94 % reference station). They must not inject a saturating
95 % arrival stream into the shared-queue decomposition, so
96 % give them a vanishing-rate departure process.
97 DEP{ind,r} = map_exponential(1/GlobalConstants.Zero);
98 else
99 DEP{ind,r} = PH{ist}{r};
100 end
101 end
102 else
103 for r=1:K
104 if lambda(r) > 0
105 DEP{ind,r} = map_exponential(1/lambda(r));
106 else
107 DEP{ind,r} = map_exponential(1/GlobalConstants.Immediate);
108 end
109 end
110 end
111 end
112 end
113
114 % Compute arrival processes with FJ synchronization
115 ARV = solver_mam_traffic_mmap(sn, DEP, config, fjSyncMap);
116
117 QN_1 = QN;
118 for ist=1:M
119 ind = sn.stationToNode(ist);
120 switch sn.nodetype(ind)
121 case NodeType.Join
122 for k=1:K
123 TN(ist,k) = lambda(k);
124 UN(ist,k) = 0;
125 QN(ist,k) = 0;
126 RN(ist,k) = 0;
127 end
128 case NodeType.Queue
129 if ~isempty(ARV{ind}) && iscell(ARV{ind})
130 if length(ARV{ind}{1}) > config.space_max
131 if options.verbose
132 line_printf('\nArrival process at node %d is now at %d states. Compressing.', ind, length(ARV{ind}{1}));
133 end
134 ARV{ind} = mmap_compress(ARV{ind}, config);
135 end
136
137 finiteCapUsed = false;
138 switch sn.sched(ist)
139 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.FCFSPRPRIO}
140 isFiniteCap = isfinite(sn.cap(ist));
141 if isFiniteCap
142 capK = sn.cap(ist);
143 [isMmck, muMmck] = mam_detect_mmck(sn, ist, K, ARV{ind});
144 if isMmck
145 aggrLambda_ist = sum(mmap_lambda(ARV{ind}), 'omitnan');
146 exactRes = qsys_mmck(aggrLambda_ist, muMmck, sn.nservers(ist), capK);
147 meanQ_fc = exactRes.meanQueueLength;
148 lossProb_fc = exactRes.lossProbability;
149 else
150 [meanQ_fc, lossProb_fc, ~] = mam_truncate_renorm( ...
151 {ARV{ind}{[1,3:end]}}, {pie{ist}{:}}, {D0{ist,:}}, capK);
152 end
153 lambdaInflow = mmap_lambda(ARV{ind});
154 lambdaInflow(isnan(lambdaInflow)) = 0;
155 TN_eff = lambdaInflow * (1 - lossProb_fc);
156 sumTN = sum(TN_eff);
157 S_actual = zeros(1, K);
158 for k=1:K
159 S_actual(k) = map_mean(PH{ist}{k}) * sn.nservers(ist);
160 end
161 if sumTN > 0
162 Savg_eff = sum(TN_eff .* S_actual, 'omitnan') / sumTN;
163 Wq = max(0, meanQ_fc / sumTN - Savg_eff);
164 else
165 Wq = 0;
166 end
167 for k=1:K
168 TN(ist,k) = TN_eff(k);
169 UN(ist,k) = TN(ist,k) * map_mean(PH{ist}{k});
170 if TN(ist,k) > 0
171 RN(ist,k) = Wq + S_actual(k);
172 QN(ist,k) = TN(ist,k) * RN(ist,k);
173 else
174 RN(ist,k) = 0;
175 QN(ist,k) = 0;
176 end
177 end
178 finiteCapUsed = true;
179 else
180 rho_ist_classes = mmap_lambda(ARV{ind}) .* arrayfun(@(k) map_mean(PH{ist}{k}), 1:K);
181 rho_ist_classes(isnan(rho_ist_classes)) = 0;
182 % Exclude self-looping classes: they are pinned
183 % by the closed wrapper and must not drive the
184 % FCFS station into the saturation branch.
185 rho_ist = sum(rho_ist_classes(~sn.isslc));
186 if rho_ist < 1 - GlobalConstants.FineTol
187 [Qret{1:K}, ~] = MMAPPH1FCFS({ARV{ind}{[1,3:end]}}, {pie{ist}{:}}, {D0{ist,:}}, 'ncMoms', 1, 'ncDistr', 2);
188 for k=1:K
189 QN(ist,k) = sum(Qret{k});
190 end
191 else
192 % Saturation: bound queue lengths so the
193 % wrapper's bisection can recognise overload
194 % without NaNs propagating from MMAPPH1FCFS.
195 for k=1:K
196 if isfinite(sn.njobs(k))
197 QN(ist,k) = sn.njobs(k);
198 else
199 QN(ist,k) = 1/GlobalConstants.FineTol;
200 end
201 end
202 end
203 TN(ist,:) = mmap_lambda(ARV{ind});
204 end
205 case SchedStrategy.PS
206 TN(ist,:) = mmap_lambda(ARV{ind});
207 for k=1:K
208 UN(ist,k) = TN(ist,k) * S(ist,k);
209 end
210 % Self-looping classes are pinned by the closed
211 % wrapper and must not count toward the PS sharing
212 % denominator (a single permanent SLC job would
213 % otherwise drive the queue to saturation).
214 Uden = min([1-GlobalConstants.FineTol, sum(UN(ist,~sn.isslc))]);
215 for k=1:K
216 QN(ist,k) = UN(ist,k)/(1-Uden);
217 end
218 end
219
220 if ~finiteCapUsed
221 for k=1:K
222 UN(ist,k) = TN(ist,k) * map_mean(PH{ist}{k});
223 QN(ist,k) = QN(ist,k) + TN(ist,k)*(map_mean(PH{ist}{k})*sn.nservers(ist)) * (sn.nservers(ist)-1)/sn.nservers(ist);
224 RN(ist,k) = QN(ist,k) ./ TN(ist,k);
225 end
226 end
227 end
228 otherwise
229 switch sn.sched(ist)
230 case SchedStrategy.INF
231 if ~isempty(ARV{ind}) && iscell(ARV{ind})
232 TN(ist,:) = mmap_lambda(ARV{ind});
233 end
234 for k=1:K
235 if TN(ist,k) > 0
236 UN(ist,k) = S(ist,k)*TN(ist,k);
237 QN(ist,k) = TN(ist,k)*S(ist,k);
238 RN(ist,k) = S(ist,k);
239 end
240 end
241 case SchedStrategy.EXT
242 % Source: TN already set above
243 end
244 end
245 end
246
247 if it >= 3 && max(abs(QN(:)-QN_1(:))./(QN_1(:)+GlobalConstants.FineTol)) < options.iter_tol
248 break;
249 end
250
251 % Update departure processes
252 for ist=1:M
253 ind = sn.stationToNode(ist);
254 switch sn.nodetype(ind)
255 case NodeType.Queue
256 if ~isempty(ARV{ind}) && iscell(ARV{ind})
257 switch sn.sched(ist)
258 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.FCFSPRPRIO}
259 for r=1:K
260 A = mmap_hide(ARV{ind}, setdiff(1:K,r));
261 Srv = PH{ist}{r};
262 na = length(A{1});
263 ns = length(Srv{1});
264 etaqa_n = config.etaqa_trunc;
265 etaqa_sz = (etaqa_n+1)*na*ns;
266 rho = sum(UN(ist,:));
267 if etaqa_sz <= config.space_max && rho < 1-GlobalConstants.FineTol
268 try
269 DEP{ind,r} = qbd_depproc_etaqa(A, Srv, etaqa_n);
270 DEP{ind,r} = map_normalize(DEP{ind,r});
271 catch
272 DEP{ind,r} = Srv;
273 end
274 else
275 DEP{ind,r} = Srv;
276 end
277 if V(ist,r) > 0 && lambda(r) > 0
278 DEP{ind,r} = map_scale(DEP{ind,r}, 1 / (lambda(r) * V(ist,r)));
279 end
280 end
281 case SchedStrategy.PS
282 for r=1:K
283 A = mmap_hide(ARV{ind}, setdiff(1:K,r));
284 Srv = PH{ist}{r};
285 na = length(A{1});
286 ns = length(Srv{1});
287 etaqa_n = config.etaqa_trunc;
288 etaqa_sz = (etaqa_n+1)*na*ns;
289 rho = sum(UN(ist,:));
290 if V(ist,r) > 0 && lambda(r) > 0
291 if etaqa_sz <= config.space_max && rho < 1-GlobalConstants.FineTol
292 try
293 DEP{ind,r} = qbd_depproc_etaqa_ps(A, Srv, etaqa_n);
294 DEP{ind,r} = map_normalize(DEP{ind,r});
295 catch
296 DEP{ind,r} = Srv;
297 end
298 else
299 DEP{ind,r} = Srv;
300 end
301 DEP{ind,r} = map_scale(DEP{ind,r}, 1 / (lambda(r) * V(ist,r)));
302 end
303 end
304 end
305 end
306 case NodeType.Join
307 for r=1:K
308 if TN(ist,r) > 0
309 DEP{ind,r} = map_exponential(1/TN(ist,r));
310 end
311 end
312 end
313 end
314end
315
316totiter = it;
317if options.verbose
318 line_printf('\nMAM FJ parametric decomposition completed in %d iterations.', it);
319end
320
321% Join: derive QN/RN from parallel branch means
322for joinIdx = find(sn.nodetype == NodeType.Join)'
323 joinStat = sn.nodeToStation(joinIdx);
324 if isnan(joinStat)
325 continue;
326 end
327 syncGroups = unique(fjSyncMap.nodeSync(joinIdx, :));
328 syncGroups = syncGroups(syncGroups > 0);
329 for r = 1:K
330 if TN(joinStat, r) <= 0
331 continue;
332 end
333 syncDelay = 0;
334 joinArrivalRate = 0;
335 for gid = syncGroups
336 branchNodes = find(fjSyncMap.nodeSync(joinIdx, :) == gid);
337 branchRt = zeros(1, numel(branchNodes));
338 branchTput = zeros(1, numel(branchNodes));
339 used = 0;
340 for b = 1:numel(branchNodes)
341 branchStat = sn.nodeToStation(branchNodes(b));
342 if isnan(branchStat) || RN(branchStat, r) <= 0
343 continue;
344 end
345 used = used + 1;
346 branchRt(used) = RN(branchStat, r);
347 branchTput(used) = TN(branchStat, r);
348 end
349 branchRt = branchRt(1:used);
350 branchTput = branchTput(1:used);
351 if numel(branchRt) < 2
352 continue;
353 end
354 lambdai = 1 ./ branchRt;
355 maxBranchRt = 0;
356 for pow = 0:(numel(branchRt) - 1)
357 maxBranchRt = maxBranchRt + (-1)^pow * sum(1 ./ sum(nchoosek(lambdai, pow + 1), 2));
358 end
359 syncDelay = syncDelay + max(maxBranchRt - mean(branchRt), 0);
360 joinArrivalRate = joinArrivalRate + sum(branchTput);
361 end
362 RN(joinStat, r) = syncDelay;
363 QN(joinStat, r) = joinArrivalRate * syncDelay;
364 UN(joinStat, r) = 0;
365 end
366end
367
368CN = sum(RN,1);
369QN(isnan(QN)) = 0;
370RN(isnan(RN)) = 0;
371UN(isnan(UN)) = 0;
372TN(isnan(TN)) = 0;
373end