9#ifndef LINE_API_MAM_LIBQBD_TAYLOR_H
10#define LINE_API_MAM_LIBQBD_TAYLOR_H
69 if (x < 0.0 || a <= 0.0)
throw InputError(
"gammainc_lower: a must be positive and x >= 0");
70 if (x == 0.0)
return 0.0;
71 const double gln = std::lgamma(a);
73 double ap = a, del = 1.0 / a,
sum = del;
74 for (
int n = 0; n < 1000; ++n) {
78 if (std::fabs(del) < std::fabs(
sum) * 1e-16)
break;
80 return sum * std::exp(-x + a * std::log(x) - gln);
82 const double tiny = 1e-300;
83 double b = x + 1.0 - a, c = 1.0 / tiny, d = 1.0 / b, h = d;
84 for (
int i = 1; i <= 1000; ++i) {
85 const double an = -
static_cast<double>(i) * (
static_cast<double>(i) - a);
88 if (std::fabs(d) < tiny) d = tiny;
90 if (std::fabs(c) < tiny) c = tiny;
92 const double del = d * c;
94 if (std::fabs(del - 1.0) < 1e-16)
break;
96 return 1.0 - std::exp(-x + a * std::log(x) - gln) * h;
105 if (!A0_.empty() || !Ap_.empty())
106 throw InputError(
"LibQbdProcess: level zero already exists");
107 Ap_.push_back(
Aplus);
108 A0_.push_back(diag_negrowsum(
Aplus));
111 if (!A0_.empty() || !Ap_.empty())
112 throw InputError(
"LibQbdProcess: level zero already exists");
114 Ap_.push_back(
Aplus);
121 Ap_.push_back(
Aplus);
127 Ap_.push_back(
Aplus);
134 A0_.push_back(diag_negrowsum2(
Aminus, prevAp));
135 Ap_.push_back(prevAp);
142 Ap_.push_back(prevAp);
145 bool empty()
const {
return A0_.empty(); }
149 return A0_[std::min(level, A0_.size() - 1)];
152 return Ap_[std::min(level, Ap_.size() - 1)];
155 if (level == 0)
throw InputError(
"LibQbdProcess: A_minus at level zero is undefined");
156 return Am_[std::min(level, Am_.size()) - 1];
162 for (std::size_t i = 0; i < M.rows(); ++i)
163 if (M(i, i) < v) v = M(i, i);
174 std::vector<std::vector<T>>
mul_row(
const std::vector<std::vector<T>>& vec,
175 const T& cons)
const {
177 const std::size_t n = vec.size();
178 if (n == 0)
throw InputError(
"LibQbdProcess: an empty vector was passed");
179 std::vector<std::vector<T>> res(n + 1);
180 for (std::size_t j = 0; j <= n; ++j) {
181 const std::size_t dim =
182 (j < n) ?
A0(j).cols() :
Aplus(n - 1).cols();
183 res[j].assign(dim, zero);
184 if (j > 0) accumulate(res[j], vec[j - 1],
Aplus(j - 1));
185 if (j < n) accumulate(res[j], vec[j],
A0(j));
186 if (j + 1 < n) accumulate(res[j], vec[j + 1],
Aminus(j + 1));
187 for (T& v : res[j]) v *= cons;
194 if (!(nrm > 0.0)) res.pop_back();
203 for (std::size_t i = 0; i < A.
rows(); ++i) {
205 for (std::size_t j = 0; j < A.
cols(); ++j) s += A(i, j);
210 static Matrix<T> diag_negrowsum2(
const Matrix<T>& A,
const Matrix<T>& B) {
211 const T zero = num_traits<T>::from_int(0);
212 Matrix<T> D(A.rows(), A.rows(), zero);
213 for (std::size_t i = 0; i < A.rows(); ++i) {
215 for (std::size_t j = 0; j < A.cols(); ++j) s += A(i, j);
216 for (std::size_t j = 0; j < B.cols(); ++j) s += B(i, j);
221 static void accumulate(std::vector<T>& out,
const std::vector<T>& v,
const Matrix<T>& M) {
222 if (v.size() != M.rows())
return;
223 for (std::size_t i = 0; i < M.rows(); ++i) {
224 if (v[i] == num_traits<T>::from_int(0))
continue;
225 for (std::size_t j = 0; j < M.cols() && j < out.size(); ++j) out[j] += v[i] * M(i, j);
229 void check_filled()
const {
230 if (A0_.size() != Ap_.size() || Ap_.size() != Am_.size() + 1)
231 throw InputError(
"LibQbdProcess: unfilled levels found");
234 std::vector<Matrix<T>> Ap_, A0_, Am_;
241 std::vector<std::vector<std::vector<T>>>
dists;
257 const std::vector<std::vector<T>>& pi0,
258 double error,
double max_time) {
260 "taylor_series_adaptive evaluates an incomplete gamma truncation bound");
261 if (proc.
empty())
throw InputError(
"taylor_series_adaptive: the generator is empty");
263 if (!(min_elem > 0.0))
264 throw NumericError(
"taylor_series_adaptive: the generator has no negative diagonal");
265 const unsigned max_degree = 177u;
268 out.
times.push_back(0.0);
269 out.
dists.push_back(pi0);
272 while (out.
times.back() < max_time) {
273 std::vector<std::vector<T>> deriv = out.
dists.back();
274 std::vector<std::vector<T>> res = deriv;
276 double two_delta_in_n = 0.5;
277 double er = std::numeric_limits<double>::infinity();
278 while (er > error && k < max_degree) {
279 deriv = proc.
mul_row(deriv, min_elem_inv);
281 const double c = std::exp(-std::lgamma(
static_cast<double>(k) + 2.0));
283 if (res.size() < deriv.size()) res.resize(deriv.size());
284 for (std::size_t l = 0; l < deriv.size(); ++l) {
285 if (res[l].size() < deriv[l].size())
287 for (std::size_t i = 0; i < deriv[l].size(); ++i) res[l][i] += ct * deriv[l][i];
290 for (
const std::vector<T>& lv : deriv)
292 er = nrm *
gammainc_lower(
static_cast<double>(k) + 2.0, 2.0) * std::exp(2.0) *
294 two_delta_in_n *= 0.5;
297 out.
dists.push_back(res);
298 out.
times.push_back(out.
times.back() + 1.0 / min_elem);
NumericError(const std::string &what)
A QBD's level blocks, as libQBD's QBD class holds them.
const Matrix< T > & Aplus(std::size_t level) const
void add_zero_level(const Matrix< T > &Aplus)
Level zero from its upward block alone; the local block is the row-sum negative.
const Matrix< T > & A0(std::size_t level) const
Blocks at a level, with every level above the last one REPEATING it.
std::vector< std::vector< T > > mul_row(const std::vector< std::vector< T > > &vec, const T &cons) const
vec Q scaled by cons, where vec is one row vector per level.
void add_zero_level(const Matrix< T > &A0, const Matrix< T > &Aplus)
void add_final_level(const Matrix< T > &Aminus, const Matrix< T > &A0)
void add_final_level(const Matrix< T > &Aminus)
The repeating level: its up block is the previous one, reused for ever.
void add_level(const Matrix< T > &Aminus, const Matrix< T > &Aplus)
A level from its down and up blocks; the local block closes the rows.
T min_element() const
The most negative diagonal entry over every local block.
void add_level(const Matrix< T > &Aminus, const Matrix< T > &A0, const Matrix< T > &Aplus)
const Matrix< T > & Aminus(std::size_t level) const
The exception types the port throws.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
Dense matrix and non-owning view.
double gammainc_lower(double a, double x)
Regularized lower incomplete gamma P(a, x), MATLAB's gammainc(x, a, 'lower').
TaylorSeriesResult< T > taylor_series_adaptive(const LibQbdProcess< T > &proc, const std::vector< std::vector< T > > &pi0, double error, double max_time)
libQBD's TaylorSeriesAdaptive, restricted to the reference grid that solver_mam_ldqbd_transient reads...
Number-type abstraction for the templated API port.
What the adaptive Taylor series returns: the reference grid and its laws.
std::vector< double > times
the reference points
std::vector< std::vector< std::vector< T > > > dists
per point, per level, per phase