LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
pfqn_lldsingle.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_LLDSINGLE_H
6
#define LINE_API_PFQN_LLDSINGLE_H
7
8
/**
9
* @file
10
* @ingroup api_pfqn
11
* Exact normalizing constant of a SINGLE-CLASS closed network whose stations
12
* are LIMITED load dependent, i.e. whose rate functions stay constant past a
13
* per-station threshold.
14
*
15
* Templated port of matlab/src/api/pfqn/pfqn_lldsingle.m.
16
*
17
* Same recursion, same arithmetic and bit-identical results to
18
* pfqn_gldsingle, but with the rate-offset axis truncated at that threshold
19
* instead of at the population. Unrolling the recursion of pfqn_gldsingle,
20
*
21
* g(m, n, t) = g(m-1, n, 1) + L(m) g(m, n-1, t+1) / mu(m, t)
22
*
23
* shows that its third index is an offset into station m's rate function,
24
*
25
* g(m,n,t) = sum_{j=0..n} prod_{i=0..j-1} L(m)/mu(m,t+i) * g(m-1,n-j,1)
26
*
27
* so once t >= s_m, where s_m is the population past which mu(m, .) stays
28
* constant, every factor is mu(m, s_m), the product collapses to
29
* (L(m)/mu(m,s_m))^j and
30
*
31
* g(m, n, t) = g(m, n, s_m) for all t >= s_m
32
*
33
* The N - s_m upper slices that pfqn_gldsingle materializes are duplicates of
34
* one another. Capping the offset at s_m and reading g(m, n-1, min(t+1, s_m))
35
* keeps every value the answer reads.
36
*
37
* COST. O(N sum_k s_k) time against O(M N^2) for pfqn_gldsingle, and
38
* O(N max_k s_k) space against O(M N^2), the station levels being rolled. On a
39
* multiserver model, where s_k is the server count, this is LINEAR in the
40
* population rather than quadratic. Unlike pfqn_explicit_ld, which reaches the
41
* same asymptotics through Gordon's alternating partial fraction, this loses no
42
* digits to cancellation at T = double: the arithmetic performed is a SUBSET of
43
* pfqn_gldsingle's, so the two agree to the last bit in every field.
44
*
45
* There is no gain on a station whose rates never settle, an infinite server
46
* mu(m,n) = n being the usual case: it gets s_m = N and costs what it costs in
47
* pfqn_gldsingle. The saving is over the OTHER stations, so a model carrying
48
* one delay among M queues drops from O(M N^2) to O(N^2 + N sum_k s_k).
49
*
50
* Arithmetic: EXACT-CAPABLE, and the threshold scan is what keeps it so. It
51
* compares rates with the field's own operator==, never a tolerance: at
52
* T = Rational or T = Real<D> a tolerance has no meaning, and at T = double a
53
* multiserver row repeats its tail exactly. A MISSED tie only costs time, since
54
* the routine then behaves as pfqn_gldsingle; a FALSE tie would be a wrong
55
* answer, which exact comparison cannot produce. The reference carries an
56
* eps-relative tolerance instead, being confined to IEEE double.
57
*
58
* As in pfqn_gldsingle this port keeps only the linear recursion, the
59
* reference's log-space branch being a range-management device for IEEE double,
60
* and an infinite rate is accepted the same way: the term L/mu vanishes.
61
*/
62
63
#include <algorithm>
64
#include <cstddef>
65
#include <vector>
66
67
#include "
line/api/pfqn/pfqn_ca.h
"
68
#include "
line/num/number.h
"
69
#include "
line/util/error.h
"
70
#include "
line/util/matrix.h
"
71
72
namespace
line
{
73
namespace
pfqn
{
74
75
/**
76
* @brief Exact normalizing constant of a SINGLE-CLASS closed network whose
77
* stations are LIMITED load dependent, i.e. whose rate functions stay
78
* constant past a per-station threshold.
79
*
80
* @param L (M x 1) service demands, one class
81
* @param N population
82
* @param mu (M x >=N) load-dependent rates, mu(i,k) with k jobs at station i
83
*/
84
template
<
class
T>
85
NcResult<T>
pfqn_lldsingle
(
const
Matrix<T>
& L,
int
N,
const
Matrix<T>
& mu) {
86
if
(!L.
empty
() && L.
cols
() != 1)
87
throw
InputError
(
"pfqn_lldsingle: multiclass model detected, this routine is single class"
);
88
if
(N < 0)
throw
InputError
(
"pfqn_lldsingle: negative population"
);
89
90
const
std::size_t M = L.
empty
() ? 0 : L.
rows
();
91
const
T zero =
num_traits<T>::from_int
(0);
92
const
T one =
num_traits<T>::from_int
(1);
93
94
if
(N == 0)
return
{one, 0.0};
95
if
(M == 0)
return
{zero,
num_traits<T>::log_as_double
(zero)};
96
if
(mu.
rows
() != M)
throw
InputError
(
"pfqn_lldsingle: mu has the wrong station count"
);
97
if
(
static_cast<
int
>
(mu.
cols
()) < N)
98
throw
InputError
(
"pfqn_lldsingle: mu has fewer rate columns than the population"
);
99
100
const
std::size_t Nu =
static_cast<
std::size_t
>
(N);
101
102
// pfqn_gldsingle reads every rate column 1..N somewhere in its triangle and
103
// rejects a zero there; the capped sweep reads a subset, so the rejection is
104
// hoisted to keep the two routines refusing the same models.
105
for
(std::size_t m = 0; m < M; ++m)
106
for
(std::size_t t = 0; t < Nu; ++t)
107
if
(mu(m, t) == zero)
108
throw
NumericError
(
109
"pfqn_lldsingle: a load-dependent rate is zero, the station cannot serve "
110
"and the normalizing constant diverges"
);
111
112
// s[m]: the smallest offset past which row m of mu is constant, so that
113
// mu(m,t) == mu(m,s[m]) for every t >= s[m]. Stored one-based.
114
std::vector<std::size_t> s(M, Nu);
115
for
(std::size_t m = 0; m < M; ++m) {
116
const
T& tail = mu(m, Nu - 1);
117
for
(std::size_t n = Nu - 1; n >= 1; --n) {
118
if
(mu(m, n - 1) == tail)
119
s[m] = n;
120
else
121
break
;
122
}
123
}
124
125
// gprev[n] = g(m-1, n, 1); cur[n * sm + (t-1)] = g(m, n, t)
126
std::vector<T> gprev(Nu + 1, zero);
127
gprev[0] = one;
// g(0, 0, 1) = 1, and g(0, n, 1) = 0 for n >= 1
128
std::vector<T> cur;
129
for
(std::size_t m = 1; m <= M; ++m) {
130
const
std::size_t sm = s[m - 1];
131
cur.assign((Nu + 1) * sm, zero);
132
for
(std::size_t t = 0; t < sm; ++t) cur[t] = one;
// g(m, 0, t) = 1
133
for
(std::size_t n = 1; n <= Nu; ++n) {
134
// offsets above N-n+1 are never read back, exactly as in
135
// pfqn_gldsingle, so the triangle is kept
136
const
std::size_t tmax = std::min(sm, Nu - n + 1);
137
for
(std::size_t t = 1; t <= tmax; ++t) {
138
const
std::size_t tsrc = std::min(t + 1, sm);
139
cur[n * sm + (t - 1)] =
140
gprev[n] + L(m - 1, 0) * cur[(n - 1) * sm + (tsrc - 1)] / mu(m - 1, t - 1);
141
}
142
}
143
for
(std::size_t n = 0; n <= Nu; ++n) gprev[n] = cur[n * sm];
144
}
145
146
const
T G = gprev[Nu];
147
return
{G,
num_traits<T>::log_as_double
(G)};
148
}
149
150
}
// namespace pfqn
151
}
// namespace line
152
153
#endif
// LINE_API_PFQN_LLDSINGLE_H
line::InputError::InputError
InputError(const std::string &what)
Definition
error.h:39
line::Matrix
Definition
matrix.h:56
line::Matrix::cols
std::size_t cols() const
Definition
matrix.h:90
line::Matrix::rows
std::size_t rows() const
Definition
matrix.h:89
line::Matrix::empty
bool empty() const
Definition
matrix.h:92
line::NumericError::NumericError
NumericError(const std::string &what)
Definition
error.h:45
error.h
The exception types the port throws.
matrix.h
Dense matrix and non-owning view.
line::pfqn
Definition
cd_peak_scaling.h:43
line::pfqn::pfqn_lldsingle
NcResult< T > pfqn_lldsingle(const Matrix< T > &L, int N, const Matrix< T > &mu)
Exact normalizing constant of a SINGLE-CLASS closed network whose stations are LIMITED load dependent...
Definition
pfqn_lldsingle.h:85
line
Definition
aoi_dist2ph.h:52
number.h
Number-type abstraction for the templated API port.
pfqn_ca.h
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
line::num_traits
Definition
number.h:111
line::pfqn::NcResult
Return value of the normalizing-constant family, mirroring Ret.pfqnNc.
Definition
pfqn_ca.h:44
include
line
api
pfqn
pfqn_lldsingle.h
Generated by
1.18.0