LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
da_cacheqn.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_H
6#define LINE_API_DA_DA_CACHEQN_H
7
8/**
9 * @file
10 * @ingroup api_da
11 * Decomposition-aggregation driver for integrated cache-queueing models, a port
12 * of matlab/src/api/da/da_cacheqn.m.
13 *
14 * Alternates between (i) solving each cache in isolation given the current
15 * per-class arrival rates and (ii) solving the surrounding queueing network with
16 * every cache replaced by a class switch that routes according to the current
17 * hit/miss probabilities, until the cache arrival rates reach a fixed point
18 * (da_fpi under the 1-norm). The cache node is RELABELLED to a ClassSwitch but
19 * stays stateful, so it remains a retained node of the stochastic complement --
20 * exactly as the reference does. The per-sweep routing rewrite goes straight
21 * into `rtnodes`, after which `da_recompute_visits_from_rtnodes` rebuilds `rt`
22 * and the visits with the chains held fixed.
23 *
24 * ARITHMETIC: the miss solve (cache_mva / cache_miss_fpi) and the network solve
25 * decide the achievable T; the driver itself is field arithmetic. The tolerance
26 * loop is inexact by construction whatever the backend.
27 */
28
29#include <cstddef>
30#include <limits>
31#include <functional>
32#include <vector>
33
39#include "line/api/da/da_fpi.h"
42#include "line/util/matrix.h"
43
44namespace line {
45namespace da {
46
47/** The converged isolated-cache inputs, kept for per-item occupancy reporting. */
48template <class T>
50 std::vector<Matrix<T> > gamma; ///< per cache: (n x h)
51 std::vector<std::vector<int> > itemcap; ///< per cache
52 std::vector<std::vector<Matrix<T> > > lambda_cache;///< per cache: (u) of n x (h+1)
53 std::vector<std::vector<std::vector<Matrix<T> > > > Rcost;
54 std::vector<lang::ReplacementStrategy> strat;
55};
56
57template <class T>
60 Matrix<T> hitprob; ///< (ncaches x nclasses)
61 Matrix<T> missprob; ///< (ncaches x nclasses)
62 int iter = 0;
64};
65
66/**
67 * Per-item occupancy of every cache from the CONVERGED access factors.
68 *
69 * This is the EMBEDDED (per-request) law: the stationary law of the cache-content
70 * chain seen at request instants, which coincides with the time-stationary one
71 * only under PASTA. SolverCTMC reports the time-weighted counterpart instead.
72 *
73 * RR/FIFO past 10 items is NaN, not the fixed point's marginals: the exact
74 * recursion is what makes these a distribution, and an approximation of it is
75 * not one. SolverMVA and SolverNC read the same law off their own fixed points,
76 * so it is derived here rather than in either analyzer.
77 *
78 * @return one (n x h+1) matrix per cache, column 0 the miss probability; an
79 * EMPTY matrix where the cache carries no access factors
80 */
81template <class T>
82std::vector<Matrix<T> > da_cacheqn_itemprob(const CacheqnInfo<T>& info) {
83 std::vector<Matrix<T> > out(info.gamma.size(), Matrix<T>());
84 for (std::size_t ci = 0; ci < info.gamma.size(); ++ci) {
85 if (info.gamma[ci].rows() == 0) continue;
86 const std::size_t ni = info.gamma[ci].rows();
87 const std::size_t hi = info.itemcap[ci].size();
88 if (info.strat[ci] == lang::ReplacementStrategy::LRU) {
89 std::vector<T> mT(hi);
90 for (std::size_t l = 0; l < hi; ++l)
91 mT[l] = num_traits<T>::from_int(info.itemcap[ci][l]);
92 out[ci] = cache::cache_ttl_lrua(
93 info.lambda_cache[ci][0], info.Rcost[ci][0], mT,
95 } else if (ni > 10) {
96 out[ci] = Matrix<T>(ni, hi + 1,
98 std::numeric_limits<double>::quiet_NaN()));
99 } else {
100 out[ci] = cache::cache_prob_erec(info.gamma[ci], info.itemcap[ci]);
101 }
102 }
103 return out;
104}
105
106/**
107 * @brief Decomposition-aggregation driver for integrated cache-queueing
108 * models, a port of matlab/src/api/da/da_cacheqn.m.
109 *
110 * @param sn the model struct, taken by value and mutated (caches relabelled)
111 * @param exact true selects the exact isolated-cache miss, false the
112 * approximation; consulted only when `missfun` is empty
113 * @param opt iteration controls (iter_max, tol)
114 * @param netfun solves the surrounding queueing network on the mutated struct;
115 * its MvaSolution.X is read as the per-class system throughput
116 * @param missfun THE ISOLATED-CACHE MISS ALGORITHM, as the reference's `missfun`
117 * handle. Empty selects the MVA pair (`cache_mva` /
118 * `cache_miss_fpi`), which is what SolverMVA passes; SolverNC
119 * supplies `cache_prob_erec` / `cache_miss_spm` instead. The
120 * reference has always taken a handle here -- an earlier version
121 * of this port collapsed it to `exact`, which hardcoded one
122 * solver's pair into a driver both solvers share.
123 *
124 * THE FOURTH ARGUMENT IS THE CACHE ITSELF, as the reference's
125 * `missfun(gamma, m, lambda_cache, ch)` passes it. Without it a
126 * handle cannot see the replacement strategy or the access graph
127 * of the cache it is being asked about, so a model with two
128 * caches under different policies would be solved by whichever
129 * one the handle was written for. SolverFLD reads exactly those
130 * two fields; SolverMVA and SolverNC ignore the argument.
131 */
132template <class T>
134 const std::function<mva::MvaSolution<T>(const qn::NetworkStruct<T>&)>&
135 netfun,
136 const std::function<std::vector<T>(const Matrix<T>&,
137 const std::vector<int>&,
138 const std::vector<Matrix<T> >&,
139 const qn::CacheParam<T>&)>&
140 missfun = nullptr) {
141 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
142 const std::size_t I = sn.nodes.size(), K = sn.nclasses;
143
144 std::vector<std::size_t> caches; // 0-based node indices
145 for (std::size_t nd = 0; nd < I; ++nd)
146 if (sn.nodes[nd].nodetype == qn::NodeType::Cache) caches.push_back(nd);
147 const std::size_t ncaches = caches.size();
148
149 // connmatrix from the ORIGINAL node routing, before the cache rows are
150 // rewritten: node ind connects to jnd if any class routes ind -> jnd.
151 std::vector<std::vector<bool> > conn(I, std::vector<bool>(I, false));
152 for (std::size_t a = 0; a < I; ++a)
153 for (std::size_t b = 0; b < I; ++b) {
154 if (a == b) continue; // the cache's read self-switch is not a downstream
155 for (std::size_t r = 0; r < K && !conn[a][b]; ++r)
156 for (std::size_t s = 0; s < K && !conn[a][b]; ++s)
157 if (sn.rtnodes(a * K + r, b * K + s) > zero) conn[a][b] = true;
158 }
159
160 CacheqnResult<T> outr;
161 outr.hitprob = Matrix<T>(ncaches, K, zero);
162 outr.missprob = Matrix<T>(ncaches, K, zero);
163 outr.info.gamma.resize(ncaches);
164 outr.info.itemcap.resize(ncaches);
165 outr.info.lambda_cache.resize(ncaches);
166 outr.info.Rcost.resize(ncaches);
167 outr.info.strat.resize(ncaches);
168
169 // seed cache arrival rates and relabel the caches as class switches
170 std::vector<T> lambda0(K, zero);
171 for (std::size_t ci = 0; ci < ncaches; ++ci) {
172 const qn::CacheParam<T>& ch = sn.nodeparam.at(caches[ci] + 1);
173 for (std::size_t r = 0; r < ch.hitclass.size(); ++r)
174 if (ch.hitclass[r] != 0)
175 lambda0[r] = num_traits<T>::from_double(0.5); // deterministic seed (no rand)
176 sn.nodes[caches[ci]].nodetype = qn::NodeType::ClassSwitch;
177 }
178
179 da::FpiOptions fopt;
180 fopt.iter_max = opt.iter_max;
181 fopt.iter_tol = opt.tol;
182 // The reference stops on the 1-norm of the increment; this da_fpi uses the
183 // max-norm. Both converge to the same fixed point (the point is what the
184 // metrics read), so only the iteration count can differ, which is not
185 // compared.
186
187 mva::MvaSolution<T> lastres;
188 auto sweep = [&](const std::vector<T>& x,
189 std::size_t) -> std::pair<std::vector<T>, std::vector<T> > {
190 std::vector<T> lambda = x;
191 for (std::size_t ci = 0; ci < ncaches; ++ci) {
192 const std::size_t ind = caches[ci];
193 const qn::CacheParam<T>& ch = sn.nodeparam.at(ind + 1);
194
196 dch.itemcap = ch.itemcap;
197 dch.nitems = ch.nitems;
198 dch.pread = ch.pread;
199 dch.accost = ch.accost;
200 const da::CacheIsolateResult<T> iso = da::da_cache_isolate(dch, lambda);
201 outr.info.gamma[ci] = iso.gamma;
202 outr.info.itemcap[ci] = ch.itemcap;
203 outr.info.lambda_cache[ci] = iso.lambda_cache;
204 outr.info.Rcost[ci] = iso.Rcost;
205 outr.info.strat[ci] = ch.replacestrat;
206
207 // per-class miss rate of the isolated cache
208 std::vector<T> missrate(K, zero);
209 if (missfun) {
210 const std::vector<T> mr = missfun(iso.gamma, ch.itemcap, iso.lambda_cache, ch);
211 for (std::size_t v = 0; v < mr.size() && v < K; ++v) missrate[v] = mr[v];
212 } else if (exact) {
214 const std::size_t n = cm.pij.rows(), h = cm.pij.cols();
215 std::vector<T> pmiss(n, zero);
216 for (std::size_t k = 0; k < n; ++k) {
217 T s = zero;
218 for (std::size_t l = 0; l < h; ++l) s = T(s + cm.pij(k, l));
219 pmiss[k] = T(one - s);
220 }
221 for (std::size_t v = 0; v < iso.lambda_cache.size() && v < K; ++v) {
222 T acc = zero;
223 for (std::size_t k = 0; k < n; ++k)
224 acc = T(acc + iso.lambda_cache[v](k, 0) * pmiss[k]);
225 missrate[v] = acc;
226 }
227 } else {
228 // cache_miss_fpi wants lambda as the (u x n) first-list slice.
229 const std::size_t u = iso.lambda_cache.size();
230 const std::size_t n = ch.nitems;
231 Matrix<T> lam_un(u, n, zero);
232 for (std::size_t v = 0; v < u; ++v)
233 for (std::size_t k = 0; k < n; ++k) lam_un(v, k) = iso.lambda_cache[v](k, 0);
235 cache::cache_miss_fpi(iso.gamma, ch.itemcap, lam_un);
236 for (std::size_t v = 0; v < mr.MU.size() && v < K; ++v) missrate[v] = mr.MU[v];
237 }
238
239 for (std::size_t r = 0; r < K; ++r) {
240 if (lambda[r] > zero) {
241 outr.missprob(ci, r) = T(missrate[r] / lambda[r]);
242 outr.hitprob(ci, r) = T(one - outr.missprob(ci, r));
243 } else {
244 outr.missprob(ci, r) = zero;
245 outr.hitprob(ci, r) = zero;
246 }
247 }
248
249 // rewrite the cache-as-classswitch routing into rtnodes: send the
250 // hit/miss mass to EVERY connected node, exactly as the reference. The
251 // row over-sums when there is more than one downstream node; the
252 // reference does not normalise it, and the surplus is absorbed by the
253 // pass-through classes (which each queue routes onward to its own
254 // successors) at zero residence.
255 for (std::size_t r = 0; r < ch.hitclass.size(); ++r) {
256 if (ch.hitclass[r] == 0) continue; // class r does not read this cache
257 const std::size_t hc = ch.hitclass[r] - 1, mc = ch.missclass[r] - 1;
258 for (std::size_t col = 0; col < I * K; ++col) sn.rtnodes(ind * K + r, col) = zero;
259 for (std::size_t jnd = 0; jnd < I; ++jnd) {
260 if (!conn[ind][jnd]) continue;
261 sn.rtnodes(ind * K + r, jnd * K + hc) = outr.hitprob(ci, r);
262 sn.rtnodes(ind * K + r, jnd * K + mc) = outr.missprob(ci, r);
263 }
264 }
265 }
266 // The cache switch forces classes onto nodes that never carried them in
267 // the base model, where they have no onward routing. The reference's
268 // refresh routes every class at a node to that node's physical
269 // successors (same class); reproduce it for the classes left unrouted, so
270 // a pass-through class exits instead of dead-ending (which would zero its
271 // throughput and unbalance the served classes).
272 for (std::size_t i = 0; i < I; ++i) {
273 if (sn.nodes[i].nodetype == qn::NodeType::Cache ||
274 sn.nodes[i].nodetype == qn::NodeType::ClassSwitch)
275 continue; // caches (relabelled) carry their explicit switch row
276 std::size_t nsucc = 0;
277 for (std::size_t j = 0; j < I; ++j)
278 if (conn[i][j]) ++nsucc;
279 if (nsucc == 0) continue; // a Sink has no successors
280 const T share = T(one / num_traits<T>::from_int(long(nsucc)));
281 for (std::size_t s = 0; s < K; ++s) {
282 T rowsum = zero;
283 for (std::size_t col = 0; col < I * K; ++col) rowsum = T(rowsum + sn.rtnodes(i * K + s, col));
284 if (rowsum > zero) continue; // already routed
285 for (std::size_t j = 0; j < I; ++j)
286 if (conn[i][j]) sn.rtnodes(i * K + s, j * K + s) = share;
287 }
288 }
289 sn.da_recompute_visits_from_rtnodes();
290
291 lastres = netfun(sn);
292
293 // node visits summed over chains
294 Matrix<T> nv(I, K, zero);
295 for (std::size_t c = 0; c < sn.nchains; ++c)
296 for (std::size_t a = 0; a < I; ++a)
297 for (std::size_t r = 0; r < K; ++r) nv(a, r) = T(nv(a, r) + sn.nodevisits[c](a, r));
298
299 // update cache arrival rates from the network throughputs
300 for (std::size_t ci = 0; ci < ncaches; ++ci) {
301 const std::size_t ind = caches[ci];
302 const qn::CacheParam<T>& ch = sn.nodeparam.at(ind + 1);
303 for (std::size_t r = 0; r < ch.hitclass.size(); ++r) {
304 if (ch.hitclass[r] == 0) continue;
305 // chain of class r
306 std::size_t c = sn.nchains;
307 for (std::size_t cc = 0; cc < sn.nchains; ++cc)
308 if (sn.chains[cc][r]) { c = cc; break; }
309 if (c == sn.nchains) continue;
310 T xsum = zero;
311 for (std::size_t k : sn.inchain[c]) xsum = T(xsum + lastres.X[k - 1]);
312 const std::size_t rstat = sn.classes[r].refstat; // 1-based station
313 const std::size_t refnode = sn.station_to_node[rstat - 1] - 1; // 0-based
314 const std::size_t refcls =
315 (sn.refclass[c] > 0) ? sn.refclass[c] - 1 : r; // 0-based
316 const T denom = nv(refnode, refcls);
317 if (denom > zero) lambda[r] = T(xsum * nv(ind, r) / denom);
318 }
319 }
320 return std::make_pair(lambda, x);
321 };
322
323 const da::FpiResult<T> fp = da::da_fpi<T>(sweep, lambda0, fopt);
324 outr.iter = static_cast<int>(fp.iterations);
325 outr.res = lastres;
326 return outr;
327}
328
329} // namespace da
330} // namespace line
331
332#endif // LINE_API_DA_DA_CACHEQN_H
Cache miss rates from the fixed-point multipliers.
Exact mean value analysis of a multi-list cache.
Exact per-item hit and miss probabilities of a multi-list cache.
TTL (characteristic-time) approximation of an LRU cache whose lists form an arbitrary access graph.
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.
Dense matrix and non-owning view.
The option and result types every MVA analyzer shares.
CacheMvaResult< T > cache_mva(const Matrix< T > &gamma, const std::vector< int > &m)
Exact mean value analysis of a multi-list cache.
Definition cache_mva.h:66
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.
Matrix< T > cache_prob_erec(const Matrix< T > &gamma, const std::vector< int > &m, const std::vector< int > &sigma, const std::vector< int > &k)
Per-item hit and miss probabilities under per-list storage cost caps, pi_ij = m_j gamma(i,...
Matrix< T > cache_ttl_lrua(const Matrix< T > &lambda, const std::vector< Matrix< T > > &R, const std::vector< T > &m, const T &tol, unsigned maxswp=200)
TTL (characteristic-time) approximation of an LRU cache whose lists form an arbitrary access graph.
CacheqnResult< T > da_cacheqn(qn::NetworkStruct< T > sn, bool exact, const mva::MvaOptions &opt, const std::function< mva::MvaSolution< T >(const qn::NetworkStruct< T > &)> &netfun, const std::function< std::vector< T >(const Matrix< T > &, const std::vector< int > &, const std::vector< Matrix< T > > &, const qn::CacheParam< T > &)> &missfun=nullptr)
Decomposition-aggregation driver for integrated cache-queueing models, a port of matlab/src/api/da/da...
Definition da_cacheqn.h:133
std::vector< Matrix< T > > da_cacheqn_itemprob(const CacheqnInfo< T > &info)
Per-item occupancy of every cache from the CONVERGED access factors.
Definition da_cacheqn.h:82
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.
@ LRU
least recently used
Definition lang_types.h:382
A queueing network and its refreshed NetworkStruct.
Return value of cache_miss, mirroring [M,MU,MI,pi0].
Definition cache_miss.h:49
std::vector< T > MU
(u) per-user miss rate; empty when no lambda given
Definition cache_miss.h:51
Return value of cache_mva, mirroring [pi,pi0,pij,x,u,E].
Definition cache_mva.h:50
Matrix< T > pij
(n x h) probability that item k sits in list l
Definition cache_mva.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
std::vector< std::vector< Matrix< T > > > Rcost
(u x n) of (h+1)x(h+1)
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
The converged isolated-cache inputs, kept for per-item occupancy reporting.
Definition da_cacheqn.h:49
std::vector< lang::ReplacementStrategy > strat
Definition da_cacheqn.h:54
std::vector< std::vector< std::vector< Matrix< T > > > > Rcost
Definition da_cacheqn.h:53
std::vector< Matrix< T > > gamma
per cache: (n x h)
Definition da_cacheqn.h:50
std::vector< std::vector< int > > itemcap
per cache
Definition da_cacheqn.h:51
std::vector< std::vector< Matrix< T > > > lambda_cache
per cache: (u) of n x (h+1)
Definition da_cacheqn.h:52
Matrix< T > missprob
(ncaches x nclasses)
Definition da_cacheqn.h:61
Matrix< T > hitprob
(ncaches x nclasses)
Definition da_cacheqn.h:60
CacheqnInfo< T > info
Definition da_cacheqn.h:63
mva::MvaSolution< T > res
Definition da_cacheqn.h:59
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
static constexpr double FineTol
Definition lang_types.h:668
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::vector< T > X
Definition mva_types.h:98
std::vector< std::vector< Matrix< T > > > accost
(u) x (n) of (h+1)x(h+1), or empty
std::vector< int > itemcap
std::vector< std::size_t > missclass
std::vector< std::size_t > hitclass
lang::ReplacementStrategy replacestrat
std::vector< std::vector< T > > pread
(u) x (n), empty row = NaN