LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_hh1_lindley.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_HH1_LINDLEY_H
6#define LINE_API_QSYS_QSYS_HH1_LINDLEY_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Conditional waiting-time moments of the Hl/Hn/1 Lindley recursion.
12 *
13 * Templated port of matlab/src/api/qsys/qsys_hh1_lindley.m. No JAR
14 * counterpart. Hyperexponential primitives are mixtures of exponentials, so
15 * conditioning on the arrival phase i and the service phase j reduces one
16 * Lindley step to the M/M/1 step at rates lambda(i) and mu(j), and
17 *
18 * E[W_{n+1}^m | W_n] = sum_i sum_j pa(i) ps(j) E_ij[W_{n+1}^m | W_n].
19 *
20 * Phases are drawn independently for each customer, which is what makes the
21 * mixture exact rather than an approximation; a Markov-modulated arrival
22 * stream would not decompose this way.
23 *
24 * The variance is NOT the mixture of the per-phase variances, because the
25 * phase is itself random. It is recovered from the first two MIXED raw
26 * moments, which adds the between-phase spread of the means, and mixing the
27 * variances instead would understate it.
28 *
29 * Unlike qsys_mm1_lindley the mean is read from moments(:,1) rather than from
30 * an explicit closed form, since the mixture has none; that is also what
31 * MATLAB does.
32 *
33 * Reference: S. Palomo, J. Pender, "Learning the Tandem Network Lindley
34 * Recursion", Proc. Winter Simulation Conference, 2021, theorem 3.
35 * Registered in .citations() as 'condlindley'.
36 */
37
38#include <cstddef>
39#include <string>
40#include <vector>
41
44#include "line/num/number.h"
45#include "line/util/error.h"
46#include "line/util/matrix.h"
47
48namespace line {
49namespace qsys {
50
51/**
52 * @brief Conditional waiting-time moments of the Hl/Hn/1 Lindley recursion.
53 *
54 * @param lambda arrival phase rates, positive
55 * @param pa arrival phase probabilities, nonnegative and summing to 1
56 * @param mu service phase rates, positive
57 * @param ps service phase probabilities, nonnegative and summing to 1
58 * @param Wn waiting times of customer n, finite and nonnegative
59 * @param mmax highest moment order; raised to 2 when smaller, as in MATLAB
60 */
61template <class T>
62LindleyResult<T> qsys_hh1_lindley(const std::vector<T>& lambda, const std::vector<T>& pa,
63 const std::vector<T>& mu, const std::vector<T>& ps,
64 const std::vector<T>& Wn, unsigned mmax = 2) {
66 "qsys_hh1_lindley requires transcendental arithmetic");
67 const T zero = num_traits<T>::from_int(0);
68 const T one = num_traits<T>::from_int(1);
69 const T tol = num_traits<T>::from_double(1e-10);
70 if (lambda.empty() || lambda.size() != pa.size())
71 throw InputError("qsys_hh1_lindley: lambda and pa must be nonempty and of equal length");
72 if (mu.empty() || mu.size() != ps.size())
73 throw InputError("qsys_hh1_lindley: mu and ps must be nonempty and of equal length");
74 if (mmax < 1) throw InputError("qsys_hh1_lindley: mmax must be a positive integer");
75
76 T sa = zero, ss = zero;
77 for (std::size_t i = 0; i < lambda.size(); ++i) {
78 if (lambda[i] <= zero)
79 throw InputError("qsys_hh1_lindley: the arrival rates lambda must be positive real");
80 if (pa[i] < zero) throw InputError("qsys_hh1_lindley: pa must be nonnegative");
81 sa += pa[i];
82 }
83 for (std::size_t j = 0; j < mu.size(); ++j) {
84 if (mu[j] <= zero)
85 throw InputError("qsys_hh1_lindley: the service rates mu must be positive real");
86 if (ps[j] < zero) throw InputError("qsys_hh1_lindley: ps must be nonnegative");
87 ss += ps[j];
88 }
89 if (num_abs(T(sa - one)) > tol) throw InputError("qsys_hh1_lindley: pa must sum to 1");
90 if (num_abs(T(ss - one)) > tol) throw InputError("qsys_hh1_lindley: ps must sum to 1");
91 for (std::size_t i = 0; i < Wn.size(); ++i)
92 if (Wn[i] < zero) throw InputError("qsys_hh1_lindley: Wn must be nonnegative");
93
94 if (mmax < 2) mmax = 2;
95 const std::size_t nw = Wn.size();
97 r.mmax = mmax;
98 r.analyzer = "qsys_hh1_lindley";
99 r.moments = Matrix<T>(nw, mmax, zero);
100 for (std::size_t i = 0; i < lambda.size(); ++i)
101 for (std::size_t j = 0; j < mu.size(); ++j) {
102 const T weight = pa[i] * ps[j];
103 if (weight == zero) continue;
104 for (unsigned m = 1; m <= mmax; ++m) {
105 const std::vector<T> col = qsys_lindley_moment(lambda[i], mu[j], Wn, m);
106 for (std::size_t k = 0; k < nw; ++k) r.moments(k, m - 1) += weight * col[k];
107 }
108 }
109
110 r.mean.reserve(nw);
111 r.var.reserve(nw);
112 for (std::size_t k = 0; k < nw; ++k) {
113 r.mean.push_back(r.moments(k, 0));
114 r.var.push_back(r.moments(k, 1) - r.moments(k, 0) * r.moments(k, 0));
115 }
116 return r;
117}
118
119/** Scalar-Wn overload. */
120template <class T>
121LindleyResult<T> qsys_hh1_lindley(const std::vector<T>& lambda, const std::vector<T>& pa,
122 const std::vector<T>& mu, const std::vector<T>& ps,
123 const T& Wn, unsigned mmax = 2) {
124 return qsys_hh1_lindley(lambda, pa, mu, ps, std::vector<T>(1, Wn), mmax);
125}
126
127} // namespace qsys
128} // namespace line
129
130#endif // LINE_API_QSYS_QSYS_HH1_LINDLEY_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Dense matrix and non-owning view.
LindleyResult< T > qsys_hh1_lindley(const std::vector< T > &lambda, const std::vector< T > &pa, const std::vector< T > &mu, const std::vector< T > &ps, const std::vector< T > &Wn, unsigned mmax=2)
Conditional waiting-time moments of the Hl/Hn/1 Lindley recursion.
std::vector< T > qsys_lindley_moment(const T &lambda, const T &mu, const std::vector< T > &Wn, unsigned m)
One conditional Lindley moment for exponential primitives.
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
One conditional Lindley moment for exponential primitives.
Conditional waiting-time moments of the M/M/1 Lindley recursion.
Mirrors the struct MATLAB returns from qsys_mm1_lindley.
unsigned mmax
highest moment order computed
std::vector< T > mean
conditional mean, one per Wn entry
std::vector< T > var
conditional variance, one per Wn entry
std::string analyzer
identifier string, as in MATLAB
Matrix< T > moments
(numel(Wn) x mmax) conditional raw moments