LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_nc_conv.m
1function [Q,U,R,T,C,X,lG,runtime,iter,method] = solver_nc_conv(sn, options)
2% [Q,U,R,T,C,X,LG,RUNTIME,ITER,METHOD] = SOLVER_NC_CONV(SN, OPTIONS)
3%
4% Exact normalizing constant solver for closed networks with Limited
5% class-dependent (cdscaling) service rates, using the multichain
6% convolution algorithm of Sauer (1983), Section 5.2.
7%
8% This solver handles models where some stations have class-dependent scaling
9% (e.g., Flow-Equivalent Servers from aggregateFES).
10
11% Copyright (c) 2012-2026, Imperial College London
12% All rights reserved.
13
14Tstart = tic;
15method = 'conv';
16iter = 1;
17
18M = sn.nstations;
19K = sn.nclasses;
20NK = sn.njobs';
21nservers = sn.nservers;
22
23V = cellsum(sn.visits);
24ST = 1 ./ sn.rates;
25ST(isnan(ST)) = 0;
26
27% Demands: L(ist,k) = V(ist,k) * ST(ist,k)
28Ldemand = V .* ST;
29
30% Separate delay and queue stations
31isDelay = isinf(nservers);
32delayIdx = find(isDelay);
33queueIdx = find(~isDelay);
34nQueues = length(queueIdx);
35
36% Build pfqn_conv inputs
37% Z: total delay demand per class
38Z_conv = zeros(1, K);
39for ist = delayIdx(:)'
40 Z_conv = Z_conv + Ldemand(ist, :);
41end
42
43% L_conv: demands for queue stations only
44L_conv = Ldemand(queueIdx, :);
45
46% Class-dependence handles beta_{i,r}(n) for the queue stations. Each handle
47% takes the per-class population vector at its station and returns either a
48% scalar (chain-independent) or a length-R vector of per-class rates.
49cdscaling_conv = cell(nQueues, 1);
50if ~isempty(sn.cdscaling)
51 for qi = 1:nQueues
52 ist = queueIdx(qi);
53 if ist <= length(sn.cdscaling) && ~isempty(sn.cdscaling{ist})
54 cdscaling_conv{qi} = sn.cdscaling{ist};
55 end
56 end
57end
58
59%% Compute G(N)
60[G_N, lG] = pfqn_conv(L_conv, NK, Z_conv, cdscaling_conv);
61
62%% Compute G(N - e_k) for each class -> throughput
63XN = zeros(1, K);
64for k = 1:K
65 if NK(k) > 0
66 NK_minus = NK;
67 NK_minus(k) = NK_minus(k) - 1;
68 [G_Nk, ~] = pfqn_conv(L_conv, NK_minus, Z_conv, cdscaling_conv);
69 XN(k) = G_Nk / G_N;
70 end
71end
72
73%% Compute per-station throughput
74TN = V .* repmat(XN, M, 1);
75
76%% Compute queue lengths
77QN = zeros(M, K);
78
79% Delay stations: Q = L * X
80for ist = delayIdx(:)'
81 for k = 1:K
82 QN(ist, k) = Ldemand(ist, k) * XN(k);
83 end
84end
85
86% Queue stations: use marginal distribution
87% P_m(n|N) = X_m(n) * G_{-m}(N-n) / G(N)
88% Q_m_k = sum_{n: n_k>=1} n_k * P_m(n|N)
89%
90% G_{-m}(N-n) is computed by pfqn_conv on all stations except m
91stateSpaceSize = prod(NK + 1);
92
93for qi = 1:nQueues
94 ist = queueIdx(qi);
95
96 % Build X_m(n) for this station
97 Xm = zeros(stateSpaceSize, 1);
98 Xm(1) = 1; % X_m(0) = 1
99 isCdStation = ~isempty(cdscaling_conv{qi});
100
101 n = pprod_init(NK);
102 while n(1) >= 0
103 idx = hashpop(n, NK);
104 if sum(n) > 0
105 if isCdStation
106 % class-dependent: X_m(n) = (L/mu_km(n)) * X_m(n-e_k) via eq. (40)
107 % Pick any k with n_k > 0 (result is path-independent)
108 for r = 1:K
109 if n(r) > 0
110 % beta_{qi,r}(n): DIMENSIONLESS scaling of the demand,
111 % so the effective demand is L/beta. Handle returns a
112 % scalar (shared by all classes) or a length-R vector.
113 bval = cdscaling_conv{qi}(n);
114 if numel(bval) > 1
115 beta = bval(r);
116 else
117 beta = bval;
118 end
119 % X_m(n) = (|n|/n_r) * (L/beta) * X_m(n-e_r); at beta=1
120 % this is the load-independent multinomial recurrence.
121 tot = sum(n);
122 nr = n(r);
123 n(r) = n(r) - 1;
124 idx_prev = hashpop(n, NK);
125 n(r) = n(r) + 1;
126 if beta > 0
127 Xm(idx) = (tot / nr) * (L_conv(qi, r) / beta) * Xm(idx_prev);
128 end
129 break
130 end
131 end
132 else
133 % LI: X_m(n) = Σ_r L(m,r) * X_m(n-e_r) (multinomial form)
134 for r = 1:K
135 if n(r) > 0
136 n(r) = n(r) - 1;
137 idx_prev = hashpop(n, NK);
138 n(r) = n(r) + 1;
139 Xm(idx) = Xm(idx) + L_conv(qi, r) * Xm(idx_prev);
140 end
141 end
142 end
143 end
144 n = pprod_next(n, NK);
145 end
146
147 % Build complement: all stations except qi
148 L_comp = L_conv; L_comp(qi, :) = [];
149 cd_comp = cdscaling_conv; cd_comp(qi) = [];
150
151 % Compute Q_m_k using marginal
152 n = pprod_init(NK);
153 while n(1) >= 0
154 if any(n > 0)
155 idx = hashpop(n, NK);
156 nmi = NK - n;
157 if all(nmi >= 0)
158 % G_{-m}(N-n) with delay
159 [G_comp, ~] = pfqn_conv(L_comp, nmi, Z_conv, cd_comp);
160 prob = Xm(idx) * G_comp / G_N;
161 for k = 1:K
162 QN(ist, k) = QN(ist, k) + n(k) * prob;
163 end
164 end
165 end
166 n = pprod_next(n, NK);
167 end
168end
169
170%% Compute remaining metrics
171RN = QN ./ TN;
172RN(TN == 0) = 0;
173UN = TN .* ST;
174
175% Utilization at a class-dependent station is the fraction of the station's
176% PEAK service capacity in use, not T*ST: the scaling beta_{i,r}(n) multiplies
177% the nominal rate, so T*ST measures capacity used in units of the nominal
178% rate and reaches max_n beta(n), not 1, at saturation. Normalize by that peak,
179% exactly as SOLVER_NCLD does with max(lldscaling(ist,:)) for the load-dependent
180% case. On a beta emulating c servers this returns E[busy]/c, which agrees to
181% machine precision with the true c-server station; without it a 2-server beta
182% reports U = 2*(true utilization) and exceeds 1.
183%
184% No cap is needed: sum_r U(ist,r) = E[sum_r (n_r/|n|) beta_r(n)] / max_n beta,
185% and the inner sum is a convex combination of the beta_r(n), hence <= max_n
186% beta. So U <= 1 holds by construction for the exact convolution.
187% Utilization at class-dependent stations is normalized by the user-declared
188% peak rate scaling per class (sn.cdscalingpeak), giving the T*S/peak
189% convention of ordinary multiserver stations.
190for qi = 1:nQueues
191 ist = queueIdx(qi);
192 if isempty(cdscaling_conv{qi})
193 continue
194 end
195 for r = 1:K
196 bmax = sn.cdscalingpeak(ist,r);
197 if bmax > 0
198 UN(ist,r) = UN(ist,r) / bmax;
199 end
200 end
201end
202CN = NK ./ XN;
203CN(XN == 0) = 0;
204CN = CN - sum(Z_conv .* (repmat(1, M, 1) .* isDelay(:)), 1); % subtract delay
205
206% Output
207Q = QN;
208U = UN;
209R = RN;
210T = TN;
211C = CN;
212X = XN;
213runtime = toc(Tstart);
214end
215
216%% --- Local helper functions ---
217
218
219function idx = hashpop(n, N)
220idx = 1;
221R = length(N);
222for r = 1:R
223 idx = idx + prod(N(1:r-1) + 1) * n(r);
224end
225end
226
227function n = pprod_init(N)
228n = zeros(size(N));
229end
230
231function n = pprod_next(n, N)
232R = length(N);
233if all(n == N)
234 n = -1 * ones(1, R);
235 return
236end
237s = R;
238while s > 0 && n(s) == N(s)
239 n(s) = 0;
240 s = s - 1;
241end
242if s > 0
243 n(s) = n(s) + 1;
244end
245end
Definition Station.m:245