LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fes_compute_throughputs.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_FES_COMPUTE_THROUGHPUTS_H
6#define LINE_API_FES_COMPUTE_THROUGHPUTS_H
7
8/**
9 * @file
10 * @ingroup api_fes
11 * Per-class throughput table of an isolated subnetwork, tabulated over the
12 * population lattice, for use as the load-dependent rates of a
13 * flow-equivalent server (Chandy, Herzog and Woo 1975).
14 *
15 * Templated port of matlab/src/api/fes/fes_compute_throughputs.m. Every
16 * population state 0 <= n <= cutoffs is solved with pfqn_mva on the isolated
17 * subnetwork; scalingTable[r][idx-1] holds X_r(n) at the LJD_LINEARIZE index
18 * idx of n. States with n_r = 0 store 0 for class r, and the empty state
19 * stores 0 for every class.
20 *
21 * Arithmetic. pfqn_mva stays in the field of the inputs, and this function
22 * only enumerates and stores, so the table is exact at T = Rational.
23 *
24 * Deviation from MATLAB, mechanical: MATLAB walks the lattice with a BFS over
25 * a cell-array queue guarded by a visited mask, which reaches every state
26 * exactly once; the port enumerates the same lattice directly with next_pop.
27 * The states, and hence the table, are identical; only the visit order
28 * differs, and each state is solved independently of the others. The MATLAB
29 * try/catch that stores zeros when the solver fails is mirrored by catching
30 * line::Error.
31 */
32
33#include <cstddef>
34#include <vector>
35
39#include "line/num/number.h"
40#include "line/util/error.h"
41#include "line/util/matrix.h"
43
44namespace line {
45namespace fes {
46
47/**
48 * @brief Per-class throughput table of an isolated subnetwork, tabulated over
49 * the population lattice, for use as the load-dependent rates of a
50 * flow-equivalent server (Chandy, Herzog and Woo 1975).
51 *
52 * @param L (M_sub x K) service demands of the isolated subnetwork
53 * @param mi (M_sub) servers per station; entries of delay stations are
54 * ignored (MATLAB stores Inf there)
55 * @param isDelay (M_sub) true where the station is a pure delay
56 * @param cutoffs (K) per-class population cutoffs
57 * @return one linearized throughput vector per class, each of length
58 * prod_k (cutoffs[k] + 1)
59 */
60template <class T>
61std::vector<std::vector<T>> fes_compute_throughputs(const Matrix<T>& L, const std::vector<int>& mi,
62 const std::vector<bool>& isDelay,
63 const std::vector<int>& cutoffs) {
64 const std::size_t M_sub = L.rows();
65 const std::size_t K = L.cols();
66 if (isDelay.size() != M_sub)
67 throw InputError("fes_compute_throughputs: isDelay has the wrong length");
68 if (!mi.empty() && mi.size() != M_sub)
69 throw InputError("fes_compute_throughputs: mi has the wrong length");
70 if (cutoffs.size() != K)
71 throw InputError("fes_compute_throughputs: cutoffs and demands disagree on the class count");
72
73 const T zero = num_traits<T>::from_int(0);
74
75 // Queue stations go into L_queue; delay stations contribute their demands
76 // to the think time Z.
77 std::vector<std::size_t> queueIdx, delayIdx;
78 for (std::size_t i = 0; i < M_sub; ++i) (isDelay[i] ? delayIdx : queueIdx).push_back(i);
79 const std::size_t M_queue = queueIdx.size();
80
81 Matrix<T> L_queue(M_queue, K, zero);
82 std::vector<int> mi_queue;
83 for (std::size_t a = 0; a < M_queue; ++a) {
84 for (std::size_t k = 0; k < K; ++k) L_queue(a, k) = L(queueIdx[a], k);
85 mi_queue.push_back(mi.empty() ? 1 : mi[queueIdx[a]]);
86 }
87 Matrix<T> Z(1, K, zero);
88 for (std::size_t d : delayIdx)
89 for (std::size_t k = 0; k < K; ++k) Z(0, k) += L(d, k);
90
91 std::size_t tableSize = 1;
92 for (int c : cutoffs) {
93 if (c < 0) throw InputError("fes_compute_throughputs: negative cutoff");
94 tableSize *= static_cast<std::size_t>(c + 1);
95 }
96 std::vector<std::vector<T>> scalingTable(K, std::vector<T>(tableSize, zero));
97
98 std::vector<int> nvec(K, 0);
99 bool more = true;
100 while (more) {
101 const std::size_t idx = ljd_linearize(nvec, cutoffs);
102 int totalPop = 0;
103 for (int v : nvec) totalPop += v;
104 if (totalPop > 0) {
105 std::vector<T> XN(K, zero);
106 if (M_queue > 0) {
107 // pfqn_mva's `mi` is NOT a server count -- it enters only as the
108 // additive term of C(i,s)=L(i,s)*(mi(i)+Qarv), so passing the real
109 // multiplicity INFLATES the residence time instead of adding
110 // servers. pfqn_mvams forwards to pfqn_mva when every station is a
111 // single server and to the load-dependent recursion with
112 // mu(i,n)=min(n,S(i)) when one is not.
113 // See _kb/03-api-layer.md (pfqn_mva: mi is not S).
114 const pfqn::MvaResult<T> r =
115 pfqn::pfqn_mvams(std::vector<T>(K, zero), L_queue, nvec, Z,
116 std::vector<int>(), mi_queue);
117 XN = r.XN;
118 } else {
119 // only delay stations: X_k = n_k / Z_k
120 for (std::size_t k = 0; k < K; ++k)
121 if (nvec[k] > 0 && Z(0, k) > zero)
122 XN[k] = num_traits<T>::from_int(nvec[k]) / Z(0, k);
123 }
124 // A failure here is NOT caught: an FES table silently filled with
125 // zeros is a wrong aggregate, not a degraded one, and every
126 // downstream beta_r(n) reads it as "the subnetwork serves nothing".
127 for (std::size_t k = 0; k < K; ++k)
128 scalingTable[k][idx - 1] = nvec[k] > 0 ? XN[k] : zero;
129 }
130 more = next_pop(nvec, cutoffs);
131 }
132 return scalingTable;
133}
134
135} // namespace fes
136} // namespace line
137
138#endif // LINE_API_FES_COMPUTE_THROUGHPUTS_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.
Linearized index of a per-class population vector, for Limited Joint Dependence (LJD) tables.
Dense matrix and non-owning view.
std::vector< std::vector< T > > fes_compute_throughputs(const Matrix< T > &L, const std::vector< int > &mi, const std::vector< bool > &isDelay, const std::vector< int > &cutoffs)
Per-class throughput table of an isolated subnetwork, tabulated over the population lattice,...
std::size_t ljd_linearize(const std::vector< int > &nvec, const std::vector< int > &cutoffs)
Linearized index of a per-class population vector, for Limited Joint Dependence (LJD) tables.
MvaResult< T > pfqn_mvams(const std::vector< T > &lambda, const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< int > &mi, const std::vector< int > &S)
General-purpose exact MVA for mixed networks with multiserver stations.
Definition pfqn_mvams.h:840
bool next_pop(std::vector< int > &n, const std::vector< int > &N)
Advance n to the next population vector in the lattice 0 <= n <= N, odometer order with the last clas...
Definition population.h:56
Number-type abstraction for the templated API port.
Exact Mean Value Analysis for closed product-form networks (Reiser and Lavenberg 1980).
Exact Mean Value Analysis for mixed open/closed networks with multiserver stations.
Population-vector enumeration and combinatorics.
std::vector< T > XN
(R) per-class throughput
Definition pfqn_mva.h:45