5#ifndef LINE_SOLVERS_AG_SOLVER_AG_H
6#define LINE_SOLVERS_AG_SOLVER_AG_H
117 std::size_t from_station = 0, from_class = 0;
118 std::size_t to_station = 0, to_class = 0;
120 bool is_negative =
false;
121 bool is_catastrophe =
false;
122 std::size_t removal_class = 0;
123 bool has_removal_dist =
false;
129 std::size_t num_processes = 0;
131 std::vector<std::vector<std::size_t>> process_map;
132 std::vector<RcatAction> actions;
133 std::vector<std::size_t> N;
134 std::vector<Matrix<T>> Aa, Pb;
135 std::vector<Matrix<T>> L;
136 std::vector<std::size_t> act, psv;
137 std::vector<bool> is_open_proc;
138 std::vector<std::size_t> nlev;
139 std::vector<std::size_t> mph;
140 std::vector<std::vector<std::size_t>> level;
142 std::vector<std::vector<T>> svcrate;
144 std::vector<std::vector<T>> svcdown;
153struct RcatComponent {
154 std::size_t ist = 0, r = 0;
158 std::size_t na = 1, ns = 1, mph = 1, nlev = 0, N = 0;
159 T lam_neg = num_traits<T>::from_int(0);
160 T lam_cat = num_traits<T>::from_int(0);
161 std::vector<std::pair<T, std::size_t>> batch;
165inline std::size_t blk(std::size_t n, std::size_t mph) {
return n * mph; }
169Matrix<T> madd_local(
const Matrix<T>& A,
const Matrix<T>& B) {
170 Matrix<T> C(A.rows(), A.cols(), num_traits<T>::from_int(0));
171 for (std::size_t i = 0; i < A.rows(); ++i)
172 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = T(A(i, j) + B(i, j));
178void add_block(Matrix<T>& target, std::size_t li, std::size_t lj, std::size_t mph,
179 const Matrix<T>& block,
const T& scale) {
180 const std::size_t r0 = blk(li, mph), c0 = blk(lj, mph);
181 for (std::size_t i = 0; i < mph; ++i)
182 for (std::size_t j = 0; j < mph; ++j) target(r0 + i, c0 + j) += T(scale * block(i, j));
187void add_identity(Matrix<T>& target, std::size_t li, std::size_t lj, std::size_t mph,
189 const std::size_t r0 = blk(li, mph), c0 = blk(lj, mph);
190 for (std::size_t i = 0; i < mph; ++i) target(r0 + i, c0 + i) += scale;
195void set_identity(Matrix<T>& target, std::size_t li, std::size_t lj, std::size_t mph) {
196 const std::size_t r0 = blk(li, mph), c0 = blk(lj, mph);
197 const T one = num_traits<T>::from_int(1);
198 for (std::size_t i = 0; i < mph; ++i) target(r0 + i, c0 + i) = one;
203Matrix<T> level_block(
const Matrix<T>& Q, std::size_t li, std::size_t lj, std::size_t mph) {
204 Matrix<T> out(mph, mph, num_traits<T>::from_int(0));
205 const std::size_t r0 = blk(li, mph), c0 = blk(lj, mph);
206 for (std::size_t i = 0; i < mph; ++i)
207 for (std::size_t j = 0; j < mph; ++j) out(i, j) = Q(r0 + i, c0 + j);
218bool is_markovian_map(
const Matrix<T>& D0,
const Matrix<T>& D1) {
219 const std::size_t n = D0.rows();
220 if (n == 0 || D0.cols() != n || D1.rows() != n || D1.cols() != n)
return false;
222 for (std::size_t i = 0; i < n; ++i)
223 for (std::size_t j = 0; j < n; ++j) {
224 const double a = num_traits<T>::to_double(D0(i, j));
225 const double b = num_traits<T>::to_double(D1(i, j));
226 if (!std::isfinite(a) || !std::isfinite(b))
return false;
227 scale = std::max(scale, std::max(std::fabs(a), std::fabs(b)));
229 const double tol = 1e-9 * scale;
230 for (std::size_t i = 0; i < n; ++i) {
232 for (std::size_t j = 0; j < n; ++j) {
233 const double a = num_traits<T>::to_double(D0(i, j));
234 const double b = num_traits<T>::to_double(D1(i, j));
235 if (i != j && a < -tol)
return false;
236 if (b < -tol)
return false;
239 if (std::fabs(row) > tol)
return false;
252std::pair<Matrix<T>, Matrix<T>> proc_map(
const qn::NetworkStruct<T>& L, std::size_t ist,
254 const T zero = num_traits<T>::from_int(0);
255 if (L.has_service_law(ist, r)) {
258 if (is_markovian_map(m.D0, m.D1))
return std::make_pair(m.D0, m.D1);
259 }
catch (
const std::exception&) {
263 T rate = L.disabled[ist][r] ? zero : L.rates(ist, r);
264 if (!(rate > zero) || !std::isfinite(num_traits<T>::to_double(rate))) rate = zero;
265 Matrix<T> D0(1, 1, zero), D1(1, 1, zero);
266 D0(0, 0) = T(zero - rate);
268 return std::make_pair(D0, D1);
272bool is_tridiagonal(
const Matrix<T>& Q) {
273 const std::size_t n = Q.rows();
274 for (std::size_t i = 0; i < n; ++i)
275 for (std::size_t j = 0; j < n; ++j) {
276 const std::size_t d = (i > j) ? (i - j) : (j - i);
277 if (d > 1 && std::fabs(num_traits<T>::to_double(Q(i, j))) > 1e-14)
return false;
287bool is_block_tridiagonal(
const Matrix<T>& Q,
const std::vector<std::size_t>& lvl) {
288 const std::size_t n = Q.rows();
289 for (std::size_t i = 0; i < n; ++i)
290 for (std::size_t j = 0; j < n; ++j) {
291 const std::size_t d = (lvl[i] > lvl[j]) ? (lvl[i] - lvl[j]) : (lvl[j] - lvl[i]);
292 if (d > 1 && std::fabs(num_traits<T>::to_double(Q(i, j))) > 1e-14)
return false;
306std::vector<T> birth_death_solve(
const Matrix<T>& Q) {
307 const std::size_t n = Q.rows();
308 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
309 if (n <= 1)
return std::vector<T>(1, one);
311 std::vector<T> pi(n, zero);
313 for (std::size_t i = 1; i < n; ++i) {
314 const T birth = Q(i - 1, i);
315 const T death = Q(i, i - 1);
316 pi[i] = (death > zero) ? T(pi[i - 1] * birth / death) : zero;
319 for (
const T& v : pi) total += v;
321 for (T& v : pi) v /= total;
323 const T u = one / num_traits<T>::from_int(
static_cast<long>(n));
324 for (T& v : pi) v = u;
341std::vector<T> stat_vector(
const Matrix<T>& C) {
342 const std::size_t m = C.rows();
343 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
345 Matrix<T> At(m, m, zero);
346 for (std::size_t i = 0; i < m; ++i)
347 for (std::size_t j = 0; j < m; ++j) At(j, i) = (j == 0) ? one : C(i, j);
348 std::vector<T> rhs(m, zero);
350 const std::vector<std::size_t> piv =
lu_factor(At);
366std::vector<T> qbd_finite_solve(
const Matrix<T>& Q, std::size_t m, std::size_t nlev) {
367 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
370 std::vector<Matrix<T>> C(nlev);
371 C[nlev - 1] = level_block(Q, nlev - 1, nlev - 1, m);
372 for (std::size_t t = nlev - 1; t-- > 0;) {
373 const Matrix<T> F = level_block(Q, t, t + 1, m);
374 const Matrix<T> D = level_block(Q, t + 1, t, m);
375 Matrix<T> negC = C[t + 1];
376 for (std::size_t i = 0; i < m; ++i)
377 for (std::size_t j = 0; j < m; ++j) negC(i, j) = T(zero - negC(i, j));
379 Matrix<T> Cn = level_block(Q, t, t, m);
380 const Matrix<T> corr =
matmul(step, D);
381 for (std::size_t i = 0; i < m; ++i)
382 for (std::size_t j = 0; j < m; ++j) Cn(i, j) += corr(i, j);
386 std::vector<T> pi(nlev * m, zero);
387 const std::vector<T> p0 = stat_vector(C[0]);
388 for (std::size_t i = 0; i < m; ++i) pi[i] = p0[i];
389 for (std::size_t n = 0; n + 1 < nlev; ++n) {
390 Matrix<T> negC = C[n + 1];
391 for (std::size_t i = 0; i < m; ++i)
392 for (std::size_t j = 0; j < m; ++j) negC(i, j) = T(zero - negC(i, j));
393 const Matrix<T> step =
matmul(level_block(Q, n, n + 1, m),
inverse(negC));
394 for (std::size_t j = 0; j < m; ++j) {
396 for (std::size_t i = 0; i < m; ++i) acc += T(pi[blk(n, m) + i] * step(i, j));
397 pi[blk(n + 1, m) + j] = acc;
402 for (
const T& v : pi) total += v;
404 for (T& v : pi) v /= total;
406 const T u = one / num_traits<T>::from_int(
static_cast<long>(nlev * m));
407 for (T& v : pi) v = u;
424std::vector<T> solve_component(
const Matrix<T>& Q, std::size_t mph, std::size_t nlev,
425 const std::vector<std::size_t>& lvl) {
427 if (is_tridiagonal(Q))
return birth_death_solve(Q);
428 }
else if (is_block_tridiagonal(Q, lvl)) {
429 return qbd_finite_solve(Q, mph, nlev);
438 std::vector<T> pi0, pi1;
440 T qlen = num_traits<T>::from_int(0);
459QbdTail<T> qbd_matrix_tail(
const Matrix<T>& Qk,
const Matrix<T>& A0,
const Matrix<T>& A1,
460 const Matrix<T>& A2, std::size_t mph) {
462 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
465 R =
qbd_R_logred(A2, A1, A0, 500u, T(num_traits<T>::from_double(1e-14)));
466 }
catch (
const std::exception&) {
469 for (std::size_t i = 0; i < mph; ++i)
470 for (std::size_t j = 0; j < mph; ++j) {
471 const double v = num_traits<T>::to_double(R(i, j));
474 if (!std::isfinite(v) || v < -1e-12)
return out;
477 const Matrix<T> B00 = level_block(Qk, 0, 0, mph);
478 Matrix<T> IR =
eye<T>(mph);
479 for (std::size_t i = 0; i < mph; ++i)
480 for (std::size_t j = 0; j < mph; ++j) IR(i, j) = T(IR(i, j) - R(i, j));
481 std::vector<T> tail_mass;
484 }
catch (
const std::exception&) {
493 for (std::size_t i = 0; i < mph; ++i) {
494 const double w = num_traits<T>::to_double(tail_mass[i]);
495 if (!std::isfinite(w) || w < 1.0 - 1e-9)
return out;
499 const Matrix<T> lower_right = madd_local(A1,
matmul(R, A2));
500 const std::size_t n2 = 2 * mph;
501 Matrix<T> SysT(n2, n2, zero);
502 for (std::size_t i = 0; i < mph; ++i)
503 for (std::size_t j = 0; j < mph; ++j) {
504 SysT(j, i) = B00(i, j);
505 SysT(mph + j, i) = A0(i, j);
506 SysT(j, mph + i) = A2(i, j);
507 SysT(mph + j, mph + i) = lower_right(i, j);
509 for (std::size_t i = 0; i < mph; ++i) {
511 SysT(0, mph + i) = tail_mass[i];
513 std::vector<T> v(n2, zero);
517 const std::vector<std::size_t> piv =
lu_factor(lu);
519 }
catch (
const std::exception&) {
522 for (std::size_t i = 0; i < n2; ++i)
523 if (!std::isfinite(num_traits<T>::to_double(v[i])))
return out;
525 out.pi0.assign(v.begin(), v.begin() +
static_cast<long>(mph));
526 out.pi1.assign(v.begin() +
static_cast<long>(mph), v.end());
527 const Matrix<T> IRinv =
inverse(IR);
528 out.busy =
vecmul(out.pi1, IRinv);
529 const std::vector<T> qv =
vecmul(out.busy, IRinv);
531 for (
const T& t : qv) q += t;
544std::vector<T> qbd_tail_expand(
const QbdTail<T>& g, std::size_t nlev, std::size_t mph) {
545 std::vector<T> pi(nlev * mph, num_traits<T>::from_int(0));
546 for (std::size_t i = 0; i < mph; ++i) pi[i] = g.pi0[i];
547 std::vector<T> v = g.pi1;
548 for (std::size_t n = 1; n < nlev; ++n) {
549 for (std::size_t i = 0; i < mph; ++i) pi[blk(n, mph) + i] = v[i];
557T pmf_at(
const std::vector<T>& pmf, std::size_t k) {
558 return k < pmf.size() ? pmf[k] : num_traits<T>::from_int(0);
568void add_batch_removal(Matrix<T>& B,
const std::vector<T>& pmf,
const T& rate,
569 std::size_t nlev, std::size_t mph) {
570 const T one = num_traits<T>::from_int(1), zero = num_traits<T>::from_int(0);
571 for (std::size_t n = 1; n < nlev; ++n) {
572 for (std::size_t m = 1; m <= n; ++m) {
573 const T p = pmf_at(pmf, n - m);
574 if (p > zero) add_identity(B, n, m, mph, T(rate * p));
577 for (std::size_t j = 0; j + 1 <= n; ++j) cdf += pmf_at(pmf, j);
578 const T tail = T(one - cdf);
579 if (tail > zero) add_identity(B, n, 0, mph, T(rate * tail));
584inline double qbd_scalar_rho(
double f,
double b,
double g) {
585 const double inf = std::numeric_limits<double>::infinity();
589 if (f + g <= 0.0)
return inf;
592 const double c1 = -(f + b + g);
593 const double disc = c1 * c1 - 4.0 * b * f;
594 if (disc < 0.0)
return inf;
595 const double sq = std::sqrt(disc);
596 double r1 = (-c1 - sq) / (2.0 * b), r2 = (-c1 + sq) / (2.0 * b);
597 if (r1 > r2) std::swap(r1, r2);
598 return (r1 > 0.0) ? r1 : r2;
617bool is_signal_class(
const qn::NetworkStruct<T>& L, std::size_t r) {
618 return r < L.issignal.size() && L.issignal[r];
628lang::SignalType signal_type_of(
const qn::NetworkStruct<T>& L, std::size_t r) {
643void arrival_map(
const qn::NetworkStruct<T>& L, RcatComponent<T>& c,
644 const std::vector<std::size_t>& source_stations) {
646 const std::size_t K = L.nclasses;
647 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
648 bool have_arrival =
false;
649 Matrix<T> Da0(1, 1, zero), Da1(1, 1, zero);
651 for (std::size_t isrc : source_stations)
652 for (std::size_t s = 0; s < K; ++s) {
653 const bool is_signal = is_signal_class(L, s);
658 for (std::size_t sd = 0; sd < K; ++sd)
659 prob_src += L.rt(isrc * K + s, c.ist * K + sd);
661 prob_src = L.rt(isrc * K + s, c.ist * K + c.r);
663 if (!(prob_src > zero) || L.disabled[isrc][s])
continue;
664 const T src_rate = L.rates(isrc, s);
665 const bool removal_signal =
666 is_signal && (signal_type_of(L, s) == SignalType::NEGATIVE ||
667 signal_type_of(L, s) == SignalType::CATASTROPHE);
668 if (removal_signal) {
669 if (signal_type_of(L, s) == SignalType::CATASTROPHE) {
670 c.lam_cat += T(src_rate * prob_src);
671 }
else if (s < L.signalremdist.size() && !L.signalremdist[s].empty()) {
672 c.batch.emplace_back(T(src_rate * prob_src), s);
674 c.lam_neg += T(src_rate * prob_src);
678 if (!(src_rate > zero))
continue;
680 std::pair<Matrix<T>, Matrix<T>> sm = proc_map(L, isrc, s);
681 Matrix<T> S0 = sm.first, S1 = sm.second;
682 if (prob_src < one) {
683 const T keep = prob_src, drop = T(one - prob_src);
684 for (std::size_t i = 0; i < S0.rows(); ++i)
685 for (std::size_t j = 0; j < S0.cols(); ++j) S0(i, j) += T(drop * S1(i, j));
686 for (std::size_t i = 0; i < S1.rows(); ++i)
687 for (std::size_t j = 0; j < S1.cols(); ++j) S1(i, j) = T(keep * S1(i, j));
690 Da0 =
krons(Da0, S0);
691 Da1 =
krons(Da1, S1);
704Matrix<T> build_local_rates(
const qn::NetworkStruct<T>& L,
const RcatComponent<T>& c,
705 const std::vector<std::size_t>& sink_nodes) {
706 const std::size_t K = L.nclasses;
707 const std::size_t mph = c.mph, nlev = c.nlev;
708 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
709 Matrix<T> Lm(c.N, c.N, zero);
715 add_block(Lm, 0, 0, mph,
kron(c.Da0,
eye<T>(c.ns)), one);
716 const Matrix<T> Lbusy =
krons(c.Da0, c.Ds0);
717 for (std::size_t n = 1; n < nlev; ++n) add_block(Lm, n, n, mph, Lbusy, one);
720 const Matrix<T> Aup =
kron(c.Da1,
eye<T>(c.ns));
721 for (std::size_t n = 0; n + 1 < nlev; ++n) add_block(Lm, n, n + 1, mph, Aup, one);
725 add_block(Lm, nlev - 1, nlev - 1, mph, Aup, one);
728 if (c.lam_cat > zero)
729 for (std::size_t n = 1; n < nlev; ++n) add_identity(Lm, n, 0, mph, c.lam_cat);
730 for (
const std::pair<T, std::size_t>& ba : c.batch)
731 add_batch_removal(Lm, L.signalremdist[ba.second], ba.first, nlev, mph);
733 if (c.lam_neg > zero)
734 for (std::size_t n = 1; n < nlev; ++n) add_identity(Lm, n, n - 1, mph, c.lam_neg);
738 if (L.disabled[c.ist][c.r])
return Lm;
739 const T mu = L.rates(c.ist, c.r);
740 if (!(mu > zero))
return Lm;
746 const std::size_t node = L.node_of_station(c.ist + 1);
749 for (std::size_t jsnk : sink_nodes)
750 for (std::size_t s = 0; s < K; ++s) {
751 const std::size_t from = (node - 1) * K + c.r, to = (jsnk - 1) * K + s;
752 if (from < L.rtnodes.rows() && to < L.rtnodes.cols())
753 prob_sink += L.rtnodes(from, to);
755 const T prob_self = L.rt(c.ist * K + c.r, c.ist * K + c.r);
757 if (prob_sink > zero)
758 for (std::size_t n = 1; n < nlev; ++n) add_block(Lm, n, n - 1, mph, c.Dsvc, prob_sink);
762 if (prob_self > zero)
763 for (std::size_t n = 1; n < nlev; ++n) add_block(Lm, n, n, mph, c.Dsvc, prob_self);
769RcatModel<T> build_rcat(
const qn::NetworkStruct<T>& L, std::size_t max_states) {
771 const std::size_t M = L.nstations, K = L.nclasses;
772 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
774 m.process_map.assign(M, std::vector<std::size_t>(K, 0));
776 std::vector<std::size_t> source_stations, queue_stations, sink_nodes;
777 for (std::size_t i = 0; i < L.nof_nodes(); ++i)
778 if (L.nodes[i].nodetype == qn::NodeType::Sink) sink_nodes.push_back(i + 1);
779 for (std::size_t ist = 0; ist < M; ++ist) {
786 if (ty == qn::NodeType::Source) source_stations.push_back(ist);
787 else if (ty != qn::NodeType::Sink) queue_stations.push_back(ist);
792 std::size_t pidx = 0;
793 for (std::size_t ist : queue_stations)
794 for (std::size_t r = 0; r < K; ++r) {
795 if (is_signal_class(L, r) || L.disabled[ist][r])
continue;
796 if (!(L.rates(ist, r) > zero))
continue;
797 m.process_map[ist][r] = ++pidx;
799 m.num_processes = pidx;
800 if (m.num_processes == 0)
return m;
804 const std::vector<double> njobs = L.njobs();
805 std::vector<RcatComponent<T>> comp(m.num_processes);
806 m.N.assign(m.num_processes, 0);
807 m.nlev.assign(m.num_processes, 0);
808 m.mph.assign(m.num_processes, 1);
809 m.level.assign(m.num_processes, std::vector<std::size_t>());
810 m.svcrate.assign(m.num_processes, std::vector<T>());
811 m.svcdown.assign(m.num_processes, std::vector<T>());
812 m.is_open_proc.assign(m.num_processes,
false);
813 for (std::size_t ist : queue_stations)
814 for (std::size_t r = 0; r < K; ++r) {
815 const std::size_t p = m.process_map[ist][r];
816 if (p == 0 || m.N[p - 1] != 0)
continue;
817 RcatComponent<T>& c = comp[p - 1];
820 const std::pair<Matrix<T>, Matrix<T>> svc = proc_map(L, ist, r);
823 arrival_map(L, c, source_stations);
827 if (std::isfinite(njobs[r])) {
828 c.nlev =
static_cast<std::size_t
>(njobs[r]) + 1;
831 m.is_open_proc[p - 1] =
true;
835 c.N = c.nlev * c.mph;
838 m.nlev[p - 1] = c.nlev;
839 m.mph[p - 1] = c.mph;
840 m.level[p - 1].assign(c.N, 0);
841 for (std::size_t n = 0; n < c.nlev; ++n)
842 for (std::size_t j = 0; j < c.mph; ++j) m.level[p - 1][n * c.mph + j] = n;
843 m.svcdown[p - 1].assign(c.mph, zero);
844 for (std::size_t i = 0; i < c.mph; ++i) {
846 for (std::size_t j = 0; j < c.mph; ++j) acc += c.Dsvc(i, j);
847 m.svcdown[p - 1][i] = acc;
849 m.svcrate[p - 1].assign(c.N, zero);
850 for (std::size_t n = 1; n < c.nlev; ++n)
851 for (std::size_t j = 0; j < c.mph; ++j)
852 m.svcrate[p - 1][n * c.mph + j] = m.svcdown[p - 1][j];
855 for (std::size_t ist : queue_stations)
856 for (std::size_t r = 0; r < K; ++r) {
857 if (m.process_map[ist][r] == 0)
continue;
858 const bool is_removal =
859 is_signal_class(L, r) && (signal_type_of(L, r) == SignalType::NEGATIVE ||
860 signal_type_of(L, r) == SignalType::CATASTROPHE);
861 const bool is_cat = is_removal && signal_type_of(L, r) == SignalType::CATASTROPHE;
862 const bool has_dist =
863 is_removal && r < L.signalremdist.size() && !L.signalremdist[r].empty();
864 for (std::size_t jst : queue_stations)
865 for (std::size_t s = 0; s < K; ++s) {
866 if (m.process_map[jst][s] == 0)
continue;
867 if (ist == jst && r == s)
continue;
868 const T pr = L.rt(ist * K + r, jst * K + s);
869 if (!(pr > zero))
continue;
871 a.from_station = ist;
875 a.prob = num_traits<T>::to_double(pr);
876 a.is_negative = is_removal;
877 a.is_catastrophe = is_cat;
878 a.has_removal_dist = has_dist;
880 m.actions.push_back(a);
884 m.L.reserve(m.num_processes);
885 for (std::size_t p = 0; p < m.num_processes; ++p)
886 m.L.push_back(build_local_rates(L, comp[p], sink_nodes));
888 const std::size_t A = m.actions.size();
893 for (
const RcatAction& a : m.actions) {
894 const std::size_t pa = m.process_map[a.from_station][a.from_class] - 1;
895 const std::size_t pp = m.process_map[a.to_station][a.to_class] - 1;
899 const RcatComponent<T>& ca = comp[pa];
900 const T prob = num_traits<T>::from_double(a.prob);
901 Matrix<T> Am(ca.N, ca.N, zero);
902 for (std::size_t n = 1; n < ca.nlev; ++n)
903 add_block(Am, n, n - 1, ca.mph, ca.Dsvc, prob);
910 if (std::isfinite(njobs[a.from_class])) {
911 const std::size_t off = blk(ca.nlev - 1, ca.mph);
912 for (std::size_t i = 0; i < ca.mph; ++i)
913 Am(off + i, off + i) += T(m.svcdown[pa][i] * prob);
917 const RcatComponent<T>& cp = comp[pp];
918 Matrix<T> Bm(cp.N, cp.N, zero);
920 if (a.is_catastrophe) {
921 for (std::size_t n = 0; n < cp.nlev; ++n) set_identity(Bm, n, 0, cp.mph);
922 }
else if (a.has_removal_dist) {
923 add_batch_removal(Bm, L.signalremdist[a.removal_class], one, cp.nlev, cp.mph);
924 set_identity(Bm, 0, 0, cp.mph);
926 set_identity(Bm, 0, 0, cp.mph);
927 for (std::size_t n = 1; n < cp.nlev; ++n) set_identity(Bm, n, n - 1, cp.mph);
934 for (std::size_t n = 0; n + 1 < cp.nlev; ++n) set_identity(Bm, n, n + 1, cp.mph);
935 set_identity(Bm, cp.nlev - 1, cp.nlev - 1, cp.mph);
944Matrix<T> assemble_generator(
const RcatModel<T>& m,
const std::vector<T>& x, std::size_t k) {
945 Matrix<T> Qk = m.L[k];
946 for (std::size_t c = 0; c < m.actions.size(); ++c) {
948 for (std::size_t i = 0; i < Qk.rows(); ++i)
949 for (std::size_t j = 0; j < Qk.cols(); ++j) Qk(i, j) += T(x[c] * m.Pb[c](i, j));
950 }
else if (m.act[c] == k) {
951 for (std::size_t i = 0; i < Qk.rows(); ++i)
952 for (std::size_t j = 0; j < Qk.cols(); ++j) Qk(i, j) += m.Aa[c](i, j);
963Matrix<double> ag_as_double(
const Matrix<T>& m) {
964 Matrix<double> out(m.rows(), m.cols(), 0.0);
965 for (std::size_t i = 0; i < m.rows(); ++i)
966 for (std::size_t j = 0; j < m.cols(); ++j)
967 out(i, j) = num_traits<T>::to_double(m(i, j));
977AgWirePayload wire_payload(
const RcatModel<T>& m, std::size_t k) {
979 a.k =
static_cast<int>(k);
980 a.n =
static_cast<int>(m.N[k]);
981 a.mph =
static_cast<int>(m.mph[k]);
982 a.nlev =
static_cast<int>(m.nlev[k]);
983 a.level.assign(m.level[k].begin(), m.level[k].end());
985 for (std::size_t c = 0; c < m.actions.size(); ++c) {
987 a.passive_c.push_back(
static_cast<int>(c));
988 a.passive_m.push_back(
ag_triplets(ag_as_double(m.Pb[c])));
989 }
else if (m.act[c] == k) {
990 a.active_c.push_back(
static_cast<int>(c));
991 a.active_m.push_back(
ag_triplets(ag_as_double(m.Aa[c])));
1007void compute_equilibrium(
const RcatModel<T>& m,
const std::vector<T>& x,
1008 std::vector<std::vector<T>>& pi, std::vector<Matrix<T>>& Q,
1009 const AgOptions* opt =
nullptr, AgWorkerPool* pool =
nullptr) {
1010 pi.assign(m.num_processes, std::vector<T>());
1011 Q.assign(m.num_processes, Matrix<T>());
1013 auto gen = [&](std::size_t k) {
return assemble_generator(m, x, k); };
1014 auto sol = [&](
const Matrix<T>& Qk, std::size_t k) {
1015 return solve_component(Qk, m.mph[k], m.nlev[k], m.level[k]);
1018 const std::string mode = (opt ==
nullptr) ? std::string(
exec_serial()) : opt->exec;
1022 auto payload = [&](std::size_t k) {
return wire_payload(m, k); };
1035void compute_equilibrium_qbd(
const RcatModel<T>& m,
const std::vector<T>& x,
1036 std::vector<std::vector<T>>& pi, std::vector<Matrix<T>>& Q,
1037 std::vector<double>& rho_proc, std::vector<bool>& is_geom,
1038 std::vector<QbdTail<T>>& geom_data) {
1039 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
1040 pi.assign(m.num_processes, std::vector<T>());
1041 Q.assign(m.num_processes, Matrix<T>());
1042 rho_proc.assign(m.num_processes, 0.0);
1043 is_geom.assign(m.num_processes,
false);
1044 geom_data.assign(m.num_processes, QbdTail<T>());
1046 for (std::size_t k = 0; k < m.num_processes; ++k) {
1047 const std::size_t Nk = m.N[k];
1048 Matrix<T> Off = m.L[k];
1049 for (std::size_t i = 0; i < Nk; ++i) Off(i, i) = zero;
1050 for (std::size_t c = 0; c < m.actions.size(); ++c) {
1051 if (m.psv[c] == k) {
1052 for (std::size_t i = 0; i < Nk; ++i)
1053 for (std::size_t j = 0; j < Nk; ++j) Off(i, j) += T(x[c] * m.Pb[c](i, j));
1054 }
else if (m.act[c] == k) {
1055 for (std::size_t i = 0; i < Nk; ++i)
1056 for (std::size_t j = 0; j < Nk; ++j) Off(i, j) += m.Aa[c](i, j);
1059 for (std::size_t i = 0; i < Nk; ++i) Off(i, i) = zero;
1062 const std::size_t mph = m.mph[k], nlev = m.nlev[k];
1063 bool solved_geom =
false;
1064 if (m.is_open_proc[k] && nlev >= 5) {
1068 const std::size_t s0 = Nk - 2;
1069 const double f = num_traits<T>::to_double(Off(s0, s0 + 1));
1070 const double b = num_traits<T>::to_double(Off(s0, s0 - 1));
1071 const double g0 = num_traits<T>::to_double(Off(s0, 0));
1075 double inter_down = 0.0;
1076 for (std::size_t j = 1; j + 3 < Nk; ++j)
1077 inter_down += num_traits<T>::to_double(Off(s0, j));
1078 if (inter_down <= 1e-11 && f > 0.0) {
1079 const double rho = qbd_scalar_rho(f, b, g0);
1080 if (std::isfinite(rho) && rho > 0.0 && rho < 1.0 - 1e-12) {
1083 pi[k].assign(Nk, zero);
1084 const T rt = num_traits<T>::from_double(rho);
1085 T acc = T(one - rt);
1086 for (std::size_t n = 0; n < Nk; ++n) {
1093 }
else if (is_block_tridiagonal(Q[k], m.level[k])) {
1097 const std::size_t s0 = nlev - 2;
1098 const Matrix<T> A0 = level_block(Q[k], s0, s0 + 1, mph);
1099 const Matrix<T> A1 = level_block(Q[k], s0, s0, mph);
1100 const Matrix<T> A2 = level_block(Q[k], s0, s0 - 1, mph);
1101 bool any_up =
false;
1102 for (std::size_t i = 0; i < mph && !any_up; ++i) {
1104 for (std::size_t j = 0; j < mph; ++j) rs += A0(i, j);
1105 if (rs > zero) any_up =
true;
1108 const QbdTail<T> g = qbd_matrix_tail(Q[k], A0, A1, A2, mph);
1112 pi[k] = qbd_tail_expand(g, nlev, mph);
1118 if (!solved_geom) pi[k] = solve_component(Q[k], mph, nlev, m.level[k]);
1124double block_norm(
const std::vector<std::vector<T>>& a,
const std::vector<std::vector<T>>& b) {
1126 for (std::size_t k = 0; k < a.size() && k < b.size(); ++k) {
1127 const std::size_t n = std::min(a[k].size(), b[k].size());
1129 for (std::size_t i = 0; i < n; ++i)
1130 s += std::fabs(num_traits<T>::to_double(T(a[k][i] - b[k][i])));
1138mva::MvaSolution<T> rcat_metrics(
const qn::NetworkStruct<T>& L,
const RcatModel<T>& m,
1139 const std::vector<std::vector<T>>& pi,
1140 const std::vector<double>& rho_proc,
1141 const std::vector<bool>& is_geom,
1142 const std::vector<QbdTail<T>>& geom_data) {
1143 const std::size_t M = L.nstations, K = L.nclasses;
1144 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
1145 mva::MvaSolution<T> s;
1150 s.C.assign(K, zero);
1151 s.X.assign(K, zero);
1153 for (std::size_t ist = 0; ist < M; ++ist)
1154 for (std::size_t r = 0; r < K; ++r) {
1155 const std::size_t p = m.process_map.empty() ? 0 : m.process_map[ist][r];
1156 if (p == 0 || p > pi.size() || pi[p - 1].empty())
continue;
1157 const bool disabled = L.disabled[ist][r];
1158 const std::size_t mph = m.mph[p - 1];
1159 if (!is_geom.empty() && p <= is_geom.size() && is_geom[p - 1]) {
1164 const T mu = disabled ? zero : L.rates(ist, r);
1165 const T rho = num_traits<T>::from_double(rho_proc[p - 1]);
1166 s.Q(ist, r) = T(rho / (one - rho));
1168 if (mu > zero) s.Tp(ist, r) = T(mu * rho);
1171 const QbdTail<T>& g = geom_data[p - 1];
1172 T busy = zero, tput = zero;
1173 for (std::size_t i = 0; i < mph; ++i) {
1175 tput += T(g.busy[i] * m.svcdown[p - 1][i]);
1177 s.Q(ist, r) = g.qlen;
1179 s.Tp(ist, r) = tput;
1182 const std::vector<T>& v = pi[p - 1];
1183 T q = zero, tput = zero;
1184 for (std::size_t n = 0; n < v.size(); ++n) {
1185 q += T(num_traits<T>::from_int(
static_cast<long>(m.level[p - 1][n])) * v[n]);
1189 tput += T(m.svcrate[p - 1][n] * v[n]);
1193 for (std::size_t i = 0; i < mph; ++i) level0 += v[i];
1194 s.U(ist, r) = T(one - level0);
1195 s.Tp(ist, r) = tput;
1199 for (std::size_t ist = 0; ist < M; ++ist)
1200 for (std::size_t r = 0; r < K; ++r)
1201 if (s.Tp(ist, r) > zero) s.R(ist, r) = T(s.Q(ist, r) / s.Tp(ist, r));
1213 for (std::size_t ist = 0; ist < M; ++ist) {
1214 if (L.stations[ist].nodetype != qn::NodeType::Source)
continue;
1215 for (std::size_t r = 0; r < K; ++r)
1216 s.Tp(ist, r) = L.disabled[ist][r] ? zero : L.rates(ist, r);
1219 const std::vector<double> njobs = L.njobs();
1220 for (std::size_t r = 0; r < K; ++r) {
1221 if (!std::isfinite(njobs[r])) {
1222 for (std::size_t ist = 0; ist < M; ++ist) {
1224 if (L.stations[ist].nodetype == qn::NodeType::Source) {
1225 s.X[r] = L.disabled[ist][r] ? zero : L.rates(ist, r);
1230 for (std::size_t ist = 0; ist < M; ++ist) c += s.R(ist, r);
1233 const std::size_t refst = L.classes[r].refstat;
1234 if (refst > 0 && refst <= M) {
1235 s.X[r] = s.Tp(refst - 1, r);
1237 s.C[r] = T(num_traits<T>::from_double(njobs[r]) / s.X[r]);
1309 std::size_t max_states = 0) {
1310 if (max_states == 0) max_states =
opt.max_states;
1313 "solver_ag: the RCAT analyzers stop on a tolerance and the matrix-geometric "
1314 "variant takes a square root, so they need transcendental arithmetic; rerun this "
1315 "model with --arith double or --arith real");
1317 using namespace ag_detail;
1321 std::string method =
opt.method;
1322 if (method ==
"default") method =
"inap";
1323 if (method ==
"exact") {
1328 if (method !=
"inap" && method !=
"inapplus" && method !=
"inapinf")
1341 "solver_ag: the 'cluster' execution backend does not carry the 'inapinf' agent "
1342 "solve (the matrix-geometric tail of an open agent runs on the coordinator "
1343 "only); use exec 'serial' or 'parallel' with 'inapinf', or method 'inap'/"
1344 "'inapplus' with 'cluster'");
1351 const std::string hint = (
opt.exec ==
"threads")
1352 ?
"; 'threads' was renamed to 'parallel' (alias 'para')" :
"";
1353 throw InputError(
"solver_ag: unknown execution backend '" +
opt.exec +
1354 "'; use 'serial', 'parallel' (alias 'para') or 'cluster'" + hint);
1357 std::unique_ptr<AgWorkerPool> pool;
1362 const RcatModel<T> m = build_rcat(L, max_states);
1364 if (m.num_processes == 0) {
1371 out.
sol.C.assign(K, zero);
1372 out.
sol.X.assign(K, zero);
1373 out.
sol.method =
opt.method;
1378 const std::size_t A = m.actions.size();
1379 std::vector<std::vector<T>> pi;
1380 std::vector<Matrix<T>> Q;
1390 pi.assign(m.num_processes, std::vector<T>());
1391 for (std::size_t k = 0; k < m.num_processes; ++k) {
1393 pi[k] = solve_component(Qk, m.mph[k], m.nlev[k], m.level[k]);
1395 out.
sol = rcat_metrics(L, m, pi, std::vector<double>(), std::vector<bool>(),
1396 std::vector<QbdTail<T>>());
1397 out.
sol.method =
opt.method;
1415 std::vector<bool> not_birth_death(m.num_processes,
false);
1416 for (std::size_t k = 0; k < m.num_processes; ++k) {
1417 if (m.mph[k] > 1) not_birth_death[k] =
true;
1418 for (std::size_t n = 0; n < m.N[k]; ++n)
1419 for (std::size_t j = 0; j < m.N[k]; ++j) {
1420 const std::size_t
ln = m.level[k][n], lj = m.level[k][j];
1421 const std::size_t d = (
ln > lj) ? (
ln - lj) : (lj -
ln);
1422 if (d > 1 && m.L[k](n, j) > zero) not_birth_death[k] =
true;
1428 std::vector<std::vector<std::size_t>> active_cols(A);
1429 for (std::size_t a = 0; a < A; ++a)
1430 for (std::size_t j = 0; j < m.Aa[a].cols(); ++j) {
1432 for (std::size_t i = 0; i < m.Aa[a].rows(); ++i) colsum += m.Aa[a](i, j);
1433 if (colsum > zero) active_cols[a].push_back(j);
1437 std::vector<T> x(A, zero);
1438 for (std::size_t a = 0; a < A; ++a)
1440 static_cast<double>(A + 1));
1442 std::vector<double> rho_proc;
1443 std::vector<bool> is_geom;
1444 std::vector<QbdTail<T>> geom_data;
1445 const bool qbd = (method ==
"inapinf");
1446 if (qbd) compute_equilibrium_qbd(m, x, pi, Q, rho_proc, is_geom, geom_data);
1447 else compute_equilibrium(m, x, pi, Q, &
opt, pool.get());
1449 std::vector<std::vector<T>> a_rowsum(A);
1450 for (std::size_t a = 0; a < A; ++a) {
1451 a_rowsum[a].assign(m.Aa[a].rows(), zero);
1452 for (std::size_t i = 0; i < m.Aa[a].rows(); ++i)
1453 for (std::size_t j = 0; j < m.Aa[a].cols(); ++j) a_rowsum[a][i] += m.Aa[a](i, j);
1460 const double tol =
opt.tol;
1461 std::size_t iter = 0;
1462 bool converged =
false;
1463 for (iter = 1; iter <= static_cast<std::size_t>(
opt.iter_max); ++iter) {
1464 const std::vector<std::vector<T>> pi_ref = pi;
1465 for (std::size_t a = 0; a < A; ++a) {
1466 const std::size_t k = m.act[a];
1469 if (m.mph[k] == 1) {
1472 const std::size_t idx = std::min<std::size_t>(1, m.N[k] - 1);
1479 for (std::size_t i = 0; i < m.mph[k]; ++i)
1480 acc += T(geom_data[k].busy[i] * a_rowsum[a][m.mph[k] + i]);
1485 for (std::size_t i = 0; i < pi[k].size() && i < a_rowsum[a].size(); ++i)
1486 acc += T(pi[k][i] * a_rowsum[a][i]);
1491 const std::vector<T> v =
vecmul(pi[k], m.Aa[a]);
1492 if (method ==
"inapplus" || not_birth_death[k]) {
1495 for (std::size_t j = 0; j < v.size(); ++j)
sum += v[j];
1496 if (
sum > zero) x[a] =
sum;
1504 std::size_t cnt = 0;
1505 for (std::size_t j : active_cols[a])
1506 if (pi[k][j] > zero && v[j] > zero) {
1507 sum += T(v[j] / pi[k][j]);
1514 if (qbd) compute_equilibrium_qbd(m, x, pi, Q, rho_proc, is_geom, geom_data);
1515 else compute_equilibrium(m, x, pi, Q, &
opt, pool.get());
1517 if (block_norm<T>(pi, pi_ref) < tol) {
1525 if (!converged) iter =
static_cast<std::size_t
>(
opt.iter_max) + 1;
1529 for (std::size_t a = 0; a < A; ++a) {
1530 const std::size_t k = m.act[a];
1532 for (std::size_t j = 0; j < m.N[k]; ++j) {
1533 T acc = T(x[a] * pi[k][j]);
1534 for (std::size_t i = 0; i < m.N[k]; ++i) acc -= T(pi[k][i] * m.Aa[a](i, j));
1538 res = std::max(res, std::sqrt(s));
1543 out.
sol = rcat_metrics(L, m, pi, rho_proc, is_geom, geom_data);
1544 out.
sol.method =
opt.method;
1545 out.
sol.iter =
static_cast<int>(iter);
Execution backends of the reversed-rate fixed point.
Options of the agent-based (RCAT) solver.
UnsupportedError(const std::string &what)
AgWorkerPool(const std::vector< std::string > &endpoints, double timeout_seconds)
A network plus its refreshed NetworkStruct.
Steady-state distribution of a continuous-time Markov chain.
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.
LU factorization with partial pivoting, templated on the number type.
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.
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
void ag_sweep_cluster(std::size_t n, AgWorkerPool &workers, const std::vector< T > &x, Gen gen, Sol sol, Payload payload, std::vector< Matrix< T > > &Q, std::vector< std::vector< T > > &pi)
The same sweep with the agents partitioned over remote ag-worker processes.
std::vector< std::array< double, 3 > > ag_triplets(const Matrix< double > &m)
Non-zero entries of M as [row, col, value] triplets, 0-based.
void ag_sweep_serial(std::size_t n, Gen gen, Sol sol, std::vector< Matrix< T > > &Q, std::vector< std::vector< T > > &pi)
Evaluate every agent of one sweep.
Matrix< T > krons(const Matrix< T > &A, const Matrix< T > &B)
Kronecker sum, MATLAB's krons: kron(A, I_nb) + kron(I_na, B).
Matrix< T > kron(const Matrix< T > &A, const Matrix< T > &B)
Kronecker product.
const char * exec_cluster()
Partition the agents over remote ag-worker processes.
const char * exec_serial()
The caller's own loop, in agent order.
Matrix< T > qbd_R_logred(const Matrix< T > &B, const Matrix< T > &L, const Matrix< T > &F, unsigned iter_max, const T &tol)
R by logarithmic reduction (qbd_R_logred.m).
AgResult< T > solver_ag(const qn::NetworkStruct< T > &L, const AgOptions &opt, std::size_t max_states=0)
Port of solver_ag.m.
void ag_sweep_parallel(std::size_t n, unsigned nworkers, Gen gen, Sol sol, std::vector< Matrix< T > > &Q, std::vector< std::vector< T > > &pi)
The same sweep over a thread pool.
bool exec_is_parallel(const std::string &mode)
True for either spelling of the local-thread-pool backend.
bool rcat_supports_process(lang::ProcessType t)
The process types the RCAT construction can give a phase dimension to.
mam::Map< T > dist_to_map(const Distrib< T > &d)
SignalType
G-network signal classes, with the values of MATLAB SignalType.
@ NEGATIVE
removes a batch of jobs (Gelenbe's negative customer)
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
NodeType
Node kinds, with the values of MATLAB NodeType.
Matrix< T > krons(const Matrix< T > &A, const Matrix< T > &B)
Kronecker sum, MATLAB's krons: kron(A, I_nb) + kron(I_na, B).
Matrix< T > kron(const Matrix< T > &A, const Matrix< T > &B)
Kronecker product.
Matrix< T > qbd_R_logred(const Matrix< T > &B, const Matrix< T > &L, const Matrix< T > &F, unsigned iter_max, const T &tol)
R by logarithmic reduction (qbd_R_logred.m).
Matrix< T > ctmc_makeinfgen(const Matrix< T > &Q)
Set the diagonal so that every row sums to zero (ctmc_makeinfgen).
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
void lu_solve(const Matrix< T > &LU, const std::vector< std::size_t > &piv, std::vector< T > &b)
Solve LUx = Pb in place on b, using the factors from lu_factor.
std::vector< T > vecmul(const std::vector< T > &v, const Matrix< T > &A)
Row vector times matrix, v A.
std::vector< std::size_t > lu_factor(Matrix< T > &A)
In-place LU of A (n x n).
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
std::vector< T > mulvec(const Matrix< T > &A, const std::vector< T > &v)
Matrix times column vector, A v.
std::vector< T > ones(std::size_t n)
Column vector of ones, the ubiquitous e in MAP algebra.
Matrix< T > eye(std::size_t n)
Identity of order n.
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Quasi-birth-death processes: the rate matrix R, the fundamental matrix G, the caudal characteristic,...
What the RCAT analyzer returns beyond the metrics.
double rcat_residual
The RCAT product-form residual of Remark 2, max_l ||pi (x_l I - T_l)||_2, computed by 'inapinf' only.
mva::MvaSolution< T > sol
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.