LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fluid_qsys.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_SOLVERS_FLUID_FLUID_QSYS_H
6#define LINE_SOLVERS_FLUID_FLUID_QSYS_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Port of matlab/src/solvers/FLD/solver_fluid_qsys_analyzer.m: the
12 * single-station fluid limits.
13 *
14 * A Source -> Queue -> Sink model with one class, answered by a closed-form
15 * fluid or Gaussian limit rather than by integrating the network drift.
16 *
17 * WHY THESE ARE FLUID METHODS AND NOT MVA ONES. Each depends on the service or
18 * patience law BEYOND ITS MEAN -- the stationary point of the Liu-Whitt model is
19 * where the patience ccdf crosses 1/rho, the Mt/G/inf mean is a convolution with
20 * the service ccdf -- and each is the limit of a sequence of systems, not an
21 * approximation to a fixed one. That is the fluid solver's contract.
22 *
23 * METHODS
24 * `ggisgi` stationary point of the G/GI/s+GI fluid model (Liu and Whitt,
25 * Operations Research 60(5), 2012)
26 * `ggingi` truncated Gaussian approximation, the O(sqrt(n)) fluctuation
27 * around that point (Liu, Whitt and Yu, NRL 63(3), 2016)
28 * `tvms` the Gt/Mt/st+GI many-server fluid queue at CONSTANT staffing
29 * (Liu and Whitt, INFORMS J. Computing 26(1), 2014)
30 * `mtginf` the exact Mt/G/inf mean (Eick, Massey and Whitt, Management
31 * Science 39(2), 1993)
32 * `mol` the modified-offered-load approximation for a finite server
33 * count (Massey and Whitt, Ann. Appl. Prob. 4(4), 1994)
34 *
35 * ARITHMETIC: transcendental. Every one of them integrates or bisects.
36 */
37
38#include <algorithm>
39#include <cmath>
40#include <cstddef>
41#include <functional>
42#include <limits>
43#include <string>
44#include <vector>
45
55#include "line/num/number.h"
57
58namespace line {
59namespace fluid {
60
61namespace detail {
62
63/**
64 * The method names the single-station limits answer, AFTER `fluid_unqualify`.
65 *
66 * That strips a leading `fld.`, so the qualified spellings arrive here as the
67 * canonical bare names reported by `FluidSolution::method`.
68 */
69inline bool fluid_qsys_handles(const std::string& m) {
70 return m == "ggisgi" || m == "ggingi" ||
71 m == "tvms" || m == "mtginf" || m == "mol";
72}
73
74/** The primary name of an alias. */
75inline std::string fluid_qsys_canonical(const std::string& m) { return m; }
76
77/**
78 * The integration window of a time-varying single-station limit, asked as a
79 * predicate rather than thrown.
80 *
81 * A horizon is a solver OPTION and not a model feature, so the feature registry
82 * has no name for it and a report has to ask this predicate directly.
83 * `fluid_qsys_horizon` asks the same one on the solve path, which is what keeps
84 * the report and the run from disagreeing about whether a method can be asked
85 * for.
86 *
87 * @param opt the fluid knobs, read for the end of the horizon
88 * @return an empty string when the window is a finite non-empty interval
89 */
90inline std::string fluid_qsys_horizon_reason(const FluidOptions& opt) {
91 const double t1 = opt.timespan_end;
92 if (!std::isfinite(t1) || t1 <= 0.0)
93 return "solver_fluid_qsys: a time-varying fluid method needs a finite horizon; set "
94 "options.timespan_end";
95 return std::string();
96}
97
98/**
99 * The integration window, [0, timespan_end].
100 *
101 * The C++ FluidOptions carries only the END of the horizon, where MATLAB and
102 * Python carry a pair; the start is 0 for every fluid route in this port, which
103 * is also what `runAnalyzer.m` forces when the start is not finite. A
104 * non-positive or infinite end has no trajectory to report, and rather than
105 * substituting `fluid_default_horizon`'s 30/min_rate -- a stationary-model rule
106 * of thumb that says nothing about the PERIOD of a time-varying arrival -- the
107 * caller is asked for one.
108 */
109template <class T>
110void fluid_qsys_horizon(const qn::NetworkStruct<T>&, const FluidOptions& opt, double& t0,
111 double& t1) {
112 const std::string reason = fluid_qsys_horizon_reason(opt);
113 if (!reason.empty()) throw UnsupportedError(ErrorKind{"Option"}, reason);
114 t0 = 0.0;
115 t1 = opt.timespan_end;
116}
117
118} // namespace detail
119
120/**
121 * The three single-station limits that report a TRAJECTORY rather than a
122 * stationary point, and so need a finite horizon; `ggisgi` and `ggingi` are
123 * stationary and are not among them.
124 *
125 * @param m the method name, already unqualified
126 * @return true when the method integrates over a finite horizon
127 */
128inline bool fluid_is_time_varying_limit(const std::string& m) {
129 return m == "tvms" || m == "mtginf" || m == "mol";
130}
131
132/**
133 * The horizon rule the time-varying limits impose, as a public predicate a
134 * REPORT can ask: empty when `method` may be asked for with these options.
135 *
136 * `solver_fluid_qsys` refuses through the same body, so a pair the report
137 * offers is a pair the limit runs.
138 *
139 * @param method the method name, qualified or not
140 * @param opt the fluid knobs, read for the end of the horizon
141 * @return an empty string when the method may run, else the refusal
142 */
143inline std::string fluid_qsys_horizon_supports(const std::string& method,
144 const FluidOptions& opt) {
145 // The `fld.` prefix is stripped here rather than through
146 // `detail::fluid_unqualify`, which lives in fluid_runner.h: that header
147 // includes this one, so the dependency only goes the one way.
148 const std::string m = (method.size() > 4 && method.compare(0, 4, "fld.") == 0)
149 ? method.substr(4)
150 : method;
151 if (!fluid_is_time_varying_limit(m)) return std::string();
152 const std::string reason = detail::fluid_qsys_horizon_reason(opt);
153 if (reason.empty()) return std::string();
154 return "the '" + method + "' method reports a trajectory. " + reason;
155}
156
157/**
158 * Solve a single-station model with one of the closed-form fluid limits.
159 *
160 * The steady-state row of a TIME-VARYING model is the TIME AVERAGE over the
161 * horizon, which is what a stationary reader of a periodic system measures; the
162 * trajectory itself is available from `solver_fluid_qsys_transient`.
163 */
164template <class T>
166 std::vector<FluidTranPoint>* traj = nullptr) {
167 if constexpr (!num_traits<T>::has_transcendental) {
168 throw UnsupportedError(ErrorKind{"Arithmetic"},
169 "solver_fluid_qsys: the single-station fluid limits integrate and bisect, so they need "
170 "transcendental arithmetic; rerun with --arith double or --arith real");
171 } else {
172 const std::size_t M = sn.nstations;
173 const std::size_t K = sn.nclasses;
174 FluidSolution out;
175 out.QN = Matrix<double>(M, K, 0.0);
176 out.UN = Matrix<double>(M, K, 0.0);
177 out.RN = Matrix<double>(M, K, 0.0);
178 out.TN = Matrix<double>(M, K, 0.0);
179 out.CN.assign(K, 0.0);
180 out.XN.assign(K, 0.0);
181 out.iters = 1;
182
183 std::size_t src = 0;
184 std::size_t qi = 0;
185 bool haveSrc = false;
186 bool haveQ = false;
187 for (std::size_t i = 0; i < sn.nof_nodes(); ++i) {
188 if (sn.nodes[i].nodetype == qn::NodeType::Source) {
189 src = sn.nodes[i].station - 1;
190 haveSrc = true;
191 } else if (sn.nodes[i].nodetype == qn::NodeType::Queue ||
192 sn.nodes[i].nodetype == qn::NodeType::Delay) {
193 qi = sn.nodes[i].station - 1;
194 haveQ = true;
195 }
196 }
197 // THE SHAPE THESE LIMITS ARE STATED FOR, refused by name rather than
198 // answered on a model they do not describe: one open class through one
199 // queueing station. The MVA qsys analyzer is reached by a structural
200 // dispatch that guarantees it; these methods are selected by NAME, so the
201 // check has to live here.
202 if (!haveSrc || !haveQ)
203 throw UnsupportedError(ErrorKind{"Topology"},
204 "solver_fluid_qsys: the single-station fluid limits need a Source and a queueing "
205 "station");
206 if (K != 1 || sn.nclosedjobs() > 0)
207 throw UnsupportedError(ErrorKind{"Topology"}, "solver_fluid_qsys: the '" + opt.method +
208 "' method is a single-station limit: it needs one open class "
209 "through one Source and one queueing station");
210
211 const std::size_t qstateful = sn.stateful_of_station(qi + 1);
212 const T Vq = sn.visits[0](qstateful - 1, 0);
213 const T lambda = T(sn.rates(src, 0) * Vq);
214 const T mu = sn.rates(qi, 0);
215 const double nserv = sn.stations[qi].nservers;
216 const T scvS = sn.scv(qi, 0);
217 const T ca = num_traits<T>::from_double(std::sqrt(num_traits<T>::to_double(sn.scv(src, 0))));
218 const T cs = num_traits<T>::from_double(std::sqrt(num_traits<T>::to_double(scvS)));
220 {
221 const std::string patWhy = api::sn_patience_refusal(sn, qi, 0);
222 if (!patWhy.empty()) throw UnsupportedError(ErrorKind{"Structure"}, "solver_fluid_qsys: " + patWhy);
223 }
224
225 // The service ccdf, needed by the two Mt/G methods: they are exact in the
226 // service DISTRIBUTION, not in its mean, which is the whole point of the
227 // Eick-Massey-Whitt lag.
228 const lang::Distrib<T>& svc = sn.service[qi][0];
229 std::function<T(const T&)> serviceCcdf;
230 if (svc.has_map()) {
231 const mam::Map<T> sm = lang::dist_to_map(svc);
232 const T one = num_traits<T>::from_int(1);
233 serviceCcdf = [sm, one](const T& x) {
234 std::vector<T> pts(1, x);
235 return T(one - mam::map_cdf(sm, pts)[0]);
236 };
237 } else {
238 serviceCcdf = [mu](const T& x) { return qsys::detail::num_exp(T(-mu * x)); };
239 }
240 const T ES = T(num_traits<T>::from_int(1) / mu);
241 const double ES2 =
244
245 const std::string m = detail::fluid_qsys_canonical(opt.method);
246 const double VqD = num_traits<T>::to_double(Vq);
247 const double lamD = num_traits<T>::to_double(lambda);
248 const double muD = num_traits<T>::to_double(mu);
249
250 // Little's law on the CARRIED rate, as every LINE solver reports a station
251 // that loses work.
252 auto stationary = [&](double Lsys, double Tq, double Uq) {
253 const double R = Tq > 0 ? Lsys / Tq : 0.0;
254 out.RN(qi, 0) = R;
255 out.QN(qi, 0) = Lsys;
256 out.UN(qi, 0) = Uq;
257 out.TN(qi, 0) = Tq;
258 out.TN(src, 0) = lamD / VqD;
259 out.XN[0] = Tq;
260 out.CN[0] = R * VqD;
261 };
262
263 auto transient = [&](const std::vector<T>& t, const std::vector<T>& Lt,
264 const std::vector<T>& Ut, const std::vector<T>& Tt,
265 const std::vector<T>& arrival) {
266 const std::size_t n = t.size();
267 std::vector<double> td(n), Ld(n), Ud(n), Td(n), Ad(n);
268 for (std::size_t i = 0; i < n; ++i) {
269 td[i] = num_traits<T>::to_double(t[i]);
270 Ld[i] = num_traits<T>::to_double(Lt[i]);
271 Ud[i] = num_traits<T>::to_double(Ut[i]);
272 Td[i] = num_traits<T>::to_double(Tt[i]);
273 Ad[i] = num_traits<T>::to_double(arrival[i]);
274 }
275 auto trapz = [&](const std::vector<double>& y) {
276 double s = 0;
277 for (std::size_t i = 1; i < n; ++i) s += 0.5 * (y[i] + y[i - 1]) * (td[i] - td[i - 1]);
278 return s;
279 };
280 const double span = td[n - 1] - td[0];
281 const double Lbar = span > 0 ? trapz(Ld) / span : Ld[0];
282 const double Ubar = span > 0 ? trapz(Ud) / span : Ud[0];
283 const double Tbar = span > 0 ? trapz(Td) / span : Td[0];
284 const double Abar = span > 0 ? trapz(Ad) / span : Ad[0];
285 out.QN(qi, 0) = Lbar;
286 out.UN(qi, 0) = Ubar;
287 out.TN(qi, 0) = Tbar;
288 out.TN(src, 0) = Abar / VqD;
289 out.RN(qi, 0) = Tbar > 0 ? Lbar / Tbar : 0.0;
290 out.XN[0] = Tbar;
291 out.CN[0] = out.RN(qi, 0) * VqD;
292 if (traj != nullptr) {
293 traj->clear();
294 traj->reserve(n);
295 for (std::size_t i = 0; i < n; ++i) {
297 p.t = td[i];
298 p.QN = Matrix<double>(M, K, 0.0);
299 p.UN = Matrix<double>(M, K, 0.0);
300 p.TN = Matrix<double>(M, K, 0.0);
301 p.QN(qi, 0) = Ld[i];
302 p.UN(qi, 0) = Ud[i];
303 p.TN(qi, 0) = Td[i];
304 p.TN(src, 0) = Ad[i] / VqD;
305 traj->push_back(p);
306 }
307 }
308 };
309
310 auto requirePatience = [&]() {
311 if (!h.present)
312 throw UnsupportedError(ErrorKind{"Reneging"}, "solver_fluid_qsys: the '" + m +
313 "' method needs a reneging patience law on the queue "
314 "(Queue.setPatience)");
315 };
316 auto requireFiniteServers = [&]() {
317 if (!std::isfinite(nserv) || nserv < 1)
318 throw UnsupportedError(ErrorKind{"DelayStation"}, "solver_fluid_qsys: the '" + m +
319 "' method needs a finite number of servers");
320 };
321 auto linspaceT = [](double a, double b, std::size_t n) {
322 std::vector<T> v(n);
323 for (std::size_t i = 0; i < n; ++i)
324 v[i] = num_traits<T>::from_double(a + (b - a) * static_cast<double>(i) /
325 static_cast<double>(n - 1));
326 return v;
327 };
328
329 if (m == "ggisgi") {
330 requirePatience();
332 lambda, mu, static_cast<unsigned>(std::llround(nserv)), h.ccdf);
335 } else if (m == "ggingi") {
336 requirePatience();
337 requireFiniteServers();
339 lambda, mu, static_cast<unsigned>(std::llround(nserv)), ca, cs, h.ccdf, h.pdf,
340 serviceCcdf);
341 const double pa = num_traits<T>::to_double(r.probAbandon);
342 stationary(num_traits<T>::to_double(r.meanNumber), lamD * (1.0 - pa),
343 std::min(num_traits<T>::to_double(r.meanNumberInService) / nserv, 1.0));
344 } else if (m == "tvms") {
345 requirePatience();
346 requireFiniteServers();
348 double t0 = 0, t1 = 0;
349 detail::fluid_qsys_horizon(sn, opt, t0, t1);
350 // CONSTANT STAFFING. Nothing in a Network declares a time-varying server
351 // count, so s(t) is the station's own s; the time variation the method
352 // is for enters through lambda(t) alone. A staffing schedule would need
353 // a model feature that does not exist, and inventing one here would make
354 // the solver answer a model the user did not build.
355 const T sT = num_traits<T>::from_double(nserv);
357 tvopt.pdf = h.pdf;
359 rf.lambda, [sT](const T&) { return sT; }, [mu](const T&) { return mu; }, h.ccdf,
360 num_traits<T>::from_double(t1 - t0), tvopt);
361 std::vector<T> times = r.times;
362 for (std::size_t i = 0; i < times.size(); ++i)
363 times[i] = T(times[i] + num_traits<T>::from_double(t0));
364 std::vector<T> served(r.B.size());
365 for (std::size_t i = 0; i < r.B.size(); ++i) served[i] = T(mu * r.B[i]);
366 transient(times, r.X, r.utilization, served, r.arrivalRate);
367 } else if (m == "mtginf") {
369 double t0 = 0, t1 = 0;
370 detail::fluid_qsys_horizon(sn, opt, t0, t1);
372 rf.lambda, serviceCcdf, ES, linspaceT(t0, t1, 200),
373 -std::numeric_limits<double>::infinity(), ES2);
374 // An infinite-server station serves everything that arrives, so the
375 // throughput is the arrival rate and the busy-server count is what a
376 // utilization column can carry.
377 transient(r.times, r.meanNumber, r.meanNumber, r.arrivalRate, r.arrivalRate);
378 } else if (m == "mol") {
379 requireFiniteServers();
381 double t0 = 0, t1 = 0;
382 detail::fluid_qsys_horizon(sn, opt, t0, t1);
383 const double cap = sn.cap[qi];
384 // A finite buffer beyond the servers is not part of the loss model the
385 // approximation is for; only s servers and no waiting room is.
386 const bool useDelay = std::isfinite(cap) && cap > nserv;
388 rf.lambda, serviceCcdf, ES, static_cast<unsigned>(std::llround(nserv)),
389 linspaceT(t0, t1, 200), -std::numeric_limits<double>::infinity(), useDelay);
390 std::vector<T> util(r.meanBusyMOL.size());
391 std::vector<T> served(r.meanBusyMOL.size());
392 const T sT = num_traits<T>::from_double(nserv);
393 for (std::size_t i = 0; i < r.meanBusyMOL.size(); ++i) {
394 util[i] = T(r.meanBusyMOL[i] / sT);
395 served[i] = T(mu * r.meanBusyMOL[i]);
396 }
397 transient(r.times, r.meanBusyMOL, util, served, r.arrivalRate);
398 } else {
399 throw UnsupportedError(ErrorKind{"Method"}, "solver_fluid_qsys: the '" + m +
400 "' method is not a single-station fluid limit");
401 }
402 out.method = m;
403 return out;
404 } // if constexpr has_transcendental
405}
406
407} // namespace fluid
408} // namespace line
409
410#endif // LINE_SOLVERS_FLUID_FLUID_QSYS_H
UnsupportedError(const std::string &what)
Definition error.h:119
A network plus its refreshed NetworkStruct.
Cumulative distribution of the inter-arrival time of a MAP.
PatienceHandles< T > sn_patience_handles(const qn::NetworkStruct< T > &sn, std::size_t ist, std::size_t r)
Build the patience handles of station ist (0-based), class r.
std::string sn_patience_refusal(const qn::NetworkStruct< T > &sn, std::size_t ist, std::size_t r)
Port of sn_patience_refusal.m: the by-name refusal of a reneging patience that is not a Markovian law...
ArrivalRateFun< T > sn_arrival_rate_fun(const qn::NetworkStruct< T > &sn, std::size_t ist, std::size_t r)
Build lambda(t) for station ist (0-based), class r.
std::string fluid_qsys_horizon_supports(const std::string &method, const FluidOptions &opt)
The horizon rule the time-varying limits impose, as a public predicate a REPORT can ask: empty when m...
Definition fluid_qsys.h:143
bool fluid_is_time_varying_limit(const std::string &m)
The three single-station limits that report a TRAJECTORY rather than a stationary point,...
Definition fluid_qsys.h:128
FluidSolution solver_fluid_qsys(const qn::NetworkStruct< T > &sn, const FluidOptions &opt, std::vector< FluidTranPoint > *traj=nullptr)
Solve a single-station model with one of the closed-form fluid limits.
Definition fluid_qsys.h:165
mam::Map< T > dist_to_map(const Distrib< T > &d)
The (D0,D1) pair that reaches sn.proc.
std::vector< T > map_cdf(const Map< T > &m, const std::vector< T > &points)
Cumulative distribution of the inter-arrival time at the given points.
Definition map_cdf.h:63
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.
QsysFluidAbandonResult< T > qsys_ggisgi_fluid(const T &lambda, const T &mu, unsigned s, const std::function< T(const T &)> &patienceCcdf, const std::function< T(const T &)> &servingCcdf=std::function< T(const T &)>(), const std::vector< T > &agePoints=std::vector< T >(), double tol=1e-12, double maxTime=std::numeric_limits< double >::quiet_NaN())
Steady state of the G/GI/s+GI fluid model.
QsysMtginfResult< T > qsys_mtginf(const std::function< T(const T &)> &lambdaFun, const std::function< T(const T &)> &serviceCcdf, const T &ES, const std::vector< T > &tvals, double startTime=-std::numeric_limits< double >::infinity(), double ES2=std::numeric_limits< double >::quiet_NaN(), const std::function< T(const T &)> &servicePdf=std::function< T(const T &)>(), double tol=1e-12, std::size_t panels=4000, double maxAge=1e12)
Exact time-varying analysis of the Mt/G/infinity queue.
QsysMolResult< T > qsys_mtgs0_mol(const std::function< T(const T &)> &lambdaFun, const std::function< T(const T &)> &serviceCcdf, const T &ES, unsigned s, const std::vector< T > &tvals, double startTime=-std::numeric_limits< double >::infinity(), bool delay=false)
Modified-offered-load and pointwise-stationary approximations for a time-varying multiserver system.
QsysTgaResult< T > qsys_ggingi_tga(const T &lambda, const T &mu, unsigned n, const T &ca, const T &cs, const std::function< T(const T &)> &patienceCcdf, const std::function< T(const T &)> &patiencePdf=std::function< T(const T &)>(), const std::function< T(const T &)> &serviceCcdf=std::function< T(const T &)>())
Truncated Gaussian approximation (TGA-G) for the G/GI/n+GI queue.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Truncated Gaussian approximation (TGA-G) for the G/GI/n+GI queue.
Steady state of the G/GI/s+GI fluid model.
The Gt/Mt/st+GI many-server fluid queue, and the network of them.
Exact time-varying analysis of the Mt/G/infinity queue.
Modified-offered-load and pointwise-stationary approximations for a time-varying multiserver system.
Port of matlab/src/api/sn/sn_arrival_rate_fun.m.
Port of matlab/src/api/sn/sn_patience_handles.m.
SolverFluid: the closing method, a port of solver_fluid.m, solver_fluid_iteration....
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
lambda(t), with whether it actually varies and the cycle length.
std::function< T(const T &)> lambda
the rate as a function of time
The patience law of one station-class pair, in the forms the solvers consume.
std::function< T(const T &)> pdf
The patience density.
std::function< T(const T &)> ccdf
F^c(t) = P(patience > t).
bool present
Whether the pair declares reneging at all; everything below is unset when false.
Controls, defaulting to SolverOptions('Fluid') in the reference.
What the analyzer returns, in the same shape as the MVA solver's result.
std::vector< double > XN
std::vector< double > CN
One point of a transient trajectory: the metrics at time t.
bool has_map() const
True when the type carries a (D0,D1) pair of its own.
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Steady state of the G/GI/s+GI fluid model.
MOL and PSA measures of a time-varying multiserver system.
std::vector< T > times
the evaluation times
std::vector< T > meanBusyMOL
carried load m(t)(1-B), or min(m,s) for the delay model
std::vector< T > arrivalRate
lambda(t)
Time-varying measures of the Mt/G/infinity queue.
Definition qsys_mtginf.h:57
std::vector< T > meanNumber
m(t), the Poisson mean
Definition qsys_mtginf.h:59
std::vector< T > times
the evaluation times
Definition qsys_mtginf.h:58
std::vector< T > arrivalRate
lambda(t)
Definition qsys_mtginf.h:61
Steady-state measures of the G/GI/n+GI truncated Gaussian approximation.
T meanNumber
E[X] = E[B] + E[Q].
T probAbandon
P(patience < wait).
Trajectory of the Gt/Mt/st+GI fluid queue; every vector is on the time grid.
std::vector< T > utilization
B/s.
std::vector< T > arrivalRate
lambda on the grid
std::vector< T > B
fluid in service
std::vector< T > times
the time grid
Options of qsys_gtmtst_fluid, all with the MATLAB defaults.
std::function< T(const T &)> pdf
patience density; differenced when empty