LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mm1_dps.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_QSYS_QSYS_MM1_DPS_H
6#define LINE_API_QSYS_QSYS_MM1_DPS_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Multiclass M/M/1 under DPS (discriminatory processor sharing), solved
12 * numerically on the truncated population chain.
13 *
14 * Templated port of matlab/src/api/qsys/qsys_mm1_dps.m, cross-checked against
15 * jar/src/main/java/jline/api/qsys/Qsys_mm1_dps.java.
16 *
17 * The state is the per-class population vector (n_1,...,n_K); class k arrives
18 * at rate lambda_k and completes at rate
19 *
20 * mu_k n_k w_k / sum_j n_j w_j,
21 *
22 * so the server capacity is split in proportion to the weighted populations.
23 * The chain is truncated at a total population level taken from the geometric
24 * tail bound, N = max(16, ceil(log(tol)/log(rho))), and the level is doubled
25 * until the per-class mean counts move by less than tol or the hard cutoff is
26 * reached. Response times follow by Little's law, T_k = E[N_k]/lambda_k.
27 *
28 * The truncation blocks arrivals at the top level rather than dropping them,
29 * which is what makes the answer a lower bound that converges from below as
30 * the cutoff grows; that is the reference behaviour and is kept.
31 *
32 * ARITHMETIC. The cutoff is chosen through a logarithm and the doubling loop
33 * is driven to a tolerance, so the function is gated on transcendental
34 * arithmetic. The inner solve is exact field arithmetic given the truncation,
35 * but the truncation itself is the approximation.
36 *
37 * COST. The truncated state space has C(N+K,K) states and the stationary
38 * solve is dense here, so the MATLAB default maxCutoff of 2048 is not usable
39 * at K >= 2; callers should pass a cutoff matched to the load. The defaults
40 * are kept as the MATLAB ones so that a like-for-like comparison is possible
41 * on small instances.
42 */
43
44#include <algorithm>
45#include <cstddef>
46#include <vector>
47
50#include "line/num/number.h"
51#include "line/util/error.h"
52#include "line/util/matrix.h"
53
54namespace line {
55namespace qsys {
56
57template <class T>
59 std::vector<T> T_; ///< per-class mean response time
60 T rho; ///< total utilization sum_k lambda_k/mu_k
61};
62
63namespace detail {
64
65/** Recursive helper of dps_states. */
66inline void dps_states_rec(std::size_t K, unsigned rem, std::vector<unsigned>& cur,
67 std::vector<std::vector<unsigned>>& out) {
68 if (cur.size() == K) {
69 out.push_back(cur);
70 return;
71 }
72 for (unsigned v = 0; v <= rem; ++v) {
73 cur.push_back(v);
74 dps_states_rec(K, rem - v, cur, out);
75 cur.pop_back();
76 }
77}
78
79/** All population vectors of K classes with total at most Ncut. */
80inline std::vector<std::vector<unsigned>> dps_states(std::size_t K, unsigned Ncut) {
81 std::vector<std::vector<unsigned>> out;
82 std::vector<unsigned> cur;
83 cur.reserve(K);
84 dps_states_rec(K, Ncut, cur, out);
85 return out;
86}
87
88/** Per-class mean populations of the chain truncated at total level Ncut. */
89template <class T>
90std::vector<T> dps_solve_trunc(const std::vector<T>& lambda, const std::vector<T>& mu,
91 const std::vector<T>& w, unsigned Ncut) {
92 const std::size_t K = lambda.size();
93 const std::vector<std::vector<unsigned>> S = dps_states(K, Ncut);
94 const std::size_t n = S.size();
95 std::vector<std::size_t> stride(K, 1);
96 for (std::size_t k = 1; k < K; ++k) stride[k] = stride[k - 1] * (Ncut + 1);
97 std::vector<std::size_t> index(stride[K - 1] * (Ncut + 1), n);
98 for (std::size_t i = 0; i < n; ++i) {
99 std::size_t key = 0;
100 for (std::size_t k = 0; k < K; ++k) key += S[i][k] * stride[k];
101 index[key] = i;
102 }
103
104 const T zero = num_traits<T>::from_int(0);
105 Matrix<T> Q(n, n, zero);
106 for (std::size_t i = 0; i < n; ++i) {
107 unsigned tot = 0;
108 for (std::size_t k = 0; k < K; ++k) tot += S[i][k];
109 T den = zero;
110 for (std::size_t k = 0; k < K; ++k)
111 den += num_traits<T>::from_int(static_cast<long>(S[i][k])) * w[k];
112 std::size_t key = 0;
113 for (std::size_t k = 0; k < K; ++k) key += S[i][k] * stride[k];
114 for (std::size_t k = 0; k < K; ++k) {
115 if (tot < Ncut) Q(i, index[key + stride[k]]) += lambda[k];
116 if (S[i][k] > 0)
117 Q(i, index[key - stride[k]]) +=
118 mu[k] * num_traits<T>::from_int(static_cast<long>(S[i][k])) * w[k] / den;
119 }
120 }
121 for (std::size_t i = 0; i < n; ++i) {
122 T s = zero;
123 for (std::size_t j = 0; j < n; ++j)
124 if (j != i) s += Q(i, j);
125 Q(i, i) = -s;
126 }
127 const std::vector<T> pi = mc::ctmc_solve(Q);
128 std::vector<T> EN(K, zero);
129 for (std::size_t i = 0; i < n; ++i)
130 for (std::size_t k = 0; k < K; ++k)
131 EN[k] += pi[i] * num_traits<T>::from_int(static_cast<long>(S[i][k]));
132 return EN;
133}
134
135} // namespace detail
136
137/**
138 * @brief Multiclass M/M/1 under DPS (discriminatory processor sharing),
139 * solved numerically on the truncated population chain.
140 *
141 * @param lambda per-class Poisson arrival rates
142 * @param mu per-class exponential service rates
143 * @param w per-class DPS weights, positive
144 * @param tol convergence tolerance on the mean counts (MATLAB 1e-10)
145 * @param maxCutoff hard bound on the truncation level (MATLAB 2048)
146 */
147template <class T>
148Mm1DpsResult<T> qsys_mm1_dps(const std::vector<T>& lambda, const std::vector<T>& mu,
149 const std::vector<T>& w, const T& tol, unsigned maxCutoff) {
151 "qsys_mm1_dps requires transcendental arithmetic");
152 const std::size_t K = lambda.size();
153 if (mu.size() != K || w.size() != K)
154 throw InputError("qsys_mm1_dps: lambda, mu and w must have the same length");
155 if (K == 0) throw InputError("qsys_mm1_dps: at least one class is required");
156 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
157 for (std::size_t k = 0; k < K; ++k)
158 if (lambda[k] <= zero || mu[k] <= zero || w[k] <= zero)
159 throw InputError("qsys_mm1_dps: lambda, mu and w must all be positive");
160 T rho = zero;
161 for (std::size_t k = 0; k < K; ++k) rho += lambda[k] / mu[k];
162 if (rho >= one) throw InputError("qsys_mm1_dps: system is unstable, rho >= 1");
163
164 const double lt = num_traits<T>::log_as_double(tol);
165 const double lr = num_traits<T>::log_as_double(rho);
166 unsigned N = static_cast<unsigned>(std::max(16.0, std::ceil(lt / lr)));
167 if (N > maxCutoff) N = maxCutoff;
168
169 std::vector<T> ENprev = detail::dps_solve_trunc(lambda, mu, w, N);
170 while (N < maxCutoff) {
171 const unsigned N2 = std::min(2u * N, maxCutoff);
172 const std::vector<T> EN = detail::dps_solve_trunc(lambda, mu, w, N2);
173 T gap = zero;
174 for (std::size_t k = 0; k < K; ++k) {
175 const T d = num_abs(T(EN[k] - ENprev[k]));
176 if (d > gap) gap = d;
177 }
178 ENprev = EN;
179 if (gap < tol) break;
180 N = N2;
181 if (N2 == maxCutoff) break;
182 }
183
185 r.rho = rho;
186 r.T_.resize(K);
187 for (std::size_t k = 0; k < K; ++k) r.T_[k] = ENprev[k] / lambda[k];
188 return r;
189}
190
191/** MATLAB defaults: tol = 1e-10, maxCutoff = 2048. */
192template <class T>
193Mm1DpsResult<T> qsys_mm1_dps(const std::vector<T>& lambda, const std::vector<T>& mu,
194 const std::vector<T>& w) {
195 return qsys_mm1_dps(lambda, mu, w, T(num_traits<T>::from_double(1e-10)), 2048u);
196}
197
198} // namespace qsys
199} // namespace line
200
201#endif // LINE_API_QSYS_QSYS_MM1_DPS_H
InputError(const std::string &what)
Definition error.h:39
Steady-state distribution of a continuous-time Markov chain.
The exception types the port throws.
Dense matrix and non-owning view.
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
Definition ctmc_solve.h:122
Mm1DpsResult< T > qsys_mm1_dps(const std::vector< T > &lambda, const std::vector< T > &mu, const std::vector< T > &w, const T &tol, unsigned maxCutoff)
Multiclass M/M/1 under DPS (discriminatory processor sharing), solved numerically on the truncated po...
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
Shared return type and arithmetic helpers for the templated qsys port.
std::vector< T > T_
per-class mean response time
T rho
total utilization sum_k lambda_k/mu_k