LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_nc_oi_analyzer.m
1function [Q,U,R,T,C,X,lG,runtime,iter,method] = solver_nc_oi_analyzer(sn, options)
2% [Q,U,R,T,C,X,LG,RUNTIME,ITER,METHOD] = SOLVER_NC_OI_NC_ANALYZER(SN, OPTIONS)
3%
4% Exact normalizing-constant analysis of a closed queueing network that mixes
5% order-independent (OI) stations with ordinary BCMP product-form stations.
6% Supported stations:
7% - OI stations (SchedStrategy.OI / PAS with an empty swap graph), analyzed
8% by the balanced-fairness rank rate mu(supp n) (Bonald & Proutiere 2003);
9% - any BCMP product-form station: infinite-server (delay, IS), processor
10% sharing (PS), LCFS-PR, and class-independent-rate FCFS, single- or multi-
11% server, analyzed by the load-dependent BCMP weight table
12% W_i(n) = (sum n)!/prod(n_r!) * prod_r D_{i,r}^{n_r} / prod_{k=1}^{sum n} beta_i(k),
13% with D_{i,r} = V(i,r)/rate(i,r) the per-class demand and beta_i(k) the
14% load-dependent capacity (min(k,c) for a c-server queue, k for IS).
15%
16% The full-network normalizing-constant table G(P) over the lattice
17% 0 <= P <= N is assembled by balanced-fairness convolution of all station
18% tables, with the OI stations and the aggregated delay evaluated through
19% PFQN_NCOI. The exact per-class mean queue length at any station follows from
20% the OI functional-server (FNC) identity of PFQN_OI_FNC (Casale, QEST 2006):
21% E[n_{i,r}] = ( sum_{0<=b<=N} Psi_{i,r}(b) G(N-b) ) / G(N) - 1,
22% Psi_{i,r} being the FNC balance function built from that station's balance
23% table with f(n)=n_r. Per-class throughput X_r = G(N-e_r)/G(N); delay queue
24% length and response time follow from Little's law.
25%
26% General per-class visits at the OI stations are supported: they enter the
27% v-weighted balanced-fairness balance Phi^v(n)=(1/mu(n)) sum_r v_r Phi^v(n-e_r).
28% BCMP-station visits are arbitrary (folded into D).
29%
30% Copyright (c) 2012-2026, Imperial College London
31% All rights reserved.
32Tstart = tic;
33iter = 1;
34method = 'oi';
35
36M = sn.nstations;
37K = sn.nclasses;
38
39% ---- reject class switching (OI rank rates are per raw class) --------------
40for c = 1:sn.nchains
41 if numel(sn.inchain{c}) > 1
42 line_error(mfilename, 'solver_nc_oi requires one class per chain (no class switching).');
43 end
44end
45if any(isinf(sn.njobs))
46 line_error(mfilename, 'solver_nc_oi requires a closed queueing network.');
47end
48N = round(sn.njobs(:)');
49
50% ---- classify stations -----------------------------------------------------
51% OI: rank-rate balanced-fairness station. INF: aggregated into the delay Z.
52% Q : ordinary BCMP product-form station (PS / LCFS-PR / FCFS), a load-
53% dependent weight table with server count c(ist).
54isOI = false(M,1);
55isINF = false(M,1);
56isQ = false(M,1);
57svc = cell(M,1);
58for ist = 1:M
59 ind = sn.stationToNode(ist);
60 if sn.sched(ist) == SchedStrategy.INF
61 isINF(ist) = true;
62 elseif sn.sched(ist) == SchedStrategy.PAS || sn.sched(ist) == SchedStrategy.OI
63 sg = [];
64 if ind >= 1 && ind <= numel(sn.nodeparam) && isstruct(sn.nodeparam{ind}) ...
65 && isfield(sn.nodeparam{ind}, 'swapGraph')
66 sg = sn.nodeparam{ind}.swapGraph;
67 end
68 if isempty(sg) || any(sg(:) ~= 0)
69 line_error(mfilename, 'solver_nc_oi supports OI stations only (PAS with a non-empty swap graph is not order-independent).');
70 end
71 isOI(ist) = true;
72 svc{ist} = sn.nodeparam{ind}.svcRateFun;
73 if isempty(svc{ist})
74 line_error(mfilename, 'OI station %d has no service rate function; set it via setService(@(c) ...).', ist);
75 end
76 elseif any(sn.sched(ist) == [SchedStrategy.PS, SchedStrategy.LCFSPR, SchedStrategy.FCFS, SchedStrategy.SIRO])
77 isQ(ist) = true;
78 if any(sn.sched(ist) == [SchedStrategy.FCFS, SchedStrategy.SIRO])
79 % BCMP type 1 (and the order-insensitive SIRO, which shares the
80 % FCFS queue-length distribution for exponential service) require a
81 % class-independent service rate for product form.
82 rr = sn.rates(ist, :);
83 rr = rr(isfinite(rr) & sn.njobs > 0);
84 if ~isempty(rr) && (max(rr) - min(rr)) > 1e-9 * max(rr)
85 line_error(mfilename, 'Station %d has class-dependent FCFS/SIRO rates and is not product form; solver_nc_oi requires class-independent rates.', ist);
86 end
87 end
88 else
89 line_error(mfilename, 'solver_nc_oi supports only INF (delay), OI, PS, LCFS-PR, SIRO and class-independent FCFS stations.');
90 end
91end
92
93% ---- per-class visits (chain == class); normalize to the reference station -
94V = zeros(M, K);
95for r = 1:K
96 c = find(sn.chains(:, r)); % the chain carrying class r
97 vis = sn.visits{c}; % (nstateful x nclasses)
98 for ist = 1:M
99 isf = sn.stationToStateful(ist);
100 V(ist, r) = vis(isf, r);
101 end
102 vref = V(sn.refstat(r), r);
103 if vref > 0
104 V(:, r) = V(:, r) / vref;
105 end
106end
107
108% ---- per-class demand and aggregated delay demand Z_r ----------------------
109% Z_r = sum over INF stations of V(i,r) * mean service time(i,r).
110ST = 1 ./ sn.rates;
111ST(~isfinite(ST)) = 0;
112Z = zeros(1, K);
113for ist = find(isINF(:))'
114 for r = 1:K
115 Z(r) = Z(r) + V(ist, r) * ST(ist, r);
116 end
117end
118D = zeros(M, K); % per-class demand at BCMP queues
119for ist = find(isQ(:))'
120 for r = 1:K
121 D(ist, r) = V(ist, r) * ST(ist, r);
122 end
123end
124
125% OI-station class visit ratios feed the v-weighted balanced-fairness balance
126% (pfqn_ncoi / oi_phi); general (non-unit) visits are supported.
127oiList = find(isOI(:))';
128oivis = cell(1, numel(oiList));
129for m = 1:numel(oiList)
130 oivis{m} = V(oiList(m), :);
131end
132
133% ---- OI rank-rate handles on a per-class count vector ----------------------
134% svcRateFun(c) is permutation-invariant, so it is a function of the count vector
135% n; evaluate it on a canonical microstate holding n_r copies of class r. see
136% _kb/06-solver-catalog.md (NC section, OI analyzer)
137rates = cell(1, numel(oiList));
138for m = 1:numel(oiList)
139 fun = svc{oiList(m)};
140 rates{m} = @(n) fun(oi_microstate(n));
141end
142
143% ---- population lattice ----------------------------------------------------
144[shp, stride, total] = oi_lattice(N);
145
146% ---- core normalizing-constant table (OI stations + aggregated delay) ------
147Gfull = zeros(total, 1);
148for i = 1:total
149 P = oi_sub(i, shp);
150 Gfull(i) = pfqn_ncoi(Z, P, rates, oivis);
151end
152
153% ---- fold the BCMP queueing stations by lattice convolution ----------------
154qList = find(isQ(:))';
155for ist = qList
156 c = sn.nservers(ist);
157 Wq = oi_ld_table(D(ist, :), c, shp, total);
158 Gfull = oi_conv(Gfull, Wq, shp, stride, total);
159end
160
161G = Gfull(total);
162lG = log(G);
163
164% ---- per-class throughput X_r = G(N - e_r)/G(N) ----------------------------
165X = zeros(1, K);
166for r = 1:K
167 if N(r) > 0
168 er = zeros(1, K); er(r) = 1;
169 X(r) = Gfull(1 + sum((N - er) .* stride)) / G;
170 end
171end
172
173% ---- per-station per-class mean queue length via the FNC identity ----------
174Q = zeros(M, K);
175for m = 1:numel(oiList) % OI stations
176 ist = oiList(m);
177 Phi = oi_phi(rates{m}, N, oivis{m});
178 for r = 1:K
179 if N(r) > 0
180 [~, Psir] = pfqn_oi_fnc(Phi, N, @(n) n(r));
181 Q(ist, r) = oi_fnc_mean(Psir, Gfull, shp, stride, total) / G - 1;
182 end
183 end
184end
185for ist = qList % BCMP queueing stations
186 Wq = oi_ld_table(D(ist, :), sn.nservers(ist), shp, total);
187 for r = 1:K
188 if N(r) > 0
189 [~, Psir] = pfqn_oi_fnc(Wq, N, @(n) n(r));
190 Q(ist, r) = oi_fnc_mean(Psir, Gfull, shp, stride, total) / G - 1;
191 end
192 end
193end
194for ist = find(isINF(:))' % delay: Little's law
195 for r = 1:K
196 Q(ist, r) = X(r) * V(ist, r) * ST(ist, r);
197 end
198end
199
200% ---- throughput, utilization, response time per station --------------------
201T = zeros(M, K);
202U = zeros(M, K);
203R = zeros(M, K);
204for ist = 1:M
205 for r = 1:K
206 T(ist, r) = X(r) * V(ist, r);
207 end
208end
209for ist = find(isINF(:))'
210 U(ist, :) = Q(ist, :); % INF utilization convention
211end
212for ist = qList % BCMP queue: offered-load per server
213 c = sn.nservers(ist);
214 if ~isfinite(c) || c <= 0, c = 1; end
215 for r = 1:K
216 U(ist, r) = X(r) * D(ist, r) / c;
217 end
218end
219for m = 1:numel(oiList)
220 % In-service utilization U_r = E[sir_r]/c via the functional-server identity
221 % E[f(n)] = G^{+}/G - 1 (pfqn_oi_fnc, pfqn_oi_insvc); see
222 % _kb/06-solver-catalog.md (NC section, OI analyzer)
223 ist = oiList(m);
224 S = sn.nservers(ist);
225 if ~isfinite(S) || S <= 0, S = 1; end
226 Phi = oi_phi(rates{m}, N, oivis{m});
227 gins = pfqn_oi_insvc(rates{m}, N);
228 for r = 1:K
229 if N(r) > 0
230 fr = @(n) gins(1 + sum(n .* stride), r);
231 [~, Psir] = pfqn_oi_fnc(Phi, N, fr);
232 U(ist, r) = (oi_fnc_mean(Psir, Gfull, shp, stride, total) / G - 1) / S;
233 end
234 end
235end
236for ist = 1:M
237 for r = 1:K
238 if T(ist, r) > 0
239 R(ist, r) = Q(ist, r) / T(ist, r);
240 end
241 end
242end
243
244C = zeros(1, K); % per-class system response time
245for r = 1:K
246 if X(r) > 0
247 C(r) = N(r) / X(r);
248 end
249end
250
251runtime = toc(Tstart);
252end
253
254% ==========================================================================
255function [shp, stride, total] = oi_lattice(N)
256% Column-major lattice descriptor for populations 0 <= n <= N.
257N = round(N(:)'); R = numel(N); shp = N + 1;
258stride = ones(1, R);
259for d = 2:R, stride(d) = stride(d-1) * shp(d-1); end
260total = prod(shp);
261end
262
263% ==========================================================================
264function c = oi_microstate(n)
265% Canonical ordered microstate holding n_r copies of class r (n a count
266% vector). For an order-independent station the service rate is invariant to
267% the ordering, so this representative suffices to evaluate svcRateFun(c).
268c = repelem(1:numel(n), round(n));
269end
270
271% ==========================================================================
272function n = oi_sub(i, shp)
273% Decode linear index i (1-based) to the subscript vector n (0-based counts).
274R = numel(shp); n = zeros(1, R); li = i - 1;
275for d = 1:R, n(d) = mod(li, shp(d)); li = floor(li / shp(d)); end
276end
277
278% ==========================================================================
279function Phi = oi_phi(oirate, N, vis)
280% Forward v-weighted balanced-fairness fill of the OI balance function:
281% Phi(0)=1, Phi(n) = (1/mu(n)) sum_{r: n_r>0} v_r Phi(n - e_r).
282[shp, stride, total] = oi_lattice(N);
283R = numel(shp);
284if nargin < 3 || isempty(vis), vis = ones(1, R); end
285Phiv = zeros(total, 1);
286for i = 1:total
287 n = oi_sub(i, shp);
288 if sum(n) == 0, Phiv(i) = 1; continue, end
289 s = 0;
290 for r = 1:R, if n(r) > 0, s = s + vis(r) * Phiv(i - stride(r)); end, end
291 Phiv(i) = s / oirate(n);
292end
293if R == 1, Phi = Phiv; else, Phi = reshape(Phiv, shp); end
294end
295
296% ==========================================================================
297function W = oi_ld_table(Dq, c, shp, total)
298% BCMP load-dependent weight table over the lattice:
299% W(n) = (sum n)!/prod(n_r!) * prod_r D_r^{n_r} / prod_{k=1}^{sum n} beta(k),
300% beta(k) = min(k,c) for a c-server queue (c=1 -> single server, beta==1).
301% Column-major flat vector. W(0)=1.
302Dq = Dq(:)'; R = numel(shp); W = zeros(total, 1);
303if ~isfinite(c) || c <= 0, c = 1; end
304for i = 1:total
305 n = oi_sub(i, shp);
306 tot = sum(n);
307 logf = gammaln(tot + 1); ok = true;
308 for r = 1:R
309 if n(r) > 0
310 if Dq(r) <= 0, ok = false; break, end
311 logf = logf + n(r) * log(Dq(r)) - gammaln(n(r) + 1);
312 end
313 end
314 if ~ok, continue, end
315 for k = 1:tot
316 logf = logf - log(min(k, c));
317 end
318 W(i) = exp(logf);
319end
320end
321
322% ==========================================================================
323function Cv = oi_conv(Av, Bv, shp, stride, total)
324% Lattice convolution Cv(m) = sum_{0<=a<=m} Av(a) Bv(m-a) over 0..N.
325R = numel(shp);
326subs = zeros(total, R);
327for i = 1:total, subs(i, :) = oi_sub(i, shp); end
328Cv = zeros(total, 1);
329for i = 1:total
330 m = subs(i, :); acc = 0;
331 for j = 1:i
332 a = subs(j, :);
333 if all(a <= m)
334 acc = acc + Av(j) * Bv(1 + sum((m - a) .* stride));
335 end
336 end
337 Cv(i) = acc;
338end
339end
340
341% ==========================================================================
342function val = oi_fnc_mean(Psi, Gfull, shp, stride, total)
343% G^{+} = sum_{0<=b<=N} Psi(b) G(N-b): the FNC of the target station convolved
344% against the full-network normalizing-constant table, evaluated at n = N.
345N = shp - 1; Psiv = Psi(:); val = 0;
346for i = 1:total
347 if Psiv(i) == 0, continue, end
348 b = oi_sub(i, shp);
349 val = val + Psiv(i) * Gfull(1 + sum((N - b) .* stride));
350end
351end