LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mg1_sjf.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_SJF_H
6#define LINE_API_QSYS_QSYS_MG1_SJF_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * M/G/1 with non-preemptive shortest-job-first scheduling: per-class mean
12 * response times.
13 *
14 * Port of matlab/src/api/qsys/qsys_mg1_sjf.m. A job of size x waits
15 *
16 * W(x) = (lambda E[S^2]/2) / (1 - rho(x))^2, rho(x) = lambda int_0^x t f(t) dt,
17 *
18 * (Phipps, 1956), and the class mean averages W(x) over the class's size law.
19 * Each class's size law is the reference's two-moment fit (exponential,
20 * balanced-means H2 above cs = 1, an Erlang mixture below), and both integrals
21 * are the reference's trapezoidal rules on the same uniform grid, so the
22 * numbers agree with MATLAB to rounding.
23 *
24 * ARITHMETIC: double, as the reference.
25 */
26
27#include <algorithm>
28#include <cmath>
29#include <cstddef>
30#include <limits>
31#include <vector>
32
34#include "line/util/error.h"
35
36namespace line {
37namespace qsys {
38
39namespace sjfdetail {
40
41/** The reference's sjf_fit: exponential, H2 or Erlang mixture by cs^2. */
42struct SjfFit {
43 int type = 0; // 0 exp, 1 h2, 2 erlmix
44 double rate = 0, p = 0, r1 = 0, r2 = 0, rate_min = 0, rate_max = 0;
45 int k = 0;
46};
47
48inline SjfFit sjf_fit(double mu, double cs) {
49 SjfFit f;
50 const double mean_x = 1.0 / mu;
51 const double c2 = cs * cs;
52 if (std::fabs(c2 - 1.0) < 1e-9) {
53 f.type = 0;
54 f.rate = f.rate_min = f.rate_max = mu;
55 } else if (c2 > 1.0) {
56 const double pr = 0.5 * (1.0 + std::sqrt((c2 - 1.0) / (c2 + 1.0)));
57 f.type = 1;
58 f.p = pr;
59 f.r1 = 2.0 * pr * mu;
60 f.r2 = 2.0 * (1.0 - pr) * mu;
61 f.rate_min = std::min(f.r1, f.r2);
62 f.rate_max = std::max(f.r1, f.r2);
63 } else {
64 const int k = static_cast<int>(std::ceil(1.0 / c2));
65 const double pr = (1.0 / (1.0 + c2)) * (k * c2 - std::sqrt(k * (1.0 + c2) - static_cast<double>(k) * k * c2));
66 f.type = 2;
67 f.k = k;
68 f.p = pr;
69 f.rate = f.rate_min = f.rate_max = (k - pr) / mean_x;
70 }
71 return f;
72}
73
74/** Erlang(n, rate) density at x, in the reference's log-Poisson form. */
75inline double erlang_pdf(int n, double rate, double x) {
76 if (n <= 0) return 0.0;
77 const double t = rate * x;
78 const int m = n - 1;
79 double logp;
80 if (t <= 0.0) logp = (m == 0) ? 0.0 : -std::numeric_limits<double>::infinity();
81 else logp = m * std::log(t) - t - std::lgamma(m + 1.0);
82 return rate * std::exp(logp);
83}
84
85inline double sjf_pdf(const SjfFit& f, double x) {
86 switch (f.type) {
87 case 0: return f.rate * std::exp(-f.rate * x);
88 case 1: return f.p * f.r1 * std::exp(-f.r1 * x) + (1.0 - f.p) * f.r2 * std::exp(-f.r2 * x);
89 default: return f.p * erlang_pdf(f.k - 1, f.rate, x) + (1.0 - f.p) * erlang_pdf(f.k, f.rate, x);
90 }
91}
92
93} // namespace sjfdetail
94
95/** MATLAB's [W, rho_total, res] outputs. */
97 std::vector<double> W; ///< per-class mean response time
98 double rhohat = 0; ///< Q/(1+Q), MATLAB's rho_total
99 QsysRes res; ///< meanSojournTime, meanWaitingTime, meanQueueLength, utilization, conservationResidual, analyzer
100};
101
102/**
103 * @brief M/G/1 non-preemptive SJF.
104 *
105 * @param lambda per-class arrival rates
106 * @param mu per-class service rates
107 * @param cs per-class coefficients of variation of the service time
108 */
109inline Mg1SjfResult qsys_mg1_sjf(const std::vector<double>& lambda, const std::vector<double>& mu,
110 const std::vector<double>& cs) {
111 using namespace sjfdetail;
112 const std::size_t K = lambda.size();
113 if (mu.size() != K || cs.size() != K)
114 throw InputError(ErrorKind("Argument"), "qsys_mg1_sjf: lambda, mu and cs must have the same length");
115 for (std::size_t r = 0; r < K; ++r)
116 if (!(lambda[r] > 0.0) || !(mu[r] > 0.0) || !(cs[r] >= 0.0))
117 throw InputError(ErrorKind("Argument"), "qsys_mg1_sjf: lambda and mu must be positive, cs non-negative");
118 double lambda_total = 0.0, rho_util = 0.0, lES2 = 0.0;
119 for (std::size_t r = 0; r < K; ++r) {
120 lambda_total += lambda[r];
121 rho_util += lambda[r] / mu[r];
122 lES2 += lambda[r] * (1.0 + cs[r] * cs[r]) / (mu[r] * mu[r]);
123 }
124 if (rho_util >= 1.0) resdetail::throw_unstable("qsys_mg1_sjf", "rho", rho_util);
125 std::vector<SjfFit> fits(K);
126 double rate_min = std::numeric_limits<double>::infinity(), rate_max = 0.0;
127 for (std::size_t r = 0; r < K; ++r) {
128 fits[r] = sjf_fit(mu[r], cs[r]);
129 rate_min = std::min(rate_min, fits[r].rate_min);
130 rate_max = std::max(rate_max, fits[r].rate_max);
131 }
132 const double xmax = 40.0 / rate_min;
133 const long N = std::min(2000000L, std::max(20000L, static_cast<long>(std::ceil(200.0 * rate_max / rate_min))));
134 const std::size_t np = static_cast<std::size_t>(N) + 1;
135 std::vector<double> x(np), fmix(np, 0.0);
136 // linspace(0, xmax, N+1), as MATLAB computes it
137 for (std::size_t i = 0; i < np; ++i) x[i] = xmax * static_cast<double>(i) / static_cast<double>(N);
138 x[np - 1] = xmax;
139 std::vector<std::vector<double>> pdf(K, std::vector<double>(np));
140 for (std::size_t r = 0; r < K; ++r) {
141 const double p = lambda[r] / lambda_total;
142 for (std::size_t i = 0; i < np; ++i) {
143 pdf[r][i] = sjf_pdf(fits[r], x[i]);
144 fmix[i] += p * pdf[r][i];
145 }
146 }
147 // rho_x = lambda_total * cumtrapz(x, x .* fmix)
148 std::vector<double> Wx(np);
149 double cum = 0.0;
150 for (std::size_t i = 0; i < np; ++i) {
151 if (i > 0) cum += 0.5 * (x[i] - x[i - 1]) * (x[i] * fmix[i] + x[i - 1] * fmix[i - 1]);
152 const double denom = std::max(1.0 - lambda_total * cum, 1e-12);
153 Wx[i] = (lES2 / 2.0) / (denom * denom);
154 }
155 auto trapz = [&](const std::vector<double>& y) {
156 double s = 0.0;
157 for (std::size_t i = 1; i < np; ++i) s += 0.5 * (x[i] - x[i - 1]) * (y[i] + y[i - 1]);
158 return s;
159 };
160 Mg1SjfResult out;
161 std::vector<double> Wq(K), Qi(K), y(np);
162 out.W.assign(K, 0.0);
163 double Q = 0.0;
164 for (std::size_t r = 0; r < K; ++r) {
165 for (std::size_t i = 0; i < np; ++i) y[i] = Wx[i] * pdf[r][i];
166 Wq[r] = trapz(y);
167 out.W[r] = Wq[r] + 1.0 / mu[r];
168 Qi[r] = lambda[r] * out.W[r];
169 Q += Qi[r];
170 }
171 out.rhohat = Q / (1.0 + Q);
172 for (std::size_t i = 0; i < np; ++i) y[i] = x[i] * fmix[i] * Wx[i];
173 out.res.set("meanSojournTime", out.W);
174 out.res.set("meanWaitingTime", Wq);
175 out.res.set("meanQueueLength", Qi);
176 out.res.set("utilization", rho_util);
177 out.res.set("conservationResidual", lambda_total * trapz(y) - rho_util * lES2 / (2.0 * (1.0 - rho_util)));
178 out.res.analyzer = "exact:M/G/1-SJF";
179 return out;
180}
181
182} // namespace qsys
183} // namespace line
184
185#endif // LINE_API_QSYS_QSYS_MG1_SJF_H
InputError(const std::string &what)
Definition error.h:105
The exception types the port throws.
void throw_unstable(const char *fn, const char *what, double v)
A LINE:input:Unstable refusal "fn: unstable system: <what> = %g >= 1".
Definition qsys_res.h:213
double erlang_pdf(int n, double rate, double x)
Erlang(n, rate) density at x, in the reference's log-Poisson form.
SjfFit sjf_fit(double mu, double cs)
double sjf_pdf(const SjfFit &f, double x)
Mg1SjfResult qsys_mg1_sjf(const std::vector< double > &lambda, const std::vector< double > &mu, const std::vector< double > &cs)
M/G/1 non-preemptive SJF.
QsysResT< double > QsysRes
Definition qsys_res.h:96
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
The res struct the exact queueing-system functions return, and the numeric helpers those functions sh...
ErrorKind(std::string n)
Definition error.h:39
MATLAB's [W, rho_total, res] outputs.
QsysRes res
meanSojournTime, meanWaitingTime, meanQueueLength, utilization, conservationResidual,...
double rhohat
Q/(1+Q), MATLAB's rho_total.
std::vector< double > W
per-class mean response time
std::string analyzer
Definition qsys_res.h:59
void set(const std::string &name, const T &v)
Sets (or overwrites) a scalar field.
Definition qsys_res.h:62
The reference's sjf_fit: exponential, H2 or Erlang mixture by cs^2.