5#ifndef LINE_API_CACHE_CACHE_MISS_RMF_H
6#define LINE_API_CACHE_CACHE_MISS_RMF_H
116 return i + k * n_items;
138namespace rmf_detail {
142T hit_rate(
const std::vector<T>& x,
const std::vector<T>& p, std::size_t level,
143 std::size_t n_items) {
145 for (std::size_t i = 0; i < n_items; ++i) hr += p[i] * x[
cache_miss_rmf_index(i, level, n_items)];
151std::vector<T> drift(
const std::vector<T>& x,
const std::vector<T>& p,
const std::vector<T>& m,
152 std::size_t n_items, std::size_t h) {
154 const std::size_t model_dim = n_items * (h + 1);
155 std::vector<T> hr(h + 1, zero);
156 for (std::size_t k = 0; k <= h; ++k) hr[k] = hit_rate(x, p, k, n_items);
157 std::vector<T> dX(model_dim, zero);
158 for (std::size_t i = 0; i < n_items; ++i) {
159 for (std::size_t k = 0; k + 1 <= h; ++k) {
162 const T flow = p[i] * x[ik] - hr[k] * x[ik1] / m[k];
194Matrix<T> jacobian(
const std::vector<T>& x,
const std::vector<T>& p,
const std::vector<T>& m,
195 std::size_t n_items, std::size_t h) {
196 const T zero = num_traits<T>::from_int(0);
197 const std::size_t model_dim = n_items * (h + 1);
198 std::vector<T> hr(h + 1, zero);
199 for (std::size_t k = 0; k <= h; ++k) hr[k] = hit_rate(x, p, k, n_items);
200 Matrix<T> Fp(model_dim, model_dim, zero);
201 for (std::size_t i = 0; i < n_items; ++i) {
202 for (std::size_t k = 0; k + 1 <= h; ++k) {
207 Fp(ik, ik1) += hr[k] / m[k];
208 Fp(ik1, ik1) -= hr[k] / m[k];
209 for (std::size_t j = 0; j < n_items; ++j) {
212 Fp(ik, jk1) -= p[i] * x[ik] / m[k];
213 Fp(ik1, jk1) += p[i] * x[ik] / m[k];
214 Fp(ik, jk) += p[j] * x[ik1] / m[k];
215 Fp(ik1, jk) -= p[j] * x[ik1] / m[k];
228std::vector<T> hessian(
const std::vector<T>& p,
const std::vector<T>& m, std::size_t n_items,
230 const T zero = num_traits<T>::from_int(0);
231 const std::size_t md = n_items * (h + 1);
232 std::vector<T> H(md * md * md, zero);
233 const auto at = [md](std::size_t a, std::size_t b, std::size_t c) {
234 return (a * md + b) * md + c;
236 for (std::size_t i = 0; i < n_items; ++i) {
237 for (std::size_t k = 0; k + 1 <= h; ++k) {
240 for (std::size_t j = 0; j < n_items; ++j) {
241 if (j == i)
continue;
244 H[at(ik, jk, ik1)] += p[j] / m[k];
245 H[at(ik, ik1, jk)] += p[j] / m[k];
246 H[at(ik, jk1, ik)] -= p[i] / m[k];
247 H[at(ik, ik, jk1)] -= p[i] / m[k];
248 H[at(ik1, jk, ik1)] -= p[j] / m[k];
249 H[at(ik1, ik1, jk)] -= p[j] / m[k];
250 H[at(ik1, jk1, ik)] += p[i] / m[k];
251 H[at(ik1, ik, jk1)] += p[i] / m[k];
260Matrix<T> noise_matrix(
const std::vector<T>& x,
const std::vector<T>& p,
const std::vector<T>& m,
261 std::size_t n_items, std::size_t h) {
262 const T zero = num_traits<T>::from_int(0);
263 const std::size_t md = n_items * (h + 1);
264 Matrix<T> Q(md, md, zero);
265 const int signs[4] = {-1, 1, 1, -1};
266 for (std::size_t i = 0; i < n_items; ++i) {
267 for (std::size_t k = 0; k + 1 <= h; ++k) {
268 for (std::size_t j = 0; j < n_items; ++j) {
275 for (
int ia = 0; ia < 4; ++ia)
276 for (
int ib = 0; ib < 4; ++ib)
277 Q(idx[ia], idx[ib]) +=
278 rate * num_traits<T>::from_int(signs[ia] * signs[ib]);
292std::vector<T> fixed_point(
const std::vector<T>& x0,
const std::vector<T>& p,
293 const std::vector<T>& m, std::size_t n_items, std::size_t h,
294 const T& tmax,
const T& rtol,
const T& atol) {
298 opt.store_trajectory =
false;
299 const auto f = [&](
const T& t,
const std::vector<T>& x) {
301 return drift(x, p, m, n_items, h);
304 return ode_rosenbrock4(f, T(num_traits<T>::from_int(0)), tmax, x0, opt).final_state();
309std::vector<T> fixed_point(
const std::vector<T>& x0,
const std::vector<T>& p,
310 const std::vector<T>& m, std::size_t n_items, std::size_t h,
312 return fixed_point(x0, p, m, n_items, h, tmax, T(num_traits<T>::from_double(1e-8)),
313 T(num_traits<T>::from_double(1e-10)));
331Matrix<T> lyapunov(
const Matrix<T>& F,
const Matrix<T>& Q) {
332 const std::size_t r = F.rows();
333 const T zero = num_traits<T>::from_int(0);
334 Matrix<T> A(r * r, r * r, zero);
335 std::vector<T> rhs(r * r, zero);
336 for (std::size_t a = 0; a < r; ++a)
337 for (std::size_t b = 0; b < r; ++b) {
338 const std::size_t row = a * r + b;
339 for (std::size_t c = 0; c < r; ++c) {
340 A(row, c * r + b) += F(a, c);
341 A(row, a * r + c) += F(b, c);
346 Matrix<T> W(r, r, zero);
347 for (std::size_t a = 0; a < r; ++a)
348 for (std::size_t b = 0; b < r; ++b) W(a, b) = w[a * r + b];
373 std::size_t rank = 0;
374 std::vector<std::vector<T>> basis;
378NullSpace<T> left_null_space(
const Matrix<T>& Fp) {
379 const std::size_t n = Fp.rows();
380 const T zero = num_traits<T>::from_int(0);
382 Matrix<T> M(n, n, zero);
384 for (std::size_t i = 0; i < n; ++i)
385 for (std::size_t j = 0; j < n; ++j) {
388 if (a > scale) scale = a;
390 if (scale == zero) scale = num_traits<T>::from_int(1);
393 const T tol = scale * num_traits<T>::from_double(1e-8);
395 std::vector<std::size_t> col_of_pivot;
396 std::vector<std::size_t> perm(n);
397 for (std::size_t i = 0; i < n; ++i) perm[i] = i;
399 for (std::size_t col = 0; col < n && row < n; ++col) {
402 for (std::size_t i = row + 1; i < n; ++i) {
403 const T a =
num_abs(M(i, col));
409 if (best <= tol)
continue;
411 for (std::size_t j = 0; j < n; ++j) std::swap(M(row, j), M(p, j));
412 const T d = M(row, col);
413 for (std::size_t j = 0; j < n; ++j) M(row, j) = M(row, j) / d;
414 for (std::size_t i = 0; i < n; ++i) {
415 if (i == row)
continue;
416 const T f = M(i, col);
417 if (f == zero)
continue;
418 for (std::size_t j = 0; j < n; ++j) M(i, j) -= f * M(row, j);
420 col_of_pivot.push_back(col);
425 ns.rank = col_of_pivot.size();
426 std::vector<bool> is_pivot(n,
false);
427 for (std::size_t c : col_of_pivot) is_pivot[c] =
true;
429 for (std::size_t free_col = 0; free_col < n; ++free_col) {
430 if (is_pivot[free_col])
continue;
431 std::vector<T> v(n, zero);
432 v[free_col] = num_traits<T>::from_int(1);
433 for (std::size_t r = 0; r < col_of_pivot.size(); ++r)
434 v[col_of_pivot[r]] = -M(r, free_col);
435 ns.basis.push_back(v);
439 for (std::size_t i = 0; i < ns.basis.size(); ++i) {
440 for (std::size_t k = 0; k < i; ++k) {
442 for (std::size_t j = 0; j < n; ++j) dot += ns.basis[i][j] * ns.basis[k][j];
443 for (std::size_t j = 0; j < n; ++j) ns.basis[i][j] -= dot * ns.basis[k][j];
446 for (std::size_t j = 0; j < n; ++j) nrm2 += ns.basis[i][j] * ns.basis[i][j];
447 const T nrm = sqrt(nrm2);
448 if (nrm <= tol)
throw NumericError(ErrorKind{
"Degenerate"},
"cache_miss_rmf: the null-space basis degenerated");
449 for (std::size_t j = 0; j < n; ++j) ns.basis[i][j] = ns.basis[i][j] / nrm;
471inline NullSpace<double> left_null_space_svd(
const Matrix<double>& Fp) {
472#ifndef LINE_MP_HAVE_LAPACK
473 return left_null_space(Fp);
475 const std::size_t n = Fp.rows();
476 std::vector<double> a(n * n);
477 for (std::size_t i = 0; i < n; ++i)
478 for (std::size_t j = 0; j < n; ++j) a[j * n + i] = Fp(i, j);
479 const int ni =
static_cast<int>(n);
480 std::vector<double> s(n), u(n * n), vt(1);
481 int info = 0, lwork = -1;
484 dgesvd_(
"A",
"N", &ni, &ni, a.data(), &ni, s.data(), u.data(), &ni, vt.data(), &one, &wopt,
486 if (info != 0)
throw NumericError(ErrorKind{
"Lapack"},
"cache_miss_rmf: LAPACK workspace query failed");
487 lwork =
static_cast<int>(wopt);
488 std::vector<double> work(
static_cast<std::size_t
>(lwork));
489 dgesvd_(
"A",
"N", &ni, &ni, a.data(), &ni, s.data(), u.data(), &ni, vt.data(), &one,
490 work.data(), &lwork, &info);
491 if (info != 0)
throw NumericError(ErrorKind{
"NonConvergence"},
"cache_miss_rmf: LAPACK dgesvd failed to converge");
493 const double tol = 1e-8 * s[0];
494 NullSpace<double> ns;
497 if (v > tol) ++ns.rank;
498 for (std::size_t c = ns.rank; c < n; ++c) {
499 std::vector<double> row(n, 0.0);
500 for (std::size_t i = 0; i < n; ++i) row[i] = u[c * n + i];
501 ns.basis.push_back(row);
511inline Matrix<double> lna_centered_basis(std::size_t n) {
512 if (n <= 1)
return Matrix<double>(n, 0, 0.0);
513 std::vector<std::vector<double>> cols;
514 for (std::size_t c = 0; c < n && cols.size() + 1 < n; ++c) {
515 std::vector<double> v(n, 0.0);
516 for (std::size_t i = 0; i < n; ++i)
517 v[i] = (i == c ? 1.0 : 0.0) - 1.0 /
static_cast<double>(n);
518 for (std::size_t b = 0; b < cols.size(); ++b) {
520 for (std::size_t i = 0; i < n; ++i) d += v[i] * cols[b][i];
521 for (std::size_t i = 0; i < n; ++i) v[i] -= d * cols[b][i];
524 for (std::size_t i = 0; i < n; ++i) nv += v[i] * v[i];
526 if (nv <= 1e-12)
continue;
527 for (std::size_t i = 0; i < n; ++i) v[i] /= nv;
530 Matrix<double> U(n, cols.size(), 0.0);
531 for (std::size_t c = 0; c < cols.size(); ++c)
532 for (std::size_t i = 0; i < n; ++i) U(i, c) = cols[c][i];
537inline Matrix<double> lna_kron(
const Matrix<double>& A,
const Matrix<double>& B) {
538 Matrix<double> K(A.rows() * B.rows(), A.cols() * B.cols(), 0.0);
539 for (std::size_t i = 0; i < A.rows(); ++i)
540 for (std::size_t j = 0; j < A.cols(); ++j)
541 for (std::size_t k = 0; k < B.rows(); ++k)
542 for (std::size_t l = 0; l < B.cols(); ++l)
543 K(i * B.rows() + k, j * B.cols() + l) = A(i, j) * B(k, l);
554inline Matrix<double> lna_covariance(
const std::vector<double>& x,
const std::vector<double>& p,
555 const std::vector<double>& m, std::size_t n, std::size_t h,
557 if (x.size() != dim)
throw InputError(
"cache_rmf_lna: the fixed point has the wrong length");
559 const Matrix<double> Fp = jacobian<double>(x, p, m, n, h);
560 Matrix<double> Q = noise_matrix<double>(x, p, m, n, h);
561 for (std::size_t i = 0; i < Q.rows(); ++i)
562 for (std::size_t j = 0; j < i; ++j) {
563 const double v = 0.5 * (Q(i, j) + Q(j, i));
568 const Matrix<double> Ui = lna_centered_basis(n);
569 const Matrix<double> Ul = lna_centered_basis(h + 1);
570 const Matrix<double> V = lna_kron(Ul, Ui);
571 if (V.cols() == 0)
return Matrix<double>(dim, dim, 0.0);
574 "cache_rmf_lna: the centred basis does not span the state dimension; n, h and dim "
577 const std::size_t d = V.cols();
578 Matrix<double> Ar(d, d, 0.0), Qr(d, d, 0.0);
579 for (std::size_t a = 0; a < d; ++a)
580 for (std::size_t b = 0; b < d; ++b) {
581 double sa = 0.0, sq = 0.0;
582 for (std::size_t i = 0; i < dim; ++i)
583 for (std::size_t j = 0; j < dim; ++j) {
584 sa += V(i, a) * Fp(i, j) * V(j, b);
585 sq += V(i, a) * Q(i, j) * V(j, b);
590 for (std::size_t i = 0; i < d; ++i)
591 for (std::size_t j = 0; j < i; ++j) {
592 const double v = 0.5 * (Qr(i, j) + Qr(j, i));
597 const std::vector<std::complex<double>> ev =
eig_values(Ar);
598 double worst = -std::numeric_limits<double>::infinity();
599 for (std::size_t i = 0; i < ev.size(); ++i) worst = std::max(worst, ev[i].real());
600 if (worst >= -std::sqrt(2.220446049250313e-16))
602 "cache_rmf_lna: the cache fluid fixed point is not exponentially stable on the "
603 "reachable subspace, so the occupancy process has no stationary covariance");
605 Matrix<double> ArT(d, d, 0.0);
606 for (std::size_t i = 0; i < d; ++i)
607 for (std::size_t j = 0; j < d; ++j) ArT(i, j) = Ar(j, i);
609 for (std::size_t i = 0; i < d; ++i)
610 for (std::size_t j = 0; j < i; ++j) {
611 const double v = 0.5 * (Wr(i, j) + Wr(j, i));
616 Matrix<double> W(dim, dim, 0.0);
617 for (std::size_t i = 0; i < dim; ++i)
618 for (std::size_t j = 0; j < dim; ++j) {
620 for (std::size_t a = 0; a < d; ++a)
621 for (std::size_t b = 0; b < d; ++b) s += V(i, a) * Wr(a, b) * V(j, b);
624 for (std::size_t i = 0; i < dim; ++i)
625 for (std::size_t j = 0; j < i; ++j) {
626 const double v = 0.5 * (W(i, j) + W(j, i));
635void lna_covariance_into(Matrix<T>& W,
const std::vector<T>& x,
const std::vector<T>& p,
636 const std::vector<T>& m, std::size_t n, std::size_t h, std::size_t dim) {
637 (void)W; (void)x; (void)p; (void)m; (void)n; (void)h; (void)dim;
641inline void lna_covariance_into<double>(Matrix<double>& W,
const std::vector<double>& x,
642 const std::vector<double>& p,
const std::vector<double>& m,
643 std::size_t n, std::size_t h, std::size_t dim) {
645 Matrix<double> C = lna_covariance(x, p, m, n, h, dim);
646 for (std::size_t i = 0; i < C.rows(); ++i)
647 for (std::size_t j = 0; j < C.cols(); ++j)
648 if (!std::isfinite(C(i, j)))
return;
650 }
catch (
const Error&) {
657NullSpace<T> left_null_space_for(
const Matrix<T>& Fp) {
658 return left_null_space(Fp);
662inline NullSpace<double> left_null_space_for<double>(
const Matrix<double>& Fp) {
663 return left_null_space_svd(Fp);
675Matrix<T> linear_graph(std::size_t h) {
676 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
677 Matrix<T> g(h + 1, h + 1, zero);
679 for (std::size_t a = 1; a + 1 <= h; ++a) g(a, a + 1) = one;
696std::vector<Matrix<T> > build_item_graphs(
const std::vector<std::vector<Matrix<T> > >& accost,
697 const Matrix<T>& lambda, std::size_t n, std::size_t h) {
698 std::vector<Matrix<T> > G;
699 if (accost.empty())
return G;
700 const T zero = num_traits<T>::from_int(0);
701 const Matrix<T> lin = linear_graph<T>(h);
702 const std::size_t u = accost.size();
703 std::vector<Matrix<T> > Gc(n, lin);
704 bool is_linear =
true;
705 for (std::size_t k = 0; k < n; ++k) {
706 Matrix<T> num(h + 1, h + 1, zero);
708 for (std::size_t v = 0; v < u; ++v) {
709 if (k >= accost[v].size())
continue;
710 const Matrix<T>& gvk = accost[v][k];
711 if (gvk.rows() == 0)
continue;
712 if (gvk.rows() != h + 1 || gvk.cols() != h + 1)
713 throw InputError(
"cache_miss_rmf: an access graph is not (h+1)x(h+1)");
714 double w = (v < lambda.rows() && k < lambda.cols())
715 ? num_traits<T>::to_double(lambda(v, k))
717 if (!std::isfinite(w)) w = 0.0;
718 const T wv = num_traits<T>::from_double(w);
719 for (std::size_t a = 0; a <= h; ++a)
720 for (std::size_t b = 0; b <= h; ++b) num(a, b) += wv * gvk(a, b);
725 for (std::size_t a = 0; a <= h; ++a)
726 for (std::size_t b = 0; b <= h; ++b) gk(a, b) = num(a, b) / den;
727 }
else if (!accost.empty() && k < accost[0].size() && accost[0][k].rows() == h + 1) {
730 for (std::size_t a = 0; a <= h; ++a) {
732 for (std::size_t b = 0; b <= h; ++b) srow += gk(a, b);
734 for (std::size_t b = 0; b <= h; ++b) gk(a, b) = gk(a, b) / srow;
737 for (std::size_t a = 0; a <= h && is_linear; ++a)
738 for (std::size_t b = 0; b <= h && is_linear; ++b)
739 if (std::fabs(num_traits<T>::to_double(gk(a, b)) -
740 num_traits<T>::to_double(lin(a, b))) >= 1e-9)
743 if (!is_linear) G = Gc;
758std::vector<T> drift_graph(
const std::vector<T>& x_in,
const std::vector<T>& p,
759 const std::vector<Matrix<T> >& G,
const std::vector<T>& m,
760 std::size_t n, std::size_t h) {
761 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
762 const std::size_t model_dim = n * (h + 1);
763 std::vector<T> x = x_in;
764 for (std::size_t a = 0; a < x.size(); ++a) {
765 if (x[a] < zero) x[a] = zero;
766 if (x[a] > one) x[a] = one;
769 Matrix<T> A(h + 1, h + 1, zero);
770 for (std::size_t s = 0; s <= h; ++s)
771 for (std::size_t j = 0; j < n; ++j) {
773 if (xjs == zero)
continue;
774 for (std::size_t i = 1; i <= h; ++i) A(s, i) += p[j] * xjs * G[j](s, i);
776 std::vector<T> dX(model_dim, zero);
777 for (std::size_t k = 0; k < n; ++k) {
779 for (std::size_t i = 1; i <= h; ++i) {
781 T infl = p[k] * outk * G[k](0, i);
782 for (std::size_t s = 1; s + 1 <= i; ++s)
784 for (std::size_t b = i + 1; b <= h; ++b)
786 T outfl = p[k] * xki * T(one - G[k](i, i));
788 for (std::size_t s = 0; s + 1 <= i; ++s) disp += A(s, i);
789 outfl += disp * xki / m[i - 1];
805std::vector<T> fixed_point_graph(
const std::vector<T>& x0,
const std::vector<T>& p,
806 const std::vector<Matrix<T> >& G,
const std::vector<T>& m,
807 std::size_t n, std::size_t h) {
809 opt.rtol = num_traits<T>::from_double(1e-8);
810 opt.atol = num_traits<T>::from_double(1e-10);
811 opt.store_trajectory =
false;
812 const auto f = [&](
const T& t,
const std::vector<T>& x) {
814 return drift_graph(x, p, G, m, n, h);
817 T(num_traits<T>::from_int(20000)), x0, opt)
844 const std::vector<std::vector<
Matrix<T> > >& accost) {
846 "cache_miss_rmf requires transcendental arithmetic: its fixed point is reached "
847 "by a tolerance-driven integration of the mean-field drift");
851 const std::size_t u = lambda.
rows();
852 const std::size_t n_items = lambda.
cols();
853 const std::size_t h = m_in.size();
854 if (u == 0 || n_items == 0)
throw InputError(
"cache_miss_rmf: empty request-rate matrix");
855 if (h == 0)
throw InputError(
"cache_miss_rmf: at least one cache list is required");
857 std::vector<T> m(h, zero);
858 for (std::size_t k = 0; k < h; ++k) {
859 if (m_in[k] <= 0)
throw InputError(
"cache_miss_rmf: a list has non-positive capacity");
864 std::vector<T> lam_i(n_items, zero);
866 for (std::size_t v = 0; v < u; ++v)
867 for (std::size_t i = 0; i < n_items; ++i) {
868 lam_i[i] += lambda(v, i);
869 lam_tot += lambda(v, i);
871 if (lam_tot == zero)
throw InputError(
"cache_miss_rmf: all request rates are zero");
872 std::vector<T> p(n_items, zero);
873 for (std::size_t i = 0; i < n_items; ++i) p[i] = lam_i[i] / lam_tot;
875 const std::size_t model_dim = n_items * (h + 1);
879 std::vector<T> x0(model_dim, zero);
881 for (std::size_t k = 1; k <= h; ++k)
882 for (
int jj = 0; jj < m_in[k - 1]; ++jj) {
889 const std::vector<Matrix<T> > G = rmf_detail::build_item_graphs(accost, lambda, n_items, h);
893 res.
xss = rmf_detail::fixed_point_graph(x0, p, G, m, n_items, h);
894 res.
pi0.assign(n_items, zero);
895 for (std::size_t i = 0; i < n_items; ++i) {
897 if (v < zero) v = zero;
898 if (v > one) v = one;
901 res.
MI.assign(n_items, zero);
903 for (std::size_t i = 0; i < n_items; ++i) {
904 res.
MI[i] = lam_i[i] * res.
pi0[i];
907 res.
MU.assign(u, zero);
908 for (std::size_t v = 0; v < u; ++v) {
910 for (std::size_t i = 0; i < n_items; ++i) s += lambda(v, i) * res.
pi0[i];
918 std::vector<T> xss = rmf_detail::fixed_point(x0, p, m, n_items, h, tmax);
922 const Matrix<T> Fp = rmf_detail::jacobian(xss, p, m, n_items, h);
923 const rmf_detail::NullSpace<T> ns = rmf_detail::left_null_space_for(Fp);
924 const std::size_t rk = ns.rank;
925 if (rk == 0 || rk >= model_dim)
927 const std::vector<T> Fpp = rmf_detail::hessian(p, m, n_items, h);
928 const Matrix<T> Q = rmf_detail::noise_matrix(xss, p, m, n_items, h);
934 for (std::size_t l = 0; l <= h && d < rk; ++l)
935 for (std::size_t i = 0; i + 1 < n_items && d < rk; ++i, ++d)
937 if (d != rk)
throw NumericError(
ErrorKind{
"Degenerate"},
"cache_miss_rmf: the reduction basis is too small");
939 if (ns.basis.size() != model_dim - rk)
940 throw NumericError(
ErrorKind{
"Degenerate"},
"cache_miss_rmf: the null-space basis has the wrong size");
941 for (std::size_t i = 0; i < ns.basis.size(); ++i)
942 for (std::size_t j = 0; j < model_dim; ++j) C(rk + i, j) = ns.basis[i][j];
948 for (std::size_t a = 0; a < rk; ++a)
949 for (std::size_t b = 0; b < rk; ++b) Fp_r(a, b) = tmp(a, b);
953 Matrix<T> Ct(model_dim, model_dim, zero);
954 for (std::size_t a = 0; a < model_dim; ++a)
955 for (std::size_t b = 0; b < model_dim; ++b) Ct(a, b) = C(b, a);
957 for (std::size_t a = 0; a < rk; ++a)
958 for (std::size_t b = 0; b < rk; ++b) Q_r(a, b) = tmp(a, b);
962 const auto Hat = [model_dim](std::size_t a, std::size_t b, std::size_t c) {
963 return (a * model_dim + b) * model_dim + c;
965 std::vector<T> tmp1(rk * model_dim * model_dim, zero);
966 for (std::size_t a = 0; a < rk; ++a)
967 for (std::size_t j = 0; j < model_dim; ++j)
968 for (std::size_t k = 0; k < model_dim; ++k) {
970 for (std::size_t i = 0; i < model_dim; ++i) s += C(a, i) * Fpp[Hat(i, j, k)];
971 tmp1[(a * model_dim + j) * model_dim + k] = s;
973 std::vector<T> tmp2(rk * rk * model_dim, zero);
974 for (std::size_t a = 0; a < rk; ++a)
975 for (std::size_t b = 0; b < rk; ++b)
976 for (std::size_t k = 0; k < model_dim; ++k) {
978 for (std::size_t j = 0; j < model_dim; ++j)
979 s += tmp1[(a * model_dim + j) * model_dim + k] * Cinv(j, b);
980 tmp2[(a * rk + b) * model_dim + k] = s;
982 std::vector<T> Fpp_r(rk * rk * rk, zero);
983 for (std::size_t a = 0; a < rk; ++a)
984 for (std::size_t b = 0; b < rk; ++b)
985 for (std::size_t c = 0; c < rk; ++c) {
987 for (std::size_t k = 0; k < model_dim; ++k)
988 s += tmp2[(a * rk + b) * model_dim + k] * Cinv(k, c);
989 Fpp_r[(a * rk + b) * rk + c] = s;
992 const Matrix<T> W_r = rmf_detail::lyapunov(Fp_r, Q_r);
994 std::vector<T> C_r(rk, zero);
995 for (std::size_t a = 0; a < rk; ++a) {
997 for (std::size_t b = 0; b < rk; ++b)
998 for (std::size_t c = 0; c < rk; ++c) s += Fpp_r[(a * rk + b) * rk + c] * W_r(b, c);
1001 std::vector<T> rhs(rk, zero);
1002 for (std::size_t a = 0; a < rk; ++a)
1004 const std::vector<T> V_r =
line::solve(Fp_r, rhs);
1006 std::vector<T> xref(model_dim, zero);
1008 for (std::size_t i = 0; i < model_dim; ++i) {
1010 for (std::size_t a = 0; a < rk; ++a) s += Cinv(i, a) * V_r[a];
1029 rmf_detail::lna_covariance_into(res.
Wcov, xss, p, m, n_items, h, model_dim);
1032 res.
pi0.assign(n_items, zero);
1033 for (std::size_t i = 0; i < n_items; ++i) {
1035 if (v < zero) v = zero;
1036 if (v > one) v = one;
1039 res.
MI.assign(n_items, zero);
1041 for (std::size_t i = 0; i < n_items; ++i) {
1042 res.
MI[i] = lam_i[i] * res.
pi0[i];
1045 res.
MU.assign(u, zero);
1046 for (std::size_t v = 0; v < u; ++v) {
1048 for (std::size_t i = 0; i < n_items; ++i) s += lambda(v, i) * res.
pi0[i];
1057 const Matrix<T>& lambda,
const T& tmax) {
1080 const Matrix<T>& lambda,
const T& t0,
const T& t1,
1081 const std::vector<T>& x0init,
1082 const std::vector<std::vector<
Matrix<T> > >& accost,
1085 "cache_miss_rmf_transient requires transcendental arithmetic");
1088 const std::size_t u = lambda.
rows();
1089 const std::size_t n_items = lambda.
cols();
1090 const std::size_t h = m_in.size();
1091 const std::size_t model_dim = n_items * (h + 1);
1092 if (x0init.size() != model_dim)
1093 throw InputError(
"cache_miss_rmf_transient: initial occupancy has the wrong length");
1095 std::vector<T> m(h, zero);
1096 for (std::size_t k = 0; k < h; ++k) m[k] = num_traits<T>::from_int(
static_cast<long>(m_in[k]));
1098 std::vector<T> lam_i(n_items, zero);
1100 for (std::size_t v = 0; v < u; ++v)
1101 for (std::size_t i = 0; i < n_items; ++i) {
1102 lam_i[i] += lambda(v, i);
1103 lam_tot += lambda(v, i);
1105 std::vector<T> p(n_items, zero);
1106 for (std::size_t i = 0; i < n_items; ++i) p[i] = lam_i[i] / lam_tot;
1113 const std::vector<Matrix<T> > G = rmf_detail::build_item_graphs(accost, lambda, n_items, h);
1114 const auto f = [&](
const T& t,
const std::vector<T>& x) {
1116 if (!G.empty())
return rmf_detail::drift_graph(x, p, G, m, n_items, h);
1117 return rmf_detail::drift(x, p, m, n_items, h);
1122 const std::size_t nt = s.
t.size();
1125 for (std::size_t j = 0; j < nt; ++j)
1126 for (std::size_t i = 0; i < model_dim; ++i) res.
xtraj(i, j) = s.
y[j][i];
1128 for (std::size_t i = 0; i < n_items; ++i)
1129 for (std::size_t j = 0; j < nt; ++j) {
1131 if (v < zero) v = zero;
1132 if (v > one) v = one;
1133 res.
pi0_t(i, j) = v;
1136 for (std::size_t v = 0; v < u; ++v)
1137 for (std::size_t j = 0; j < nt; ++j) {
1139 for (std::size_t i = 0; i < n_items; ++i) acc += lambda(v, i) * res.
pi0_t(i, j);
1140 res.
MU_t(v, j) = acc;
1145 res.
sojourn.MU.assign(u, zero);
1146 for (std::size_t v = 0; v < u; ++v) {
1148 for (std::size_t i = 0; i < n_items; ++i) {
1150 if (pb < zero) pb = zero;
1151 if (pb > one) pb = one;
1152 acc += lambda(v, i) * pb;
1165 const Matrix<T>& lambda,
const T& t0,
const T& t1,
1166 const std::vector<T>& x0init,
1169 std::vector<std::vector<
Matrix<T> > >(), clk);
1178 std::vector<Matrix<T> >
W;
1211 const std::vector<int>& m_in,
const Matrix<T>& lambda,
const T& time, std::size_t n_points,
1213 if (!std::is_same<T, double>::value)
1215 "cache_miss_rmf_expansion_transient: the coupled (X,V,W) system is integrated with "
1216 "LSODA, whose coefficients assume double precision; rerun with --arith double");
1218 const std::size_t u = lambda.
rows();
1219 const std::size_t n_items = lambda.
cols();
1220 const std::size_t h = m_in.size();
1221 if (u == 0 || n_items == 0)
1222 throw InputError(
"cache_miss_rmf_expansion_transient: empty request-rate matrix");
1224 throw InputError(
"cache_miss_rmf_expansion_transient: at least one cache list is required");
1226 throw InputError(
"cache_miss_rmf_expansion_transient: at least two output points are "
1227 "required to describe a trajectory");
1229 throw InputError(
"cache_miss_rmf_expansion_transient: the horizon must be positive");
1231 std::vector<T> m(h, zero);
1232 for (std::size_t k = 0; k < h; ++k) {
1234 throw InputError(
"cache_miss_rmf_expansion_transient: a list has non-positive capacity");
1238 std::vector<T> lam_i(n_items, zero);
1240 for (std::size_t v = 0; v < u; ++v)
1241 for (std::size_t i = 0; i < n_items; ++i) {
1242 lam_i[i] += lambda(v, i);
1243 lam_tot += lambda(v, i);
1245 if (lam_tot == zero)
1246 throw InputError(
"cache_miss_rmf_expansion_transient: all request rates are zero");
1247 std::vector<T> p(n_items, zero);
1248 for (std::size_t i = 0; i < n_items; ++i) p[i] = lam_i[i] / lam_tot;
1250 const std::size_t d = n_items * (h + 1);
1253 std::vector<T> x0(d, zero);
1255 std::size_t obj = 0;
1256 for (std::size_t k = 1; k <= h; ++k)
1257 for (
int jj = 0; jj < m_in[k - 1]; ++jj) {
1262 for (std::size_t i = obj; i < n_items; ++i)
1266 std::vector<double> grid(n_points, 0.0);
1268 for (std::size_t j = 0; j < n_points; ++j)
1269 grid[j] = tend *
static_cast<double>(j) /
static_cast<double>(n_points - 1);
1271 const std::size_t total = order == 0 ? d : d + d + d * d;
1272 std::vector<double> y0(total, 0.0);
1273 for (std::size_t i = 0; i < d; ++i) y0[i] = num_traits<T>::to_double(x0[i]);
1275 const std::vector<T> Fpp = order == 0 ? std::vector<T>() : rmf_detail::hessian(p, m, n_items, h);
1277 const auto rhs = [&](
double t,
const double* y,
double* dy) {
1279 std::vector<T> x(d, zero);
1280 for (std::size_t i = 0; i < d; ++i) x[i] = num_traits<T>::from_double(y[i]);
1281 const std::vector<T> F = rmf_detail::drift(x, p, m, n_items, h);
1282 for (std::size_t i = 0; i < d; ++i) dy[i] = num_traits<T>::to_double(F[i]);
1283 if (order == 0)
return;
1284 const Matrix<T> Fp = rmf_detail::jacobian(x, p, m, n_items, h);
1285 const Matrix<T> Q = rmf_detail::noise_matrix(x, p, m, n_items, h);
1286 for (std::size_t a = 0; a < d; ++a) {
1291 for (std::size_t b = 0; b < d; ++b)
1292 for (std::size_t c = 0; c < d; ++c) {
1293 const T hv = Fpp[(a * d + b) * d + c];
1294 if (hv == zero)
continue;
1300 for (std::size_t a = 0; a < d; ++a)
1301 for (std::size_t b = 0; b < d; ++b) {
1303 for (std::size_t c = 0; c < d; ++c)
1314 if (!s.
success || s.
y.size() != n_points)
1315 throw NumericError(
ErrorKind{
"NonConvergence"},
"cache_miss_rmf_expansion_transient: the coupled (X,V,W) system could "
1316 "not be integrated over the requested horizon");
1319 out.
t.assign(n_points, zero);
1322 out.W.assign(n_points,
Matrix<T>(d, d, zero));
1323 for (std::size_t j = 0; j < n_points; ++j) {
1325 for (std::size_t i = 0; i < d; ++i) {
1329 if (order == 0)
continue;
1330 for (std::size_t a = 0; a < d; ++a)
1331 for (std::size_t b = 0; b < d; ++b)
Grid-independent sojourn-weighted average of a cache mean-field transient.
The algorithm cannot produce a trustworthy answer on this instance: LINE:numerical:<kind>.
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
Eigenvalues and singular values, backed by LAPACK.
The exception types the port throws.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
LSODA: the LINE-facing wrapper over the vendored solver in third_party/lsoda.hpp.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
CacheMissRmfResult< T > cache_miss_rmf(const std::vector< T > &gamma, const std::vector< int > &m_in, const Matrix< T > &lambda, const T &tmax, const std::vector< std::vector< Matrix< T > > > &accost)
Refined mean-field miss rates of a RANDOM(m) multi-list cache.
CacheRmfExpansionTransient< T > cache_miss_rmf_expansion_transient(const std::vector< int > &m_in, const Matrix< T > &lambda, const T &time, std::size_t n_points, int order)
Refined mean-field TRANSIENT, CacheRMF.meanFieldExpansionTransient.
CacheSojournResult< T > cache_sojourn_ode(const F &f, const T &t0, const T &t1, const std::vector< T > &x0, const CacheSojournClock< T > &clk)
Integrates dx/dt = f(t, x) over [t0, t1] from x0 together with clk; see the file comment.
CacheMissRmfResult< T > cache_miss_rmf_transient(const std::vector< int > &m_in, const Matrix< T > &lambda, const T &t0, const T &t1, const std::vector< T > &x0init, const std::vector< std::vector< Matrix< T > > > &accost, const CacheSojournClock< T > &clk=CacheSojournClock< T >())
Transient mean-field trajectory over [t0,t1] from a given initial occupancy, the optional TSPAN/X0INI...
std::size_t cache_miss_rmf_index(std::size_t i, std::size_t k, std::size_t n_items)
Flat index of (item i, list k), k = 0 meaning "not cached" (rmf_index.m).
double dot(const std::vector< double > &a, const std::vector< double > &b)
The inner product of a row vector with a column held as a vector.
Conservation laws of a layered queueing network, enumerated from its structure.
OdeSolution< T > ode_rosenbrock4(const F &f, const J &jac, const T &t0, const T &t1, const std::vector< T > &y0, const OdeOptions< T > &opt)
Integrate y' = f(t,y) from t0 to t1 with an analytic Jacobian.
LsodaSolution lsoda_integrate(const LsodaRhs &f, const std::vector< double > &y0, const std::vector< double > &t_eval, const LsodaOptions &opt=LsodaOptions())
Integrate dy/dt = f(t, y) from t_eval.front() through every later entry of t_eval,...
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
std::vector< std::complex< double > > eig_values(const Matrix< double > &A)
Eigenvalues of a general real square matrix, in LAPACK's order.
Matrix< double > lyap_schur(const Matrix< double > &A, const Matrix< double > &B, const Matrix< double > &C)
MATLAB lyap(A,B,C) at double via Bartels-Stewart: A X + X B + C = 0.
Number-type abstraction for the templated API port.
Adaptive stiff ODE integrator: a four-stage Rosenbrock method of order four with an embedded order-th...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
double atol
absolute tolerance, applied to every component
double rtol
relative tolerance, applied to every component
Result of an integration, mirroring OdeSolution in ode.h.
bool success
false when LSODA returned istate < 0
std::vector< std::vector< double > > y
y[i] is the state at t[i]
std::vector< double > t
output times, t[0] = t_eval[0]
Result of an integration.
std::vector< std::vector< T > > y
y[i] is the state at t[i]
std::vector< T > t
accepted time points, t[0] = t0
Return value of cache_miss_rmf, mirroring [M,MU,MI,pi0,tout,pi0_t,MU_t,xtraj].
std::vector< T > pi0
(n_items) per-item miss probability, clipped to [0,1]
Matrix< T > pi0_t
(n_items x nt) transient miss probability
std::vector< T > tout
transient time grid, empty unless tspan was given
Matrix< T > xtraj
(model_dim x nt) transient occupancy
bool refined
true when the 1/N correction was accepted
Matrix< T > MU_t
(u x nt) transient per-user miss rate
Matrix< T > Wcov
LNA stationary covariance of the occupancy (model_dim x model_dim); empty when unavailable or not fin...
std::vector< T > MI
(n_items) per-item miss rate
CacheSojournResult< T > sojourn
Grid-independent average of the transient against a clock; empty unless one was given.
std::vector< T > xss
the occupancy the metrics were read from
std::vector< T > MU
(u) per-user miss rate
Result of cache_miss_rmf_expansion_transient.
std::vector< Matrix< T > > W
(n_points) covariance, each model_dim x model_dim
Matrix< T > X
(n_points x model_dim) mean-field trajectory
Matrix< T > V
(n_points x model_dim) 1/N correction trajectory
std::vector< T > t
(n_points) output instants, t[0] = 0
A phase-type clock: dphi/dt = phi A, phi(t0) = phi0, density phi c.
A sojourn average: xbar (empty when wtot is not positive), clock mass, per-user miss rate.
The Sylvester equation A X + X B = C, and MATLAB's lyap(A,B,C).