LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_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_TANDEM_LINDLEY_H
6#define LINE_API_QSYS_QSYS_TANDEM_LINDLEY_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Sample-path Lindley recursion along a tandem of single-server FCFS queues.
12 *
13 * Templated port of matlab/src/api/qsys/qsys_tandem_lindley.m. No JAR
14 * counterpart. Given the interarrival times A of N customers at the first
15 * station and the (N x K) service times S, it replays
16 *
17 * W(n+1,k) = max(W(n,k) + S(n,k) - G(n,k), 0)
18 *
19 * where G(n,k) is the interarrival gap SEEN AT STATION k. The coupling that
20 * makes this a tandem rather than K independent queues is the gap update
21 *
22 * G(n,k+1) = max(G(n,k) - W(n,k) - S(n,k), 0) + S(n+1,k),
23 *
24 * the interdeparture time of station k: customer n+1 either catches up with
25 * customer n, in which case the gap collapses to the service time of n+1 at
26 * station k, or it does not, and the residual gap survives. Nothing here is
27 * distributional, so the recursion holds for arbitrary service laws and is
28 * the reference any conditional-moment approximation is checked against.
29 *
30 * The last customer has no successor, so its row of G stays unset; MATLAB
31 * leaves it NaN and the port does the same rather than filling it with a
32 * value that has no meaning.
33 *
34 * Reference: S. Palomo, J. Pender, "Learning the Tandem Network Lindley
35 * Recursion", Proc. Winter Simulation Conference, 2021. Registered in
36 * .citations() as 'tandemlindley'.
37 *
38 * ARITHMETIC: additions, subtractions and comparisons only, so the exact
39 * instantiation replays the same sample path with no rounding at all.
40 */
41
42#include <cstddef>
43#include <limits>
44#include <string>
45#include <vector>
46
47#include "line/num/number.h"
48#include "line/util/error.h"
49#include "line/util/matrix.h"
50
51namespace line {
52namespace qsys {
53
54/** Mirrors the struct MATLAB returns from qsys_tandem_lindley. */
55template <class T>
57 Matrix<T> W; ///< (N x K) waiting time of each customer at each station
58 Matrix<T> G; ///< (N x K) gap seen at each station, last row unset (NaN)
59 Matrix<T> T_; ///< (N x K) sojourn time W + S
60 Matrix<T> departure; ///< (N x K) departure epoch from each station
61 std::string analyzer;
62};
63
64/**
65 * @brief Sample-path Lindley recursion along a tandem of single-server FCFS
66 * queues.
67 *
68 * @param A (N) interarrival times at the first station, finite nonnegative
69 * @param S (N x K) service times, finite nonnegative
70 * @param W0 (K) initial waiting times, one per station; empty means all zero
71 */
72template <class T>
73TandemLindleyResult<T> qsys_tandem_lindley(const std::vector<T>& A, const Matrix<T>& S,
74 const std::vector<T>& W0 = std::vector<T>()) {
75 const T zero = num_traits<T>::from_int(0);
76 const std::size_t N = A.size();
77 if (N < 1) throw InputError("qsys_tandem_lindley: A must hold at least one interarrival time");
78 if (S.rows() != N)
79 throw InputError("qsys_tandem_lindley: S must have one row per interarrival time");
80 const std::size_t K = S.cols();
81 if (K < 1) throw InputError("qsys_tandem_lindley: S must have at least one column");
82 for (std::size_t n = 0; n < N; ++n) {
83 if (A[n] < zero)
84 throw InputError("qsys_tandem_lindley: A must hold nonnegative interarrival times");
85 for (std::size_t k = 0; k < K; ++k)
86 if (S(n, k) < zero)
87 throw InputError("qsys_tandem_lindley: S must hold nonnegative service times");
88 }
89 std::vector<T> w0 = W0;
90 if (w0.empty()) w0.assign(K, zero);
91 if (w0.size() != K)
92 throw InputError("qsys_tandem_lindley: W0 must hold one waiting time per station");
93 for (std::size_t k = 0; k < K; ++k)
94 if (w0[k] < zero)
95 throw InputError("qsys_tandem_lindley: W0 must hold nonnegative waiting times");
96
98 r.analyzer = "qsys_tandem_lindley";
99 r.W = Matrix<T>(N, K, zero);
100 // the last customer has no successor, so its gap row is undefined
101 r.G = Matrix<T>(N, K, num_traits<T>::from_double(std::numeric_limits<double>::quiet_NaN()));
102 for (std::size_t k = 0; k < K; ++k) r.W(0, k) = w0[k];
103
104 for (std::size_t n = 0; n + 1 < N; ++n) {
105 T gap = A[n];
106 for (std::size_t k = 0; k < K; ++k) {
107 r.G(n, k) = gap;
108 const T adv = r.W(n, k) + S(n, k);
109 r.W(n + 1, k) = (adv > gap) ? T(adv - gap) : zero;
110 const T residual = (gap > adv) ? T(gap - adv) : zero;
111 gap = residual + S(n + 1, k);
112 }
113 }
114
115 r.T_ = Matrix<T>(N, K, zero);
116 for (std::size_t n = 0; n < N; ++n)
117 for (std::size_t k = 0; k < K; ++k) r.T_(n, k) = r.W(n, k) + S(n, k);
118
119 r.departure = Matrix<T>(N, K, zero);
120 T epoch = zero;
121 for (std::size_t n = 0; n < N; ++n) {
122 if (n > 0) epoch += A[n - 1];
123 r.departure(n, 0) = epoch + r.T_(n, 0);
124 for (std::size_t k = 1; k < K; ++k)
125 r.departure(n, k) = r.departure(n, k - 1) + r.T_(n, k);
126 }
127 return r;
128}
129
130} // namespace qsys
131} // namespace line
132
133#endif // LINE_API_QSYS_QSYS_TANDEM_LINDLEY_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Dense matrix and non-owning view.
TandemLindleyResult< T > qsys_tandem_lindley(const std::vector< T > &A, const Matrix< T > &S, const std::vector< T > &W0=std::vector< T >())
Sample-path Lindley recursion along a tandem of single-server FCFS queues.
Number-type abstraction for the templated API port.
Mirrors the struct MATLAB returns from qsys_tandem_lindley.
Matrix< T > T_
(N x K) sojourn time W + S
Matrix< T > departure
(N x K) departure epoch from each station
Matrix< T > G
(N x K) gap seen at each station, last row unset (NaN)
Matrix< T > W
(N x K) waiting time of each customer at each station