LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_conv.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_CONV_H
6#define LINE_API_PFQN_CONV_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Multichain convolution algorithm with class-dependent service rates
12 * (Sauer 1983, "Computational Algorithms for State-Dependent Queueing
13 * Networks", ACM TOCS 1(1):67-92, Section 5.2).
14 *
15 * Templated port of matlab/src/api/pfqn/pfqn_conv.m.
16 *
17 * G(N) is the multivariate discrete convolution of the M station factors and
18 * the delay factor,
19 *
20 * G_0(n) = F_Z(n) = prod_r Z_r^{n_r} / n_r!,
21 * G_m(n) = sum_{0 <= i <= n} X_m(i) G_{m-1}(n - i),
22 *
23 * where a class-dependent station builds its factor from Sauer eq. (40),
24 *
25 * X_m(n) = (|n| / n_r) (L(m,r) / beta_{m,r}(n)) X_m(n - e_r), X_m(0) = 1,
26 *
27 * for the first class r with n_r > 0. beta is the DIMENSIONLESS scaling of the
28 * service demand supplied by the caller, so beta = 1 means "no correction" and
29 * the recurrence collapses to the load-independent multinomial form. A station
30 * with no scaling callable is folded in by the classical in-place Buzen update
31 *
32 * G_m(n) = G_{m-1}(n) + sum_r L(m,r) G_m(n - e_r)
33 *
34 * in O(P R) rather than O(P^2), P = prod_r (N_r + 1), so a model with no
35 * class-dependent station reduces operation for operation to pfqn_ca on the
36 * same demands and returns the identical value.
37 *
38 * Arithmetic: EXACT-CAPABLE. Every operation is an addition, a multiplication
39 * or a division in the field of the inputs; the reference's use of log/exp
40 * inside its local Fz is a range-management device for the delay factor and is
41 * replaced here by the same detail::pff_delay that pfqn_ca uses. Whether the
42 * result is exact for a class-dependent station is a property of the supplied
43 * beta callables, which are evaluated but never inspected. Note that unlike
44 * pfqn_ca this routine applies NO power-of-two rescaling in double: the
45 * reference does not, and a class-dependent station factor is not homogeneous
46 * in the demands once beta is state dependent, so no exact exponent shift
47 * exists in general. Use T = Real<D> or T = Rational when the constant leaves
48 * the double range.
49 */
50
51#include <cstddef>
52#include <vector>
53
56#include "line/num/number.h"
57#include "line/util/error.h"
58#include "line/util/matrix.h"
60
61namespace line {
62namespace pfqn {
63
64/**
65 * @brief Multichain convolution algorithm with class-dependent service rates
66 * (Sauer 1983, "Computational Algorithms for State-Dependent Queueing
67 * Networks", ACM TOCS 1(1):67-92, Section 5.2).
68 *
69 * @param L (M x R) service demands
70 * @param N (R) population per class, finite
71 * @param Z (K x R) think times, summed over rows; may be empty
72 * @param cdscaling (M) class-dependence callables; an empty entry marks a
73 * load-independent station. Pass an empty vector for none.
74 */
75template <class T>
76NcResult<T> pfqn_conv(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z,
77 const std::vector<CdScaling<T>>& cdscaling) {
78 const std::size_t M = L.empty() ? 0 : L.rows();
79 const std::size_t R = N.size();
80 if (!L.empty() && L.cols() != R)
81 throw InputError("pfqn_conv: L and N disagree on the class count");
82 if (!cdscaling.empty() && cdscaling.size() != M)
83 throw InputError("pfqn_conv: the scaling vector has the wrong station count");
84 for (int v : N)
85 if (v < 0) throw InputError("pfqn_conv: the convolution algorithm requires finite, "
86 "nonnegative (closed) populations");
87
88 const T zero = num_traits<T>::from_int(0);
89 const T one = num_traits<T>::from_int(1);
90
91 std::vector<T> Zsum(R, zero);
92 if (!Z.empty()) {
93 if (Z.cols() != R) throw InputError("pfqn_conv: Z and N disagree on the class count");
94 for (std::size_t k = 0; k < Z.rows(); ++k)
95 for (std::size_t r = 0; r < R; ++r) Zsum[r] += Z(k, r);
96 }
97
98 const std::vector<std::size_t> prods = plane_sizes(N);
99 const std::size_t total = population_count(N);
100
101 // ---- G_0(n) = F_Z(n) ----------------------------------------------------
102 std::vector<T> G(total, zero);
103 {
104 std::vector<int> n(R, 0);
105 bool more = true;
106 while (more) {
107 G[pop_index(n, prods)] = detail::pff_delay(Zsum, n);
108 more = next_pop(n, N);
109 }
110 }
111
112 // ---- fold in one station at a time --------------------------------------
113 for (std::size_t ist = 0; ist < M; ++ist) {
114 const bool isCd = !cdscaling.empty() && static_cast<bool>(cdscaling[ist]);
115 if (!isCd) {
116 // Load-independent: in-place Buzen update, lexicographic order
117 // guarantees n - e_r is already at its new value.
118 std::vector<int> n(R, 0);
119 bool more = true;
120 while (more) {
121 const std::size_t idx = pop_index(n, prods);
122 T acc = G[idx];
123 for (std::size_t r = 0; r < R; ++r)
124 if (n[r] >= 1) acc += L(ist, r) * G[idx - prods[r]];
125 G[idx] = acc;
126 more = next_pop(n, N);
127 }
128 continue;
129 }
130
131 // Class-dependent: build the station factor X_m, then convolve.
132 std::vector<T> Xm(total, zero);
133 Xm[0] = one;
134 {
135 std::vector<int> n(R, 0);
136 bool more = next_pop(n, N); // X_m(0) is already set
137 while (more) {
138 const std::size_t idx = pop_index(n, prods);
139 std::size_t r = 0;
140 while (r < R && n[r] == 0) ++r;
141 // next_pop only yields nonzero vectors past the origin, so r < R.
142 std::vector<T> row(R);
143 for (std::size_t s = 0; s < R; ++s)
144 row[s] = num_traits<T>::from_int(n[s]);
145 const std::vector<T> bval = cdscaling[ist](row);
146 if (bval.empty())
147 throw InputError("pfqn_conv: a class-dependence callable returned nothing");
148 const T beta = bval.size() > 1 ? bval.at(r) : bval[0];
149 if (beta > zero) {
150 int tot = 0;
151 for (int v : n) tot += v;
152 const T fac = num_traits<T>::from_int(tot) / num_traits<T>::from_int(n[r]);
153 Xm[idx] = fac * (L(ist, r) / beta) * Xm[idx - prods[r]];
154 } else {
155 Xm[idx] = zero; // a nonpositive scaling kills the state
156 }
157 more = next_pop(n, N);
158 }
159 }
160
161 std::vector<T> Gold(G);
162 std::vector<int> n(R, 0);
163 bool more = true;
164 while (more) {
165 const std::size_t idxn = pop_index(n, prods);
166 T acc = zero;
167 // Inner sweep over 0 <= i <= n.
168 std::vector<int> i(R, 0);
169 bool more_i = true;
170 while (more_i) {
171 std::size_t idx_i = 0, idx_nmi = 0;
172 for (std::size_t r = 0; r < R; ++r) {
173 idx_i += prods[r] * static_cast<std::size_t>(i[r]);
174 idx_nmi += prods[r] * static_cast<std::size_t>(n[r] - i[r]);
175 }
176 acc += Xm[idx_i] * Gold[idx_nmi];
177 more_i = next_pop(i, n);
178 }
179 G[idxn] = acc;
180 more = next_pop(n, N);
181 }
182 }
183
184 const T Gn = G[total - 1];
185 return {Gn, num_traits<T>::log_as_double(Gn)};
186}
187
188/** Overload with no class dependence, i.e. plain multichain convolution. */
189template <class T>
190NcResult<T> pfqn_conv(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z) {
191 return pfqn_conv(L, N, Z, std::vector<CdScaling<T>>());
192}
193
194template <class T>
195NcResult<T> pfqn_conv(const Matrix<T>& L, const std::vector<int>& N) {
196 return pfqn_conv(L, N, Matrix<T>(), std::vector<CdScaling<T>>());
197}
198
199} // namespace pfqn
200} // namespace line
201
202#endif // LINE_API_PFQN_CONV_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
bool empty() const
Definition matrix.h:92
The exception types the port throws.
Dense matrix and non-owning view.
std::function< std::vector< T >(const std::vector< T > &)> CdScaling
A per-station class-dependence callable: the population row -> 1 or R rates.
Definition pfqn_cdfun.h:45
NcResult< T > pfqn_conv(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< CdScaling< T > > &cdscaling)
Multichain convolution algorithm with class-dependent service rates (Sauer 1983, "Computational Algor...
Definition pfqn_conv.h:76
std::size_t population_count(const std::vector< int > &N)
Number of population vectors n with 0 <= n <= N.
Definition population.h:38
std::vector< std::size_t > plane_sizes(const std::vector< int > &N)
Mixed-radix plane sizes: prods[r] = prod_{s<r} (N[s]+1).
Definition population.h:27
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
std::size_t pop_index(const std::vector< int > &n, const std::vector< std::size_t > &prods)
Index of n in the lattice, 0-based (MATLAB hashpop is 1-based).
Definition population.h:45
Number-type abstraction for the templated API port.
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
AMVA-QD class-dependence function.
Population-vector enumeration and combinatorics.
Return value of the normalizing-constant family, mirroring Ret.pfqnNc.
Definition pfqn_ca.h:44