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 a single think-time vector Z; collect the
55% load-independent (LI) queue demands and the OI-station rate handles.
56% PFQN_MVAOI models an LI queue as a SINGLE server, so a multiserver BCMP queue
57% (c > 1) cannot go down that path. It is instead promoted to an OI station:
58% the c-server BCMP weight W(n) = |n|!/prod(n_r!) prod_r D_r^{n_r} /
59% prod_{k<=|n|} beta(k), beta(k) = min(k,c), satisfies the OI balance recursion
60% Phi(n) = (1/mu(n)) sum_{r:n_r>0} Phi(n - e_r) exactly for the permutation-
61% invariant rate mu(n) = (beta(|n|)/|n|) * sum_r n_r/D_r, since
62% W(n - e_r)/W(n) = n_r beta(|n|)/(|n| D_r). This reuses the exact multi-OI CMVA
63% instead of approximating the multiserver station. Reporting still classifies
64% the station as a queue (ms_list), so its utilization keeps the per-server
65% offered-load convention.
66Z = zeros(1, R);
67li_list = [];
68ms_list = [];
69for i = 1:M
70 if isOI(i)
71 continue
72 elseif isDelay(i)
73 Z = Z + D(i,:);
74 elseif isfinite(sn.nservers(i)) && sn.nservers(i) > 1
75 ms_list(end+1) = i; %#ok<AGROW>
76 else
77 li_list(end+1) = i; %#ok<AGROW>
78 end
79end
80Dli = D(li_list, :);
81
82muCell = cell(1, numel(oi_list) + numel(ms_list));
83for o = 1:numel(oi_list)
84 node_oi = sn.stationToNode(oi_list(o));
85 svcRateFun = sn.nodeparam{node_oi}.svcRateFun;
86 muCell{o} = @(n) oi_rate(svcRateFun, n, R);
87end
88for j = 1:numel(ms_list)
89 muCell{numel(oi_list) + j} = ms_oi_rate(D(ms_list(j), :), sn.nservers(ms_list(j)));
90end
91
92[X, Qoi, Qli, ~, Soi] = pfqn_mvaoi(Z, N, muCell, Dli, options);
93
94% Assemble per-station mean queue-lengths.
95QN = zeros(M, R);
96for o = 1:numel(oi_list)
97 QN(oi_list(o), :) = Qoi(o, :);
98end
99for j = 1:numel(ms_list)
100 QN(ms_list(j), :) = Qoi(numel(oi_list) + j, :);
101end
102for j = 1:numel(li_list)
103 QN(li_list(j), :) = Qli(j, :);
104end
105for i = 1:M
106 if isDelay(i)
107 QN(i, :) = X .* D(i, :); % IS station: exact product-form share
108 end
109end
110XN = X;
111
112% Row of Soi/Qoi holding each OI station (muCell order: oi_list, then ms_list).
113oiRow = zeros(1, M);
114for o = 1:numel(oi_list)
115 oiRow(oi_list(o)) = o;
116end
117
118% Assemble outputs at the full population.
119TN = zeros(M, R);
120RN = zeros(M, R);
121UN = zeros(M, R);
122CN = zeros(M, R);
123for i = 1:M
124 for r = 1:R
125 TN(i,r) = XN(r) * V(i,r);
126 if XN(r) > 0
127 RN(i,r) = QN(i,r) / XN(r);
128 end
129 if isOI(i)
130 % In-service utilization U_r = E[sir_r]/nservers, as in
131 % solver_nc_oi_analyzer and the exact CTMC/LDES: sir_r counts the
132 % class-r jobs receiving a strictly positive rank rate. Soi holds
133 % E[sir_r] from the OI count marginal.
134 sv = sn.nservers(i);
135 if ~isfinite(sv) || sv <= 0
136 sv = 1;
137 end
138 UN(i,r) = Soi(oiRow(i), r) / sv;
139 elseif isDelay(i)
140 UN(i,r) = QN(i,r);
141 else
142 sv = sn.nservers(i);
143 if ~isfinite(sv) || sv <= 0
144 sv = 1;
145 end
146 UN(i,r) = XN(r) * D(i,r) / sv;
147 end
148 CN(i,r) = RN(i,r);
149 end
150end
151
152lG = 0;
153runtime = toc(tstart);
154iter = sum(N);
155actualmethod = 'oi';
156end
157
158% =========================================================================
159% Helper functions
160% =========================================================================
161
162function oi_list = find_oi_stations(sn)
163% Station indices of all OI stations: PAS/OI scheduling with an all-zero swap
164% graph and a service-rate function (mirrors nc_is_oi_model detection).
165oi_list = [];
166for ist = 1:sn.nstations
167 if sn.sched(ist) ~= SchedStrategy.PAS && sn.sched(ist) ~= SchedStrategy.OI
168 continue
169 end
170 ind = sn.stationToNode(ist);
171 if ind < 1 || ind > numel(sn.nodeparam) || ~isstruct(sn.nodeparam{ind})
172 continue
173 end
174 np = sn.nodeparam{ind};
175 if ~isfield(np, 'swapGraph') || ~isfield(np, 'svcRateFun') || isempty(np.svcRateFun)
176 continue
177 end
178 sg = np.swapGraph;
179 if isempty(sg) || any(sg(:) ~= 0)
180 continue
181 end
182 oi_list(end+1) = ist; %#ok<AGROW>
183end
184end
185
186function h = ms_oi_rate(Dq, c)
187% OI rate function reproducing the c-server BCMP station with per-class demands
188% Dq: mu(n) = (min(|n|,c)/|n|) * sum_{r: n_r>0} n_r/Dq_r. For c = 1 this is the
189% familiar total completion rate of a multiclass single-server queue, and for a
190% single class it reduces to min(n,c)/Dq (M/M/c).
191Dq = Dq(:)';
192if ~isfinite(c) || c <= 0
193 c = 1;
194end
195h = @(n) ms_oi_rate_eval(n, Dq, c);
196end
197
198function rate = ms_oi_rate_eval(n, Dq, c)
199tot = sum(n);
200if tot == 0
201 rate = 0;
202 return
203end
204acc = 0;
205for r = 1:numel(n)
206 if n(r) > 0 && Dq(r) > 0
207 acc = acc + n(r) / Dq(r);
208 end
209end
210rate = (min(tot, c) / tot) * acc;
211end
212
213function rate = oi_rate(svcRateFun, n, R)
214% OI total service rate at count vector n via the 1-based canonical microstate.
215if sum(n) == 0
216 rate = 0;
217 return
218end
219micro = repelem(1:R, n);
220rate = svcRateFun(micro);
221end
Definition Station.m:245