LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_mam_basic_mmap_closed.m
1function [QN,UN,RN,TN,CN,XN,totiter] = solver_mam_basic_mmap_closed(sn, options)
2% [QN,UN,RN,TN,CN,XN,TOTITER] = SOLVER_MAM_BASIC_MMAP_CLOSED(SN, OPTIONS)
3%
4% Closed-network wrapper around solver_mam_basic_mmap_inner. Drives a
5% per-class bisection on the surrogate arrival rate LAMBDA so that the
6% inner solver's queue lengths match the closed population SN.NJOBS.
7% Mirrors the outer-loop structure of solver_mna_closed.
8%
9% Copyright (c) 2012-2026, Imperial College London
10% All rights reserved.
11
12K = sn.nclasses;
13M = sn.nstations;
14S = 1./sn.rates;
15
16% Per-class bisection bounds: upper = slowest non-INF station rate for that class
17nonInfStations = find(sn.nservers < Inf);
18lambda_lb = zeros(1,K);
19lambda_ub = zeros(1,K);
20for k=1:K
21 rates_k = [];
22 if ~isempty(nonInfStations)
23 rates_k = sn.rates(nonInfStations, k);
24 rates_k = rates_k(isfinite(rates_k) & rates_k > 0);
25 end
26 if isempty(rates_k)
27 infStations = find(sn.nservers == Inf);
28 rates_inf = sn.rates(infStations, k);
29 rates_inf = rates_inf(isfinite(rates_inf) & rates_inf > 0);
30 if isempty(rates_inf)
31 lambda_ub(k) = 1;
32 else
33 lambda_ub(k) = max(rates_inf);
34 end
35 else
36 lambda_ub(k) = min(rates_k);
37 end
38end
39
40QNc = sn.njobs;
41QNc(~isfinite(QNc)) = 0; % open classes contribute 0; only closed populations gate convergence
42QN_chain = zeros(1,K);
43
44it_out = 0;
45lambda = lambda_ub;
46% Self-looping classes are pinned by the SLC clamp below; they must not
47% contribute a (saturating) surrogate arrival stream to the inner algorithm.
48lambda(sn.isslc) = 0;
49
50inner_options = options;
51inner_options.iter_max = max(20, ceil(options.iter_max/10));
52inner_options.verbose = false;
53% Cap the MMAP phase-dimension truncation at the same value used by the
54% solver_mna_closed wrapper this routine mirrors. The analyzer default
55% (space_max=128) lets the FJ synchronization/superposition inflate the
56% arrival MMAP to ~128 phases, whose ETAQA/QBD solve is O(dim^3), while the
57% bisection re-enters the inner solve ~29 times: ~154s on a 3-class model
58% that CTMC solves in 0.7s. Measured, the compressed result is bit-identical
59% from space_max=4 up to 128 (the surrogate arrival stream carries only a
60% few effective phases), so the inflated dimension is pure wasted work.
61% Capping at 16 is lossless here and matches solver_mna_closed.
62if ~isfield(inner_options.config, 'space_max') || inner_options.config.space_max > 16
63 inner_options.config.space_max = 16;
64end
65
66QN = zeros(M,K);
67UN = zeros(M,K);
68RN = zeros(M,K);
69TN = zeros(M,K);
70CN = zeros(1,K);
71XN = zeros(1,K);
72
73% Last successful inner-algorithm outputs (for fallback if final trial diverges)
74QN_last = QN; UN_last = UN; RN_last = RN;
75TN_last = TN; CN_last = CN; XN_last = XN;
76have_good = false;
77
78bisect_tol = max(options.iter_tol, 1e-3);
79
80while max(abs(QN_chain - QNc)) > bisect_tol && it_out < options.iter_max
81 it_out = it_out + 1;
82 if it_out > 1
83 bracket_collapsed = true;
84 for k=1:K
85 if ~isfinite(QNc(k)) || QNc(k) == 0 || sn.isslc(k)
86 continue;
87 end
88 if QN_chain(k) < QNc(k)
89 lambda_lb(k) = lambda(k);
90 else
91 lambda_ub(k) = lambda(k);
92 end
93 lambda(k) = 0.5 * (lambda_lb(k) + lambda_ub(k));
94 % Bisection can still refine class k only while its bracket is
95 % wider than the precision floor below which LAMBDA cannot move
96 % any reported metric.
97 if (lambda_ub(k) - lambda_lb(k)) > GlobalConstants.FineTol * max(1, abs(lambda_ub(k)))
98 bracket_collapsed = false;
99 end
100 end
101 % Bracket-width stagnation break. The loop condition above tests only
102 % the residual population gap, which never closes when no surrogate
103 % LAMBDA reproduces the closed population (the per-class targets are
104 % not simultaneously attainable). The bisection then keeps halving
105 % brackets that have already narrowed past FineTol, re-entering the
106 % inner algorithm for changes it cannot resolve. Stop once every
107 % active bracket has collapsed and keep the outputs in hand.
108 if bracket_collapsed
109 it_out = it_out - 1;
110 break;
111 end
112 end
113
114 try
115 [QN, UN, RN, TN, CN, XN, ~] = solver_mam_basic_mmap_inner(sn, inner_options, lambda);
116 algorithm_ok = true;
117 catch
118 % Inner algorithm diverged (typically MMAPPH1FCFS / lyap NaN under
119 % saturation). Treat all chains as overloaded so the bisection
120 % drops lambda on its next step.
121 algorithm_ok = false;
122 end
123
124 if algorithm_ok
125 % SLC clamp: all jobs at refstat for self-looping classes
126 for k=1:K
127 if sn.isslc(k)
128 QN(:,k) = 0;
129 QN(sn.refstat(k), k) = sn.njobs(k);
130 end
131 end
132 QN_chain = sum(QN, 1);
133 QN_chain(isnan(QN_chain) | isinf(QN_chain)) = 1/GlobalConstants.FineTol;
134 QN_last = QN; UN_last = UN; RN_last = RN;
135 TN_last = TN; CN_last = CN; XN_last = XN;
136 have_good = true;
137 else
138 QN_chain = ones(1,K) * (1/GlobalConstants.FineTol);
139 end
140end
141
142% If the last trial diverged, fall back to the most recent successful one
143if ~algorithm_ok && have_good
144 QN = QN_last; UN = UN_last; RN = RN_last;
145 TN = TN_last; CN = CN_last; XN = XN_last;
146end
147
148% Final SLC pass: pin throughput/utilisation at refstat (mirrors solver_mna_closed)
149for k=1:K
150 if sn.isslc(k)
151 QN(:,k) = 0;
152 ist = sn.refstat(k);
153 QN(ist, k) = sn.njobs(k);
154 TN(ist, k) = sn.njobs(k) * sn.rates(ist, k);
155 if TN(ist, k) > 0
156 RN(ist, k) = QN(ist, k) / TN(ist, k);
157 else
158 RN(ist, k) = 0;
159 end
160 UN(ist, k) = S(ist, k) * TN(ist, k);
161 end
162end
163
164% Population redistribution within chain (matches solver_mna_closed:323-328)
165for c=1:sn.nchains
166 inchain = sn.inchain{c};
167 if isfinite(sn.njobs(c))
168 sumQ = sum(sum(QN(:,inchain)));
169 if sumQ > 0
170 QN(:,inchain) = sn.njobs(c) .* QN(:,inchain) / sumQ;
171 end
172 end
173end
174
175% Delay/INF utilisation = mean number of jobs (matches solver_mna_closed)
176for ist=1:sn.nstations
177 if sn.sched(ist) == SchedStrategy.INF
178 UN(ist,:) = QN(ist,:);
179 end
180end
181
182CN = sum(RN, 1);
183QN(isnan(QN)) = 0;
184UN(isnan(UN)) = 0;
185RN(isnan(RN)) = 0;
186TN(isnan(TN)) = 0;
187CN(isnan(CN)) = 0;
188totiter = it_out;
189end
Definition Station.m:245