LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
retrieval_fpi_latency.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_FPI_LATENCY_H
6#define LINE_API_RETRIEVAL_RETRIEVAL_FPI_LATENCY_H
7
8/**
9 * @file
10 * @ingroup api_retrieval
11 * FPI-based approximation of the delayed-hit count and the expected latency of
12 * a list-based cache with a phase-type retrieval system.
13 *
14 * Templated port of matlab/src/api/retrieval/retrieval_fpi_latency.m,
15 * cross-checked against
16 * jar/src/main/java/jline/api/retrieval/Retrieval_fpi_latency.java.
17 *
18 * From the fetch-period duration F_i of item i,
19 * d_i = phi_i lambda_i E0[F_i^2] / (2 E0[F_i]),
20 * with the Palm moments of a reduced absorbing CTMC of item i's visits to the
21 * retrieval stations, E0[F_i^k] = k! pi_e (-D0)^{-k} e. The steps are
22 * 1. retrieval_fpi on the whole system -> phi_i, pi_{i,0}
23 * 2. retrieval_fpi without item i -> phitilde_s, the PS occupancy
24 * 3. one PH block per station, shared stations slowed by 1/(1+phitilde_s),
25 * routed by R, absorbing on return to the cache
26 * 4. the two moments, hence d_i
27 * 5. Z = sum_i (phi_i + d_i) / sum_i lambda_i (phi_i + pi_{i,0})
28 *
29 * Routing convention, as in MATLAB: index 0 is the outside (entry on a miss,
30 * return to the cache on completion) and indices 1..S are the retrieval
31 * stations, R[i](a,b) being the probability of a -> b for item i.
32 *
33 * Station types: IS fetches are independent; PS and LCFSPR are symmetric
34 * insensitive disciplines and admit general phase-type, class-dependent
35 * service; SIRO and FCFS reduce to the same single-exponential sojourn only
36 * with exponential service at a class-independent rate, and are rejected
37 * otherwise. Any other discipline is rejected outright, matching MATLAB.
38 *
39 * ARITHMETIC: it calls retrieval_fpi, a tolerance-stopped successive
40 * substitution, so it is gated on has_transcendental for the same reason. The
41 * linear algebra around it (the visit-ratio solve and the two moment solves)
42 * would itself be exact, but the phitilde it is fed is not.
43 */
44
45#include <cmath>
46#include <cstddef>
47#include <vector>
48
50#include "line/num/number.h"
51#include "line/util/error.h"
52#include "line/util/linalg.h"
53#include "line/util/lu.h"
54#include "line/util/matrix.h"
55
56namespace line {
57namespace retrieval {
58
59/** Scheduling of a retrieval station, mirroring the MATLAB station_type strings. */
60enum class RetrievalStationType { IS, PS, SIRO, FCFS, LCFSPR };
61
62/** True for the disciplines that carry the mean-field sharing slowdown. */
66
67/** Mirrors the [Z, d, phi, pi0] return list of the MATLAB function. */
68template <class T>
70 T Z; ///< expected latency of the delayed-hit system
71 std::vector<T> d; ///< (n) mean delayed hits awaiting fetch, per item
72 std::vector<T> phi; ///< (n) delayed-hit ratio phi_i
73 std::vector<T> pi0; ///< (n) miss ratio pi_{i,0}
74};
75
76/**
77 * Phase-type service of every item at one retrieval station.
78 *
79 * MATLAB carries this as alpha{s}(1,:,i) and T{s}(:,:,i); here alpha is one
80 * (n x f) matrix whose row i is the entry vector of item i, and sub is one
81 * (f x f) subgenerator per item.
82 */
83template <class T>
85 Matrix<T> alpha; ///< (n x f) entry vectors, row per item
86 std::vector<Matrix<T>> sub; ///< (n) subgenerators, (f x f) each
88
89 std::size_t phases() const { return alpha.cols(); }
90};
91
92namespace detail {
93
94/** Mean phase-type service time -alpha inv(Tm) 1. */
95template <class T>
96T ph_mean(const Matrix<T>& alpha_row, const Matrix<T>& Tm) {
97 const std::size_t f = Tm.rows();
98 std::vector<T> e(f, num_traits<T>::from_int(1));
99 Matrix<T> LU = Tm;
100 const std::vector<std::size_t> piv = lu_factor(LU);
101 lu_solve(LU, piv, e); // e <- inv(Tm) 1
102 T tau = num_traits<T>::from_int(0);
103 for (std::size_t k = 0; k < f; ++k) tau += alpha_row(0, k) * e[k];
104 return -tau;
105}
106
107} // namespace detail
108
109/**
110 * @brief FPI-based approximation of the delayed-hit count and the expected
111 * latency of a list-based cache with a phase-type retrieval system.
112 *
113 * @param m (h) cache list capacities
114 * @param lambda (n) per-item arrival rates
115 * @param gamma (n x h) access factors
116 * @param station (S) phase-type service and discipline of each retrieval station
117 * @param R (n) routing matrices, each (S+1) x (S+1), index 0 = outside
118 * @param options fixed-point options (tolerance, iteration cap, damping)
119 */
120template <class T>
122 const std::vector<T>& lambda,
123 const Matrix<T>& gamma,
124 const std::vector<RetrievalStationPH<T>>& station,
125 const std::vector<Matrix<T>>& R,
126 const FpiOptions& options = FpiOptions()) {
128 "retrieval_fpi_latency requires transcendental arithmetic: it is driven by "
129 "retrieval_fpi, a successive substitution stopped on a relative tolerance");
130 const std::size_t n = lambda.size();
131 const std::size_t S = station.size();
132 if (S == 0) throw InputError("retrieval_fpi_latency: no retrieval station");
133 if (gamma.rows() != n)
134 throw InputError("retrieval_fpi_latency: gamma and lambda disagree on the item count");
135 if (R.size() != n)
136 throw InputError("retrieval_fpi_latency: one routing matrix per item is required");
137 for (std::size_t i = 0; i < n; ++i)
138 if (R[i].rows() != S + 1 || R[i].cols() != S + 1)
139 throw InputError("retrieval_fpi_latency: routing matrices must be (S+1) x (S+1)");
140
141 std::vector<std::size_t> fsz(S);
142 for (std::size_t s = 0; s < S; ++s) {
143 fsz[s] = station[s].phases();
144 if (station[s].sub.size() != n)
145 throw InputError("retrieval_fpi_latency: one subgenerator per item is required");
146 if (station[s].alpha.rows() != n)
147 throw InputError("retrieval_fpi_latency: alpha must have one row per item");
148 for (std::size_t i = 0; i < n; ++i)
149 if (station[s].sub[i].rows() != fsz[s] || station[s].sub[i].cols() != fsz[s])
150 throw InputError("retrieval_fpi_latency: subgenerator size disagrees with alpha");
151 // SIRO/FCFS PS-collapse rationale: see _kb/03-api-layer.md (cpp port notes: retrieval)
152 if ((station[s].type == RetrievalStationType::SIRO ||
153 station[s].type == RetrievalStationType::FCFS) &&
154 fsz[s] > 1)
155 throw UnsupportedError(
156 "retrieval_fpi_latency: SIRO/FCFS retrieval stations require exponential "
157 "(single-phase) service");
158 }
159
160 // per-item mean service times at each station
162 for (std::size_t s = 0; s < S; ++s)
163 for (std::size_t i = 0; i < n; ++i) {
164 Matrix<T> arow(1, fsz[s]);
165 for (std::size_t k = 0; k < fsz[s]; ++k) arow(0, k) = station[s].alpha(i, k);
166 tau(i, s) = detail::ph_mean(arow, station[s].sub[i]);
167 }
168
169 // ... and the class-independence requirement at SIRO/FCFS stations
170 for (std::size_t s = 0; s < S; ++s) {
171 if (station[s].type != RetrievalStationType::SIRO &&
172 station[s].type != RetrievalStationType::FCFS)
173 continue;
174 double lo = 0.0, hi = 0.0;
175 for (std::size_t i = 0; i < n; ++i) {
176 const double x = num_traits<T>::to_double(tau(i, s));
177 if (i == 0 || x < lo) lo = x;
178 if (i == 0 || x > hi) hi = x;
179 }
180 if (hi - lo > 1e-9 * hi)
181 throw UnsupportedError(
182 "retrieval_fpi_latency: SIRO/FCFS retrieval stations require class-independent "
183 "mean service rates");
184 }
185
186 // IS stations aggregate into column 0 of eta; each shared station gets its own column.
187 std::vector<std::size_t> is_idx, ps_idx;
188 for (std::size_t s = 0; s < S; ++s) {
189 if (station[s].type == RetrievalStationType::IS)
190 is_idx.push_back(s);
191 else
192 ps_idx.push_back(s);
193 }
194 const std::size_t r = ps_idx.size();
195
196 // eta_{s,i} = (visits per fetch) * (mean service time)
197 Matrix<T> eta(n, r + 1, num_traits<T>::from_int(0));
198 for (std::size_t i = 0; i < n; ++i) {
199 Matrix<T> ImP(S, S);
200 for (std::size_t a = 0; a < S; ++a)
201 for (std::size_t b = 0; b < S; ++b)
202 ImP(a, b) = (a == b ? num_traits<T>::from_int(1) : num_traits<T>::from_int(0)) -
203 R[i](a + 1, b + 1);
204 const Matrix<T> Vinv = inverse(ImP);
205 std::vector<T> visits(S, num_traits<T>::from_int(0));
206 for (std::size_t b = 0; b < S; ++b)
207 for (std::size_t a = 0; a < S; ++a) visits[b] += R[i](0, a + 1) * Vinv(a, b);
208
209 T is_sum = num_traits<T>::from_int(0);
210 for (std::size_t s : is_idx) is_sum += visits[s] * tau(i, s);
211 eta(i, 0) = is_sum;
212 for (std::size_t p = 0; p < r; ++p) eta(i, 1 + p) = visits[ps_idx[p]] * tau(i, ps_idx[p]);
213 }
214
215 // step 1: FPI on the full system
216 const RetrievalFpiResult<T> full = retrieval_fpi(m, lambda, eta, gamma, options);
218 out.pi0 = full.pmiss;
219 out.phi.assign(n, num_traits<T>::from_int(0));
220 for (std::size_t i = 0; i < n; ++i)
221 for (std::size_t s = 0; s <= r; ++s) out.phi[i] += full.pdh(s, i);
222
223 out.d.assign(n, num_traits<T>::from_int(0));
224 const std::size_t Phi = [&]() {
225 std::size_t t = 0;
226 for (std::size_t s = 0; s < S; ++s) t += fsz[s];
227 return t;
228 }();
229 std::vector<std::size_t> off(S, 0);
230 for (std::size_t s = 1; s < S; ++s) off[s] = off[s - 1] + fsz[s - 1];
231
232 for (std::size_t i = 0; i < n; ++i) {
233 // step 2: FPI without item i, giving the occupancy left by the others
234 std::vector<T> lambda_i;
235 lambda_i.reserve(n - 1);
236 Matrix<T> eta_i(n - 1, r + 1);
237 Matrix<T> gamma_i(n - 1, gamma.cols());
238 std::size_t q = 0;
239 for (std::size_t k = 0; k < n; ++k) {
240 if (k == i) continue;
241 lambda_i.push_back(lambda[k]);
242 for (std::size_t c = 0; c <= r; ++c) eta_i(q, c) = eta(k, c);
243 for (std::size_t c = 0; c < gamma.cols(); ++c) gamma_i(q, c) = gamma(k, c);
244 ++q;
245 }
246 std::vector<T> phitilde(S, num_traits<T>::from_int(0));
247 if (n > 1) {
248 const RetrievalFpiResult<T> without =
249 retrieval_fpi(m, lambda_i, eta_i, gamma_i, options);
250 for (std::size_t p = 0; p < r; ++p) {
251 T acc = num_traits<T>::from_int(0);
252 for (std::size_t k = 0; k + 1 < n; ++k) acc += without.pdh(1 + p, k);
253 phitilde[ps_idx[p]] = acc;
254 }
255 }
256
257 // step 3: the reduced absorbing CTMC of item i's fetch
258 Matrix<T> D0(Phi, Phi, num_traits<T>::from_int(0));
259 std::vector<T> pe(Phi, num_traits<T>::from_int(0));
260 for (std::size_t s = 0; s < S; ++s) {
261 const T scale = is_shared_station(station[s].type)
263 (num_traits<T>::from_int(1) + phitilde[s])
265 Matrix<T> blk(fsz[s], fsz[s]);
266 for (std::size_t a = 0; a < fsz[s]; ++a)
267 for (std::size_t b = 0; b < fsz[s]; ++b) blk(a, b) = scale * station[s].sub[i](a, b);
268 for (std::size_t a = 0; a < fsz[s]; ++a)
269 for (std::size_t b = 0; b < fsz[s]; ++b) D0(off[s] + a, off[s] + b) += blk(a, b);
270 // completion rates out of each phase of station s
271 std::vector<T> compl_(fsz[s], num_traits<T>::from_int(0));
272 for (std::size_t a = 0; a < fsz[s]; ++a) {
273 T acc = num_traits<T>::from_int(0);
274 for (std::size_t b = 0; b < fsz[s]; ++b) acc += blk(a, b);
275 compl_[a] = -acc;
276 }
277 for (std::size_t sp = 0; sp < S; ++sp)
278 for (std::size_t a = 0; a < fsz[s]; ++a)
279 for (std::size_t b = 0; b < fsz[sp]; ++b)
280 D0(off[s] + a, off[sp] + b) +=
281 compl_[a] * R[i](s + 1, sp + 1) * station[sp].alpha(i, b);
282 for (std::size_t a = 0; a < fsz[s]; ++a)
283 pe[off[s] + a] = R[i](0, s + 1) * station[s].alpha(i, a);
284 }
285
286 // step 4: the two Palm moments, hence d_i
287 Matrix<T> A(Phi, Phi);
288 for (std::size_t a = 0; a < Phi; ++a)
289 for (std::size_t b = 0; b < Phi; ++b) A(a, b) = -D0(a, b);
290 Matrix<T> LU = A;
291 const std::vector<std::size_t> piv = lu_factor(LU);
292 std::vector<T> y(Phi, num_traits<T>::from_int(1));
293 lu_solve(LU, piv, y); // y = inv(A) 1
294 std::vector<T> y2 = y;
295 lu_solve(LU, piv, y2); // y2 = inv(A) y
297 for (std::size_t a = 0; a < Phi; ++a) {
298 M1 += pe[a] * y[a];
299 M2 += pe[a] * y2[a];
300 }
302 if (M1 == num_traits<T>::from_int(0))
303 throw NumericError("retrieval_fpi_latency: zero mean fetch period");
304 out.d[i] = out.phi[i] * lambda[i] * M2 / (num_traits<T>::from_int(2) * M1);
305 }
306
307 // step 5: the expected latency
309 for (std::size_t i = 0; i < n; ++i) {
310 num += out.phi[i] + out.d[i];
311 den += lambda[i] * (out.phi[i] + out.pi0[i]);
312 }
313 if (den == num_traits<T>::from_int(0))
314 throw NumericError("retrieval_fpi_latency: no fetching traffic, the latency is undefined");
315 out.Z = num / den;
316 return out;
317}
318
319} // namespace retrieval
320} // namespace line
321
322#endif // LINE_API_RETRIEVAL_RETRIEVAL_FPI_LATENCY_H
InputError(const std::string &what)
Definition error.h:39
std::size_t size() const
Definition matrix.h:91
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 linear algebra over the templated number type: products, identity, inverse, and powers.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
RetrievalStationType
Scheduling of a retrieval station, mirroring the MATLAB station_type strings.
bool is_shared_station(RetrievalStationType t)
True for the disciplines that carry the mean-field sharing slowdown.
RetrievalFpiLatencyResult< T > retrieval_fpi_latency(const std::vector< int > &m, const std::vector< T > &lambda, const Matrix< T > &gamma, const std::vector< RetrievalStationPH< T > > &station, const std::vector< Matrix< T > > &R, const FpiOptions &options=FpiOptions())
FPI-based approximation of the delayed-hit count and the expected latency of a list-based cache with ...
RetrievalFpiResult< T > retrieval_fpi(const std::vector< int > &m, const std::vector< T > &lambda, const Matrix< T > &eta, const Matrix< T > &gamma, const FpiOptions &options=FpiOptions())
Fixed-point heuristic for a delayed-hit (list-based) cache.
void lu_solve(const Matrix< T > &LU, const std::vector< std::size_t > &piv, std::vector< T > &b)
Solve LUx = Pb in place on b, using the factors from lu_factor.
Definition lu.h:94
std::vector< std::size_t > lu_factor(Matrix< T > &A)
In-place LU of A (n x n).
Definition lu.h:48
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Definition linalg.h:72
Number-type abstraction for the templated API port.
Fixed-point heuristic for a delayed-hit (list-based) cache.
Options mirroring the trailing (max_iter, tol) arguments of the MATLAB function.
Mirrors the [Z, d, phi, pi0] return list of the MATLAB function.
std::vector< T > pi0
(n) miss ratio pi_{i,0}
std::vector< T > d
(n) mean delayed hits awaiting fetch, per item
T Z
expected latency of the delayed-hit system
std::vector< T > phi
(n) delayed-hit ratio phi_i
Mirrors the [pmiss, phit, pdh] return list, plus the iteration diagnostics.
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}
Phase-type service of every item at one retrieval station.
std::vector< Matrix< T > > sub
(n) subgenerators, (f x f) each
Matrix< T > alpha
(n x f) entry vectors, row per item