LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_ctmc_reward.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_SOLVERS_CTMC_SOLVER_CTMC_REWARD_H
6#define LINE_SOLVERS_CTMC_SOLVER_CTMC_REWARD_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Port of `solver_ctmc_reward.m` and the `@@SolverCTMC` reward surface
12 * (`runRewardAnalyzer`, `getAvgReward`, `getTranReward`).
13 *
14 * TWO DIFFERENT QUANTITIES SHARE THE WORD "REWARD", and conflating them is the
15 * trap this file exists to avoid:
16 *
17 * steady state E[r] = sum_s pi(s) r(s), a RATE -- the long-run average
18 * reward earned per unit time.
19 * value function V^k(s), the reward ACCUMULATED over k uniformized steps
20 * starting from s. It grows without bound in a recurrent chain,
21 * because it is a total and not an average.
22 *
23 * `V` is therefore not "the transient version of E[r]" and does not converge to
24 * it; its SLOPE does. The reference reports both and so does this port.
25 *
26 * UNIFORMIZATION IS WHAT MAKES THE VALUE ITERATION A CTMC ANSWER. The embedded
27 * chain P = Q/q + I with q = max|diag(Q)| has the same stationary law as Q and
28 * a uniform step of mean duration 1/q, so iteration index k maps to time k/q.
29 * Any q at least as large as the maximum exit rate is valid; taking the maximum
30 * is the tightest, hence the fastest-mixing, choice.
31 */
32
33#include <cmath>
34#include <cstddef>
35#include <string>
36#include <vector>
37
41#include "line/util/error.h"
42#include "line/util/matrix.h"
43
44namespace line {
45namespace ctmc {
46
47/** What the reward analyzer produces, per declared reward. */
48template <class T>
49struct CtmcReward {
50 std::vector<std::string> names;
51 std::vector<T> steady_state; ///< E[r] per reward
52 std::vector<Matrix<T>> V; ///< V[r] is (Tmax+1 x nstates)
53 std::vector<T> t; ///< iteration index / q
54 Matrix<T> state_space_aggr; ///< the rows the reward saw
56};
57
58/**
59 * Port of `solver_ctmc_reward.m`.
60 *
61 * @param tmax number of value-iteration steps; the reference's default is 1000
62 * @param sn the refreshed network struct, carrying the reward definitions
63 * @param opt CTMC options (state-space cutoff, tolerances, method)
64 */
65template <class T>
67 std::size_t tmax = 1000) {
68 if (sn.reward.empty())
69 throw InputError(
70 "solver_ctmc_reward: no rewards are defined; declare one with set_reward(name, fn) "
71 "before asking for a reward analysis");
72
73 CtmcReward<T> out;
75 const std::size_t n = out.chain.chain.space.size();
76 const std::size_t nr = sn.reward.size();
77 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
78
79 out.state_space_aggr = ctmc_state_space_aggr(sn, out.chain.chain.space);
80
81 // The reward vector of each declaration, evaluated once per state on the
82 // aggregate row -- the same row `RewardState` wraps in the reference.
83 Matrix<T> R(nr, n, zero);
84 out.names.resize(nr);
85 for (std::size_t r = 0; r < nr; ++r) {
86 out.names[r] = sn.reward[r].name;
87 for (std::size_t s = 0; s < n; ++s) {
88 std::vector<T> row(out.state_space_aggr.cols());
89 for (std::size_t c = 0; c < row.size(); ++c) row[c] = out.state_space_aggr(s, c);
90 R(r, s) = sn.reward[r].fn(row);
91 }
92 }
93
94 out.steady_state.assign(nr, zero);
95 for (std::size_t r = 0; r < nr; ++r)
96 for (std::size_t s = 0; s < n; ++s)
97 out.steady_state[r] += T(out.chain.pi[s] * R(r, s));
98
99 // Uniformization rate: the largest exit rate. A generator with none -- every
100 // state absorbing -- would divide by zero, so it falls back to 1, which
101 // leaves P = I and a value function that simply accumulates r(s).
102 double q = 0;
103 for (std::size_t s = 0; s < n; ++s)
104 q = std::max(q, std::fabs(num_traits<T>::to_double(out.chain.chain.Q(s, s))));
105 if (q == 0) q = 1.0;
106 const T qq = num_traits<T>::from_double(q);
107
108 Matrix<T> P(n, n, zero);
109 for (std::size_t a = 0; a < n; ++a)
110 for (std::size_t b = 0; b < n; ++b)
111 P(a, b) = a == b ? T(out.chain.chain.Q(a, b) / qq + one)
112 : T(out.chain.chain.Q(a, b) / qq);
113
114 out.V.assign(nr, Matrix<T>(tmax + 1, n, zero));
115 for (std::size_t r = 0; r < nr; ++r) {
116 std::vector<T> prev(n, zero);
117 for (std::size_t k = 1; k <= tmax; ++k) {
118 std::vector<T> next(n, zero);
119 // V^{k+1}(s) = r(s) + sum_s' P(s,s') V^k(s'), the reference's
120 // `R(r,:) + v_prev*P'` -- note the TRANSPOSE, which makes it a
121 // forward expectation over the successor rather than a backward
122 // one over the predecessor.
123 for (std::size_t s = 0; s < n; ++s) {
124 T acc = R(r, s);
125 for (std::size_t sp = 0; sp < n; ++sp) acc += T(P(s, sp) * prev[sp]);
126 next[s] = acc;
127 }
128 for (std::size_t s = 0; s < n; ++s) out.V[r](k, s) = next[s];
129 prev.swap(next);
130 }
131 }
132
133 out.t.resize(tmax + 1);
134 for (std::size_t k = 0; k <= tmax; ++k)
135 out.t[k] = T(num_traits<T>::from_double(static_cast<double>(k)) / qq);
136 return out;
137}
138
139/**
140 * Port of `@@SolverCTMC/getTranReward`: E[r(X(t))] = sum_s pi_t(s) r(s).
141 *
142 * NOT THE VALUE FUNCTION `V` above. This is the expected reward RATE at time t,
143 * which converges to the steady-state E[r]; `V` is the reward accumulated over
144 * k uniformized steps and diverges. The two are related by V being roughly the
145 * integral of this, and confusing them is the easiest mistake to make here.
146 *
147 * @param t0,t1 the timespan; an infinite one has no transient to report
148 * @param sn the refreshed network struct, carrying the reward definitions
149 * @param opt CTMC options (state-space cutoff, tolerances, method)
150 * @param tout optional out-parameter receiving the integration time points
151 * @param names optional out-parameter receiving the reward names, in result order
152 * @return `out[r][i]` is reward r at time `t[i]`
153 */
154template <class T>
155std::vector<std::vector<T>> solver_ctmc_tran_reward(const NetworkStruct<T>& sn,
156 const CtmcOptions& opt, const T& t0,
157 const T& t1, std::vector<T>* tout = nullptr,
158 std::vector<std::string>* names = nullptr) {
160 "solver_ctmc_tran_reward integrates the forward equation, which needs "
161 "transcendental arithmetic; use --arith double or real");
162 if (sn.reward.empty())
163 throw InputError(
164 "solver_ctmc_tran_reward: no rewards are defined; declare one with "
165 "set_reward(name, fn) before asking for a transient reward");
166 if (!std::isfinite(num_traits<T>::to_double(t1)))
167 throw InputError(
168 "solver_ctmc_tran_reward: a finite timespan is required; an unbounded one has no "
169 "transient to report, so ask for the steady-state reward instead");
170
172 const Matrix<T> A = ctmc_state_space_aggr(sn, tr.chain.chain.space);
173 const std::size_t n = tr.chain.chain.space.size(), nr = sn.reward.size(), nt = tr.t.size();
174 const T zero = num_traits<T>::from_int(0);
175
176 // The reward vector is state-dependent only, so it is evaluated ONCE per
177 // state rather than once per (state, time): r does not depend on t, and
178 // re-evaluating a user callback nt times would be the dominant cost.
179 Matrix<T> R(nr, n, zero);
180 if (names) names->clear();
181 for (std::size_t r = 0; r < nr; ++r) {
182 if (names) names->push_back(sn.reward[r].name);
183 for (std::size_t s = 0; s < n; ++s) {
184 std::vector<T> row(A.cols());
185 for (std::size_t c = 0; c < row.size(); ++c) row[c] = A(s, c);
186 R(r, s) = sn.reward[r].fn(row);
187 }
188 }
189
190 std::vector<std::vector<T>> out(nr, std::vector<T>(nt, zero));
191 for (std::size_t r = 0; r < nr; ++r)
192 for (std::size_t i = 0; i < nt; ++i)
193 for (std::size_t s = 0; s < n; ++s) out[r][i] += T(tr.pit(i, s) * R(r, s));
194 if (tout) *tout = tr.t;
195 return out;
196}
197
198/** Port of `@@SolverCTMC/getAvgReward`: the steady-state expected rewards. */
199template <class T>
201 std::vector<std::string>* names = nullptr) {
203 if (names) *names = r.names;
204 return r.steady_state;
205}
206
207} // namespace ctmc
208} // namespace line
209
210#endif // LINE_SOLVERS_CTMC_SOLVER_CTMC_REWARD_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
A network plus its refreshed NetworkStruct.
The exception types the port throws.
Dense matrix and non-owning view.
std::vector< T > solver_ctmc_avg_reward(const NetworkStruct< T > &sn, const CtmcOptions &opt, std::vector< std::string > *names=nullptr)
Port of @@SolverCTMC/getAvgReward: the steady-state expected rewards.
CtmcSolution< T > solver_ctmc_analyzer(const NetworkStruct< T > &sn_in, const CtmcOptions &opt)
Port of solver_ctmc_analyzer.m plus the fork-join wrapper of @@SolverCTMC/runAnalyzer....
CtmcTransient< T > solver_ctmc_transient_analyzer(const NetworkStruct< T > &sn, const CtmcOptions &opt, const T &t0, const T &t1, const std::vector< T > &grid=std::vector< T >())
Port of solver_ctmc_transient_analyzer.m.
Matrix< T > ctmc_state_space_aggr(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space)
Port of StateSpaceAggr: the per-(station, class) job counts of every state, as an (nstates x nstation...
std::vector< std::vector< T > > solver_ctmc_tran_reward(const NetworkStruct< T > &sn, const CtmcOptions &opt, const T &t0, const T &t1, std::vector< T > *tout=nullptr, std::vector< std::string > *names=nullptr)
Port of @@SolverCTMC/getTranReward: E[r(X(t))] = sum_s pi_t(s) r(s).
CtmcReward< T > solver_ctmc_reward(const NetworkStruct< T > &sn, const CtmcOptions &opt, std::size_t tmax=1000)
Port of solver_ctmc_reward.m.
A queueing network and its refreshed NetworkStruct.
Port of solver_ctmc_analyzer.m and the parts of @@SolverCTMC/runAnalyzer.m that surround one solve: t...
Port of solver_ctmc_transient_analyzer.m: the time-dependent counterpart of solver_ctmc_analyzer,...
The SolverCTMC knobs this port honours.
What the reward analyzer produces, per declared reward.
std::vector< T > steady_state
E[r] per reward.
std::vector< Matrix< T > > V
V[r] is (Tmax+1 x nstates).
std::vector< T > t
iteration index / q
Matrix< T > state_space_aggr
the rows the reward saw
std::vector< std::string > names
Everything one CTMC solve produces.
What one transient CTMC solve produces.