LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
cache_is.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_API_CACHE_IS_H
6#define LINE_API_CACHE_IS_H
7
8/**
9 * @file
10 * @ingroup api_cache
11 * Importance-sampling estimate of the cache normalizing constant.
12 *
13 * Templated port of matlab/src/api/cache/cache_is.m. A configuration is drawn
14 * by choosing mt = sum(m) of the n items uniformly without replacement and
15 * splitting them across the lists in the given order, a proposal of constant
16 * density 1/(C(n,mt) multinomial(mt;m)); the estimator is the sample mean of
17 * the product of the access factors divided by that density, so it is
18 * unbiased. It scales to item counts at which the exact enumeration of
19 * cache_erec is out of reach, at the price of a Monte Carlo error.
20 *
21 * MATLAB draws the sample with randperm(n,mt) and then shuffles it again
22 * before splitting. This port draws it with a partial Fisher-Yates pass, which
23 * produces a uniformly random ordered sequence of mt distinct items directly;
24 * the second shuffle of the reference is a permutation of an already exchangeable
25 * sequence and changes nothing in distribution. The stream is therefore NOT
26 * reproducible against MATLAB run for run: only the estimate is comparable,
27 * within its own Monte Carlo error.
28 *
29 * Arithmetic. static_assert(has_transcendental) -- the estimator is formed in
30 * the log domain (log-factorials, a log-sum-exp average) because the raw
31 * weights overflow, and it is a Monte Carlo average in any case, so exact
32 * arithmetic is meaningless here.
33 */
34
35#include <cmath>
36#include <cstddef>
37#include <limits>
38#include <random>
39#include <vector>
40
43#include "line/num/number.h"
44#include "line/util/error.h"
45#include "line/util/matrix.h"
46
47namespace line {
48namespace cache {
49
50namespace detail {
51
52/** gamma with the all-zero rows deleted, i.e. gamma(sum(gamma,2)>0,:). */
53template <class T>
54Matrix<T> gamma_drop_zero_rows(const Matrix<T>& gamma) {
55 const T zero = num_traits<T>::from_int(0);
56 std::size_t keep = 0;
57 for (std::size_t i = 0; i < gamma.rows(); ++i) {
58 T s = zero;
59 for (std::size_t j = 0; j < gamma.cols(); ++j) s += gamma(i, j);
60 if (s > zero) ++keep;
61 }
62 Matrix<T> g(keep, gamma.cols());
63 std::size_t r = 0;
64 for (std::size_t i = 0; i < gamma.rows(); ++i) {
65 T s = zero;
66 for (std::size_t j = 0; j < gamma.cols(); ++j) s += gamma(i, j);
67 if (s <= zero) continue;
68 for (std::size_t j = 0; j < gamma.cols(); ++j) g(r, j) = gamma(i, j);
69 ++r;
70 }
71 return g;
72}
73
74/**
75 * mt distinct items out of n, uniformly, as an ordered sequence: a partial
76 * Fisher-Yates pass over a lazily materialized identity permutation.
77 */
78inline void sample_without_replacement(std::size_t n, std::size_t mt, std::mt19937_64& rng,
79 std::vector<std::size_t>& perm,
80 std::vector<std::size_t>& out) {
81 perm.resize(n);
82 for (std::size_t i = 0; i < n; ++i) perm[i] = i;
83 out.resize(mt);
84 for (std::size_t k = 0; k < mt; ++k) {
85 std::uniform_int_distribution<std::size_t> d(k, n - 1);
86 const std::size_t j = d(rng);
87 const std::size_t t = perm[k];
88 perm[k] = perm[j];
89 perm[j] = t;
90 out[k] = perm[k];
91 }
92}
93
94} // namespace detail
95
96template <class T>
98 T E; ///< normalizing constant estimate
99 T lE; ///< its logarithm
100};
101
102/**
103 * @brief Importance-sampling estimate of the cache normalizing constant.
104 *
105 * @param gamma_in (n x h) access factors
106 * @param m (h) list capacities
107 * @param samples number of Monte Carlo samples (MATLAB default 1e5)
108 * @param seed seed of the sampling stream
109 * @param sigma_in (n) per-item storage cost; empty for uncapped lists
110 * @param k (h) per-list cost cap; empty for uncapped lists
111 */
112template <class T>
113CacheIsResult<T> cache_is(const Matrix<T>& gamma_in, const std::vector<int>& m,
114 std::size_t samples, std::uint64_t seed,
115 const std::vector<int>& sigma_in, const std::vector<int>& k) {
117 "cache_is requires transcendental arithmetic: the estimator is a Monte Carlo "
118 "average formed in the log domain");
119 if (gamma_in.cols() != m.size())
120 throw InputError("cache_is: gamma and m disagree on the number of lists");
121 if (samples == 0) throw InputError("cache_is: at least one sample is required");
122
123 using std::log;
124 const T zero = num_traits<T>::from_int(0);
125 const T one = num_traits<T>::from_int(1);
126
127 const bool capped = !sigma_in.empty() && !k.empty();
128 const Matrix<T> gamma = detail::gamma_drop_zero_rows(gamma_in);
129 std::vector<int> sigma;
130 if (capped) {
131 // gamma_drop_zero_rows keeps only the rows with a positive row sum
132 for (std::size_t i = 0; i < gamma_in.rows(); ++i) {
133 T rs = num_traits<T>::from_int(0);
134 for (std::size_t j = 0; j < gamma_in.cols(); ++j) rs += gamma_in(i, j);
135 if (rs > num_traits<T>::from_int(0)) sigma.push_back(sigma_in[i]);
136 }
137 }
138 const std::size_t n = gamma.rows();
139 const std::size_t h = m.size();
140 long mt = 0;
141 for (int v : m) {
142 if (v < 0) throw InputError("cache_is: negative list capacity");
143 mt += v;
144 }
145
147 if (n == 0 || mt == 0) {
148 res.E = one;
149 res.lE = zero;
150 return res;
151 }
152 if (static_cast<long>(n) < mt) {
153 // Fewer items than cache slots: no valid configuration exists, exactly
154 // as the reference reports (it warns and returns zero).
155 res.E = zero;
156 res.lE = -T(std::numeric_limits<T>::infinity());
157 return res;
158 }
159 if (static_cast<long>(n) == mt) {
160 // Every item must be cached: a single configuration, taken exactly.
161 res.E = cache_erec(gamma, m, sigma, k);
162 res.lE = log(res.E);
163 return res;
164 }
165
166 Matrix<T> lgam(n, h, zero);
167 const T floorv = num_traits<T>::from_double(1e-300);
168 for (std::size_t i = 0; i < n; ++i)
169 for (std::size_t j = 0; j < h; ++j) lgam(i, j) = log(T(gamma(i, j) + floorv));
170
171 T logMFact = zero;
172 for (std::size_t j = 0; j < h; ++j)
173 logMFact += pfqn::detail::num_logfact_int<T>(static_cast<long>(m[j]));
174 const T logComb = pfqn::detail::num_logfact_int<T>(static_cast<long>(n)) -
175 pfqn::detail::num_logfact_int<T>(mt) -
176 pfqn::detail::num_logfact_int<T>(static_cast<long>(n) - mt);
177 const T logMultinom = pfqn::detail::num_logfact_int<T>(mt) - logMFact;
178 const T logProposal = -logComb - logMultinom;
179
180 std::mt19937_64 rng(seed);
181 std::vector<std::size_t> perm, sel;
182 std::vector<T> lZ(samples, zero);
183 for (std::size_t s = 0; s < samples; ++s) {
184 detail::sample_without_replacement(n, static_cast<std::size_t>(mt), rng, perm, sel);
185 T logState = logMFact;
186 std::size_t idx = 0;
187 bool feasible = true;
188 for (std::size_t j = 0; j < h && feasible; ++j) {
189 if (capped) {
190 long listCost = 0;
191 for (int c = 0; c < m[j]; ++c) listCost += sigma[sel[idx + c]];
192 if (listCost > k[j]) {
193 feasible = false;
194 break;
195 }
196 }
197 for (int c = 0; c < m[j]; ++c) logState += lgam(sel[idx++], j);
198 }
199 if (!feasible) {
200 lZ[s] = -T(std::numeric_limits<T>::infinity()); // I{S_v in O} = 0
201 continue;
202 }
203 lZ[s] = logState - logProposal;
204 }
205 res.lE = pfqn::detail::logsumexp(lZ) - log(num_traits<T>::from_int(static_cast<long>(samples)));
206 using std::exp;
207 res.E = exp(res.lE);
208 return res;
209}
210
211/** cache_is without storage cost caps. */
212template <class T>
213CacheIsResult<T> cache_is(const Matrix<T>& gamma_in, const std::vector<int>& m,
214 std::size_t samples, std::uint64_t seed) {
215 return cache_is(gamma_in, m, samples, seed, std::vector<int>(), std::vector<int>());
216}
217
218/** cache_is with the MATLAB default of 1e5 samples. */
219template <class T>
220CacheIsResult<T> cache_is(const Matrix<T>& gamma, const std::vector<int>& m) {
221 return cache_is(gamma, m, static_cast<std::size_t>(100000), static_cast<std::uint64_t>(0));
222}
223
224} // namespace cache
225} // namespace line
226
227#endif // LINE_API_CACHE_IS_H
Exact recursive normalizing constant of a multi-list cache model.
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Dense matrix and non-owning view.
CacheIsResult< T > cache_is(const Matrix< T > &gamma_in, const std::vector< int > &m, std::size_t samples, std::uint64_t seed, const std::vector< int > &sigma_in, const std::vector< int > &k)
Importance-sampling estimate of the cache normalizing constant.
Definition cache_is.h:113
T cache_erec(const Matrix< T > &gamma, const std::vector< int > &m)
Exact recursive normalizing constant of a multi-list cache model.
Definition cache_erec.h:201
Number-type abstraction for the templated API port.
Shared scalar machinery for the integration / asymptotic members of the pfqn family (pfqn_le,...
T E
normalizing constant estimate
Definition cache_is.h:98