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)
4% MAM/
MMAP fork-join decomposition algorithm 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.
8% Copyright (c) 2012-2026, Imperial College London
11config = options.config;
12if ~isfield(config, 'fj_sync_q_len
')
13 config.fj_sync_q_len = 2;
15if ~isfield(config, 'etaqa_trunc
')
16 config.etaqa_trunc = 8;
23V = cellsum(sn.visits);
33% Build FJ synchronization map
34fjSyncMap = sn_build_fj_sync_map(sn);
36% Prepare PH service distributions
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}
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;
52 PH{ist}{k} = map_exponential(GlobalConstants.Immediate);
55 case SchedStrategy.INF
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;
62 PH{ist}{k} = map_exponential(GlobalConstants.Immediate);
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;
73 PH{ist}{k} = map_exponential(GlobalConstants.Immediate);
79% departure-process fixed point (FJ parametric decomposition), driven on
80% the station queue lengths by the generic DA driver
83fpopts.config.da_miniter = 3; % legacy loop tested convergence only from the third sweep
84fpopts.config.da_norm = @(xn,xr) max(abs(xn(:)-xr(:))./(xr(:)+GlobalConstants.FineTol)); % relative difference
85[~, totiter] = da_fpi(@mmap_dec_sweep, QN, fpopts);
87 line_printf('\nMAM FJ parametric decomposition completed in %d iterations.
', totiter);
90% Join: derive QN/RN from parallel branch means
91for joinIdx = find(sn.nodetype == NodeType.Join)'
92 joinStat = sn.nodeToStation(joinIdx);
96 syncGroups = unique(fjSyncMap.nodeSync(joinIdx, :));
97 syncGroups = syncGroups(syncGroups > 0);
99 if TN(joinStat, r) <= 0
105 branchNodes = find(fjSyncMap.nodeSync(joinIdx, :) == gid);
106 branchRt = zeros(1, numel(branchNodes));
107 branchTput = zeros(1, numel(branchNodes));
109 for b = 1:numel(branchNodes)
110 branchStat = sn.nodeToStation(branchNodes(b));
111 if isnan(branchStat) || RN(branchStat, r) <= 0
115 branchRt(used) = RN(branchStat, r);
116 branchTput(used) = TN(branchStat, r);
118 branchRt = branchRt(1:used);
119 branchTput = branchTput(1:used);
120 if numel(branchRt) < 2
123 lambdai = 1 ./ branchRt;
125 for pow = 0:(numel(branchRt) - 1)
126 maxBranchRt = maxBranchRt + (-1)^pow * sum(1 ./ sum(nchoosek(lambdai, pow + 1), 2));
128 syncDelay = syncDelay + max(maxBranchRt - mean(branchRt), 0);
129 joinArrivalRate = joinArrivalRate + sum(branchTput);
131 RN(joinStat, r) = syncDelay;
132 QN(joinStat, r) = joinArrivalRate * syncDelay;
143 function [xnew, xref] = mmap_dec_sweep(~, itnum)
144 % Initialize departure processes (node-indexed: DEP{ind,r})
148 isForkJoin = (sn.nodetype(ind) == NodeType.Fork || sn.nodetype(ind) == NodeType.Join);
149 if sn.isstation(ind) && ~isForkJoin
150 ist = sn.nodeToStation(ind);
152 if V(ist,r) > 0 && lambda(r) > 0
153 DEP{ind,r} = map_scale(PH{ist}{r}, 1 / (lambda(r) * V(ist,r)));
155 % SLC vanishing-rate departure process; see _kb/06-solver-catalog.md
for rationale
156 DEP{ind,r} = map_exponential(1/GlobalConstants.Zero);
158 DEP{ind,r} = PH{ist}{r};
164 DEP{ind,r} = map_exponential(1/lambda(r));
166 DEP{ind,r} = map_exponential(1/GlobalConstants.Immediate);
173 % Compute arrival processes with FJ synchronization
174 ARV = solver_mam_traffic_mmap(sn, DEP, config, fjSyncMap);
178 ind = sn.stationToNode(ist);
179 switch sn.nodetype(ind)
182 TN(ist,k) = lambda(k);
188 if ~isempty(ARV{ind}) && iscell(ARV{ind})
189 if length(ARV{ind}{1}) > config.space_max
191 line_printf(
'\nArrival process at node %d is now at %d states. Compressing.', ind, length(ARV{ind}{1}));
193 ARV{ind} = mmap_compress(ARV{ind}, config);
196 finiteCapUsed =
false;
198 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.FCFSPRPRIO}
199 isFiniteCap = isfinite(sn.cap(ist));
202 [isMmck, muMmck] = mam_detect_mmck(sn, ist, K, ARV{ind});
204 aggrLambda_ist = sum(mmap_lambda(ARV{ind}),
'omitnan');
205 exactRes = qsys_mmck(aggrLambda_ist, muMmck, sn.nservers(ist), capK);
206 meanQ_fc = exactRes.meanQueueLength;
207 lossProb_fc = exactRes.lossProbability;
209 [meanQ_fc, lossProb_fc, ~] = mam_truncate_renorm( ...
210 {ARV{ind}{[1,3:end]}}, {pie{ist}{:}}, {D0{ist,:}}, capK);
212 lambdaInflow = mmap_lambda(ARV{ind});
213 lambdaInflow(isnan(lambdaInflow)) = 0;
214 TN_eff = lambdaInflow * (1 - lossProb_fc);
216 S_actual = zeros(1, K);
218 S_actual(k) = map_mean(PH{ist}{k}) * sn.nservers(ist);
221 Savg_eff = sum(TN_eff .* S_actual,
'omitnan') / sumTN;
222 Wq = max(0, meanQ_fc / sumTN - Savg_eff);
227 TN(ist,k) = TN_eff(k);
228 UN(ist,k) = TN(ist,k) * map_mean(PH{ist}{k});
230 RN(ist,k) = Wq + S_actual(k);
231 QN(ist,k) = TN(ist,k) * RN(ist,k);
237 finiteCapUsed =
true;
239 rho_ist_classes = mmap_lambda(ARV{ind}) .* arrayfun(@(k) map_mean(PH{ist}{k}), 1:K);
240 rho_ist_classes(isnan(rho_ist_classes)) = 0;
241 % Exclude SLC from FCFS saturation test; see _kb/06-solver-catalog.md
for rationale
242 rho_ist = sum(rho_ist_classes(~sn.isslc));
243 if rho_ist < 1 - GlobalConstants.FineTol
244 % Exact MAP/MAP/1
for correlated single-
class service;
245 % see _kb/06-solver-catalog.md
for rationale
246 useMapMap1 = (K == 1) && (sn.nservers(ist) == 1) && ...
247 (abs(map_acf(PH{ist}{1}, 1)) > GlobalConstants.CoarseTol);
251 ql = Q_CT_MAP_MAP_1(Carv0, Carv1, PH{ist}{1}{1}, PH{ist}{1}{2},
'MaxNumComp', 100000);
253 QN(ist,1) = sum((0:numel(ql)-1)
' .* ql);
255 [Qret{1:K}, ~] = MMAPPH1FCFS({ARV{ind}{[1,3:end]}}, {pie{ist}{:}}, {D0{ist,:}}, 'ncMoms
', 1, 'ncDistr
', 2);
257 QN(ist,k) = sum(Qret{k});
261 % Bound queue lengths under overload (no NaN);
262 % see _kb/06-solver-catalog.md for rationale
264 if isfinite(sn.njobs(k))
265 QN(ist,k) = sn.njobs(k);
267 QN(ist,k) = 1/GlobalConstants.FineTol;
271 TN(ist,:) = mmap_lambda(ARV{ind});
273 case SchedStrategy.PS
274 TN(ist,:) = mmap_lambda(ARV{ind});
276 UN(ist,k) = TN(ist,k) * S(ist,k);
278 % Exclude SLC from PS sharing denominator; see _kb/06-solver-catalog.md for rationale
279 Uden = min([1-GlobalConstants.FineTol, sum(UN(ist,~sn.isslc))]);
281 QN(ist,k) = UN(ist,k)/(1-Uden);
287 UN(ist,k) = TN(ist,k) * map_mean(PH{ist}{k});
288 QN(ist,k) = QN(ist,k) + TN(ist,k)*(map_mean(PH{ist}{k})*sn.nservers(ist)) * (sn.nservers(ist)-1)/sn.nservers(ist);
289 RN(ist,k) = QN(ist,k) ./ TN(ist,k);
295 case SchedStrategy.INF
296 if ~isempty(ARV{ind}) && iscell(ARV{ind})
297 TN(ist,:) = mmap_lambda(ARV{ind});
301 UN(ist,k) = S(ist,k)*TN(ist,k);
302 QN(ist,k) = TN(ist,k)*S(ist,k);
303 RN(ist,k) = S(ist,k);
306 case SchedStrategy.EXT
307 % Source: TN already set above
312 % Update departure processes
314 ind = sn.stationToNode(ist);
315 switch sn.nodetype(ind)
317 if ~isempty(ARV{ind}) && iscell(ARV{ind})
319 case {SchedStrategy.FCFS, SchedStrategy.HOL, SchedStrategy.FCFSPRPRIO}
321 A = mmap_hide(ARV{ind}, setdiff(1:K,r));
325 etaqa_n = config.etaqa_trunc;
326 etaqa_sz = (etaqa_n+1)*na*ns;
327 rho = sum(UN(ist,:));
328 if etaqa_sz <= config.space_max && rho < 1-GlobalConstants.FineTol
330 DEP{ind,r} = qbd_depproc_etaqa(A, Srv, etaqa_n);
331 DEP{ind,r} = map_normalize(DEP{ind,r});
338 if V(ist,r) > 0 && lambda(r) > 0
339 DEP{ind,r} = map_scale(DEP{ind,r}, 1 / (lambda(r) * V(ist,r)));
342 case SchedStrategy.PS
344 A = mmap_hide(ARV{ind}, setdiff(1:K,r));
348 etaqa_n = config.etaqa_trunc;
349 etaqa_sz = (etaqa_n+1)*na*ns;
350 rho = sum(UN(ist,:));
351 if V(ist,r) > 0 && lambda(r) > 0
352 if etaqa_sz <= config.space_max && rho < 1-GlobalConstants.FineTol
354 DEP{ind,r} = qbd_depproc_etaqa_ps(A, Srv, etaqa_n);
355 DEP{ind,r} = map_normalize(DEP{ind,r});
362 DEP{ind,r} = map_scale(DEP{ind,r}, 1 / (lambda(r) * V(ist,r)));
370 DEP{ind,r} = map_exponential(1/TN(ist,r));