LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
retrieval_mva.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_RETRIEVAL_RETRIEVAL_MVA_H
6#define LINE_API_RETRIEVAL_RETRIEVAL_MVA_H
7
8/**
9 * @file
10 * @ingroup api_retrieval
11 * Exact MVA-style recursion for delayed-hit (list-based) cache metrics.
12 *
13 * Templated port of matlab/src/api/retrieval/retrieval_mva.m. There is no JAR
14 * counterpart: jline/api/retrieval/ ships nc, metrics, fpi and fpi_latency
15 * only, so MATLAB is the sole reference for this function.
16 *
17 * This is the exact recursion that retrieval_fpi approximates. Writing
18 * phi^{(k)} for the delayed-hit probability in the system WITHOUT item k,
19 *
20 * theta_ij(m) = gamma_ij / (1 + lambda_i eta_{0,i}
21 * + sum_s lambda_i eta_{s,i}(1 + sum_{k!=i} phi^{(i)}_{s,k}(m-1_j)))
22 * xi_j(m) = m_j / sum_i theta_ij(m)(1 - pihit_i(m-1_j))
23 * pi_ij(m) = theta_ij(m) xi_j(m) (1 - pihit_i(m-1_j))
24 * pi_i0(m) = (1 - pihit_i(m)) / (1 + lambda_i eta_{0,i}
25 * + sum_s lambda_i eta_{s,i}(1 + sum_{k!=i} phi^{(i)}_{s,k}(m)))
26 * phi_{s,k}(m)= lambda_k pi_k0(m) eta_{s,k}(1 + sum_{i!=k} phi^{(k)}_{s,i}(m))
27 * phi_{0,k}(m)= lambda_k eta_{0,k} pi_k0(m)
28 *
29 * memoized over (item subset, capacity vector). The recursion bottoms out at
30 * the empty item set and at any capacity able to hold every remaining item,
31 * where the items are permanently cached (pihit = 1, no fetching at all).
32 * Cost is O(2^n n^2 h r prod_j (1+m_j)) time and memory, so it is a small-case
33 * oracle: use retrieval_fpi beyond that.
34 *
35 * ARITHMETIC: additions, multiplications and divisions of the inputs only, so
36 * a finite field computation, exact in the exact instantiation. It then agrees
37 * with retrieval_metrics as an identity between rationals, which is the
38 * strongest available check on either implementation.
39 */
40
41#include <cstddef>
42#include <vector>
43
44#include "line/num/number.h"
45#include "line/util/error.h"
46#include "line/util/matrix.h"
47
48namespace line {
49namespace retrieval {
50
51/** Mirrors the [pmiss, phit, pdh] return list of the MATLAB function. */
52template <class T>
54 std::vector<T> pmiss; ///< (n) miss ratios pi_{i,0}
55 Matrix<T> phit; ///< (h x n) hit ratios pi_{i,j}
56 Matrix<T> pdh; ///< ((r+1) x n) delayed-hit probabilities phi_{s,i}, s = 0..r
57};
58
59namespace detail {
60
61/**
62 * Memo tables and the recursion itself. Held in an object rather than in
63 * statics so the function is reentrant and leaves no state behind.
64 */
65template <class T>
66class RetrievalMvaSolver {
67public:
68 RetrievalMvaSolver(const std::vector<int>& m, const std::vector<T>& lambda,
69 const Matrix<T>& eta, const Matrix<T>& gamma)
70 : m_(m), lambda_(lambda), eta_(eta), gamma_(gamma), n_(lambda.size()), h_(m.size()),
71 r_(eta.cols() - 1) {
72 radix_.resize(h_);
73 ncap_ = 1;
74 for (std::size_t j = 0; j < h_; ++j) {
75 radix_[j] = static_cast<std::size_t>(m_[j]) + 1;
76 ncap_ *= radix_[j];
77 }
78 nmask_ = static_cast<std::size_t>(1) << n_;
79 const std::size_t cells = nmask_ * ncap_;
80 done_.assign(cells, false);
81 const T zero = num_traits<T>::from_int(0);
82 pi0_.assign(cells * n_, zero);
83 pihit_.assign(cells * n_, zero);
84 pij_.assign(cells * n_ * h_, zero);
85 phi_.assign(cells * n_ * (r_ + 1), zero);
86 }
87
89 const std::size_t full = nmask_ - 1;
90 solve(full, m_);
91 const std::size_t ci = capidx(m_);
93 out.pmiss.assign(n_, num_traits<T>::from_int(0));
94 out.phit = Matrix<T>(h_, n_, num_traits<T>::from_int(0));
95 out.pdh = Matrix<T>(r_ + 1, n_, num_traits<T>::from_int(0));
96 for (std::size_t i = 0; i < n_; ++i) {
97 out.pmiss[i] = pi0_[(full * ncap_ + ci) * n_ + i];
98 for (std::size_t j = 0; j < h_; ++j)
99 out.phit(j, i) = pij_[((full * ncap_ + ci) * n_ + i) * h_ + j];
100 for (std::size_t s = 0; s <= r_; ++s)
101 out.pdh(s, i) = phi_[((full * ncap_ + ci) * n_ + i) * (r_ + 1) + s];
102 }
103 return out;
104 }
105
106private:
107 std::size_t capidx(const std::vector<int>& c) const {
108 std::size_t idx = 0, mul = 1;
109 for (std::size_t j = 0; j < h_; ++j) {
110 idx += static_cast<std::size_t>(c[j]) * mul;
111 mul *= radix_[j];
112 }
113 return idx;
114 }
115
116 T& PI0(std::size_t mask, std::size_t ci, std::size_t i) {
117 return pi0_[(mask * ncap_ + ci) * n_ + i];
118 }
119 T& PIHIT(std::size_t mask, std::size_t ci, std::size_t i) {
120 return pihit_[(mask * ncap_ + ci) * n_ + i];
121 }
122 T& PIJ(std::size_t mask, std::size_t ci, std::size_t i, std::size_t j) {
123 return pij_[((mask * ncap_ + ci) * n_ + i) * h_ + j];
124 }
125 T& PHI(std::size_t mask, std::size_t ci, std::size_t i, std::size_t s) {
126 return phi_[((mask * ncap_ + ci) * n_ + i) * (r_ + 1) + s];
127 }
128
129 void solve(std::size_t mask, const std::vector<int>& c) {
130 const std::size_t ci = capidx(c);
131 if (done_[mask * ncap_ + ci]) return;
132 if (mask == 0) {
133 done_[mask * ncap_ + ci] = true;
134 return;
135 }
136 std::vector<std::size_t> active;
137 for (std::size_t i = 0; i < n_; ++i)
138 if (mask & (static_cast<std::size_t>(1) << i)) active.push_back(i);
139
140 long csum = 0;
141 for (int x : c) csum += x;
142 if (csum >= static_cast<long>(active.size())) {
143 // The cache holds every active item, so all of them are cached
144 // permanently: no miss, no fetch, hit probability one.
145 for (std::size_t i : active) PIHIT(mask, ci, i) = num_traits<T>::from_int(1);
146 done_[mask * ncap_ + ci] = true;
147 return;
148 }
149
150 const T one = num_traits<T>::from_int(1);
151
152 // dependencies at m - 1_j, with and without each active item
153 for (std::size_t j = 0; j < h_; ++j) {
154 if (c[j] > 0) {
155 std::vector<int> cj = c;
156 cj[j] -= 1;
157 solve(mask, cj);
158 for (std::size_t i : active) solve(mask & ~(static_cast<std::size_t>(1) << i), cj);
159 }
160 }
161 for (std::size_t k : active) solve(mask & ~(static_cast<std::size_t>(1) << k), c);
162
163 // theta, xi and pi_ij, all evaluated on the cache recursion m - 1_j
164 for (std::size_t j = 0; j < h_; ++j) {
165 if (c[j] == 0) continue;
166 std::vector<int> cj = c;
167 cj[j] -= 1;
168 const std::size_t cjx = capidx(cj);
169 std::vector<T> theta(n_, num_traits<T>::from_int(0));
170 for (std::size_t i : active) {
171 const std::size_t maski = mask & ~(static_cast<std::size_t>(1) << i);
172 T acc = num_traits<T>::from_int(0);
173 for (std::size_t s = 0; s < r_; ++s) {
174 T sphi = num_traits<T>::from_int(0);
175 for (std::size_t k : active)
176 if (k != i) sphi += PHI(maski, cjx, k, s + 1);
177 acc += lambda_[i] * eta_(i, s + 1) * (one + sphi);
178 }
179 theta[i] = gamma_(i, j) / (one + lambda_[i] * eta_(i, 0) + acc);
180 }
181 T sden = num_traits<T>::from_int(0);
182 for (std::size_t i : active) sden += theta[i] * (one - PIHIT(mask, cjx, i));
183 if (sden == num_traits<T>::from_int(0))
184 throw NumericError("retrieval_mva: degenerate list occupancy (zero denominator)");
185 const T xi_j = num_traits<T>::from_int(static_cast<long>(c[j])) / sden;
186 for (std::size_t i : active)
187 PIJ(mask, ci, i, j) = theta[i] * xi_j * (one - PIHIT(mask, cjx, i));
188 }
189
190 for (std::size_t i : active) {
191 T ph = num_traits<T>::from_int(0);
192 for (std::size_t j = 0; j < h_; ++j) ph += PIJ(mask, ci, i, j);
193 PIHIT(mask, ci, i) = ph;
194 }
195
196 for (std::size_t i : active) {
197 const std::size_t maski = mask & ~(static_cast<std::size_t>(1) << i);
198 T acc = num_traits<T>::from_int(0);
199 for (std::size_t s = 0; s < r_; ++s) {
200 T sphi = num_traits<T>::from_int(0);
201 for (std::size_t k : active)
202 if (k != i) sphi += PHI(maski, ci, k, s + 1);
203 acc += lambda_[i] * eta_(i, s + 1) * (one + sphi);
204 }
205 PI0(mask, ci, i) =
206 (one - PIHIT(mask, ci, i)) / (one + lambda_[i] * eta_(i, 0) + acc);
207 }
208
209 for (std::size_t k : active) {
210 const std::size_t maskk = mask & ~(static_cast<std::size_t>(1) << k);
211 const T pi0k = PI0(mask, ci, k);
212 PHI(mask, ci, k, 0) = lambda_[k] * eta_(k, 0) * pi0k;
213 for (std::size_t s = 0; s < r_; ++s) {
214 T sphi = num_traits<T>::from_int(0);
215 for (std::size_t i : active)
216 if (i != k) sphi += PHI(maskk, ci, i, s + 1);
217 PHI(mask, ci, k, s + 1) = lambda_[k] * pi0k * eta_(k, s + 1) * (one + sphi);
218 }
219 }
220
221 done_[mask * ncap_ + ci] = true;
222 }
223
224 const std::vector<int>& m_;
225 const std::vector<T>& lambda_;
226 const Matrix<T>& eta_;
227 const Matrix<T>& gamma_;
228 std::size_t n_, h_, r_;
229 std::vector<std::size_t> radix_;
230 std::size_t ncap_ = 1, nmask_ = 1;
231 std::vector<bool> done_;
232 std::vector<T> pi0_, pihit_, pij_, phi_;
233};
234
235} // namespace detail
236
237/**
238 * @brief Exact MVA-style recursion for delayed-hit (list-based) cache
239 * metrics.
240 *
241 * @param m (h) cache list capacities
242 * @param lambda (n) per-item arrival rates
243 * @param eta (n x (r+1)) fetching demands, column 0 = IS station, columns 1..r = PS
244 * @param gamma (n x h) access factors
245 */
246template <class T>
247RetrievalMvaResult<T> retrieval_mva(const std::vector<int>& m, const std::vector<T>& lambda,
248 const Matrix<T>& eta, const Matrix<T>& gamma) {
249 const std::size_t n = lambda.size();
250 if (eta.rows() != n || gamma.rows() != n)
251 throw InputError("retrieval_mva: eta/gamma and lambda disagree on the item count");
252 if (gamma.cols() != m.size())
253 throw InputError("retrieval_mva: gamma and m disagree on the number of lists");
254 if (eta.cols() == 0) throw InputError("retrieval_mva: eta has no columns");
255 for (int x : m)
256 if (x < 0) throw InputError("retrieval_mva: negative list capacity");
257 if (n > 8 * sizeof(std::size_t) - 1)
258 throw UnsupportedError("retrieval_mva: too many items for a bitmask subset enumeration");
259 detail::RetrievalMvaSolver<T> solver(m, lambda, eta, gamma);
260 return solver.run();
261}
262
263} // namespace retrieval
264} // namespace line
265
266#endif // LINE_API_RETRIEVAL_RETRIEVAL_MVA_H
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
NumericError(const std::string &what)
Definition error.h:45
UnsupportedError(const std::string &what)
Definition error.h:51
The exception types the port throws.
Dense matrix and non-owning view.
RetrievalMvaResult< T > retrieval_mva(const std::vector< int > &m, const std::vector< T > &lambda, const Matrix< T > &eta, const Matrix< T > &gamma)
Exact MVA-style recursion for delayed-hit (list-based) cache metrics.
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
Number-type abstraction for the templated API port.
Mirrors the [pmiss, phit, pdh] return list of the MATLAB function.
Matrix< T > phit
(h x n) hit ratios pi_{i,j}
Matrix< T > pdh
((r+1) x n) delayed-hit probabilities phi_{s,i}, s = 0..r
std::vector< T > pmiss
(n) miss ratios pi_{i,0}