5#ifndef LINE_SOLVERS_NC_SOLVER_NC_PROB_H
6#define LINE_SOLVERS_NC_SOLVER_NC_PROB_H
84 const std::size_t M =
sn.nstations;
85 const std::size_t w = std::max<std::size_t>(1, Ntot);
87 for (std::size_t i = 0; i < M; ++i) {
88 const double S =
sn.stations[i].nservers;
89 for (std::size_t n = 1; n <= w; ++n)
91 std::isinf(S) ?
static_cast<double>(n)
92 : std::min<double>(
static_cast<double>(n), S));
100 std::vector<int>
nc(
sn.nchains, 0);
101 for (std::size_t c = 0; c <
sn.nchains; ++c)
102 for (std::size_t k :
sn.inchain[c])
nc[c] += nir[k - 1];
110 for (std::size_t j = 0; j < A.
cols(); ++j) r(0, j) = A(i, j);
119 for (std::size_t k = 0; k < A.
rows(); ++k) {
120 if (k == i)
continue;
121 for (std::size_t j = 0; j < A.
cols(); ++j) r(o, j) = A(k, j);
132 for (std::size_t i = 0; i <
sn.nstations; ++i)
133 for (std::size_t r = 0; r <
sn.nclasses; ++r)
134 if (!
sn.disabled[i][r] &&
sn.rates(i, r) != zero) ST(i, r) = T(one /
sn.rates(i, r));
151 for (std::size_t c = 0; c <
sn.nchains; ++c)
152 for (std::size_t i = 0; i <
sn.nstations; ++i) {
153 const std::size_t sf =
sn.stateful_of_station(i + 1) - 1;
154 for (std::size_t k :
sn.inchain[c]) V(i, k - 1) =
sn.visits[c](sf, k - 1);
166 ": the state probability is defined on a CLOSED network, and "
167 "this model has an open class");
170 return static_cast<std::size_t
>(std::llround(t));
176 if (nir.size() !=
sn.nstations)
177 throw InputError(std::string(who) +
": the marginal state has " +
178 std::to_string(nir.size()) +
" stations, the model has " +
179 std::to_string(
sn.nstations));
180 for (
const std::vector<int>& row : nir)
181 if (row.size() !=
sn.nclasses)
183 ": every station's marginal must give one count per class");
216 (void)
sn; (void)
opt; (void)nir; (void)lG;
218 "solver_nc_margaggr: the state probability is exp(lF_i + lG_{-i} - lG), a difference "
219 "of logarithms of normalizing constants, and needs transcendental arithmetic");
222 const std::size_t M =
sn.nstations, K =
sn.nclasses;
223 detail::check_marginal(
sn, nir,
"solver_nc_margaggr");
224 const std::size_t Ntot = detail::closed_total(
sn,
"solver_nc_margaggr");
230 std::vector<int> Nchain(
sn.nchains, 0);
231 for (std::size_t c = 0; c <
sn.nchains; ++c)
232 Nchain[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
246 out.
P.assign(M, zero);
247 out.
logP.assign(M, zero);
248 for (std::size_t i = 0; i < M; ++i) {
251 if (v < 0) ignore =
true;
252 if (ignore)
continue;
253 const std::vector<int>
nc = detail::to_chain(
sn, nir[i]);
254 std::vector<int> Nrest(
sn.nchains, 0);
255 for (std::size_t c = 0; c <
sn.nchains; ++c) Nrest[c] = Nchain[c] -
nc[c];
256 const double lG_minus_i =
258 detail::drop_row(mu, i), pm, atol, nopt)
262 for (std::size_t r = 0; r < K; ++r) Fi(0, r) = T(ST(i, r) * V(i, r));
264 pfqn::pfqn_ncld(Fi, nir[i], Zk, detail::row_of(mu, i), pm, atol, nopt).lG;
265 const double lp = lF_i + lG_minus_i - lG;
297 (void)
sn; (void)
opt; (void)nir; (void)lG;
299 "solver_nc_marg: the state probability is exp(lF_i + lG_{-i} - lG), a difference of "
300 "logarithms of normalizing constants, and needs transcendental arithmetic");
303 const std::size_t M =
sn.nstations, K =
sn.nclasses;
304 detail::check_marginal(
sn, nir,
"solver_nc_marg");
305 const std::size_t Ntot = detail::closed_total(
sn,
"solver_nc_marg");
311 std::vector<int> Nchain(
sn.nchains, 0);
312 for (std::size_t c = 0; c <
sn.nchains; ++c)
313 Nchain[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
327 const auto is_exponential = [&](std::size_t i, std::size_t r) {
328 if (
sn.disabled[i][r])
return true;
329 return sn.service[i][r].D0.rows() <= 1;
332 out.
P.assign(M, zero);
333 out.
logP.assign(M, zero);
334 for (std::size_t i = 0; i < M; ++i) {
337 if (v < 0) ignore =
true;
338 if (ignore)
continue;
339 const std::vector<int>
nc = detail::to_chain(
sn, nir[i]);
340 std::vector<int> Nrest(
sn.nchains, 0);
341 for (std::size_t c = 0; c <
sn.nchains; ++c) Nrest[c] = Nchain[c] -
nc[c];
342 const double lG_minus_i =
344 detail::drop_row(mu, i), pm, atol, nopt)
349 for (
int v : nir[i]) ntot_i += v;
352 for (
long n = 1; n <= ntot_i && n <= static_cast<long>(mu.
cols()); ++n)
357 if (sc == qn::SchedStrategy::FCFS) {
359 for (std::size_t r = 0; r < K; ++r) {
360 if (
sn.disabled[i][r])
continue;
361 if (!is_exponential(i, r))
363 "solver_nc_marg: the product-form state probability requires "
364 "exponential service times at FCFS nodes, and this station's class " +
365 std::to_string(r + 1) +
" is not exponential");
368 for (std::size_t r = 0; r < K; ++r) {
369 if (
sn.disabled[i][r] || nir[i][r] == 0)
continue;
373 "solver_nc_marg: the product-form state probability requires "
374 "identical service times across classes at FCFS nodes, and this "
375 "station's class " + std::to_string(r + 1) +
" differs");
384 for (std::size_t r = 0; r < K; ++r) {
385 if (nir[i][r] == 0)
continue;
389 "solver_nc_marg: class " + std::to_string(r + 1) +
390 " holds jobs at a station it never visits");
391 lF_i +=
static_cast<double>(nir[i][r]) * std::log(v);
395 }
else if (sc == qn::SchedStrategy::SIRO) {
397 "solver_nc_marg: the SIRO branch weighs the state by log(n_ci / sum n), which "
398 "needs the CLASS OF THE JOB IN SERVICE; a per-class marginal does not carry "
399 "it. Use getProbAggr, whose aggregate marginal has no such dependency");
400 }
else if (sc == qn::SchedStrategy::PS || sc == qn::SchedStrategy::INF) {
401 for (std::size_t r = 0; r < K; ++r) {
402 if (
sn.disabled[i][r])
continue;
403 if (!is_exponential(i, r))
405 "solver_nc_marg: a non-exponential service law at a " +
406 std::string(sc == qn::SchedStrategy::PS ?
"PS" :
"delay") +
407 " station makes the balance function depend on the PHASE-LEVEL "
408 "occupancy, which a per-class marginal does not carry");
409 if (nir[i][r] == 0)
continue;
413 "solver_nc_marg: class " + std::to_string(r + 1) +
414 " holds jobs at a station whose demand for it is zero");
415 lF_i +=
static_cast<double>(nir[i][r]) * std::log(w);
416 for (
int q = 2; q <= nir[i][r]; ++q) lF_i -= std::log(
static_cast<double>(q));
418 for (
long q = 2; q <= ntot_i; ++q) lF_i += std::log(static_cast<double>(q));
425 const double lp = lF_i + lG_minus_i - lG;
444 (void)
sn; (void)
opt; (void)nir; (void)lG_out;
446 "solver_nc_joint: the joint state probability is a difference of logarithms of "
447 "normalizing constants and needs transcendental arithmetic");
450 const std::size_t M =
sn.nstations, K =
sn.nclasses;
451 detail::check_marginal(
sn, nir,
"solver_nc_joint");
452 const std::size_t Ntot = detail::closed_total(
sn,
"solver_nc_joint");
457 std::vector<int> Nchain(
sn.nchains, 0);
458 for (std::size_t c = 0; c <
sn.nchains; ++c)
459 Nchain[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
470 if (lG_out !=
nullptr) *lG_out = lG;
473 for (std::size_t i = 0; i < M; ++i) {
474 const std::vector<int>
nc = detail::to_chain(
sn, nir[i]);
475 const Matrix<T> mui = detail::row_of(mu, i);
479 for (std::size_t r = 0; r < K; ++r) g0(0, r) = T(ST(i, r) * d.
alpha(i, r));
480 const double lg0_i =
pfqn::pfqn_ncld(g0, nir[i], Zk, mui, pm, atol, nopt).lG;
483 lPr += lF_i + (lg0_i - lG0_i);
502 (void)
sn; (void)
opt; (void)nir; (void)lG_out;
504 "solver_nc_jointaggr: the joint state probability is a difference of logarithms of "
505 "normalizing constants and needs transcendental arithmetic");
508 const std::size_t M =
sn.nstations, K =
sn.nclasses;
509 detail::check_marginal(
sn, nir,
"solver_nc_jointaggr");
510 const std::size_t Ntot = detail::closed_total(
sn,
"solver_nc_jointaggr");
514 std::vector<int> Nchain(
sn.nchains, 0);
515 for (std::size_t c = 0; c <
sn.nchains; ++c)
516 Nchain[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
529 if (
opt.method ==
"exact") {
531 ST = detail::service_times(
sn);
537 if (lG_out !=
nullptr) *lG_out = lG;
542 for (std::size_t c = 0; c <
sn.nchains; ++c)
543 for (std::size_t i = 0; i < M; ++i) {
544 const std::size_t sf =
sn.stateful_of_station(i + 1) - 1;
545 for (std::size_t r = 0; r < K; ++r) V(i, r) = T(V(i, r) +
sn.visits[c](sf, r));
549 for (std::size_t i = 0; i < M; ++i) {
550 const std::vector<int>
nc = detail::to_chain(
sn, nir[i]);
553 if (v > 0) any =
true;
556 for (std::size_t r = 0; r < K; ++r) Fi(0, r) = T(ST(i, r) * V(i, r));
557 lPr +=
pfqn::pfqn_ncld(Fi, nir[i], Zk, detail::row_of(mu, i), pm, atol, nopt).lG;
611 const std::vector<int>& nir) {
612 if (ist == 0 || ist >
sn.nstations)
613 throw InputError(
"getProb: station index out of range");
617 std::numeric_limits<double>::quiet_NaN())
624 std::size_t ist,
const std::vector<int>& nir,
double lG) {
625 if (ist == 0 || ist >
sn.nstations)
626 throw InputError(
"getProbAggr: station index out of range");
654 (void)
sn; (void)
opt; (void)ist;
656 "getProbMarg: the queue-length distribution is assembled from exponentiated "
657 "log-constants and needs transcendental arithmetic");
660 if (ist == 0 || ist >
sn.nstations)
661 throw InputError(
"getProbMarg: station index out of range");
662 const std::size_t K =
sn.nclasses;
663 const std::size_t Ntot = detail::closed_total(
sn,
"getProbMarg");
665 std::vector<int> N(K, 0);
666 for (std::size_t r = 0; r < K; ++r)
667 N[r] =
static_cast<int>(std::llround(
sn.classes[r].population));
669 out.
P.assign(Ntot + 1, zero);
671 -std::numeric_limits<double>::infinity()));
673 if (
opt.method ==
"comom") {
677 const std::size_t M =
sn.nstations, C =
sn.nchains;
680 std::vector<std::size_t> queueStations;
681 for (std::size_t i = 0; i < M; ++i) {
682 const double S =
sn.stations[i].nservers;
684 for (std::size_t c = 0; c < C; ++c) Ztot(0, c) += d.
Lchain(i, c);
686 queueStations.push_back(i);
688 for (std::size_t c = 0; c < C; ++c) {
689 Lms(i, c) = T(d.
Lchain(i, c) / cs);
694 const auto pos = std::find(queueStations.begin(), queueStations.end(), ist - 1);
695 if (pos == queueStations.end()) {
700 fallback.
method =
"default";
703 Matrix<T> Lq(queueStations.size(), C, zero);
704 for (std::size_t a = 0; a < queueStations.size(); ++a)
705 for (std::size_t c = 0; c < C; ++c) Lq(a, c) = Lms(queueStations[a], c);
706 std::vector<int> Nchain(C, 0);
707 std::size_t sumNchain = 0;
708 for (std::size_t c = 0; c < C; ++c) {
709 Nchain[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
710 sumNchain +=
static_cast<std::size_t
>(Nchain[c]);
712 std::vector<T> Zv(C, zero);
713 for (std::size_t c = 0; c < C; ++c) Zv[c] = Ztot(0, c);
715 const std::size_t row =
static_cast<std::size_t
>(pos - queueStations.begin());
716 const std::size_t len = std::min(sumNchain + 1, Ntot + 1);
717 for (std::size_t n = 0; n < len && n < Pr.
cols(); ++n) {
718 out.
P[n] = Pr(row, n);
729 std::vector<int> Nchain(
sn.nchains, 0);
730 for (std::size_t c = 0; c <
sn.nchains; ++c)
731 Nchain[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
734 detail::ncld_pfqn_method(
opt.method),
738 std::vector<int> part(K, 0);
739 for (std::size_t n = 0; n <= Ntot; ++n) {
743 std::function<void(std::size_t,
int)> rec = [&](std::size_t r,
int left) {
745 if (left > N[r])
return;
755 const int hi = std::min(left, N[r]);
756 for (
int v = 0; v <= hi; ++v) {
758 rec(r + 1, left - v);
761 if (K == 0)
continue;
762 rec(0,
static_cast<int>(n));
777 for (std::size_t n = 0; n <= Ntot; ++n) total += num_traits<T>::to_double(out.
P[n]);
778 if (total > 0.0 && std::fabs(total - 1.0) > 1e-10) {
779 const double ltotal = std::log(total);
780 for (std::size_t n = 0; n <= Ntot; ++n) {
817 "solver_nc_jointmarg: getProbSysMarg requires a closed model: the joint law of "
818 "the total queue lengths is not defined when a class has an infinite population");
819 for (std::size_t i = 0; i <
sn.nstations; ++i) {
820 if (!
sn.stations[i].lldscaling.empty())
822 "solver_nc_jointmarg: getProbSysMarg does not support load-dependent stations "
823 "(sn.lldscaling is set at station " + std::to_string(i + 1) +
"): the permanent "
824 "identity supplies exactly one n_i! per queueing station");
825 const double S =
sn.stations[i].nservers;
826 if (std::isfinite(S) && S > 1.0)
828 "solver_nc_jointmarg: getProbSysMarg does not support the multiserver station " +
829 std::to_string(i + 1) +
" (" + std::to_string(
static_cast<int>(S)) +
830 " servers): the permanent identity supplies exactly one n_i! per queueing "
854 const std::vector<int>& nvec,
const std::string& engine =
"exact",
855 double* lG_out =
nullptr) {
856 const std::size_t M =
sn.nstations;
857 if (nvec.size() != M)
858 throw InputError(
"solver_nc_jointmarg: the occupancy vector has " +
859 std::to_string(nvec.size()) +
" entries but the model has " +
860 std::to_string(M) +
" stations");
861 detail::check_jointmarg_supported(
sn);
866 for (std::size_t i = 0; i < Lchain.
rows(); ++i)
867 for (std::size_t c = 0; c < Lchain.
cols(); ++c)
869 std::vector<int> Nchain(
sn.nchains, 0);
870 for (std::size_t c = 0; c <
sn.nchains; ++c)
871 Nchain[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
873 std::vector<std::size_t> infset;
874 for (std::size_t i = 0; i < M; ++i)
875 if (std::isinf(
sn.stations[i].nservers)) infset.push_back(i);
880 std::vector<bool> isinf(M,
false);
881 for (std::size_t k = 0; k < infset.size(); ++k) isinf[infset[k]] =
true;
883 for (std::size_t i = 0; i < M; ++i)
887 for (std::size_t i = 0; i < M; ++i) {
888 if (isinf[i])
continue;
889 for (std::size_t c = 0; c <
sn.nchains; ++c) Lq(a, c) = Lchain(i, c);
893 if (!infset.empty()) {
895 for (std::size_t k = 0; k < infset.size(); ++k)
896 for (std::size_t c = 0; c <
sn.nchains; ++c) Z(0, c) += Lchain(infset[k], c);
902 static_cast<std::uint64_t
>(
opt.seed));
914 const std::vector<int>& nvec,
const std::string& engine =
"exact") {
NumericError(const std::string &what)
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
Dense matrix and non-owning view.
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
ChainDemands< T > sn_get_demands_chain(const qn::NetworkStruct< T > &L)
Port of sn_get_demands_chain.
T solver_nc_jointmarg(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const std::vector< int > &nvec, const std::string &engine="exact", double *lG_out=nullptr)
Joint probability that station i holds nvec[i] jobs IN TOTAL, all classes summed out.
T solver_nc_jointaggr(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir, double *lG_out)
Port of solver_nc_jointaggr.m: the aggregate joint.
T solver_nc_getprob_sys_marg(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const std::vector< int > &nvec, const std::string &engine="exact")
Port of @@SolverNC/getProbSysMarg.m.
T solver_nc_getprob_sys_aggr(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir)
Port of @@SolverNC/getProbSysAggr.m.
T solver_nc_joint(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir, double *lG_out)
Port of solver_nc_joint.m: the probability of the WHOLE system state.
NcSolution< T > solver_nc(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt)
Port of solver_nc.m.
T solver_nc_getprob_sys(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir)
Port of @@SolverNC/getProbSys.m.
NcQueueLengthDist< T > solver_nc_getprob_marg(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, std::size_t ist)
Port of @@SolverNC/getProbMarg.m: the TOTAL queue-length distribution.
NcMargResult< T > solver_nc_margaggr(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir, double lG)
Port of solver_nc_margaggr.m.
NcMargResult< T > solver_nc_marg(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir, double lG)
Port of solver_nc_marg.m: the DETAILED marginal, which weighs the station's internal arrangement and ...
std::vector< std::vector< int > > MarginalState
A state, as this port expresses it: nir[i][r] jobs of class r at station i.
T solver_nc_getprob(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, std::size_t ist, const std::vector< int > &nir)
Port of @@SolverNC/getProb.m: the DETAILED state probability at one station.
T solver_nc_jointaggr_ld(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, const MarginalState &nir, double *lG_out)
Port of solver_nc_jointaggr_ld.m: the load-dependent joint.
T solver_nc_getprob_aggr(const qn::NetworkStruct< T > &sn, const NcSolverOptions &opt, std::size_t ist, const std::vector< int > &nir, double lG)
Port of @@SolverNC/getProbAggr.m: the AGGREGATE probability at one station.
ProcomomResult< T > pfqn_procomom(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const T &atol)
Marginal queue-length distributions of every station.
NcldMethod
The load-dependent methods this port dispatches.
NcResult< T > pfqn_ca(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z)
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
NcldResult< T > pfqn_ncld(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const Matrix< T > &mu, NcldMethod method, const T &atol, const NcOptions &nopt)
Normalizing constant of a LOAD-DEPENDENT closed network: the dispatcher.
T pfqn_jointmarg(const std::vector< int > &n, const Matrix< T > &L, const std::vector< int > &N, const std::vector< std::size_t > &infset, const T &G, const std::string &engine="exact", std::uint64_t seed=0)
Joint probability of the per-station TOTAL queue lengths.
Controls and result shape shared by the normalizing-constant analyzers.
A queueing network and its refreshed NetworkStruct.
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
Joint probability of the per-station TOTAL queue lengths.
Normalizing constant of a LOAD-DEPENDENT closed network: the dispatcher.
ProCoMoM: marginal queue-length probabilities of a closed multiclass product-form network by the clas...
Chain aggregation and de-aggregation.
Port of solver_nc.m: the load-INDEPENDENT normalizing-constant analyzer.
Port of solver_ncld.m: the LOAD-DEPENDENT normalizing-constant analyzer.
The chain-level view of a layer, as sn_get_demands_chain returns it.
std::vector< double > Nchain
(C) population, infinite for an open chain
Matrix< T > alpha
(M x K) class share of its chain's visits at a station
Matrix< T > STchain
(M x C) mean service time
Matrix< T > Lchain
(M x C) demand
static constexpr double FineTol
What the marginal analyzers return: one probability per station.
std::vector< T > P
(M) probability that station i holds its given vector
double lG
the log normalizing constant that normalized them
std::vector< T > logP
(M) the same, in logs
The marginal queue-length distribution and its logarithm.
std::vector< T > P
P[n] = Pr[n jobs at the station], n = 0..sum(N).
The [Q,U,R,T,C,X,lG] of the reference, plus the algorithm that ran.
mva::MvaSolution< T > sol
Matrix< T > STeff
the service times of the last pass, MATLAB's STeff
Controls, defaulting to SolverOptions('NC') in the reference.
The options fields compute_norm_const reads beyond the method itself.
unsigned long seed
SolverOptions('NC').seed.
std::size_t samples
SolverOptions('NC').samples.
double tol
handed to pfqn_comomrm
Return value of the normalizing-constant family, mirroring Ret.pfqnNc.
T G
normalizing constant in the requested arithmetic
double lG
log of the constant, always a double and always finite
One job class of the network.
double population
infinite for an open class