5#ifndef LINE_SOLVERS_MAM_SOLVER_MAM_TRANSIENT_QBD_H
6#define LINE_SOLVERS_MAM_SOLVER_MAM_TRANSIENT_QBD_H
61namespace transient_qbd_detail {
65inline CMat to_complex(
const Matrix<T>& A) {
66 CMat C(A.rows(), A.cols());
67 for (std::size_t i = 0; i < A.rows(); ++i)
68 for (std::size_t j = 0; j < A.cols(); ++j)
69 C(i, j) =
Complex(num_traits<T>::to_double(A(i, j)), 0.0);
75 CMat C(A.rows() * B.rows(), A.cols() * B.cols(),
Complex(0.0, 0.0));
76 for (std::size_t i = 0; i < A.rows(); ++i)
77 for (std::size_t j = 0; j < A.cols(); ++j) {
78 if (A(i, j) ==
Complex(0.0, 0.0))
continue;
79 for (std::size_t k = 0; k < B.rows(); ++k)
80 for (std::size_t l = 0; l < B.cols(); ++l)
81 C(i * B.rows() + k, j * B.cols() + l) = A(i, j) * B(k, l);
90 return transient_detail::madd(left, right);
94inline Complex quad(
const std::vector<Complex>& pi0,
const CMat& V,
const std::vector<Complex>& w) {
95 if (pi0.size() != V.rows() || V.cols() != w.size())
96 throw InputError(
"solver_mam_transient_qbd: quadratic form dimension mismatch");
98 for (std::size_t i = 0; i < V.rows(); ++i) {
99 if (pi0[i] ==
Complex(0.0, 0.0))
continue;
101 for (std::size_t j = 0; j < V.cols(); ++j) row += V(i, j) * w[j];
120 "solver_mam_transient_qbd: the level-to-level transform is evaluated at complex "
121 "Laplace nodes and inverted with transcendental CME weights; rerun with --arith "
122 "double or --arith real");
124 using namespace transient_qbd_detail;
129 "solver_mam_transient_qbd: the transient QBD method requires a single-class model");
130 if (!std::isinf(L.
classes[0].population))
132 "solver_mam_transient_qbd: the transient QBD method requires an open model");
134 std::size_t src = 0, q = 0, nsrc = 0, nq = 0;
135 for (std::size_t i = 1; i <= M; ++i) {
136 if (L.
stations[i - 1].sched == SchedStrategy::EXT) { src = i; ++nsrc; }
137 else if (L.
stations[i - 1].sched == SchedStrategy::FCFS) { q = i; ++nq; }
139 if (nsrc != 1 || nq != 1)
141 "solver_mam_transient_qbd: the transient QBD method requires exactly one Source "
142 "and one FCFS Queue");
143 if (L.
stations[q - 1].nservers != 1.0)
145 "solver_mam_transient_qbd: the Laplace transient QBD supports single-server "
151 const CMat Da0 = to_complex(arr.
D0), Da1 = to_complex(arr.
D1);
152 const CMat Ds0 = to_complex(svc.
D0), Ds1 = to_complex(svc.
D1);
153 const std::size_t na = Da0.
rows(), ns = Ds0.
rows();
157 const CMat Lrep = ckrons(Da0, Ds0);
158 const CMat Frep = ckron(Da1, Ins);
159 const CMat Brep = ckron(Ina, Ds1);
161 const CMat Lv0 = ckron(Da0, Ins);
162 const CMat F0 = ckron(Da1, Ins);
163 const CMat B0 = ckron(Ina, Ds1);
166 const std::vector<T> piArr =
map_prob(arr);
167 const std::vector<T> piSvc =
map_prob(svc);
168 std::vector<Complex> pi0(na * ns);
169 for (std::size_t i = 0; i < na; ++i)
170 for (std::size_t j = 0; j < ns; ++j)
176 std::vector<Complex> wDep(na * ns,
Complex(0.0, 0.0));
177 for (std::size_t i = 0; i < na; ++i)
178 for (std::size_t j = 0; j < ns; ++j) {
180 for (std::size_t l = 0; l < ns; ++l) s += Ds1(j, l);
181 wDep[i * ns + j] = s;
183 const std::vector<Complex> wOne(na * ns,
Complex(1.0, 0.0));
185 const double bufCap = L.
cap[q - 1];
186 const bool isFinite = std::isfinite(bufCap);
189 const double T_start =
opt.timespan_start;
190 const double T_end =
opt.timespan_end;
191 if (!(T_end > T_start) || !std::isfinite(T_end))
193 "solver_mam_transient_qbd: the timespan must be a finite interval with a positive "
195 const double dur = T_end - T_start;
196 const std::size_t nTimePoints =
static_cast<std::size_t
>(std::min(
197 101.0, std::max(11.0,
static_cast<double>(std::llround(dur * 10.0)))));
198 std::vector<double> times(nTimePoints);
199 for (std::size_t i = 0; i < nTimePoints; ++i)
200 times[i] = nTimePoints == 1 ? T_start
201 : T_start + dur *
static_cast<double>(i) /
202 static_cast<double>(nTimePoints - 1);
206 std::vector<double> tpos;
207 std::vector<std::size_t> posIdx;
208 for (std::size_t i = 0; i < times.size(); ++i)
209 if (times[i] > 0.0) { tpos.push_back(times[i]); posIdx.push_back(i); }
211 std::size_t maxFnEvals = 100;
212 if (
opt.iter_max > 1)
213 maxFnEvals =
static_cast<std::size_t
>(
214 std::min(1000L, std::max(11L,
static_cast<long>(std::llround(
215 static_cast<double>(
opt.iter_max))))));
217 std::vector<double> EN(times.size(), 0.0), DEP(times.size(), 0.0), Uval(times.size(), 0.0);
221 const long Ncap =
static_cast<long>(std::llround(bufCap));
224 "solver_mam_transient_qbd: a finite buffer must hold at least one job");
227 const CMat LvTop = transient_detail::madd(
228 ckron(transient_detail::madd(Da0, Da1), Ins), ckron(Ina, Ds0));
235 for (
long m = 1; m <= Ncap; ++m)
236 acc +=
static_cast<double>(m) *
247 for (
long m = 1; m <= Ncap; ++m)
252 for (std::size_t i = 0; i < posIdx.size(); ++i) {
253 EN[posIdx[i]] = en[i];
254 DEP[posIdx[i]] = dep[i];
255 Uval[posIdx[i]] = 1.0 - p0[i];
262 {Lv0, Lrep}, {0, 1});
263 const std::size_t nrep = Lrep.
rows();
266 const auto geom = [&](
const Complex& s,
unsigned power,
267 const std::vector<Complex>& w) {
268 const CMat Lk = transient_detail::msub(Lrep, transient_detail::sI(s, nrep));
272 for (std::size_t i = 0; i < nrep; ++i) rhs(i, 0) = w[i];
273 for (
unsigned p = 0; p < power; ++p) rhs = transient_detail::mldivide(ImR, rhs);
275 std::vector<Complex> col(nrep);
276 for (std::size_t i = 0; i < nrep; ++i) col[i] = rhs(i, 0);
277 return quad(pi0, V1, col);
280 const std::vector<double> en =
281 matlab_ilt([&](
const Complex& s) {
return geom(s, 2, wOne); }, tpos, maxFnEvals);
285 const std::vector<double> dep =
286 matlab_ilt([&](
const Complex& s) {
return geom(s, 1, wDep); }, tpos, maxFnEvals);
287 for (std::size_t i = 0; i < posIdx.size(); ++i) {
288 EN[posIdx[i]] = en[i];
289 DEP[posIdx[i]] = dep[i];
290 Uval[posIdx[i]] = 1.0 - p0[i];
298 std::vector<T> qv(times.size()), uv(times.size()), tv(times.size());
299 for (std::size_t i = 0; i < times.size(); ++i) {
304 out.
Qt[q - 1][0].values = qv;
305 out.
Qt[q - 1][0].times = times;
306 out.
Ut[q - 1][0].values = uv;
307 out.
Ut[q - 1][0].times = times;
308 out.
Tt[q - 1][0].values = tv;
309 out.
Tt[q - 1][0].times = times;
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
std::vector< double > cap
sn.cap and sn.classcap: the total and per-class buffers.
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
std::complex<double> as a number type for the generic linear algebra.
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
Laplace-domain transient level-to-level transform V(s,n,m) of a piecewise level-dependent QBD,...
The option and result types SolverMAM shares with its analyzers.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Numerical inverse Laplace transform in the Abate-Whitt framework, the port of matlab/lib/thirdparty/i...
Dense matrix and non-owning view.
mam::Map< T > dist_to_map(const Distrib< T > &d)
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
CMat mam_transient2_open(const TransientQbd &q, long n, long m, const Complex &s)
V(s,n,m) for an OPEN piecewise QBD, the port of mam_transient2_open.m.
CMat mam_transient2(const TransientQbd &q, long n, long m, const Complex &s)
V(s,n,m) for a FINITE piecewise QBD, the port of mam_transient2.m.
TranResult< T > solver_mam_transient_qbd(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of solver_mam_transient_qbd.m.
std::vector< T > map_prob(const Map< T > &m)
Stationary distribution of the phase process, pi (D0 + D1) = 0.
CQbdFundMat qbd_fundmat_laplace(const CMat &B, const CMat &L, const CMat &F, double precision=1e-14, unsigned maxNumIt=50)
Port of qbd_fundmat.m at complex argument: cyclic reduction (Bini-Meini logarithmic reduction) on the...
TransientQbd make_transient_qbd(const std::vector< CMat > &B, const std::vector< CMat > &L, const std::vector< CMat > &F, const std::vector< CMat > &Lv, const std::vector< long > &T)
Build the 1-based padded form from plain 0-based vectors.
std::vector< double > matlab_ilt(const std::function< std::complex< double >(const std::complex< double > &)> &fun, const std::vector< double > ×, std::size_t maxFnEvals, IltMethod method=IltMethod::Cme)
Invert a Laplace transform at the requested time points.
std::complex< double > Complex
Matrix< T > eye(std::size_t n)
Identity of order n.
A queueing network and its refreshed NetworkStruct.
Port of solver_mam_ldqbd_transient.m: transient queue length, utilization and throughput of a single-...
G and R of a QBD whose local block is complex.
The options SolverMAM reads.
A MAP as the pair of matrices (D0, D1).
One station-class transient curve, the reference's [metric, time] pair.
What getTranAvg returns: queue length, utilization and throughput curves.
std::vector< std::vector< TranCurve< T > > > Qt
Indexed [station][class]; only the queue station is populated.
std::vector< std::vector< TranCurve< T > > > Ut
std::vector< std::vector< TranCurve< T > > > Tt
The piecewise QBD blocks, 1-based exactly as the reference's cell arrays.