LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mh21.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_MH21_H
6#define LINE_API_QSYS_QSYS_MH21_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Exact analysis of the M/H2/1 queue: hyperexponential service with branch
12 * probabilities (p1, 1-p1) and rates (mu1, mu2).
13 *
14 * Port of matlab/src/api/qsys/qsys_mh21.m. The probability generating
15 * function of the number in system is rational with two poles z1 < z2 outside
16 * the unit disk, the roots of a1 a2 z^2 - (a1 + a2 + a1 a2) z + 1 + a1 + a2 - rho
17 * (a_i = lambda/mu_i), so P_n = C1 z1^-n + C2 z2^-n, and the sojourn and
18 * waiting times are mixtures of two exponentials of rates lambda (z_i - 1).
19 *
20 * ARITHMETIC: double, as the reference.
21 */
22
23#include <algorithm>
24#include <cmath>
25#include <functional>
26#include <vector>
27
30#include "line/util/error.h"
31
32namespace line {
33namespace qsys {
34
35/**
36 * @brief Exact M/H2/1 queue.
37 *
38 * @param lambda arrival rate
39 * @param p1 probability of the first service phase
40 * @param mu1 rate of the first phase
41 * @param mu2 rate of the second phase
42 * @param o tPoints, percentiles and nmax options
43 */
44inline QsysRes qsys_mh21(double lambda, double p1, double mu1, double mu2,
45 const QsysResOptions& o = QsysResOptions()) {
46 using namespace resdetail;
47 check_percentiles(o.percentiles, "qsys_mh21");
48 if (!(lambda > 0.0) || !(mu1 > 0.0) || !(mu2 > 0.0))
49 throw Error("qsys_mh21: lambda, mu1 and mu2 must be positive scalars");
50 if (!(p1 >= 0.0) || p1 > 1.0) throw Error("qsys_mh21: p1 must lie in [0, 1]");
51 const double p2 = 1.0 - p1;
52 const double rho = lambda * (p1 / mu1 + p2 / mu2);
53 if (rho >= 1.0) throw_unstable("qsys_mh21", "rho", rho);
54 const double a1 = lambda / mu1, a2 = lambda / mu2;
55 // roots([a1 a2, -(a1 + a2 + a1 a2), 1 + a1 + a2 - rho]), real parts sorted
56 const double qa = a1 * a2, qb = -(a1 + a2 + a1 * a2), qc = 1.0 + a1 + a2 - rho;
57 double z1, z2;
58 const double disc = qb * qb - 4.0 * qa * qc;
59 if (disc >= 0.0) {
60 const double sq = std::sqrt(disc);
61 const double qq = -0.5 * (qb + (qb >= 0.0 ? sq : -sq));
62 z1 = qq / qa;
63 z2 = qc / qq;
64 if (z1 > z2) std::swap(z1, z2);
65 } else {
66 z1 = z2 = -qb / (2.0 * qa);
67 }
68 if (std::fabs(z1 - z2) < 1e-10 * std::max(1.0, std::fabs(z2)))
69 throw NumericError(ErrorKind{"Root"},
70 "qsys_mh21: the two roots coincide; use qsys_mg1 or qsys_mm1 for this service law");
71 const double C1 = (z1 - 1.0) * (1.0 - rho * z2) / (z1 - z2);
72 const double C2 = (z2 - 1.0) * (1.0 - rho * z1) / (z2 - z1);
73 const double mua = lambda * (z1 - 1.0), mub = lambda * (z2 - 1.0);
74 const double pia = C1 * z1 / (z1 - 1.0), pib = C2 * z2 / (z2 - 1.0);
75 auto hp = [&](double s) {
76 return 1.0 - lambda * (p1 * mu1 / ((mu1 + s) * (mu1 + s)) + p2 * mu2 / ((mu2 + s) * (mu2 + s)));
77 };
78 const double wa = -(1.0 - rho) / hp(-mua), wb = -(1.0 - rho) / hp(-mub);
79
80 const double ES = p1 / mu1 + p2 / mu2;
81 const double ES2 = 2.0 * (p1 / (mu1 * mu1) + p2 / (mu2 * mu2));
82 const double ES3 = 6.0 * (p1 / (mu1 * mu1 * mu1) + p2 / (mu2 * mu2 * mu2));
83 QsysRes res = qsys_mg1_res(lambda, 1.0 / ES, std::sqrt(std::max(ES2 / (ES * ES) - 1.0, 0.0)), ES3);
84
85 long nmax = o.nmax;
86 if (nmax < 0) nmax = std::max(1L, static_cast<long>(std::ceil(std::log(1e-14) / std::log(1.0 / z1))));
87 std::vector<double> P(static_cast<std::size_t>(nmax + 1));
88 for (long n = 0; n <= nmax; ++n)
89 P[static_cast<std::size_t>(n)] = C1 * std::pow(z1, -static_cast<double>(n)) + C2 * std::pow(z2, -static_cast<double>(n));
90 res.set("queueLengthDist", P);
91 res.set("roots", std::vector<double>{z1, z2});
92 res.set("sojournRates", std::vector<double>{mua, mub});
93 res.set("sojournWeights", std::vector<double>{pia, pib});
94 res.set("waitingWeights", std::vector<double>{wa, wb});
95 const std::function<double(double)> Wccdf = [=](double t) {
96 return t < 0.0 ? 1.0 : wa * std::exp(-mua * t) + wb * std::exp(-mub * t);
97 };
98 const std::function<double(double)> Tccdf = [=](double t) {
99 return t < 0.0 ? 1.0 : pia * std::exp(-mua * t) + pib * std::exp(-mub * t);
100 };
101 if (!o.tPoints.empty()) {
102 res.set("tPoints", o.tPoints);
103 res.set("waitingTimeCdf", cdf_of(Wccdf, o.tPoints));
104 res.set("sojournTimeCdf", cdf_of(Tccdf, o.tPoints));
105 }
106 if (!o.percentiles.empty()) {
107 const double hi = 50.0 / std::min(mua, mub);
108 res.set("percentiles", o.percentiles);
109 res.set("waitingTimePercentile", pct_of(Wccdf, o.percentiles, hi));
110 res.set("sojournTimePercentile", pct_of(Tccdf, o.percentiles, hi));
111 }
112 res.analyzer = "exact:M/H2/1";
113 return res;
114}
115
116} // namespace qsys
117} // namespace line
118
119#endif // LINE_API_QSYS_QSYS_MH21_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.
QsysRes qsys_mh21(double lambda, double p1, double mu1, double mu2, const QsysResOptions &o=QsysResOptions())
Exact M/H2/1 queue.
Definition qsys_mh21.h:44
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
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).
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 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