LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
cache_xi_iter.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_CACHE_XI_ITER_H
6#define LINE_API_CACHE_XI_ITER_H
7
8/**
9 * @file
10 * @ingroup api_cache
11 * Lagrange multipliers of a multi-list cache by the Gast-Van Houdt iteration.
12 *
13 * Templated port of matlab/src/api/cache/cache_xi_iter.m, cross-checked
14 * against jar/src/main/java/jline/api/cache/Cache_xi_iter.java (and its
15 * verbatim duplicate Cache_xi_bvh.java).
16 *
17 * Writing pp(0,k) = 1 and pp(l,k) = gamma(k,l-1), the stationary occupancy of
18 * list l under the asymptotic independence approximation is
19 *
20 * F_l(z_l) = sum_k z_l pp(l,k) / (n z_l pp(l,k) + a_l(k)),
21 * a_l(k) = n sum_{s != l} z_s pp(s,k),
22 *
23 * which is increasing in z_l, so the capacity constraint F_l(z_l) = m(l)/n has
24 * a unique root. The outer loop is a Gauss-Seidel sweep over the lists, each
25 * inner solve a bracketed bisection (the bracket [0,1] when the unit point
26 * already overshoots, otherwise doubled from 1 until it does), refined by a
27 * fixed 50 halvings. Convergence of the sweep is declared when the multipliers
28 * move by less than 1e-12 relative.
29 *
30 * ARITHMETIC: only field operations, but the answer is defined by two nested
31 * tolerances -- a fixed 50-step bisection and a 1e-12 sweep test -- so it is
32 * inexact by construction and is gated on transcendental arithmetic. Note in
33 * particular that the 50 halvings cap the achievable accuracy at about 1e-15
34 * of the bracket regardless of the precision of T.
35 *
36 * REFERENCE DEFECT (MATLAB): the third argument is declared as `tmax` and is
37 * assigned Inf when absent, but is never read in the body, so it has no
38 * effect. cache_spm passes its own `xi0` warm start into that slot, meaning
39 * the warm start silently does nothing. The argument is not offered here.
40 */
41
42#include <cstddef>
43#include <vector>
44
45#include "line/num/number.h"
46#include "line/util/error.h"
47#include "line/util/matrix.h"
48
49namespace line {
50namespace cache {
51
52namespace detail {
53
54/** sum_k z pp(l,k) / (n z pp(l,k) + a(k)), the occupancy of list l at z. */
55template <class T>
56T xi_iter_occupancy(const std::vector<T>& ppl, const std::vector<T>& a, const T& z, long n) {
57 const T zero = num_traits<T>::from_int(0);
58 const T nT = num_traits<T>::from_int(n);
59 T s = zero;
60 for (std::size_t k = 0; k < ppl.size(); ++k) {
61 const T den = nT * z * ppl[k] + a[k];
62 if (den == zero) throw NumericError("cache_xi_iter: singular occupancy denominator");
63 s += z * ppl[k] / den;
64 }
65 return s;
66}
67
68} // namespace detail
69
70/**
71 * @brief Lagrange multipliers of a multi-list cache by the Gast-Van Houdt
72 * iteration.
73 *
74 * @param gamma (n x h) access factors
75 * @param m (h) list capacities
76 * @return (h) multipliers xi
77 */
78template <class T>
79std::vector<T> cache_xi_iter(const Matrix<T>& gamma, const std::vector<int>& m) {
81 "cache_xi_iter requires transcendental arithmetic");
82 const std::size_t n = gamma.rows();
83 const std::size_t h = m.size();
84 if (gamma.cols() != h)
85 throw InputError("cache_xi_iter: gamma and m disagree on the number of lists");
86 if (n == 0) throw InputError("cache_xi_iter: no items");
87
88 const T zero = num_traits<T>::from_int(0);
89 const T one = num_traits<T>::from_int(1);
90 const T two = num_traits<T>::from_int(2);
91 const T nT = num_traits<T>::from_int(static_cast<long>(n));
92
93 std::vector<T> f(h, zero);
94 for (std::size_t l = 0; l < h; ++l) f[l] = num_traits<T>::from_int(static_cast<long>(m[l])) / nT;
95
96 // pp: (h+1) x n, row 0 all ones, row l+1 the l-th column of gamma.
97 Matrix<T> pp(h + 1, n, one);
98 for (std::size_t l = 0; l < h; ++l)
99 for (std::size_t k = 0; k < n; ++k) pp(l + 1, k) = gamma(k, l);
100
101 std::vector<T> z(h + 1, one), zold(h + 1, zero);
102 const T reltol = num_traits<T>::from_double(1e-12);
103
104 for (int sweep = 0;; ++sweep) {
105 T dmax = zero, omax = zero;
106 for (std::size_t l = 0; l <= h; ++l) {
107 const T d = num_abs(T(z[l] - zold[l]));
108 if (d > dmax) dmax = d;
109 const T o = num_abs(zold[l]);
110 if (o > omax) omax = o;
111 }
112 if (!(dmax > reltol * omax)) break;
113 if (sweep > 10000)
114 throw NumericError("cache_xi_iter: the Gauss-Seidel sweep did not converge");
115 zold = z;
116
117 // temp(k) = n sum_s z(s) pp(s,k)
118 std::vector<T> temp(n, zero);
119 for (std::size_t k = 0; k < n; ++k) {
120 T s = zero;
121 for (std::size_t l = 0; l <= h; ++l) s += z[l] * pp(l, k);
122 temp[k] = nT * s;
123 }
124
125 for (std::size_t l = 0; l < h; ++l) {
126 std::vector<T> ppl(n), a(n);
127 for (std::size_t k = 0; k < n; ++k) {
128 ppl[k] = pp(l + 1, k);
129 a[k] = temp[k] - nT * z[l + 1] * ppl[k];
130 }
131
132 const T Fi = detail::xi_iter_occupancy(ppl, a, one, static_cast<long>(n));
133 T zmin, zmax;
134 if (Fi > f[l]) {
135 zmin = zero;
136 zmax = one;
137 } else {
138 zmin = one;
139 zmax = two;
140 while (detail::xi_iter_occupancy(ppl, a, zmax, static_cast<long>(n)) < f[l]) {
141 zmin = zmax;
142 zmax = zmax * two;
143 }
144 }
145 for (int b = 0; b < 50; ++b) {
146 const T mid = (zmin + zmax) / two;
147 z[l + 1] = mid;
148 if (detail::xi_iter_occupancy(ppl, a, mid, static_cast<long>(n)) < f[l])
149 zmin = mid;
150 else
151 zmax = mid;
152 }
153 }
154 }
155
156 return std::vector<T>(z.begin() + 1, z.end());
157}
158
159} // namespace cache
160} // namespace line
161
162#endif // LINE_API_CACHE_XI_ITER_H
InputError(const std::string &what)
Definition error.h:39
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
The exception types the port throws.
Dense matrix and non-owning view.
std::vector< T > cache_xi_iter(const Matrix< T > &gamma, const std::vector< int > &m)
Lagrange multipliers of a multi-list cache by the Gast-Van Houdt iteration.
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.