LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_mva_oi_analyzer.m
1function [QN,UN,RN,TN,CN,XN,lG,runtime,iter,actualmethod] = solver_mva_oi_analyzer(sn, options)
2% SOLVER_MVA_OI_ANALYZER Exact mean-value MVA for order-independent networks
3%
4% An order-independent (OI) station is a class-dependent load-dependent server
5% whose total service rate mu(n) is a permutation-invariant function of the
6% per-class count vector n. A closed network of infinite-server (delay) and
7% load-independent (single-server, product-form) stations plus ANY number of OI
8% stations is product-form. This analyzer aggregates the delay stations into a
9% single think-time vector Z, collects the load-independent (LI) queue demands
10% and the OI-station rate handles, and calls PFQN_MVAOI, the mean-value
11% Conditional-MVA (CMVA) that carries one rate-shift vector per OI station and
12% returns exact per-class throughput and queue-lengths WITHOUT any normalizing
13% constant or joint marginal. The marginal-distribution counterpart is
14% PFQN_MVAOI_MARG.
15%
16% Reference:
17% Reiser, Lavenberg (1980). Mean-Value Analysis of Closed Multichain Queuing
18% Networks. JACM 27(2). Load-dependent extension: Bruell, Balbo, Afshari
19% (1984). OI stations / CMVA: Casale (2009); Casale, Comte, Dorsman (2026).
20%
21% Copyright (c) 2012-2026, Imperial College London
22% All rights reserved.
23
24tstart = tic;
25
26oi_list = find_oi_stations(sn);
27if isempty(oi_list)
28 error('solver_mva_oi_analyzer:NoOIStation', ...
29 'OI solver requires at least one order-independent station');
30end
31
32M = sn.nstations;
33R = sn.nclasses;
34N = round(sn.njobs(:)');
35
36isOI = false(1, M);
37isOI(oi_list) = true;
38isDelay = (sn.sched(:)' == SchedStrategy.INF);
39
40% Per-class service demand D_ir = V_ir/rate_ir at non-OI stations.
41V = zeros(M, R);
42for c = 1:numel(sn.visits)
43 V = V + sn.visits{c};
44end
45D = zeros(M, R);
46for i = 1:M
47 for r = 1:R
48 if isfinite(sn.rates(i,r)) && sn.rates(i,r) > 0
49 D(i,r) = V(i,r) / sn.rates(i,r);
50 end
51 end
52end
53
54% Aggregate the delay stations into Z; collect the LI queue demands and OI-station
55% rate handles. A multiserver BCMP queue (c>1) cannot take the single-server LI
56% path, so it is promoted to an OI station via the c-server BCMP weight; see
57% _kb/06-solver-catalog.md (NC section, OI analyzer) for the W(n)/mu(n) identity.
58Z = zeros(1, R);
59li_list = [];
60ms_list = [];
61for i = 1:M
62 if isOI(i)
63 continue
64 elseif isDelay(i)
65 Z = Z + D(i,:);
66 elseif isfinite(sn.nservers(i)) && sn.nservers(i) > 1
67 ms_list(end+1) = i; %#ok<AGROW>
68 else
69 li_list(end+1) = i; %#ok<AGROW>
70 end
71end
72Dli = D(li_list, :);
73
74muCell = cell(1, numel(oi_list) + numel(ms_list));
75% Per-muCell-station visit vectors. Genuine OI stations carry their class visit
76% ratios V(oi,:) (the rate handle has no visits); ms-promoted stations pass
77% ones, as their visits are already folded into D by ms_oi_rate.
78oivis = cell(1, numel(oi_list) + numel(ms_list));
79for o = 1:numel(oi_list)
80 node_oi = sn.stationToNode(oi_list(o));
81 svcRateFun = sn.nodeparam{node_oi}.svcRateFun;
82 muCell{o} = @(n) oi_rate(svcRateFun, n, R);
83 oivis{o} = V(oi_list(o), :);
84end
85for j = 1:numel(ms_list)
86 muCell{numel(oi_list) + j} = ms_oi_rate(D(ms_list(j), :), sn.nservers(ms_list(j)));
87 oivis{numel(oi_list) + j} = ones(1, R);
88end
89
90[X, Qoi, Qli, ~, Soi] = pfqn_mvaoi(Z, N, muCell, Dli, oivis, options);
91
92% Assemble per-station mean queue-lengths.
93QN = zeros(M, R);
94for o = 1:numel(oi_list)
95 QN(oi_list(o), :) = Qoi(o, :);
96end
97for j = 1:numel(ms_list)
98 QN(ms_list(j), :) = Qoi(numel(oi_list) + j, :);
99end
100for j = 1:numel(li_list)
101 QN(li_list(j), :) = Qli(j, :);
102end
103for i = 1:M
104 if isDelay(i)
105 QN(i, :) = X .* D(i, :); % IS station: exact product-form share
106 end
107end
108XN = X;
109
110% Row of Soi/Qoi holding each OI station (muCell order: oi_list, then ms_list).
111oiRow = zeros(1, M);
112for o = 1:numel(oi_list)
113 oiRow(oi_list(o)) = o;
114end
115
116% Assemble outputs at the full population.
117TN = zeros(M, R);
118RN = zeros(M, R);
119UN = zeros(M, R);
120CN = zeros(M, R);
121for i = 1:M
122 for r = 1:R
123 TN(i,r) = XN(r) * V(i,r);
124 if XN(r) > 0
125 RN(i,r) = QN(i,r) / XN(r);
126 end
127 if isOI(i)
128 % In-service utilization U_r = E[sir_r]/nservers; see
129 % _kb/06-solver-catalog.md (NC section, OI analyzer). Soi holds E[sir_r].
130 sv = sn.nservers(i);
131 if ~isfinite(sv) || sv <= 0
132 sv = 1;
133 end
134 UN(i,r) = Soi(oiRow(i), r) / sv;
135 elseif isDelay(i)
136 UN(i,r) = QN(i,r);
137 else
138 sv = sn.nservers(i);
139 if ~isfinite(sv) || sv <= 0
140 sv = 1;
141 end
142 UN(i,r) = XN(r) * D(i,r) / sv;
143 end
144 CN(i,r) = RN(i,r);
145 end
146end
147
148lG = 0;
149runtime = toc(tstart);
150iter = sum(N);
151actualmethod = 'oi';
152end
153
154% =========================================================================
155% Helper functions
156% =========================================================================
157
158function oi_list = find_oi_stations(sn)
159% Station indices of all OI stations: PAS/OI scheduling with an all-zero swap
160% graph and a service-rate function (mirrors nc_is_oi_model detection).
161oi_list = [];
162for ist = 1:sn.nstations
163 if sn.sched(ist) ~= SchedStrategy.PAS && sn.sched(ist) ~= SchedStrategy.OI
164 continue
165 end
166 ind = sn.stationToNode(ist);
167 if ind < 1 || ind > numel(sn.nodeparam) || ~isstruct(sn.nodeparam{ind})
168 continue
169 end
170 np = sn.nodeparam{ind};
171 if ~isfield(np, 'swapGraph') || ~isfield(np, 'svcRateFun') || isempty(np.svcRateFun)
172 continue
173 end
174 sg = np.swapGraph;
175 if isempty(sg) || any(sg(:) ~= 0)
176 continue
177 end
178 oi_list(end+1) = ist; %#ok<AGROW>
179end
180end
181
182function h = ms_oi_rate(Dq, c)
183% OI rate function reproducing the c-server BCMP station with per-class demands
184% Dq: mu(n) = (min(|n|,c)/|n|) * sum_{r: n_r>0} n_r/Dq_r. For c = 1 this is the
185% familiar total completion rate of a multiclass single-server queue, and for a
186% single class it reduces to min(n,c)/Dq (M/M/c).
187Dq = Dq(:)';
188if ~isfinite(c) || c <= 0
189 c = 1;
190end
191h = @(n) ms_oi_rate_eval(n, Dq, c);
192end
193
194function rate = ms_oi_rate_eval(n, Dq, c)
195tot = sum(n);
196if tot == 0
197 rate = 0;
198 return
199end
200acc = 0;
201for r = 1:numel(n)
202 if n(r) > 0 && Dq(r) > 0
203 acc = acc + n(r) / Dq(r);
204 end
205end
206rate = (min(tot, c) / tot) * acc;
207end
208
209function rate = oi_rate(svcRateFun, n, R)
210% OI total service rate at count vector n via the 1-based canonical microstate.
211if sum(n) == 0
212 rate = 0;
213 return
214end
215micro = repelem(1:R, n);
216rate = svcRateFun(micro);
217end