LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
npfqn_bnd_bpt.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_NPFQN_BND_BPT_H
6#define LINE_API_NPFQN_NPFQN_BND_BPT_H
7
8/**
9 * @file
10 * @ingroup api_npfqn
11 * First-order linear-programming relaxation of the achievable region of a
12 * multiclass open Markovian queueing network.
13 *
14 * Templated port of matlab/src/api/npfqn/npfqn_bnd_bpt.m, cross-checked against
15 * jar/src/main/java/jline/api/npfqn/Npfqn_bnd_bpt.java.
16 *
17 * Returns a LOWER bound on sum_r c_r x_r, where x_r is the mean sojourn time of
18 * class r, valid for EVERY non-idling scheduling policy. A "class" here is a
19 * buffer with its own exponential service rate and its own Markovian routing,
20 * so a station serving several customer types owns one class per type. The
21 * network is open: class r receives external Poisson arrivals at rate
22 * lambda0(r) and, on completing service, becomes class r' with probability
23 * P(r,r') or leaves with the row deficit.
24 *
25 * METHOD. Uniformize the chain and let R(t) = sum_r f(r) n_r(t) for an
26 * arbitrary vector f. The steady-state balance of E[R^2] is an identity
27 * quadratic in f; since it holds for every f, the two sides' coefficient
28 * matrices agree entrywise. Diagonal entries give one equation per class,
29 * off-diagonal entries one per unordered pair, in the variables
30 *
31 * x_r = E[T_r], the mean sojourn time of class r,
32 * I(r,l) = E[1{server sigma(r) busy with class r} n_l],
33 * N(i,l) = E[1{server i idle} n_l].
34 *
35 * A third block states that the events "station i serves class r" and "station
36 * i idle" are mutually exclusive and exhaustive, so their terms sum to
37 * E[n_l] = lambda_l x_l. Minimizing over this polyhedron is a relaxation of the
38 * achievable region, hence a lower bound.
39 *
40 * EXACT ON M/M/1. The LP reduces to mu*I11 - lambda^2*x = lambda and
41 * I11 + N11 = lambda*x with N11 >= 0, whence x >= 1/(mu-lambda) with equality.
42 *
43 * NOT INCLUDED, DELIBERATELY. The valid inequality I(r,r) >= rho_r would
44 * tighten the relaxation but is not part of the reference's characterization,
45 * and reproducing the reference's published bounds is the acceptance test.
46 *
47 * ARITHMETIC. Rational-clean: the equations are polynomial in the data and the
48 * dense simplex is exact, so at T = line::Rational the returned value is the
49 * exact optimum of the exact polytope. Nothing here is transcendental.
50 *
51 * Reference: D. Bertsimas, I. Paschalidis, J. Tsitsiklis (1994). Optimization
52 * of multiclass queueing networks: polyhedral and nonlinear characterizations
53 * of achievable performance. Annals of Applied Probability 4(1), 43-75. See
54 * also D. Bertsimas (1995), Queueing Systems 21, 337-389, Theorem 9, which
55 * restates the same characterization.
56 */
57
58#include <cstddef>
59#include <string>
60#include <vector>
61
62#include "line/num/number.h"
63#include "line/util/error.h"
64#include "line/util/linalg.h"
65#include "line/util/lp_highs.h"
66#include "line/util/matrix.h"
67#include "line/util/simplex.h"
68
69namespace line {
70namespace npfqn {
71
72template <class T>
73struct BndBpt {
74 T zlb = T(); ///< lower bound on sum_r c_r x_r
75 std::vector<T> x; ///< the x block of the LP optimizer
76 std::vector<T> lambda; ///< effective arrival rate of each class
77 std::vector<T> rho; ///< per-class utilization lambda_r/mu_r
78 std::vector<T> rhoStation; ///< per-station utilization
79 std::size_t nvars = 0;
80 std::size_t nrows = 0;
81};
82
83/**
84 * @brief First-order linear-programming relaxation of the achievable region
85 * of a multiclass open Markovian queueing network.
86 *
87 * @param lambda0 external Poisson arrival rate into each class (0 if none)
88 * @param mu exponential service rate of each class
89 * @param P K x K routing, P(r,r') = P(class r becomes r' after service)
90 * @param stationOf zero-based station index of each class
91 * @param c objective weights; empty means all ones
92 */
93template <class T>
94BndBpt<T> npfqn_bnd_bpt(const std::vector<T>& lambda0, const std::vector<T>& mu,
95 const Matrix<T>& P, const std::vector<std::size_t>& stationOf,
96 const std::vector<T>& c) {
97 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
98 const T two = num_traits<T>::from_int(2);
99 const std::size_t K = lambda0.size();
100 if (mu.size() != K || stationOf.size() != K)
101 throw InputError("npfqn_bnd_bpt: lambda0, mu and stationOf must have the same length");
102 if (P.rows() != K || P.cols() != K)
103 throw InputError("npfqn_bnd_bpt: P must be K x K");
104 std::vector<T> cost = c;
105 if (cost.empty()) cost.assign(K, one);
106 if (cost.size() != K) throw InputError("npfqn_bnd_bpt: c must have K entries");
107 for (std::size_t r = 0; r < K; ++r) {
108 if (!(mu[r] > zero))
109 throw InputError("npfqn_bnd_bpt: every class needs a strictly positive service rate");
110 T rowSum = zero;
111 for (std::size_t s = 0; s < K; ++s) rowSum = T(rowSum + P(r, s));
112 if (rowSum > T(one + num_traits<T>::from_double(1e-9)))
113 throw InputError("npfqn_bnd_bpt: the routing matrix has a row summing above one");
114 }
115 std::size_t M = 0;
116 for (std::size_t r = 0; r < K; ++r) M = std::max(M, stationOf[r] + 1);
117
118 // ---- traffic equations, lambda = lambda0 + P' lambda ----
119 Matrix<T> ImPt(K, K, zero);
120 for (std::size_t i = 0; i < K; ++i)
121 for (std::size_t j = 0; j < K; ++j) ImPt(i, j) = T((i == j ? one : zero) - P(j, i));
122 Matrix<T> rhs0(K, 1, zero);
123 for (std::size_t r = 0; r < K; ++r) rhs0(r, 0) = lambda0[r];
124 const Matrix<T> lamM = matmul(inverse(ImPt), rhs0);
125
126 BndBpt<T> out;
127 out.lambda.assign(K, zero);
128 out.rho.assign(K, zero);
129 out.rhoStation.assign(M, zero);
130 for (std::size_t r = 0; r < K; ++r) {
131 out.lambda[r] = lamM(r, 0) < zero ? zero : lamM(r, 0);
132 out.rho[r] = T(out.lambda[r] / mu[r]);
133 out.rhoStation[stationOf[r]] = T(out.rhoStation[stationOf[r]] + out.rho[r]);
134 }
135 for (std::size_t i = 0; i < M; ++i)
136 if (!(out.rhoStation[i] < one))
137 throw UnsupportedError("npfqn_bnd_bpt: station " + std::to_string(i + 1) +
138 " is saturated: no policy stabilizes the network");
139
140 // ---- variable layout: x(r) | I(r,l) | N(i,l) ----
141 const std::size_t oI = K, oN = K + K * K, nv = K + K * K + M * K;
142 lp::LpModel<T> m(nv);
143 m.set_maximize(false);
144
145 // (a) diagonal equations, test function n_r^2
146 for (std::size_t r = 0; r < K; ++r) {
147 m.row_clear();
148 m.row_add(oI + r * K + r, T(two * mu[r]));
149 for (std::size_t w = 0; w < K; ++w)
150 if (P(w, r) != zero) m.row_add(oI + w * K + r, T(-(two * mu[w] * P(w, r))));
151 m.row_add(r, T(-(two * lambda0[r] * out.lambda[r])));
152 m.emit(lp::LpSense::EQ, T(two * out.lambda[r] * (one - P(r, r))));
153 }
154
155 // (b) off-diagonal equations, test function n_r*n_s
156 for (std::size_t r = 1; r < K; ++r) {
157 for (std::size_t s = 0; s < r; ++s) {
158 m.row_clear();
159 m.row_add(oI + r * K + s, mu[r]);
160 m.row_add(oI + s * K + r, mu[s]);
161 for (std::size_t w = 0; w < K; ++w) {
162 if (P(w, r) != zero) m.row_add(oI + w * K + s, T(-(mu[w] * P(w, r))));
163 if (P(w, s) != zero) m.row_add(oI + w * K + r, T(-(mu[w] * P(w, s))));
164 }
165 m.row_add(s, T(-(lambda0[r] * out.lambda[s])));
166 m.row_add(r, T(-(lambda0[s] * out.lambda[r])));
168 T(-(out.lambda[r] * P(r, s)) - out.lambda[s] * P(s, r)));
169 }
170 }
171
172 // (c) exhaustiveness at each station
173 for (std::size_t i = 0; i < M; ++i) {
174 for (std::size_t l = 0; l < K; ++l) {
175 m.row_clear();
176 for (std::size_t r = 0; r < K; ++r)
177 if (stationOf[r] == i) m.row_add(oI + r * K + l, one);
178 m.row_add(oN + i * K + l, one);
179 m.row_add(l, T(-out.lambda[l]));
180 m.emit(lp::LpSense::EQ, zero);
181 }
182 }
183
184 for (std::size_t r = 0; r < K; ++r) m.set_cost(r, cost[r]);
185
186 out.nvars = m.num_vars();
187 out.nrows = m.num_rows();
188 const lp::LpSolution<T> s = lp::lp_solve(m);
189 if (!s.ok())
190 throw UnsupportedError(std::string("npfqn_bnd_bpt: the achievable-region LP did not "
191 "solve to optimality (") +
192 lp::lp_status_name(s.status) + ")");
193 out.zlb = s.objective;
194 out.x.assign(K, zero);
195 for (std::size_t r = 0; r < K; ++r) out.x[r] = s.x[r];
196 return out;
197}
198
199} // namespace npfqn
200} // namespace line
201
202#endif // LINE_API_NPFQN_NPFQN_BND_BPT_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
UnsupportedError(const std::string &what)
Definition error.h:51
Sparse LP in the natural form, with per-variable bounds.
Definition simplex.h:112
void set_maximize(bool m)
true to maximize c'x (the default), false to minimize.
Definition simplex.h:174
void emit(LpSense sense, const T &rhs)
Emit the accumulated row with the given relation and right-hand side.
Definition simplex.h:200
std::size_t num_rows() const
Definition simplex.h:125
void set_cost(std::size_t j, const T &v)
Definition simplex.h:164
std::size_t num_vars() const
Definition simplex.h:124
void row_clear()
Discard whatever the row accumulator holds.
Definition simplex.h:179
void row_add(std::size_t j, const T &v)
row(j) += v, the accumulation the MATLAB reference performs.
Definition simplex.h:188
The exception types the port throws.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
A sparse LP backend for line::lp::LpModel, on HiGHS (MIT).
Dense matrix and non-owning view.
LpSolution< T > lp_solve(const LpModel< T > &model, std::size_t dense_max_cols=512)
Solve, choosing the backend by arithmetic and size.
Definition lp_highs.h:176
const char * lp_status_name(LpStatus s)
Definition simplex.h:84
BndBpt< T > npfqn_bnd_bpt(const std::vector< T > &lambda0, const std::vector< T > &mu, const Matrix< T > &P, const std::vector< std::size_t > &stationOf, const std::vector< T > &c)
First-order linear-programming relaxation of the achievable region of a multiclass open Markovian que...
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Definition linalg.h:72
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
Definition linalg.h:36
Number-type abstraction for the templated API port.
Templated primal simplex with Bland's rule.
T objective
c'x, in the sense requested (max or min)
Definition simplex.h:97
bool ok() const
Definition simplex.h:99
std::vector< T > x
primal solution in the ORIGINAL variable space
Definition simplex.h:96
std::vector< T > lambda
effective arrival rate of each class
std::vector< T > x
the x block of the LP optimizer
std::vector< T > rhoStation
per-station utilization
std::vector< T > rho
per-class utilization lambda_r/mu_r
T zlb
lower bound on sum_r c_r x_r