LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
pfqn_ncld.m
1%{
2%{
3 % @file pfqn_ncld.m
4 % @brief Normalizing constant for load-dependent closed networks.
5%}
6%}
7
8%{
9%{
10 % @brief Normalizing constant for load-dependent closed networks.
11 % @fn pfqn_ncld(L, N, Z, mu, varargin)
12 % @param L Service demand matrix.
13 % @param N Population vector.
14 % @param Z Think time vector.
15 % @param mu Load-dependent rate matrix.
16 % @param varargin Optional solver parameters.
17 % @return lG Logarithm of normalizing constant.
18 % @return G Normalizing constant.
19 % @return method Method used for computation.
20%}
21%}
22function [lG,G,method] = pfqn_ncld(L,N,Z,mu,varargin)
23% [LGN,G,METHOD] = PFQN_NCLD(L,N,Z,VARARGIN)
24
25options = Solver.parseOptions(varargin, SolverNC.defaultOptions);
26lG = NaN;
27G = NaN;
28method = options.method;
29
30% backup initial parameters
31
32mu = mu(:,1:sum(N));
33% first remove empty classes
34nnzClasses = find(N);
35L = L(:,nnzClasses);
36N = N(:,nnzClasses);
37Z = Z(:,nnzClasses);
38
39% then scale demands in [0,1], importat that stays before the other
40% simplications in case both D and Z are all very small or very large in a
41% given class, in which case the may look to filter but not if all of them
42% are at the same scale
43R = length(N);
44scalevec = ones(1,R);
45for r=1:R
46 scalevec(r) = max([L(:,r);Z(:,r)]);
47end
48L = L ./ repmat(scalevec,size(L,1),1);
49Z = Z ./ scalevec;
50
51% remove stations with no demand
52Lsum = sum(L,2);
53Lmax = max(L,[],2);
54demStations = find((Lmax./Lsum)>GlobalConstants.FineTol);
55L = L(demStations,:);
56mu = mu(demStations,:);
57
58% if there is a class with jobs but with L and Z all zero
59if any(N((sum(L,1) + sum(Z,1)) == 0)>0)
60 line_warning(mfilename,'The model has no positive demands in any class.\n');
61 if isempty(Z) || sum(Z(:))<options.tol
62 lG = 0;
63 else
64 lG = - sum(factln(N)) + sum(N.*log(sum(Z,1))) + N*log(scalevec)';
65 end
66 G = NaN;
67
68 return
69end
70
71% update M and R
72[M,R]=size(L);
73
74% return immediately if the model is a degenerate case
75if isempty(L) || sum(L(:))<options.tol % all demands are zero
76 if isempty(Z) || sum(Z(:))<options.tol
77 lG = 0;
78 else
79 lG = - sum(factln(N)) + sum(N.*log(sum(Z,1))) + N*log(scalevec)';
80 end
81 return
82elseif M==1 && (isempty(Z) || sum(Z(:))<options.tol) % single node and no think time
83 lG = factln(sum(N)) - sum(factln(N)) + sum(N.*log(sum(L,1))) + N*log(scalevec)' - sum(log(mu(1:sum(N))));
84 return
85end
86
87% determine contribution from jobs that permanently loop at delay
88zeroDemandClasses = find(sum(L,1)<options.tol); % all jobs in delay
89nonzeroDemandClasses = setdiff(1:R, zeroDemandClasses);
90
91if isempty(sum(Z,1)) || all(sum(Z(:,zeroDemandClasses),1)<options.tol)
92 lGzdem = 0;
93 Nz = 0;
94else
95 if isempty(zeroDemandClasses) % for old MATLAB release compatibility
96 lGzdem = 0;
97 Nz = 0;
98 else
99 Nz = N(zeroDemandClasses);
100 lGzdem = - sum(factln(Nz)) + sum(Nz.*log(sum(Z(:,zeroDemandClasses),1))) + Nz*log(scalevec(zeroDemandClasses))';
101 end
102end
103L = L(:,nonzeroDemandClasses);
104N = N(nonzeroDemandClasses);
105Z = Z(:,nonzeroDemandClasses);
106scalevecz = scalevec(nonzeroDemandClasses);
107% compute G for classes No with non-zero demand
108if any(N<0)
109 lGnnzdem = 0;
110else
111 [lGnnzdem,method] = compute_norm_const_ld(L, N, Z, mu, options);
112end
113% scale back to original demands
114lG = lGnnzdem + lGzdem + N*log(scalevecz)';
115G = exp(lG);
116end
117
118function [lG,method] = compute_norm_const_ld(L,N,Z,mu,options)
119% LG = COMPUTE_NORM_CONST_LD(L,N,Z,OPTIONS)
120[M,R] = size(L);
121method = options.method;
122switch options.method
123 case {'default','exact'}
124 % In 'default' mode prefer the Choudhury-Leung-Whitt generating-function
125 % inversion (pfqn_clw_lld) for genuinely multi-station, low-class-count
126 % models. Two independent limits, both calibrated by profiling the
127 % canonical JAR, gate its use (else fall back to exact/gld):
128 % 1) Speed. Cost is exactly prod_j 2*l_j*N_j contour points (l defaults
129 % [1,2,2,3,3,...]) at a steady ~1e7 points/s, so runtime ~=
130 % clwPredCost/1e7 s. It grows as the product of the per-class
131 % populations (degree R in the total population for balanced classes:
132 % ~2*Ntot^2 at R=2, ~Ntot^3 at R=3) and exponentially in R. Cap at a
133 % ~2s budget (clwMaxCost).
134 % 2) Numerical validity. The inversion sums 2*N_j alternating-sign
135 % contour points per class, so it loses accuracy to catastrophic
136 % cancellation as the population grows: clw_lld returns NaN
137 % intermittently above per-class ~450 (JAR; native ~700). Restrict
138 % to total population sum(N) <= clwMaxPop = 200, well inside the
139 % reliable regime; larger models revert to the exact default.
140 % The explicit 'exact' method always takes the convolution path below;
141 % 'clw' forces the inversion irrespective of these caps.
142 clwMaxClasses = 5; % class-count gate (clw cost is exponential in R)
143 clwMaxPop = 200; % total-population cap (numerical validity; NaN onset ~450)
144 clwMaxCost = 2e7; % contour-point budget (~2s at ~1e7 pts/s; profiler)
145 clwPredCost = prod(2 * clw_lattice(R) .* N(:).');
146 if strcmp(options.method,'default') && M > 1 && R >= 2 ...
147 && R <= clwMaxClasses && sum(N) <= clwMaxPop ...
148 && clwPredCost <= clwMaxCost
149 [~,lG] = pfqn_clw_lld(L, N, sum(Z,1), mu, options);
150 method = 'clw';
151 else
152 if sum(Z(:))<GlobalConstants.FineTol
153 Lz = L;
154 muz = mu;
155 else
156 D = size(Z,1); % number of delays
157 Lz = [L;Z];
158 muz = [mu; repmat(1:size(mu,2),D,1)];
159 end
160 if R==1
161 [lG] = pfqn_gldsingle(Lz, N, muz, options);
162 method = 'exact/gld';
163 elseif M==1 && any(Z>0)
164 [~,lG]= pfqn_comomrm_ld(L, N, Z, mu, options);
165 method = 'exact/comomld';
166 elseif M==1 && max(Z(:)) < GlobalConstants.FineTol
167 % M counts QUEUEING stations; pfqn_comomrm_ld is the finite
168 % repairman routine (one queueing station, plus an optional
169 % INF-server row it folds into the think time). This guard read
170 % M==2 before; it is now M==1 so a genuine two-queueing-station
171 % model (no delay) falls through to pfqn_gld below, which matches
172 % pfqn_ca to machine precision on those models.
173 [~,lG] = pfqn_comomrm_ld(L, N, 0*N, mu, options);
174 method = 'exact/comomld';
175 else
176 [~,lG] = pfqn_gld(Lz, N, muz, options);
177 method = 'exact/gld';
178 end
179 end
180 case {'is'}
181 % Importance sampling for a load-dependent closed network: the
182 % sample-an-ordering estimator of pfqn_ld_is (the LD counterpart of
183 % pfqn_is / pfqn_oi_is / pfqn_pas_is).
184 [~,lG] = pfqn_ld_is(L,N,sum(Z,1),mu,options);
185 method = 'is';
186 case 'clw'
187 % Choudhury-Leung-Whitt generating-function inversion extended to
188 % limited load-dependent stations (Bertozzi-McKenna transforms); the
189 % delay term is passed as the aggregate IS demand sum(Z,1).
190 [~,lG] = pfqn_clw_lld(L, N, sum(Z,1), mu, options);
191 method = 'clw';
192 case 'rd'
193 [lG] = pfqn_rd(L, N, Z, mu, options);
194 case 'nrp'
195 [lG] = pfqn_nrp(L, N, Z, mu, options);
196 case 'nrl'
197 [lG] = pfqn_nrl(L, N, Z, mu, options);
198 case 'comomld'
199 if M<=1 || sum(Z) < GlobalConstants.Zero
200 [~,lG]= pfqn_comomrm_ld(L, N, Z, mu, options);
201 else
202 line_warning(mfilename,'Load-dependent CoMoM is available only in models with a delay and m identical stations, running the ''rd'' algorithm instead.\n');
203 [lG] = pfqn_rd(L, N, Z, mu, options);
204 method = 'rd';
205 end
206 otherwise
207 line_error(mfilename,sprintf('Unrecognized method for solving load-dependent models: %s',options.method));
208end
209return
210end
211
212function l = clw_lattice(p)
213% Default CLW inner lattice parameters l_j (roundoff control): l_1=1,
214% l_2=l_3=2, l_j>=4 = 3. Used to predict the clw_lld contour-point cost.
215l = 3 * ones(1, p);
216l(1) = 1;
217if p >= 2, l(2) = 2; end
218if p >= 3, l(3) = 2; end
219end
Definition Station.m:245