LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
npfqn_feedback_elim.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_NPFQN_FEEDBACK_ELIM_H
6#define LINE_API_NPFQN_FEEDBACK_ELIM_H
7
8/**
9 * @file
10 * @ingroup api_npfqn
11 * Near-immediate feedback elimination for the robust queueing network analyzer.
12 *
13 * Templated port of matlab/src/api/npfqn/npfqn_feedback_elim.m, cross-checked
14 * against jar/src/main/java/jline/api/npfqn/Npfqn_feedback_elim.java.
15 *
16 * WHY FEEDBACK BREAKS DECOMPOSITION. A parametric decomposition treats the
17 * arrival stream at each station as renewal. Feedback destroys that badly: a
18 * customer that leaves a busy station and comes straight back arrives exactly
19 * when the station is busy, so the flow is strongly correlated with the queue it
20 * feeds. The fix is not to model the correlation but to REMOVE the feedback, by
21 * folding the repeated visits into the service time:
22 *
23 * effective mean service E[S]/(1-p)
24 * effective service SCV p + (1-p)cs^2 (37)
25 * fresh arrival rate lambda(1-p)
26 * per-visit waiting time (1-p) times the wait in the modified system
27 *
28 * The modified system has the SAME heavy-traffic limits for queue length,
29 * workload, waiting time and external departures, so this is asymptotically
30 * exact rather than merely plausible.
31 *
32 * NEAR-IMMEDIATE, NOT JUST IMMEDIATE. What matters is whether the customer
33 * returns WITHOUT PASSING A BUSIER STATION: a detour through a station of lower
34 * traffic intensity is fast on the time scale of the busy station. The
35 * probability computed here is therefore the probability of returning to station
36 * i through stations of strictly smaller rho only, obtained from the absorbing
37 * chain restricted to those stations.
38 *
39 * ARITHMETIC. Only a linear solve, so this instantiates at T = Rational too.
40 *
41 * Reference: W. Whitt, W. You (2022). A robust queueing network analyzer based
42 * on indices of dispersion. Naval Research Logistics 69(1), 36-56, Section 4.
43 */
44
45#include <cstddef>
46#include <vector>
47
48#include "line/num/number.h"
49#include "line/util/error.h"
50#include "line/util/lu.h"
51#include "line/util/matrix.h"
52
53namespace line {
54namespace npfqn {
55
56/** Outcome of the feedback elimination. */
57template <class T>
59 std::vector<T> feedbackProb; ///< p-hat per station
60 std::vector<T> visitInflation; ///< 1/(1-p), the mean visits per customer
61 std::vector<T> modifiedScv; ///< p + (1-p)cs^2, empty when no SCV was given
62 std::vector<T> modifiedRates; ///< lambda(1-p), empty when no rate was given
63 Matrix<T> modifiedRouting; ///< the immediate-feedback reduction of P
64 bool reductionExact = false; ///< whether that reduction describes this network
65};
66
67/**
68 * @brief Near-immediate feedback elimination for the robust queueing network
69 * analyzer.
70 *
71 * @param P routing matrix, substochastic
72 * @param rho traffic intensity of each station
73 * @param cs2 service SCV of each station, empty to skip modifiedScv
74 * @param lambda arrival rate of each station, empty to skip modifiedRates
75 * @param immediateOnly keep only the self-loops, i.e. Section 4.1 feedback
76 */
77template <class T>
78FeedbackElimResult<T> npfqn_feedback_elim(const Matrix<T>& P, const std::vector<T>& rho,
79 const std::vector<T>& cs2 = std::vector<T>(),
80 const std::vector<T>& lambda = std::vector<T>(),
81 bool immediateOnly = false) {
82 const T zero = num_traits<T>::from_int(0);
83 const T one = num_traits<T>::from_int(1);
84 const std::size_t m = P.rows();
85 if (P.cols() != m) throw InputError("npfqn_feedback_elim: the routing matrix must be square");
86 for (std::size_t i = 0; i < m; ++i) {
87 T row = zero;
88 for (std::size_t j = 0; j < m; ++j) {
89 if (P(i, j) < -num_traits<T>::from_double(1e-12))
90 throw InputError("npfqn_feedback_elim: the routing matrix must be non-negative");
91 row += P(i, j);
92 }
93 if (row > one + num_traits<T>::from_double(1e-9))
94 throw InputError("npfqn_feedback_elim: the routing matrix must be substochastic");
95 }
96 if (rho.size() != m)
97 throw InputError("npfqn_feedback_elim: one traffic intensity per station is required");
98
100 res.feedbackProb.assign(m, zero);
101 res.visitInflation.assign(m, zero);
102 for (std::size_t i = 0; i < m; ++i) {
103 T ret = P(i, i);
104 if (!immediateOnly) {
105 // Stations a customer may pass through on a near-immediate return:
106 // those NOT MORE loaded than i. A detour through a busier station is
107 // not fast on the time scale of station i, so it is not
108 // near-immediate; one through a station of equal load is, which is
109 // why the test is <= and not <. This is the cloud of eqs.
110 // (3.8)-(3.9) with H = {i}, and the same one solver_rqna applies --
111 // the two must not drift.
112 const T slack = num_traits<T>::from_double(1e-9);
113 std::vector<std::size_t> idx;
114 for (std::size_t j = 0; j < m; ++j)
115 if (j != i && rho[j] <= T(rho[i] + slack)) idx.push_back(j);
116 if (!idx.empty()) {
117 const std::size_t k = idx.size();
118 Matrix<T> A(k, k);
119 std::vector<T> b(k, zero);
120 for (std::size_t a = 0; a < k; ++a) {
121 for (std::size_t c = 0; c < k; ++c)
122 A(a, c) = (a == c ? one : zero) - P(idx[a], idx[c]);
123 b[a] = P(idx[a], i);
124 }
125 // (I-Q)^-1 r: the probability of eventually reaching i from each
126 // allowed station without leaving the allowed set.
127 const std::vector<T> reach = solve(A, b);
128 for (std::size_t a = 0; a < k; ++a) ret += P(i, idx[a]) * reach[a];
129 }
130 }
131 if (ret < zero) ret = zero;
132 const T cap = one - num_traits<T>::from_double(1e-12);
133 if (ret > cap) ret = cap;
134 res.feedbackProb[i] = ret;
135 res.visitInflation[i] = one / (one - ret);
136 }
137
138 if (!cs2.empty()) {
139 if (cs2.size() != m)
140 throw InputError("npfqn_feedback_elim: one service SCV per station is required");
141 res.modifiedScv.resize(m);
142 for (std::size_t i = 0; i < m; ++i) // eq. (37)
143 res.modifiedScv[i] = res.feedbackProb[i] + (one - res.feedbackProb[i]) * cs2[i];
144 }
145 if (!lambda.empty()) {
146 if (lambda.size() != m)
147 throw InputError("npfqn_feedback_elim: one arrival rate per station is required");
148 res.modifiedRates.resize(m);
149 for (std::size_t i = 0; i < m; ++i)
150 res.modifiedRates[i] = lambda[i] * (one - res.feedbackProb[i]);
151 }
152
153 // The immediate-feedback reduction: drop the self-loop and renormalize the
154 // rest of the row. For near-immediate feedback the return path runs through
155 // other stations, so no row-local reduction exists and the elimination
156 // applies to the service description instead.
157 res.modifiedRouting = P;
158 bool exact = true;
159 for (std::size_t i = 0; i < m; ++i) {
160 if (num_abs(T(res.feedbackProb[i] - P(i, i))) > num_traits<T>::from_double(1e-12))
161 exact = false;
162 const T loop = P(i, i);
163 if (loop <= zero) continue;
164 res.modifiedRouting(i, i) = zero;
165 T rest = zero, total = zero;
166 for (std::size_t j = 0; j < m; ++j) {
167 rest += res.modifiedRouting(i, j);
168 total += P(i, j);
169 }
170 if (rest > zero)
171 for (std::size_t j = 0; j < m; ++j)
172 res.modifiedRouting(i, j) = res.modifiedRouting(i, j) * (total - loop) / rest;
173 }
174 res.reductionExact = immediateOnly || exact;
175 return res;
176}
177
178} // namespace npfqn
179} // namespace line
180
181#endif // LINE_API_NPFQN_FEEDBACK_ELIM_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.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
FeedbackElimResult< T > npfqn_feedback_elim(const Matrix< T > &P, const std::vector< T > &rho, const std::vector< T > &cs2=std::vector< T >(), const std::vector< T > &lambda=std::vector< T >(), bool immediateOnly=false)
Near-immediate feedback elimination for the robust queueing network analyzer.
T num_abs(const T &v)
Definition number.h:172
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
Number-type abstraction for the templated API port.
Outcome of the feedback elimination.
std::vector< T > visitInflation
1/(1-p), the mean visits per customer
Matrix< T > modifiedRouting
the immediate-feedback reduction of P
std::vector< T > feedbackProb
p-hat per station
std::vector< T > modifiedRates
lambda(1-p), empty when no rate was given
bool reductionExact
whether that reduction describes this network
std::vector< T > modifiedScv
p + (1-p)cs^2, empty when no SCV was given