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]));
241 const std::string& pm =
opt.method;
251 out.
P.assign(M, zero);
252 out.
logP.assign(M, zero);
253 for (std::size_t i = 0; i < M; ++i) {
256 if (v < 0) ignore =
true;
257 if (ignore)
continue;
258 const std::vector<int>
nc = detail::to_chain(
sn, nir[i]);
259 std::vector<int> Nrest(
sn.nchains, 0);
260 for (std::size_t c = 0; c <
sn.nchains; ++c) Nrest[c] = Nchain[c] -
nc[c];
261 const double lG_minus_i =
263 detail::drop_row(mu, i), pm, atol, nopt)
267 for (std::size_t r = 0; r < K; ++r) Fi(0, r) = T(ST(i, r) * V(i, r));
269 pfqn::pfqn_ncld(Fi, nir[i], Zk, detail::row_of(mu, i), pm, atol, nopt).lG;
270 const double lp = lF_i + lG_minus_i - lG;
302 (void)
sn; (void)
opt; (void)nir; (void)lG;
304 "solver_nc_marg: the state probability is exp(lF_i + lG_{-i} - lG), a difference of "
305 "logarithms of normalizing constants, and needs transcendental arithmetic");
308 const std::size_t M =
sn.nstations, K =
sn.nclasses;
309 detail::check_marginal(
sn, nir,
"solver_nc_marg");
310 const std::size_t Ntot = detail::closed_total(
sn,
"solver_nc_marg");
316 std::vector<int> Nchain(
sn.nchains, 0);
317 for (std::size_t c = 0; c <
sn.nchains; ++c)
318 Nchain[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
327 const std::string& pm =
opt.method;
337 const auto is_exponential = [&](std::size_t i, std::size_t r) {
338 if (
sn.disabled[i][r])
return true;
339 return sn.service[i][r].D0.rows() <= 1;
342 out.
P.assign(M, zero);
343 out.
logP.assign(M, zero);
344 for (std::size_t i = 0; i < M; ++i) {
347 if (v < 0) ignore =
true;
348 if (ignore)
continue;
349 const std::vector<int>
nc = detail::to_chain(
sn, nir[i]);
350 std::vector<int> Nrest(
sn.nchains, 0);
351 for (std::size_t c = 0; c <
sn.nchains; ++c) Nrest[c] = Nchain[c] -
nc[c];
352 const double lG_minus_i =
354 detail::drop_row(mu, i), pm, atol, nopt)
359 for (
int v : nir[i]) ntot_i += v;
362 for (
long n = 1; n <= ntot_i && n <= static_cast<long>(mu.
cols()); ++n)
367 if (sc == qn::SchedStrategy::FCFS) {
369 for (std::size_t r = 0; r < K; ++r) {
370 if (
sn.disabled[i][r])
continue;
371 if (!is_exponential(i, r))
373 "solver_nc_marg: the product-form state probability requires "
374 "exponential service times at FCFS nodes, and this station's class " +
375 std::to_string(r + 1) +
" is not exponential");
378 for (std::size_t r = 0; r < K; ++r) {
379 if (
sn.disabled[i][r] || nir[i][r] == 0)
continue;
383 "solver_nc_marg: the product-form state probability requires "
384 "identical service times across classes at FCFS nodes, and this "
385 "station's class " + std::to_string(r + 1) +
" differs");
394 for (std::size_t r = 0; r < K; ++r) {
395 if (nir[i][r] == 0)
continue;
399 "solver_nc_marg: class " + std::to_string(r + 1) +
400 " holds jobs at a station it never visits");
401 lF_i +=
static_cast<double>(nir[i][r]) * std::log(v);
405 }
else if (sc == qn::SchedStrategy::SIRO) {
407 "solver_nc_marg: the SIRO branch weighs the state by log(n_ci / sum n), which "
408 "needs the CLASS OF THE JOB IN SERVICE; a per-class marginal does not carry "
409 "it. Use getProbAggr, whose aggregate marginal has no such dependency");
410 }
else if (sc == qn::SchedStrategy::PS || sc == qn::SchedStrategy::INF) {
411 for (std::size_t r = 0; r < K; ++r) {
412 if (
sn.disabled[i][r])
continue;
413 if (!is_exponential(i, r))
415 "solver_nc_marg: a non-exponential service law at a " +
416 std::string(sc == qn::SchedStrategy::PS ?
"PS" :
"delay") +
417 " station makes the balance function depend on the PHASE-LEVEL "
418 "occupancy, which a per-class marginal does not carry");
419 if (nir[i][r] == 0)
continue;
423 "solver_nc_marg: class " + std::to_string(r + 1) +
424 " holds jobs at a station whose demand for it is zero");
425 lF_i +=
static_cast<double>(nir[i][r]) * std::log(w);
426 for (
int q = 2; q <= nir[i][r]; ++q) lF_i -= std::log(
static_cast<double>(q));
428 for (
long q = 2; q <= ntot_i; ++q) lF_i += std::log(static_cast<double>(q));
435 const double lp = lF_i + lG_minus_i - lG;
454 (void)
sn; (void)
opt; (void)nir; (void)lG_out;
456 "solver_nc_joint: the joint state probability is a difference of logarithms of "
457 "normalizing constants and needs transcendental arithmetic");
460 const std::size_t M =
sn.nstations, K =
sn.nclasses;
461 detail::check_marginal(
sn, nir,
"solver_nc_joint");
462 const std::size_t Ntot = detail::closed_total(
sn,
"solver_nc_joint");
467 std::vector<int> Nchain(
sn.nchains, 0);
468 for (std::size_t c = 0; c <
sn.nchains; ++c)
469 Nchain[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
478 const std::string& pm =
opt.method;
485 if (lG_out !=
nullptr) *lG_out = lG;
488 for (std::size_t i = 0; i < M; ++i) {
489 const std::vector<int>
nc = detail::to_chain(
sn, nir[i]);
490 const Matrix<T> mui = detail::row_of(mu, i);
494 for (std::size_t r = 0; r < K; ++r) g0(0, r) = T(ST(i, r) * d.
alpha(i, r));
495 const double lg0_i =
pfqn::pfqn_ncld(g0, nir[i], Zk, mui, pm, atol, nopt).lG;
498 lPr += lF_i + (lg0_i - lG0_i);
517 (void)
sn; (void)
opt; (void)nir; (void)lG_out;
519 "solver_nc_jointaggr: the joint state probability is a difference of logarithms of "
520 "normalizing constants and needs transcendental arithmetic");
523 const std::size_t M =
sn.nstations, K =
sn.nclasses;
524 detail::check_marginal(
sn, nir,
"solver_nc_jointaggr");
525 const std::size_t Ntot = detail::closed_total(
sn,
"solver_nc_jointaggr");
529 std::vector<int> Nchain(
sn.nchains, 0);
530 for (std::size_t c = 0; c <
sn.nchains; ++c)
531 Nchain[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
540 const std::string& pm =
opt.method;
549 if (
opt.method ==
"exact") {
551 ST = detail::service_times(
sn);
557 if (lG_out !=
nullptr) *lG_out = lG;
562 for (std::size_t c = 0; c <
sn.nchains; ++c)
563 for (std::size_t i = 0; i < M; ++i) {
564 const std::size_t sf =
sn.stateful_of_station(i + 1) - 1;
565 for (std::size_t r = 0; r < K; ++r) V(i, r) = T(V(i, r) +
sn.visits[c](sf, r));
569 for (std::size_t i = 0; i < M; ++i) {
570 const std::vector<int>
nc = detail::to_chain(
sn, nir[i]);
573 if (v > 0) any =
true;
576 for (std::size_t r = 0; r < K; ++r) Fi(0, r) = T(ST(i, r) * V(i, r));
577 lPr +=
pfqn::pfqn_ncld(Fi, nir[i], Zk, detail::row_of(mu, i), pm, atol, nopt).lG;
631 const std::vector<int>& nir) {
632 if (ist == 0 || ist >
sn.nstations)
633 throw InputError(
"getProb: station index out of range");
637 std::numeric_limits<double>::quiet_NaN())
644 std::size_t ist,
const std::vector<int>& nir,
double lG) {
645 if (ist == 0 || ist >
sn.nstations)
646 throw InputError(
"getProbAggr: station index out of range");
674 (void)
sn; (void)
opt; (void)ist;
676 "getProbMarg: the queue-length distribution is assembled from exponentiated "
677 "log-constants and needs transcendental arithmetic");
680 if (ist == 0 || ist >
sn.nstations)
681 throw InputError(
"getProbMarg: station index out of range");
682 const std::size_t K =
sn.nclasses;
683 const std::size_t Ntot = detail::closed_total(
sn,
"getProbMarg");
685 std::vector<int> N(K, 0);
686 for (std::size_t r = 0; r < K; ++r)
687 N[r] =
static_cast<int>(std::llround(
sn.classes[r].population));
689 out.
P.assign(Ntot + 1, zero);
691 -std::numeric_limits<double>::infinity()));
693 if (
opt.method ==
"comom") {
697 const std::size_t M =
sn.nstations, C =
sn.nchains;
700 std::vector<std::size_t> queueStations;
701 for (std::size_t i = 0; i < M; ++i) {
702 const double S =
sn.stations[i].nservers;
704 for (std::size_t c = 0; c < C; ++c) Ztot(0, c) += d.
Lchain(i, c);
706 queueStations.push_back(i);
708 for (std::size_t c = 0; c < C; ++c) {
709 Lms(i, c) = T(d.
Lchain(i, c) / cs);
714 const auto pos = std::find(queueStations.begin(), queueStations.end(), ist - 1);
715 if (pos == queueStations.end()) {
720 fallback.
method =
"default";
723 Matrix<T> Lq(queueStations.size(), C, zero);
724 for (std::size_t a = 0; a < queueStations.size(); ++a)
725 for (std::size_t c = 0; c < C; ++c) Lq(a, c) = Lms(queueStations[a], c);
726 std::vector<int> Nchain(C, 0);
727 std::size_t sumNchain = 0;
728 for (std::size_t c = 0; c < C; ++c) {
729 Nchain[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
730 sumNchain +=
static_cast<std::size_t
>(Nchain[c]);
732 std::vector<T> Zv(C, zero);
733 for (std::size_t c = 0; c < C; ++c) Zv[c] = Ztot(0, c);
735 const std::size_t row =
static_cast<std::size_t
>(pos - queueStations.begin());
736 const std::size_t len = std::min(sumNchain + 1, Ntot + 1);
737 for (std::size_t n = 0; n < len && n < Pr.
cols(); ++n) {
738 out.
P[n] = Pr(row, n);
749 std::vector<int> Nchain(
sn.nchains, 0);
750 for (std::size_t c = 0; c <
sn.nchains; ++c)
751 Nchain[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
758 std::vector<int> part(K, 0);
759 for (std::size_t n = 0; n <= Ntot; ++n) {
763 std::function<void(std::size_t,
int)> rec = [&](std::size_t r,
int left) {
765 if (left > N[r])
return;
775 const int hi = std::min(left, N[r]);
776 for (
int v = 0; v <= hi; ++v) {
778 rec(r + 1, left - v);
781 if (K == 0)
continue;
782 rec(0,
static_cast<int>(n));
797 for (std::size_t n = 0; n <= Ntot; ++n) total += num_traits<T>::to_double(out.
P[n]);
798 if (total > 0.0 && std::fabs(total - 1.0) > 1e-10) {
799 const double ltotal = std::log(total);
800 for (std::size_t n = 0; n <= Ntot; ++n) {
837 "solver_nc_jointmarg: getProbSysMarg requires a closed model: the joint law of "
838 "the total queue lengths is not defined when a class has an infinite population");
839 for (std::size_t i = 0; i <
sn.nstations; ++i) {
840 if (!
sn.stations[i].lldscaling.empty())
842 "solver_nc_jointmarg: getProbSysMarg does not support load-dependent stations "
843 "(sn.lldscaling is set at station " + std::to_string(i + 1) +
"): the permanent "
844 "identity supplies exactly one n_i! per queueing station");
845 const double S =
sn.stations[i].nservers;
846 if (std::isfinite(S) && S > 1.0)
848 "solver_nc_jointmarg: getProbSysMarg does not support the multiserver station " +
849 std::to_string(i + 1) +
" (" + std::to_string(
static_cast<int>(S)) +
850 " servers): the permanent identity supplies exactly one n_i! per queueing "
874 const std::vector<int>& nvec,
const std::string& engine =
"exact",
875 double* lG_out =
nullptr) {
876 const std::size_t M =
sn.nstations;
877 if (nvec.size() != M)
878 throw InputError(
"solver_nc_jointmarg: the occupancy vector has " +
879 std::to_string(nvec.size()) +
" entries but the model has " +
880 std::to_string(M) +
" stations");
881 detail::check_jointmarg_supported(
sn);
886 for (std::size_t i = 0; i < Lchain.
rows(); ++i)
887 for (std::size_t c = 0; c < Lchain.
cols(); ++c)
889 std::vector<int> Nchain(
sn.nchains, 0);
890 for (std::size_t c = 0; c <
sn.nchains; ++c)
891 Nchain[c] =
static_cast<int>(std::llround(d.
Nchain[c]));
893 std::vector<std::size_t> infset;
894 for (std::size_t i = 0; i < M; ++i)
895 if (std::isinf(
sn.stations[i].nservers)) infset.push_back(i);
900 std::vector<bool> isinf(M,
false);
901 for (std::size_t k = 0; k < infset.size(); ++k) isinf[infset[k]] =
true;
903 for (std::size_t i = 0; i < M; ++i)
907 for (std::size_t i = 0; i < M; ++i) {
908 if (isinf[i])
continue;
909 for (std::size_t c = 0; c <
sn.nchains; ++c) Lq(a, c) = Lchain(i, c);
913 if (!infset.empty()) {
915 for (std::size_t k = 0; k < infset.size(); ++k)
916 for (std::size_t c = 0; c <
sn.nchains; ++c) Z(0, c) += Lchain(infset[k], c);
922 static_cast<std::uint64_t
>(
opt.seed));
934 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.
NcldResult< T > pfqn_ncld(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const Matrix< T > &mu, const std::string &method_name, const T &atol, const NcOptions &nopt)
Normalizing constant of a LOAD-DEPENDENT closed network: the dispatcher.
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...
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.
Conservation laws of a layered queueing network, enumerated from its structure.
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