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