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 (paper eqs. theta/xi/pi/pi_0/phi_FPI). 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 : solved JOINTLY from m_j = sum_i theta_ij xi_j / (1 + sum_l theta_il xi_l),
25 * i.e. cache_xi_fp(theta, m), the no-delay cache fixed point with gamma := theta
26 * A_i = 1 / (1 + sum_l theta_il xi_l)
27 * pi_ij = theta_ij xi_j A_i
28 * pi_i0 = A_i / D_i
29 * phi_{0,i} = A_i lambda_i eta_{0,i} / D_i, phi_{s,i} = A_i lambda_i eta_{s,i} F_{s,i} / D_i
30 *
31 * starting from phi = 0 and pi from the cache WITHOUT delayed hits
32 * (cache_xi_fp(gamma, m), pi_i0 = 1 - sum_j pi_ij), and stopping when the
33 * largest relative change of the miss, hit and delayed-hit ratios falls below
34 * tol. Every iterate is feasible, sum_i pi_ij = m_j. When r = 0 (no PS
35 * station) D does not depend on phi, so one sweep is the fixed point and the
36 * iteration stops after it.
37 *
38 * ARITHMETIC: the iteration stops on a tolerance, so its answer is the fixed
39 * point only to within tol whatever the arithmetic; it is gated on
40 * has_transcendental so nobody instantiates it at exact arithmetic expecting
41 * an exact result. Use retrieval_metrics or retrieval_mva for that.
42 */
43
44#include <cmath>
45#include <cstddef>
46#include <cstdio>
47#include <iostream>
48#include <string>
49#include <vector>
50
52#include "line/num/number.h"
53#include "line/util/error.h"
55#include "line/util/matrix.h"
56
57namespace line {
58namespace retrieval {
59
60/** Options mirroring the trailing (max_iter, tol) arguments of the MATLAB function. */
61struct FpiOptions {
62 std::size_t max_iter = 1000;
63 double tol = 1e-6;
64};
65
66/** Mirrors the [pmiss, phit, pdh] return list, plus the iteration diagnostics. */
67template <class T>
69 std::vector<T> pmiss; ///< (n) miss ratios pi_{i,0}
70 Matrix<T> phit; ///< (h x n) hit ratios pi_{i,j}
71 Matrix<T> pdh; ///< ((r+1) x n) delayed-hit probabilities phi_{s,i}, s = 0..r
72 std::size_t iterations = 0;
73 bool converged = false;
74 /// True when the iterate left the finite range; MATLAB warns and breaks here.
75 bool diverged = false;
76 /// The reference's line_warning text (divergence or iteration cap), empty when it did not warn.
77 std::string warning;
78};
79
80namespace detail {
81
82/** MATLAB's local reldiff: max|a-b| over max|b|, with an all-zero b read as 1. */
83template <class T>
84double fpi_reldiff(const std::vector<T>& a, const std::vector<T>& b) {
85 double num = 0.0, den = 0.0;
86 for (std::size_t i = 0; i < a.size(); ++i) {
87 const double d = num_traits<T>::to_double(num_abs(T(a[i] - b[i])));
88 const double e = num_traits<T>::to_double(num_abs(T(b[i])));
89 if (d > num) num = d;
90 if (e > den) den = e;
91 }
92 if (den == 0.0) den = 1.0;
93 return num / den;
94}
95
96} // namespace detail
97
98/**
99 * @brief Fixed-point heuristic for a delayed-hit (list-based) cache.
100 *
101 * @param m (h) cache list capacities
102 * @param lambda (n) per-item arrival rates
103 * @param eta (n x (r+1)) fetching demands, column 0 = IS station, columns 1..r = PS
104 * @param gamma (n x h) access factors
105 * @param options fixed-point options (tolerance, iteration cap, damping)
106 */
107template <class T>
108RetrievalFpiResult<T> retrieval_fpi(const std::vector<int>& m, const std::vector<T>& lambda,
109 const Matrix<T>& eta, const Matrix<T>& gamma,
110 const FpiOptions& options = FpiOptions()) {
112 "retrieval_fpi requires transcendental arithmetic: it is a successive "
113 "substitution stopped on a relative tolerance, so its answer is the fixed "
114 "point only to within tol whatever the arithmetic");
115 const std::size_t n = lambda.size();
116 const std::size_t h = m.size();
117 if (eta.rows() != n || gamma.rows() != n)
118 throw InputError("retrieval_fpi: eta/gamma and lambda disagree on the item count");
119 if (gamma.cols() != h)
120 throw InputError("retrieval_fpi: gamma and m disagree on the number of lists");
121 if (eta.cols() == 0) throw InputError("retrieval_fpi: eta has no columns");
122 const std::size_t r = eta.cols() - 1;
123
124 const T one = num_traits<T>::from_int(1);
125 const T zero = num_traits<T>::from_int(0);
126
127 // initial guess of the paper: phi = 0 and pi from the cache without delayed
128 // hits, so that sum_i pi_ij = m_j holds from the start.
129 // phi is stored (r+1) x n and pij is stored h x n, as MATLAB returns them.
130 Matrix<T> phi(r + 1, n, zero);
131 Matrix<T> pij(h, n, zero);
132 std::vector<T> pi0(n, one);
133 {
135 for (std::size_t i = 0; i < n; ++i) {
136 T sum_pij = zero;
137 for (std::size_t j = 0; j < h; ++j) {
138 pij(j, i) = c0.pij(i, j);
139 sum_pij += pij(j, i);
140 }
141 pi0[i] = one - sum_pij;
142 }
143 }
144
146 double last_delta = 0.0;
147 for (std::size_t t = 1; t <= options.max_iter; ++t) {
148 out.iterations = t;
149
150 // F(s,i) = 1 + sum_{k != i} phi_{s,k}
151 Matrix<T> F(r, n, one);
152 for (std::size_t s = 0; s < r; ++s) {
153 T tot = zero;
154 for (std::size_t i = 0; i < n; ++i) tot += phi(s + 1, i);
155 for (std::size_t i = 0; i < n; ++i) F(s, i) = one + (tot - phi(s + 1, i));
156 }
157
158 std::vector<T> D(n);
159 for (std::size_t i = 0; i < n; ++i) {
160 D[i] = one + lambda[i] * eta(i, 0);
161 for (std::size_t s = 0; s < r; ++s) D[i] += lambda[i] * eta(i, s + 1) * F(s, i);
162 }
163
164 Matrix<T> theta(n, h);
165 for (std::size_t i = 0; i < n; ++i)
166 for (std::size_t j = 0; j < h; ++j) theta(i, j) = gamma(i, j) / D[i];
167
168 // xi solves the capacity constraints jointly: the no-delay cache fixed point with gamma := theta
169 const std::vector<T> xi = cache::cache_xi_fp(theta, m).xi;
170
171 Matrix<T> pij_new(h, n);
172 std::vector<T> pi0_new(n);
173 Matrix<T> phi_new(r + 1, n);
174 for (std::size_t i = 0; i < n; ++i) {
175 T denom = one;
176 for (std::size_t l = 0; l < h; ++l) denom += theta(i, l) * xi[l];
177 const T A = one / denom;
178 for (std::size_t j = 0; j < h; ++j) pij_new(j, i) = theta(i, j) * xi[j] * A;
179 pi0_new[i] = A / D[i];
180 phi_new(0, i) = A * lambda[i] * eta(i, 0) / D[i];
181 for (std::size_t s = 0; s < r; ++s)
182 phi_new(s + 1, i) = A * lambda[i] * eta(i, s + 1) * F(s, i) / D[i];
183 }
184
185 const std::vector<T> pij_flat(pij_new.data(), pij_new.data() + pij_new.size());
186 const std::vector<T> pij_old(pij.data(), pij.data() + pij.size());
187 const std::vector<T> phi_flat(phi_new.data(), phi_new.data() + phi_new.size());
188 const std::vector<T> phi_old(phi.data(), phi.data() + phi.size());
189 double delta = detail::fpi_reldiff(pi0_new, pi0);
190 const double d2 = detail::fpi_reldiff(pij_flat, pij_old);
191 const double d3 = detail::fpi_reldiff(phi_flat, phi_old);
192 if (d2 > delta) delta = d2;
193 if (d3 > delta) delta = d3;
194
195 pij = pij_new;
196 pi0 = pi0_new;
197 phi = phi_new;
198 last_delta = delta;
199
200 if (!std::isfinite(delta)) {
201 out.diverged = true;
202 out.warning = "FPI diverged (non-finite iterate) at iteration " + std::to_string(t) + ".";
203 break;
204 }
205 // r == 0: D does not depend on phi, so one sweep is the fixed point
206 if (delta < options.tol || r == 0) {
207 out.converged = true;
208 break;
209 }
210 }
211
212 if (!out.converged && !out.diverged && options.max_iter > 0) {
213 char buf[64];
214 std::snprintf(buf, sizeof(buf), "%.2e", last_delta);
215 out.warning = "FPI did not converge within " + std::to_string(options.max_iter) +
216 " iterations (last rel. change " + buf + ").";
217 }
218 // line_warning(mfilename, ...) in the reference: printed unless the session is SILENT
220 std::cerr << "[LINE] Warning: retrieval_fpi: " << out.warning << std::endl;
221
222 out.pmiss = pi0;
223 out.phit = pij;
224 out.pdh = phi;
225 return out;
226}
227
228} // namespace retrieval
229} // namespace line
230
231#endif // LINE_API_RETRIEVAL_RETRIEVAL_FPI_H
Lagrange multipliers of a multi-list cache by fixed-point iteration.
InputError(const std::string &what)
Definition error.h:105
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
static VerboseLevel get_verbose()
The session verbosity.
The exception types the port throws.
Running progress log of a LINE solver run (the "solver console").
Dense matrix and non-owning view.
CacheXiFpResult< T > cache_xi_fp(const Matrix< T > &gamma, const std::vector< int > &m)
Lagrange multipliers of a multi-list cache by fixed-point iteration.
Definition cache_xi_fp.h:65
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.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
T num_abs(const T &v)
Definition number.h:184
Number-type abstraction for the templated API port.
Return value of cache_xi_fp, mirroring [xi,pi0,pij,it].
Definition cache_xi_fp.h:51
Matrix< T > pij
(n x h) per-item per-list hit probability
Definition cache_xi_fp.h:54
Options mirroring the trailing (max_iter, tol) arguments of the MATLAB function.
Mirrors the [pmiss, phit, pdh] return list, plus the iteration diagnostics.
std::string warning
The reference's line_warning text (divergence or iteration cap), empty when it did not warn.
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}