LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mer1.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_MER1_H
6#define LINE_API_QSYS_QSYS_MER1_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Exact analysis of the M/Er/1 queue (Erlang-r service of total rate mu/r per
12 * phase-sum, i.e. each phase has rate mu).
13 *
14 * Port of matlab/src/api/qsys/qsys_mer1.m. The number of phases of work in the
15 * system has the distribution p_n = sum_k c_k x_k^n, where x_1..x_r are the
16 * roots inside the unit disk of mu x^{r+1} - (lambda + mu) x^r + lambda and
17 * c_k = (1 - rho) / prod_{j != k} (1 - x_j / x_k). The number of customers
18 * groups the phases in blocks of r. The waiting time is a mixture of
19 * exponentials and the sojourn time a mixture of Erlangs.
20 *
21 * The roots and coefficients are complex: they are returned beside `res`
22 * (MATLAB's `res.roots` and `res.coeffs`), which holds the real measures.
23 *
24 * ARITHMETIC: double, as the reference.
25 */
26
27#include <algorithm>
28#include <cmath>
29#include <complex>
30#include <cstdio>
31#include <functional>
32#include <vector>
33
37#include "line/util/error.h"
38
39namespace line {
40namespace qsys {
41
42/** The M/Er/1 `res`: the real measures plus the complex `roots` and `coeffs`. */
43struct Mer1Result {
44 QsysRes res; ///< every real field of MATLAB's res, in order
45 std::vector<std::complex<double>> roots; ///< res.roots, the x_k inside the unit disk
46 std::vector<std::complex<double>> coeffs; ///< res.coeffs, the c_k
47};
48
49/**
50 * @brief Exact M/Er/1 queue.
51 *
52 * @param lambda arrival rate
53 * @param mu rate of each Erlang phase (mean service r/mu)
54 * @param r number of phases
55 * @param o tPoints, percentiles and nmax (number of customer levels)
56 */
57inline Mer1Result qsys_mer1(double lambda, double mu, unsigned r, const QsysResOptions& o = QsysResOptions()) {
58 using namespace resdetail;
59 using Cplx = std::complex<double>;
60 check_percentiles(o.percentiles, "qsys_mer1");
61 if (!(lambda > 0.0) || !(mu > 0.0)) throw Error("qsys_mer1: lambda and mu must be positive scalars");
62 if (r < 1) throw Error("qsys_mer1: r must be a positive integer");
63 const double rho = lambda * r / mu;
64 if (rho >= 1.0) throw_unstable("qsys_mer1", "rho", rho);
65
66 // roots([mu, -(lambda + mu), zeros(1, r-1), lambda])
67 std::vector<double> poly(r + 2, 0.0);
68 poly[0] = mu;
69 poly[1] = -(lambda + mu);
70 poly[r + 1] = lambda;
71 std::vector<Cplx> all = mg1psdetail::poly_roots(poly);
72 // Newton polish against the original coefficients.
73 for (Cplx& z : all) {
74 for (int it = 0; it < 4; ++it) {
75 Cplx f(0.0, 0.0), df(0.0, 0.0);
76 for (double c : poly) {
77 df = df * z + f;
78 f = f * z + c;
79 }
80 if (std::abs(df) < 1e-300) break;
81 z -= f / df;
82 }
83 }
84 std::vector<Cplx> x;
85 for (const Cplx& z : all)
86 if (std::abs(z) < 1.0 - 1e-10) x.push_back(z);
87 if (x.size() != r) {
88 char buf[128];
89 std::snprintf(buf, sizeof buf, "qsys_mer1: found %d roots inside the unit disk, expected %d",
90 static_cast<int>(x.size()), static_cast<int>(r));
91 throw NumericError(ErrorKind{"Root"}, buf);
92 }
93 std::vector<Cplx> ck(r);
94 for (unsigned k = 0; k < r; ++k) {
95 Cplx prod(1.0, 0.0);
96 for (unsigned j = 0; j < r; ++j)
97 if (j != k) prod *= (1.0 - x[j] / x[k]);
98 ck[k] = (1.0 - rho) / prod;
99 }
100 const double rr = static_cast<double>(r);
101 const double ES = rr / mu, ES2 = rr * (rr + 1.0) / (mu * mu), ES3 = rr * (rr + 1.0) * (rr + 2.0) / (mu * mu * mu);
102 Mer1Result out;
103 out.res = qsys_mg1_res(lambda, 1.0 / ES, std::sqrt(ES2 / (ES * ES) - 1.0), ES3);
104
105 double xmax = 0.0, ckmax = 0.0;
106 for (const Cplx& z : x) xmax = std::max(xmax, std::abs(z));
107 for (const Cplx& c : ck) ckmax = std::max(ckmax, std::abs(c));
108 long nlev;
109 if (o.nmax < 0) {
110 long nph = static_cast<long>(std::ceil(std::log(1e-14 * (1.0 - xmax) / ckmax) / std::log(xmax))) + r;
111 nph = std::max(nph, static_cast<long>(r));
112 nlev = static_cast<long>(std::ceil(static_cast<double>(nph) / rr));
113 } else {
114 nlev = o.nmax;
115 }
116 const long nph = nlev * static_cast<long>(r);
117 std::vector<double> p(static_cast<std::size_t>(nph + 1));
118 for (long n = 0; n <= nph; ++n) {
119 Cplx s(0.0, 0.0);
120 for (unsigned k = 0; k < r; ++k) s += ck[k] * std::pow(x[k], static_cast<double>(n));
121 p[static_cast<std::size_t>(n)] = std::max(s.real(), 0.0);
122 }
123 std::vector<double> q(static_cast<std::size_t>(nlev + 1), 0.0);
124 q[0] = p[0];
125 for (long i = 1; i <= nlev; ++i)
126 for (long j = (i - 1) * r + 1; j <= i * static_cast<long>(r); ++j) q[static_cast<std::size_t>(i)] += p[static_cast<std::size_t>(j)];
127
128 out.res.set("queueLengthDist", q);
129 out.res.set("phaseDist", p);
130 out.roots = x;
131 out.coeffs = ck;
132 const std::function<double(double)> Wccdf = [=](double t) {
133 if (t < 0.0) return 1.0;
134 Cplx s(0.0, 0.0);
135 for (unsigned k = 0; k < r; ++k) s += ck[k] * x[k] / (1.0 - x[k]) * std::exp(-mu * (1.0 - x[k]) * t);
136 return s.real();
137 };
138 const std::function<double(double)> Tccdf = [=](double t) {
139 if (t < 0.0) return 1.0;
140 double s = 0.0;
141 for (long n = 0; n <= nph; ++n)
142 s += p[static_cast<std::size_t>(n)] * gammainc_upper(mu * t, static_cast<double>(n) + rr);
143 return s;
144 };
145 if (!o.tPoints.empty()) {
146 out.res.set("tPoints", o.tPoints);
147 out.res.set("waitingTimeCdf", cdf_of(Wccdf, o.tPoints));
148 out.res.set("sojournTimeCdf", cdf_of(Tccdf, o.tPoints));
149 }
150 if (!o.percentiles.empty()) {
151 const double hi = 50.0 / (mu * (1.0 - xmax));
152 out.res.set("percentiles", o.percentiles);
153 out.res.set("waitingTimePercentile", pct_of(Wccdf, o.percentiles, hi));
154 out.res.set("sojournTimePercentile", pct_of(Tccdf, o.percentiles, hi));
155 }
156 out.res.analyzer = "exact:M/E" + std::to_string(r) + "/1";
157 return out;
158}
159
160} // namespace qsys
161} // namespace line
162
163#endif // LINE_API_QSYS_QSYS_MER1_H
Error(const std::string &what)
Definition error.h:75
NumericError(const std::string &what)
Definition error.h:112
The exception types the port throws.
Mer1Result qsys_mer1(double lambda, double mu, unsigned r, const QsysResOptions &o=QsysResOptions())
Exact M/Er/1 queue.
Definition qsys_mer1.h:57
QsysRes qsys_mg1_res(double lambda, double mu, double cs, double ES3=-1.0)
The res output of qsys_mg1 (Pollaczek-Khinchine and Takacs).
Definition qsys_mg1.h:60
QsysResT< double > QsysRes
Definition qsys_res.h:96
std::complex< double > Cplx
Definition qsys_mg1_ps.h:89
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Exact mean response time of the M/G/1 queue (Pollaczek-Khinchine).
Sojourn-time distribution of the M/G/1 processor-sharing queue.
The res struct the exact queueing-system functions return, and the numeric helpers those functions sh...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
The M/Er/1 res: the real measures plus the complex roots and coeffs.
Definition qsys_mer1.h:43
std::vector< std::complex< double > > roots
res.roots, the x_k inside the unit disk
Definition qsys_mer1.h:45
QsysRes res
every real field of MATLAB's res, in order
Definition qsys_mer1.h:44
std::vector< std::complex< double > > coeffs
res.coeffs, the c_k
Definition qsys_mer1.h:46
The name-value options shared by the exact functions: tPoints, percentiles and nmax (a negative nmax ...
Definition qsys_res.h:102
std::string analyzer
Definition qsys_res.h:59
void set(const std::string &name, const T &v)
Sets (or overwrites) a scalar field.
Definition qsys_res.h:62