LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_pam.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_PAM_H
6#define LINE_API_PFQN_PAM_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Hsieh-Lam Proportional Approximation Methods (PAMB / PAMI / PAMT).
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_pam.m, cross-checked against
14 * jar/src/main/java/jline/api/pfqn/mva/Pfqn_pam.java. C. T. Hsieh, S. S. Lam,
15 * "PAM - A noniterative approximate solution method for closed multichain
16 * queueing networks", ACM SIGMETRICS Perform. Eval. Rev. 16(1), 1988. The
17 * three variants are NONITERATIVE: the queue lengths are seeded by the
18 * proportion of a class demand that falls at each centre,
19 *
20 * E_ck = D_ck / sum_i D_ci, Q_ck(N) = E_ck N_c,
21 *
22 * and the MVA equations are then unrolled a fixed number of times. PAMB
23 * applies the last MVA step; PAMI additionally scales a class down wherever it
24 * would drive a centre past full utilization; PAMT seeds at N - 1_i - 1_j and
25 * applies the last TWO MVA steps before that capping.
26 *
27 * The seed spreads the whole class population over the queueing centres and
28 * ignores Z, exactly as published: PAM buys speed, not accuracy.
29 *
30 * Arithmetic: sums, products, divisions and comparisons only, and there is no
31 * fixed point, so the result is EXACT in rational arithmetic.
32 */
33
34#include <cstddef>
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/** Which of the three proportional approximations to run. */
46enum class PamVariant { Basic, Improved, Two };
47
48/**
49 * @brief Hsieh-Lam Proportional Approximation Methods (PAMB / PAMI / PAMT).
50 *
51 * @param L (M x R) demands, @param N (R) populations,
52 * @param Z (R) think times (empty for none), @param variant PAMB/PAMI/PAMT
53 */
54template <class T>
55AmvaResult<T> pfqn_pam(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z,
56 PamVariant variant = PamVariant::Basic) {
57 const std::size_t M = L.rows(), R = L.cols();
58 if (N.size() != R) throw InputError("pfqn_pam: L and N disagree on the class count");
59 if (!Z.empty() && Z.size() != R) throw InputError("pfqn_pam: Z has the wrong length");
60
61 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
63 r.XN.assign(R, zero);
64 r.QN = Matrix<T>(M, R, zero);
65 r.UN = Matrix<T>(M, R, zero);
66 r.RN = Matrix<T>(M, R, zero);
67 r.iterations = 1;
68 r.converged = true;
69 if (M == 0) return r;
70
71 // E_ck, the share of the class-c demand served at centre k
72 Matrix<T> E(M, R, zero);
73 for (std::size_t s = 0; s < R; ++s) {
74 T tot = zero;
75 for (std::size_t i = 0; i < M; ++i) tot += L(i, s);
76 if (tot > zero)
77 for (std::size_t i = 0; i < M; ++i) E(i, s) = L(i, s) / tot;
78 }
79 Matrix<T> Q(M, R, zero);
80 for (std::size_t i = 0; i < M; ++i)
81 for (std::size_t s = 0; s < R; ++s) Q(i, s) = E(i, s) * N[s];
82
83 if (variant == PamVariant::Two) {
84 for (std::size_t i = 0; i < R; ++i) {
85 Matrix<T> Qmi(M, R, zero); // Q_jk(N - 1_i)
86 for (std::size_t j = 0; j < R; ++j) {
87 // Q_ck(N - 1_i - 1_j) = Q_ck(N) - E_ck [(c==i) + (c==j)]
88 std::vector<T> Rj(M, zero);
89 T rtot = zero;
90 for (std::size_t k = 0; k < M; ++k) {
91 T agg = zero;
92 for (std::size_t c = 0; c < R; ++c) {
93 T q = Q(k, c);
94 if (c == i) q -= E(k, c);
95 if (c == j) q -= E(k, c);
96 agg += q;
97 }
98 Rj[k] = L(k, j) * T(one + agg);
99 rtot += Rj[k];
100 }
101 const T nj = (i == j) ? T(N[j] - one) : N[j];
102 const T zj = Z.empty() ? zero : Z[j];
103 const T den = T(rtot + zj);
104 const T Xj = (nj > zero && den > zero) ? T(nj / den) : zero;
105 for (std::size_t k = 0; k < M; ++k) Qmi(k, j) = Xj * Rj[k];
106 }
107 T rtot = zero;
108 for (std::size_t k = 0; k < M; ++k) {
109 T agg = zero;
110 for (std::size_t c = 0; c < R; ++c) agg += Qmi(k, c);
111 r.RN(k, i) = L(k, i) * T(one + agg);
112 rtot += r.RN(k, i);
113 }
114 const T zi = Z.empty() ? zero : Z[i];
115 if (N[i] > zero && T(rtot + zi) > zero) r.XN[i] = N[i] / T(rtot + zi);
116 }
117 } else {
118 for (std::size_t s = 0; s < R; ++s) {
119 // Q_jk(N - 1_s) = Q_jk(N) - E_jk [j == s]
120 T rtot = zero;
121 for (std::size_t k = 0; k < M; ++k) {
122 T agg = zero;
123 for (std::size_t c = 0; c < R; ++c) agg += Q(k, c) - (c == s ? E(k, c) : zero);
124 r.RN(k, s) = L(k, s) * T(one + agg);
125 rtot += r.RN(k, s);
126 }
127 const T zs = Z.empty() ? zero : Z[s];
128 if (N[s] > zero && T(rtot + zs) > zero) r.XN[s] = N[s] / T(rtot + zs);
129 }
130 }
131
132 if (variant != PamVariant::Basic) {
133 // scale a class down when it would drive a centre it visits past U = 1
134 std::vector<T> U(M, zero);
135 for (std::size_t k = 0; k < M; ++k)
136 for (std::size_t c = 0; c < R; ++c) U[k] += L(k, c) * r.XN[c];
137 for (std::size_t s = 0; s < R; ++s) {
138 bool any = false;
139 T best = zero;
140 for (std::size_t k = 0; k < M; ++k) {
141 if (L(k, s) == zero) continue;
142 if (!any || U[k] > best) {
143 best = U[k];
144 any = true;
145 }
146 }
147 if (any && best > one) r.XN[s] = r.XN[s] / best;
148 }
149 }
150
151 for (std::size_t k = 0; k < M; ++k)
152 for (std::size_t s = 0; s < R; ++s) {
153 r.QN(k, s) = r.XN[s] * r.RN(k, s);
154 r.UN(k, s) = r.XN[s] * L(k, s);
155 }
156 return r;
157}
158
159template <class T>
160AmvaResult<T> pfqn_pam(const Matrix<T>& L, const std::vector<T>& N) {
161 return pfqn_pam(L, N, std::vector<T>(), PamVariant::Basic);
162}
163
164} // namespace pfqn
165} // namespace line
166
167#endif // LINE_API_PFQN_PAM_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_pam(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, PamVariant variant=PamVariant::Basic)
Hsieh-Lam Proportional Approximation Methods (PAMB / PAMI / PAMT).
Definition pfqn_pam.h:55
PamVariant
Which of the three proportional approximations to run.
Definition pfqn_pam.h:46
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