LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mg1_psjf.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_PSJF_H
6#define LINE_API_QSYS_QSYS_MG1_PSJF_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * M/G/1 under PSJF (preemptive shortest job first).
12 *
13 * Templated port of matlab/src/api/qsys/qsys_mg1_psjf.m, cross-checked against
14 * jar/src/main/java/jline/api/qsys/Qsys_mg1_psjf.java.
15 *
16 * Priority follows the original size, so a job of size x is delayed only by
17 * work of size at most x (Wierman and Harchol-Balter, SIGMETRICS 2003,
18 * Sec. 3.2):
19 *
20 * rho(x) = lambda int_0^x t f(t) dt
21 * m2(x) = lambda int_0^x t^2 f(t) dt
22 * E[T(x)] = x/(1-rho(x)) + m2(x)/(2 (1-rho(x))^2)
23 *
24 * As in qsys_mg1_fb, MATLAB has an exponential path -- closed-form truncated
25 * moments of the mixture, class mean by quadrature over [0, 20/mu_k] -- and a
26 * general path that sorts the classes by increasing mean size and evaluates
27 * the same expression at x = 1/mu_k with the truncated moments replaced by the
28 * per-class second moments of the classes at least as fast. Both are
29 * reproduced.
30 *
31 * ARITHMETIC. exp and the adaptive quadrature make this transcendental.
32 */
33
34#include <algorithm>
35#include <cstddef>
36#include <numeric>
37#include <vector>
38
42#include "line/num/number.h"
43#include "line/util/error.h"
44
45namespace line {
46namespace qsys {
47
48namespace detail {
49
50/** E[T(x)] under PSJF for a mixture-of-exponentials job-size law. */
51template <class T>
52T mg1_psjf_response(const T& x, const std::vector<T>& mu, const std::vector<T>& p,
53 const T& lambda_total) {
54 const T one = num_traits<T>::from_int(1);
55 const T two = num_traits<T>::from_int(2);
56 T m1_x = num_traits<T>::from_int(0), m2_x = num_traits<T>::from_int(0);
57 for (std::size_t i = 0; i < mu.size(); ++i) {
58 const T mi = mu[i];
59 const T e = num_exp(T(-mi * x));
60 m1_x += p[i] * (one / mi - (one / mi + x) * e);
61 m2_x += p[i] * (two / (mi * mi) - (two / (mi * mi) + two * x / mi + x * x) * e);
62 }
63 const T rho_x = lambda_total * m1_x;
64 if (rho_x >= one)
65 throw NumericError("qsys_mg1_psjf: truncated load reaches one, the mean is infinite");
66 return x / (one - rho_x) + lambda_total * m2_x / (two * (one - rho_x) * (one - rho_x));
67}
68
69} // namespace detail
70
71/**
72 * @brief M/G/1 under PSJF (preemptive shortest job first).
73 *
74 * @param lambda per-class arrival rates
75 * @param mu per-class service rates
76 * @param cs per-class coefficients of variation of the service time
77 */
78template <class T>
79Mg1DisciplineResult<T> qsys_mg1_psjf(const std::vector<T>& lambda, const std::vector<T>& mu,
80 const std::vector<T>& cs) {
82 "qsys_mg1_psjf requires transcendental arithmetic");
83 detail::mg1_discipline_check("qsys_mg1_psjf", lambda, mu, cs);
84 const std::size_t K = lambda.size();
85 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
86 const T two = num_traits<T>::from_int(2);
87 const T cs_tol = T(num_traits<T>::from_double(1e-6));
88
89 bool all_exp = true;
90 for (std::size_t i = 0; i < K; ++i)
91 if (!(num_abs(T(cs[i] - one)) < cs_tol)) all_exp = false;
92
94 r.W.assign(K, zero);
95
96 if (all_exp) {
97 T lambda_total = zero;
98 for (const T& v : lambda) lambda_total += v;
99 std::vector<T> p(K);
100 for (std::size_t i = 0; i < K; ++i) p[i] = lambda[i] / lambda_total;
101 const T reltol = T(num_traits<T>::from_double(1e-8));
102 const T abstol = T(num_traits<T>::from_double(1e-10));
103 for (std::size_t k = 0; k < K; ++k) {
104 const T mu_k = mu[k];
105 const T x_max = num_traits<T>::from_int(20) / mu_k;
106 r.W[k] = detail::num_integral<T>(
107 [&](const T& x) {
108 return detail::mg1_psjf_response(x, mu, p, lambda_total) * mu_k *
109 detail::num_exp(T(-mu_k * x));
110 },
111 zero, x_max, reltol, abstol);
112 }
113 } else {
114 // Classes sorted by increasing mean service time; MATLAB's sort is
115 // stable, so ties keep the original class order.
116 std::vector<std::size_t> idx(K);
117 std::iota(idx.begin(), idx.end(), std::size_t(0));
118 std::stable_sort(idx.begin(), idx.end(),
119 [&](std::size_t i, std::size_t j) { return one / mu[i] < one / mu[j]; });
120 T rho_cum = zero;
121 for (std::size_t pos = 0; pos < K; ++pos) {
122 const std::size_t k = idx[pos];
123 const T x = one / mu[k];
124 rho_cum += lambda[k] / mu[k];
125 T m2_x = zero;
126 for (std::size_t q = 0; q <= pos; ++q) {
127 const std::size_t i = idx[q];
128 m2_x += lambda[i] * (one + cs[i] * cs[i]) / (mu[i] * mu[i]);
129 }
130 if (rho_cum >= one)
131 throw NumericError(
132 "qsys_mg1_psjf: truncated load reaches one, the mean is infinite");
133 r.W[k] = m2_x / (two * (one - rho_cum) * (one - rho_cum)) + x / (one - rho_cum);
134 }
135 }
136 r.rhohat = detail::mg1_discipline_rhohat(lambda, r.W);
137 return r;
138}
139
140} // namespace qsys
141} // namespace line
142
143#endif // LINE_API_QSYS_QSYS_MG1_PSJF_H
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Mg1DisciplineResult< T > qsys_mg1_psjf(const std::vector< T > &lambda, const std::vector< T > &mu, const std::vector< T > &cs)
M/G/1 under PSJF (preemptive shortest job first).
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.
Adaptive quadrature for the qsys functions whose MATLAB originals call integral(),...
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.