LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
da_cacheqn_retrieval.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_DA_DA_CACHEQN_RETRIEVAL_H
6#define LINE_API_DA_DA_CACHEQN_RETRIEVAL_H
7
8/**
9 * @file
10 * @ingroup api_da
11 * Decomposition-aggregation driver for a CLOSED integrated cache-queueing model
12 * whose Cache carries a delayed-hit retrieval system.
13 *
14 * Templated port of `matlab/src/api/da/da_cacheqn_retrieval.m`.
15 *
16 * THE FIXED POINT. The read rate reaching the cache depends on the network's
17 * throughput, which depends on how the cache splits that read rate between hits
18 * and fetches. One sweep therefore: solves the ISOLATED cache at the current
19 * read rate, rewrites the routing so the cache behaves as a class switch with
20 * that split, solves the network, and reads the new read rate back off the
21 * converged visits. `da_fpi` drives it under the 1-norm.
22 *
23 * WHAT MAKES THIS DIFFERENT FROM `da_cacheqn`, and it is not the fixed point.
24 * A miss here does not leave the cache: it becomes a per-item RETRIEVAL CLASS
25 * that must be FETCHED through a backend station and returned. Two consequences
26 * are built into the driver rather than into the caller:
27 *
28 * - THE FETCH STATION IS GIVEN A LOAD-DEPENDENT RATE, and the shape of it is
29 * the coupon-collector correction `alpha(k) = k / (n_eff (1 - (1-1/n_eff)^k))`
30 * with `n_eff = nitems - total capacity`. With k fetches in flight, the
31 * number of DISTINCT items among them is below k, because two misses may
32 * want the same item and one fetch serves both. alpha is the ratio, so the
33 * station is faster than k independent fetches would be. That is what makes
34 * the model load dependent, and it is why the caller's network solver must
35 * handle `lldscaling`.
36 * - THE CACHE BECOMES A ClassSwitch for the network solve, its routing rewritten
37 * each sweep: the read class goes to the hit class with probability `hp`, and
38 * to item i's retrieval class at the fetch node with probability
39 * `pread(i) pi0(i)`; the retrieval class returns to the miss class.
40 *
41 * THE MISS ALGORITHM IS FIXED HERE, deliberately, unlike `da_cacheqn` whose
42 * `missfun` is a handle. BOTH callers -- SolverMVA and SolverNC -- use
43 * `cache_miss_fpi`, and both report `method = 'fpi'` on this path. Nothing
44 * varies between them except the NETWORK solver, so `netfun` is the only handle
45 * and adding a `missfun` would be speculative generality. Checked against both
46 * call sites before fixing the type.
47 *
48 * DELAYED HITS FOLD INTO MISS on this path: `delayedprob` is returned as zero
49 * and the reported hit is P(item cached). The open analyzer
50 * (`solver_nc_retrieval_analyzer`) is the one that separates the three.
51 */
52
53#include <cmath>
54#include <cstddef>
55#include <functional>
56#include <utility>
57#include <vector>
58
61#include "line/api/da/da_fpi.h"
63#include "line/num/number.h"
65#include "line/util/error.h"
66#include "line/util/matrix.h"
67
68namespace line {
69namespace da {
70
71/** What the delayed-hit decomposition returns. */
72template <class T>
75 std::vector<T> hitprob; ///< (K) P(item cached), read class only
76 std::vector<T> missprob; ///< (K)
77 std::vector<T> delayedprob; ///< (K) zero on this path; see the header
78 std::size_t iter = 0;
79 qn::NetworkStruct<T> sn; ///< the mutated struct, cache relabelled
80};
81
82/**
83 * Port of `da_cacheqn_retrieval`.
84 *
85 * @param sn the model struct, taken by value and MUTATED (the cache becomes
86 * a ClassSwitch and the fetch station gains `lldscaling`)
87 * @param netfun solves the surrounding queueing network on the mutated struct
88 * @param opt iteration controls
89 */
90template <class T>
93 const std::function<mva::MvaSolution<T>(const qn::NetworkStruct<T>&)>& netfun,
94 const mva::MvaOptions& opt) {
96 "da_cacheqn_retrieval requires transcendental arithmetic: it alternates two "
97 "tolerance-stopped solves");
98 const T zero = num_traits<T>::from_int(0);
99 const T one = num_traits<T>::from_int(1);
100 const std::size_t I = sn.nodes.size(), K = sn.nclasses;
101
102 std::vector<std::size_t> caches;
103 for (std::size_t nd = 0; nd < I; ++nd)
104 if (sn.nodes[nd].nodetype == qn::NodeType::Cache) caches.push_back(nd);
105 if (caches.size() != 1)
106 throw UnsupportedError("da_cacheqn_retrieval: requires exactly one Cache node");
107 const std::size_t ci = caches[0];
108 const qn::CacheParam<T> ch = sn.nodeparam.at(ci + 1);
109
110 if (ch.retrieval_queues.empty())
111 throw UnsupportedError("da_cacheqn_retrieval: the Cache carries no retrieval system");
112 const std::size_t readClass = ch.retrieval_queues.begin()->first + 1; // 1-based
113 const std::vector<std::size_t>& queueNodes = ch.retrieval_queues.begin()->second;
114 if (queueNodes.size() != 1)
115 throw UnsupportedError(
116 "da_cacheqn_retrieval: currently supports a single-station (single-backend) retrieval "
117 "system; this cache fetches through " + std::to_string(queueNodes.size()));
118 const std::size_t fetchNode = queueNodes[0]; // 1-based
119 const std::size_t fetchStation = sn.nodes[fetchNode - 1].station;
120 if (fetchStation == 0)
121 throw UnsupportedError("da_cacheqn_retrieval: the retrieval node is not a station");
122
123 const std::size_t nitems = ch.nitems;
124 long totcap = 0;
125 for (int c : ch.itemcap) totcap += c;
126 const long n_eff_l = std::max<long>(1, static_cast<long>(nitems) - totcap);
127 const double n_eff = static_cast<double>(n_eff_l);
128
129 double Npop_d = 0.0;
130 for (const qn::JobClass& c : sn.classes)
131 if (std::isfinite(c.population)) Npop_d += c.population;
132 const std::size_t Npop = static_cast<std::size_t>(std::llround(Npop_d));
133 if (Npop == 0)
134 throw UnsupportedError(
135 "da_cacheqn_retrieval: the closed delayed-hit decomposition needs a positive closed "
136 "population");
137
138 // THE COUPON-COLLECTOR CORRECTION. With k fetches in flight the number of
139 // DISTINCT items among them is n_eff (1 - (1-1/n_eff)^k), below k, because
140 // two misses may want the same item and one fetch serves both. alpha is the
141 // ratio, so the fetch station serves k in-flight misses faster than k
142 // independent fetches would.
143 std::vector<T> alpha(std::max<std::size_t>(1, Npop), one);
144 for (std::size_t k = 1; k <= Npop; ++k) {
145 const double d = n_eff * (1.0 - std::pow(1.0 - 1.0 / n_eff, static_cast<double>(k)));
146 alpha[k - 1] = num_traits<T>::from_double(static_cast<double>(k) / d);
147 }
148 for (std::size_t i = 0; i < sn.nstations; ++i) {
149 std::vector<T>& lld = sn.stations[i].lldscaling;
150 if (lld.size() < Npop) lld.resize(Npop, one);
151 }
152 sn.stations[fetchStation - 1].lldscaling = alpha;
153
154 // The cache is solved in isolation; for the NETWORK it is a class switch.
155 sn.nodes[ci].nodetype = qn::NodeType::ClassSwitch;
156
157 std::vector<T> pread = ch.pread.at(readClass - 1);
158 T psum = zero;
159 for (const T& v : pread) psum += v;
160 if (!(psum > zero))
161 throw UnsupportedError("da_cacheqn_retrieval: the read class has no item popularity");
162 for (T& v : pread) v = T(v / psum);
163
165 outr.hitprob.assign(K, zero);
166 outr.missprob.assign(K, zero);
167 outr.delayedprob.assign(K, zero);
168
169 da::FpiOptions fpopt;
170 fpopt.iter_max = static_cast<std::size_t>(opt.iter_max);
171 fpopt.iter_tol = opt.iter_tol;
172
173 std::vector<T> x0(K, zero);
174 x0[readClass - 1] = one; // seed
175
176 const auto sweep = [&](const std::vector<T>& x,
177 std::size_t) -> std::pair<std::vector<T>, std::vector<T> > {
178 std::vector<T> lambda = x;
179
180 // 1. the isolated cache at the current read rate. `da_cache_isolate`
181 // takes its own lightweight parameter struct, not the model's.
183 dch.itemcap = ch.itemcap;
184 dch.nitems = ch.nitems;
185 // setRetrievalSystem MINTS one retrieval class per item, so the class
186 // count grows past the length of `pread`. An EMPTY row is the
187 // documented marker for "this class does not read the cache", which is
188 // exactly what a retrieval class is, so the tail is padded rather than
189 // the rate vector truncated.
190 dch.pread = ch.pread;
191 dch.pread.resize(lambda.size());
192 dch.accost = ch.accost;
193 const da::CacheIsolateResult<T> iso = da::da_cache_isolate(dch, lambda);
194 const std::size_t u = iso.lambda_cache.size();
195 Matrix<T> lam_un(u, nitems, zero);
196 for (std::size_t v = 0; v < u; ++v)
197 for (std::size_t k = 0; k < nitems; ++k) lam_un(v, k) = iso.lambda_cache[v](k, 0);
199 cache::cache_miss_fpi(iso.gamma, ch.itemcap, lam_un);
200 std::vector<T> pi0(nitems, zero);
201 for (std::size_t k = 0; k < nitems && k < mf.pi0.size(); ++k) pi0[k] = mf.pi0[k];
202 T nonhit = zero;
203 for (std::size_t k = 0; k < nitems; ++k) nonhit += T(pread[k] * pi0[k]);
204 const T hp = T(one - nonhit);
205
206 // 2. rewrite the cache-as-classswitch routing: hit straight to the hit
207 // class, miss into item i's retrieval class at the fetch node, and
208 // the retrieval class back out as the miss class.
209 const std::size_t r = readClass - 1;
210 for (std::size_t col = 0; col < I * K; ++col) sn.rtnodes(ci * K + r, col) = zero;
211 const std::size_t hc = ch.hitclass.at(r), mc = ch.missclass.at(r);
212 if (hc == 0 || mc == 0)
213 throw UnsupportedError(
214 "da_cacheqn_retrieval: the read class has no hit or miss class");
215 sn.rtnodes(ci * K + r, ci * K + (hc - 1)) = hp;
216 for (std::size_t i = 0; i < nitems; ++i) {
217 const std::size_t rcls =
218 (i < ch.retrieval_classes.size() && r < ch.retrieval_classes[i].size())
219 ? ch.retrieval_classes[i][r]
220 : 0;
221 if (rcls == 0) continue;
222 sn.rtnodes(ci * K + r, (fetchNode - 1) * K + (rcls - 1)) = T(pread[i] * pi0[i]);
223 for (std::size_t col = 0; col < I * K; ++col)
224 sn.rtnodes((fetchNode - 1) * K + (rcls - 1), col) = zero;
225 sn.rtnodes((fetchNode - 1) * K + (rcls - 1), ci * K + (mc - 1)) = one;
226 // drop the unused Cache -> Retrieval edge
227 for (std::size_t col = 0; col < I * K; ++col)
228 sn.rtnodes(ci * K + (rcls - 1), col) = zero;
229 }
230 sn.da_recompute_visits_from_rtnodes();
231
232 // 3. the network solve, and the read rate read back off the visits
233 outr.res = netfun(sn);
234
235 Matrix<T> nv(I, K, zero);
236 for (std::size_t c = 0; c < sn.nchains; ++c)
237 for (std::size_t a = 0; a < I; ++a)
238 for (std::size_t k = 0; k < K; ++k)
239 nv(a, k) = T(nv(a, k) + sn.nodevisits[c](a, k));
240 std::size_t chain = sn.nchains;
241 for (std::size_t c = 0; c < sn.nchains; ++c)
242 if (sn.chains[c][r]) chain = c;
243 T denom = zero;
244 if (chain < sn.nchains) {
245 const std::size_t refnode = sn.node_of_station(sn.classes[r].refstat);
246 denom = sn.refclass[chain] > 0 ? nv(refnode - 1, sn.refclass[chain] - 1)
247 : nv(refnode - 1, r);
248 }
249 T Xr = zero;
250 if (chain < sn.nchains)
251 for (std::size_t k = 0; k < K; ++k)
252 if (sn.chains[chain][k] && k < outr.res.X.size()) Xr += outr.res.X[k];
253 if (denom > zero) lambda[r] = T(Xr * nv(ci, r) / denom);
254
255 outr.hitprob[r] = hp;
256 outr.missprob[r] = nonhit;
257 outr.delayedprob[r] = zero;
258 return std::make_pair(lambda, x);
259 };
260
261 const da::FpiResult<T> fr = da::da_fpi<T>(sweep, x0, fpopt);
262 outr.iter = fr.iterations;
263 outr.sn = sn;
264 return outr;
265}
266
267} // namespace da
268} // namespace line
269
270#endif // LINE_API_DA_DA_CACHEQN_RETRIEVAL_H
Cache miss rates from the fixed-point multipliers.
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
Isolated-cache input construction for the decomposition methods.
Damped fixed-point iteration, the shared driver of the decomposition algorithms.
The exception types the port throws.
Dense matrix and non-owning view.
The option and result types every MVA analyzer shares.
CacheMissResult< T > cache_miss_fpi(const Matrix< T > &gamma, const std::vector< int > &m, const Matrix< T > &lambda)
Cache miss rates from the fixed-point multipliers.
CacheqnRetrievalResult< T > da_cacheqn_retrieval(qn::NetworkStruct< T > sn, const std::function< mva::MvaSolution< T >(const qn::NetworkStruct< T > &)> &netfun, const mva::MvaOptions &opt)
Port of da_cacheqn_retrieval.
FpiResult< T > da_fpi(const std::function< std::pair< std::vector< T >, std::vector< T > >(const std::vector< T > &, std::size_t)> &iterfun, const std::vector< T > &x0, const FpiOptions &options=FpiOptions())
Damped fixed-point iteration, the shared driver of the decomposition algorithms.
Definition da_fpi.h:92
CacheIsolateResult< T > da_cache_isolate(const CacheParam< T > &ch, const std::vector< T > &lambda)
Isolated-cache input construction for the decomposition methods.
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Return value of cache_miss, mirroring [M,MU,MI,pi0].
Definition cache_miss.h:49
std::vector< T > pi0
(n) per-item miss probability; empty when no lambda given
Definition cache_miss.h:53
Return value of da_cache_isolate, mirroring [gamma,lambda_cache,Rcost].
std::vector< Matrix< T > > lambda_cache
(u) matrices of size n x (h+1)
Matrix< T > gamma
(n x h) access factors
The fields of sn.nodeparam{cache} that da_cache_isolate reads.
std::vector< int > itemcap
(h) list capacities
std::vector< std::vector< Matrix< T > > > accost
(u) x (n) of (h+1)x(h+1), or empty
std::vector< std::vector< T > > pread
(u) x (n), empty row = NaN
What the delayed-hit decomposition returns.
std::vector< T > hitprob
(K) P(item cached), read class only
std::vector< T > delayedprob
(K) zero on this path; see the header
qn::NetworkStruct< T > sn
the mutated struct, cache relabelled
Options mirroring the fields MATLAB reads off the options struct.
Definition da_fpi.h:50
std::size_t iter_max
Definition da_fpi.h:51
std::size_t iterations
Definition da_fpi.h:78
The options SolverMVA reads.
Definition mva_types.h:31
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
Definition mva_types.h:96
std::map< std::size_t, std::vector< std::size_t > > retrieval_queues
read class(0-based)->nodes
std::vector< std::vector< Matrix< T > > > accost
(u) x (n) of (h+1)x(h+1), or empty
std::vector< std::vector< std::size_t > > retrieval_classes
(nitems x nclasses), 1-based
std::vector< int > itemcap
std::vector< std::size_t > missclass
std::vector< std::size_t > hitclass
std::vector< std::vector< T > > pread
(u) x (n), empty row = NaN
One job class of the network.