LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
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
45
#include "
line/api/qsys/qsys_types.h
"
46
#include "
line/num/number.h
"
47
#include "
line/util/error.h
"
48
49
namespace
line
{
50
namespace
qsys
{
51
52
/** Mirrors the struct MATLAB returns from qsys_mm1_tandem_lindley. */
53
template
<
class
T>
54
struct
Mm1TandemLindleyResult
{
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
61
namespace
detail {
62
63
/** E[max(y + Exp(mu2)^{-1} - Exp(c)^{-1}, 0)], the station-2 Lindley step. */
64
template
<
class
T>
65
T 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). */
73
template
<
class
T>
74
T 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
*/
96
template
<
class
T>
97
Mm1TandemLindleyResult<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) {
100
static_assert
(
num_traits<T>::has_transcendental
,
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
118
Mm1TandemLindleyResult<T>
r;
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. */
140
template
<
class
T>
141
Mm1TandemLindleyResult<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
line::InputError::InputError
InputError(const std::string &what)
Definition
error.h:39
error.h
The exception types the port throws.
line::qsys
Definition
qsys_bmapm1.h:58
line::qsys::qsys_mm1_tandem_lindley
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.
Definition
qsys_mm1_tandem_lindley.h:97
line
Definition
aoi_dist2ph.h:52
line::num_abs
T num_abs(const T &v)
Definition
number.h:172
number.h
Number-type abstraction for the templated API port.
qsys_types.h
Shared return type and arithmetic helpers for the templated qsys port.
line::num_traits
Definition
number.h:111
line::qsys::Mm1TandemLindleyResult
Mirrors the struct MATLAB returns from qsys_mm1_tandem_lindley.
Definition
qsys_mm1_tandem_lindley.h:54
line::qsys::Mm1TandemLindleyResult::mean
std::vector< T > mean
conditional mean waiting time at station 2
Definition
qsys_mm1_tandem_lindley.h:55
line::qsys::Mm1TandemLindleyResult::idleProb
std::vector< T > idleProb
probability station 1 empties first
Definition
qsys_mm1_tandem_lindley.h:57
line::qsys::Mm1TandemLindleyResult::analyzer
std::string analyzer
Definition
qsys_mm1_tandem_lindley.h:58
line::qsys::Mm1TandemLindleyResult::interdepMean
std::vector< T > interdepMean
mean interdeparture time of station 1
Definition
qsys_mm1_tandem_lindley.h:56
include
line
api
qsys
qsys_mm1_tandem_lindley.h
Generated by
1.18.0