5#ifndef LINE_API_PFQN_SENS_H
6#define LINE_API_PFQN_SENS_H
73 std::vector<Matrix<T>>
dQ;
74 std::vector<Matrix<T>>
dU;
75 std::vector<Matrix<T>>
dR;
87SensResult<T> sens_pack(std::size_t M, std::size_t R, std::size_t P) {
113 const std::vector<T>& Z,
const std::vector<int>& mi) {
114 const std::size_t M = L.
rows();
115 const std::size_t R = N.size();
117 throw InputError(
"pfqn_sens: demand matrix and population vector disagree on the class count");
118 if (!Z.empty() && Z.size() != R)
119 throw InputError(
"pfqn_sens: think-time vector has the wrong length");
120 if (!mi.empty() && mi.size() != M)
121 throw InputError(
"pfqn_sens: multiplicity vector has the wrong length");
124 const std::size_t P = M * R + R;
128 std::vector<std::vector<std::size_t>> pL(M, std::vector<std::size_t>(R, 0));
129 std::vector<std::size_t> pZ(R, 0);
132 for (std::size_t i = 0; i < M; ++i)
133 for (std::size_t r = 0; r < R; ++r) {
135 res.
params[p].station =
static_cast<int>(i);
140 for (std::size_t r = 0; r < R; ++r) {
142 res.
params[p].station = -1;
149 bool anyPositive =
false;
151 if (v < 0)
throw InputError(
"pfqn_sens: negative population");
152 if (v > 0) anyPositive =
true;
154 if (!anyPositive || M == 0 || R == 0)
return res;
156 const auto Zr = [&](std::size_t r) -> T {
return Z.empty() ? zero : Z[r]; };
157 const auto miT = [&](std::size_t i) -> T {
165 std::vector<Matrix<T>> Qtotd(totpop,
Matrix<T>(M, P, zero));
167 std::vector<T> CNtotd(P, zero), Xd(P, zero), Qd(P, zero);
170 for (std::size_t k = 1; k < totpop; ++k) {
172 for (std::size_t s = 0; s < R; ++s) {
173 const std::size_t row = n[s] > 0 ? k - radix[s] : 0;
175 for (std::size_t p = 0; p < P; ++p) CNtotd[p] = zero;
176 for (std::size_t i = 0; i < M; ++i) {
177 const T base = miT(i) + Qtot(row, i);
178 res.
CN(i, s) = L(i, s) * base;
179 for (std::size_t p = 0; p < P; ++p) Cd(i, p) = L(i, s) * Qtotd[row](i, p);
180 Cd(i, pL[i][s]) += base;
181 CNtot += res.
CN(i, s);
182 for (std::size_t p = 0; p < P; ++p) CNtotd[p] += Cd(i, p);
184 const T den = Zr(s) + CNtot;
188 for (std::size_t p = 0; p < P; ++p) Xd[p] = zero;
190 const T den2 = den * den;
191 res.
XN[s] = nsT / den;
192 for (std::size_t p = 0; p < P; ++p) Xd[p] = -nsT * CNtotd[p] / den2;
193 Xd[pZ[s]] -= nsT / den2;
195 for (std::size_t p = 0; p < P; ++p) res.
dX(s, p) = Xd[p];
196 for (std::size_t i = 0; i < M; ++i) {
197 res.
QN(i, s) = res.
XN[s] * res.
CN(i, s);
198 for (std::size_t p = 0; p < P; ++p) {
199 Qd[p] = Xd[p] * res.
CN(i, s) + res.
XN[s] * Cd(i, p);
200 res.
dQ[p](i, s) = Qd[p];
201 res.
dR[p](i, s) = Cd(i, p);
202 Qtotd[k](i, p) += Qd[p];
204 Qtot(k, i) += res.
QN(i, s);
209 for (std::size_t i = 0; i < M; ++i) {
210 for (std::size_t r = 0; r < R; ++r) {
211 res.
UN(i, r) = res.
XN[r] * L(i, r);
212 for (std::size_t p = 0; p < P; ++p) res.
dU[p](i, r) = res.
dX(r, p) * L(i, r);
213 res.
dU[pL[i][r]](i, r) += res.
XN[r];
226 const std::vector<T>& Z) {
227 const std::size_t R = N.size();
228 if (L.
rows() != 1)
throw InputError(
"pfqn_sens_comom: the kernel takes a single station");
229 if (L.
cols() != R || Z.size() != R)
230 throw InputError(
"pfqn_sens_comom: L, N and Z disagree on the class count");
233 const std::size_t P = 2 * R;
237 for (std::size_t r = 0; r < R; ++r) {
239 res.
params[r].station = 0;
241 res.
params[R + r].type =
'Z';
242 res.
params[R + r].station = -1;
243 res.
params[R + r].cls = r;
246 bool anyPositive =
false;
248 if (v < 0)
throw InputError(
"pfqn_sens_comom: negative population");
249 if (v > 0) anyPositive =
true;
251 if (!anyPositive || R == 0)
return res;
254 std::map<std::pair<int, std::vector<int>>, T>
cache;
256 const auto Gm = [&](
int m,
const std::vector<int>& n) -> T {
258 std::vector<std::size_t> keep;
259 for (std::size_t r = 0; r < R; ++r)
264 if (nz.empty())
return one;
265 const std::pair<int, std::vector<int>> key(m, n);
266 auto it =
cache.find(key);
267 if (it !=
cache.end())
return it->second;
270 for (std::size_t j = 0; j < keep.size(); ++j) {
271 Ls(0, j) = L(0, keep[j]);
272 Zs(0, j) = Z[keep[j]];
275 cache.emplace(key, g);
278 const auto minus = [&](
const std::vector<int>& n, std::size_t s) {
279 std::vector<int> m = n;
284 const auto qmean = [&](
const std::vector<int>& n, std::size_t s) -> T {
285 if (n[s] < 1)
return zero;
286 return L(0, s) *
Gm(2, minus(n, s)) /
Gm(1, n);
289 const auto xput = [&](
int m,
const std::vector<int>& n, std::size_t s) -> T {
290 if (n[s] < 1)
return zero;
291 return Gm(m, minus(n, s)) /
Gm(m, n);
294 const auto qplus = [&](
const std::vector<int>& n, std::size_t s) -> T {
295 if (n[s] < 1)
return zero;
296 return L(0, s) *
Gm(3, minus(n, s)) /
Gm(2, n);
299 for (std::size_t r = 0; r < R; ++r)
300 if (N[r] >= 1) res.
XN[r] = xput(1, N, r);
301 for (std::size_t s = 0; s < R; ++s) res.
QN(0, s) = qmean(N, s);
302 for (std::size_t r = 0; r < R; ++r) {
303 res.
UN(0, r) = res.
XN[r] * L(0, r);
304 if (res.
XN[r] != zero) res.
CN(0, r) = res.
QN(0, r) / res.
XN[r];
307 for (std::size_t r = 0; r < R; ++r) {
308 if (N[r] < 1)
continue;
309 const std::vector<int> Nr = minus(N, r);
310 const T Xr = res.
XN[r];
311 const T Qr = res.
QN(0, r);
312 for (std::size_t s = 0; s < R; ++s) {
316 const T dQ_L = Vrs / L(0, s);
317 const T dX_L = Xr * (qmean(Nr, s) - res.
QN(0, s)) / L(0, s);
318 const T dU_L = dX_L * L(0, r) + Xr * dlt;
319 res.
dQ[s](0, r) = dQ_L;
321 res.
dU[s](0, r) = dU_L;
322 if (Xr != zero) res.
dR[s](0, r) = (dQ_L * Xr - Qr * dX_L) / (Xr * Xr);
325 const std::size_t p = R + s;
326 const T dX_Z = Xr * (xput(1, Nr, s) - res.
XN[s]);
327 const T dQ_Z = Qr * (xput(2, Nr, s) - res.
XN[s]);
328 res.
dQ[p](0, r) = dQ_Z;
330 res.
dU[p](0, r) = dX_Z * L(0, r);
331 if (Xr != zero) res.
dR[p](0, r) = (dQ_Z * Xr - Qr * dX_Z) / (Xr * Xr);
349 const std::vector<int>& mi) {
350 const std::size_t M = L.
rows();
351 const std::size_t R = N.size();
353 std::vector<T> Zv = Z;
354 if (Zv.empty()) Zv.assign(R, zero);
359 bool anyPopulated =
false, useComom = (M == 1);
361 for (std::size_t i = 0; i < M; ++i)
362 if (mi[i] != 1) useComom =
false;
363 for (std::size_t r = 0; r < R; ++r) {
364 if (N[r] <= 0)
continue;
366 if (Zv[r] <= fineTol) useComom =
false;
367 if (M == 1 && L(0, r) <= fineTol) useComom =
false;
369 useComom = useComom && anyPopulated;
375 std::vector<std::vector<std::size_t>> pL(M, std::vector<std::size_t>(R, 0));
376 std::vector<std::vector<bool>> haveL(M, std::vector<bool>(R,
false));
377 for (std::size_t p = 0; p < res.
params.size(); ++p)
378 if (res.
params[p].type ==
'L') {
379 pL[
static_cast<std::size_t
>(res.
params[p].station)][res.
params[p].cls] = p;
380 haveL[
static_cast<std::size_t
>(res.
params[p].station)][res.
params[p].cls] =
true;
382 for (std::size_t i = 0; i < M; ++i)
383 for (std::size_t r = 0; r < R; ++r)
384 for (std::size_t j = 0; j < M; ++j)
385 for (std::size_t s = 0; s < R; ++s) {
388 v = mom.
QCov[i](r, s);
389 }
else if (haveL[j][s]) {
390 v = L(j, s) * res.
dQ[pL[j][s]](i, r);
392 res.
QCov(i * R + r, j * R + s) = v;
403 return pfqn_sens(L, N, Z, std::vector<int>());
The exception types the port throws.
Dense matrix and non-owning view.
std::vector< int > sens_lattice_decode(std::size_t k, const std::vector< int > &N, const std::vector< std::size_t > &radix)
Decode a lattice index back into a population vector.
std::vector< std::size_t > sens_lattice_radix(const std::vector< int > &N)
Radix weights of the MVA population lattice, class R-1 varying fastest.
SensMvaResult< T > pfqn_sens_mva(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const std::vector< int > &mi)
Exact per-station queue-length variances and covariances of a closed product-form network,...
ComomResult< T > pfqn_comomrm(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, int m)
CoMoM (class-oriented method of moments) for the finite repairman model: one queueing station of mult...
SensResult< T > pfqn_sens_comom(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z)
CoMoM-backed kernel for the repairman model (M = 1).
@ Gm
the reference's alias of 'cub'
SensResult< T > pfqn_sens_dmva(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const std::vector< int > &mi)
Forward-mode differentiation of the exact MVA recursion.
SensResult< T > pfqn_sens(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const std::vector< int > &mi)
Exact analytic derivatives of the mean performance measures {X,Q,U,R} of a closed product-form (BCMP)...
std::size_t population_count(const std::vector< int > &N)
Number of population vectors n with 0 <= n <= N.
Number-type abstraction for the templated API port.
CoMoM (class-oriented method of moments) for the finite repairman model: one queueing station of mult...
Exact per-station queue-length variances and covariances of a closed product-form network,...
Population-vector enumeration and combinatorics.
Matrix< T > QVar
(M x R) Var[n(i,r)]
std::vector< Matrix< T > > QCov
(M) matrices R x R, QCov[i](r,s) = Cov[n(i,r),n(i,s)]
T QCovAsym
raw asymmetry of QCov before symmetrization
std::vector< T > QTotVar
(M) Var[sum_r n(i,r)]
One differentiation parameter.
int station
0-based station index, -1 for a 'Z' parameter
std::size_t cls
0-based class index
char type
'L' for a demand, 'Z' for a think time
std::vector< Matrix< T > > dR
(P) matrices M x R
std::vector< Matrix< T > > dU
(P) matrices M x R
Matrix< T > QN
(M x R) mean queue length
Matrix< T > UN
(M x R) utilization
Matrix< T > QCov
(M*R x M*R) Cov[n(i,r),n(j,s)] at row i*R+r, column j*R+s.
std::vector< T > QTotVar
(M)
std::vector< SensParam > params
(P) the differentiation parameters
Matrix< T > dX
(R x P) dX(r)/dparam(p)
T QCovAsym
residual of the moment recursion
std::vector< T > XN
(R) throughput
std::vector< Matrix< T > > dQ
(P) matrices M x R, dQ[p](i,r)
Matrix< T > CN
(M x R) residence time (MATLAB field .R)