5#ifndef LINE_API_PFQN_MANJUNATH_H
6#define LINE_API_PFQN_MANJUNATH_H
135 std::vector<double>
Q;
136 std::vector<double>
X;
137 std::vector<double>
U;
146inline int manjunath_round(
double x) {
return static_cast<int>(std::lround(x)); }
151PfqnManjunathResult<T> manjunath_empty(
const T& G,
double lG, std::size_t peak,
152 const PfqnManjunathOptions& options, std::size_t R) {
153 PfqnManjunathResult<T> out;
156 out.peak_states = peak;
158 out.has_stats =
true;
159 out.Q.assign(R, 0.0);
160 out.X.assign(R, 0.0);
161 out.U.assign(R, 0.0);
162 out.think.assign(R, 0.0);
163 out.blocked.assign(R, 0.0);
164 out.delay.assign(R, 0.0);
171void manjunath_stats(PfqnManjunathResult<T>& out,
const Matrix<T>& L,
const std::vector<int>& N,
172 const Matrix<T>& Z,
const std::vector<std::vector<long>>& A,
173 const std::vector<long>& b,
const std::string& sense,
double lG,
174 std::size_t M, std::size_t Mz, std::size_t S, std::size_t R,
175 const PfqnManjunathOptions& options);
185T pfqn_manjunath_series(
const Matrix<T>& L,
const Matrix<T>& Z,
const std::vector<int>& N,
186 const std::vector<std::vector<long>>& A,
const std::vector<long>& b,
187 const std::string& sense,
const PfqnManjunathOptions& options,
189 const std::size_t M = L.empty() ? 0 : L.rows();
190 const std::size_t Mz = Z.empty() ? 0 : Z.rows();
191 const std::size_t S = M + Mz;
192 const std::size_t R = N.size();
193 const std::size_t J = b.size();
194 const std::size_t D = R + J;
195 const T zero = num_traits<T>::from_int(0);
200 std::vector<std::size_t> first(J, 0), last(J, 0);
201 for (std::size_t j = 0; j < J; ++j) {
203 for (std::size_t i = 0; i < S; ++i) {
205 for (std::size_t r = 0; r < R; ++r)
206 if (A[j][i + S * r] != 0) touch =
true;
207 if (!touch)
continue;
215 throw NumericError(
"pfqn_manjunath: a constraint row with no nonzero entry reached "
216 "the series; the rule was not reduced");
219 std::vector<std::size_t> dims(D, 1);
220 for (std::size_t r = 0; r < R; ++r) dims[r] = static_cast<std::size_t>(N[r]) + 1;
222 for (std::size_t d = 0; d < D; ++d) P *= dims[d];
223 std::vector<T> ser(P, zero);
224 ser[0] = num_traits<T>::from_int(1);
225 if (P > peak) peak = P;
227 for (std::size_t i = 0; i < S; ++i) {
228 for (std::size_t j = 0; j < J; ++j) {
229 if (first[j] != i)
continue;
230 const std::size_t newdim =
static_cast<std::size_t
>(b[j]) + 1;
231 std::size_t pre = 1, post = 1;
232 for (std::size_t d = 0; d < R + j; ++d) pre *= dims[d];
233 for (std::size_t d = R + j + 1; d < D; ++d) post *= dims[d];
234 if (newdim != 0 && pre * post > options.max_live_states / newdim)
236 "pfqn_manjunath: the exact transform would hold more than " +
237 std::to_string(options.max_live_states) +
238 " series coefficients at once. The floor is the product of (N_r+1) over the "
239 "classes, on top of which each simultaneously live constraint row multiplies "
240 "by (b_j+1); raise PfqnManjunathOptions::max_live_states deliberately");
243 std::vector<T> grown(pre * newdim * post, zero);
244 for (std::size_t q = 0; q < post; ++q)
245 for (std::size_t p = 0; p < pre; ++p)
246 grown[p + q * pre * newdim] = ser[p + q * pre];
248 dims[R + j] = newdim;
249 if (ser.size() > peak) peak = ser.size();
252 std::vector<std::size_t> stride(D, 1);
253 for (std::size_t d = 1; d < D; ++d) stride[d] = stride[d - 1] * dims[d - 1];
259 std::vector<std::vector<long>> delta(R, std::vector<long>(D, 0));
260 std::vector<std::size_t> off(R, 0);
261 std::vector<bool> fits(R,
true);
262 for (std::size_t r = 0; r < R; ++r) {
264 for (std::size_t j = 0; j < J; ++j) delta[r][R + j] = A[j][i + S * r];
266 for (std::size_t d = 0; d < D; ++d) {
267 if (delta[r][d] >
static_cast<long>(dims[d]) - 1)
269 o +=
static_cast<std::size_t
>(delta[r][d]) * stride[d];
280 std::vector<std::size_t> sub(D, 0);
281 for (std::size_t p = 0; p < ser.size(); ++p) {
282 for (std::size_t r = 0; r < R; ++r) {
283 if (!fits[r] || L(i, r) == zero)
continue;
285 for (std::size_t d = 0; d < D && ok; ++d)
286 if (
static_cast<long>(sub[d]) < delta[r][d]) ok =
false;
287 if (ok) ser[p] += T(L(i, r) * ser[p - off[r]]);
289 for (std::size_t d = 0; d < D; ++d) {
290 if (++sub[d] < dims[d])
break;
300 for (std::size_t r = 0; r < R; ++r) {
301 if (!fits[r] || Z(i - M, r) == zero || N[r] == 0)
continue;
302 std::vector<T> nxt(ser);
303 std::vector<T> term(ser);
304 for (
int n = 1; n <= N[r]; ++n) {
305 std::vector<T> shifted(ser.size(), zero);
306 std::vector<std::size_t> sub(D, 0);
307 for (std::size_t p = 0; p < ser.size(); ++p) {
309 for (std::size_t d = 0; d < D && ok; ++d)
310 if (
static_cast<long>(sub[d]) < delta[r][d]) ok =
false;
311 if (ok) shifted[p] = term[p - off[r]];
312 for (std::size_t d = 0; d < D; ++d) {
313 if (++sub[d] < dims[d])
break;
317 const T c = Z(i - M, r) / num_traits<T>::from_int(n);
319 for (std::size_t p = 0; p < shifted.size(); ++p) {
320 shifted[p] = T(c * shifted[p]);
321 if (!(shifted[p] == zero)) any =
true;
325 for (std::size_t p = 0; p < nxt.size(); ++p) nxt[p] += term[p];
331 for (std::size_t j = 0; j < J; ++j) {
332 if (last[j] != i)
continue;
336 std::size_t pre = 1, post = 1;
337 for (std::size_t d = 0; d < R + j; ++d) pre *= dims[d];
338 for (std::size_t d = R + j + 1; d < D; ++d) post *= dims[d];
339 const std::size_t dj = dims[R + j];
340 std::vector<T> out(pre * post, zero);
341 if (sense[j] ==
'E') {
342 const std::size_t rhs =
static_cast<std::size_t
>(b[j]);
343 for (std::size_t q = 0; q < post; ++q)
344 for (std::size_t p = 0; p < pre; ++p)
345 out[p + q * pre] = ser[p + rhs * pre + q * pre * dj];
347 for (std::size_t q = 0; q < post; ++q)
348 for (std::size_t d = 0; d < dj; ++d)
349 for (std::size_t p = 0; p < pre; ++p)
350 out[p + q * pre] += ser[p + d * pre + q * pre * dj];
357 std::size_t expect = 1;
358 for (std::size_t r = 0; r < R; ++r) expect *= static_cast<std::size_t>(N[r]) + 1;
359 if (ser.size() != expect)
360 throw NumericError(
"pfqn_manjunath: a constraint row was never discharged; the "
361 "elimination order is inconsistent");
363 std::size_t flat = 0, st = 1;
364 for (std::size_t r = 0; r < R; ++r) {
365 flat +=
static_cast<std::size_t
>(N[r]) * st;
366 st *=
static_cast<std::size_t
>(N[r]) + 1;
388 const std::vector<long>& b,
const std::string& sense,
390 const std::size_t R = N.size();
391 const std::size_t M = L.
empty() ? 0 : L.
rows();
392 const std::size_t Mz = Z.
empty() ? 0 : Z.
rows();
393 const std::size_t S = M + Mz;
395 throw InputError(
"pfqn_manjunath: L and N disagree on the class count");
397 throw InputError(
"pfqn_manjunath: Z and N disagree on the class count");
399 const std::size_t Jin = b.size();
400 if (Jin > 0 && (A.
rows() != Jin || A.
cols() != S * R))
401 throw InputError(
"pfqn_manjunath: A must be J x (M+Mz)*R, acting on the occupancy read "
402 "column by column with the queueing stations first");
403 std::string
sn = sense;
404 if (
sn.empty())
sn = std::string(Jin,
'L');
405 if (
sn.size() != Jin)
406 throw InputError(
"pfqn_manjunath: sense must have one character per row of b");
407 for (std::size_t j = 0; j < Jin; ++j)
408 if (
sn[j] !=
'E' &&
sn[j] !=
'L' &&
sn[j] !=
'G')
409 throw InputError(
"pfqn_manjunath: sense must contain only 'E' (=), 'L' (<=) or "
412 for (std::size_t r = 0; r < R; ++r)
414 return detail::manjunath_empty<T>(
num_traits<T>::from_int(0), -std::numeric_limits<double>::infinity(), 0, options, R);
418 std::vector<std::vector<long>> Ai(Jin, std::vector<long>(S * R, 0));
419 for (std::size_t j = 0; j < Jin; ++j)
420 for (std::size_t c = 0; c < S * R; ++c) {
422 if (a < 0.0 || std::fabs(a - std::round(a)) > 1e-9)
423 throw InputError(
"pfqn_manjunath: A must contain nonnegative integers; the "
424 "residue argument counts whole units");
425 Ai[j][c] = std::lround(a);
432 std::vector<std::vector<long>> Ak;
433 std::vector<long> bk;
435 for (std::size_t j = 0; j < Jin; ++j) {
437 for (std::size_t c = 0; c < S * R; ++c)
438 if (Ai[j][c] != 0) trivial =
false;
440 if (b[j] < 0 || (trivial && b[j] != 0))
441 return detail::manjunath_empty<T>(num_traits<T>::from_int(0), -std::numeric_limits<double>::infinity(), 0, options, R);
442 if (trivial)
continue;
443 }
else if (sn[j] ==
'L') {
445 return detail::manjunath_empty<T>(num_traits<T>::from_int(0), -std::numeric_limits<double>::infinity(), 0, options, R);
446 if (trivial)
continue;
448 if (b[j] < 0)
continue;
450 return detail::manjunath_empty<T>(num_traits<T>::from_int(0), -std::numeric_limits<double>::infinity(), 0, options, R);
456 const std::size_t J = bk.size();
461 for (std::size_t r = 0; r < R; ++r)
462 if (N[r] != 0) empty =
false;
463 if (empty)
return detail::manjunath_empty<T>(num_traits<T>::from_int(1), 0.0, 1, options, R);
464 return detail::manjunath_empty<T>(num_traits<T>::from_int(0), -std::numeric_limits<double>::infinity(), 0, options, R);
468 for (std::size_t r = 0; r < R; ++r) Nt += N[r];
475 if (std::is_same<T, double>::value && Nt > 0) {
476 double lGest = -std::numeric_limits<double>::infinity();
477 for (std::size_t i = 0; i < M; ++i) {
480 for (std::size_t r = 0; r < R && ok; ++r)
482 const double lir = num_traits<T>::to_double(L(i, r));
484 t += N[r] * std::log(lir);
488 if (ok && t > lGest) lGest = t;
493 for (std::size_t r = 0; r < R && ok; ++r)
496 for (std::size_t k = 0; k < Mz; ++k) zs += num_traits<T>::to_double(Z(k, r));
498 t += N[r] * std::log(zs) - std::lgamma(N[r] + 1.0);
502 if (ok && t > lGest) lGest = t;
504 if (std::isfinite(lGest))
505 kscale = detail::manjunath_round(lGest / (
static_cast<double>(Nt) * std::log(2.0)));
510 const T c = num_traits<T>::from_int(1) /
511 num_pow_int(num_traits<T>::from_int(2),
static_cast<unsigned>(std::abs(kscale)));
512 const T f = (kscale > 0) ? c : num_traits<T>::from_int(1) / c;
513 for (std::size_t i = 0; i < M; ++i)
514 for (std::size_t r = 0; r < R; ++r)
Ls(i, r) = T(
Ls(i, r) * f);
515 for (std::size_t k = 0; k < Mz; ++k)
516 for (std::size_t r = 0; r < R; ++r) Zs(k, r) = T(Zs(k, r) * f);
525 std::vector<std::size_t> gt;
526 for (std::size_t j = 0; j < J; ++j)
527 if (sk[j] ==
'G') gt.push_back(j);
528 const std::size_t K = gt.size();
529 T Gs = num_traits<T>::from_int(0);
530 std::size_t peak = 0;
531 for (std::size_t mask = 0; mask < (static_cast<std::size_t>(1) << K); ++mask) {
532 std::vector<bool> on(J,
false);
533 for (std::size_t j = 0; j < J; ++j) on[j] = (sk[j] !=
'G');
534 std::size_t bits = 0;
535 for (std::size_t t = 0; t < K; ++t)
536 if (mask & (
static_cast<std::size_t
>(1) << t)) {
540 std::vector<std::vector<long>> As;
541 std::vector<long> bs;
543 for (std::size_t j = 0; j < J; ++j)
547 ss.push_back(sk[j] ==
'G' ?
'L' : sk[j]);
549 const T g = detail::pfqn_manjunath_series<T>(
Ls, Zs, N, As, bs, ss, options, peak);
556 if (!(Gs > num_traits<T>::from_int(0))) {
558 return detail::manjunath_empty<T>(num_traits<T>::from_int(0), -std::numeric_limits<double>::infinity(), peak, options, R);
560 const double lG = num_traits<T>::log_as_double(Gs) +
561 static_cast<double>(Nt) * kscale * std::log(2.0);
564 const T two = num_traits<T>::from_int(2);
565 const unsigned e =
static_cast<unsigned>(std::abs(
static_cast<long>(Nt) * kscale));
567 G = (kscale > 0) ? T(Gs * p) : T(Gs / p);
572 out.peak_states = peak;
574 detail::manjunath_stats<T>(out, L, N, Z, Ak, bk, sk, lG, M, Mz, S, R, options);
621void manjunath_stats(PfqnManjunathResult<T>& out,
const Matrix<T>& L,
const std::vector<int>& N,
622 const Matrix<T>& Z,
const std::vector<std::vector<long>>& A,
623 const std::vector<long>& b,
const std::string& sense,
double lG,
624 std::size_t M, std::size_t Mz, std::size_t S, std::size_t R,
625 const PfqnManjunathOptions& options) {
627 throw InputError(
"pfqn_manjunath: the per-class decomposition needs exactly one delay "
628 "station, got " + std::to_string(Mz) +
629 ". Pass Z as a 1xR row of think times");
632 "pfqn_manjunath: the per-class decomposition needs exactly one queueing station, got " +
634 ". With two or more the delay->q1->q2->delay cycle makes the chain irreversible, "
635 "Kelly truncation no longer holds, and the truncated product form is not the "
636 "stationary law (measured at 131% error). G and lG are still returned and still "
637 "correct as a sum over the admissible set");
638 const std::size_t J = b.size();
640 for (std::size_t r = 0; r < R; ++r) {
641 const std::size_t dcol = S * r + (S - 1);
642 for (std::size_t j = 0; j < J; ++j)
644 throw InputError(
"pfqn_manjunath: constraint row(s) reference the delay station "
645 "in class " + std::to_string(r + 1) +
" (column " +
646 std::to_string(dcol) +
"). The delay must lie OUTSIDE the finite "
647 "capacity region, because the decomposition charges every held "
651 Matrix<T> Am(J, S * R);
652 for (std::size_t j = 0; j < J; ++j)
653 for (std::size_t c = 0; c < S * R; ++c) Am(j, c) = num_traits<T>::from_int(A[j][c]);
655 PfqnManjunathOptions sub = options;
660 out.Q.assign(R, 0.0);
661 out.X.assign(R, 0.0);
662 for (std::size_t r = 0; r < R; ++r) {
663 const std::size_t qcol = S * r;
669 std::vector<int> Nr(N);
671 std::vector<long> br(J);
672 for (std::size_t j = 0; j < J; ++j) br[j] = b[j] - A[j][qcol];
673 const PfqnManjunathResult<T> s =
pfqn_manjunath<T>(L, Nr, Z, Am, br, sense, sub);
674 if (std::isfinite(s.lG)) out.X[r] = std::exp(s.lG - lG);
680 for (
int k = 1; k <= N[r]; ++k) {
681 Matrix<T> Ak(J + 1, S * R);
682 for (std::size_t j = 0; j < J; ++j)
683 for (std::size_t c = 0; c < S * R; ++c) Ak(j, c) = Am(j, c);
684 for (std::size_t c = 0; c < S * R; ++c)
685 Ak(J, c) = num_traits<T>::from_int(c == qcol ? 1 : 0);
686 std::vector<long> bk(b);
688 const PfqnManjunathResult<T> s =
690 if (std::isfinite(s.lG)) out.Q[r] += k * std::exp(s.lG - lG);
694 out.U.assign(R, 0.0);
695 out.think.assign(R, 0.0);
696 out.delay.assign(R, 0.0);
697 out.blocked.assign(R, 0.0);
698 for (std::size_t r = 0; r < R; ++r) {
699 out.U[r] = out.X[r] * num_traits<T>::to_double(L(0, r));
700 out.think[r] = out.X[r] * num_traits<T>::to_double(Z(0, r));
701 out.delay[r] =
static_cast<double>(N[r]) - out.Q[r];
702 out.blocked[r] = out.delay[r] - out.think[r];
704 out.has_stats =
true;
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
The exception types the port throws.
Dense matrix and non-owning view.
PfqnManjunathResult< T > pfqn_manjunath(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const Matrix< T > &A, const std::vector< long > &b, const std::string &sense, const PfqnManjunathOptions &options={})
Exact normalizing constant of a closed multiclass product-form network whose state space carries arbi...
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Number-type abstraction for the templated API port.
Controls of pfqn_manjunath.
std::size_t max_live_states
Cap on the number of series coefficients held at once.
bool stats
Also return the per-class decomposition.
Result of pfqn_manjunath.
T G
normalizing constant in the requested arithmetic
bool has_stats
whether the decomposition below was computed
std::vector< double > X
class r cycle throughput
std::vector< double > think
class r jobs genuinely thinking, X_r * Z_r
std::vector< double > delay
class r jobs at the delay, think + blocked
std::vector< double > U
class r utilization of the queueing station
std::size_t peak_states
peak live series coefficients, the realised cost
double lG
log of the constant, always a double
std::vector< double > blocked
class r jobs held at the delay by the constraint
std::vector< double > Q
mean class r jobs at the queueing station