LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
pfqn_ncoi.m
1function [G, lG] = pfqn_ncoi(Z, N, mu, visits, options)
2% [G, LG] = PFQN_NCOI(Z, N, MU, OPTIONS)
3%
4% Normalizing constant for a closed product-form queueing network that
5% comprises a single aggregated infinite-server (delay) node and an
6% arbitrary number of order-independent (OI) / pass-and-swap stations with
7% empty swap graph.
8%
9% The OI stations are analyzed by the balanced-fairness recursion of
10% Bonald & Proutiere (2003), "Insensitive bandwidth sharing in data
11% networks", combined with the multichain convolution over stations. For a
12% single OI station with rank rate mu(supp(n)) the balance function is
13% Phi(0) = 1, Phi(n) = (1/mu(n)) * sum_{r: n_r>0} Phi(n - e_r),
14% and G(N) is obtained by convolving the per-station balance functions with
15% the multinomial delay factor F_Z(n) = prod_r Z_r^{n_r} / n_r!,
16% g_0(n) = F_Z(n), g_m(n) = sum_{0<=x<=n} Phi_m(x) g_{m-1}(n-x),
17% G(N) = g_M(N).
18%
19% This is a MACROSTATE routine: both the balance functions and the
20% convolution are tabulated over the count lattice 0 <= n <= N, never over
21% orderings. That is legitimate exactly because an OI rate is permutation-
22% invariant, so Phi(n) -- itself the sum of the ordered-prefix weights
23% prod_p 1/mu(c_1..c_p) over all orderings c of the multiset n -- closes on
24% the count vector. With a non-empty swap graph that closure fails and the
25% microstate routine PFQN_PAS_NC must be used instead.
26%
27% COST. With L = prod_r (N_r+1) the balance functions cost O(M R L) and the
28% convolutions O(M sum_{n<=N} prod_r (n_r+1)) = O(M prod_r (N_r+1)(N_r+2)/2),
29% i.e. the order of a load-dependent Buzen convolution: polynomial in the
30% population for a fixed number of classes.
31%
32% Parameters:
33% Z - (1 x R) think-time demand vector of the aggregated delay node.
34% Z(r) = 1/sigma_r for a delay with per-class rate sigma_r.
35% N - (1 x R) closed population vector, finite.
36% mu - cell array {1 x M} of function handles, one per OI station. Each
37% mu{m}(n) returns the total service rate of station m given the
38% per-class occupancy (count) vector n (1 x R). For an order-
39% independent station this rate depends only on the support of n
40% (which classes are present), i.e. mu{m}(n) = sum of the capacities
41% of the servers compatible with the classes present in n. A station
42% state whose rate is non-positive is unreachable and is assigned a
43% zero balance value. May be empty to model a pure delay network.
44% options - solver options (optional, currently unused; accepted for
45% signature parity with the other pfqn_* routines).
46%
47% Returns:
48% G - Normalizing constant G(N).
49% lG - log(G(N)).
50%
51% Example (IS + two OI stations, R classes):
52% oirate = @(n) sum(mu1(any(compat1(:, find(n>0)) ~= 0, 2)));
53% oirate2 = @(n) sum(mu2(any(compat2(:, find(n>0)) ~= 0, 2)));
54% G = pfqn_ncoi(1./sigma, N, {oirate, oirate2});
55%
56% See also PFQN_PAS_NC, PFQN_OI_FNC, PFQN_OI_INSVC, PFQN_MVAOI.
57%
58% Copyright (c) 2012-2026, Imperial College London
59% All rights reserved.
60
61if nargin < 5
62 options = struct(); %#ok<NASGU>
63end
64if nargin < 4
65 visits = [];
66end
67if nargin < 3 || isempty(mu)
68 mu = {};
69end
70if ~iscell(mu)
71 mu = {mu};
72end
73
74R = numel(N);
75if isempty(Z)
76 Z = zeros(1, R);
77end
78if numel(Z) ~= R
79 line_error(mfilename, 'Z and N must have the same number of classes.');
80end
81if any(~isfinite(N))
82 line_error(mfilename, 'pfqn_ncoi requires finite (closed) populations.');
83end
84
85N = round(N(:)');
86Z = Z(:)';
87M = numel(mu);
88
89% Per-OI-station class visit ratios. Default unit visits reproduce the plain
90% rank-rate balance; general visits enter as a per-class weight on the
91% balanced-fairness recurrence, Phi^v(n)=(1/mu(n)) sum_r v_r Phi^v(n-e_r), the
92% v-weighted balance whose class-r geometric factor prod_r v_r^{n_r} carries
93% the OI-station visit ratio (see _kb/06-solver-catalog.md, NC OI analyzer).
94if nargin < 4 || isempty(visits)
95 visits = repmat({ones(1, R)}, 1, M);
96elseif ~iscell(visits)
97 vmat = visits; visits = cell(1, M);
98 for mm = 1:M, visits{mm} = vmat(mm, :); end
99end
100
101% Count lattice 0 <= n <= N, flattened column-major; lin(v) = 1 + sum(v.*strides)
102% is affine, so lin(x+y) = lin(x) + lin(y) - 1, which the convolution exploits.
103dims = N + 1;
104ngrid = prod(dims);
105strides = [1, cumprod(dims(1:end-1))];
106counts = zeros(ngrid, R);
107sub = cell(1, R);
108for k = 1:ngrid
109 [sub{:}] = ind2sub(dims, k);
110 counts(k, :) = [sub{:}] - 1;
111end
112[~, ord] = sort(sum(counts, 2)); % population-increasing sweep order
113
114% Delay balance function: the multinomial factor F_Z(n). A class with
115% population but no delay demand makes the state infeasible (weight zero).
116g = zeros(ngrid, 1);
117for k = 1:ngrid
118 v = counts(k, :);
119 logf = 0;
120 feas = true;
121 for r = 1:R
122 if v(r) > 0
123 if Z(r) <= 0
124 feas = false;
125 break
126 end
127 logf = logf + v(r) * log(Z(r)) - gammaln(v(r) + 1);
128 end
129 end
130 if feas
131 g(k) = exp(logf);
132 end
133end
134
135% Convolve in one OI station at a time.
136for m = 1:M
137 Phim = oi_nc_balance(counts, ord, strides, mu{m}, R, ngrid, visits{m});
138 gnext = zeros(ngrid, 1);
139 for kx = 1:ngrid
140 px = Phim(kx);
141 if px == 0
142 continue
143 end
144 x = counts(kx, :);
145 rem = N - x;
146 % Linear indices of the sub-box 0 <= y <= rem, built by mixed radix.
147 ylin = 1;
148 for d = 1:R
149 ylin = bsxfun(@plus, ylin(:), strides(d) * (0:rem(d)));
150 ylin = ylin(:);
151 end
152 base = 1 + sum(x .* strides);
153 gnext(base + ylin - 1) = gnext(base + ylin - 1) + px * g(ylin);
154 end
155 g = gnext;
156end
157
158G = g(ngrid);
159lG = log(G);
160end
161
162function Phi = oi_nc_balance(counts, ord, strides, murate, R, ngrid, vis)
163% v-weighted balanced-fairness recursion
164% Phi(n) = (1/mu(n)) sum_{r: n_r>0} v_r Phi(n-e_r), over the count lattice,
165% swept in population-increasing order. vis is the 1xR class visit vector.
166if nargin < 7 || isempty(vis), vis = ones(1, R); end
167Phi = zeros(ngrid, 1);
168for t = 1:ngrid
169 k = ord(t);
170 n = counts(k, :);
171 if all(n == 0)
172 Phi(k) = 1;
173 continue
174 end
175 mun = murate(n);
176 if ~(mun > 0)
177 continue % unreachable station state: zero balance value
178 end
179 acc = 0;
180 for r = 1:R
181 if n(r) > 0
182 acc = acc + vis(r) * Phi(k - strides(r));
183 end
184 end
185 Phi(k) = acc / mun;
186end
187end