LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_lldfun.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_LLDFUN_H
6#define LINE_API_PFQN_LLDFUN_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * AMVA-QD limited-load-dependence function.
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_lldfun.m, cross-checked against
14 * jar/src/main/java/jline/api/pfqn/ld/Pfqn_lldfun.java. Returns, for every
15 * station i, the reciprocal service-capacity multiplier at the (fractional)
16 * queue length n(i), combining
17 *
18 * - the multiserver term 1/softmin(n_i, c_i, alpha), a smooth approximation
19 * of 1/min(n_i, c_i), with alpha = 20 as in the reference. A delay station
20 * (c_i infinite) contributes 1; the caller handles the 1/n_i itself.
21 * - the generic load-dependence term 1/alpha_i(n_i), obtained by CLAMPED
22 * LINEAR interpolation of the lattice lldscaling(i, 1..smax) at
23 * min(max(n_i,1), smax). Linear is exact for the piecewise-linear
24 * min(1:N, c) lattice that load dependence is overwhelmingly used to
25 * express, whereas a cubic spline overshoots between knots and extrapolates
26 * to negative rates past the lattice. A station whose lattice row is
27 * constant is skipped, exactly as `range(lldscaling(i,:)) > 0` does.
28 *
29 * softmin. The reference rewrites the literal weighted average
30 * (x e^{-ax} + y e^{-ay})/(e^{-ax} + e^{-ay}) as lo + gap w/(1+w) with
31 * w = e^{-a gap}, so the exponent argument is never positive and the limit
32 * w -> 0 returns min(x,y) exactly; that form is reproduced here, including its
33 * cutoff at a*gap > 745 where exp underflows to zero in double.
34 *
35 * Arithmetic: TRANSCENDENTAL-GATED, and here on a genuine transcendental, not
36 * on a tolerance: softmin evaluates exp(-alpha |n_i - c_i|), which is not an
37 * element of the field generated by the inputs for any alpha and gap of
38 * interest. There is no exact formulation of a soft minimum.
39 */
40
41#include <cmath>
42#include <cstddef>
43#include <limits>
44#include <vector>
45
46#include "line/num/number.h"
47#include "line/util/error.h"
48#include "line/util/matrix.h"
49
50namespace line {
51namespace pfqn {
52
53namespace detail {
54
55/** matlab/src/util/softmin.m, in the overflow-free form. */
56template <class T>
57T softmin(const T& x, const T& y, double alpha) {
58 const T lo = x < y ? x : y;
59 const T hi = x < y ? y : x;
60 const T gap = hi - lo;
61 // exp(-t) underflows to exactly 0 for t > 745.13; beyond that softmin is min.
62 if (!(num_traits<T>::to_double(gap) < 745.0 / alpha)) return lo;
63 using std::exp;
64 const T w = exp(T(-num_traits<T>::from_double(alpha) * gap));
65 return lo + gap * w / (num_traits<T>::from_int(1) + w);
66}
67
68} // namespace detail
69
70/**
71 * @brief AMVA-QD limited-load-dependence function.
72 *
73 * @param n (M) queue lengths, possibly fractional
74 * @param lldscaling (M x smax) rate lattice; empty for none
75 * @param nservers (M) server counts; a non-finite entry marks a delay
76 * station. Empty to skip the multiserver term entirely,
77 * which is what omitting the argument does in MATLAB.
78 * @return (M) reciprocals of the effective rate multipliers
79 */
80template <class T>
81std::vector<T> pfqn_lldfun(const std::vector<T>& n, const Matrix<T>& lldscaling,
82 const std::vector<double>& nservers) {
84 "pfqn_lldfun requires transcendental arithmetic");
85
86 const std::size_t M = n.size();
87 const T one = num_traits<T>::from_int(1);
88 std::vector<T> r(M, one);
89 if (!nservers.empty() && nservers.size() != M)
90 throw InputError("pfqn_lldfun: server-count vector has the wrong station count");
91 if (!lldscaling.empty() && lldscaling.rows() != M)
92 throw InputError("pfqn_lldfun: rate lattice has the wrong station count");
93 const std::size_t smax = lldscaling.empty() ? 0 : lldscaling.cols();
94 const double alpha = 20.0;
95
96 for (std::size_t i = 0; i < M; ++i) {
97 if (!nservers.empty()) {
98 if (!std::isfinite(nservers[i])) {
99 // Delay station: the 1/n_i is applied by the caller.
100 r[i] = one;
101 } else {
102 const T c = num_traits<T>::from_double(nservers[i]);
103 const T sm = detail::softmin(n[i], c, alpha);
104 if (sm == num_traits<T>::from_int(0)) {
105 // The soft minimum collapses only when both arguments do.
106 const T m = n[i] < c ? n[i] : c;
107 if (m == num_traits<T>::from_int(0))
108 throw NumericError("pfqn_lldfun: zero queue length and zero server count");
109 r[i] = r[i] / m;
110 } else {
111 r[i] = r[i] / sm;
112 }
113 }
114 }
115 if (smax > 0) {
116 T lo = lldscaling(i, 0), hi = lldscaling(i, 0);
117 for (std::size_t k = 1; k < smax; ++k) {
118 if (lldscaling(i, k) < lo) lo = lldscaling(i, k);
119 if (lldscaling(i, k) > hi) hi = lldscaling(i, k);
120 }
121 if (hi > lo) {
122 // Clamp into [1, smax] and interpolate linearly between knots.
123 T x = n[i];
124 const T xlo = one, xhi = num_traits<T>::from_int(static_cast<long>(smax));
125 if (x < xlo) x = xlo;
126 if (x > xhi) x = xhi;
127 const double xd = num_traits<T>::to_double(x);
128 std::size_t k0 = static_cast<std::size_t>(xd) - 1;
129 if (k0 + 1 >= smax) k0 = smax - 2;
130 const T t = x - num_traits<T>::from_int(static_cast<long>(k0) + 1);
131 const T val =
132 lldscaling(i, k0) + t * (lldscaling(i, k0 + 1) - lldscaling(i, k0));
133 if (val == num_traits<T>::from_int(0))
134 throw NumericError("pfqn_lldfun: the interpolated rate multiplier is zero");
135 r[i] = r[i] / val;
136 }
137 }
138 }
139 return r;
140}
141
142/** Overload without the multiserver term, matching the two-argument MATLAB call. */
143template <class T>
144std::vector<T> pfqn_lldfun(const std::vector<T>& n, const Matrix<T>& lldscaling) {
145 return pfqn_lldfun(n, lldscaling, std::vector<double>());
146}
147
148} // namespace pfqn
149} // namespace line
150
151#endif // LINE_API_PFQN_LLDFUN_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.
std::vector< T > pfqn_lldfun(const std::vector< T > &n, const Matrix< T > &lldscaling, const std::vector< double > &nservers)
AMVA-QD limited-load-dependence function.
Definition pfqn_lldfun.h:81
Number-type abstraction for the templated API port.