LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_chow.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_CHOW_H
6#define LINE_API_PFQN_CHOW_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Chow Second Approximation (SA) approximate MVA.
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_chow.m, cross-checked against
14 * jar/src/main/java/jline/api/pfqn/mva/Pfqn_chow.java. W.-M. Chow,
15 * "Approximations for large scale closed queueing networks", Perform. Eval.
16 * 3(1), 1983. The arrival-instant queue length is written exactly as
17 *
18 * A_k^(c)(N) = Q_k(N - 1_c) = Q_k(N) (1 + theta_ck),
19 * theta_ck = [Q_k(N - 1_c) - Q_k(N)] / Q_k(N),
20 *
21 * and the theta-terms are estimated ONCE, off the Bard LCP solution, before
22 * the fixed point is run. Two estimators are given: the BACKWARD one uses
23 * Qhat(N - 1_c), the FORWARD one Qhat(N + 1_c). Chow reports the forward form
24 * to be the more accurate of the two, so it is the default here. Setting every
25 * theta to zero recovers pfqn_lcp.
26 *
27 * Arithmetic: as pfqn_lcp, field operations only, with the same stopping-rule
28 * caveat.
29 */
30
31#include <cmath>
32#include <cstddef>
33#include <limits>
34#include <vector>
35
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/** Which finite difference of the LCP solution estimates the theta-terms. */
47
48/**
49 * @brief Chow Second Approximation (SA) approximate MVA.
50 *
51 * @param L (M x R) demands
52 * @param N (R) populations
53 * @param Z (R) think times, empty for none
54 * @param type (M) per-station scheduling, empty for all PS
55 * @param tol convergence tolerance
56 * @param maxiter iteration cap
57 * @param QN0 warm start for the inner LCP solves and the fixed point; may be empty
58 * @param variant estimator of the theta-terms
59 */
60template <class T>
61AmvaResult<T> pfqn_chow(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z,
62 const std::vector<AmvaSched>& type, double tol = 1e-6,
63 std::size_t maxiter = 1000, const Matrix<T>& QN0 = Matrix<T>(),
65 const std::size_t M = L.rows(), R = L.cols();
66 if (N.size() != R) throw InputError("pfqn_chow: L and N disagree on the class count");
67 if (!Z.empty() && Z.size() != R) throw InputError("pfqn_chow: Z has the wrong length");
68 if (!type.empty() && type.size() != M) throw InputError("pfqn_chow: type has the wrong length");
69
70 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
71
72 // theta-terms from the LCP solution
73 const AmvaResult<T> base = pfqn_lcp(L, N, Z, type, tol, maxiter, QN0);
74 std::vector<T> Qtot(M, zero);
75 for (std::size_t i = 0; i < M; ++i)
76 for (std::size_t s = 0; s < R; ++s) Qtot[i] += base.QN(i, s);
77
78 Matrix<T> theta(M, R, zero);
79 for (std::size_t cls = 0; cls < R; ++cls) {
80 if (N[cls] == zero) continue;
81 std::vector<T> Nalt(N);
82 Nalt[cls] = (variant == ChowVariant::Backward) ? T(N[cls] - one) : T(N[cls] + one);
83 const AmvaResult<T> alt = pfqn_lcp(L, Nalt, Z, type, tol, maxiter, QN0);
84 for (std::size_t i = 0; i < M; ++i) {
85 T qalt = zero;
86 for (std::size_t s = 0; s < R; ++s) qalt += alt.QN(i, s);
87 const T ref = (variant == ChowVariant::Backward) ? Qtot[i] : qalt;
88 const T delta = (variant == ChowVariant::Backward) ? T(qalt - Qtot[i])
89 : T(Qtot[i] - qalt);
90 if (ref > zero) theta(i, cls) = delta / ref;
91 }
92 }
93
94 // fixed point with A_k^(c) = Q_k (1 + theta_ck)
96 r.XN.assign(R, zero);
97 r.QN = Matrix<T>(M, R, zero);
98 r.UN = Matrix<T>(M, R, zero);
99 r.RN = Matrix<T>(M, R, zero);
100 Matrix<T> CN(M, R, zero);
101 if (M == 0) return r;
102 if (!QN0.empty()) {
103 if (QN0.rows() != M || QN0.cols() != R)
104 throw InputError("pfqn_chow: QN0 has the wrong shape");
105 r.QN = QN0;
106 } else {
107 for (std::size_t i = 0; i < M; ++i)
108 for (std::size_t s = 0; s < R; ++s)
109 r.QN(i, s) = N[s] / num_traits<T>::from_int(static_cast<long>(M));
110 }
111
112 for (std::size_t it = 1; it <= maxiter; ++it) {
113 r.iterations = it;
114 const Matrix<T> Qprev = r.QN;
115 for (std::size_t cls = 0; cls < R; ++cls) {
116 if (N[cls] == zero) {
117 r.XN[cls] = zero;
118 for (std::size_t i = 0; i < M; ++i) {
119 CN(i, cls) = zero;
120 r.QN(i, cls) = zero;
121 r.UN(i, cls) = zero;
122 }
123 continue;
124 }
125 T ctot = Z.empty() ? zero : Z[cls];
126 for (std::size_t i = 0; i < M; ++i) {
127 CN(i, cls) = L(i, cls);
128 if (L(i, cls) == zero) continue;
129 const bool fcfs = !type.empty() && type[i] == AmvaSched::FCFS;
130 const T infl = T(one + theta(i, cls));
131 for (std::size_t s = 0; s < R; ++s) {
132 if (fcfs && s != cls)
133 CN(i, cls) += L(i, s) * r.QN(i, s) * infl;
134 else
135 CN(i, cls) += L(i, cls) * r.QN(i, s) * infl;
136 }
137 // a theta below -1 would make the arrival-instant queue negative
138 if (CN(i, cls) < L(i, cls)) CN(i, cls) = L(i, cls);
139 ctot += CN(i, cls);
140 }
141 if (ctot == zero) throw NumericError("pfqn_chow: zero total residence time");
142 r.XN[cls] = N[cls] / ctot;
143 }
144 for (std::size_t cls = 0; cls < R; ++cls)
145 for (std::size_t i = 0; i < M; ++i) {
146 r.QN(i, cls) = r.XN[cls] * CN(i, cls);
147 r.UN(i, cls) = r.XN[cls] * L(i, cls);
148 }
149
150 double delta = 0.0;
151 for (std::size_t cls = 0; cls < R; ++cls) {
152 if (N[cls] == zero) continue;
153 for (std::size_t i = 0; i < M; ++i) {
154 if (Qprev(i, cls) == zero) {
155 if (r.QN(i, cls) == zero) continue;
156 delta = std::numeric_limits<double>::infinity();
157 continue;
158 }
159 const double d =
160 std::fabs(num_traits<T>::to_double(T(one - r.QN(i, cls) / Qprev(i, cls))));
161 if (d > delta) delta = d;
162 }
163 }
164 if (delta < tol) {
165 r.converged = true;
166 break;
167 }
168 }
169
170 for (std::size_t cls = 0; cls < R; ++cls)
171 for (std::size_t i = 0; i < M; ++i)
172 r.RN(i, cls) = (N[cls] == zero) ? zero : r.QN(i, cls) / r.XN[cls];
173 return r;
174}
175
176template <class T>
177AmvaResult<T> pfqn_chow(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z) {
178 return pfqn_chow(L, N, Z, std::vector<AmvaSched>());
179}
180
181template <class T>
182AmvaResult<T> pfqn_chow(const Matrix<T>& L, const std::vector<T>& N) {
183 return pfqn_chow(L, N, std::vector<T>(), std::vector<AmvaSched>());
184}
185
186} // namespace pfqn
187} // namespace line
188
189#endif // LINE_API_PFQN_CHOW_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.
ChowVariant
Which finite difference of the LCP solution estimates the theta-terms.
Definition pfqn_chow.h:46
AmvaResult< T > pfqn_chow(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 >(), ChowVariant variant=ChowVariant::Forward)
Chow Second Approximation (SA) approximate MVA.
Definition pfqn_chow.h:61
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.
Bard Large Customer Population (LCP) 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