5#ifndef LINE_API_QSYS_GTMTST_FLUID_H
6#define LINE_API_QSYS_GTMTST_FLUID_H
91 std::function<T(
const T&)>
pdf;
99T tv_interp(
const std::vector<T>& xs,
const std::vector<T>& ys,
const T& x) {
100 const std::size_t n = xs.size();
101 if (x <= xs[0])
return ys[0];
102 if (x >= xs[n - 1])
return ys[n - 1];
103 std::size_t lo = 0, hi = n - 1;
104 while (hi - lo > 1) {
105 const std::size_t mid = (lo + hi) / 2;
112 const T f = (x - xs[lo]) / (xs[hi] - xs[lo]);
113 return ys[lo] + f * (ys[hi] - ys[lo]);
120template <
class T,
class LamOf,
class Weight>
121T tv_integrate(LamOf&& lamOf, Weight&& weight,
const T& ti,
const T& wi,
const T& dt) {
123 if (wi <= zero)
return zero;
124 std::size_t m =
static_cast<std::size_t
>(
128 T
sum = lamOf(ti) * weight(zero) + lamOf(T(ti - wi)) * weight(wi);
129 for (std::size_t j = 1; j < m; ++j) {
133 return h / num_traits<T>::from_int(3) * sum;
150 const std::function<Tv(
const Tv&)>& sFun,
151 const std::function<Tv(
const Tv&)>& muFun,
152 const std::function<Tv(
const Tv&)>& patienceCcdf,
156 "qsys_gtmtst_fluid integrates on a grid, so it needs inexact arithmetic");
160 if (T <= zero)
throw InputError(
"qsys_gtmtst_fluid: the horizon T must be positive");
163 const std::size_t n =
165 if (n < 2)
throw InputError(
"qsys_gtmtst_fluid: the grid needs at least two points");
169 for (std::size_t i = 0; i < n; ++i)
174 std::vector<Tv> lam(n), s(n), mu(n), sp(n), gamma(n);
175 for (std::size_t i = 0; i < n; ++i) {
176 lam[i] = lambdaFun(r.
times[i]);
177 s[i] = sFun(r.
times[i]);
178 mu[i] = muFun(r.
times[i]);
179 if (s[i] <= zero)
throw InputError(
"qsys_gtmtst_fluid: the staffing must be positive");
180 if (mu[i] <= zero)
throw InputError(
"qsys_gtmtst_fluid: the service rate must be positive");
183 for (std::size_t i = 0; i < n; ++i) sp[i] = opts.sPrime(r.
times[i]);
185 for (std::size_t i = 0; i < n; ++i) {
187 sp[i] = (s[1] - s[0]) / step;
188 }
else if (i + 1 == n) {
189 sp[i] = (s[n - 1] - s[n - 2]) / step;
191 sp[i] = (s[i + 1] - s[i - 1]) / (two * step);
195 for (std::size_t i = 0; i < n; ++i) gamma[i] = sp[i] + s[i] * mu[i];
197 const std::function<Tv(
const Tv&)> past = opts.lambdaPast ? opts.lambdaPast : lambdaFun;
198 auto lamOf = [&](
const Tv& u) {
return u < zero ? past(u) : lambdaFun(u); };
199 std::function<Tv(
const Tv&)> pdf = opts.pdf;
201 pdf = [&patienceCcdf](
const Tv& x) {
213 r.
alpha.assign(n, zero);
218 r.
regime[0] = over0 ? 1 : 0;
219 if (over0) r.
B[0] = s[0];
220 r.
Q[0] = detail::tv_integrate<Tv>(lamOf, patienceCcdf, r.
times[0], r.
w[0], step);
221 r.
alpha[0] = detail::tv_integrate<Tv>(lamOf, pdf, r.
times[0], r.
w[0], step);
223 auto wdot = [&](
const Tv& tt,
const Tv& ww) {
224 const Tv den = lamOf(Tv(tt - ww)) * patienceCcdf(ww);
227 if (den <= zero)
return one;
228 return Tv(one - detail::tv_interp(r.
times, gamma, tt) / den);
231 for (std::size_t i = 0; i + 1 < n; ++i) {
235 auto f = [&](
const Tv& tt,
const Tv& bb) {
236 return detail::tv_interp(r.
times, lam, tt) - detail::tv_interp(r.
times, mu, tt) * bb;
238 const Tv k1 = f(r.
times[i], r.
B[i]);
239 const Tv k2 = f(Tv(r.
times[i] + step / two), Tv(r.
B[i] + step * k1 / two));
240 const Tv k3 = f(Tv(r.
times[i] + step / two), Tv(r.
B[i] + step * k2 / two));
241 const Tv k4 = f(Tv(r.
times[i] + step), Tv(r.
B[i] + step * k3));
244 if (bNext >= s[i + 1] && lam[i + 1] > gamma[i + 1]) {
251 if (bNext > s[i + 1]) bNext = s[i + 1];
255 const Tv k1 = wdot(r.
times[i], r.
w[i]);
256 const Tv w2 = r.
w[i] + step * k1 / two;
257 const Tv k2 = wdot(Tv(r.
times[i] + step / two), w2 < zero ? zero : w2);
258 const Tv w3 = r.
w[i] + step * k2 / two;
259 const Tv k3 = wdot(Tv(r.
times[i] + step / two), w3 < zero ? zero : w3);
260 const Tv w4 = r.
w[i] + step * k3;
261 const Tv k4 = wdot(Tv(r.
times[i] + step), w4 < zero ? zero : w4);
264 if (wNext <= zero && lam[i + 1] <= gamma[i + 1]) {
270 if (wNext < zero) wNext = zero;
276 if (r.
regime[i + 1] == 1) {
277 r.
Q[i + 1] = detail::tv_integrate<Tv>(lamOf, patienceCcdf, r.
times[i + 1], wNext, step);
278 r.
alpha[i + 1] = detail::tv_integrate<Tv>(lamOf, pdf, r.
times[i + 1], wNext, step);
285 std::vector<Tv> entry(n);
286 for (std::size_t i = 0; i < n; ++i) {
287 r.
sigma[i] = mu[i] * r.
B[i];
289 r.
X[i] = r.
B[i] + r.
Q[i];
290 entry[i] = r.
times[i] - r.
w[i];
296 for (std::size_t i = 0; i < n; ++i) {
297 const Tv u = detail::tv_interp(entry, r.
times, r.
times[i]);
298 r.
v[i] = u - r.
times[i] < zero ? zero : Tv(u - r.
times[i]);
310 std::vector<QsysTvFluidResult<T>>
queues;
333 const std::vector<std::function<Tv(
const Tv&)>>& lambdaFuns,
334 const std::vector<std::function<Tv(
const Tv&)>>& sFuns,
335 const std::vector<std::function<Tv(
const Tv&)>>& muFuns,
336 const std::vector<std::function<Tv(
const Tv&)>>& patienceCcdfs,
338 const std::vector<Tv>& B0 = std::vector<Tv>(),
const std::vector<Tv>& w0 = std::vector<Tv>(),
339 double tol = 1e-6, std::size_t maxIter = 100) {
341 "npfqn_gtmtst_fluid integrates on a grid, so it needs inexact arithmetic");
344 const std::size_t m = lambdaFuns.size();
345 if (sFuns.size() != m || muFuns.size() != m || patienceCcdfs.size() != m)
346 throw InputError(
"npfqn_gtmtst_fluid: every queue needs an arrival rate, a staffing, a "
347 "service rate and a patience law");
348 if (T <= zero)
throw InputError(
"npfqn_gtmtst_fluid: the horizon T must be positive");
351 const std::size_t n =
353 if (P.size() != m)
throw InputError(
"npfqn_gtmtst_fluid: the routing matrix must be m x m");
354 for (std::size_t i = 0; i < m; ++i) {
355 if (P[i].size() != m)
356 throw InputError(
"npfqn_gtmtst_fluid: the routing matrix must be m x m");
358 for (std::size_t j = 0; j < m; ++j) {
360 throw InputError(
"npfqn_gtmtst_fluid: the routing matrix must be non-negative");
364 throw InputError(
"npfqn_gtmtst_fluid: the routing matrix must be substochastic");
369 for (std::size_t i = 0; i < n; ++i)
373 std::vector<std::vector<Tv>> ext(m, std::vector<Tv>(n, zero));
374 for (std::size_t i = 0; i < m; ++i)
375 for (std::size_t k = 0; k < n; ++k) ext[i][k] = lambdaFuns[i](out.
times[k]);
376 std::vector<std::vector<Tv>> lam = ext;
379 for (std::size_t iter = 1; iter <= maxIter; ++iter) {
382 std::vector<std::vector<Tv>> sigma(m, std::vector<Tv>(n, zero));
383 for (std::size_t i = 0; i < m; ++i) {
384 const std::vector<Tv>& row = lam[i];
385 const std::vector<Tv>& grid = out.
times;
386 std::function<Tv(
const Tv&)> fun = [&grid, &row](
const Tv& u) {
387 return detail::tv_interp(grid, row, u);
391 o.
B0 = B0.empty() ? zero : B0[i];
392 o.
w0 = w0.empty() ? zero : w0[i];
395 sigma[i] = res.
sigma;
396 out.
queues.push_back(std::move(res));
400 std::vector<std::vector<Tv>> newlam = ext;
401 for (std::size_t j = 0; j < m; ++j) {
402 for (std::size_t k = 0; k < n; ++k) {
403 for (std::size_t i = 0; i < m; ++i) newlam[j][k] += sigma[i][k] * P[i][j];
404 const Tv d = newlam[j][k] - lam[j][k];
405 const Tv ad = d < zero ? Tv(-d) : d;
406 if (ad > diff) diff = ad;
The exception types the port throws.
QsysTvFluidResult< Tv > qsys_gtmtst_fluid(const std::function< Tv(const Tv &)> &lambdaFun, const std::function< Tv(const Tv &)> &sFun, const std::function< Tv(const Tv &)> &muFun, const std::function< Tv(const Tv &)> &patienceCcdf, const Tv &T, const TvFluidOptions< Tv > &opts=TvFluidOptions< Tv >())
The Gt/Mt/st+GI many-server fluid queue, and the network of them.
NpfqnTvFluidResult< Tv > npfqn_gtmtst_fluid(const std::vector< std::function< Tv(const Tv &)> > &lambdaFuns, const std::vector< std::function< Tv(const Tv &)> > &sFuns, const std::vector< std::function< Tv(const Tv &)> > &muFuns, const std::vector< std::function< Tv(const Tv &)> > &patienceCcdfs, const std::vector< std::vector< Tv > > &P, const Tv &T, const Tv &dt=num_traits< Tv >::from_int(0), const std::vector< Tv > &B0=std::vector< Tv >(), const std::vector< Tv > &w0=std::vector< Tv >(), double tol=1e-6, std::size_t maxIter=100)
A time-varying open network of many-server fluid queues with abandonment.
Number-type abstraction for the templated API port.
Shared return type and arithmetic helpers for the templated qsys port.
Result of the network solve.
std::vector< std::vector< T > > arrivalRates
converged total rates, one row per queue
T residual
sup-norm change at the last iteration
std::size_t iterations
iterations of the traffic-rate fixed point
std::vector< QsysTvFluidResult< T > > queues
the per-queue trajectories
std::vector< T > times
the time grid
Trajectory of the Gt/Mt/st+GI fluid queue; every vector is on the time grid.
std::vector< T > alpha
abandonment rate
std::vector< T > utilization
B/s.
std::vector< T > Q
fluid in queue
std::vector< T > arrivalRate
lambda on the grid
std::vector< T > v
potential waiting time
std::vector< int > regime
1 overloaded, 0 underloaded
std::vector< T > capacityRate
Gamma = s' + s mu.
std::vector< T > sigma
service completion rate mu B
std::vector< T > B
fluid in service
std::vector< T > w
boundary waiting time
std::vector< T > times
the time grid
std::vector< T > staffing
s on the grid
Options of qsys_gtmtst_fluid, all with the MATLAB defaults.
T B0
fluid in service at time 0
T dt
grid step; non-positive takes T/2000
std::function< T(const T &)> lambdaPast
arrival rate before time 0
T w0
boundary waiting time at time 0
std::function< T(const T &)> sPrime
s'(t); differentiated numerically when empty
std::function< T(const T &)> pdf
patience density; differenced when empty