LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mg1_lrpt.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_MG1_LRPT_H
6#define LINE_API_QSYS_QSYS_MG1_LRPT_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * M/G/1 under LRPT (longest remaining processing time).
12 *
13 * Templated port of matlab/src/api/qsys/qsys_mg1_lrpt.m, cross-checked against
14 * jar/src/main/java/jline/api/qsys/Qsys_mg1_lrpt.java.
15 *
16 * LRPT gives the server to the job with the most work left, so the slowdown is
17 * the same for every job in the system (Wierman and Harchol-Balter,
18 * SIGMETRICS 2003, Sec. 3.2):
19 *
20 * E[T(x)] = x/(1-rho) + lambda E[X^2] / (2 (1-rho)^2)
21 *
22 * with rho the total load and E[X^2] the second moment of the mixture size.
23 * Only the first term depends on x, so under the exponential path the class
24 * mean is
25 *
26 * E[T_k] = int_0^{20/mu_k} E[T(x)] mu_k e^{-mu_k x} dx
27 *
28 * and this integral is elementary. MATLAB evaluates it by adaptive quadrature;
29 * the port uses the closed form of the same truncated integral,
30 *
31 * int_0^X x mu e^{-mu x} dx = (1 - e^{-muX}(1 + muX))/mu,
32 * int_0^X mu e^{-mu x} dx = 1 - e^{-muX}, muX = 20,
33 *
34 * which is the value MATLAB's quadrature converges to, so the two agree to
35 * MATLAB's RelTol of 1e-8 and in practice to round-off. The truncation at 20
36 * mean service times is kept: dropping it would change the answer in the
37 * eighth digit and no longer reproduce the reference.
38 *
39 * The general path (some cs != 1) is MATLAB's class-based preemptive-priority
40 * surrogate, with the classes sorted by decreasing mean size. Note that it
41 * ignores cs entirely -- the residual term is sum_{i<=k} lambda_i/mu_i^2,
42 * i.e. the second moment of an exponential -- so for a non-exponential input
43 * the reference answer does not depend on the variability it was given. That
44 * is a defect of the reference, reproduced here rather than silently fixed.
45 *
46 * ARITHMETIC. exp appears in the exponential path, so the function is gated.
47 */
48
49#include <algorithm>
50#include <cstddef>
51#include <numeric>
52#include <vector>
53
56#include "line/num/number.h"
57#include "line/util/error.h"
58
59namespace line {
60namespace qsys {
61
62/**
63 * @brief M/G/1 under LRPT (longest remaining processing time).
64 *
65 * @param lambda per-class arrival rates
66 * @param mu per-class service rates
67 * @param cs per-class coefficients of variation of the service time
68 */
69template <class T>
70Mg1DisciplineResult<T> qsys_mg1_lrpt(const std::vector<T>& lambda, const std::vector<T>& mu,
71 const std::vector<T>& cs) {
73 "qsys_mg1_lrpt requires transcendental arithmetic");
74 const T rho_total = detail::mg1_discipline_check("qsys_mg1_lrpt", lambda, mu, cs);
75 const std::size_t K = lambda.size();
76 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
77 const T two = num_traits<T>::from_int(2);
78 const T cs_tol = T(num_traits<T>::from_double(1e-6));
79
80 bool all_exp = true;
81 for (std::size_t i = 0; i < K; ++i)
82 if (!(num_abs(T(cs[i] - one)) < cs_tol)) all_exp = false;
83
85 r.W.assign(K, zero);
86
87 if (all_exp) {
88 T lambda_total = zero;
89 for (const T& v : lambda) lambda_total += v;
90 T E_X2 = zero;
91 for (std::size_t i = 0; i < K; ++i)
92 E_X2 += (lambda[i] / lambda_total) * two / (mu[i] * mu[i]);
93 const T flat = lambda_total * E_X2 / (two * (one - rho_total) * (one - rho_total));
94 // muX = 20 for every class, so the two truncation factors are shared.
95 const T e20 = detail::num_exp(T(num_traits<T>::from_int(-20)));
96 const T mass = one - e20;
97 const T first = one - e20 * num_traits<T>::from_int(21);
98 for (std::size_t k = 0; k < K; ++k)
99 r.W[k] = (first / mu[k]) / (one - rho_total) + flat * mass;
100 } else {
101 // Classes sorted by decreasing mean service time; MATLAB's sort is
102 // stable, so ties keep the original class order.
103 std::vector<std::size_t> idx(K);
104 std::iota(idx.begin(), idx.end(), std::size_t(0));
105 std::stable_sort(idx.begin(), idx.end(),
106 [&](std::size_t i, std::size_t j) { return one / mu[j] < one / mu[i]; });
107 T rho_prev = zero, E_R_k = zero;
108 for (std::size_t pos = 0; pos < K; ++pos) {
109 const std::size_t k = idx[pos];
110 const T rho_curr = rho_prev + lambda[k] / mu[k];
111 E_R_k += lambda[k] / (mu[k] * mu[k]);
112 r.W[k] = E_R_k / ((one - rho_prev) * (one - rho_curr)) + one / mu[k];
113 rho_prev = rho_curr;
114 }
115 }
116 r.rhohat = detail::mg1_discipline_rhohat(lambda, r.W);
117 return r;
118}
119
120} // namespace qsys
121} // namespace line
122
123#endif // LINE_API_QSYS_QSYS_MG1_LRPT_H
The exception types the port throws.
Mg1DisciplineResult< T > qsys_mg1_lrpt(const std::vector< T > &lambda, const std::vector< T > &mu, const std::vector< T > &cs)
M/G/1 under LRPT (longest remaining processing time).
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
M/G/1 under SETF (shortest elapsed time first), the non-preemptive counterpart of FB/LAS.
Shared return type and arithmetic helpers for the templated qsys port.
std::vector< T > W
per-class mean response time
T rhohat
Q/(1+Q) with Q = sum_k lambda_k W_k.