LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_lcp.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_PFQN_LCP_H
6#define LINE_API_PFQN_LCP_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Bard Large Customer Population (LCP) approximate MVA.
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_lcp.m, cross-checked against
14 * jar/src/main/java/jline/api/pfqn/mva/Pfqn_lcp.java. Y. Bard, "Some
15 * extensions to multiclass queueing network analysis", in Performance of
16 * Computer Systems, North-Holland, 1979: the first approximate MVA algorithm.
17 * It estimates the arrival-instant queue length by the time-averaged one
18 * WITHOUT removing the arriving customer,
19 *
20 * A_k^(c)(N) = Q_k(N - 1_c) ~= Q_k(N) = sum_s Q_ks(N),
21 *
22 * since with a large population one customer less cannot change the mean queue
23 * lengths appreciably. Dropping the Bard-Schweitzer proportional factor
24 * (N_r - 1)/N_r from pfqn_bs is exactly this algorithm, so LCP is uniformly
25 * more pessimistic than pfqn_bs and is inaccurate at small populations.
26 *
27 * Arithmetic: sums, products and divisions only, so the iterate is EXACT in
28 * rational arithmetic. The stopping rule still selects WHICH iterate is
29 * returned, the same caveat pfqn_bs carries.
30 */
31
32#include <cmath>
33#include <cstddef>
34#include <limits>
35#include <vector>
36
38#include "line/num/number.h"
39#include "line/util/error.h"
40#include "line/util/matrix.h"
41
42namespace line {
43namespace pfqn {
44
45/**
46 * @brief Bard Large Customer Population (LCP) approximate MVA.
47 *
48 * @param L (M x R) demands
49 * @param N (R) populations
50 * @param Z (R) think times, empty for none
51 * @param type (M) per-station scheduling, empty for all PS
52 * @param tol convergence tolerance
53 * @param maxiter iteration cap
54 * @param QN0 queue lengths that warm-start the iteration; empty for a cold start
55 */
56template <class T>
57AmvaResult<T> pfqn_lcp(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z,
58 const std::vector<AmvaSched>& type, double tol = 1e-6,
59 std::size_t maxiter = 1000, const Matrix<T>& QN0 = Matrix<T>()) {
60 const std::size_t M = L.rows(), R = L.cols();
61 if (N.size() != R) throw InputError("pfqn_lcp: L and N disagree on the class count");
62 if (!Z.empty() && Z.size() != R) throw InputError("pfqn_lcp: Z has the wrong length");
63 if (!type.empty() && type.size() != M) throw InputError("pfqn_lcp: type has the wrong length");
64
65 const T zero = num_traits<T>::from_int(0);
67 r.XN.assign(R, zero);
68 r.QN = Matrix<T>(M, R, zero);
69 r.UN = Matrix<T>(M, R, zero);
70 r.RN = Matrix<T>(M, R, zero);
71 Matrix<T> CN(M, R, zero);
72
73 if (M == 0) return r;
74 if (!QN0.empty()) {
75 if (QN0.rows() != M || QN0.cols() != R)
76 throw InputError("pfqn_lcp: QN0 has the wrong shape");
77 r.QN = QN0;
78 } else {
79 for (std::size_t i = 0; i < M; ++i)
80 for (std::size_t s = 0; s < R; ++s)
81 r.QN(i, s) = N[s] / num_traits<T>::from_int(static_cast<long>(M));
82 }
83
84 for (std::size_t it = 1; it <= maxiter; ++it) {
85 r.iterations = it;
86 const Matrix<T> Qprev = r.QN;
87
88 for (std::size_t cls = 0; cls < R; ++cls) {
89 if (N[cls] == zero) {
90 r.XN[cls] = zero;
91 for (std::size_t i = 0; i < M; ++i) {
92 CN(i, cls) = zero;
93 r.QN(i, cls) = zero;
94 r.UN(i, cls) = zero;
95 }
96 continue;
97 }
98 T ctot = Z.empty() ? zero : Z[cls];
99 for (std::size_t i = 0; i < M; ++i) {
100 CN(i, cls) = L(i, cls);
101 if (L(i, cls) == zero) continue;
102 const bool fcfs = !type.empty() && type[i] == AmvaSched::FCFS;
103 for (std::size_t s = 0; s < R; ++s) {
104 // the arriving customer is NOT removed: no (N-1)/N factor
105 if (fcfs && s != cls)
106 CN(i, cls) += L(i, s) * r.QN(i, s);
107 else
108 CN(i, cls) += L(i, cls) * r.QN(i, s);
109 }
110 ctot += CN(i, cls);
111 }
112 if (ctot == zero) throw NumericError("pfqn_lcp: zero total residence time");
113 r.XN[cls] = N[cls] / ctot;
114 }
115 for (std::size_t cls = 0; cls < R; ++cls)
116 for (std::size_t i = 0; i < M; ++i) {
117 r.QN(i, cls) = r.XN[cls] * CN(i, cls);
118 r.UN(i, cls) = r.XN[cls] * L(i, cls);
119 }
120
121 double delta = 0.0;
122 for (std::size_t cls = 0; cls < R; ++cls) {
123 if (N[cls] == zero) continue;
124 for (std::size_t i = 0; i < M; ++i) {
125 if (Qprev(i, cls) == zero) {
126 if (r.QN(i, cls) == zero) continue; // 0/0, omitted by max
127 delta = std::numeric_limits<double>::infinity();
128 continue;
129 }
130 const T one = num_traits<T>::from_int(1);
131 const double d =
132 std::fabs(num_traits<T>::to_double(T(one - r.QN(i, cls) / Qprev(i, cls))));
133 if (d > delta) delta = d;
134 }
135 }
136 if (delta < tol) {
137 r.converged = true;
138 break;
139 }
140 }
141
142 for (std::size_t cls = 0; cls < R; ++cls)
143 for (std::size_t i = 0; i < M; ++i)
144 r.RN(i, cls) = (N[cls] == zero) ? zero : r.QN(i, cls) / r.XN[cls];
145 return r;
146}
147
148template <class T>
149AmvaResult<T> pfqn_lcp(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z) {
150 return pfqn_lcp(L, N, Z, std::vector<AmvaSched>());
151}
152
153template <class T>
154AmvaResult<T> pfqn_lcp(const Matrix<T>& L, const std::vector<T>& N) {
155 return pfqn_lcp(L, N, std::vector<T>(), std::vector<AmvaSched>());
156}
157
158} // namespace pfqn
159} // namespace line
160
161#endif // LINE_API_PFQN_LCP_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
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Dense matrix and non-owning view.
AmvaResult< T > pfqn_lcp(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const std::vector< AmvaSched > &type, double tol=1e-6, std::size_t maxiter=1000, const Matrix< T > &QN0=Matrix< T >())
Bard Large Customer Population (LCP) approximate MVA.
Definition pfqn_lcp.h:57
Number-type abstraction for the templated API port.
Bard-Schweitzer approximate MVA.
Matrix< T > RN
(M x R) residence time
Definition pfqn_bs.h:52
std::vector< T > XN
(R) throughput
Definition pfqn_bs.h:49
Matrix< T > UN
(M x R) utilization
Definition pfqn_bs.h:51
std::size_t iterations
Definition pfqn_bs.h:53
Matrix< T > QN
(M x R) queue length
Definition pfqn_bs.h:50