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