LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_gldsingle.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_GLDSINGLE_H
6#define LINE_API_PFQN_GLDSINGLE_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Exact normalizing constant of a SINGLE-CLASS closed network whose stations
12 * are load dependent.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_gldsingle.m.
15 *
16 * The recursion carries a rate offset t alongside the station index m and the
17 * population n, so that the jobs already placed at station m shift its rate
18 * lattice without materializing a separate shifted matrix:
19 *
20 * g(0, n, t) = 0 for n >= 1
21 * g(m, 0, t) = 1
22 * g(m, n, t) = g(m-1, n, 1) + L(m) g(m, n-1, t+1) / mu(m, t)
23 * G = g(M, N, 1)
24 *
25 * This is the single-class specialization of pfqn_gld; the two agree to the
26 * last bit on every model both accept, and the specialization is kept because
27 * pfqn_ncld dispatches to it directly for R = 1 and because it costs O(M N^2)
28 * rather than going through the general convolution.
29 *
30 * Arithmetic: EXACT-CAPABLE. MATLAB carries TWO implementations of the same
31 * recursion, a linear one and a log-space one selected by
32 *
33 * useLog = isreal(L) && isreal(mu) && all(L>=0) && all(mu>0)
34 *
35 * with a pairwise log-sum-exp replacing the addition. That branch is purely a
36 * range-management device for IEEE double -- the comment in the reference says
37 * so explicitly, citing underflow of the delay term to realmin at N >= 190 --
38 * and the two branches compute the same mathematical quantity. This port keeps
39 * only the linear recursion, which is exact in any field: at T = Rational
40 * there is no underflow to manage, and at T = Real<D> the exponent range is
41 * wide enough that the models which drove the reference into log space stay in
42 * range. Callers that genuinely need the double path on such a model should
43 * raise the arithmetic rather than reintroduce the logs, since the log-sum-exp
44 * form cannot represent the negative intermediate rates that the reference's
45 * own `useLog` guard exists to fall back from.
46 *
47 * An infinite rate is accepted the same way the reference accepts it: the term
48 * L/mu vanishes. In an exact field there is no infinity, so a caller expressing
49 * "this station cannot hold this many jobs" must pass a zero DEMAND instead.
50 */
51
52#include <cstddef>
53#include <vector>
54
56#include "line/num/number.h"
57#include "line/util/error.h"
58#include "line/util/matrix.h"
59
60namespace line {
61namespace pfqn {
62
63/**
64 * @brief Exact normalizing constant of a SINGLE-CLASS closed network whose
65 * stations are load dependent.
66 *
67 * @param L (M x 1) service demands, one class
68 * @param N population
69 * @param mu (M x >=N) load-dependent rates, mu(i,k) with k jobs at station i
70 */
71template <class T>
72NcResult<T> pfqn_gldsingle(const Matrix<T>& L, int N, const Matrix<T>& mu) {
73 if (!L.empty() && L.cols() != 1)
74 throw InputError("pfqn_gldsingle: multiclass model detected, this routine is single class");
75 if (N < 0) throw InputError("pfqn_gldsingle: negative population");
76
77 const std::size_t M = L.empty() ? 0 : L.rows();
78 const T zero = num_traits<T>::from_int(0);
79 const T one = num_traits<T>::from_int(1);
80
81 if (N == 0) return {one, 0.0};
82 if (M == 0) return {zero, num_traits<T>::log_as_double(zero)};
83 if (mu.rows() != M) throw InputError("pfqn_gldsingle: mu has the wrong station count");
84 if (static_cast<int>(mu.cols()) < N)
85 throw InputError("pfqn_gldsingle: mu has fewer rate columns than the population");
86
87 const std::size_t Nu = static_cast<std::size_t>(N);
88 // g[m][n][t], t = 1 .. N+2 stored at index t; index 0 unused. The station
89 // index m runs 0 .. M with m = 0 the empty network.
90 const std::size_t Tdim = Nu + 3;
91 std::vector<T> g(static_cast<std::size_t>(M + 1) * (Nu + 1) * Tdim, zero);
92 const auto at = [&](std::size_t m, std::size_t n, std::size_t t) -> T& {
93 return g[(m * (Nu + 1) + n) * Tdim + t];
94 };
95
96 // g(0, n, t) = 0 for n >= 1 (already zero); g(0, 0, t) is never read.
97 for (std::size_t m = 1; m <= M; ++m) {
98 for (std::size_t t = 1; t <= Nu + 1; ++t) at(m, 0, t) = one;
99 for (std::size_t n = 1; n <= Nu; ++n) {
100 for (std::size_t t = 1; t + n <= Nu + 1; ++t) {
101 const T& rate = mu(m - 1, t - 1);
102 if (rate == zero)
103 throw NumericError(
104 "pfqn_gldsingle: a load-dependent rate is zero, the station cannot serve "
105 "and the normalizing constant diverges");
106 at(m, n, t) = at(m - 1, n, 1) + L(m - 1, 0) * at(m, n - 1, t + 1) / rate;
107 }
108 }
109 }
110
111 const T G = at(M, Nu, 1);
112 return {G, num_traits<T>::log_as_double(G)};
113}
114
115} // namespace pfqn
116} // namespace line
117
118#endif // LINE_API_PFQN_GLDSINGLE_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
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Dense matrix and non-owning view.
NcResult< T > pfqn_gldsingle(const Matrix< T > &L, int N, const Matrix< T > &mu)
Exact normalizing constant of a SINGLE-CLASS closed network whose stations are load dependent.
Number-type abstraction for the templated API port.
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
Return value of the normalizing-constant family, mirroring Ret.pfqnNc.
Definition pfqn_ca.h:44