LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qbd_setupdelayoff_mmk.h
Go to the documentation of this file.
1/**
2 * @file qbd_setupdelayoff_mmk.h
3 * @brief Multi-server queue with setup and delayed-off servers.
4 *
5 * Templated port of matlab/src/api/mam/qbd_setupdelayoff_mmk.m.
6 */
7
8#ifndef LINE_API_MAM_QBD_SETUPDELAYOFF_MMK_H
9#define LINE_API_MAM_QBD_SETUPDELAYOFF_MMK_H
10
11#include <cmath>
12#include <cstddef>
13#include <functional>
14#include <map>
15#include <string>
16#include <vector>
17
21#include "line/num/number.h"
22#include "line/util/error.h"
23#include "line/util/matrix.h"
24
25namespace line {
26namespace mam {
27
28/** Mean number present and throughput of the setup/delayed-off pool. */
29template <class T>
31 T QN; ///< mean number at the station: queued, waiting for a setup or in service
32 T XN; ///< throughput of the station
33};
34
35namespace detail {
36
37/**
38 * Stationary (QN, XN) of the chain of qbd_setupdelayoff_mmk with arrivals at
39 * rate lam(n) while n < nmax jobs are present; PTOP receives P(n = nmax).
40 */
41template <class T>
42SetupDelayoffMMk<T> setupdelayoff_mmk_solve(int k, int nmax, const std::function<T(int)>& lam,
43 const T& mu, const T& alpharate, const T& alphascv,
44 const T& betarate, const T& betascv, double& ptop) {
45 const T zero = num_traits<T>::from_int(0);
46 const Matrix<T> Ta = coxian_phase_subgen(alpharate, alphascv);
47 const int na = static_cast<int>(Ta.rows());
48 std::vector<T> ta(na, zero);
49 for (int i = 0; i < na; ++i) {
50 T s = zero;
51 for (int j = 0; j < na; ++j) s += Ta(i, j);
52 ta[i] = T(-s);
53 }
54 const Matrix<T> Tb = coxian_phase_subgen(betarate, betascv);
55 const int nb = static_cast<int>(Tb.rows());
56 std::vector<T> tb(nb, zero);
57 for (int i = 0; i < nb; ++i) {
58 T s = zero;
59 for (int j = 0; j < nb; ++j) s += Tb(i, j);
60 tb[i] = T(-s);
61 }
62
63 // state = [q, b, a, s_1..s_na, d_1..d_nb]; off = k - b - a - sum(s) - sum(d)
64 const int L = 3 + na + nb;
65 std::vector<std::vector<int>> states;
66 std::map<std::vector<int>, std::size_t> index;
67 states.push_back(std::vector<int>(L, 0));
68 index[states[0]] = 0;
69 struct Arc {
70 std::size_t from, to;
71 T rate;
72 };
73 std::vector<Arc> arcs;
74 std::vector<std::pair<std::vector<int>, T>> out;
75 auto emit = [&](const std::vector<int>& st, const T& rate, int i1, int dv1, int i2, int dv2) {
76 std::vector<int> x = st;
77 x[i1] += dv1;
78 if (i2 >= 0) x[i2] += dv2;
79 out.emplace_back(x, rate);
80 };
81 for (std::size_t head = 0; head < states.size(); ++head) {
82 const std::vector<int> st = states[head];
83 out.clear();
84 const int q = st[0], b = st[1], a = st[2];
85 int ssum = 0, dsum = 0;
86 for (int i = 0; i < na; ++i) ssum += st[3 + i];
87 for (int j = 0; j < nb; ++j) dsum += st[3 + na + j];
88 const int off = k - b - a - ssum - dsum;
89 const int n = q + b;
90 const T l = n < nmax ? lam(n) : zero;
91 if (num_traits<T>::to_double(l) > 0) {
92 if (a > 0) {
93 emit(st, l, 2, -1, 1, 1);
94 } else if (dsum > 0) {
95 for (int j = 0; j < nb; ++j)
96 if (st[3 + na + j] > 0)
97 emit(st, T(l * num_traits<T>::from_int(st[3 + na + j]) / num_traits<T>::from_int(dsum)),
98 3 + na + j, -1, 1, 1);
99 } else if (off > 0) {
100 emit(st, l, 0, 1, 3, 1);
101 } else {
102 emit(st, l, 0, 1, -1, 0);
103 }
104 }
105 if (b > 0) {
106 const T rb = T(num_traits<T>::from_int(b) * mu);
107 if (q > 0) emit(st, rb, 0, -1, -1, 0);
108 else emit(st, rb, 1, -1, 3 + na, 1);
109 }
110 for (int i = 0; i < na; ++i) {
111 const int si = st[3 + i];
112 if (si <= 0) continue;
113 for (int i2 = 0; i2 < na; ++i2)
114 if (i2 != i && num_traits<T>::to_double(Ta(i, i2)) > 0)
115 emit(st, T(num_traits<T>::from_int(si) * Ta(i, i2)), 3 + i, -1, 3 + i2, 1);
116 if (num_traits<T>::to_double(ta[i]) > 0) {
117 // a finished setup takes the head of the queue, or stays ACTIVE and idle
118 std::vector<int> x = st;
119 x[3 + i] -= 1;
120 if (q > 0) {
121 x[0] -= 1;
122 x[1] += 1;
123 } else {
124 x[2] += 1;
125 }
126 out.emplace_back(x, T(num_traits<T>::from_int(si) * ta[i]));
127 }
128 }
129 for (int j = 0; j < nb; ++j) {
130 const int dj = st[3 + na + j];
131 if (dj <= 0) continue;
132 for (int j2 = 0; j2 < nb; ++j2)
133 if (j2 != j && num_traits<T>::to_double(Tb(j, j2)) > 0)
134 emit(st, T(num_traits<T>::from_int(dj) * Tb(j, j2)), 3 + na + j, -1, 3 + na + j2, 1);
135 if (num_traits<T>::to_double(tb[j]) > 0)
136 emit(st, T(num_traits<T>::from_int(dj) * tb[j]), 3 + na + j, -1, -1, 0);
137 }
138 for (const auto& o : out) {
139 auto it = index.find(o.first);
140 std::size_t to;
141 if (it == index.end()) {
142 to = states.size();
143 index[o.first] = to;
144 states.push_back(o.first);
145 } else {
146 to = it->second;
147 }
148 arcs.push_back(Arc{head, to, o.second});
149 }
150 }
151 const std::size_t m = states.size();
152 Matrix<T> Q(m, m, zero);
153 for (const Arc& arc : arcs) {
154 Q(arc.from, arc.to) += arc.rate;
155 Q(arc.from, arc.from) -= arc.rate;
156 }
157 const std::vector<T> pi = mc::ctmc_solve(Q);
158 double total = 0.0;
159 for (std::size_t i = 0; i < m; ++i) total += std::max(0.0, num_traits<T>::to_double(pi[i]));
161 res.QN = zero;
162 res.XN = zero;
163 ptop = 0.0;
164 if (total <= 0) return res;
165 const T norm = num_traits<T>::from_double(total);
166 T QN = zero, B = zero;
167 for (std::size_t i = 0; i < m; ++i) {
168 if (num_traits<T>::to_double(pi[i]) <= 0) continue;
169 const T p = T(pi[i] / norm);
170 QN += T(num_traits<T>::from_int(states[i][0] + states[i][1]) * p);
171 B += T(num_traits<T>::from_int(states[i][1]) * p);
172 if (states[i][0] + states[i][1] == nmax) ptop += num_traits<T>::to_double(p);
173 }
174 res.QN = QN;
175 res.XN = T(mu * B);
176 return res;
177}
178
179} // namespace detail
180
181/**
182 * Mean number present and throughput of a k-SERVER queue whose servers each pay
183 * a setup when woken from off and count down a delay-off when idle.
184 *
185 * The M/M/k/setup/delayedoff model of Gandhi, Harchol-Balter and Adan
186 * (Performance Evaluation 67(11), 2010), solved exactly as a finite CTMC over its
187 * REACHABLE states, under the admission rules of the LDES engine
188 * (Solver_ssj_ln.admitWithSetup), which are what a SetupTask means:
189 *
190 * - an arrival takes an ACTIVE idle server if one exists, else one still
191 * counting down its delay-off (the countdown is cancelled and it pays
192 * nothing), else it queues and wakes ONE off server, if any is off;
193 * - a server that completes takes the head of the queue, or starts its delay-off
194 * when the queue is empty, and powers off when that expires;
195 * - a setup is never cancelled: a server finishing it takes the head of the
196 * queue, or stays ACTIVE and idle with no countdown when the queue is empty.
197 *
198 * A state is (queued, busy, active-idle, setup phase counts, delay-off phase
199 * counts), the remaining servers being off. Both phases are taken in canonical
200 * Coxian form, entered at phase 1 for every SCV; servers in a phase are
201 * exchangeable, so an arrival that catches a delay-off server catches one in
202 * phase j with probability proportional to the count there, which is exact
203 * whenever the delay-off is exponential. With k = 1 the chain is the one
204 * `qbd_setupdelayoff_closed` solves.
205 *
206 * With N finite the population is closed and ARV is the complementary delay Z,
207 * so lambda(n) = (N-n)/Z. With N infinite ARV is the Poisson rate and the level
208 * is truncated where the probability of the top level falls below
209 * GlobalConstants::Zero, doubling the truncation until it does; the queue must
210 * then be stable, arv < k*mu.
211 *
212 * ARITHMETIC. Gated on num_traits<T>::has_transcendental, like the closed twin:
213 * the Coxian fit takes a square root.
214 *
215 * @param k number of servers
216 * @param N closed population, or infinity for an open queue
217 * @param arv complementary delay Z when N is finite, arrival rate when it is not
218 * @param mu service rate of one server
219 * @param alpharate rate of the setup phase
220 * @param alphascv SCV of the setup phase
221 * @param betarate rate of the delay-off phase
222 * @param betascv SCV of the delay-off phase
223 */
224template <class T>
225SetupDelayoffMMk<T> qbd_setupdelayoff_mmk(int k, double N, const T& arv, const T& mu,
226 const T& alpharate, const T& alphascv,
227 const T& betarate, const T& betascv) {
229 "qbd_setupdelayoff_mmk requires transcendental arithmetic");
230 const T zero = num_traits<T>::from_int(0);
231 if (k <= 0 || num_traits<T>::to_double(mu) <= 0)
232 throw InputError("qbd_setupdelayoff_mmk needs k >= 1 servers and a positive service rate");
234 res.QN = zero;
235 res.XN = zero;
236 double ptop = 0.0;
237 if (std::isfinite(N)) {
238 const int pop = static_cast<int>(std::llround(N));
239 if (pop <= 0) return res;
240 T Z = arv;
243 const std::function<T(int)> lam = [pop, Z](int n) {
244 return T(num_traits<T>::from_int(pop - n) / Z);
245 };
246 return detail::setupdelayoff_mmk_solve<T>(k, pop, lam, mu, alpharate, alphascv, betarate,
247 betascv, ptop);
248 }
249 if (num_traits<T>::to_double(arv) <= 0) return res;
251 throw InputError("qbd_setupdelayoff_mmk: the open queue is unstable, arrival rate " +
252 std::to_string(num_traits<T>::to_double(arv)) + " against capacity " +
253 std::to_string(k * num_traits<T>::to_double(mu)));
254 const T rate = arv;
255 const std::function<T(int)> lam = [rate](int) { return rate; };
256 int nmax = 2 * k + 32;
257 while (true) {
258 res = detail::setupdelayoff_mmk_solve<T>(k, nmax, lam, mu, alpharate, alphascv, betarate,
259 betascv, ptop);
260 if (ptop < lang::GlobalConstants::Zero) return res;
261 nmax *= 2;
262 }
263}
264
265} // namespace mam
266} // namespace line
267
268#endif // LINE_API_MAM_QBD_SETUPDELAYOFF_MMK_H
InputError(const std::string &what)
Definition error.h:105
std::size_t rows() const
Definition matrix.h:89
Steady-state distribution of a continuous-time Markov chain.
The exception types the port throws.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
Dense matrix and non-owning view.
Matrix< T > coxian_phase_subgen(const T &rate, const T &scv)
Sub-generator of the canonical Coxian form with the given RATE and SCV, entered at phase 1 (the coxia...
SetupDelayoffMMk< T > qbd_setupdelayoff_mmk(int k, double N, const T &arv, const T &mu, const T &alpharate, const T &alphascv, const T &betarate, const T &betascv)
Mean number present and throughput of a k-SERVER queue whose servers each pay a setup when woken from...
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
Definition ctmc_solve.h:122
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Number-type abstraction for the templated API port.
Mean queue length of an M/M/1 queue with a setup delay and a delayed-off period, solved as a QBD.
static constexpr double FineTol
Definition lang_types.h:765
static constexpr double Zero
Definition lang_types.h:767
Mean number present and throughput of the setup/delayed-off pool.
T QN
mean number at the station: queued, waiting for a setup or in service
T XN
throughput of the station