LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
pfqn_nc.m
1% Computes the normalizing constant of a product-form queueing network.
2% [LG,X,Q,METHOD] = PFQN_NC(LAMBDA,L,N,Z,VARARGIN)
3function [lG,X,Q,method] = pfqn_nc(lambda,L,N,Z,varargin)
4
5options = Solver.parseOptions(varargin, SolverNC.defaultOptions);
6method = 'exact'; % if early return is triggered
7% backup initial parameters
8Rin = length(N);
9
10if any(N<0) || isempty(N)
11 lG = -Inf;
12 X = [];
13 Q = [];
14 return
15end
16
17if sum(N)==0
18 lG = 0;
19 X = [];
20 Q = [];
21 return
22end
23
24if isempty(lambda)
25 lambda=0*N;
26end
27
28X=[]; Q=[];
29
30% compute open class contributions
31Qopen = [];
32lGopen = 0;
33Ut = zeros(1, size(L,1));
34for i=1:size(L,1)
35 Ut(i) = (1-lambda*L(i,:)');
36 if isnan(Ut(i))
37 Ut(i) = 0;
38 end
39 L(i,:) = L(i,:)/Ut(i);
40 Qopen(i,:) = lambda.*L(i,:)/Ut(i);
41 %lGopen = lGopen + log(Ut(i));
42end
43Qopen(isnan(Qopen))=0;
44ocl = find(isinf(N));
45% then erase open classes
46N(isinf(N)) = 0;
47
48% first remove empty classes
49nnzClasses = find(N);
50lambda = lambda(:,nnzClasses);
51L = L(:,nnzClasses);
52N = N(:,nnzClasses);
53Z = Z(:,nnzClasses);
54
55% then scale demands in [0,1], importat that stays before the other
56% simplications in case both D and Z are all very small or very large in a
57% given class, in which case the may look to filter but not if all of them
58% are at the same scale
59R = length(N);
60scalevec = ones(1,R);
61%switch options.method
62% case {'adaptive','comom','default'}
63% % no-op
64% otherwise
65%end
66for r=1:R
67 scalevec(r) = max([L(:,r);Z(:,r)]);
68end
69%end
70L = L ./ repmat(scalevec,size(L,1),1);
71Z = Z ./ scalevec;
72
73% remove stations with no demand
74Lsum = sum(L,2);
75Lmax = max(L,[],2);
76demStations = find((Lmax./Lsum)>GlobalConstants.FineTol);
77noDemStations = setdiff(1:size(L,1), demStations);
78L = L(demStations,:);
79if any(N((sum(L,1) + sum(Z,1)) == 0)>0) % if there is a class with jobs but L and Z all zero
80 if options.verbose
81 line_warning(mfilename,'The model has no positive demands in any class.\n');
82 end
83 if isempty(Z) || sum(Z(:))<options.tol
84 lG = 0;
85 else
86 lG = - sum(factln(N)) + sum(N.*log(sum(Z,1))) + N*log(scalevec)';
87 end
88 return
89end
90
91% update M and R
92[M,R]=size(L);
93
94% return immediately if degenerate case
95if isempty(L) || sum(L(:))<options.tol % all demands are zero
96 if isempty(Z) || sum(Z(:))<options.tol
97 lG = lGopen;
98 else
99 lG = lGopen - sum(factln(N)) + sum(N.*log(sum(Z,1))) + N*log(scalevec)';
100 end
101 return
102elseif M==1 && (isempty(Z) || sum(Z(:))<options.tol) % single node and no think time
103 lG = factln(sum(N)) - sum(factln(N)) + sum(N.*log(L(1,:))) + N*log(scalevec)';
104 return
105elseif size(unique(L,'rows'),1)==1 && (isempty(Z) || sum(Z(:))<options.tol) % M identical replicas
106 lG = factln(sum(N)+M-1) - sum(factln(N)) + sum(N.*log(L(1,:))) + N*log(scalevec)' - factln(M-1);
107 return
108end
109
110% determine contribution from jobs that permanently loop at delay
111zeroDemandClasses = find(sum(L,1)<options.tol); % all jobs in delay
112nonzeroDemandClasses = setdiff(1:R, zeroDemandClasses);
113
114if isempty(sum(Z,1)) || all(sum(Z(:,zeroDemandClasses),1)<options.tol)
115 lGzdem = 0;
116 Nz = 0;
117else
118 if isempty(zeroDemandClasses) % for old MATLAB release compatibility
119 lGzdem = 0;
120 Nz = 0;
121 else
122 Nz = N(zeroDemandClasses);
123 lGzdem = - sum(factln(Nz)) + sum(Nz.*log(sum(Z(:,zeroDemandClasses),1))) + Nz*log(scalevec(zeroDemandClasses))';
124 end
125end
126L = L(:,nonzeroDemandClasses);
127N = N(nonzeroDemandClasses);
128Zz = Z(:,zeroDemandClasses);
129Z = Z(:,nonzeroDemandClasses);
130scalevecz = scalevec(nonzeroDemandClasses);
131% compute G for classes No with non-zero demand
132[lGnzdem,Xnnzdem,Qnnzdem,method] = compute_norm_const(L, N, Z, options);
133
134if isempty(Xnnzdem) % in this case the NC method does not return metrics as a by-product
135 X = [];
136 Q = [];
137else
138 zClasses = setdiff(1:Rin, nnzClasses);
139 Xz = zeros(1,length(zClasses));
140 Xnnz = zeros(1,length(nnzClasses));
141 Xnnz(zeroDemandClasses) = Nz./ sum(Zz,1)./ scalevec(zeroDemandClasses);
142 Xnnz(nonzeroDemandClasses) = Xnnzdem./ scalevec(nonzeroDemandClasses);
143 X(1,[zClasses, nnzClasses]) = [Xz, Xnnz];
144 X(ocl) = lambda(ocl);
145 Qz = zeros(size(Qnnzdem,1),length(zClasses));
146 Qnnz = zeros(size(Qnnzdem,1),length(nnzClasses));
147 Qnnz(:,zeroDemandClasses) = 0; % they are all in the delay
148 Qnnz(:,nonzeroDemandClasses) = Qnnzdem; % Q does not require scaling
149 Q(noDemStations,:) = 0;
150 Q(demStations,[zClasses, nnzClasses]) = [Qz, Qnnz];
151 Q(:,ocl) = Qopen(:,ocl);
152end
153% scale back to original demands
154lG = lGopen + lGnzdem + lGzdem + N*log(scalevecz)';
155end
156
157function [lG,X,Q,method] = compute_norm_const(L,N,Z,options)
158% LG = COMPUTE_NORM_CONST(L,N,Z,OPTIONS)
159% Auxiliary script that computes LG after the initial filtering of L,N,Z
160
161% Note: methods that can handle more efficiently replicas need to do so
162% within the method function
163
164% L,N,Z
165[M,R] = size(L);
166X=[];Q=[];
167method = options.method;
168switch options.method
169 case {'ca'}
170 [~,lG] = pfqn_ca(L,N,sum(Z,1));
171 case {'clw'}
172 % Choudhury-Leung-Whitt generating function inversion: each
173 % single-server station is a multiplicity-1 queue, delay is the IS term
174 [~,lG] = pfqn_clw(L,N,sum(Z,1));
175 case {'adaptive','default'}
176 if M>1
177 if sum(N)<1e3
178 % Sequence of Grundmann-Mueller cubature (CUB) executions
179 %
180 % This is slowish since CUB is O(N^M) but competitors fails
181 % 'imci' has occasionally large errors in the benchmarks and
182 % 'kt' fails with self-looping customers leading to
183 % degradations see eg in example_mixedModel_4
184 Cmax = M*R*(50)^3; % upper cost budget
185 maxorder = min(ceil((sum(N)-1)/2),16);
186
187 totCost = 0;
188 order = 0; % will be raised as far as possible
189 while order < maxorder
190 nextCost = R * nchoosek(M + 2*(order+1), M-1); % cost of order+1
191 if totCost + nextCost <= Cmax
192 order = order + 1;
193 totCost = totCost + nextCost;
194 else
195 break
196 end
197 end
198
199 [~,lG] = pfqn_cub(L,N,sum(Z,1),order,GlobalConstants.FineTol);
200 method = 'cub';
201 else
202 [~,lG] = pfqn_le(L,N,sum(Z,1));
203 method = 'le';
204 end
205 elseif sum(Z(:))==0 % single queue, no delay
206 lG = -N*log(L)';
207 method = 'exact';
208 else % repairman model
209 if N<10000
210 % gleint is a better method but there are JAR loading issues
211 % at times upon loading the txt files
212 %[~,lG] = pfqn_mmint2_gausslegendre(L,N,sum(Z,1));
213 %method = 'gleint';
214 [lG] = pfqn_comomrm(L,N,Z,1,options.tol);
215 method = 'comom';
216 else
217 [~,lG] = pfqn_le(L,N,sum(Z,1));
218 method = 'le';
219 end
220 end
221 case {'sampling'}
222 if M==1
223 [~,lG] = pfqn_mmsample2(L,N,sum(Z,1),options.samples);
224 method = 'sampling';
225 elseif M>R
226 [~,lG] = pfqn_mci(L,N,sum(Z,1),options.samples,'imci');
227 method = 'imci';
228 else
229 [~,lG] = pfqn_ls(L,N,sum(Z,1),options.samples);
230 method = 'ls';
231 end
232 case {'mmint2','gleint'}
233 if size(L,1)>1
234 if options.verbose
235 line_warning(mfilename,sprintf('The %s method requires a model with a delay and a single queueing station.',options.method));
236 end
237 lG = [];
238 return
239 end
240 [~,lG] = pfqn_mmint2_gausslegendre(L,N,sum(Z,1));
241 case {'cub','gm'} % Grundmann-Mueller cubatures
242 order = ceil((sum(N)-1)/2); % exact
243 [~,lG] = pfqn_cub(L,N,sum(Z,1),order,GlobalConstants.FineTol);
244 case 'kt'
245 [~,lG] = pfqn_kt(L,N,sum(Z,1));
246 case 'le'
247 [~,lG] = pfqn_le(L,N,sum(Z,1));
248 case 'ls'
249 [~,lG] = pfqn_ls(L,N,sum(Z,1),options.samples);
250 case {'is'}
251 % Importance sampling for a load-independent closed network: the
252 % sample-an-ordering estimator of pfqn_is. The order-independent and
253 % pass-and-swap cases are intercepted upstream by solver_nc_analyzer,
254 % which routes to pfqn_pas_is / pfqn_oi_is instead.
255 [~,lG] = pfqn_is(L,N,sum(Z,1),options);
256 method = 'is';
257 case {'mci','imci'}
258 [~,lG] = pfqn_mci(L,N,sum(Z,1),options.samples,options.method);
259 case {'mva'}
260 [~,~,~,~,lG] = pfqn_mva(L,N,sum(Z,1));
261 %case 'mom'
262 % line_warning(mfilename,'mom supported only in SolverJMT (method ).');
263 case {'exact'}
264 if M>=R || sum(N)>10 || sum(Z(:))>0
265 [~,lG] = pfqn_ca(L,N,sum(Z,1));
266 method = 'exact/ca';
267 else
268 [~,lG] = pfqn_recal(L,N,sum(Z,1));% implemented with Z=0
269 method = 'exact/recal';
270 end
271 case {'comom'}
272 if R>1
273 % comom evaluates the normalizing constant of the repairman model,
274 % so it needs a single queueing station. This is checked BEFORE the
275 % try below on purpose: line_error must not be swallowed by that
276 % catch and turned back into an empty lG.
277 %
278 % This used to emit a warning gated on options.verbose and then
279 % return lG = [], which the caller turned into all-zero queue
280 % lengths while still reporting a completed analysis. A silently
281 % zeroed result is worse than no result: refuse instead.
282 if M>1
283 line_error(mfilename,'The ''comom'' method supports a single queueing station, but this model has %d. Use ''default'' or ''ca'' for an exact normalizing constant, or SolverJMT with method ''jmva.comom''.', M);
284 end
285 try
286 % comom has a bug in computing X, sometimes the
287 % order is switched
288 [lG] = pfqn_comomrm(L,N,Z,1,options.tol);
289 catch ME
290 getReport(ME,'basic')
291 % java exception, probably singular linear system
292 %if options.verbose
293 %line_warning(mfilename,'Numerical problems.');
294 %end
295 lG = [];
296 end
297 else
298 [~,lG] = pfqn_ca(L,N,sum(Z,1));
299 method = 'ca';
300 end
301 case {'panacea'}
302 [~,lG] = pfqn_panacea(L,N,sum(Z,1));
303 if isnan(lG)
304 if options.verbose
305 line_warning(mfilename,'Model is not in normal usage, panacea cannot continue.\n');
306 lG = [];
307 return
308 end
309 end
310 case 'propfair'
311 [~,lG] = pfqn_propfair(L,N,sum(Z,1));
312 case {'recal'}
313 if sum(Z(:))>0
314 if options.verbose
315 line_warning(mfilename,'RECAL is currently available only for models with non-zero think times.\n');
316 lG = [];
317 return
318 end
319 end
320 [~,lG] = pfqn_recal(L,N,sum(Z,1));
321 % case 'rgf'
322 % if sum(Z(:))>0
323 % if option.verbose
324 % line_warning(mfilename,'RGF is defined only for models with non-zero think times.\n');
325 % lG = [];
326 % return
327 % end
328 % end
329 % [~,lG] = pfqn_rgf(L,N);
330 otherwise
331 lG=[];
332 X=[];
333 Q=[];
334 if options.verbose
335 line_warning(mfilename,sprintf('Unrecognized method: %s',options.method));
336 lG = [];
337 return
338 end
339 return
340end
341end
Definition Station.m:245