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% Joint Class Dependence (LJCD) service rates, using the multichain
6% convolution algorithm of Sauer (1983), Section 5.2.
7%
8% This solver handles models where some stations have LJCD 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% mu_conv: load-dependent rates for queue stations (ones for LI, lldscaling otherwise)
47Nt = sum(NK);
48mu_conv = ones(nQueues, Nt);
49if ~isempty(sn.lldscaling)
50 for qi = 1:nQueues
51 ist = queueIdx(qi);
52 nEntries = min(Nt, size(sn.lldscaling, 2));
53 mu_conv(qi, 1:nEntries) = sn.lldscaling(ist, 1:nEntries);
54 end
55end
56
57% LJCD scaling tables for queue stations
58ljcdscaling_conv = cell(nQueues, 1);
59ljcdcutoffs_conv = cell(nQueues, 1);
60hasLjcd = ~isempty(sn.ljcdscaling);
61if hasLjcd
62 for qi = 1:nQueues
63 ist = queueIdx(qi);
64 if ist <= length(sn.ljcdscaling) && ~isempty(sn.ljcdscaling{ist})
65 ljcdscaling_conv{qi} = sn.ljcdscaling{ist};
66 ljcdcutoffs_conv{qi} = sn.ljcdcutoffs{ist};
67 end
68 end
69end
70
71%% Compute G(N)
72[G_N, lG] = pfqn_conv(L_conv, NK, Z_conv, mu_conv, ljcdscaling_conv, ljcdcutoffs_conv);
73
74%% Compute G(N - e_k) for each class -> throughput
75XN = zeros(1, K);
76for k = 1:K
77 if NK(k) > 0
78 NK_minus = NK;
79 NK_minus(k) = NK_minus(k) - 1;
80 [G_Nk, ~] = pfqn_conv(L_conv, NK_minus, Z_conv, mu_conv, ljcdscaling_conv, ljcdcutoffs_conv);
81 XN(k) = G_Nk / G_N;
82 end
83end
84
85%% Compute per-station throughput
86TN = V .* repmat(XN, M, 1);
87
88%% Compute queue lengths
89QN = zeros(M, K);
90
91% Delay stations: Q = L * X
92for ist = delayIdx(:)'
93 for k = 1:K
94 QN(ist, k) = Ldemand(ist, k) * XN(k);
95 end
96end
97
98% Queue stations: use marginal distribution
99% P_m(n|N) = X_m(n) * G_{-m}(N-n) / G(N)
100% Q_m_k = sum_{n: n_k>=1} n_k * P_m(n|N)
101%
102% G_{-m}(N-n) is computed by pfqn_conv on all stations except m
103stateSpaceSize = prod(NK + 1);
104
105for qi = 1:nQueues
106 ist = queueIdx(qi);
107
108 % Build X_m(n) for this station
109 Xm = zeros(stateSpaceSize, 1);
110 Xm(1) = 1; % X_m(0) = 1
111 isLjcdStation = ~isempty(ljcdscaling_conv{qi});
112
113 n = pprod_init(NK);
114 while n(1) >= 0
115 idx = hashpop(n, NK);
116 if sum(n) > 0
117 if isLjcdStation
118 % LJCD: X_m(n) = (L/mu_km(n)) * X_m(n-e_k) via eq. (40)
119 % Pick any k with n_k > 0 (result is path-independent)
120 for r = 1:K
121 if n(r) > 0
122 cutoffs = ljcdcutoffs_conv{qi};
123 nClamped = min(n, cutoffs);
124 lidx = ljd_linearize(nClamped, cutoffs);
125 mu_km = ljcdscaling_conv{qi}{r}(lidx);
126 n(r) = n(r) - 1;
127 idx_prev = hashpop(n, NK);
128 n(r) = n(r) + 1;
129 if mu_km > 0
130 Xm(idx) = (L_conv(qi, r) / mu_km) * Xm(idx_prev);
131 end
132 break
133 end
134 end
135 else
136 % LI: X_m(n) = Σ_r L(m,r) * X_m(n-e_r) (multinomial form)
137 for r = 1:K
138 if n(r) > 0
139 n(r) = n(r) - 1;
140 idx_prev = hashpop(n, NK);
141 n(r) = n(r) + 1;
142 Xm(idx) = Xm(idx) + L_conv(qi, r) * Xm(idx_prev);
143 end
144 end
145 end
146 end
147 n = pprod_next(n, NK);
148 end
149
150 % Build complement: all stations except qi
151 L_comp = L_conv; L_comp(qi, :) = [];
152 mu_comp = mu_conv; mu_comp(qi, :) = [];
153 ljcd_comp = ljcdscaling_conv; ljcd_comp(qi) = [];
154 ljcdc_comp = ljcdcutoffs_conv; ljcdc_comp(qi) = [];
155
156 % Compute Q_m_k using marginal
157 n = pprod_init(NK);
158 while n(1) >= 0
159 if any(n > 0)
160 idx = hashpop(n, NK);
161 nmi = NK - n;
162 if all(nmi >= 0)
163 % G_{-m}(N-n) with delay
164 [G_comp, ~] = pfqn_conv(L_comp, nmi, Z_conv, mu_comp, ljcd_comp, ljcdc_comp);
165 prob = Xm(idx) * G_comp / G_N;
166 for k = 1:K
167 QN(ist, k) = QN(ist, k) + n(k) * prob;
168 end
169 end
170 end
171 n = pprod_next(n, NK);
172 end
173end
174
175%% Compute remaining metrics
176RN = QN ./ TN;
177RN(TN == 0) = 0;
178UN = TN .* ST;
179CN = NK ./ XN;
180CN(XN == 0) = 0;
181CN = CN - sum(Z_conv .* (repmat(1, M, 1) .* isDelay(:)), 1); % subtract delay
182
183% Output
184Q = QN;
185U = UN;
186R = RN;
187T = TN;
188C = CN;
189X = XN;
190runtime = toc(Tstart);
191end
192
193%% --- Local helper functions ---
194
195function idx = hashpop(n, N)
196idx = 1;
197R = length(N);
198for r = 1:R
199 idx = idx + prod(N(1:r-1) + 1) * n(r);
200end
201end
202
203function n = pprod_init(N)
204n = zeros(size(N));
205end
206
207function n = pprod_next(n, N)
208R = length(N);
209if all(n == N)
210 n = -1 * ones(1, R);
211 return
212end
213s = R;
214while s > 0 && n(s) == N(s)
215 n(s) = 0;
216 s = s - 1;
217end
218if s > 0
219 n(s) = n(s) + 1;
220end
221end