LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_aql.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_AQL_H
6#define LINE_API_PFQN_AQL_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Aggregate Queue Length (AQL) approximate MVA.
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_aql.m. AQL solves K+1 coupled
14 * populations at once: the full population N and each N - e_s. The arrival
15 * estimate is
16 * R(k,s|n) = L(k,s) [1 + (|n| - 1) (Q(k|n)/|n| - gamma(k,s))]
17 * with the correction gamma(k,s) = Q(k|N)/|N| - Q(k|N - e_s)/(|N| - 1)
18 * refreshed after every sweep. The aggregate queue length Q(k|n) is kept per
19 * population rather than per class, which is what distinguishes AQL from
20 * Linearizer and makes it cheaper by a factor of the class count.
21 *
22 * Iterates to a relative tolerance, so exact arithmetic buys nothing: the
23 * static_assert records that.
24 */
25
26#include <cmath>
27#include <cstddef>
28#include <vector>
29
31#include "line/num/number.h"
32#include "line/util/error.h"
33#include "line/util/matrix.h"
34
35namespace line {
36namespace pfqn {
37
38/**
39 * @brief Aggregate Queue Length (AQL) approximate MVA.
40 *
41 * @param L (M x K) demands
42 * @param N (K) populations
43 * @param Z (K) think times, empty for none
44 * @param tol convergence tolerance
45 * @param maxiter iteration cap
46 * @return the standard AMVA metrics; AN holds the arrival-instant aggregate
47 * queue lengths Q(k | N - e_s), which callers use for the arrival
48 * theorem diagnostics
49 */
50template <class T>
51AmvaResult<T> pfqn_aql(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z,
52 double tol = 1e-7, std::size_t maxiter = 1000) {
54 "pfqn_aql requires transcendental arithmetic: it iterates to a relative "
55 "tolerance, so its answer is a fixed point only to within tol");
56 const std::size_t M = L.rows(), K = L.cols();
57 if (N.size() != K) throw InputError("pfqn_aql: L and N disagree on the class count");
58 if (!Z.empty() && Z.size() != K) throw InputError("pfqn_aql: Z has the wrong length");
59
60 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
61 T Ntot = zero;
62 for (const T& v : N) Ntot += v;
63
64 AmvaResult<T> out;
65 out.XN.assign(K, zero);
66 out.QN = Matrix<T>(M, K, zero);
67 out.UN = Matrix<T>(M, K, zero);
68 out.RN = Matrix<T>(M, K, zero);
69 if (M == 0 || Ntot == zero) return out;
70
71 // Populations: index 0 is N, index s+1 is N - e_s.
72 std::vector<std::vector<T>> pops(K + 1, N);
73 for (std::size_t s = 0; s < K; ++s)
74 pops[s + 1][s] = (N[s] > zero) ? N[s] - one : zero;
75
76 std::vector<std::vector<T>> Q(K + 1, std::vector<T>(M, zero));
77 std::vector<Matrix<T>> R(K + 1, Matrix<T>(M, K, zero));
78 std::vector<std::vector<T>> X(K + 1, std::vector<T>(K, zero));
79 Matrix<T> gamma(M, K, zero);
80
81 for (std::size_t t = 0; t <= K; ++t)
82 for (std::size_t k = 0; k < M; ++k)
83 Q[t][k] = Ntot / num_traits<T>::from_int(static_cast<long>(M));
84
85 for (std::size_t it = 1; it <= maxiter; ++it) {
86 out.iterations = it;
87 const std::vector<T> Qprev = Q[0];
88
89 for (std::size_t t = 0; t <= K; ++t) {
90 const std::vector<T>& n = pops[t];
91 T ntot = zero;
92 for (const T& v : n) ntot += v;
93 if (ntot == zero) {
94 for (std::size_t k = 0; k < M; ++k) Q[t][k] = zero;
95 for (std::size_t s = 0; s < K; ++s) X[t][s] = zero;
96 continue;
97 }
98 for (std::size_t k = 0; k < M; ++k)
99 for (std::size_t s = 0; s < K; ++s)
100 R[t](k, s) = L(k, s) * (one + (ntot - one) * (Q[t][k] / ntot - gamma(k, s)));
101 for (std::size_t s = 0; s < K; ++s) {
102 T denom = Z.empty() ? zero : Z[s];
103 for (std::size_t k = 0; k < M; ++k) denom += R[t](k, s);
104 X[t][s] = (denom == zero) ? zero : n[s] / denom;
105 }
106 for (std::size_t k = 0; k < M; ++k) {
107 T q = zero;
108 for (std::size_t s = 0; s < K; ++s) q += X[t][s] * R[t](k, s);
109 Q[t][k] = q;
110 }
111 }
112
113 if (Ntot > one)
114 for (std::size_t k = 0; k < M; ++k)
115 for (std::size_t s = 0; s < K; ++s)
116 gamma(k, s) = Q[0][k] / Ntot - Q[s + 1][k] / (Ntot - one);
117
118 double delta = 0.0;
119 for (std::size_t k = 0; k < M; ++k) {
120 if (Q[0][k] == zero) continue;
121 const double d = std::fabs(num_traits<T>::to_double(T((Qprev[k] - Q[0][k]) / Q[0][k])));
122 if (d > delta) delta = d;
123 }
124 if (delta < tol) {
125 out.converged = true;
126 break;
127 }
128 }
129
130 out.XN = X[0];
131 out.RN = R[0];
132 for (std::size_t k = 0; k < M; ++k)
133 for (std::size_t s = 0; s < K; ++s) {
134 out.UN(k, s) = out.XN[s] * L(k, s);
135 out.QN(k, s) = out.UN(k, s) * (one + Q[s + 1][k]);
136 }
137 return out;
138}
139
140template <class T>
141AmvaResult<T> pfqn_aql(const Matrix<T>& L, const std::vector<T>& N) {
142 return pfqn_aql(L, N, std::vector<T>());
143}
144
145} // namespace pfqn
146} // namespace line
147
148#endif // LINE_API_PFQN_AQL_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.
AmvaResult< T > pfqn_aql(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, double tol=1e-7, std::size_t maxiter=1000)
Aggregate Queue Length (AQL) approximate MVA.
Definition pfqn_aql.h:51
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