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