5#ifndef LINE_API_PFQN_QSA_H
6#define LINE_API_PFQN_QSA_H
54Matrix<T> qsa_shift(
const Matrix<T>& q,
const Matrix<T>& pops,
const std::vector<long>& sIdx,
55 const Matrix<long>& pIdx,
long s,
long t,
int levels) {
56 const std::size_t M = q.rows(), R = pops.cols();
57 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
58 Matrix<T> Y(M, R, zero);
60 for (std::size_t r = 0; r < R; ++r)
62 for (std::size_t i = 0; i < M; ++i)
63 Y(i, r) = one + q(i,
static_cast<std::size_t
>(sIdx[r])) - q(i, 0);
65 if (levels < 3)
return qsa_shift(q, pops, sIdx, pIdx, -1, -1, levels);
66 const std::size_t ps =
static_cast<std::size_t
>(sIdx[
static_cast<std::size_t
>(s)]);
67 for (std::size_t r = 0; r < R; ++r) {
68 const long pr = pIdx(
static_cast<std::size_t
>(s), r);
69 if (pr >= 0 && pops(ps, r) >= one)
70 for (std::size_t i = 0; i < M; ++i)
71 Y(i, r) = one + q(i,
static_cast<std::size_t
>(pr)) - q(i, ps);
74 const Matrix<T> Ys = qsa_shift(q, pops, sIdx, pIdx, s, -1, levels);
75 const Matrix<T> Yt = qsa_shift(q, pops, sIdx, pIdx, t, -1, levels);
76 const Matrix<T> Y0 = qsa_shift(q, pops, sIdx, pIdx, -1, -1, levels);
77 for (std::size_t i = 0; i < M; ++i)
78 for (std::size_t r = 0; r < R; ++r) Y(i, r) = Ys(i, r) + Yt(i, r) - Y0(i, r);
84inline void qsa_which(std::size_t p,
const std::vector<long>& sIdx,
const Matrix<long>& pIdx,
89 for (std::size_t k = 0; k < sIdx.size(); ++k)
90 if (sIdx[k] ==
static_cast<long>(p)) {
91 s =
static_cast<long>(k);
94 for (std::size_t a = 0; a < pIdx.rows(); ++a)
95 for (std::size_t b = 0; b < pIdx.cols(); ++b)
96 if (pIdx(a, b) ==
static_cast<long>(p)) {
97 s =
static_cast<long>(a);
98 t =
static_cast<long>(b);
109std::vector<T> qsa_resid(
const std::vector<T>& x,
const Matrix<T>& L,
const std::vector<T>& Z,
110 const Matrix<T>& pops,
const std::vector<long>& sIdx,
111 const Matrix<long>& pIdx,
const std::vector<std::size_t>& qc,
112 const std::vector<T>& Ldc,
int levels,
bool& adm) {
113 const std::size_t M = L.rows(), R = L.cols(), nP = pops.rows(), mq = qc.size();
114 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
116 Matrix<T> q(M, nP, zero);
117 for (std::size_t a = 0; a < mq; ++a)
118 for (std::size_t p = 0; p < nP; ++p) {
119 q(qc[a], p) = x[a * nP + p];
120 if (x[a * nP + p] < zero) adm =
false;
122 std::vector<T> F(mq * nP, zero);
123 for (std::size_t p = 0; p < nP; ++p) {
125 qsa_which(p, sIdx, pIdx, s, t);
126 const Matrix<T> Y = qsa_shift(q, pops, sIdx, pIdx, s, t, levels);
127 std::vector<T> acc(mq, zero);
128 for (std::size_t r = 0; r < R; ++r) {
129 if (pops(p, r) < one)
continue;
130 T c = Z.empty() ? Ldc[r] : T(Z[r] + Ldc[r]);
131 for (std::size_t a = 0; a < mq; ++a) c += L(qc[a], r) * (q(qc[a], p) + Y(qc[a], r));
132 if (!(c > zero) || !std::isfinite(num_traits<T>::to_double(c))) {
134 c = num_traits<T>::from_double(1e-300);
136 const T xr = pops(p, r) / c;
137 for (std::size_t a = 0; a < mq; ++a)
138 acc[a] += xr * L(qc[a], r) * (q(qc[a], p) + Y(qc[a], r));
140 for (std::size_t a = 0; a < mq; ++a) F[a * nP + p] = q(qc[a], p) - acc[a];
150std::vector<T> qsa_aggbs(
const Matrix<T>& L,
const std::vector<T>& n,
const std::vector<T>& Z,
151 const std::vector<bool>& isQC) {
152 const std::size_t M = L.rows(), R = L.cols();
153 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
154 std::vector<T> q(M, zero);
155 std::vector<T> nn(R, zero);
157 for (std::size_t r = 0; r < R; ++r) {
158 nn[r] = (n[r] > zero) ? n[r] : zero;
159 if (nn[r] > zero) empty =
false;
163 std::vector<T> Zeff(R, zero);
164 for (std::size_t r = 0; r < R; ++r) {
165 Zeff[r] = Z.empty() ? zero : Z[r];
166 for (std::size_t i = 0; i < M; ++i)
167 if (!isQC[i]) Zeff[r] += L(i, r);
170 for (std::size_t i = 0; i < M; ++i)
173 std::vector<T> X(R, zero);
175 Matrix<T> Lq(mq, R, zero);
177 for (std::size_t i = 0; i < M; ++i)
179 for (std::size_t r = 0; r < R; ++r) Lq(a, r) = L(i, r);
182 const AmvaResult<T> bs =
pfqn_bs(Lq, nn, Zeff, std::vector<AmvaSched>());
184 for (std::size_t i = 0; i < M; ++i)
187 for (std::size_t r = 0; r < R; ++r) s += bs.QN(a, r);
193 for (std::size_t r = 0; r < R; ++r)
194 if (nn[r] >= one && Zeff[r] > zero) X[r] = nn[r] / Zeff[r];
196 for (std::size_t i = 0; i < M; ++i)
199 for (std::size_t r = 0; r < R; ++r) s += X[r] * L(i, r);
222 const std::vector<AmvaSched>& type,
double tol = 1e-10,
223 std::size_t maxiter = 100,
int levels = 3) {
225 "pfqn_qsa requires transcendental arithmetic: the Newton iteration stops on a "
226 "residual tolerance, so its answer is a fixed point only to within tol");
227 const std::size_t M = L.
rows(), R = L.
cols();
228 if (N.size() != R)
throw InputError(
"pfqn_qsa: L and N disagree on the class count");
229 if (!Z.empty() && Z.size() != R)
throw InputError(
"pfqn_qsa: Z has the wrong length");
230 if (!type.empty() && type.size() != M)
throw InputError(
"pfqn_qsa: type has the wrong length");
231 if (levels != 2 && levels != 3)
throw InputError(
"pfqn_qsa: levels must be 2 or 3");
234 std::vector<bool> isQC(M,
true);
235 for (std::size_t i = 0; i < M && !type.empty(); ++i) isQC[i] = type[i] !=
AmvaSched::INF;
238 out.
XN.assign(R, zero);
243 bool emptyDemands =
true;
244 for (std::size_t i = 0; i < M && emptyDemands; ++i)
245 for (std::size_t r = 0; r < R; ++r)
246 if (L(i, r) != zero) {
247 emptyDemands =
false;
250 bool emptyPop =
true;
251 for (std::size_t r = 0; r < R; ++r)
252 if (N[r] > zero) emptyPop =
false;
253 if (M == 0 || emptyDemands || emptyPop) {
254 for (std::size_t r = 0; r < R; ++r) {
255 if (N[r] > zero && !Z.empty() && Z[r] > zero) out.
XN[r] = N[r] / Z[r];
256 for (std::size_t i = 0; i < M; ++i) out.
UN(i, r) = out.
XN[r] * L(i, r);
262 std::vector<std::vector<T>> popList;
263 popList.push_back(N);
264 std::vector<long> sIdx(R, -1);
266 for (std::size_t s = 0; s < R; ++s) {
267 std::vector<T> n = N;
270 for (std::size_t r = 0; r < R; ++r)
271 if (n[r] < zero) ok =
false;
273 popList.push_back(n);
274 sIdx[s] =
static_cast<long>(popList.size()) - 1;
278 for (std::size_t s = 0; s < R; ++s)
279 for (std::size_t t = s; t < R; ++t) {
280 std::vector<T> n = N;
284 for (std::size_t r = 0; r < R; ++r)
285 if (n[r] < zero) ok =
false;
287 popList.push_back(n);
288 pIdx(s, t) =
static_cast<long>(popList.size()) - 1;
289 pIdx(t, s) = pIdx(s, t);
293 const std::size_t nP = popList.size();
295 for (std::size_t p = 0; p < nP; ++p)
296 for (std::size_t r = 0; r < R; ++r) pops(p, r) = popList[p][r];
298 std::vector<std::size_t> qc;
299 for (std::size_t i = 0; i < M; ++i)
300 if (isQC[i]) qc.push_back(i);
301 const std::size_t mq = qc.size();
302 std::vector<T> Ldc(R, zero);
303 for (std::size_t r = 0; r < R; ++r)
304 for (std::size_t i = 0; i < M; ++i)
305 if (!isQC[i]) Ldc[r] += L(i, r);
309 for (std::size_t p = 0; p < nP; ++p) {
310 const std::vector<T> qp = detail::qsa_aggbs(L, popList[p], Z, isQC);
311 for (std::size_t i = 0; i < M; ++i) q(i, p) = qp[i];
314 const std::size_t nUnk = mq * nP;
315 std::vector<T> x(nUnk, zero);
316 for (std::size_t a = 0; a < mq; ++a)
317 for (std::size_t p = 0; p < nP; ++p) x[a * nP + p] = q(qc[a], p);
320 std::vector<T> F = detail::qsa_resid(x, L, Z, pops, sIdx, pIdx, qc, Ldc, levels, adm);
321 auto nrm = [](
const std::vector<T>& v) {
323 for (std::size_t i = 0; i < v.size(); ++i) {
329 double fnrm = nrm(F);
330 for (std::size_t it = 1; it <= maxiter; ++it) {
331 if (fnrm < tol)
break;
334 for (std::size_t col = 0; col < nUnk; ++col) {
336 const double hd = 1e-7 * std::max(1.0, std::fabs(xc));
338 std::vector<T> xp = x;
341 const std::vector<T> Fp =
342 detail::qsa_resid(xp, L, Z, pops, sIdx, pIdx, qc, Ldc, levels, dummy);
343 for (std::size_t row = 0; row < nUnk; ++row) J(row, col) = (Fp[row] - F[row]) / h;
345 std::vector<T> rhs(nUnk, zero);
346 for (std::size_t row = 0; row < nUnk; ++row) rhs[row] = zero - F[row];
349 step =
solve(J, rhs);
350 }
catch (
const std::exception&) {
353 bool accepted =
false;
355 for (
int ls = 0; ls < 40; ++ls) {
356 std::vector<T> xn(nUnk, zero);
359 for (std::size_t row = 0; row < nUnk; ++row) {
360 xn[row] = x[row] + lam * step[row];
365 const std::vector<T> Fn =
366 detail::qsa_resid(xn, L, Z, pops, sIdx, pIdx, qc, Ldc, levels, admn);
367 const double nn = nrm(Fn);
368 if (admn && nn < fnrm) {
379 if (!accepted)
break;
384 for (std::size_t a = 0; a < mq; ++a)
385 for (std::size_t p = 0; p < nP; ++p) q(qc[a], p) = x[a * nP + p];
386 const Matrix<T> Y0 = detail::qsa_shift(q, pops, sIdx, pIdx, -1, -1, levels);
387 for (std::size_t r = 0; r < R; ++r) {
388 if (N[r] < one)
continue;
390 for (std::size_t i = 0; i < M; ++i) {
391 out.
RN(i, r) = isQC[i] ? T(L(i, r) * (q(i, 0) + Y0(i, r))) : L(i, r);
392 sumW += out.
RN(i, r);
394 const T denom = (Z.empty() ? zero : Z[r]) + sumW;
395 out.
XN[r] = N[r] / denom;
396 for (std::size_t i = 0; i < M; ++i) {
397 out.
QN(i, r) = out.
XN[r] * out.
RN(i, r);
398 out.
UN(i, r) = out.
XN[r] * L(i, r);
406 return pfqn_qsa(L, N, Z, std::vector<AmvaSched>());
411 return pfqn_qsa(L, N, std::vector<T>(), std::vector<AmvaSched>());
The exception types the port throws.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
AmvaResult< T > pfqn_qsa(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const std::vector< AmvaSched > &type, double tol=1e-10, std::size_t maxiter=100, int levels=3)
Queue-Shift Approximation (QSA) for closed product-form networks.
AmvaResult< T > pfqn_bs(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const std::vector< AmvaSched > &type, double tol=1e-6, std::size_t maxiter=1000, const Matrix< T > &QN0=Matrix< T >())
Bard-Schweitzer approximate MVA.
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Number-type abstraction for the templated API port.
Bard-Schweitzer approximate MVA.
Matrix< T > RN
(M x R) residence time
std::vector< T > XN
(R) throughput
Matrix< T > UN
(M x R) utilization
Matrix< T > QN
(M x R) queue length