LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
cache_gamma.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_GAMMA_H
6#define LINE_API_CACHE_GAMMA_H
7
8/**
9 * @file
10 * @ingroup api_cache
11 * Access factors of a multi-list cache whose lists form a general access GRAPH.
12 *
13 * Templated port of jar/src/main/java/jline/api/cache/Cache_gamma.java. MATLAB
14 * has no counterpart: it carries only cache_gamma_lp, the specialization to a
15 * tree ("linear path"), which recovers the path by walking the unique parent
16 * relation and rejects a node with two parents.
17 *
18 * Here the structure is only required to be reachable. The path is the
19 * BREADTH-FIRST shortest path in the access graph of item i, so a node with
20 * several parents is admissible and the first shortest path found in node order
21 * is the one taken. Along that path,
22 *
23 * gamma(i,j) = (sum_v lambda(v,i,0)) prod_{edges (a,b)} sum_v lambda(v,i,a) R{v,i}(a,b)
24 *
25 * THREE DIVERGENCES FROM cache_gamma_lp, all faithful to the JAR and all of
26 * them changing the number, so a caller must not treat the two as substitutes:
27 * - THE DESTINATION IS NODE j, NOT NODE j+1. cache_gamma_lp walks to node l+1
28 * for column l, node 0 being the miss list; this walks to node j. Column 0
29 * therefore has the trivial one-node path and carries NO edge factor at all,
30 * where the tree version carries the miss-to-first-list edge.
31 * - the leading factor is the aggregate miss-node request rate sum_v
32 * lambda(v,i,0), whereas the tree version starts the product at one;
33 * - each edge factor reads lambda(v,i,a) at the SOURCE node a alone, whereas
34 * the tree version sums lambda(v,i,t) over every t <= a.
35 * The first of these is hard to read as anything but an off-by-one against the
36 * shared meaning of gamma. It is reproduced rather than corrected because this
37 * routine exists only in the JAR -- MATLAB has no cache_gamma to arbitrate, and
38 * no solver in any codebase calls it (only a JUnit import does), so "fixing" it
39 * would leave the C++ disagreeing with the sole reference that defines it. Use
40 * cache_gamma_lp for the access factors a cache solver consumes.
41 *
42 * An unreachable node gives gamma(i,j) = 0, which unlike the tree version is a
43 * REACHABLE branch here: the BFS genuinely returns no path.
44 *
45 * THE GRAPH IS READ FROM USER 0 ONLY. The JAR takes R.get(0).get(i) for the
46 * adjacency and then sums the per-user rates along that one path, so a model
47 * whose users route an item differently is analysed on the first user's graph.
48 * Reproduced rather than corrected, since changing it would silently move the
49 * answer for every such model.
50 *
51 * Arithmetic: EXACT-CAPABLE. Sums and products only; the BFS is pure integer
52 * bookkeeping and tests adjacency against zero, which is exact in any T.
53 */
54
55#include <cstddef>
56#include <deque>
57#include <vector>
58
59#include "line/num/number.h"
60#include "line/util/error.h"
61#include "line/util/matrix.h"
62
63namespace line {
64namespace cache {
65
66/** Return value of cache_gamma, mirroring the JAR's Ret.cacheGamma. */
67template <class T>
69 Matrix<T> gamma; ///< (n x h) access factors
70 std::size_t u; ///< number of user streams
71 std::size_t n; ///< number of items
72 std::size_t h; ///< number of lists
73};
74
75namespace detail {
76
77/**
78 * Breadth-first shortest path from source to destination over the nonzero
79 * entries of an adjacency matrix; empty when the destination is unreachable.
80 */
81template <class T>
82std::vector<std::size_t> cache_bfs_path(const Matrix<T>& adjacency, std::size_t source,
83 std::size_t destination) {
84 const std::size_t n = adjacency.rows();
85 if (source >= n || destination >= n) return std::vector<std::size_t>();
86 const T zero = num_traits<T>::from_int(0);
87 std::vector<bool> visited(n, false);
88 std::vector<int> parent(n, -1);
89 std::deque<std::size_t> queue;
90 queue.push_back(source);
91 visited[source] = true;
92 while (!queue.empty()) {
93 const std::size_t current = queue.front();
94 queue.pop_front();
95 if (current == destination) {
96 std::vector<std::size_t> path;
97 int node = static_cast<int>(destination);
98 while (node != -1) {
99 path.insert(path.begin(), static_cast<std::size_t>(node));
100 node = parent[node];
101 }
102 return path;
103 }
104 for (std::size_t next = 0; next < n; ++next)
105 if (!visited[next] && adjacency(current, next) > zero) {
106 visited[next] = true;
107 parent[next] = static_cast<int>(current);
108 queue.push_back(next);
109 }
110 }
111 return std::vector<std::size_t>();
112}
113
114} // namespace detail
115
116/**
117 * @brief Access factors of a multi-list cache whose lists form a general
118 * access GRAPH.
119 *
120 * @param lambda (u) matrices of size (n x (h+1)): lambda[v](i,t) is the rate at
121 * which user v requests item i while it sits at node t
122 * @param R (u x n) routing matrices of size ((h+1) x (h+1))
123 */
124template <class T>
126 const std::vector<std::vector<Matrix<T>>>& R) {
127 if (lambda.empty()) throw InputError("cache_gamma: no user streams");
128 const std::size_t u = lambda.size();
129 const std::size_t n = lambda[0].rows();
130 if (lambda[0].cols() == 0) throw InputError("cache_gamma: empty lambda");
131 const std::size_t h = lambda[0].cols() - 1;
132 if (R.size() != u) throw InputError("cache_gamma: R and lambda disagree on the user count");
133 for (std::size_t v = 0; v < u; ++v)
134 if (R[v].size() != n)
135 throw InputError("cache_gamma: R and lambda disagree on the item count");
136
137 const T zero = num_traits<T>::from_int(0);
139 res.u = u;
140 res.n = n;
141 res.h = h;
142 res.gamma = Matrix<T>(n, h, zero);
143
144 for (std::size_t i = 0; i < n; ++i) {
145 const Matrix<T>& graph = R[0][i];
146 for (std::size_t j = 0; j < h; ++j) {
147 const std::vector<std::size_t> Pj = detail::cache_bfs_path(graph, 0, j);
148 if (Pj.empty()) continue; // unreachable list: no access factor
149 T g = zero;
150 for (std::size_t v = 0; v < u; ++v) g += lambda[v](i, 0);
151 for (std::size_t li = 1; li < Pj.size(); ++li) {
152 const std::size_t a = Pj[li - 1];
153 const std::size_t b = Pj[li];
154 T y = zero;
155 for (std::size_t v = 0; v < u; ++v) y += lambda[v](i, a) * R[v][i](a, b);
156 g *= y;
157 }
158 res.gamma(i, j) = g;
159 }
160 }
161 return res;
162}
163
164} // namespace cache
165} // namespace line
166
167#endif // LINE_API_CACHE_GAMMA_H
InputError(const std::string &what)
Definition error.h:39
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Dense matrix and non-owning view.
CacheGammaGraphResult< T > cache_gamma(const std::vector< Matrix< T > > &lambda, const std::vector< std::vector< Matrix< T > > > &R)
Access factors of a multi-list cache whose lists form a general access GRAPH.
Number-type abstraction for the templated API port.
Return value of cache_gamma, mirroring the JAR's Ret.cacheGamma.
Definition cache_gamma.h:68
std::size_t h
number of lists
Definition cache_gamma.h:72
Matrix< T > gamma
(n x h) access factors
Definition cache_gamma.h:69
std::size_t u
number of user streams
Definition cache_gamma.h:70
std::size_t n
number of items
Definition cache_gamma.h:71