LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_amvald.m
1function [Q,U,R,T,C,X,lG,totiter,converged] = solver_amvald(sn, Lchain,STchain,Vchain,alpha,Nchain,SCVchain,refstatchain, options)
2totiter = 0;
3max_totiter = min(options.iter_max, 10000); % Hard cap for stability
4
5M = sn.nstations;
6K = sn.nchains;
7Nt = sum(Nchain(isfinite(Nchain)));
8delta = (Nt - 1) / Nt;
9deltaclass = (Nchain - 1) ./ Nchain;
10deltaclass(isinf(Nchain)) = 1;
11tol = options.iter_tol;
12nservers = sn.nservers;
13schedparam = sn.schedparam;
14lldscaling = sn.lldscaling;
15cdscaling = sn.cdscaling;
16jdscaling = sn.jdscaling;
17sched = sn.sched;
18classprio = sn.classprio;
19
20Uchain = zeros(M,K);
21Tchain = zeros(M,K);
22Cchain_s = zeros(1,K);
23% Initialized so an early wall-clock timeout (options.timeout) that breaks before
24% any forward evaluation still yields a valid empty result instead of erroring.
25Wchain = zeros(M,K);
26Rchain = zeros(M,K);
27STeff = STchain;
28
29%% initialize Q,X, U
30Qchain = options.init_sol;
31if ~isempty(Qchain) && ~isequal(size(Qchain), [M K])
32 % stale warm-start hint from a different station/chain basis
33 Qchain = [];
34end
35if isempty(Qchain)
36 % balanced initialization
37 Qchain = ones(M,K);
38 Qchain = Qchain ./ repmat(sum(Qchain,1),size(Qchain,1),1) .* repmat(Nchain,size(Qchain,1),1);
39 Qchain(isinf(Qchain))=0; % open classes
40 for r=find(isinf(Nchain)) % open classes
41 Qchain(refstatchain(r),r)=0;
42 end
43end
44
45nnzclasses = find(Nchain>0);
46Xchain = 1./sum(STchain,1);
47for r=find(isinf(Nchain)) % open classes
48 if STchain(refstatchain(r),r) > 0
49 Xchain(r) = 1 ./ STchain(refstatchain(r),r);
50 else
51 % Open chain with no arrivals (e.g. Disabled source arrival): the
52 % throughput is 0, not 1/0 = Inf. An Inf here poisons the FCFS
53 % utilization renormalization below and zeroes the utilization of
54 % the other, active open classes.
55 Xchain(r) = 0;
56 end
57end
58
59for k=1:M
60 for r=nnzclasses
61 if isinf(nservers(k)) % infinite server
62 Uchain(k,r) = Vchain(k,r)*STchain(k,r)*Xchain(r);
63 else
64 Uchain(k,r) = Vchain(k,r)*STchain(k,r)*Xchain(r)/nservers(k);
65 end
66 end
67end
68
69switch options.method
70 case {'lin','qdlin'}
71 gamma = zeros(K,M,K); % class-based customer fraction corrections
72 tau = zeros(K,K); % throughput difference
73 otherwise
74 gamma = zeros(K,M); % total customer fraction corrections
75 tau = zeros(K,K); % throughput difference
76end
77
78%% main loop
79omicron = 0.5; % under-relaxation parameter
80outer_iter = 0;
81while (outer_iter < 2 || max(max(abs(Qchain-QchainOuter_1))) > tol) && outer_iter < sqrt(options.iter_max) && totiter <= max_totiter && ~lineTimeoutExceeded(options)
82 outer_iter = outer_iter + 1;
83 QchainOuter_1 = Qchain;
84 XchainOuter_1 = Xchain;
85 UchainOuter_1 = Uchain;
86
87 if isfinite(Nt) && Nt>0
88 switch options.method
89 %case {'aql','qdaql'}
90 %line_error(mfilename,'AQL is currently disabled in SolverMVA, please use the SolverJMT implementation (method jmva.aql).');
91 case {'lin','qdlin'}
92 % iteration at population N-1_s
93 for s=1:K
94 if isfinite(Nchain(s)) % don't recur on open classes
95 iter_s = 0;
96 Nchain_s = oner(Nchain,s);
97 Qchain_s = Qchain * (Nt-1)/Nt;
98 Xchain_s = Xchain * (Nt-1)/Nt;
99 Uchain_s = Uchain * (Nt-1)/Nt;
100 while (iter_s < 2 || max(max(abs(Qchain_s-Qchain_s_1))) > tol) && iter_s <= sqrt(options.iter_max) && ~lineTimeoutExceeded(options)
101 iter_s = iter_s + 1;
102
103 Qchain_s_1 = Qchain_s;
104 Xchain_s_1 = Xchain_s;
105 Uchain_s_1 = Uchain_s;
106
107 [Wchain_s, STeff_s] = solver_amvald_forward(M, K, nservers, schedparam, lldscaling, cdscaling, jdscaling, sched, classprio, gamma, tau, Qchain_s_1, Xchain_s_1, Uchain_s_1, STchain, Vchain, Nchain_s, SCVchain, options);
108 totiter = totiter + 1;
109 if totiter >= max_totiter
110 break
111 end
112
113 %% update other metrics
114 for r=nnzclasses
115 if sum(Wchain_s(:,r)) == 0
116 Xchain_s(r) = 0;
117 else
118 if isinf(Nchain_s(r))
119 Cchain_s(r) = Vchain(:,r)' * Wchain_s(:,r);
120 % X(r) remains constant
121 elseif Nchain(r)==0
122 Xchain_s(r) = 0;
123 Cchain_s(r) = 0;
124 else
125 Cchain_s(r) = Vchain(:,r)' * Wchain_s(:,r);
126 Xchain_s(r) = omicron * Nchain_s(r) / Cchain_s(r) + (1-omicron) * Xchain_s_1(r);
127 end
128 end
129 for k=1:M
130 Rchain_s(k,r) = Vchain(k,r) * Wchain_s(k,r);
131 Qchain_s(k,r) = omicron * Xchain_s(r) * Vchain(k,r) * Wchain_s(k,r) + (1-omicron) * Qchain_s_1(k,r);
132 Tchain_s(k,r) = Xchain_s(r) * Vchain(k,r);
133 Uchain_s(k,r) = omicron * Vchain(k,r) * STeff_s(k,r) * Xchain_s(r) + (1-omicron) * Uchain_s_1(k,r);
134 end
135 end
136 end
137
138 % Gamma update: only 'lin' takes the per-class form, 'qdlin'
139 % included takes the class-aggregate one. Do NOT add 'qdlin'
140 % to the 'lin' case without re-baselining the AMVA goldens.
141 % see _kb/06-solver-catalog.md (MVA section) for why.
142 switch options.method
143 case {'lin'}
144 for k=1:M
145 for r=nnzclasses
146 if ~isinf(Nchain(r)) && Nchain_s(r)>0
147 gamma(s,k,r) = Qchain_s_1(k,r)./Nchain_s(r) - QchainOuter_1(k,r)./Nchain(r);
148 end
149 end
150 end
151 case {'qdlin'} % class-aggregate correction, see note above
152 for k=1:M
153 gamma(s,k) = sum(Qchain_s_1(k,:),2)/(Nt-1) - sum(QchainOuter_1(k,:),2)/Nt;
154 end
155 otherwise
156 for k=1:M
157 gamma(s,k) = sum(Qchain_s_1(k,:),2)/(Nt-1) - sum(QchainOuter_1(k,:),2)/Nt;
158 end
159 end
160
161 for r=nnzclasses
162 tau(s,r) = Xchain_s_1(r) - XchainOuter_1(r); % save throughput for priority AMVA
163 end
164 end
165 % Check if iteration limit exceeded (break out of for s loop)
166 if totiter >= max_totiter
167 break
168 end
169 end
170 end
171 end
172 % Check if iteration limit exceeded (break out of outer while loop)
173 if totiter >= max_totiter
174 break
175 end
176
177 iter = 0;
178 % iteration at population N
179 while (iter < 2 || max(max(abs(Qchain-Qchain_1))) > tol) && iter <= sqrt(options.iter_max) && ~lineTimeoutExceeded(options)
180 iter = iter + 1;
181
182 Qchain_1 = Qchain;
183 Xchain_1 = Xchain;
184 Uchain_1 = Uchain;
185
186 [Wchain, STeff] = solver_amvald_forward(M, K, nservers, schedparam, lldscaling, cdscaling, jdscaling, sched, classprio, gamma, tau, Qchain_1, Xchain_1, Uchain_1, STchain, Vchain, Nchain, SCVchain, options);
187 totiter = totiter + 1;
188 if totiter >= max_totiter
189 break
190 end
191
192
193 %% update other metrics
194 for r=nnzclasses
195 if sum(Wchain(:,r)) == 0
196 Xchain(r) = 0;
197 else
198 if isinf(Nchain(r))
199 Cchain_s(r) = Vchain(:,r)'*Wchain(:,r);
200 % X(r) remains constant
201 elseif Nchain(r)==0
202 Xchain(r) = 0;
203 Cchain_s(r) = 0;
204 else
205 Cchain_s(r) = Vchain(:,r)'*Wchain(:,r);
206 Xchain(r) = omicron * Nchain(r) / Cchain_s(r) + (1-omicron) *Xchain_1(r);
207 end
208 end
209 for k=1:M
210 Rchain(k,r) = Vchain(k,r) * Wchain(k,r);
211 Qchain(k,r) = omicron * Xchain(r) * Vchain(k,r) * Wchain(k,r) + (1-omicron) * Qchain_1(k,r);
212 Tchain(k,r) = Xchain(r) * Vchain(k,r);
213 Uchain(k,r) = omicron * Vchain(k,r) * STeff(k,r) * Xchain(r) + (1-omicron) * Uchain_1(k,r);
214 end
215 end
216 end
217end
218
219
220% the next block is a coarse approximation for LD and CD, would need
221% cdterm and qterm in it but these are hidden within the iteration calls
222for k=1:M
223 for r=1:K
224 if Vchain(k,r) * STeff(k,r) >0
225 switch sn.sched(k)
226 case {SchedStrategy.FCFS, SchedStrategy.SIRO, SchedStrategy.PS, SchedStrategy.LCFSPR, SchedStrategy.DPS, SchedStrategy.HOL}
227 if sum(Uchain(k,:))>1
228 Uchain(k,r) = min(1,sum(Uchain(k,:))) * Vchain(k,r) * STeff(k,r) * Xchain(r) / ((Vchain(k,:) .* STeff(k,:)) * Xchain(:));
229 end
230 end
231 end
232 end
233end
234
235Rchain = Qchain./Tchain;
236Xchain(~isfinite(Xchain))=0;
237Uchain(~isfinite(Uchain))=0;
238%Qchain(~isfinite(Qchain))=0;
239Rchain(~isfinite(Rchain))=0;
240
241Xchain(Nchain==0)=0;
242Uchain(:,Nchain==0)=0;
243%Qchain(:,Nchain==0)=0;
244Rchain(:,Nchain==0)=0;
245Tchain(:,Nchain==0)=0;
246
247if isempty(sn.lldscaling) && isempty(sn.cdscaling) && isempty(sn.jdscaling)
248 [Q,U,R,T,C,X] = sn_deaggregate_chain_results(sn, Lchain, [], STchain, Vchain, alpha, [], [], Rchain, Tchain, [], Xchain);
249else
250 [Q,U,R,T,C,X] = sn_deaggregate_chain_results(sn, Lchain, [], STchain, Vchain, alpha, [], Uchain, Rchain, Tchain, [], Xchain);
251end
252
253% Stations with limited class dependence report utilization as T*S/peak,
254% where peak is the user-declared peak rate scaling per class
255% (sn.cdscalingpeak), matching the T*S/c convention of ordinary multiserver
256% stations.
257if ~isempty(sn.cdscaling) && ~any(isinf(sn.njobs))
258 Kcls = sn.nclasses; % U and rates are class-indexed; K here counts chains
259 for ist=1:M
260 if length(sn.cdscaling) >= ist && ~isempty(sn.cdscaling{ist})
261 for k=1:Kcls
262 bmax = sn.cdscalingpeak(ist,k);
263 if isfinite(sn.rates(ist,k)) && sn.rates(ist,k) > 0 && bmax > 0
264 U(ist,k) = T(ist,k) / sn.rates(ist,k) / bmax;
265 else
266 U(ist,k) = 0;
267 end
268 end
269 end
270 end
271end
272
273% Stations with limited joint dependence report utilization as T*S/peak in
274% the same way, using the user-declared peak rate scaling sn.jdscalingpeak.
275if ~isempty(sn.jdscaling) && ~any(isinf(sn.njobs))
276 Kcls = sn.nclasses;
277 for ist=1:M
278 if length(sn.jdscaling) >= ist && ~isempty(sn.jdscaling{ist})
279 for k=1:Kcls
280 bmax = sn.jdscalingpeak(ist,k);
281 if isfinite(sn.rates(ist,k)) && sn.rates(ist,k) > 0 && bmax > 0
282 U(ist,k) = T(ist,k) / sn.rates(ist,k) / bmax;
283 else
284 U(ist,k) = 0;
285 end
286 end
287 end
288 end
289end
290
291% estimate normalizing constant for closed classes
292ccl = isfinite(Nchain);
293Nclosed = Nchain(ccl);
294Xclosed = Xchain(ccl);
295lG = - Nclosed(Xclosed>options.tol) * log(Xclosed(Xclosed>options.tol))'; % asymptotic approximation
296
297% Residual (not totiter, which aggregates nested sweeps) decides convergence;
298% see _kb/06-solver-catalog.md (MVA section) for AMVA convergence flag-vs-count
299converged = max(max(abs(Qchain-QchainOuter_1))) <= tol;
300end