LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
retrieval_fpi.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_H
6#define LINE_API_RETRIEVAL_RETRIEVAL_FPI_H
7
8/**
9 * @file
10 * @ingroup api_retrieval
11 * Fixed-point heuristic for a delayed-hit (list-based) cache.
12 *
13 * Templated port of matlab/src/api/retrieval/retrieval_fpi.m, cross-checked
14 * against jar/src/main/java/jline/api/retrieval/Retrieval_fpi.java.
15 *
16 * The exact recursion of retrieval_metrics costs O(2^n); the heuristic
17 * truncates the perturbation expansion at zeroth order,
18 * pi_{i,l}(m-1_j) ~ pi_{i,l}(m), and solves the resulting nonlinear system by
19 * successive substitution. One sweep is
20 *
21 * F_{s,i} = 1 + sum_{k!=i} phi_{s,k}
22 * D_i = 1 + lambda_i eta_{0,i} + sum_s lambda_i eta_{s,i} F_{s,i}
23 * theta_ij = gamma_ij / D_i
24 * xi_j = m_j / sum_k theta_kj (1 - sum_l pi_kl)
25 * pi_ij = theta_ij xi_j / (1 + sum_l theta_il xi_l)
26 * pi_i0 = (1 - sum_j pi_ij) / D_i
27 * phi_{0,i} = lambda_i eta_{0,i} pi_i0, phi_{s,i} = lambda_i eta_{s,i} F_{s,i} pi_i0
28 *
29 * stopping when the largest relative change of the miss, hit and delayed-hit
30 * ratios falls below tol. Note that xi and the "1 - sum_l pi_kl" factor read
31 * the PREVIOUS sweep's pi, exactly as MATLAB and the JAR do, so the port is
32 * iterate-for-iterate identical to them and not merely fixed-point identical.
33 *
34 * ARITHMETIC: the iteration stops on a tolerance, so its answer is the fixed
35 * point only to within tol whatever the arithmetic; it is gated on
36 * has_transcendental so nobody instantiates it at exact arithmetic expecting
37 * an exact result. Use retrieval_metrics or retrieval_mva for that.
38 */
39
40#include <cmath>
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/** Options mirroring the trailing (max_iter, tol) arguments of the MATLAB function. */
52struct FpiOptions {
53 std::size_t max_iter = 1000;
54 double tol = 1e-6;
55};
56
57/** Mirrors the [pmiss, phit, pdh] return list, plus the iteration diagnostics. */
58template <class T>
60 std::vector<T> pmiss; ///< (n) miss ratios pi_{i,0}
61 Matrix<T> phit; ///< (h x n) hit ratios pi_{i,j}
62 Matrix<T> pdh; ///< ((r+1) x n) delayed-hit probabilities phi_{s,i}, s = 0..r
63 std::size_t iterations = 0;
64 bool converged = false;
65 /// True when the iterate left the finite range; MATLAB warns and breaks here.
66 bool diverged = false;
67};
68
69namespace detail {
70
71/** MATLAB's local reldiff: max|a-b| over max|b|, with an all-zero b read as 1. */
72template <class T>
73double fpi_reldiff(const std::vector<T>& a, const std::vector<T>& b) {
74 double num = 0.0, den = 0.0;
75 for (std::size_t i = 0; i < a.size(); ++i) {
76 const double d = num_traits<T>::to_double(num_abs(T(a[i] - b[i])));
77 const double e = num_traits<T>::to_double(num_abs(T(b[i])));
78 if (d > num) num = d;
79 if (e > den) den = e;
80 }
81 if (den == 0.0) den = 1.0;
82 return num / den;
83}
84
85} // namespace detail
86
87/**
88 * @brief Fixed-point heuristic for a delayed-hit (list-based) cache.
89 *
90 * @param m (h) cache list capacities
91 * @param lambda (n) per-item arrival rates
92 * @param eta (n x (r+1)) fetching demands, column 0 = IS station, columns 1..r = PS
93 * @param gamma (n x h) access factors
94 * @param options fixed-point options (tolerance, iteration cap, damping)
95 */
96template <class T>
97RetrievalFpiResult<T> retrieval_fpi(const std::vector<int>& m, const std::vector<T>& lambda,
98 const Matrix<T>& eta, const Matrix<T>& gamma,
99 const FpiOptions& options = FpiOptions()) {
101 "retrieval_fpi requires transcendental arithmetic: it is a successive "
102 "substitution stopped on a relative tolerance, so its answer is the fixed "
103 "point only to within tol whatever the arithmetic");
104 const std::size_t n = lambda.size();
105 const std::size_t h = m.size();
106 if (eta.rows() != n || gamma.rows() != n)
107 throw InputError("retrieval_fpi: eta/gamma and lambda disagree on the item count");
108 if (gamma.cols() != h)
109 throw InputError("retrieval_fpi: gamma and m disagree on the number of lists");
110 if (eta.cols() == 0) throw InputError("retrieval_fpi: eta has no columns");
111 const std::size_t r = eta.cols() - 1;
112
113 const T one = num_traits<T>::from_int(1);
114
115 // initial guess of the paper: the h+1 cache states and the r+2 retrieval
116 // states are each given the same mass.
117 const T init_phi = one / num_traits<T>::from_int(static_cast<long>((h + 1) * (r + 2)));
118 const T init_pij = one / num_traits<T>::from_int(static_cast<long>(h + 1));
119
120 // phi is stored (r+1) x n and pij is stored h x n, as MATLAB returns them.
121 Matrix<T> phi(r + 1, n, init_phi);
122 Matrix<T> pij(h, n, init_pij);
123 std::vector<T> pi0(n, init_phi);
124
126 for (std::size_t t = 1; t <= options.max_iter; ++t) {
127 out.iterations = t;
128
129 // F(s,i) = 1 + sum_{k != i} phi_{s,k}
130 Matrix<T> F(r, n, one);
131 for (std::size_t s = 0; s < r; ++s) {
132 T tot = num_traits<T>::from_int(0);
133 for (std::size_t i = 0; i < n; ++i) tot += phi(s + 1, i);
134 for (std::size_t i = 0; i < n; ++i) F(s, i) = one + (tot - phi(s + 1, i));
135 }
136
137 std::vector<T> D(n);
138 for (std::size_t i = 0; i < n; ++i) {
139 D[i] = one + lambda[i] * eta(i, 0);
140 for (std::size_t s = 0; s < r; ++s) D[i] += lambda[i] * eta(i, s + 1) * F(s, i);
141 }
142
143 Matrix<T> theta(n, h);
144 for (std::size_t i = 0; i < n; ++i)
145 for (std::size_t j = 0; j < h; ++j) theta(i, j) = gamma(i, j) / D[i];
146
147 // 1 - sum_l pi_kl, read off the previous sweep as MATLAB does
148 std::vector<T> oneminus(n);
149 for (std::size_t k = 0; k < n; ++k) {
151 for (std::size_t j = 0; j < h; ++j) s += pij(j, k);
152 oneminus[k] = one - s;
153 }
154
155 std::vector<T> xi(h);
156 for (std::size_t j = 0; j < h; ++j) {
157 T den = num_traits<T>::from_int(0);
158 for (std::size_t k = 0; k < n; ++k) den += theta(k, j) * oneminus[k];
159 xi[j] = num_traits<T>::from_int(static_cast<long>(m[j])) / den;
160 }
161
162 Matrix<T> pij_new(h, n);
163 std::vector<T> pi0_new(n);
164 Matrix<T> phi_new(r + 1, n);
165 for (std::size_t i = 0; i < n; ++i) {
166 T denom = one;
167 for (std::size_t l = 0; l < h; ++l) denom += theta(i, l) * xi[l];
168 T sum_pij = num_traits<T>::from_int(0);
169 for (std::size_t j = 0; j < h; ++j) {
170 pij_new(j, i) = theta(i, j) * xi[j] / denom;
171 sum_pij += pij_new(j, i);
172 }
173 pi0_new[i] = (one - sum_pij) / D[i];
174 phi_new(0, i) = lambda[i] * eta(i, 0) * pi0_new[i];
175 for (std::size_t s = 0; s < r; ++s)
176 phi_new(s + 1, i) = lambda[i] * eta(i, s + 1) * F(s, i) * pi0_new[i];
177 }
178
179 const std::vector<T> pij_flat(pij_new.data(), pij_new.data() + pij_new.size());
180 const std::vector<T> pij_old(pij.data(), pij.data() + pij.size());
181 const std::vector<T> phi_flat(phi_new.data(), phi_new.data() + phi_new.size());
182 const std::vector<T> phi_old(phi.data(), phi.data() + phi.size());
183 double delta = detail::fpi_reldiff(pi0_new, pi0);
184 const double d2 = detail::fpi_reldiff(pij_flat, pij_old);
185 const double d3 = detail::fpi_reldiff(phi_flat, phi_old);
186 if (d2 > delta) delta = d2;
187 if (d3 > delta) delta = d3;
188
189 pij = pij_new;
190 pi0 = pi0_new;
191 phi = phi_new;
192
193 if (!std::isfinite(delta)) {
194 out.diverged = true;
195 break;
196 }
197 if (delta < options.tol) {
198 out.converged = true;
199 break;
200 }
201 }
202
203 out.pmiss = pi0;
204 out.phit = pij;
205 out.pdh = phi;
206 return out;
207}
208
209} // namespace retrieval
210} // namespace line
211
212#endif // LINE_API_RETRIEVAL_RETRIEVAL_FPI_H
InputError(const std::string &what)
Definition error.h:39
std::size_t size() const
Definition matrix.h:91
T * data()
Definition matrix.h:94
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Dense matrix and non-owning view.
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.
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
Options mirroring the trailing (max_iter, tol) arguments of the MATLAB function.
Mirrors the [pmiss, phit, pdh] return list, plus the iteration diagnostics.
bool diverged
True when the iterate left the finite range; MATLAB warns and breaks here.
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}