5#ifndef LINE_SOLVERS_MAM_SOLVER_MAM_PROB_H
6#define LINE_SOLVERS_MAM_SOLVER_MAM_PROB_H
63namespace prob_detail {
67void require_single_queue(
const qn::NetworkStruct<T>& L,
const char* fn) {
69 for (std::size_t i = 0; i < L.nstations; ++i)
70 if (L.stations[i].nodetype == qn::NodeType::Queue) ++nq;
74 " is not supported for networks with multiple queues in SolverMAM. The MAM solver "
75 "uses QBD (quasi-birth-death) analysis, which is fundamentally a single-queue "
76 "method. Use SolverCTMC or SolverSSA for networks with multiple queues");
79 ": the model does not contain any queue stations");
87std::vector<PhService<T>> station_services(
const qn::NetworkStruct<T>& L, std::size_t ist) {
88 using lang::GlobalConstants;
89 const T one = num_traits<T>::from_int(1);
90 const std::size_t K = L.nclasses;
91 const double ns = L.stations[ist - 1].nservers;
92 std::vector<PhService<T>> svc(K);
93 for (std::size_t r = 0; r < K; ++r) {
94 if (L.disabled[ist - 1][r] || !(L.rates(ist - 1, r) > num_traits<T>::from_int(0))) {
97 svc[r].sigma.assign(1, one);
103 ? T(T(one / L.rates(ist - 1, r)) / num_traits<T>::from_double(ns))
104 : T(one / L.rates(ist - 1, r));
114std::vector<T> level_marginal(
const qn::NetworkStruct<T>& L,
const MamOptions& opt,
115 std::size_t ist,
const mva::AvgResult<T>& avg,
116 std::vector<PhService<T>>& svc, std::vector<T>& classLambda,
118 using lang::GlobalConstants;
119 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
120 const std::size_t K = L.nclasses;
121 svc = station_services(L, ist);
122 classLambda.assign(K, zero);
125 for (
const qn::JobClass& c : L.classes)
126 if (std::isinf(c.population)) closed =
false;
129 std::size_t maxLevel;
132 std::size_t Ntot = 0;
133 for (
const qn::JobClass& c : L.classes)
134 Ntot +=
static_cast<std::size_t
>(std::llround(c.population));
137 for (std::size_t r = 0; r < K; ++r) {
138 classLambda[r] = avg.TN(ist - 1, r);
139 tot += classLambda[r];
142 return std::vector<T>();
145 for (std::size_t r = 0; r < K; ++r)
146 arr.Dc.push_back(Matrix<T>(1, 1, classLambda[r]));
148 const std::size_t src = L.sourceIdx;
149 if (src == 0)
throw InputError(
"getProb: the open model has no Source");
150 maxLevel = opt.cutoff > 0 ? opt.cutoff :
static_cast<std::size_t
>(100);
151 std::vector<Map<T>> arrMaps(K);
153 for (std::size_t r = 0; r < K; ++r) {
154 if (L.disabled[src - 1][r]) {
160 tot += classLambda[r];
163 return std::vector<T>();
165 arr.D0 = arrMaps[0].D0;
166 arr.D1 = arrMaps[0].D1;
167 arr.Dc.assign(1, arrMaps[0].D1);
175 Map<T> super = arrMaps[0];
176 for (std::size_t r = 1; r < K; ++r) {
178 m.D0 =
krons(super.D0, arrMaps[r].D0);
179 m.D1 =
krons(super.D1, arrMaps[r].D1);
184 for (std::size_t r = 0; r < K; ++r) {
185 Matrix<T> Dr = super.D1;
186 const T w = T(classLambda[r] / tot);
187 for (std::size_t i = 0; i < Dr.rows(); ++i)
188 for (std::size_t j = 0; j < Dr.cols(); ++j) Dr(i, j) *= w;
189 arr.Dc.push_back(Dr);
197 std::vector<T> p(d[0].size(), zero);
199 for (std::size_t n = 0; n < d[0].size(); ++n) {
204 for (T& v : p) v /= mass;
231 "getProb: the queue-length distribution comes from the MMAP[K]/PH[K]/1 age process, "
232 "whose first-return matrix is a tolerance-terminated Riccati doubling; rerun with "
233 "--arith double or --arith real");
237 throw InputError(
"getProb: node number exceeds the number of nodes in the model");
238 const std::size_t ist = L.
nodes[node - 1].station;
239 if (ist == 0)
throw InputError(
"getProb: the specified node is not a station");
240 prob_detail::require_single_queue(L,
"getProb");
243 std::vector<PhService<T>> svc;
244 std::vector<T> classLambda;
246 const std::vector<T> p =
247 prob_detail::level_marginal(L,
opt, ist, avg, svc, classLambda, closed);
252 std::size_t levels = 1;
254 std::size_t Ntot = 0;
256 Ntot +=
static_cast<std::size_t
>(std::llround(c.
population));
259 levels =
opt.cutoff > 0 ?
opt.cutoff :
static_cast<std::size_t
>(100);
266 std::size_t nPhases = 1;
267 for (std::size_t r = 0; r < K; ++r) nPhases = std::max(nPhases, svc[r].S.rows());
277 for (std::size_t r = 0; r < K; ++r) tot += classLambda[r];
278 std::vector<T> avgPie(nPhases, zero);
280 for (std::size_t r = 0; r < K; ++r) {
281 const std::size_t m = svc[r].S.rows();
285 for (std::size_t i = 0; i < m; ++i) {
287 for (std::size_t j = 0; j < m; ++j) exit -= svc[r].S(i, j);
288 for (std::size_t j = 0; j < m && j < svc[r].sigma.size(); ++j)
289 ph.
D1(i, j) = T(exit * svc[r].sigma[j]);
291 const std::vector<T> piq =
map_prob(ph);
292 for (std::size_t i = 0; i < piq.size() && i < nPhases; ++i)
293 avgPie[i] += T(piq[i] * classLambda[r] / tot);
296 for (
const T& v : avgPie) s += v;
301 for (T& v : avgPie) v /= s;
305 for (std::size_t n = 0; n < p.size(); ++n)
306 for (std::size_t i = 0; i < nPhases; ++i) out.
P(n, i) = T(p[n] * avgPie[i]);
317 std::size_t ist, std::size_t jobclass,
321 "getProbMarg: the queue-length distribution comes from the MMAP[K]/PH[K]/1 age "
322 "process, whose first-return matrix is a tolerance-terminated Riccati doubling; "
323 "rerun with --arith double or --arith real");
327 throw InputError(
"getProbMarg: station number exceeds the number of stations");
328 if (jobclass == 0 || jobclass > L.
nclasses)
329 throw InputError(
"getProbMarg: job class index exceeds the number of classes");
330 prob_detail::require_single_queue(L,
"getProbMarg");
332 std::vector<PhService<T>> svc;
333 std::vector<T> classLambda;
335 const std::vector<T> p =
336 prob_detail::level_marginal(L,
opt, ist, avg, svc, classLambda, closed);
339 const std::size_t levels =
340 closed ?
static_cast<std::size_t
>(std::llround(L.
classes[jobclass - 1].population)) + 1
341 :
static_cast<std::size_t
>(100);
342 std::vector<T> out(levels, zero);
346 if (!closed)
return p;
348 const double Nr = L.
classes[jobclass - 1].population;
351 "getProbMarg: class " + std::to_string(jobclass) +
352 " has zero population, so it has no marginal queue-length distribution");
353 const std::size_t Nk =
static_cast<std::size_t
>(std::llround(Nr));
354 std::vector<T> out(Nk + 1, zero);
356 for (std::size_t n = 0; n <= Nk && n < p.size(); ++n) {
361 for (T& v : out) v /= mass;
380 "getMAMResult: qsys_bmapm1 runs a functional iteration for G and an adaptive level "
381 "truncation, both tolerance-terminated; rerun with --arith double or --arith real");
383 std::size_t src = 0, q = 0;
384 for (std::size_t i = 1; i <= L.
nstations; ++i) {
386 if (nt == qn::NodeType::Source) src = i;
387 else if (nt == qn::NodeType::Queue) {
390 "getMAMResult exposes the matrix-analytic internals of a single-queue model "
395 if (src == 0 || q == 0)
397 "getMAMResult requires an open model with one Source and one Queue");
400 "getMAMResult exposes the matrix-analytic internals of a single-class model only");
401 if (L.
stations[q - 1].nservers != 1.0)
406 if (svc.
D0.rows() != 1)
408 "getMAMResult exposes the M/G/1-type internals for exponential service only; the "
409 "queue has a multi-phase service process");
410 const T mu = T(-svc.
D0(0, 0));
413 std::vector<Matrix<T>> D;
415 const std::vector<Matrix<T>>& batches = L.
service[src - 1][0].Dmark;
416 if (batches.empty()) {
419 for (
const Matrix<T>& Dk : batches) D.push_back(Dk);
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
std::size_t nof_nodes() const
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< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
std::size_t node_of_station(std::size_t st) const
1-based node index of a station, and the reverse; 0 when absent.
std::vector< NodeDef > nodes
every node, in creation order
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
The option and result types SolverMAM shares with its analyzers.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
The MMAP assembly primitives solver_mam_basic.m builds its per-station arrival stream from: mmap_expo...
The MMAP[K]/PH[K]/1 FCFS queue: per-class mean number in system and per-class queue-length distributi...
mam::Map< T > dist_to_map(const Distrib< T > &d)
NodeType
Node kinds, with the values of MATLAB NodeType.
qsys::BmapM1Result< T > solver_mam_getmamresult(const qn::NetworkStruct< T > &L)
Port of @@SolverMAM/getMAMResult.m: the matrix-analytic internals of a single-queue model,...
Matrix< T > krons(const Matrix< T > &A, const Matrix< T > &B)
Kronecker sum, MATLAB's krons: kron(A, I_nb) + kron(I_na, B).
std::vector< T > map_prob(const Map< T > &m)
Stationary distribution of the phase process, pi (D0 + D1) = 0.
std::vector< std::vector< T > > mmapph1fcfs_ncdistr(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc, std::size_t levels)
Per-class queue-length distribution, BUTools' 'ncDistr', n: P(N_k = 0..n-1).
Map< T > map_scale(const Map< T > &in, const T &new_mean)
Rescale time so that the mean inter-arrival time becomes new_mean.
std::vector< T > map_pie(const Map< T > &m)
Phase distribution seen by an arriving job, pie = pi D1 / (pi D1 e).
Map< T > map_normalize(const Map< T > &in)
Clamp negative off-diagonal entries of D0 and negative entries of D1 to zero, then rebuild the diagon...
ProbTable< T > solver_mam_getprob(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t node, const mva::AvgResult< T > &avg)
Port of @@SolverMAM/getProb.m.
T map_lambda(const Map< T > &m)
Stationary arrival rate, lambda = pi D1 e.
std::vector< T > solver_mam_getprobmarg(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::size_t ist, std::size_t jobclass, const mva::AvgResult< T > &avg)
Port of @@SolverMAM/getProbMarg.m: P(n jobs of class jobclass) at station ist, for n = 0....
BmapM1Result< T > qsys_bmapm1(const std::vector< Matrix< T > > &D, const T &mu, const T &qParam, std::size_t maxLevelParam, unsigned maxIter, const T &tol, double tailTol)
BMAP/M/1 by the matrix-analytic (M/G/1-type) method.
A queueing network and its refreshed NetworkStruct.
BMAP/M/1 by the matrix-analytic (M/G/1-type) method.
The SolverMVA class surface: @@SolverMVA/runAnalyzer.m and the gates around it.
static constexpr double Immediate
Rate of an Immediate distribution; its mean is 1/Immediate = 1e-8.
static constexpr double FineTol
The options SolverMAM reads.
A MAP as the pair of matrices (D0, D1).
The joint (level, phase) table getProb returns: rows levels, cols phases.
The metrics getAvg returns, after filtering.
One job class of the network.
double population
infinite for an open class
Everything qsys_bmapm1 returns, mirroring the MATLAB result struct.