LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mapqn_params.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_MAPQN_MAPQN_PARAMS_H
6#define LINE_API_MAPQN_MAPQN_PARAMS_H
7
8/**
9 * @file
10 * @ingroup api_mapqn
11 * Model parameters and variable indexing shared by the mapqn QR bounds.
12 *
13 * A MAP queueing network here is M queues, N circulating jobs, K(i) phases at
14 * queue i, completion rates mu{i}(k,h), background (non-completion) phase
15 * transition rates v{i}(k,h), load-dependent scalings alpha(i,n) and routing
16 * probabilities r(i,j). This mirrors the params struct that
17 * matlab/lib/qrf/mapqn_bnd_qr_ld.m takes and the Mapqn_parameters class in
18 * jar/src/main/java/jline/api/mapqn/.
19 *
20 * Index convention: queues and phases are 0-based here (C++ convention),
21 * populations are as written, 0..N. The MATLAB reference is 1-based in queues
22 * and phases, so every loop `for i = 1:M` becomes `for i = 0; i < M; ++i` and
23 * every `q_val(i,j,k,h,n)` argument drops by one except n. This matches the
24 * JAR, whose q takes 0-based i,j,k,h and a 1-based population n (see the mapqn
25 * section of _kb/03-api-layer.md).
26 *
27 * Arithmetic: nothing here is transcendental. q is a sum of products of model
28 * data, so at T = line::Rational every LP coefficient is an exact rational and
29 * the bound the LP returns is the exact optimum of the exact polytope.
30 */
31
32#include <cstddef>
33#include <string>
34#include <vector>
35
36#include "line/num/number.h"
37#include "line/util/error.h"
38#include "line/util/matrix.h"
39
40namespace line {
41namespace mapqn {
42
43/** Parameters of a MAP queueing network for the QR bounds. */
44template <class T>
46 int M = 0; ///< number of queues
47 int N = 0; ///< total population
48 std::vector<int> K; ///< K[i] = number of phases at queue i
49 std::vector<Matrix<T>> mu; ///< mu[i] is K(i) x K(i), completion rates
50 std::vector<Matrix<T>> v; ///< v[i] is K(i) x K(i), background rates
51 Matrix<T> alpha; ///< M x N load-dependent scalings, 1 beyond the table
52 Matrix<T> r; ///< M x M routing probabilities
53 T Z = T(); ///< think time (delay model only)
54 T D1 = num_traits<T>::from_int(1); ///< service demand at queue 1 (delay model only)
55
56 void validate() const {
57 if (M <= 0) throw InputError("mapqn: M must be positive");
58 if (N < 0) throw InputError("mapqn: N must be nonnegative");
59 if (static_cast<int>(K.size()) != M) throw InputError("mapqn: K has the wrong length");
60 if (static_cast<int>(mu.size()) != M || static_cast<int>(v.size()) != M)
61 throw InputError("mapqn: mu and v must have one entry per queue");
62 for (int i = 0; i < M; ++i) {
63 if (K[i] <= 0) throw InputError("mapqn: every queue needs at least one phase");
64 const std::size_t k = static_cast<std::size_t>(K[i]);
65 if (mu[i].rows() != k || mu[i].cols() != k)
66 throw InputError("mapqn: mu{i} must be K(i) x K(i)");
67 if (v[i].rows() != k || v[i].cols() != k)
68 throw InputError("mapqn: v{i} must be K(i) x K(i)");
69 }
70 if (r.rows() != static_cast<std::size_t>(M) || r.cols() != static_cast<std::size_t>(M))
71 throw InputError("mapqn: r must be M x M");
72 if (!alpha.empty() && alpha.rows() != static_cast<std::size_t>(M))
73 throw InputError("mapqn: alpha must have M rows");
74 }
75};
76
77/**
78 * q(i,j,k,h,n): rate at which queue i, holding n jobs and in phase k, moves to
79 * phase h while routing a job to queue j.
80 *
81 * Port of the local q_func in mapqn_bnd_qr_ld.m. n == 0 returns 0 (an empty
82 * queue completes nothing); the load-dependent factor alpha(i,n) is 1 beyond
83 * the width of the alpha table, exactly as MATLAB's `if n <= size(alpha,2)`
84 * guard does. The i == j branch adds the background rate v, because a
85 * self-routing completion and a phase change without completion are
86 * indistinguishable in the marginal process.
87 */
88template <class T>
89T mapqn_q(const MapqnParams<T>& p, int i, int j, int k, int h, int n) {
90 if (n == 0) return T();
91 T alpha_val = num_traits<T>::from_int(1);
92 if (!p.alpha.empty() && static_cast<std::size_t>(n) <= p.alpha.cols())
93 alpha_val = p.alpha(static_cast<std::size_t>(i), static_cast<std::size_t>(n - 1));
94 const std::size_t ki = static_cast<std::size_t>(k), hi = static_cast<std::size_t>(h);
95 if (j != i) {
96 const T base = p.r(static_cast<std::size_t>(i), static_cast<std::size_t>(j)) * p.mu[i](ki, hi);
97 return T(base * alpha_val);
98 }
99 const T base = p.v[i](ki, hi) +
100 p.r(static_cast<std::size_t>(i), static_cast<std::size_t>(i)) * p.mu[i](ki, hi);
101 return T(base * alpha_val);
102}
103
104/**
105 * Flat index of the joint variable p2(j,nj,k,i,ni,h).
106 *
107 * The enumeration order is the one mapqn_bnd_qr_ld.m builds:
108 * for j, for nj = 0..N, for k = 1..K(j), for i, for ni = 0..N, for h = 1..K(i)
109 * which factorizes as outer(j,nj,k) * B + inner(i,ni,h) with the same map used
110 * for both halves and B = (N+1) * sum_i K(i). Keeping the two halves identical
111 * is what makes the SYMMETRY family a plain index swap.
112 */
113struct P2Index {
114 int M = 0, N = 0;
115 std::vector<int> K;
116 std::vector<int> cumK; ///< cumK[i] = sum_{i' < i} K(i')
117 std::size_t block = 0; ///< (N+1) * sum_i K(i)
118
120 P2Index(int m, int n, const std::vector<int>& k) : M(m), N(n), K(k) {
121 cumK.assign(static_cast<std::size_t>(M) + 1, 0);
122 for (int i = 0; i < M; ++i) cumK[i + 1] = cumK[i] + K[i];
123 block = static_cast<std::size_t>(N + 1) * static_cast<std::size_t>(cumK[M]);
124 }
125
126 /** Half-index of (queue, population, phase); identical for both halves. */
127 std::size_t half(int i, int ni, int h) const {
128 return static_cast<std::size_t>(N + 1) * static_cast<std::size_t>(cumK[i]) +
129 static_cast<std::size_t>(ni) * static_cast<std::size_t>(K[i]) +
130 static_cast<std::size_t>(h);
131 }
132
133 std::size_t operator()(int j, int nj, int k, int i, int ni, int h) const {
134 return half(j, nj, k) * block + half(i, ni, h);
135 }
136
137 std::size_t num_vars() const { return block * block; }
138};
139
140/** Result of a QR bound solve. */
141template <class T>
143 bool ok = false; ///< the LP reached an optimal vertex
144 std::string status; ///< textual LP status
145 T objective = T(); ///< the bound
146 std::vector<T> x; ///< full solution vector, indexed by P2Index
147 std::vector<Matrix<T>> p2marginals; ///< p2marginals[j](nj, k) = p2(j,nj,k,j,nj,k)
148 std::size_t num_vars = 0;
149 std::size_t num_rows = 0;
150 std::size_t iterations = 0;
151};
152
153/** Which direction the bound is taken in. */
154enum class MapqnSense { Max, Min };
155
156} // namespace mapqn
157} // namespace line
158
159#endif // LINE_API_MAPQN_MAPQN_PARAMS_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
The exception types the port throws.
Dense matrix and non-owning view.
MapqnSense
Which direction the bound is taken in.
T mapqn_q(const MapqnParams< T > &p, int i, int j, int k, int h, int n)
q(i,j,k,h,n): rate at which queue i, holding n jobs and in phase k, moves to phase h while routing a ...
Number-type abstraction for the templated API port.
Parameters of a MAP queueing network for the QR bounds.
Matrix< T > r
M x M routing probabilities.
std::vector< Matrix< T > > mu
mu[i] is K(i) x K(i), completion rates
int N
total population
std::vector< int > K
K[i] = number of phases at queue i.
T Z
think time (delay model only)
Matrix< T > alpha
M x N load-dependent scalings, 1 beyond the table.
int M
number of queues
T D1
service demand at queue 1 (delay model only)
std::vector< Matrix< T > > v
v[i] is K(i) x K(i), background rates
Result of a QR bound solve.
std::string status
textual LP status
std::vector< T > x
full solution vector, indexed by P2Index
std::vector< Matrix< T > > p2marginals
p2marginals[j](nj, k) = p2(j,nj,k,j,nj,k)
bool ok
the LP reached an optimal vertex
P2Index(int m, int n, const std::vector< int > &k)
std::vector< int > K
std::size_t num_vars() const
std::size_t block
(N+1) * sum_i K(i)
std::size_t operator()(int j, int nj, int k, int i, int ni, int h) const
std::size_t half(int i, int ni, int h) const
Half-index of (queue, population, phase); identical for both halves.
std::vector< int > cumK
cumK[i] = sum_{i' < i} K(i')