LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
cache_sojourn.h
Go to the documentation of this file.
1/**
2 * @file cache_sojourn.h
3 * @brief Grid-independent sojourn-weighted average of a cache mean-field transient.
4 *
5 * Twin of MATLAB `cache_sojourn_ode.m`, JAR `CacheSojourn` and Python
6 * `api/cache/sojourn.py`. For a drift dx/dt = f(x) and a phase-type clock with
7 * row phase vector phi, dphi/dt = phi A, density g(t) = phi(t) c, it returns
8 *
9 * xbar = int_{t0}^{t1} x g dt / int_{t0}^{t1} g dt, wtot = int_{t0}^{t1} g dt,
10 *
11 * by augmenting the state with phi, int g x and int g, so the integrator itself
12 * carries the integral. The value is accurate to the ODE tolerance and does NOT
13 * depend on any output grid: a Riemann sum over the grid did, and that is what
14 * made the adaptive (MATLAB, C++) and fixed-grid (JAR, Python) ENV cache mean
15 * fields differ in the third digit.
16 *
17 * Copyright (c) 2012-2026, Imperial College London
18 * All rights reserved.
19 */
20
21#ifndef LINE_API_CACHE_CACHE_SOJOURN_H
22#define LINE_API_CACHE_CACHE_SOJOURN_H
23
24#include <cstddef>
25#include <vector>
26
27#include "line/num/number.h"
28#include "line/util/matrix.h"
29#include "line/util/ode.h"
30
31namespace line {
32namespace cache {
33
34/** A phase-type clock: dphi/dt = phi A, phi(t0) = phi0, density phi c. Empty = no clock. */
35template <class T>
38 std::vector<T> phi0;
39 std::vector<T> c;
40 bool empty() const { return phi0.empty(); }
41};
42
43/** A sojourn average: `xbar` (empty when `wtot` is not positive), clock mass, per-user miss rate. */
44template <class T>
46 std::vector<T> xbar;
48 std::vector<T> MU; ///< per-user miss rate of xbar, filled by the policy
49 std::vector<T> tout; ///< the reference's tout: accepted time points of the integration
50 Matrix<T> xtraj; ///< the reference's xtraj: (n x nt) occupancy at tout
51 bool empty() const { return xbar.empty(); }
52};
53
54/**
55 * The holding-time clock of a random-environment stage whose holding time has
56 * sub-generator D0 and entry vector pie, in a drift time unit that is `lam` times
57 * real time: A = D0/lam, c = -D0 1/lam, phi0 = pie, so int_0^T g = F(T/lam).
58 */
59template <class T>
60CacheSojournClock<T> cache_sojourn_clock(const Matrix<T>& D0, const std::vector<T>& pie, double lam) {
62 const std::size_t nph = D0.rows();
63 const T L = num_traits<T>::from_double(lam);
64 clk.A = Matrix<T>(nph, nph, num_traits<T>::from_int(0));
65 clk.c.assign(nph, num_traits<T>::from_int(0));
66 clk.phi0 = pie;
67 for (std::size_t i = 0; i < nph; ++i) {
69 for (std::size_t j = 0; j < nph; ++j) {
70 clk.A(i, j) = T(D0(i, j) / L);
71 rs = T(rs + D0(i, j));
72 }
73 clk.c[i] = T(-rs / L);
74 }
75 return clk;
76}
77
78/** Integrates dx/dt = f(t, x) over [t0, t1] from x0 together with `clk`; see the file comment. */
79template <class T, class F>
80CacheSojournResult<T> cache_sojourn_ode(const F& f, const T& t0, const T& t1,
81 const std::vector<T>& x0, const CacheSojournClock<T>& clk) {
82 const std::size_t n = x0.size(), nph = clk.phi0.size();
83 const std::size_t dim = 2 * n + nph + 1;
84 const T zero = num_traits<T>::from_int(0);
85 const auto rhs = [&](const T& t, const std::vector<T>& z) {
86 std::vector<T> x(z.begin(), z.begin() + static_cast<std::ptrdiff_t>(n));
87 const std::vector<T> dx = f(t, x);
88 std::vector<T> dz(dim, zero);
89 for (std::size_t i = 0; i < n; ++i) dz[i] = dx[i];
90 T g = zero;
91 for (std::size_t j = 0; j < nph; ++j) {
92 T s = zero;
93 for (std::size_t i = 0; i < nph; ++i) s = T(s + T(z[n + i] * clk.A(i, j)));
94 dz[n + j] = s;
95 g = T(g + T(z[n + j] * clk.c[j]));
96 }
97 for (std::size_t i = 0; i < n; ++i) dz[n + nph + i] = T(g * x[i]);
98 dz[dim - 1] = g;
99 return dz;
100 };
101 std::vector<T> z0(dim, zero);
102 for (std::size_t i = 0; i < n; ++i) z0[i] = x0[i];
103 for (std::size_t j = 0; j < nph; ++j) z0[n + j] = clk.phi0[j];
105 opt.rtol = num_traits<T>::from_double(1e-8);
106 opt.atol = num_traits<T>::from_double(1e-10);
107 opt.store_trajectory = true;
108 const OdeSolution<T> sol = ode_rosenbrock4(rhs, t0, t1, z0, opt);
109 const std::vector<T> z1 = sol.final_state();
111 r.wtot = z1[dim - 1];
112 if (num_traits<T>::to_double(r.wtot) > 0.0) {
113 r.xbar.assign(n, zero);
114 for (std::size_t i = 0; i < n; ++i) r.xbar[i] = T(z1[n + nph + i] / r.wtot);
115 }
116 // the reference's [tout, xtraj]: xtraj = Z(:, 1:n)', one column per time point
117 r.tout = sol.t;
118 r.xtraj = Matrix<T>(n, sol.y.size(), zero);
119 for (std::size_t k = 0; k < sol.y.size(); ++k)
120 for (std::size_t i = 0; i < n; ++i) r.xtraj(i, k) = sol.y[k][i];
121 return r;
122}
123
124/**
125 * The reference's clock-less form, cache_sojourn_ode(drift, tspan, x0): a plain
126 * integration of dx/dt = f(t, x) over [t0, t1] from x0 with ode15s's default
127 * tolerances as the reference passes them (RelTol 1e-8, AbsTol 1e-10). Only
128 * `tout` and `xtraj` are filled; `xbar` stays empty and `wtot` zero, which is
129 * the reference's `sj = []`.
130 */
131template <class T, class F>
132CacheSojournResult<T> cache_sojourn_ode(const F& f, const T& t0, const T& t1,
133 const std::vector<T>& x0) {
134 const std::size_t n = x0.size();
135 const T zero = num_traits<T>::from_int(0);
137 opt.rtol = num_traits<T>::from_double(1e-8);
138 opt.atol = num_traits<T>::from_double(1e-10);
139 opt.store_trajectory = true;
140 const OdeSolution<T> sol = ode_rosenbrock4(f, t0, t1, x0, opt);
142 r.tout = sol.t;
143 r.xtraj = Matrix<T>(n, sol.y.size(), zero);
144 for (std::size_t k = 0; k < sol.y.size(); ++k)
145 for (std::size_t i = 0; i < n; ++i) r.xtraj(i, k) = sol.y[k][i];
146 return r;
147}
148
149} // namespace cache
150} // namespace line
151
152#endif // LINE_API_CACHE_CACHE_SOJOURN_H
std::size_t rows() const
Definition matrix.h:89
Dense matrix and non-owning view.
CacheSojournClock< T > cache_sojourn_clock(const Matrix< T > &D0, const std::vector< T > &pie, double lam)
The holding-time clock of a random-environment stage whose holding time has sub-generator D0 and entr...
CacheSojournResult< T > cache_sojourn_ode(const F &f, const T &t0, const T &t1, const std::vector< T > &x0, const CacheSojournClock< T > &clk)
Integrates dx/dt = f(t, x) over [t0, t1] from x0 together with clk; see the file comment.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
OdeSolution< T > ode_rosenbrock4(const F &f, const J &jac, const T &t0, const T &t1, const std::vector< T > &y0, const OdeOptions< T > &opt)
Integrate y' = f(t,y) from t0 to t1 with an analytic Jacobian.
Definition ode.h:303
Number-type abstraction for the templated API port.
Adaptive stiff ODE integrator: a four-stage Rosenbrock method of order four with an embedded order-th...
Integration controls.
Definition ode.h:117
Result of an integration.
Definition ode.h:142
std::vector< std::vector< T > > y
y[i] is the state at t[i]
Definition ode.h:144
std::vector< T > t
accepted time points, t[0] = t0
Definition ode.h:143
const std::vector< T > & final_state() const
Definition ode.h:150
A phase-type clock: dphi/dt = phi A, phi(t0) = phi0, density phi c.
A sojourn average: xbar (empty when wtot is not positive), clock mass, per-user miss rate.
std::vector< T > MU
per-user miss rate of xbar, filled by the policy
Matrix< T > xtraj
the reference's xtraj: (n x nt) occupancy at tout
std::vector< T > tout
the reference's tout: accepted time points of the integration