LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
pfqn_clw.m
1%{
2%{
3 % @file pfqn_clw.m
4 % @brief Choudhury-Leung-Whitt normalization constant by numerical inversion
5 % of the generating function (JACM 42(5):935-970, 1995).
6%}
7%}
8
9%{
10%{
11 % @brief Computes the normalization constant g(K) of a multichain closed
12 % product-form network with single-server and (optionally)
13 % infinite-server queues by numerically inverting its p-dimensional
14 % generating function (Choudhury, Leung and Whitt, 1995).
15 %
16 % The generating function of g(K) is (eq. 4.5)
17 %
18 % G(z) = exp( sum_j rho_{j0} z_j ) / prod_i ( 1 - sum_j rho_{ji} z_j )^{m_i}
19 %
20 % where j = 1..p indexes closed chains, i = 1..q' indexes the distinct
21 % single-server queues with multiplicity m_i, rho_{ji} is the relative
22 % traffic intensity of chain j at queue i and rho_{j0} the aggregate
23 % relative traffic intensity of chain j at the infinite-server queues.
24 % g(K) is the coefficient of prod_j z_j^{K_j}, recovered by p nested
25 % one-dimensional lattice-Poisson inversions (eq. 2.3) with restrictive
26 % static scaling (eqs. 5.41-5.46) and log-domain recovery (eq. 7.1).
27 %
28 % @fn pfqn_clw(L, N, Z, m, options)
29 % @param L (q' x p) single-server relative traffic intensities, L(i,j)=rho_{ji}.
30 % @param N (1 x p) closed-chain population vector K.
31 % @param Z (1 x p) aggregate infinite-server relative intensities rho_{j0}
32 % (think-time term). Default: zeros(1,p).
33 % @param m (q' x 1) queue multiplicities m_i. Default: ones(q',1).
34 % @param options struct with optional fields:
35 % .l (1 x p) inner lattice parameters l_j (roundoff control).
36 % .gamma (1 x p) aliasing parameters gamma_j (aliasing ~ 10^-gamma_j).
37 % Defaults follow the paper: l_1=1,g_1=11; l_2=l_3=2,g=13;
38 % l_j>=4 = 3, g=15.
39 % @return G Normalization constant g(K). Inf if it overflows double range.
40 % @return lG Natural logarithm of g(K) (always finite).
41 %
42 % Validation: reproduces the paper's Table I (Example 8.1, p=1, all K up to
43 % 2e7) and the direct-summation rows of Table II (Example 8.2, p=4) to 7
44 % significant digits, and matches exact convolution (pfqn_ca) to ~1e-9.
45 %
46 % Scope: this routine implements the exact nested lattice-Poisson inversion
47 % with restrictive static scaling. Its cost is prod_j 2 l_j K_j, so it is
48 % practical for moderate populations and few chains. The paper's speed-ups
49 % for large populations/chains (Euler summation of the inner sums, Section
50 % 2.4, and dimension reduction, Sections 3 and 5.4) are not applied here;
51 % Tables III-VI and the largest Table II rows rely on them for tractability.
52%}
53%}
54function [G, lG] = pfqn_clw(L, N, Z, m, options)
55[qd, p] = size(L);
56if nargin < 3 || isempty(Z)
57 Z = zeros(1, p);
58end
59if nargin < 4 || isempty(m)
60 m = ones(qd, 1);
61end
62if nargin < 5
63 options = struct();
64end
65N = N(:).';
66Z = Z(:).';
67m = m(:);
68
69% default lattice/aliasing parameters (Section 2.2, page 962)
70if isfield(options, 'l') && ~isempty(options.l)
71 l = options.l(:).';
72else
73 l = 3 * ones(1, p);
74 l(1) = 1;
75 if p >= 2, l(2) = 2; end
76 if p >= 3, l(3) = 2; end
77end
78if isfield(options, 'gamma') && ~isempty(options.gamma)
79 gam = options.gamma(:).';
80else
81 gam = 15 * ones(1, p);
82 gam(1) = 11;
83 if p >= 2, gam(2) = 13; end
84 if p >= 3, gam(3) = 13; end
85end
86
87% trivial populations
88if any(N < 0)
89 G = 0; lG = -Inf; return
90end
91if all(N == 0)
92 G = 1; lG = 0; return
93end
94
95% contour radii r_j = 10^{-gamma_j/(2 l_j K_j)} (eq. 2.7)
96r = zeros(1, p);
97for j = 1:p
98 if N(j) == 0
99 r(j) = 1;
100 else
101 r(j) = 10^(-gam(j) / (2 * l(j) * N(j)));
102 end
103end
104
105% restrictive static scaling: alpha_j, alpha0_j (eqs. 5.41-5.46), with the
106% outer contour variables evaluated at |z_k| = r_k (most restrictive point).
107alpha = ones(1, p);
108alpha0 = ones(1, p);
109used = zeros(qd, 1); % sum_{k<j} alpha_k rho_{ki} r_k, per queue
110etaMat = double(L ~= 0); % eta_{ki} = 1 iff rho_{ki} ~= 0 (eq. 5.46)
111for j = 1:p
112 Kj = N(j); lj = l(j);
113 denom = 1 - used; % 1 - sum_{k<j} rhobar_{ki}|z_k|
114 denom(denom <= 0) = eps;
115 e = L(:, j) ./ denom; % effective intensity per queue
116 posq = find(L(:, j) > 0);
117 aj = Inf;
118 if ~isempty(posq)
119 [es, ord] = sort(e(posq), 'descend');
120 qs = posq(ord); % original queue indices, sorted
121 ms = m(qs); % aligned multiplicities (mtilde)
122 cumrho = cumsum(es) ./ (1:numel(es))'; % rhobar_n (eq. 5.44)
123 cummb = cumsum(ms); % mbar_n (eqs. 5.40/5.45)
124 for n = 1:numel(es)
125 qi = qs(n);
126 % N_{ij} = mbar_n - 1 + sum_{k>j} K_k eta_{k,qi} (eq. 5.43)
127 Nn = cummb(n) - 1 + sum(N(j+1:p) .* etaMat(qi, j+1:p));
128 if Nn <= 0
129 an = 1;
130 else
131 ll = (1:Nn)';
132 an = (prod((Kj + ll) ./ (Kj + 2 * lj * Kj + ll)))^(1 / (2 * lj * Kj));
133 end
134 aj = min(aj, an / cumrho(n));
135 end
136 end
137 if Z(j) > 0
138 aj = min(aj, Kj / Z(j)); % IS/Poisson term K_j/rho_{j0}
139 end
140 if ~isfinite(aj)
141 aj = 1; % chain with no demand anywhere
142 end
143 alpha(j) = aj;
144 alpha0(j) = exp(-aj * Z(j));
145 used = used + aj * L(:, j) * r(j); % deflate for subsequent chains
146end
147
148% context for the recursion (local functions, not nested, to avoid MATLAB
149% nested-function workspace sharing across recursive calls)
150ctx.N = N;
151ctx.l = l;
152ctx.r = r;
153ctx.p = p;
154ctx.arho0 = alpha .* Z; % 1 x p : alpha_j rho_{j0}
155ctx.rhoS = L .* alpha; % q' x p : alpha_j rho_{ji}
156ctx.mrow = m; % q' x 1
157ctx.chunk = 2e6; % vectorization chunk for the innermost sum
158
159% run the nested inversion on the scaled generating function -> gbar(K)
160gbar = clw_invert(1, zeros(1, 0), ctx);
161
162% recovery g(K) = prod alpha0_j^{-1} prod alpha_j^{-K_j} gbar(K) (eq. 7.1)
163lG = log(gbar) + sum(ctx.arho0) - sum(N .* log(alpha));
164if lG > 709
165 G = Inf;
166else
167 G = exp(lG);
168end
169end
170
171% ---- one-dimensional lattice-Poisson inversion (eq. 2.3), scaled ----
172% Extracts the coefficient of w_j^{K_j} from g^{(j)}, recursing on inner chains.
173function val = clw_invert(j, wfixed, ctx)
174Kj = ctx.N(j); lj = ctx.l(j); rj = ctx.r(j);
175acc = 0;
176for k1 = 0:lj-1
177 ph = exp(-1i * pi * k1 / lj);
178 kk = (-Kj):(Kj-1);
179 signs = (-1) .^ kk;
180 theta = pi * (k1 + lj * kk) / (lj * Kj);
181 wj = rj * exp(1i * theta); % 1 x 2Kj contour points
182 if j == ctx.p
183 inner = 0;
184 nk = numel(wj);
185 for a = 1:ctx.chunk:nk
186 b = min(a + ctx.chunk - 1, nk);
187 W = [repmat(wfixed, b - a + 1, 1), wj(a:b).'];
188 fv = clw_gbar(W, ctx);
189 inner = inner + sum(signs(a:b).' .* fv);
190 end
191 else
192 inner = 0;
193 for t = 1:numel(wj)
194 inner = inner + signs(t) * clw_invert(j + 1, [wfixed, wj(t)], ctx);
195 end
196 end
197 acc = acc + ph * inner;
198end
199val = acc / (2 * lj * Kj * rj^Kj);
200if j == 1
201 val = real(val);
202end
203end
204
205% ---- scaled generating function Gbar evaluated at rows of W (n x p) ----
206function g = clw_gbar(W, ctx)
207% Gbar(w) = exp( sum_j alpha_j rho_{j0} (w_j - 1) )
208% / prod_i (1 - sum_j alpha_j rho_{ji} w_j)^{m_i}
209expo = (W - 1) * ctx.arho0.'; % n x 1
210A = W * ctx.rhoS.'; % n x q'
211logden = log(1 - A) * ctx.mrow; % n x 1 (principal complex log)
212g = exp(expo - logden);
213end
Definition Station.m:245