1function [G, lG] = pfqn_ld_is(L, N, Z, mu, options)
2% [G, LG] = PFQN_LD_IS(L, N, Z, MU, OPTIONS)
4% Importance-sampling (IS) estimate of
the normalizing constant of a closed
5% LOAD-DEPENDENT product-form queueing network. This
is the load-dependent
6% counterpart of PFQN_PAS_IS / PFQN_OI_IS:
the same sample-an-ordering estimator,
7% with
the order-independent rank rate replaced by
the load-dependent capacity.
9% Identity. Every product-form station
's balance function is the sum, over the
10% orderings q of a given per-class count vector n, of an ordered product of a
12% F_i(n) = |n|!/prod_r(n_r!) * prod_r L(i,r)^{n_r} / prod_{k=1}^{|n|} mu_i(k)
13% = sum_{q: |q|=n} prod_{p=1}^{|n|} L(i,q_p) / mu_i(p),
14% since the multiset has |n|!/prod_r(n_r!) orderings and each contributes the
15% same ordered product. The delay (infinite-server) node is the special case
16% mu_Z(k) = k, giving F_Z(n) = prod_r Z_r^{n_r}/n_r!; a single-server queue is
17% mu_i(k) = 1; a c-server queue is mu_i(k) = min(k,c).
19% Consequently, writing ell = sum(N) and letting a "cut vector" split an ordering
20% c of all ell jobs into S contiguous segments (one per station),
21% G(N) = sum_{c} sum_{cuts} prod_{m=1}^{S} w_m(seg_m),
22% w_m(q) = prod_{p=1}^{|q|} L(m,q_p) / mu_m(p),
23% because summing over the orderings of each segment independently reproduces
24% prod_m F_m(n_m), and each count split (n_1,...,n_S) is realized exactly once.
26% Estimator. An ordering c is drawn by placing, at each step, a uniformly random
27% present class; p(c) is the product of the reciprocal branching factors. For the
28% sampled c the inner sum over ALL cut vectors is computed exactly by the
30% A_0(0) = 1, A_m(k) = sum_{j=0}^{k} A_{m-1}(j) * w_m(c_{j+1..k}),
31% so S(c) = A_S(ell) in O(S*ell^2) time (no cut enumeration). Then
32% G = E_{C~p}[ S(C) / p(C) ]
33% is unbiased and is estimated by the sample mean.
36% L - (M x R) per-class service demands at the M queueing stations.
37% N - (1 x R) closed population vector, finite.
38% Z - (1 x R) aggregated think time (delay) demand; [] or zeros if none.
39% mu - load-dependent capacities. Either an (M x ell) matrix with
40% mu(i,k) the capacity of station i holding k jobs, or a cell
41% {1 x M} of function handles mu{i}(k), or [] for the
42% load-independent case mu(i,k) = 1 (see PFQN_IS).
43% options - solver options (optional). Fields used:
44% .samples number of IS samples (default 1e4);
45% .seed RNG seed for reproducibility (optional).
48% G - IS estimate of the normalizing constant G(N).
51% Example (2 queues + delay, load-dependent):
52% L = [0.5 0.3; 0.2 0.4]; N = [3 2]; Z = [1 1];
53% mu = [1 2 2 2 2; 1 1 1 1 1]; % station 1 is a 2-server queue
54% [G,lG] = pfqn_ld_is(L, N, Z, mu, struct('samples
',1e5));
56% See also PFQN_IS, PFQN_OI_IS, PFQN_PAS_IS, PFQN_NCLD, PFQN_NC.
58% Copyright (c) 2012-2026, Imperial College London
61if nargin < 5, options = struct(); end
62if nargin < 4, mu = []; end
63if nargin < 3, Z = []; end
68 line_error(mfilename,
'L must have as many columns as N has classes.');
71 line_error(mfilename,
'pfqn_ld_is requires finite (closed) populations.');
73if isempty(Z), Z = zeros(1, R); end
78if isfield(options, 'samples
') && ~isempty(options.samples)
79 nsamples = round(options.samples);
81if isfield(options, 'seed
') && ~isempty(options.seed)
90% ---- assemble the station list: M queues, plus the delay as mu_Z(k)=k -------
91% D(m,r) is the per-class demand of station m; B(m,k) its capacity at k jobs.
99 B(i, :) = 1; % load-independent single server
101 for k = 1:ell, B(i, k) = mu{i}(k); end
103 B(i, 1:min(ell, size(mu, 2))) = mu(i, 1:min(ell, size(mu, 2)));
104 if size(mu, 2) < ell % extend with the last capacity
105 B(i, size(mu,2)+1:ell) = mu(i, end);
111 B(S, :) = 1:ell; % delay: mu_Z(k) = k
114 line_error(mfilename, 'load-dependent capacities must be strictly positive.
');
119 % ---- draw an ordering c (uniformly random present class at each step) ---
126 pick = avail(randi(na));
128 logp = logp - log(na);
129 x(pick) = x(pick) - 1;
132 % ---- exact inner sum over all cut vectors, by dynamic programming -------
133 % A(k) = weight of assigning the first k jobs of c to the stations seen so
134 % far; W(j+1,k) = w_m(c_{j+1..k}) accumulated incrementally over k.
135 A = zeros(1, ell + 1);
136 A(1) = 1; % A(k+1) indexes k jobs placed
138 Anew = zeros(1, ell + 1);
140 if A(j + 1) == 0, continue, end
142 Anew(j + 1) = Anew(j + 1) + A(j + 1); % empty segment
144 w = w * D(m, c(k)) / B(m, k - j); % position within segment
145 if w == 0, break, end
146 Anew(k + 1) = Anew(k + 1) + A(j + 1) * w;
151 acc = acc + A(ell + 1) * exp(-logp);