4 % @brief Choudhury-Leung-Whitt normalization constant by numerical inversion
5 % of
the generating function (JACM 42(5):935-970, 1995).
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).
16 % The generating function of g(K)
is (eq. 4.5)
18 % G(z) = exp( sum_j rho_{j0} z_j ) / prod_i ( 1 - sum_j rho_{ji} z_j )^{m_i}
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).
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;
39 % @
return G Normalization constant g(K). Inf
if it overflows
double range.
40 % @
return lG Natural logarithm of g(K) (always finite).
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.
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.
54function [G, lG] = pfqn_clw(L, N, Z, m, options)
56if nargin < 3 || isempty(Z)
59if nargin < 4 || isempty(m)
69%
default lattice/aliasing parameters (Section 2.2, page 962)
70if isfield(options,
'l') && ~isempty(options.l)
75 if p >= 2, l(2) = 2; end
76 if p >= 3, l(3) = 2; end
78if isfield(options, 'gamma') && ~isempty(options.gamma)
79 gam = options.gamma(:).';
81 gam = 15 * ones(1, p);
83 if p >= 2, gam(2) = 13; end
84 if p >= 3, gam(3) = 13; end
89 G = 0; lG = -Inf; return
95% contour radii r_j = 10^{-gamma_j/(2 l_j K_j)} (eq. 2.7)
101 r(j) = 10^(-gam(j) / (2 * l(j) * N(j)));
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).
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)
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);
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)
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));
132 an = (prod((Kj + ll) ./ (Kj + 2 * lj * Kj + ll)))^(1 / (2 * lj * Kj));
134 aj = min(aj, an / cumrho(n));
138 aj = min(aj, Kj / Z(j)); % IS/Poisson term K_j/rho_{j0}
141 aj = 1; % chain with no demand anywhere
144 alpha0(j) = exp(-aj * Z(j));
145 used = used + aj * L(:, j) * r(j); % deflate
for subsequent chains
148% context
for the recursion (local functions, not nested, to avoid MATLAB
149% nested-function workspace sharing across recursive calls)
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
159% run
the nested inversion on
the scaled generating function -> gbar(K)
160gbar = clw_invert(1, zeros(1, 0), ctx);
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));
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);
177 ph = exp(-1i * pi * k1 / lj);
180 theta = pi * (k1 + lj * kk) / (lj * Kj);
181 wj = rj * exp(1i * theta); % 1 x 2Kj contour points
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);
194 inner = inner + signs(t) * clw_invert(j + 1, [wfixed, wj(t)], ctx);
197 acc = acc + ph * inner;
199val = acc / (2 * lj * Kj * rj^Kj);
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);