LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_res.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_QSYS_QSYS_RES_H
6#define LINE_API_QSYS_QSYS_RES_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * The `res` struct the exact queueing-system functions return, and the numeric
12 * helpers those functions share.
13 *
14 * MATLAB's exact closed forms (qsys_mm1, qsys_mmk, qsys_mg1, qsys_gm1,
15 * qsys_mmck, qsys_mg1_prio and the finite-source, priority, busy-period and
16 * renewal-arrival functions) return a struct whose field set depends on the
17 * options passed (tPoints, percentiles, ES3, lambda, ...). `QsysRes` holds the
18 * same fields in the same order, by the same names, so the dispatch layer can
19 * encode them one for one; `analyzer` is MATLAB's `res.analyzer` string.
20 *
21 * The distributional measures (incomplete gamma tails, Bessel densities,
22 * log-space distributions, bisection percentiles) are evaluated in double
23 * precision, as in the reference.
24 */
25
26#include <algorithm>
27#include <cmath>
28#include <cstdio>
29#include <functional>
30#include <limits>
31#include <string>
32#include <utility>
33#include <vector>
34
35#include <boost/math/special_functions/bessel.hpp>
36#include <boost/math/special_functions/gamma.hpp>
37
38#include "line/util/error.h"
39
40namespace line {
41namespace qsys {
42
43/** One measure of a `res` struct: a scalar or a row vector. */
44template <class T = double>
46 std::string name;
47 std::vector<T> value;
48 bool scalar = true;
49};
50
51/**
52 * MATLAB's `res` struct: named measures in insertion order, plus `res.analyzer`.
53 * Templated on the arithmetic so that the closed forms which are pure field
54 * arithmetic keep their exact Rational values; `QsysRes` is the double one.
55 */
56template <class T = double>
57struct QsysResT {
58 std::vector<QsysResFieldT<T>> fields;
59 std::string analyzer;
60
61 /** Sets (or overwrites) a scalar field. */
62 void set(const std::string& name, const T& v) { put(name, std::vector<T>{v}, true); }
63 /** Sets (or overwrites) a vector field. */
64 void set(const std::string& name, const std::vector<T>& v) { put(name, v, false); }
65 bool has(const std::string& name) const { return find(name) != nullptr; }
66 /** The scalar field `name`; throws when absent. */
67 T scalar(const std::string& name) const {
68 const QsysResFieldT<T>* f = find(name);
69 if (f == nullptr || f->value.empty()) throw Error("QsysRes: no field '" + name + "'");
70 return f->value[0];
71 }
72 /** The vector field `name`; throws when absent. */
73 const std::vector<T>& vec(const std::string& name) const {
74 const QsysResFieldT<T>* f = find(name);
75 if (f == nullptr) throw Error("QsysRes: no field '" + name + "'");
76 return f->value;
77 }
78
79 private:
80 const QsysResFieldT<T>* find(const std::string& name) const {
81 for (const QsysResFieldT<T>& f : fields)
82 if (f.name == name) return &f;
83 return nullptr;
84 }
85 void put(const std::string& name, const std::vector<T>& v, bool sc) {
86 for (QsysResFieldT<T>& f : fields)
87 if (f.name == name) {
88 f.value = v;
89 f.scalar = sc;
90 return;
91 }
92 fields.push_back(QsysResFieldT<T>{name, v, sc});
93 }
94};
95
97
98/**
99 * The name-value options shared by the exact functions: `tPoints`,
100 * `percentiles` and `nmax` (a negative nmax means "not given", MATLAB's []).
101 */
103 std::vector<double> tPoints;
104 std::vector<double> percentiles;
105 long nmax = -1;
106};
107
108namespace resdetail {
109
110/** MATLAB's gammaln. */
111inline double gammaln(double x) { return std::lgamma(x); }
112
113/** MATLAB's gammainc(x, a, 'upper'), the regularized upper tail Q(a, x). */
114inline double gammainc_upper(double x, double a) {
115 if (x <= 0.0) return 1.0;
116 if (std::isinf(x)) return 0.0;
117 return boost::math::gamma_q(a, x);
118}
119
120/** MATLAB's gammainc(x, a, 'lower'), the regularized lower tail P(a, x). */
121inline double gammainc_lower(double x, double a) {
122 if (x <= 0.0) return 0.0;
123 if (std::isinf(x)) return 1.0;
124 return boost::math::gamma_p(a, x);
125}
126
127/**
128 * MATLAB's besseli(1, x, 1) = I_1(x) exp(-x), x >= 0. Past the range where
129 * I_1 itself is finite the Hankel asymptotic expansion of the scaled function
130 * is used (six terms, relative error far below 1e-15 at x > 600).
131 */
132inline double besseli1_scaled(double x) {
133 if (x < 600.0) return boost::math::cyl_bessel_i(1.0, x) * std::exp(-x);
134 const double mu4 = 4.0; // 4 nu^2
135 double term = 1.0, sum = 1.0;
136 for (int k = 1; k <= 6; ++k) {
137 const double odd = 2.0 * k - 1.0;
138 term *= -(mu4 - odd * odd) / (k * 8.0 * x);
139 sum += term;
140 }
141 return sum / std::sqrt(2.0 * M_PI * x);
142}
143
144/** The 0:nmax power law c*r.^(0:nmax). */
145inline std::vector<double> geometric(double c, double r, long nmax) {
146 std::vector<double> v(static_cast<std::size_t>(nmax + 1));
147 for (long n = 0; n <= nmax; ++n) v[static_cast<std::size_t>(n)] = c * std::pow(r, static_cast<double>(n));
148 return v;
149}
150
151/**
152 * The bisection inverse of a complementary CDF shared by the reference's
153 * functions (ccdf_inverse / mmck_ccdf_inverse / mmckk_ccdf_inverse): the time at
154 * which `ccdf` falls to `target`, bracketing upward from `hi`.
155 */
156inline double ccdf_inverse(const std::function<double(double)>& ccdf, double target, double hi) {
157 if (ccdf(0.0) <= target) return 0.0;
158 double lo = 0.0;
159 while (ccdf(hi) > target) hi = 2.0 * hi;
160 for (int it = 1; it <= 200; ++it) {
161 const double mid = (lo + hi) / 2.0;
162 if (ccdf(mid) > target) lo = mid;
163 else hi = mid;
164 if (hi - lo <= 1e-12 * std::max(1.0, hi)) break;
165 }
166 return (lo + hi) / 2.0;
167}
168
169/** sum(x) */
170inline double vsum(const std::vector<double>& x) {
171 double s = 0.0;
172 for (double v : x) s += v;
173 return s;
174}
175
176/** exp(logp - logsumexp(lognorm)), the reference's log-space normalization. */
177inline std::vector<double> lognormalize(const std::vector<double>& logp, const std::vector<double>& lognorm) {
178 double mx = -std::numeric_limits<double>::infinity();
179 for (double v : lognorm) mx = std::max(mx, v);
180 double s = 0.0;
181 for (double v : lognorm) s += std::exp(v - mx);
182 const double ln = mx + std::log(s);
183 std::vector<double> p(logp.size());
184 for (std::size_t i = 0; i < logp.size(); ++i) p[i] = std::exp(logp[i] - ln);
185 return p;
186}
187
188/** Fills `out` with 1 - ccdf(t) at each t, as `1 - arrayfun(ccdf, t)`. */
189inline std::vector<double> cdf_of(const std::function<double(double)>& ccdf, const std::vector<double>& t) {
190 std::vector<double> v(t.size());
191 for (std::size_t i = 0; i < t.size(); ++i) v[i] = 1.0 - ccdf(t[i]);
192 return v;
193}
194
195/** The percentiles by bisection, `arrayfun(@(r) ccdf_inverse(ccdf, 1 - r/100, hi), pct)`. */
196inline std::vector<double> pct_of(const std::function<double(double)>& ccdf, const std::vector<double>& pct,
197 double hi) {
198 std::vector<double> v(pct.size());
199 for (std::size_t i = 0; i < pct.size(); ++i) v[i] = ccdf_inverse(ccdf, 1.0 - pct[i] / 100.0, hi);
200 return v;
201}
202
203/** The reference's `LINE:input:Unstable` refusal. */
204inline void require_stable(double rho, const char* fn) {
205 if (rho >= 1.0) {
206 char buf[64];
207 std::snprintf(buf, sizeof buf, "%g", rho);
208 throw InputError(ErrorKind("Unstable"), std::string(fn) + ": unstable system: rho = " + buf + " >= 1");
209 }
210}
211
212/** A `LINE:input:Unstable` refusal "fn: unstable system: <what> = %g >= 1". */
213[[noreturn]] inline void throw_unstable(const char* fn, const char* what, double v) {
214 char buf[64];
215 std::snprintf(buf, sizeof buf, "%g", v);
216 throw InputError(ErrorKind("Unstable"), std::string(fn) + ": unstable system: " + what + " = " + buf + " >= 1");
217}
218
219/** MATLAB's "unknown option 'name'" refusal, as LINE:input:Argument or as a bare LINE:Error. */
220[[noreturn]] inline void throw_unknown_option(const char* fn, const std::string& name, bool argument_id) {
221 const std::string msg = std::string(fn) + ": unknown option '" + name + "'";
222 if (argument_id) throw InputError(ErrorKind("Argument"), msg);
223 throw Error(msg);
224}
225
226/**
227 * The reference's nchoosek_r: exp(gammaln) rounded to the integer it is while
228 * that integer is exactly representable (below 2^52).
229 */
230inline double nchoosek_r(double n, double k) {
231 const double v = std::exp(std::lgamma(n + 1.0) - std::lgamma(k + 1.0) - std::lgamma(n - k + 1.0));
232 return v < 4503599627370496.0 ? std::round(v) : v;
233}
234
235/** A percentile vector checked against (0, 100), MATLAB's LINE:Error refusal. */
236inline void check_percentiles(const std::vector<double>& pct, const char* fn) {
237 for (double p : pct)
238 if (p <= 0.0 || p >= 100.0) throw Error(std::string(fn) + ": percentiles must lie in (0, 100)");
239}
240
241} // namespace resdetail
242} // namespace qsys
243} // namespace line
244
245#endif // LINE_API_QSYS_QSYS_RES_H
Error(const std::string &what)
Definition error.h:75
InputError(const std::string &what)
Definition error.h:105
The exception types the port throws.
double vsum(const std::vector< double > &x)
sum(x)
Definition qsys_res.h:170
std::vector< double > pct_of(const std::function< double(double)> &ccdf, const std::vector< double > &pct, double hi)
The percentiles by bisection, arrayfun(@(r) ccdf_inverse(ccdf, 1 - r/100, hi), pct).
Definition qsys_res.h:196
std::vector< double > geometric(double c, double r, long nmax)
The 0:nmax power law c*r.
Definition qsys_res.h:145
void throw_unknown_option(const char *fn, const std::string &name, bool argument_id)
MATLAB's "unknown option 'name'" refusal, as LINE:input:Argument or as a bare LINE:Error.
Definition qsys_res.h:220
void throw_unstable(const char *fn, const char *what, double v)
A LINE:input:Unstable refusal "fn: unstable system: <what> = %g >= 1".
Definition qsys_res.h:213
void require_stable(double rho, const char *fn)
The reference's LINE:input:Unstable refusal.
Definition qsys_res.h:204
void check_percentiles(const std::vector< double > &pct, const char *fn)
A percentile vector checked against (0, 100), MATLAB's LINE:Error refusal.
Definition qsys_res.h:236
double gammaln(double x)
MATLAB's gammaln.
Definition qsys_res.h:111
double nchoosek_r(double n, double k)
The reference's nchoosek_r: exp(gammaln) rounded to the integer it is while that integer is exactly r...
Definition qsys_res.h:230
double gammainc_lower(double x, double a)
MATLAB's gammainc(x, a, 'lower'), the regularized lower tail P(a, x).
Definition qsys_res.h:121
double gammainc_upper(double x, double a)
MATLAB's gammainc(x, a, 'upper'), the regularized upper tail Q(a, x).
Definition qsys_res.h:114
double besseli1_scaled(double x)
MATLAB's besseli(1, x, 1) = I_1(x) exp(-x), x >= 0.
Definition qsys_res.h:132
std::vector< double > cdf_of(const std::function< double(double)> &ccdf, const std::vector< double > &t)
Fills out with 1 - ccdf(t) at each t, as 1 - arrayfun(ccdf, t).
Definition qsys_res.h:189
double ccdf_inverse(const std::function< double(double)> &ccdf, double target, double hi)
The bisection inverse of a complementary CDF shared by the reference's functions (ccdf_inverse / mmck...
Definition qsys_res.h:156
std::vector< double > lognormalize(const std::vector< double > &logp, const std::vector< double > &lognorm)
exp(logp - logsumexp(lognorm)), the reference's log-space normalization.
Definition qsys_res.h:177
QsysResT< double > QsysRes
Definition qsys_res.h:96
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
ErrorKind(std::string n)
Definition error.h:39
One measure of a res struct: a scalar or a row vector.
Definition qsys_res.h:45
std::vector< T > value
Definition qsys_res.h:47
The name-value options shared by the exact functions: tPoints, percentiles and nmax (a negative nmax ...
Definition qsys_res.h:102
std::vector< double > percentiles
Definition qsys_res.h:104
std::vector< double > tPoints
Definition qsys_res.h:103
MATLAB's res struct: named measures in insertion order, plus res.analyzer.
Definition qsys_res.h:57
void set(const std::string &name, const std::vector< T > &v)
Sets (or overwrites) a vector field.
Definition qsys_res.h:64
std::vector< QsysResFieldT< double > > fields
Definition qsys_res.h:58
bool has(const std::string &name) const
Definition qsys_res.h:65
void set(const std::string &name, const T &v)
Sets (or overwrites) a scalar field.
Definition qsys_res.h:62
const std::vector< T > & vec(const std::string &name) const
The vector field name; throws when absent.
Definition qsys_res.h:73
T scalar(const std::string &name) const
The scalar field name; throws when absent.
Definition qsys_res.h:67