5#ifndef LINE_API_PFQN_PFQN_BUSYP_MULTICLASS_H
6#define LINE_API_PFQN_PFQN_BUSYP_MULTICLASS_H
77inline double busyp_factln(std::size_t k) {
78 return (k < 2) ? 0.0 : std::lgamma(
static_cast<double>(k) + 1.0);
88inline std::vector<double> busyp_station(
const std::vector<double>& Li,
89 const std::vector<double>& phii,
90 const std::vector<std::vector<std::size_t>>& mvec) {
91 const std::size_t size = mvec.size(), R = mvec.empty() ? 0 : mvec[0].size();
92 std::vector<double> out(size, 0.0);
93 for (std::size_t idx = 0; idx < size; ++idx) {
95 for (std::size_t r = 0; r < R; ++r) tot += mvec[idx][r];
96 double v = busyp_factln(tot);
98 for (std::size_t r = 0; r < R && ok; ++r) {
99 if (mvec[idx][r] == 0)
continue;
103 v += -busyp_factln(mvec[idx][r]) +
104 static_cast<double>(mvec[idx][r]) * std::log(Li[r]);
107 out[idx] = -std::numeric_limits<double>::infinity();
110 for (std::size_t k = 1; k <= tot; ++k)
111 v -= std::log(phii[std::min(k, phii.size()) - 1]);
122inline std::vector<double> busyp_lgvec_multi(
123 const std::vector<std::vector<double>>& L,
const std::vector<std::vector<double>>& phi,
124 const std::vector<std::vector<std::size_t>>& mvec,
const std::vector<std::size_t>& stride,
125 const std::vector<std::size_t>& pop) {
126 const double neg_inf = -std::numeric_limits<double>::infinity();
127 const std::size_t nodes = L.size(), R = pop.size(), size = mvec.size();
128 std::size_t totalN = 0;
129 for (std::size_t r = 0; r < R; ++r) totalN += pop[r];
130 std::vector<double> lg(size, neg_inf);
132 for (std::size_t i = 0; i < nodes; ++i) {
134 for (std::size_t k = 0; k < std::min(phi[i].size(), std::max<std::size_t>(1, totalN)); ++k)
135 if (phi[i][k] != 1.0) is_li =
false;
137 std::vector<double> lgnew = lg;
138 for (std::size_t idx = 0; idx < size; ++idx) {
139 double acc = lgnew[idx];
140 for (std::size_t r = 0; r < R; ++r)
141 if (mvec[idx][r] > 0 && L[i][r] > 0) {
142 const double alt = std::log(L[i][r]) + lgnew[idx - stride[r]];
143 acc = busyp_lse(std::vector<double>{acc, alt});
149 const std::vector<double> lX = busyp_station(L[i], phi[i], mvec);
150 std::vector<double> lgnew(size, neg_inf);
151 for (std::size_t a = 0; a < size; ++a) {
152 if (lg[a] == neg_inf)
continue;
153 for (std::size_t c = 0; c < size; ++c) {
154 if (lX[c] == neg_inf)
continue;
157 for (std::size_t r = 0; r < R && fits; ++r) {
158 const std::size_t s = mvec[a][r] + mvec[c][r];
164 if (fits) lgnew[j] = busyp_lse(std::vector<double>{lgnew[j], lg[a] + lX[c]});
179inline std::vector<double> busyp_lattice(
const std::vector<std::vector<double>>& L,
180 const std::vector<std::vector<double>>& scaling,
181 const std::vector<std::size_t>& target,
182 const std::vector<std::size_t>& compl_nodes,
183 const std::vector<std::size_t>& pop,
184 const std::vector<std::size_t>& n,
185 const std::vector<double>& A,
int jobclass,
186 const std::vector<bool>& open_chain) {
187 const double neg_inf = -std::numeric_limits<double>::infinity();
188 const std::size_t R = pop.size();
189 std::vector<std::size_t> stride(R, 1);
190 for (std::size_t r = 1; r < R; ++r) stride[r] = stride[r - 1] * (pop[r - 1] + 1);
191 std::size_t size = 1;
192 for (std::size_t r = 0; r < R; ++r) size *= pop[r] + 1;
193 std::vector<std::vector<std::size_t>> mvec(size, std::vector<std::size_t>(R, 0));
194 for (std::size_t idx = 0; idx < size; ++idx)
195 for (std::size_t r = 0; r < R; ++r) mvec[idx][r] = (idx / stride[r]) % (pop[r] + 1);
197 std::vector<std::vector<double>> Lsub, Lcompl, Psub, Pcompl;
198 for (std::size_t i = 0; i < target.size(); ++i) {
199 Lsub.push_back(L[target[i]]);
200 Psub.push_back(scaling[target[i]]);
202 for (std::size_t i = 0; i < compl_nodes.size(); ++i) {
203 Lcompl.push_back(L[compl_nodes[i]]);
204 Pcompl.push_back(scaling[compl_nodes[i]]);
206 const std::vector<double> lG = busyp_lgvec_multi(Lsub, Psub, mvec, stride, pop);
207 const std::vector<double> lH = busyp_lgvec_multi(Lcompl, Pcompl, mvec, stride, pop);
211 bool any_open =
false;
212 for (std::size_t r = 0; r < R; ++r) any_open = any_open || open_chain[r];
213 std::vector<double> lHbar = lH;
215 lHbar.assign(size, neg_inf);
216 for (std::size_t idx = 0; idx < size; ++idx) {
218 for (std::size_t r = 0; r < R; ++r)
219 if (!open_chain[r]) j += mvec[idx][r] * stride[r];
220 lHbar[j] = busyp_lse(std::vector<double>{lHbar[j], lH[idx]});
224 std::vector<double> b(n.size(), 0.0);
225 for (std::size_t t = 0; t < n.size(); ++t) {
226 const std::size_t nt = n[t];
227 std::vector<double> num, den;
228 for (std::size_t idx = 0; idx < size; ++idx) {
231 std::size_t level = 0;
233 for (std::size_t r = 0; r < R; ++r) level += mvec[idx][r];
235 level = mvec[idx][
static_cast<std::size_t
>(jobclass)];
238 for (std::size_t r = 0; r < R; ++r)
239 if (!open_chain[r]) j += (pop[r] - mvec[idx][r]) * stride[r];
240 num.push_back(lG[idx] + lHbar[j]);
242 if (level + 1 != nt)
continue;
243 std::vector<double> terms;
244 for (std::size_t r = 0; r < R; ++r) {
245 if (jobclass >= 0 && r !=
static_cast<std::size_t
>(jobclass))
continue;
246 if (A[r] <= 0)
continue;
247 if (!open_chain[r] && pop[r] == mvec[idx][r])
continue;
249 for (std::size_t s = 0; s < R; ++s) {
250 if (open_chain[s])
continue;
252 j += (pop[s] - mvec[idx][s] - (s == r ? 1 : 0)) * stride[s];
254 terms.push_back(std::log(A[r]) + lHbar[j]);
256 if (!terms.empty()) den.push_back(lG[idx] + busyp_lse(terms));
258 b[t] = std::exp(busyp_lse(num) - busyp_lse(den));
282 const std::vector<double>& N,
283 const std::vector<std::size_t>& subnet,
284 const std::vector<std::size_t>& n,
289 const std::size_t J = alpha.
rows(), R = alpha.
cols();
292 "pfqn_busyp_multiclass: the population vector must have one entry per chain");
293 bool is_closed =
true, is_open =
true;
294 for (std::size_t r = 0; r < R; ++r) {
295 if (std::isinf(N[r]))
300 const bool is_mixed = !is_closed && !is_open;
302 std::vector<std::size_t> target = subnet;
303 std::sort(target.begin(), target.end());
304 target.erase(std::unique(target.begin(), target.end()), target.end());
306 throw InputError(
"pfqn_busyp_multiclass: the subnetwork must be non-empty");
307 if (is_closed && target.size() >= J)
309 "pfqn_busyp_multiclass: in a closed network the subnetwork must be a proper "
310 "subset of the nodes");
311 if (target.back() >= J)
312 throw InputError(
"pfqn_busyp_multiclass: the subnetwork indexes are out of range");
313 std::vector<bool> in_subnet(J,
false);
314 for (std::size_t i = 0; i < target.size(); ++i) in_subnet[target[i]] =
true;
315 std::vector<std::size_t> compl_nodes;
316 for (std::size_t j = 0; j < J; ++j)
317 if (!in_subnet[j]) compl_nodes.push_back(j);
319 std::size_t totalN = 0;
320 for (std::size_t r = 0; r < R; ++r)
321 if (!std::isinf(N[r])) totalN +=
static_cast<std::size_t
>(std::llround(N[r]));
323 std::vector<std::vector<double>> scaling(J);
324 for (std::size_t i = 0; i < J; ++i) {
325 if (phi.rows() == J && phi.cols() > 0) {
326 scaling[i].resize(phi.cols());
327 for (std::size_t k = 0; k < phi.cols(); ++k)
330 scaling[i].assign(std::max<std::size_t>(1, totalN), 1.0);
335 std::vector<std::vector<double>> L(J, std::vector<double>(R, 0.0));
336 for (std::size_t i = 0; i < J; ++i)
337 for (std::size_t r = 0; r < R; ++r) {
340 L[i][r] = (a > 0 && m > 0) ? a / m : 0.0;
344 std::vector<double> A(R, 0.0);
346 for (std::size_t r = 0; r < R; ++r) {
347 const Matrix<T>& Pr = (P.size() == 1) ? P[0] : P[r];
348 for (std::size_t i = 0; i < compl_nodes.size(); ++i)
349 for (std::size_t j = 0; j < target.size(); ++j)
352 if (gamma.rows() == J)
353 for (std::size_t j = 0; j < target.size(); ++j)
359 "pfqn_busyp_multiclass: no job ever enters the subnetwork, its busy period is "
361 if (jobclass >=
static_cast<int>(R))
362 throw InputError(
"pfqn_busyp_multiclass: the job class index is out of range");
363 if (jobclass >= 0 && A[
static_cast<std::size_t
>(jobclass)] <= 0)
365 "pfqn_busyp_multiclass: no job of that class ever enters the subnetwork");
367 if (is_open && !is_mixed) {
370 std::vector<double> rho(J, 0.0);
371 for (std::size_t i = 0; i < J; ++i)
372 for (std::size_t r = 0; r < R; ++r) rho[i] += L[i][r];
374 std::vector<double> gsyn(J, 0.0);
376 gsyn[target[0]] = inflow;
381 const std::size_t rr =
static_cast<std::size_t
>(jobclass);
382 for (std::size_t t = 0; t < target.size(); ++t)
383 for (std::size_t k = 0; k < scaling[target[t]].size(); ++k)
384 if (scaling[target[t]][k] != 1.0)
386 "pfqn_busyp_multiclass: a per-class busy period of an open "
387 "subnetwork requires load-independent stations");
388 for (std::size_t i = 0; i < J; ++i) {
389 const double den = 1.0 - rho[i] + L[i][rr];
390 rho[i] = (den > 0) ? L[i][rr] / den : 0.0;
392 gsyn[target[0]] = A[rr];
394 const std::function<double(std::size_t, std::size_t)> rates =
395 [&](std::size_t j, std::size_t k) {
396 return scaling[j][std::min(k, scaling[j].size()) - 1];
398 return pfqn_busyp(rho, rates, zeroP, std::numeric_limits<double>::infinity(), target,
404 const std::size_t bound = (jobclass < 0)
406 :
static_cast<std::size_t
>(std::llround(N[
static_cast<std::size_t
>(jobclass)]));
407 for (std::size_t t = 0; t < n.size(); ++t)
408 if (n[t] < 1 || n[t] > bound)
410 "pfqn_busyp_multiclass: the busy period order must be an integer in "
411 "1..sum(N), or in 1..N(r) for the busy period of class r alone");
412 std::vector<std::size_t> pop(R, 0);
413 for (std::size_t r = 0; r < R; ++r)
414 pop[r] =
static_cast<std::size_t
>(std::llround(N[r]));
415 const std::vector<bool> open_chain(R,
false);
416 return detail::busyp_lattice(L, scaling, target, compl_nodes, pop, n, A, jobclass,
422 std::vector<bool> open_chain(R,
false);
423 for (std::size_t r = 0; r < R; ++r) open_chain[r] = std::isinf(N[r]);
424 std::size_t nmax = 1;
425 for (std::size_t t = 0; t < n.size(); ++t) nmax = std::max(nmax, n[t]);
426 std::size_t trunc = 8 + 2 * nmax;
427 std::vector<double> prev;
429 std::vector<std::size_t> pop(R, 0);
430 for (std::size_t r = 0; r < R; ++r)
431 pop[r] = open_chain[r] ? trunc :
static_cast<std::size_t
>(std::llround(N[r]));
432 const std::vector<double> b = detail::busyp_lattice(L, scaling, target, compl_nodes,
433 pop, n, A, jobclass, open_chain);
436 for (std::size_t t = 0; t < b.size(); ++t)
437 if (std::fabs(b[t] - prev[t]) > 1e-10 * std::fabs(b[t])) settled =
false;
438 if (settled)
return b;
444 "pfqn_busyp_multiclass: the mixed busy period did not converge, so some "
445 "station of the subnetwork is nearly saturated");
NumericError(const std::string &what)
The exception types the port throws.
Dense matrix and non-owning view.
BusyPeriodResult pfqn_busyp(const std::vector< double > &alpha, const RateSource &mu, const Matrix< T > &P, double N, const std::vector< std::size_t > &subnet, const std::vector< std::size_t > &n, const std::vector< double > &gamma={}, double tol=PFQN_BUSYP_DEFAULT_TOL)
Mean busy period of order n for the subnetwork.
constexpr double PFQN_BUSYP_DEFAULT_TOL
Default relative tolerance of the open-network tail truncation.
std::vector< double > pfqn_busyp_multiclass(const Matrix< T > &alpha, const Matrix< T > &mu, const std::vector< Matrix< T > > &P, const std::vector< double > &N, const std::vector< std::size_t > &subnet, const std::vector< std::size_t > &n, const Matrix< T > &gamma=Matrix< T >(), const Matrix< T > &phi=Matrix< T >(), double tol=PFQN_BUSYP_DEFAULT_TOL, int jobclass=-1)
Mean busy period of order n for the subnetwork, multichain.
Number-type abstraction for the templated API port.
Mean busy period of order n for a subnetwork of a product-form network.
std::vector< double > b
mean duration per requested order