5#ifndef LINE_API_CACHE_CACHE_SPM_SIZE_H
6#define LINE_API_CACHE_CACHE_SPM_SIZE_H
157 const std::vector<T>& th,
const std::vector<T>& et,
158 std::vector<T>* d_out =
nullptr) {
160 const std::size_t n = g.
rows();
161 const std::size_t h = th.size();
164 for (std::size_t a = 0; a < n; ++a) {
166 for (std::size_t b = 0; b < h; ++b) {
167 p(a, b) = g(a, b) * exp(th[b] + sg[a] * et[b]);
170 if (d_out !=
nullptr) (*d_out)[a] = s;
171 for (std::size_t b = 0; b < h; ++b) p(a, b) = p(a, b) / s;
183 const std::vector<std::size_t>& ix) {
184 const std::size_t n = p.
rows();
185 const std::size_t h = p.
cols();
186 const std::size_t nb = ix.size();
187 const std::size_t dim = h + nb;
188 std::vector<std::size_t> coord(dim);
189 for (std::size_t u = 0; u < h; ++u) coord[u] = u;
190 for (std::size_t b = 0; b < nb; ++b) coord[h + b] = ix[b];
194 for (std::size_t a = 0; a < n; ++a) {
195 for (std::size_t b = 0; b < nb; ++b) w[h + b] = sg[a];
196 for (std::size_t u = 0; u < dim; ++u) {
197 const std::size_t ju = coord[u];
198 for (std::size_t v = 0; v < dim; ++v) {
199 const std::size_t jv = coord[v];
200 const T q = (ju == jv ? p(a, ju) : zero) - p(a, ju) * p(a, jv);
201 hess(u, v) += w[u] * w[v] * q;
210std::vector<T> spm_size_solve(
const Matrix<T>& a,
const std::vector<T>& b) {
212 const std::size_t dim = b.size();
213 Matrix<T> m(dim, dim + 1);
214 for (std::size_t i = 0; i < dim; ++i) {
215 for (std::size_t j = 0; j < dim; ++j) m(i, j) = a(i, j);
218 for (std::size_t c = 0; c < dim; ++c) {
220 for (std::size_t i = c + 1; i < dim; ++i)
221 if (abs(m(i, c)) > abs(m(piv, c))) piv = i;
222 if (num_traits<T>::to_double(abs(m(piv, c))) == 0.0)
223 throw NumericError(
"cache_spm_size: the saddle-point Newton step is not finite. With "
224 "item sizes this is the rank-h degeneracy of the size-tilted Hessian: "
225 "the sizes must genuinely vary for the cost coordinate to carry "
228 for (std::size_t j = 0; j <= dim; ++j) {
229 const T tmp = m(c, j);
233 for (std::size_t i = c + 1; i < dim; ++i) {
234 const T f = m(i, c) / m(c, c);
235 for (std::size_t j = c; j <= dim; ++j) m(i, j) -= f * m(c, j);
238 std::vector<T> x(dim);
239 for (std::size_t ii = dim; ii-- > 0;) {
241 for (std::size_t j = ii + 1; j < dim; ++j) s -= m(ii, j) * x[j];
242 x[ii] = s / m(ii, ii);
249T spm_size_logdet(
const Matrix<T>& a) {
252 const std::size_t dim = a.rows();
253 const T half = num_traits<T>::from_rational(1, 2);
254 const T two = num_traits<T>::from_int(2);
255 Matrix<T> l(dim, dim, num_traits<T>::from_int(0));
256 T ld = num_traits<T>::from_int(0);
257 for (std::size_t i = 0; i < dim; ++i) {
258 for (std::size_t j = 0; j <= i; ++j) {
259 T s = half * (a(i, j) + a(j, i));
260 for (std::size_t c = 0; c < j; ++c) s -= l(i, c) * l(j, c);
262 if (num_traits<T>::to_double(s) <= 0.0)
263 throw NumericError(
"cache_spm_size: the saddle-point Hessian is not positive "
264 "definite; the ray map is singular here. With item sizes this "
265 "happens when the sizes do not vary over the items the cache "
266 "can hold, in which case the cost cap carries no information");
268 ld += two * log(l(i, j));
270 l(i, j) = s / l(j, j);
279double spm_size_damp(
const std::vector<T>& d) {
281 for (std::size_t i = 0; i < d.size(); ++i)
282 dmax = std::max(dmax, std::abs(num_traits<T>::to_double(d[i])));
284 while (step * dmax > 2.0) step /= 2.0;
289std::vector<T> spm_size_theta0(
const Matrix<T>& g,
const std::vector<T>& tgt) {
291 const std::size_t n = g.rows();
292 const std::size_t h = tgt.size();
294 for (std::size_t b = 0; b < h; ++b) tsum += num_traits<T>::to_double(tgt[b]);
295 const double slack = std::max(1.0 - tsum /
static_cast<double>(n), 1e-9);
296 const T slackT = num_traits<T>::from_double(slack);
297 const T tiny = num_traits<T>::from_double(1e-12);
298 std::vector<T> th(h);
299 for (std::size_t b = 0; b < h; ++b) {
300 T gb = num_traits<T>::from_int(0);
301 for (std::size_t a = 0; a < n; ++a) gb += g(a, b);
303 if (num_traits<T>::to_double(den) < 1e-12) den = tiny;
305 if (num_traits<T>::to_double(num) < 1e-12) num = tiny;
306 th[b] = log(num / den);
313T spm_size_obj(
const Matrix<T>& g,
const std::vector<T>& sg,
const std::vector<T>& th,
314 const std::vector<T>& et,
const std::vector<T>& tgtm,
const std::vector<T>& tgtk,
315 const std::vector<bool>& bind) {
317 std::vector<T> d(g.rows());
318 spm_size_occupancy(g, sg, th, et, &d);
319 T f = num_traits<T>::from_int(0);
320 for (std::size_t a = 0; a < g.rows(); ++a) f += log(d[a]);
321 for (std::size_t b = 0; b < th.size(); ++b) {
322 f -= tgtm[b] * th[b];
323 if (bind[b]) f -= tgtk[b] * et[b];
330std::vector<T> spm_size_saddle_free(
const Matrix<T>& g,
const std::vector<T>& tgt,
331 std::size_t& iterations) {
332 const std::size_t n = g.rows();
333 const std::size_t h = tgt.size();
334 const std::vector<T> zeros_n(n, num_traits<T>::from_int(0));
335 const std::vector<T> zeros_h(h, num_traits<T>::from_int(0));
336 const std::vector<std::size_t> noix;
337 std::vector<T> th = spm_size_theta0(g, tgt);
339 for (std::size_t b = 0; b < h; ++b)
340 tmax = std::max(tmax, std::abs(num_traits<T>::to_double(tgt[b])));
342 for (it = 1; it <= 200; ++it) {
343 const Matrix<T> p = spm_size_occupancy(g, zeros_n, th, zeros_h);
344 std::vector<T> grad(h);
346 for (std::size_t b = 0; b < h; ++b) {
347 T s = num_traits<T>::from_int(0);
348 for (std::size_t a = 0; a < n; ++a) s += p(a, b);
349 grad[b] = s - tgt[b];
350 gmax = std::max(gmax, std::abs(num_traits<T>::to_double(grad[b])));
352 if (gmax <= 1e-12 * tmax)
break;
353 std::vector<T> d = spm_size_solve(spm_size_hessian(p, zeros_n, noix), grad);
354 for (std::size_t b = 0; b < h; ++b) d[b] = -d[b];
355 const T step = num_traits<T>::from_double(spm_size_damp(d));
356 for (std::size_t b = 0; b < h; ++b) th[b] += step * d[b];
369void spm_size_saddle(
const Matrix<T>& g,
const std::vector<T>& tgtm,
const std::vector<T>& sg,
370 const std::vector<T>& tgtk,
bool cumulative, std::vector<T>& th,
371 std::vector<T>& et, std::vector<bool>& bind, std::size_t& iterations) {
372 const std::size_t n = g.rows();
373 const std::size_t h = tgtm.size();
374 const T zero = num_traits<T>::from_int(0);
375 th = spm_size_theta0(g, tgtm);
377 bind.assign(h,
true);
379 for (std::size_t b = 0; b < h; ++b) {
380 tol = std::max(tol, std::abs(num_traits<T>::to_double(tgtm[b])));
381 tol = std::max(tol, std::abs(num_traits<T>::to_double(tgtk[b])));
384 std::vector<T> thn(h), etn(h);
386 for (it = 1; it <= 200; ++it) {
387 const Matrix<T> p = spm_size_occupancy(g, sg, th, et);
388 std::vector<T> gth(h),
get(h);
389 for (std::size_t b = 0; b < h; ++b) {
392 for (std::size_t a = 0; a < n; ++a) {
394 s2 += sg[a] * p(a, b);
396 gth[b] = s1 - tgtm[b];
397 get[b] = s2 - tgtk[b];
400 for (std::size_t b = 0; b < h; ++b)
401 bind[b] = num_traits<T>::to_double(et[b]) < 0.0 ||
402 num_traits<T>::to_double(get[b]) > 0.0;
403 std::vector<std::size_t> ix;
404 for (std::size_t b = 0; b < h; ++b)
405 if (bind[b]) ix.push_back(b);
406 const std::size_t nb = ix.size();
407 std::vector<T> grad(h + nb);
409 for (std::size_t b = 0; b < h; ++b) {
411 gmax = std::max(gmax, std::abs(num_traits<T>::to_double(grad[b])));
413 for (std::size_t b = 0; b < nb; ++b) {
414 grad[h + b] =
get[ix[b]];
415 gmax = std::max(gmax, std::abs(num_traits<T>::to_double(grad[h + b])));
417 if (gmax <= tol)
break;
418 std::vector<T> d = spm_size_solve(spm_size_hessian(p, sg, ix), grad);
419 for (std::size_t b = 0; b < d.size(); ++b) d[b] = -d[b];
420 double step = spm_size_damp(d);
421 const T fcur = spm_size_obj(g, sg, th, et, tgtm, tgtk, bind);
425 const double ftol = 1e-12 * (1.0 + std::abs(num_traits<T>::to_double(fcur)));
426 for (
int ls = 0; ls < 40; ++ls) {
427 const T stepT = num_traits<T>::from_double(step);
428 for (std::size_t b = 0; b < h; ++b) {
429 thn[b] = th[b] + stepT * d[b];
432 for (std::size_t b = 0; b < nb; ++b) etn[ix[b]] = et[ix[b]] + stepT * d[h + b];
434 for (std::size_t b = 0; b < h; ++b)
435 if (num_traits<T>::to_double(etn[b]) > 0.0) etn[b] = zero;
436 const T fn = spm_size_obj(g, sg, thn, etn, tgtm, tgtk, bind);
437 if (num_traits<T>::to_double(fn) <= num_traits<T>::to_double(fcur) + ftol)
break;
441 for (std::size_t b = 0; b < h; ++b) {
442 moved = std::max(moved, std::abs(num_traits<T>::to_double(thn[b] - th[b])));
443 moved = std::max(moved, std::abs(num_traits<T>::to_double(etn[b] - et[b])));
447 if (moved <= 1e-13)
break;
451 for (std::size_t b = 0; b < h; ++b) bind[b] = num_traits<T>::to_double(et[b]) < 0.0;
456T spm_size_logfact(
int x) {
458 T s = num_traits<T>::from_int(0);
459 for (
int i = 2; i <= x; ++i) s += log(num_traits<T>::from_int(i));
463inline long spm_size_gcd(
long a,
long b) {
464 long x = a < 0 ? -a : a;
465 long y = b < 0 ? -b : b;
467 const long r = x % y;
488 const std::vector<int>& sigma,
const std::vector<int>& k,
491 "cache_spm_size is a Laplace approximation built out of logs, exps and a "
492 "square root: it is meaningless at exact arithmetic, and widening the type "
493 "sharpens the saddle solve but never the O(1/n) model error. Use cache_erec "
494 "for the exact constant");
498 if (gamma.
rows() == 0 || gamma.
cols() == 0)
499 throw InputError(
"cache_spm_size: gamma must be a non-empty n x h matrix");
500 const std::size_t n0 = gamma.
rows();
501 const std::size_t h0 = gamma.
cols();
503 throw InputError(
"cache_spm_size: the capacity vector must have one entry per cache list");
504 for (std::size_t j = 0; j < h0; ++j)
505 if (m[j] < 0)
throw InputError(
"cache_spm_size: list capacities must be non-negative");
506 if (sigma.empty() || k.empty())
507 throw InputError(
"cache_spm_size: the item sizes and the cost caps are both required; use "
508 "retrieval_rayint for the size-free expansion");
509 if (sigma.size() != n0)
510 throw InputError(
"cache_spm_size: the item size vector must have one entry per item");
512 throw InputError(
"cache_spm_size: the cost cap vector must have one entry per cache list");
513 for (std::size_t i = 0; i < n0; ++i)
514 if (sigma[i] <= 0)
throw InputError(
"cache_spm_size: item sizes must be positive integers");
523 out.
xi.assign(h0, zero);
524 out.
zeta.assign(h0, one);
526 out.
k_mean.assign(h0, zero);
530 for (std::size_t i = 0; i < n0; ++i) out.
pij(i, 0) = one;
534 for (std::size_t j = 0; j < h0; ++j) msum += m[j];
536 for (std::size_t j = 0; j < h0; ++j)
537 if (k[j] < 0) negcap =
true;
538 if (msum >
static_cast<long>(n0) || negcap) {
544 for (std::size_t j = 0; j < h0; ++j)
545 if (k[j] > 0) poscap =
true;
547 if (!(exact_mode && poscap)) {
555 std::vector<std::size_t> alive;
556 for (std::size_t i = 0; i < n0; ++i) {
558 for (std::size_t j = 0; j < h0; ++j) s += gamma(i, j);
561 std::vector<std::size_t> keep;
562 for (std::size_t j = 0; j < h0; ++j)
563 if (m[j] > 0) keep.push_back(j);
564 const std::size_t n = alive.size();
565 const std::size_t hk = keep.size();
567 for (std::size_t b = 0; b < hk; ++b) mksum += m[keep[b]];
568 if (mksum >
static_cast<long>(n)) {
572 if (mksum ==
static_cast<long>(n))
573 throw InputError(
"cache_spm_size: the expansion requires sum(m) < n; at sum(m) = n the "
574 "saddle point escapes to infinity, use cache_erec for a full cache");
577 std::vector<long> sgi(n);
578 for (std::size_t a = 0; a < n; ++a) {
579 sgi[a] = sigma[alive[a]];
580 for (std::size_t b = 0; b < hk; ++b) g(a, b) = gamma(alive[a], keep[b]);
582 std::vector<long> kki(hk);
583 std::vector<long> mki(hk);
584 for (std::size_t b = 0; b < hk; ++b) {
591 for (std::size_t a = 0; a < n; ++a) span = detail::spm_size_gcd(span, sgi[a]);
592 out.
span =
static_cast<int>(span);
594 for (std::size_t b = 0; b < hk; ++b)
595 if (kki[b] % span != 0) {
599 for (std::size_t a = 0; a < n; ++a) sgi[a] /= span;
600 for (std::size_t b = 0; b < hk; ++b) kki[b] = kki[b] / span;
602 for (std::size_t b = 0; b < hk; ++b) {
603 std::vector<long> srt;
604 for (std::size_t a = 0; a < n; ++a)
606 if (
static_cast<long>(srt.size()) < mki[b]) {
610 std::sort(srt.begin(), srt.end());
612 for (
long a = 0; a < mki[b]; ++a) lo += srt[static_cast<std::size_t>(a)];
619 for (std::size_t a = srt.size() -
static_cast<std::size_t
>(mki[b]); a < srt.size(); ++a)
630 for (std::size_t a = 1; a < n; ++a)
631 if (sgi[a] != sgi[0]) uniform =
false;
633 bool feasible =
true;
634 for (std::size_t b = 0; b < hk; ++b) {
635 const long cost = sgi[0] * mki[b];
636 if (exact_mode ? (cost != kki[b]) : (cost > kki[b])) feasible =
false;
639 out.
method =
"uniform-size";
643 out.
method =
"uniform-size";
646 std::vector<T> sg(n);
647 std::vector<T> mk(hk), kk(hk);
648 for (std::size_t a = 0; a < n; ++a) sg[a] = num_traits<T>::from_int(sgi[a]);
649 for (std::size_t b = 0; b < hk; ++b) {
655 std::vector<T> th, et;
656 std::vector<bool> bind;
657 std::size_t iters = 0;
658 std::vector<T> dvec(n);
666 detail::spm_size_saddle(g, mk, sg, kk, !exact_mode, th, et, bind, iters);
667 p = detail::spm_size_occupancy(g, sg, th, et, &dvec);
668 std::vector<std::size_t> ix;
669 for (std::size_t b = 0; b < hk; ++b)
670 if (bind[b]) ix.push_back(b);
671 for (std::size_t a = 0; a < n; ++a) phi += log(dvec[a]);
672 for (std::size_t b = 0; b < hk; ++b) phi -= mk[b] * th[b];
673 for (std::size_t b = 0; b < ix.size(); ++b) phi -= kk[ix[b]] * et[ix[b]];
674 logdet = detail::spm_size_logdet(detail::spm_size_hessian(p, sg, ix));
676 log_h = phi - half * dof * log2pi - half * logdet;
678 for (std::size_t b = 0; b < ix.size(); ++b)
679 log_h -= log(one - exp(et[ix[b]]));
682 const std::vector<T> zeros_n(n, zero);
683 th = detail::spm_size_saddle_free(g, mk, iters);
685 bind.assign(hk,
false);
686 p = detail::spm_size_occupancy(g, zeros_n, th, et, &dvec);
687 for (std::size_t a = 0; a < n; ++a) phi += log(dvec[a]);
688 for (std::size_t b = 0; b < hk; ++b) phi -= mk[b] * th[b];
689 const std::vector<std::size_t> noix;
690 logdet = detail::spm_size_logdet(detail::spm_size_hessian(p, zeros_n, noix));
692 log_h = phi - half * dof * log2pi - half * logdet;
697 for (std::size_t j = 0; j < h0; ++j) logfact += detail::spm_size_logfact<T>(m[j]);
698 out.
log_e = log_h + logfact;
703 for (std::size_t b = 0; b < hk; ++b) {
704 out.
xi[keep[b]] = exp(th[b]);
705 out.
zeta[keep[b]] = exp(et[b] / spanT);
706 out.
binding[keep[b]] = bind[b];
708 for (std::size_t a = 0; a < n; ++a) {
710 for (std::size_t b = 0; b < hk; ++b) {
711 out.
pij(alive[a], 1 + keep[b]) = p(a, b);
714 out.
pij(alive[a], 0) = one - hit;
716 for (std::size_t j = 0; j < h0; ++j) {
718 for (std::size_t i = 0; i < n0; ++i) s += num_traits<T>::from_int(sigma[i]) * out.
pij(i, 1 + j);
725 for (std::size_t b = 1; b < hk; ++b) mmin = std::min(mmin, mki[b]);
726 out.
relerr_est = 0.14 * (1.0 /
static_cast<double>(mmin) +
727 1.0 /
static_cast<double>(n -
static_cast<std::size_t
>(mksum)));
NumericError(const std::string &what)
The exception types the port throws.
Dense matrix and non-owning view.
CacheCostMode
Whether the caps bound the cost from above (matching cache_erec) or resolve it exactly.
CacheSpmSizeResult< T > cache_spm_size(const Matrix< T > &gamma, const std::vector< int > &m, const std::vector< int > &sigma, const std::vector< int > &k, CacheCostMode mode=CacheCostMode::AtMost)
Ray (WKB) asymptotic expansion of the cost-capped cache normalizing constant.
Response get(const std::string &url, int timeoutMillis)
GET a URL.
Number-type abstraction for the templated API port.
Outcome of the expansion.
int span
gcd of the item sizes, divided out as an exact lattice reduction.
T phi
The exponent Psi - m.log xi - k.log zeta.
std::vector< T > xi
Saddle point xi_j, one entry per list (0 for a list of zero capacity).
std::vector< T > zeta
Cost tilt zeta_j on the original size lattice (1 for a slack or absent list).
Matrix< T > pij
Occupancy pi, n x (h+1), column 0 the miss probability.
std::vector< T > k_mean
Mean storage cost held by each list.
std::string method
"spm-size", "spm", "uniform-size", "lattice" or "boundary".
T e
Normalizing constant, same normalization as cache_erec (may overflow; use log_e).
std::size_t iterations
Newton iterations used.
double relerr_est
Size-free error baseline 0.14*(1/min_j m_j + 1/(n - sum_j m_j)); see ACCURACY.
T log_e
Natural logarithm of e, safe for large n.
T logdet_sigma
log det of the Hessian in (log xi, log zeta), restricted to the free coordinates.
std::vector< bool > binding
Whether each list's cost cap binds.