LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_ba_bgt.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_SOLVERS_BA_SOLVER_BA_BGT_H
6#define LINE_SOLVERS_BA_SOLVER_BA_BGT_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Piecewise-linear Lyapunov UPPER bound on the steady-state queue lengths of a
12 * multitype open Markovian network, valid for EVERY work-conserving Markovian
13 * policy.
14 *
15 * Templated port of matlab/src/solvers/BA/solver_ba_bgt_analyzer.m,
16 * cross-checked against
17 * jar/src/main/java/jline/solvers/ba/analyzers/Solver_ba_bgt_analyzer.java.
18 * The polyhedron and the bound are `npfqn_bnd_bgt`; this analyzer maps the LINE
19 * model onto them and reads the bound back per station and class.
20 *
21 * CLASS SPACE. The reference's network is a MULTITYPE one: each type follows a
22 * FIXED sequence of stages, and stage k of type i is its own buffer. LINE's
23 * (station, job class) pair is that buffer, so the analyzer walks the routing
24 * matrix from the Source and turns each open class into one type whose stages
25 * are the pairs it visits. Two gates follow and are enforced by name rather
26 * than approximated: routing must be DETERMINISTIC (a pair sends everything to
27 * one successor, or everything to the Sink) and routes must NOT MERGE (a pair
28 * belongs to exactly one type, else the reference's class index (i,k) is not
29 * defined). A re-entrant line is expressible by giving the revisits distinct
30 * LINE classes.
31 *
32 * THE BOUND IS LOOSE, and knowingly so: the exception parameter of the smoothed
33 * Lyapunov function carries (Lmax+gamma)^3/gamma^2 and dominates as soon as
34 * there is more than one station. What is sharp is the STABILITY CERTIFICATE --
35 * a feasible gamma > 0 proves every work-conserving policy stable, and the LP
36 * correctly refuses the Lu-Kumar network at per-station loads of 0.7, where
37 * global stability genuinely fails -- and the geometric tail RATE.
38 *
39 * ARITHMETIC. Rational-clean, like the API core it calls.
40 *
41 * Reference: D. Bertsimas, D. Gamarnik, J. N. Tsitsiklis (2001). Performance of
42 * multiclass Markovian queueing networks via piecewise linear Lyapunov
43 * functions. Annals of Applied Probability 11(4), 1384-1428, Section 5.1.
44 */
45
46#include <cmath>
47#include <cstddef>
48#include <limits>
49#include <string>
50#include <vector>
51
55#include "line/util/error.h"
56#include "line/util/matrix.h"
57
58namespace line {
59namespace ba {
60
61namespace detail {
62
63/**
64 * The single successor of a routing row, as a pair index, or `npairs` when
65 * everything leaves the network. A probabilistic split is refused by name: the
66 * reference's network has deterministic routing, and a split is a different
67 * model rather than an approximation of this one.
68 */
69template <class T>
70std::size_t bgt_single_successor(const Matrix<T>& rtst, std::size_t row,
71 const std::vector<std::size_t>& pairFlat,
72 const std::string& who) {
73 const T tol = num_traits<T>::from_double(1e-9);
74 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
75 T mass = zero, best = zero;
76 std::size_t p = pairFlat.size();
77 for (std::size_t q = 0; q < pairFlat.size(); ++q) {
78 const T v = rtst(row, pairFlat[q]);
79 if (v > tol) {
80 mass = T(mass + v);
81 if (v > best) {
82 best = v;
83 p = q;
84 }
85 }
86 }
87 if (!(mass > tol)) return pairFlat.size();
88 const T dm = T(mass - one), db = T(best - one);
89 const bool ok = (dm < tol && T(zero - dm) < tol) && (db < tol && T(zero - db) < tol);
90 if (!ok)
91 throw UnsupportedError(
92 "solver_ba_bgt: method 'bgt.upper' needs deterministic routing: " + who +
93 " splits its departures. The reference's network routes each type along a fixed "
94 "sequence of stages");
95 return p;
96}
97
98} // namespace detail
99
100/**
101 * @param L the model
102 * @param out the (Q,U,R,Tp,C,X) block to fill; shapes are set here
103 */
104template <class T, class Solution>
105void solver_ba_bgt(const qn::NetworkStruct<T>& L, Solution& out) {
106 const T zero = num_traits<T>::from_int(0);
107 const std::size_t M = L.nstations, K = L.nclasses;
108
109 out.Q = Matrix<T>(M, K, zero);
110 out.U = Matrix<T>(M, K, zero);
111 out.R = Matrix<T>(M, K, zero);
112 out.Tp = Matrix<T>(M, K, zero);
113 out.C.assign(K, zero);
114 out.X.assign(K, zero);
115 out.lG = std::numeric_limits<double>::quiet_NaN();
116 out.iter = 1;
117
118 // ---- model gates ----
119 for (std::size_t r = 0; r < K; ++r)
120 if (std::isfinite(L.classes[r].population))
121 throw UnsupportedError(
122 "solver_ba_bgt: method 'bgt.upper' supports fully open networks only "
123 "(no closed classes)");
124 std::vector<std::size_t> srcList, qstat;
125 for (std::size_t i = 0; i < M; ++i) {
126 if (L.stations[i].nodetype == qn::NodeType::Source)
127 srcList.push_back(i);
128 else
129 qstat.push_back(i);
130 }
131 if (srcList.empty())
132 throw UnsupportedError(
133 "solver_ba_bgt: method 'bgt.upper' requires an open network with a Source station");
134 for (std::size_t a = 0; a < qstat.size(); ++a) {
135 const std::size_t i = qstat[a];
136 if (L.stations[i].sched == lang::SchedStrategy::INF)
137 throw UnsupportedError(
138 "solver_ba_bgt: method 'bgt.upper' does not support delay (infinite-server) "
139 "stations: the reference's network has one server per station");
140 const double ns = num_traits<T>::to_double(L.stations[i].nservers);
141 if (std::isfinite(ns) && ns > 1)
142 throw UnsupportedError(
143 "solver_ba_bgt: method 'bgt.upper' does not support multi-server stations");
144 }
145
146 const Matrix<T> rtst = api::sn_rt_stations(L).rtst;
147
148 std::vector<std::size_t> pairStation, pairClass, pairFlat;
149 for (std::size_t a = 0; a < qstat.size(); ++a) {
150 const std::size_t i = qstat[a];
151 for (std::size_t r = 0; r < K; ++r) {
152 pairStation.push_back(i);
153 pairClass.push_back(r);
154 pairFlat.push_back(i * K + r);
155 }
156 }
157 const std::size_t np = pairFlat.size();
158
159 // ---- walk one deterministic route per source class ----
160 std::vector<T> lambda;
161 std::vector<std::vector<std::size_t> > routes;
162 std::vector<bool> used(np, false);
163 for (std::size_t si = 0; si < srcList.size(); ++si) {
164 const std::size_t s = srcList[si];
165 for (std::size_t r0 = 0; r0 < K; ++r0) {
166 const T arr = L.rates(s, r0);
167 const double ad = num_traits<T>::to_double(arr);
168 if (!std::isfinite(ad) || !(arr > zero)) continue;
169 std::size_t cur = detail::bgt_single_successor(
170 rtst, s * K + r0, pairFlat, "the Source for class " + std::to_string(r0 + 1));
171 std::vector<std::size_t> route;
172 while (cur < np) {
173 if (used[cur])
174 throw UnsupportedError(
175 "solver_ba_bgt: method 'bgt.upper' needs routes that do not merge: "
176 "station " +
177 std::to_string(pairStation[cur] + 1) + " class " +
178 std::to_string(pairClass[cur] + 1) +
179 " is visited by more than one type. Give the visits distinct job classes");
180 used[cur] = true;
181 route.push_back(cur);
182 cur = detail::bgt_single_successor(
183 rtst, pairFlat[route.back()], pairFlat,
184 "station " + std::to_string(pairStation[route.back()] + 1) + " class " +
185 std::to_string(pairClass[route.back()] + 1));
186 }
187 if (route.empty())
188 throw UnsupportedError("solver_ba_bgt: class " + std::to_string(r0 + 1) +
189 " leaves the Source and reaches no station");
190 routes.push_back(route);
191 lambda.push_back(arr);
192 }
193 }
194 if (routes.empty()) throw UnsupportedError("solver_ba_bgt: the model carries no open traffic");
195
196 const std::size_t I = routes.size();
197 std::vector<std::vector<T> > mu(I);
198 std::vector<std::vector<std::size_t> > sigma(I);
199 std::vector<std::size_t> ustat;
200 for (std::size_t i = 0; i < I; ++i) {
201 for (std::size_t k = 0; k < routes[i].size(); ++k) {
202 const std::size_t q = routes[i][k];
203 const T m = L.rates(pairStation[q], pairClass[q]);
204 const double md = num_traits<T>::to_double(m);
205 if (!std::isfinite(md) || !(m > zero))
206 throw UnsupportedError("solver_ba_bgt: station " +
207 std::to_string(pairStation[q] + 1) +
208 " has no service rate for class " +
209 std::to_string(pairClass[q] + 1) +
210 " but carries its traffic");
211 if (L.procid(pairStation[q] + 1, pairClass[q] + 1) != lang::ProcessType::EXP)
212 throw UnsupportedError(
213 "solver_ba_bgt: method 'bgt.upper' requires exponential service: station " +
214 std::to_string(pairStation[q] + 1) + " class " +
215 std::to_string(pairClass[q] + 1) + " is not exponential");
216 mu[i].push_back(m);
217 bool seen = false;
218 for (std::size_t u = 0; u < ustat.size(); ++u)
219 if (ustat[u] == pairStation[q]) seen = true;
220 if (!seen) ustat.push_back(pairStation[q]);
221 }
222 }
223 // Dense station index space for the LP.
224 for (std::size_t i = 0; i < I; ++i) {
225 sigma[i].assign(routes[i].size(), 0);
226 for (std::size_t k = 0; k < routes[i].size(); ++k)
227 for (std::size_t u = 0; u < ustat.size(); ++u)
228 if (ustat[u] == pairStation[routes[i][k]]) sigma[i][k] = u;
229 }
230
231 const npfqn::BndBgt<T> info = npfqn::npfqn_bnd_bgt(lambda, mu, sigma, ustat.size());
232
233 // ---- read the bound back per station and class ----
234 for (std::size_t i = 0; i < I; ++i) {
235 for (std::size_t k = 0; k < routes[i].size(); ++k) {
236 const std::size_t q = routes[i][k];
237 const std::size_t ist = pairStation[q], r = pairClass[q];
238 out.Q(ist, r) = T(out.Q(ist, r) + info.Qub[i][k]);
239 out.Tp(ist, r) = T(out.Tp(ist, r) + lambda[i]);
240 out.U(ist, r) = T(out.U(ist, r) + lambda[i] / L.rates(ist, r));
241 }
242 }
243 for (std::size_t i = 0; i < M; ++i)
244 for (std::size_t r = 0; r < K; ++r)
245 if (out.Tp(i, r) > zero) out.R(i, r) = T(out.Q(i, r) / out.Tp(i, r));
246
247 // ---- exact open-network quantities ----
248 for (std::size_t si = 0; si < srcList.size(); ++si) {
249 const std::size_t s = srcList[si];
250 for (std::size_t r = 0; r < K; ++r) {
251 const T arr = L.rates(s, r);
252 const double ad = num_traits<T>::to_double(arr);
253 if (std::isfinite(ad) && arr > zero) {
254 out.Tp(s, r) = T(out.Tp(s, r) + arr);
255 out.X[r] = T(out.X[r] + arr);
256 }
257 }
258 }
259 for (std::size_t r = 0; r < K; ++r) {
260 if (out.X[r] > zero) {
261 T sum = zero;
262 for (std::size_t i = 0; i < M; ++i) sum = T(sum + out.Q(i, r));
263 out.C[r] = T(sum / out.X[r]);
264 }
265 }
266}
267
268} // namespace ba
269} // namespace line
270
271#endif // LINE_SOLVERS_BA_SOLVER_BA_BGT_H
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
std::vector< JobClass > classes
ProcessType procid(std::size_t ist, std::size_t r) const
sn.procid(i,r): the process type of a (station, class) pair.
std::vector< Station< T > > stations
stations[k-1] is the k-th station
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
The exception types the port throws.
Dense matrix and non-owning view.
SnRtStations< T > sn_rt_stations(const qn::NetworkStruct< T > &sn)
void solver_ba_bgt(const qn::NetworkStruct< T > &L, Solution &out)
BndBgt< T > npfqn_bnd_bgt(const std::vector< T > &lambda, const std::vector< std::vector< T > > &mu, const std::vector< std::vector< std::size_t > > &sigma, std::size_t J)
Piecewise-linear Lyapunov UPPER bound on the steady-state queue lengths of a multitype (deterministic...
A queueing network and its refreshed NetworkStruct.
Piecewise-linear Lyapunov UPPER bound on the steady-state queue lengths of a multitype (deterministic...
Port of matlab/src/api/sn/sn_rt_stations.m.
std::vector< std::vector< T > > Qub
per type and stage, the bound on E[Q(i,k)]