LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_amva.m
1function [Q,U,R,T,C,X,lG,totiter,method] = solver_amva(sn,options)
2% [Q,U,R,T,C,X,lG,ITER] = SOLVER_AMVA(SN, OPTIONS)
3%
4% Copyright (c) 2012-2026, Imperial College London
5% All rights reserved.
6if nargin < 2
7 options = SolverMVA.defaultOptions;
8end
9%% aggregate chains
10[Lchain,STchain,Vchain,alpha,Nchain,SCVchain,refstatchain] = sn_get_demands_chain(sn);
11
12%% check options
13if ~isfield(options.config,'np_priority')
14 options.config.np_priority = 'default';
15end
16if ~isfield(options.config,'multiserver')
17 options.config.multiserver = 'default';
18end
19if ~isfield(options.config,'highvar')
20 options.config.highvar = 'default';
21end
22
23% Apply hard cap on iter_max for stability (Python parity)
24max_iter_cap = 10000;
25options.iter_max = min(options.iter_max, max_iter_cap);
26
27switch options.method
28 case 'amva.qli'
29 options.method = 'qli';
30 case {'amva.qd', 'amva.qdamva', 'qdamva'}
31 options.method = 'qd';
32 case 'amva.aql'
33 options.method = 'aql';
34 case 'amva.qdaql'
35 options.method = 'qdaql';
36 case 'amva.lin'
37 options.method = 'lin';
38 case 'amva.qdlin'
39 options.method = 'qdlin';
40 case 'amva.fli'
41 options.method = 'fli';
42 case 'amva.bs'
43 options.method = 'bs';
44 case 'amva.ab'
45 options.method = 'ab';
46 case 'amva.schmidt'
47 options.method = 'schmidt';
48 case 'amva.schmidt-ext'
49 options.method = 'schmidt-ext';
50 case {'default','amva'}
51 if (sum(Nchain)<=2 || any(Nchain<1))
52 options.method = 'qd'; % changing to bs degrades accuracy
53 else
54 if max(sn.nservers(isfinite(sn.nservers)))==1
55 options.method = 'egflin'; % if single server model
56 else
57 options.method = 'lin'; % lin seems way worse than aql in test_LQN_8.xml
58 end
59 end
60end
61method = options.method;
62
63%% trivial models
64if sn_has_homogeneous_scheduling(sn,SchedStrategy.INF)
65 options.config.multiserver = 'default';
66 [Q,U,R,T,C,X,lG,totiter] = solver_amvald(sn,Lchain,STchain,Vchain,alpha,Nchain,SCVchain,refstatchain,options);
67 return
68end
69
70sourceIdx = sn.nodetype == NodeType.Source;
71queueIdx = sn.nodetype == NodeType.Queue;
72delayIdx = sn.nodetype == NodeType.Delay;
73
74%% run amva method
75M = sn.nstations;
76%K = sn.nclasses;
77C = sn.nchains;
78V = zeros(M,C);
79for i=sn.nodeToStation(sourceIdx)
80 for c=1:sn.nchains
81 inchain = find(sn.chains(c,:));
82 if sum(sn.rates(sn.nodeToStation(sourceIdx),inchain), 'omitnan') > 0
83 V(i,c) = 1;
84 end
85 end
86end
87Q = zeros(M,C);
88U = zeros(M,C);
89
90cond1 = sn_has_product_form_not_het_fcfs(sn);
91cond2 = ~sn_has_load_dependence(sn);
92cond3 = ~sn_has_open_classes(sn);
93if cond1 && cond2 && (cond3 || (sn_has_product_form(sn) && sn_has_open_classes(sn) && strcmpi(options.method,'lin')))
94 % we can use linearizer only if the open model is not heterfcfs as that approximation is not supported, so strict product-form is required
95 [lambda,L0,N,Z0,~,nservers,V(sn.nodeToStation(queueIdx|delayIdx),:)] = sn_get_product_form_chain_params(sn);
96 L = L0;
97 Z = Z0;
98 switch options.config.multiserver
99 case {'default','seidmann'}
100 % apply seidmann
101 L = L ./ repmat(nservers(:),1,C);
102 for j=1:size(L,1) % move component of queue j to the first delay
103 Z(1,:) = Z(1,:) + L0(j,:) .* (repmat(nservers(j),1,C) - 1)./ repmat(nservers(j),1,C);
104 end
105 case 'softmin'
106 [Q,U,R,T,C,X,lG,totiter] = solver_amvald(sn,Lchain,STchain,Vchain,alpha,Nchain,SCVchain,refstatchain,options);
107 return
108 otherwise
109 %no-op
110 end
111
112 switch options.method
113 case 'sqni' % square root non-iterative approximation
114 if sn.nstations==2 && sum(sn.sched==SchedStrategy.INF)==1
115 [Q(sn.nodeToStation(queueIdx),:),U(sn.nodeToStation(queueIdx),:),X] = pfqn_sqni(N,L,Z);
116 else
117 [Q,U,R,T,C,X,lG,totiter,method] = deal([],[],[],[],[],[],[],0,options.method);
118 return
119 end
120 totiter=1;
121 case 'bs'
122 [X,Q(sn.nodeToStation(queueIdx),:),U(sn.nodeToStation(queueIdx),:),~,totiter] = pfqn_bs(L,N,Z,options.tol,options.iter_max,[],sn.sched(queueIdx));
123 case 'aql'
124 if sn_has_multi_server(sn)
125 line_error(mfilename,'AQL cannot handle multi-server stations. Try with the ''default'' or ''lin'' methods.');
126 end
127 [X,Q(sn.nodeToStation(queueIdx),:),U(sn.nodeToStation(queueIdx),:),~,totiter] = pfqn_aql(L,N,Z,options.tol,options.iter_max);
128 case 'ab'
129 % Akyildiz-Bolch AMVA for multi-server networks
130 % Use L0/Z0 (original demands) since ab handles multi-server directly
131 % without needing Seidmann transformation
132 nDelays = sum(delayIdx); % Count actual delay stations, not size(Z)
133 if nDelays > 0
134 nservers_full = [Inf*ones(nDelays,1); nservers];
135 D_full = [Z0; L0];
136 V_full = [ones(nDelays,C); V(sn.nodeToStation(queueIdx),:)];
137 sched_full = [SchedStrategy.INF*ones(nDelays,1); sn.sched(sn.nodeToStation(queueIdx))];
138 else
139 nservers_full = nservers;
140 D_full = L0;
141 V_full = V(sn.nodeToStation(queueIdx),:);
142 sched_full = sn.sched(sn.nodeToStation(queueIdx));
143 end
144 [Q_tmp,U_tmp,~,~,X,totiter] = pfqn_ab_amva(D_full,N,V_full,nservers_full,sched_full,false,'ab');
145 if nDelays > 0
146 Q(sn.nodeToStation(delayIdx),:) = Q_tmp(1:nDelays,:);
147 U(sn.nodeToStation(delayIdx),:) = Q_tmp(1:nDelays,:); % delay utilization = queue length
148 end
149 Q(sn.nodeToStation(queueIdx),:) = Q_tmp(nDelays+1:end,:);
150 U(sn.nodeToStation(queueIdx),:) = U_tmp(nDelays+1:end,:);
151 case 'schmidt'
152 % Schmidt's exact MVA for class-dependent FCFS
153 % Use L0/Z0 (original demands) since schmidt handles multi-server directly
154 % without needing Seidmann transformation
155 nDelays = sum(delayIdx); % Count actual delay stations, not size(Z)
156 if nDelays > 0
157 D_full = [Z0; L0];
158 S_full = [Inf*ones(nDelays,1); nservers];
159 sched_full = [SchedStrategy.INF*ones(nDelays,1); sn.sched(sn.nodeToStation(queueIdx))];
160 V_full = [ones(nDelays,C); V(sn.nodeToStation(queueIdx),:)];
161 else
162 D_full = L0;
163 S_full = nservers;
164 sched_full = sn.sched(sn.nodeToStation(queueIdx));
165 V_full = V(sn.nodeToStation(queueIdx),:);
166 end
167 [X_tmp,Q_tmp,~,~,~] = pfqn_schmidt(D_full,N,S_full,sched_full,V_full);
168 if nDelays > 0
169 Q(sn.nodeToStation(delayIdx),:) = Q_tmp(1:nDelays,:);
170 U(sn.nodeToStation(delayIdx),:) = Q_tmp(1:nDelays,:); % delay utilization = queue length
171 end
172 Q(sn.nodeToStation(queueIdx),:) = Q_tmp(nDelays+1:end,:);
173 X = X_tmp(1,:);
174 % Compute utilization from throughput: U = X * D / nservers
175 U(sn.nodeToStation(queueIdx),:) = repmat(X,size(L0,1),1) .* L0 ./ repmat(nservers,1,C);
176 totiter = 1;
177 case 'schmidt-ext'
178 % Extended Schmidt MVA with alpha corrections
179 % Use L0/Z0 (original demands) since schmidt-ext handles multi-server directly
180 % without needing Seidmann transformation
181 nDelays = sum(delayIdx); % Count actual delay stations, not size(Z)
182 if nDelays > 0
183 D_full = [Z0; L0];
184 S_full = [Inf*ones(nDelays,1); nservers];
185 sched_full = [SchedStrategy.INF*ones(nDelays,1); sn.sched(sn.nodeToStation(queueIdx))];
186 else
187 D_full = L0;
188 S_full = nservers;
189 sched_full = sn.sched(sn.nodeToStation(queueIdx));
190 end
191 [X_tmp,Q_tmp,~,~,~] = pfqn_schmidt_ext(D_full,N,S_full,sched_full);
192 if nDelays > 0
193 Q(sn.nodeToStation(delayIdx),:) = Q_tmp(1:nDelays,:);
194 U(sn.nodeToStation(delayIdx),:) = Q_tmp(1:nDelays,:); % delay utilization = queue length
195 end
196 Q(sn.nodeToStation(queueIdx),:) = Q_tmp(nDelays+1:end,:);
197 X = X_tmp(1,:);
198 % Compute utilization from throughput: U = X * D / nservers
199 U(sn.nodeToStation(queueIdx),:) = repmat(X,size(L0,1),1) .* L0 ./ repmat(nservers,1,C);
200 totiter = 1;
201 case {'lin','gflin','egflin'}
202 % For models with joint dependence (LJD/LJCD), use solver_amvald which handles it
203 if sn_has_joint_dependence(sn)
204 [Q,U,R,T,C,X,lG,totiter] = solver_amvald(sn,Lchain,STchain,Vchain,alpha,Nchain,SCVchain,refstatchain,options);
205 return
206 elseif max(nservers)==1
207 % remove sources from L
208 [Q(sn.nodeToStation(queueIdx),:),U(sn.nodeToStation(queueIdx),:),~,~,X,totiter] = pfqn_linearizermx(lambda,L,N,Z,nservers,sn.sched(sn.nodeToStation(queueIdx)),options.tol,options.iter_max, options.method);
209 else
210 switch options.config.multiserver
211 case 'conway'
212 [Q(sn.nodeToStation(queueIdx),:),U(sn.nodeToStation(queueIdx),:),~,~,X,totiter] = pfqn_conwayms(L,N,Z,nservers,sn.sched(queueIdx),options.tol,options.iter_max);
213 case 'krzesinski'
214 [Q(sn.nodeToStation(queueIdx),:),U(sn.nodeToStation(queueIdx),:),~,~,X,totiter] = pfqn_linearizermx(lambda,L,N,Z,nservers,sn.sched(sn.nodeToStation(queueIdx)),options.tol,options.iter_max, options.method);
215 case {'default', 'softmin', 'seidmann'}
216 [Q,U,R,T,C,X,lG,totiter] = solver_amvald(sn,Lchain,STchain,Vchain,alpha,Nchain,SCVchain,refstatchain,options);
217 return
218 end
219 end
220 otherwise
221 switch options.config.multiserver
222 case {'conway','erlang','krzesinski'}
223 options.config.multiserver = 'default';
224 end
225 [Q,U,R,T,C,X,lG,totiter] = solver_amvald(sn,Lchain,STchain,Vchain,alpha,Nchain,SCVchain,refstatchain,options);
226 return
227 end
228
229 % compute performance at delay, then unapply seidmann if needed
230 for i=1:size(Z0,1)
231 % For ab/schmidt methods, Q for delays was already set correctly by the algorithm
232 % using original demands Z0. For other methods, use Seidmann-modified Z.
233 if strcmp(options.method,'ab') || startsWith(options.method,'schmidt')
234 Q(sn.nodeToStation(delayIdx),:) = repmat(X,sum(delayIdx),1) .* Z0;
235 else
236 Q(sn.nodeToStation(delayIdx),:) = repmat(X,sum(delayIdx),1) .* Z;
237 end
238 U(sn.nodeToStation(delayIdx),:) = Q(sn.nodeToStation(delayIdx),:);
239 switch options.config.multiserver
240 case {'default','seidmann'}
241 % Skip Seidmann un-apply for ab and schmidt methods as it removes queue length
242 if ~strcmp(options.method,'ab') && ~startsWith(options.method,'schmidt')
243 for j=1:size(L,1)
244 if i == 1 && nservers(j)>1
245 % un-apply seidmann from first delay and move it to
246 % the origin queue
247 jq = find(queueIdx,j);
248 Q(jq,:) = Q(jq,:) + (L0(j,:) .* (repmat(nservers(j),1,C) - 1)./ repmat(nservers(j),1,C)) .* X;
249 end
250 end
251 end
252 end
253 end
254 T = V .* repmat(X,M,1);
255 R = Q ./ T;
256 % For ab/schmidt methods, use original delay demands Z0
257 if strcmp(options.method,'ab') || startsWith(options.method,'schmidt')
258 C = N ./ X - Z0;
259 else
260 C = N ./ X - Z;
261 end
262 lG = NaN;
263 if sn_has_class_switching(sn)
264 [Q,U,R,T,C,X] = sn_deaggregate_chain_results(sn, Lchain, [], STchain, Vchain, alpha, [], [], R, T, [], X);
265 end
266else
267 switch options.config.multiserver
268 case {'conway','erlang','krzesinski'}
269 options.config.multiserver = 'default';
270 end
271 [Q,U,R,T,C,X,lG,totiter] = solver_amvald(sn,Lchain,STchain,Vchain,alpha,Nchain,SCVchain,refstatchain,options);
272end
273end
274