LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
pfqn_ld_is.m
1function [G, lG] = pfqn_ld_is(L, N, Z, mu, options)
2% [G, LG] = PFQN_LD_IS(L, N, Z, MU, OPTIONS)
3%
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.
8%
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
11% per-position factor:
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).
18%
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.
25%
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
29% dynamic program
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.
34%
35% Parameters:
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).
46%
47% Returns:
48% G - IS estimate of the normalizing constant G(N).
49% lG - log(G).
50%
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));
55%
56% See also PFQN_IS, PFQN_OI_IS, PFQN_PAS_IS, PFQN_NCLD, PFQN_NC.
57%
58% Copyright (c) 2012-2026, Imperial College London
59% All rights reserved.
60
61if nargin < 5, options = struct(); end
62if nargin < 4, mu = []; end
63if nargin < 3, Z = []; end
64
65[M, R] = size(L);
66N = round(N(:)');
67if numel(N) ~= R
68 line_error(mfilename, 'L must have as many columns as N has classes.');
69end
70if any(~isfinite(N))
71 line_error(mfilename, 'pfqn_ld_is requires finite (closed) populations.');
72end
73if isempty(Z), Z = zeros(1, R); end
74Z = Z(:)';
75ell = sum(N);
76
77nsamples = 1e4;
78if isfield(options, 'samples') && ~isempty(options.samples)
79 nsamples = round(options.samples);
80end
81if isfield(options, 'seed') && ~isempty(options.seed)
82 rng(options.seed);
83end
84
85if ell == 0
86 G = 1; lG = 0;
87 return
88end
89
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.
92hasZ = any(Z > 0);
93S = M + double(hasZ);
94D = zeros(S, R);
95B = ones(S, ell);
96for i = 1:M
97 D(i, :) = L(i, :);
98 if isempty(mu)
99 B(i, :) = 1; % load-independent single server
100 elseif iscell(mu)
101 for k = 1:ell, B(i, k) = mu{i}(k); end
102 else
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);
106 end
107 end
108end
109if hasZ
110 D(S, :) = Z;
111 B(S, :) = 1:ell; % delay: mu_Z(k) = k
112end
113if any(B(:) <= 0)
114 line_error(mfilename, 'load-dependent capacities must be strictly positive.');
115end
116
117acc = 0;
118for s = 1:nsamples
119 % ---- draw an ordering c (uniformly random present class at each step) ---
120 x = N;
121 c = zeros(1, ell);
122 logp = 0;
123 for ppos = 1:ell
124 avail = find(x > 0);
125 na = numel(avail);
126 pick = avail(randi(na));
127 c(ppos) = pick;
128 logp = logp - log(na);
129 x(pick) = x(pick) - 1;
130 end
131
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
137 for m = 1:S
138 Anew = zeros(1, ell + 1);
139 for j = 0:ell
140 if A(j + 1) == 0, continue, end
141 w = 1;
142 Anew(j + 1) = Anew(j + 1) + A(j + 1); % empty segment
143 for k = j+1:ell
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;
147 end
148 end
149 A = Anew;
150 end
151 acc = acc + A(ell + 1) * exp(-logp);
152end
153
154G = acc / nsamples;
155lG = log(G);
156end
Definition Station.m:245