LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mg1_fb.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_FB_H
6#define LINE_API_QSYS_QSYS_MG1_FB_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * M/G/1 under FB (feedback), also called LAS (least attained service).
12 *
13 * Templated port of matlab/src/api/qsys/qsys_mg1_fb.m, cross-checked against
14 * jar/src/main/java/jline/api/qsys/Qsys_mg1_fb.java.
15 *
16 * The job with the least attained service holds the server, so priority
17 * depends on age, not on the original or remaining size. For a job of size x
18 * (Wierman and Harchol-Balter, SIGMETRICS 2003, Sec. 3.3)
19 *
20 * rho_x = lambda int_0^x Fbar(t) dt
21 * num(x) = lambda int_0^x t Fbar(t) dt
22 * E[T(x)] = num(x)/(1-rho_x)^2 + x/(1-rho_x)
23 *
24 * with Fbar the tail of the mixture job-size law. MATLAB takes two paths:
25 *
26 * all cs_i = 1 (within 1e-6): the mixture is a mixture of exponentials, the
27 * two truncated integrals are closed forms, and the class mean is
28 * E[T_k] = int_0^{20/mu_k} E[T(x)] mu_k e^{-mu_k x} dx by quadrature;
29 * otherwise: the class is collapsed onto its mean size x = 1/mu_k and the
30 * non-exponential truncated integrals are replaced by the bounded
31 * surrogates min(x, 1/mu_i) and min(x^2/2, 1/mu_i^2).
32 *
33 * Both paths are reproduced verbatim, including the truncation of the outer
34 * integral at 20 mean service times, which is not an implementation detail:
35 * it biases the class mean low by the tail beyond 20 e-foldings, of relative
36 * order 1e-8, and a port that integrated to infinity would not reproduce the
37 * reference.
38 *
39 * ARITHMETIC. exp and the adaptive quadrature make this transcendental.
40 */
41
42#include <cstddef>
43#include <vector>
44
48#include "line/num/number.h"
49#include "line/util/error.h"
50
51namespace line {
52namespace qsys {
53
54namespace detail {
55
56/** E[T(x)] under FB for a mixture-of-exponentials job-size law. */
57template <class T>
58T mg1_fb_response(const T& x, const std::vector<T>& mu, const std::vector<T>& p,
59 const T& lambda_total) {
60 const T one = num_traits<T>::from_int(1);
61 T rho_x = num_traits<T>::from_int(0), numer = num_traits<T>::from_int(0);
62 for (std::size_t i = 0; i < mu.size(); ++i) {
63 const T e = num_exp(T(-mu[i] * x));
64 rho_x += p[i] * lambda_total * ((one - e) / mu[i]);
65 numer += p[i] * lambda_total * ((one - e * (one + mu[i] * x)) / (mu[i] * mu[i]));
66 }
67 if (rho_x >= one)
68 throw NumericError("qsys_mg1_fb: truncated load reaches one, the mean is infinite");
69 return numer / ((one - rho_x) * (one - rho_x)) + x / (one - rho_x);
70}
71
72} // namespace detail
73
74/**
75 * @brief M/G/1 under FB (feedback), also called LAS (least attained service).
76 *
77 * @param lambda per-class arrival rates
78 * @param mu per-class service rates
79 * @param cs per-class coefficients of variation of the service time
80 */
81template <class T>
82Mg1DisciplineResult<T> qsys_mg1_fb(const std::vector<T>& lambda, const std::vector<T>& mu,
83 const std::vector<T>& cs) {
85 "qsys_mg1_fb requires transcendental arithmetic");
86 detail::mg1_discipline_check("qsys_mg1_fb", lambda, mu, cs);
87 const std::size_t K = lambda.size();
88 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
89 const T two = num_traits<T>::from_int(2);
90 const T cs_tol = T(num_traits<T>::from_double(1e-6));
91
92 bool all_exp = true;
93 for (std::size_t i = 0; i < K; ++i)
94 if (!(num_abs(T(cs[i] - one)) < cs_tol)) all_exp = false;
95
97 r.W.assign(K, zero);
98
99 if (all_exp) {
100 T lambda_total = zero;
101 for (const T& v : lambda) lambda_total += v;
102 std::vector<T> p(K);
103 for (std::size_t i = 0; i < K; ++i) p[i] = lambda[i] / lambda_total;
104 const T reltol = T(num_traits<T>::from_double(1e-8));
105 const T abstol = T(num_traits<T>::from_double(1e-10));
106 for (std::size_t k = 0; k < K; ++k) {
107 const T mu_k = mu[k];
108 const T x_max = num_traits<T>::from_int(20) / mu_k;
109 r.W[k] = detail::num_integral<T>(
110 [&](const T& x) {
111 return detail::mg1_fb_response(x, mu, p, lambda_total) * mu_k *
112 detail::num_exp(T(-mu_k * x));
113 },
114 zero, x_max, reltol, abstol);
115 }
116 } else {
117 for (std::size_t k = 0; k < K; ++k) {
118 const T x = one / mu[k];
119 T rho_x = zero, numer = zero;
120 for (std::size_t i = 0; i < K; ++i) {
121 T int_Fbar, int_tFbar;
122 if (num_abs(T(cs[i] - one)) < cs_tol) {
123 const T e = detail::num_exp(T(-mu[i] * x));
124 int_Fbar = (one - e) / mu[i];
125 int_tFbar = (one - e * (one + mu[i] * x)) / (mu[i] * mu[i]);
126 } else {
127 int_Fbar = detail::num_min(x, T(one / mu[i]));
128 int_tFbar = detail::num_min(T(x * x / two), T(one / (mu[i] * mu[i])));
129 }
130 rho_x += lambda[i] * int_Fbar;
131 numer += lambda[i] * int_tFbar;
132 }
133 if (rho_x >= one)
134 throw NumericError("qsys_mg1_fb: truncated load reaches one, the mean is infinite");
135 r.W[k] = numer / ((one - rho_x) * (one - rho_x)) + x / (one - rho_x);
136 }
137 }
138 r.rhohat = detail::mg1_discipline_rhohat(lambda, r.W);
139 return r;
140}
141
142} // namespace qsys
143} // namespace line
144
145#endif // LINE_API_QSYS_QSYS_MG1_FB_H
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Mg1DisciplineResult< T > qsys_mg1_fb(const std::vector< T > &lambda, const std::vector< T > &mu, const std::vector< T > &cs)
M/G/1 under FB (feedback), also called LAS (least attained service).
Definition qsys_mg1_fb.h:82
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.