LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mm1_tandem_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_MM1_TANDEM_LINDLEY_H
6#define LINE_API_QSYS_QSYS_MM1_TANDEM_LINDLEY_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Conditional waiting time at the SECOND station of an M/M/1 -> /M/1 tandem.
12 *
13 * Templated port of matlab/src/api/qsys/qsys_mm1_tandem_lindley.m. No JAR
14 * counterpart. Station 1 is M/M/1 at rates (lambda, mu1) and station 2 is a
15 * single server at rate mu2 fed by its departures. Given that customer n
16 * waited Wk at station 1 and Wk1 at station 2, this returns
17 * E[W_{n+1} at station 2 | Wk, Wk1] together with the mean interdeparture
18 * time of station 1 and the probability that station 1 is idle when customer
19 * n+1 arrives.
20 *
21 * The interdeparture time of an M/M/1 queue is a MIXTURE, not an exponential
22 * of a single rate: with probability q = e^{-lambda Wk} mu1/(lambda+mu1) the
23 * server empties before the next arrival, and the gap is then the arrival
24 * time PLUS a service, a convolution of Exp(lambda) and Exp(mu1); otherwise
25 * the gap is the service Exp(mu1) alone. J below is the corresponding
26 * conditional Lindley step at station 2,
27 *
28 * J(c, y, mu2) = E[max(y + S2 - X_c, 0)] with X_c ~ Exp(c), S2 ~ Exp(mu2),
29 *
30 * so the mean is (1-q) mu1 J(mu1,.) + q * (the convolution mixture). At
31 * lambda == mu1 the two-term partial fraction of the convolution degenerates
32 * and the gap becomes Erlang(2, mu1); that branch is taken from a relative
33 * tolerance of 1e-9, which is MATLAB's, and evaluates the Erlang form Jw
34 * directly rather than differencing two nearly equal terms.
35 *
36 * Reference: S. Palomo, J. Pender, "Learning the Tandem Network Lindley
37 * Recursion", Proc. Winter Simulation Conference, 2021. Registered in
38 * .citations() as 'tandemlindley'.
39 */
40
41#include <cstddef>
42#include <string>
43#include <vector>
44
46#include "line/num/number.h"
47#include "line/util/error.h"
48
49namespace line {
50namespace qsys {
51
52/** Mirrors the struct MATLAB returns from qsys_mm1_tandem_lindley. */
53template <class T>
55 std::vector<T> mean; ///< conditional mean waiting time at station 2
56 std::vector<T> interdepMean; ///< mean interdeparture time of station 1
57 std::vector<T> idleProb; ///< probability station 1 empties first
58 std::string analyzer;
59};
60
61namespace detail {
62
63/** E[max(y + Exp(mu2)^{-1} - Exp(c)^{-1}, 0)], the station-2 Lindley step. */
64template <class T>
65T tandem_j(const T& c, const T& y, const T& mu2) {
66 const T one = num_traits<T>::from_int(1);
67 const T e = num_exp(T(-c * y));
68 return (y + one / mu2) * (one - e) / c - (one - e * (one + c * y)) / (c * c) +
69 e / (mu2 * (c + mu2));
70}
71
72/** The same expectation when the gap is Erlang(2,c) rather than Exp(c). */
73template <class T>
74T tandem_jw(const T& c, const T& y, const T& mu2) {
75 const T one = num_traits<T>::from_int(1);
76 const T two = num_traits<T>::from_int(2);
77 const T e = num_exp(T(-c * y));
78 const T d = c + mu2;
79 return (y + one / mu2) * (one - e * (one + c * y)) / (c * c) -
80 (two - e * (two + two * c * y + c * c * y * y)) / (c * c * c) +
81 e * (y / d + one / (d * d)) / mu2;
82}
83
84} // namespace detail
85
86/**
87 * @brief Conditional waiting time at the SECOND station of an M/M/1 -> /M/1
88 * tandem.
89 *
90 * @param lambda arrival rate at station 1, positive
91 * @param mu1 service rate at station 1, positive
92 * @param mu2 service rate at station 2, positive
93 * @param Wk waiting times at station 1, finite nonnegative
94 * @param Wk1 waiting times at station 2, same length as Wk
95 */
96template <class T>
97Mm1TandemLindleyResult<T> qsys_mm1_tandem_lindley(const T& lambda, const T& mu1, const T& mu2,
98 const std::vector<T>& Wk,
99 const std::vector<T>& Wk1) {
101 "qsys_mm1_tandem_lindley requires transcendental arithmetic");
102 const T zero = num_traits<T>::from_int(0);
103 const T one = num_traits<T>::from_int(1);
104 if (lambda <= zero) throw InputError("qsys_mm1_tandem_lindley: lambda must be positive");
105 if (mu1 <= zero) throw InputError("qsys_mm1_tandem_lindley: mu1 must be positive");
106 if (mu2 <= zero) throw InputError("qsys_mm1_tandem_lindley: mu2 must be positive");
107 if (Wk.size() != Wk1.size())
108 throw InputError("qsys_mm1_tandem_lindley: Wk and Wk1 must have the same size");
109 for (std::size_t i = 0; i < Wk.size(); ++i) {
110 if (Wk[i] < zero) throw InputError("qsys_mm1_tandem_lindley: Wk must be nonnegative");
111 if (Wk1[i] < zero) throw InputError("qsys_mm1_tandem_lindley: Wk1 must be nonnegative");
112 }
113
114 // MATLAB's relative test abs(lambda-mu1) > 1e-9*max(lambda,mu1)
115 const T scale = (lambda > mu1) ? lambda : mu1;
116 const bool distinct = num_abs(T(lambda - mu1)) > num_traits<T>::from_double(1e-9) * scale;
117
119 r.analyzer = "qsys_mm1_tandem_lindley";
120 r.mean.reserve(Wk.size());
121 r.interdepMean.reserve(Wk.size());
122 r.idleProb.reserve(Wk.size());
123 for (std::size_t i = 0; i < Wk.size(); ++i) {
124 const T q = detail::num_exp(T(-lambda * Wk[i])) * mu1 / (lambda + mu1);
125 const T base = mu1 * detail::tandem_j(mu1, Wk1[i], mu2);
126 T conv;
127 if (distinct)
128 conv = lambda * mu1 / (lambda - mu1) *
129 (detail::tandem_j(mu1, Wk1[i], mu2) - detail::tandem_j(lambda, Wk1[i], mu2));
130 else
131 conv = mu1 * mu1 * detail::tandem_jw(mu1, Wk1[i], mu2);
132 r.mean.push_back((one - q) * base + q * conv);
133 r.interdepMean.push_back(one / mu1 + q / lambda);
134 r.idleProb.push_back(q);
135 }
136 return r;
137}
138
139/** Scalar overload. */
140template <class T>
141Mm1TandemLindleyResult<T> qsys_mm1_tandem_lindley(const T& lambda, const T& mu1, const T& mu2,
142 const T& Wk, const T& Wk1) {
143 return qsys_mm1_tandem_lindley(lambda, mu1, mu2, std::vector<T>(1, Wk),
144 std::vector<T>(1, Wk1));
145}
146
147} // namespace qsys
148} // namespace line
149
150#endif // LINE_API_QSYS_QSYS_MM1_TANDEM_LINDLEY_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Mm1TandemLindleyResult< T > qsys_mm1_tandem_lindley(const T &lambda, const T &mu1, const T &mu2, const std::vector< T > &Wk, const std::vector< T > &Wk1)
Conditional waiting time at the SECOND station of an M/M/1 -> /M/1 tandem.
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
Shared return type and arithmetic helpers for the templated qsys port.
Mirrors the struct MATLAB returns from qsys_mm1_tandem_lindley.
std::vector< T > mean
conditional mean waiting time at station 2
std::vector< T > idleProb
probability station 1 empties first
std::vector< T > interdepMean
mean interdeparture time of station 1