LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
pfqn_conv.m
1function [G,lG] = pfqn_conv(L, N, Z, cdscaling, options)
2% [G,LG] = PFQN_CONV(L, N, Z, CDSCALING, OPTIONS)
3%
4% Multichain convolution algorithm for closed queueing networks with
5% class-dependent service rates.
6%
7% Implements the convolution algorithm of Sauer (1983), Section 5.2,
8% "Computational Algorithms for State-Dependent Queueing Networks",
9% ACM TOCS, Vol. 1, No. 1, pp. 67-92.
10%
11% The algorithm computes G(N) = (X_1 * X_2 * ... * X_M)(N) where X_m(n)
12% is the station factor at population vector n, and * denotes the
13% multivariate discrete convolution:
14% A(n) = sum_{i: 0<=i<=n} B(i) * C(n-i)
15%
16% For class-dependent stations, X_m(n) is computed recursively via Sauer
17% eq. (40):
18% X_m(n) = (u_km / mu_km(n)) * X_m(n - e_k)
19% where mu_km(n) = (n_k/|n|) * beta_{m,k}(n) and beta is the DIMENSIONLESS
20% class-dependent scaling of the service demand supplied by CDSCALING{m}: a
21% handle of the per-class population vector n at station m, returning either a
22% scalar (shared by every class) or a length-R vector. Equivalently
23% X_m(n) = (|n|/n_k) * (L(m,k)/beta_{m,k}(n)) * X_m(n - e_k),
24% which at beta = 1 is exactly the load-independent multinomial form, so a unit
25% scaling means "no correction". This is the same convention AMVA and CTMC use
26% (effective service time ST/beta). Any saturation/cutoff is applied inside the
27% handle.
28%
29% For standard (load-independent) stations, X_m(n) reduces to the
30% multinomial form and the convolution uses the efficient recurrence:
31% G_m(n) = G_{m-1}(n) + sum_r L(m,r) * G_m(n - e_r)
32%
34% L - Service demand matrix (M x R)
35% N - Population vector (1 x R), must be finite (closed network)
36% Z - Think time vector (1 x R), or empty
37% cdscaling - Cell array {M,1} of class-dependence handles beta_m(n);
38% empty entries denote load-independent stations
39% options - Solver options (optional)
40%
41% Returns:
42% G - Normalizing constant G(N)
43% lG - log(G(N))
44
45% Copyright (c) 2012-2026, Imperial College London
46% All rights reserved.
47
48[M, R] = size(L);
49
50if nargin < 3 || isempty(Z)
51 Z = zeros(1, R);
52end
53if nargin < 4 || isempty(cdscaling)
54 cdscaling = cell(M, 1);
55end
56
57if any(~isfinite(N))
58 line_error(mfilename, 'Convolution algorithm requires finite (closed) populations.');
59end
60
61% Total state space size
62stateSpaceSize = prod(N + 1);
63
64% Identify which stations carry a class-dependence function
65isCd = false(M, 1);
66for ist = 1:M
67 isCd(ist) = ~isempty(cdscaling{ist});
68end
69
70% --- Precompute X_m(n) tables for class-dependent stations ---
71% For class-dependent stations, X_m(n) is built via Sauer eq. (40):
72% X_m(n) = (u_km / mu_km(n)) * X_m(n - e_k), X_m(0) = 1
73% where u_km = L(m,k) (relative utilization) and mu_km(n) = beta_{m,k}(n) is
74% the service RATE for chain k at station m with population n. From eq. (40),
75% the recurrence factor is u_km / mu_km(n) = L(m,k) / beta_{m,k}(n).
76
77Xm = cell(M, 1);
78for ist = 1:M
79 if isCd(ist)
80 Xm{ist} = zeros(stateSpaceSize, 1);
81 Xm{ist}(1) = 1; % X_m(0) = 1
82
83 % Enumerate all population vectors and build X_m(n) recursively
84 n = pprod(N);
85 while n(1) >= 0
86 idx = hashpop(n, N);
87 if sum(n) == 0
88 Xm{ist}(idx) = 1;
89 else
90 % Use eq. (40): X_m(n) = (u_km / mu_km(n)) * X_m(n-e_k)
91 % Pick first class k with n(k) > 0
92 for r = 1:R
93 if n(r) > 0
94 % Get service rate mu_km(n) from the class-dependence handle
95 % beta_{ist,r}(n) is a DIMENSIONLESS scaling of the
96 % service demand: the effective demand of class r at
97 % station ist in state n is L(ist,r)/beta. The handle
98 % returns either a scalar (shared by every class) or a
99 % length-R vector.
100 bval = cdscaling{ist}(n);
101 if numel(bval) > 1
102 beta = bval(r);
103 else
104 beta = bval;
105 end
106
107 % X_m(n) = (|n|/n_r) * (L(m,r)/beta) * X_m(n - e_r).
108 % This is Sauer's eq. (40) with mu_km(n) =
109 % (n_r/|n|) * beta_{m,r}(n), i.e. the processor-sharing
110 % share of the station scaled by beta. At beta = 1 it
111 % reduces to the load-independent multinomial recurrence,
112 % so a unit scaling means "no correction".
113 tot = sum(n);
114 nr = n(r);
115 n(r) = n(r) - 1;
116 idx_prev = hashpop(n, N);
117 n(r) = n(r) + 1;
118 if beta > 0
119 Xm{ist}(idx) = (tot / nr) * (L(ist, r) / beta) * Xm{ist}(idx_prev);
120 else
121 Xm{ist}(idx) = 0;
122 end
123 break
124 end
125 end
126 end
127 n = pprod(n, N);
128 end
129 end
130end
131
132% --- Convolution ---
133% G_0(n) = F_Z(n) (delay contribution)
134% G_m(n) = (G_{m-1} * X_m)(n) for class-dependent stations
135% G_m(n) = G_{m-1}(n) + sum_r L(m,r) * G_m(n-e_r) for LI stations
136
137G_curr = zeros(stateSpaceSize, 1);
138
139% Initialize G_0(n) = F_Z(n): delay server contribution
140n = pprod(N);
141while n(1) >= 0
142 idx = hashpop(n, N);
143 G_curr(idx) = Fz(Z, n);
144 n = pprod(n, N);
145end
146
147% Convolve one station at a time
148for ist = 1:M
149 if isCd(ist)
150 % class-dependent station: direct convolution sum
151 % G_new(n) = sum_{i: 0<=i<=n} X_m(i) * G_old(n-i)
152 G_old = G_curr;
153 G_curr = zeros(stateSpaceSize, 1);
154
155 n = pprod(N);
156 while n(1) >= 0
157 idx_n = hashpop(n, N);
158 conv_sum = 0;
159
160 % Inner loop: enumerate all i from 0 to n
161 i = pprod(n);
162 while i(1) >= 0
163 idx_i = hashpop(i, N);
164 nmi = n - i; % n - i (component-wise)
165 idx_nmi = hashpop(nmi, N);
166 conv_sum = conv_sum + Xm{ist}(idx_i) * G_old(idx_nmi);
167 i = pprod(i, n);
168 end
169
170 G_curr(idx_n) = conv_sum;
171 n = pprod(n, N);
172 end
173 else
174 % Load-independent station: efficient recurrence
175 % G_m(n) = G_{m-1}(n) + sum_r L(m,r) * G_m(n - e_r)
176 n = pprod(N);
177 while n(1) >= 0
178 idx_n = hashpop(n, N);
179 % G_curr(idx_n) already has G_{m-1}(n) from previous iteration
180 for r = 1:R
181 if n(r) >= 1
182 n(r) = n(r) - 1;
183 idx_n1r = hashpop(n, N);
184 n(r) = n(r) + 1;
185 G_curr(idx_n) = G_curr(idx_n) + L(ist, r) * G_curr(idx_n1r);
186 end
187 end
188 n = pprod(n, N);
189 end
190 end
191end
192
193G = G_curr(end); % G(N) is at the last index (hashpop(N,N) = prod(N+1))
194lG = log(G);
195end
196
197%% --- Local functions ---
198
199function idx = hashpop(n, N)
200% HASHPOP Map population vector to linear index (1-based)
201% idx = 1 + n(1) + n(2)*(N(1)+1) + n(3)*(N(1)+1)*(N(2)+1) + ...
202idx = 1;
203R = length(N);
204for r = 1:R
205 idx = idx + prod(N(1:r-1) + 1) * n(r);
206end
207end
208
209function [n] = pprod(n, N)
210% PPROD Sequentially generate all vectors n: 0 <= n <= N
211% n = pprod(N) - initialize to zeros
212% n = pprod(n, N) - advance to next vector, returns n(1)=-1 when done
213if nargin == 1
214 N = n;
215 n = zeros(size(N));
216 return
217end
218
219R = length(N);
220if sum(n == N) == R
221 n = -1 * ones(1, R);
222 return
223end
224
225s = R;
226while s > 0 && n(s) == N(s)
227 n(s) = 0;
228 s = s - 1;
229end
230if s > 0
231 n(s) = n(s) + 1;
232end
233end
234
235function f = Fz(Z, n)
236% FZ Delay server unnormalized probability factor
237% F = (Z(1)^n(1) / n(1)!) * ... * (Z(R)^n(R) / n(R)!)
238R = length(n);
239if sum(n) == 0
240 f = 1;
241 return
242end
243f = 0;
244for r = 1:R
245 if Z(r) > 0
246 f = f + log(Z(r)) * n(r);
247 f = f - gammaln(1 + n(r));
248 elseif n(r) > 0
249 f = 0;
250 return
251 end
252end
253f = exp(f);
254end
Definition Station.m:245