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;
163 T prob_void = num_traits<T>::from_int(0);
167inline std::size_t blk(std::size_t n, std::size_t mph) {
return n * mph; }
171Matrix<T> madd_local(
const Matrix<T>& A,
const Matrix<T>& B) {
172 Matrix<T> C(A.rows(), A.cols(), num_traits<T>::from_int(0));
173 for (std::size_t i = 0; i < A.rows(); ++i)
174 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = T(A(i, j) + B(i, j));
180void add_block(Matrix<T>& target, std::size_t li, std::size_t lj, std::size_t mph,
181 const Matrix<T>& block,
const T& scale) {
182 const std::size_t r0 = blk(li, mph), c0 = blk(lj, mph);
183 for (std::size_t i = 0; i < mph; ++i)
184 for (std::size_t j = 0; j < mph; ++j) target(r0 + i, c0 + j) += T(scale * block(i, j));
189void add_identity(Matrix<T>& target, std::size_t li, std::size_t lj, std::size_t mph,
191 const std::size_t r0 = blk(li, mph), c0 = blk(lj, mph);
192 for (std::size_t i = 0; i < mph; ++i) target(r0 + i, c0 + i) += scale;
197void set_identity(Matrix<T>& target, std::size_t li, std::size_t lj, std::size_t mph) {
198 const std::size_t r0 = blk(li, mph), c0 = blk(lj, mph);
199 const T one = num_traits<T>::from_int(1);
200 for (std::size_t i = 0; i < mph; ++i) target(r0 + i, c0 + i) = one;
205Matrix<T> level_block(
const Matrix<T>& Q, std::size_t li, std::size_t lj, std::size_t mph) {
206 Matrix<T> out(mph, mph, num_traits<T>::from_int(0));
207 const std::size_t r0 = blk(li, mph), c0 = blk(lj, mph);
208 for (std::size_t i = 0; i < mph; ++i)
209 for (std::size_t j = 0; j < mph; ++j) out(i, j) = Q(r0 + i, c0 + j);
220bool is_markovian_map(
const Matrix<T>& D0,
const Matrix<T>& D1) {
221 const std::size_t n = D0.rows();
222 if (n == 0 || D0.cols() != n || D1.rows() != n || D1.cols() != n)
return false;
224 for (std::size_t i = 0; i < n; ++i)
225 for (std::size_t j = 0; j < n; ++j) {
226 const double a = num_traits<T>::to_double(D0(i, j));
227 const double b = num_traits<T>::to_double(D1(i, j));
228 if (!std::isfinite(a) || !std::isfinite(b))
return false;
229 scale = std::max(scale, std::max(std::fabs(a), std::fabs(b)));
231 const double tol = 1e-9 * scale;
232 for (std::size_t i = 0; i < n; ++i) {
234 for (std::size_t j = 0; j < n; ++j) {
235 const double a = num_traits<T>::to_double(D0(i, j));
236 const double b = num_traits<T>::to_double(D1(i, j));
237 if (i != j && a < -tol)
return false;
238 if (b < -tol)
return false;
241 if (std::fabs(row) > tol)
return false;
254std::pair<Matrix<T>, Matrix<T>> proc_map(
const qn::NetworkStruct<T>& L, std::size_t ist,
256 const T zero = num_traits<T>::from_int(0);
257 if (L.has_service_law(ist, r)) {
260 if (is_markovian_map(m.D0, m.D1))
return std::make_pair(m.D0, m.D1);
261 }
catch (
const std::exception&) {
265 T rate = L.disabled[ist][r] ? zero : L.rates(ist, r);
266 if (!(rate > zero) || !std::isfinite(num_traits<T>::to_double(rate))) rate = zero;
267 Matrix<T> D0(1, 1, zero), D1(1, 1, zero);
268 D0(0, 0) = T(zero - rate);
270 return std::make_pair(D0, D1);
274bool is_tridiagonal(
const Matrix<T>& Q) {
275 const std::size_t n = Q.rows();
276 for (std::size_t i = 0; i < n; ++i)
277 for (std::size_t j = 0; j < n; ++j) {
278 const std::size_t d = (i > j) ? (i - j) : (j - i);
279 if (d > 1 && std::fabs(num_traits<T>::to_double(Q(i, j))) > 1e-14)
return false;
289bool is_block_tridiagonal(
const Matrix<T>& Q,
const std::vector<std::size_t>& lvl) {
290 const std::size_t n = Q.rows();
291 for (std::size_t i = 0; i < n; ++i)
292 for (std::size_t j = 0; j < n; ++j) {
293 const std::size_t d = (lvl[i] > lvl[j]) ? (lvl[i] - lvl[j]) : (lvl[j] - lvl[i]);
294 if (d > 1 && std::fabs(num_traits<T>::to_double(Q(i, j))) > 1e-14)
return false;
308std::vector<T> birth_death_solve(
const Matrix<T>& Q) {
309 const std::size_t n = Q.rows();
310 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
311 if (n <= 1)
return std::vector<T>(1, one);
313 std::vector<T> pi(n, zero);
315 for (std::size_t i = 1; i < n; ++i) {
316 const T birth = Q(i - 1, i);
317 const T death = Q(i, i - 1);
318 pi[i] = (death > zero) ? T(pi[i - 1] * birth / death) : zero;
321 for (
const T& v : pi) total += v;
323 for (T& v : pi) v /= total;
325 const T u = one / num_traits<T>::from_int(
static_cast<long>(n));
326 for (T& v : pi) v = u;
343std::vector<T> stat_vector(
const Matrix<T>& C) {
344 const std::size_t m = C.rows();
345 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
347 Matrix<T> At(m, m, zero);
348 for (std::size_t i = 0; i < m; ++i)
349 for (std::size_t j = 0; j < m; ++j) At(j, i) = (j == 0) ? one : C(i, j);
350 std::vector<T> rhs(m, zero);
352 const std::vector<std::size_t> piv =
lu_factor(At);
368std::vector<T> qbd_finite_solve(
const Matrix<T>& Q, std::size_t m, std::size_t nlev) {
369 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
372 std::vector<Matrix<T>> C(nlev);
373 C[nlev - 1] = level_block(Q, nlev - 1, nlev - 1, m);
374 for (std::size_t t = nlev - 1; t-- > 0;) {
375 const Matrix<T> F = level_block(Q, t, t + 1, m);
376 const Matrix<T> D = level_block(Q, t + 1, t, m);
377 Matrix<T> negC = C[t + 1];
378 for (std::size_t i = 0; i < m; ++i)
379 for (std::size_t j = 0; j < m; ++j) negC(i, j) = T(zero - negC(i, j));
381 Matrix<T> Cn = level_block(Q, t, t, m);
382 const Matrix<T> corr =
matmul(step, D);
383 for (std::size_t i = 0; i < m; ++i)
384 for (std::size_t j = 0; j < m; ++j) Cn(i, j) += corr(i, j);
388 std::vector<T> pi(nlev * m, zero);
389 const std::vector<T> p0 = stat_vector(C[0]);
390 for (std::size_t i = 0; i < m; ++i) pi[i] = p0[i];
391 for (std::size_t n = 0; n + 1 < nlev; ++n) {
392 Matrix<T> negC = C[n + 1];
393 for (std::size_t i = 0; i < m; ++i)
394 for (std::size_t j = 0; j < m; ++j) negC(i, j) = T(zero - negC(i, j));
395 const Matrix<T> step =
matmul(level_block(Q, n, n + 1, m),
inverse(negC));
396 for (std::size_t j = 0; j < m; ++j) {
398 for (std::size_t i = 0; i < m; ++i) acc += T(pi[blk(n, m) + i] * step(i, j));
399 pi[blk(n + 1, m) + j] = acc;
404 for (
const T& v : pi) total += v;
406 for (T& v : pi) v /= total;
408 const T u = one / num_traits<T>::from_int(
static_cast<long>(nlev * m));
409 for (T& v : pi) v = u;
426std::vector<T> solve_component(
const Matrix<T>& Q, std::size_t mph, std::size_t nlev,
427 const std::vector<std::size_t>& lvl) {
429 if (is_tridiagonal(Q))
return birth_death_solve(Q);
430 }
else if (is_block_tridiagonal(Q, lvl)) {
431 return qbd_finite_solve(Q, mph, nlev);
440 std::vector<T> pi0, pi1;
442 T qlen = num_traits<T>::from_int(0);
461QbdTail<T> qbd_matrix_tail(
const Matrix<T>& Qk,
const Matrix<T>& A0,
const Matrix<T>& A1,
462 const Matrix<T>& A2, std::size_t mph) {
464 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
467 R =
qbd_R_logred(A2, A1, A0, 500u, T(num_traits<T>::from_double(1e-14)));
468 }
catch (
const std::exception&) {
471 for (std::size_t i = 0; i < mph; ++i)
472 for (std::size_t j = 0; j < mph; ++j) {
473 const double v = num_traits<T>::to_double(R(i, j));
476 if (!std::isfinite(v) || v < -1e-12)
return out;
479 const Matrix<T> B00 = level_block(Qk, 0, 0, mph);
480 Matrix<T> IR =
eye<T>(mph);
481 for (std::size_t i = 0; i < mph; ++i)
482 for (std::size_t j = 0; j < mph; ++j) IR(i, j) = T(IR(i, j) - R(i, j));
483 std::vector<T> tail_mass;
486 }
catch (
const std::exception&) {
495 for (std::size_t i = 0; i < mph; ++i) {
496 const double w = num_traits<T>::to_double(tail_mass[i]);
497 if (!std::isfinite(w) || w < 1.0 - 1e-9)
return out;
501 const Matrix<T> lower_right = madd_local(A1,
matmul(R, A2));
502 const std::size_t n2 = 2 * mph;
503 Matrix<T> SysT(n2, n2, zero);
504 for (std::size_t i = 0; i < mph; ++i)
505 for (std::size_t j = 0; j < mph; ++j) {
506 SysT(j, i) = B00(i, j);
507 SysT(mph + j, i) = A0(i, j);
508 SysT(j, mph + i) = A2(i, j);
509 SysT(mph + j, mph + i) = lower_right(i, j);
511 for (std::size_t i = 0; i < mph; ++i) {
513 SysT(0, mph + i) = tail_mass[i];
515 std::vector<T> v(n2, zero);
519 const std::vector<std::size_t> piv =
lu_factor(lu);
521 }
catch (
const std::exception&) {
524 for (std::size_t i = 0; i < n2; ++i)
525 if (!std::isfinite(num_traits<T>::to_double(v[i])))
return out;
527 out.pi0.assign(v.begin(), v.begin() +
static_cast<long>(mph));
528 out.pi1.assign(v.begin() +
static_cast<long>(mph), v.end());
529 const Matrix<T> IRinv =
inverse(IR);
530 out.busy =
vecmul(out.pi1, IRinv);
531 const std::vector<T> qv =
vecmul(out.busy, IRinv);
533 for (
const T& t : qv) q += t;
546std::vector<T> qbd_tail_expand(
const QbdTail<T>& g, std::size_t nlev, std::size_t mph) {
547 std::vector<T> pi(nlev * mph, num_traits<T>::from_int(0));
548 for (std::size_t i = 0; i < mph; ++i) pi[i] = g.pi0[i];
549 std::vector<T> v = g.pi1;
550 for (std::size_t n = 1; n < nlev; ++n) {
551 for (std::size_t i = 0; i < mph; ++i) pi[blk(n, mph) + i] = v[i];
559T pmf_at(
const std::vector<T>& pmf, std::size_t k) {
560 return k < pmf.size() ? pmf[k] : num_traits<T>::from_int(0);
570void add_batch_removal(Matrix<T>& B,
const std::vector<T>& pmf,
const T& rate,
571 std::size_t nlev, std::size_t mph) {
572 const T one = num_traits<T>::from_int(1), zero = num_traits<T>::from_int(0);
573 for (std::size_t n = 1; n < nlev; ++n) {
574 for (std::size_t m = 1; m <= n; ++m) {
575 const T p = pmf_at(pmf, n - m);
576 if (p > zero) add_identity(B, n, m, mph, T(rate * p));
579 for (std::size_t j = 0; j + 1 <= n; ++j) cdf += pmf_at(pmf, j);
580 const T tail = T(one - cdf);
581 if (tail > zero) add_identity(B, n, 0, mph, T(rate * tail));
586inline double qbd_scalar_rho(
double f,
double b,
double g) {
587 const double inf = std::numeric_limits<double>::infinity();
591 if (f + g <= 0.0)
return inf;
594 const double c1 = -(f + b + g);
595 const double disc = c1 * c1 - 4.0 * b * f;
596 if (disc < 0.0)
return inf;
597 const double sq = std::sqrt(disc);
598 double r1 = (-c1 - sq) / (2.0 * b), r2 = (-c1 + sq) / (2.0 * b);
599 if (r1 > r2) std::swap(r1, r2);
600 return (r1 > 0.0) ? r1 : r2;
619bool is_signal_class(
const qn::NetworkStruct<T>& L, std::size_t r) {
620 return r < L.issignal.size() && L.issignal[r];
630lang::SignalType signal_type_of(
const qn::NetworkStruct<T>& L, std::size_t r) {
645void arrival_map(
const qn::NetworkStruct<T>& L, RcatComponent<T>& c,
646 const std::vector<std::size_t>& source_stations) {
648 const std::size_t K = L.nclasses;
649 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
650 bool have_arrival =
false;
651 Matrix<T> Da0(1, 1, zero), Da1(1, 1, zero);
653 for (std::size_t isrc : source_stations)
654 for (std::size_t s = 0; s < K; ++s) {
655 const bool is_signal = is_signal_class(L, s);
660 for (std::size_t sd = 0; sd < K; ++sd)
661 prob_src += L.rt(isrc * K + s, c.ist * K + sd);
663 prob_src = L.rt(isrc * K + s, c.ist * K + c.r);
665 if (!(prob_src > zero) || L.disabled[isrc][s])
continue;
666 const T src_rate = L.rates(isrc, s);
667 const bool removal_signal =
668 is_signal && (signal_type_of(L, s) == SignalType::NEGATIVE ||
669 signal_type_of(L, s) == SignalType::CATASTROPHE);
670 if (removal_signal) {
671 if (signal_type_of(L, s) == SignalType::CATASTROPHE) {
672 c.lam_cat += T(src_rate * prob_src);
673 }
else if (s < L.signalremdist.size() && !L.signalremdist[s].empty()) {
674 c.batch.emplace_back(T(src_rate * prob_src), s);
676 c.lam_neg += T(src_rate * prob_src);
680 if (!(src_rate > zero))
continue;
682 std::pair<Matrix<T>, Matrix<T>> sm = proc_map(L, isrc, s);
683 Matrix<T> S0 = sm.first, S1 = sm.second;
684 if (prob_src < one) {
685 const T keep = prob_src, drop = T(one - prob_src);
686 for (std::size_t i = 0; i < S0.rows(); ++i)
687 for (std::size_t j = 0; j < S0.cols(); ++j) S0(i, j) += T(drop * S1(i, j));
688 for (std::size_t i = 0; i < S1.rows(); ++i)
689 for (std::size_t j = 0; j < S1.cols(); ++j) S1(i, j) = T(keep * S1(i, j));
692 Da0 =
krons(Da0, S0);
693 Da1 =
krons(Da1, S1);
706Matrix<T> build_local_rates(
const qn::NetworkStruct<T>& L,
const RcatComponent<T>& c,
707 const std::vector<std::size_t>& sink_nodes) {
708 const std::size_t K = L.nclasses;
709 const std::size_t mph = c.mph, nlev = c.nlev;
710 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
711 Matrix<T> Lm(c.N, c.N, zero);
717 add_block(Lm, 0, 0, mph,
kron(c.Da0,
eye<T>(c.ns)), one);
718 const Matrix<T> Lbusy =
krons(c.Da0, c.Ds0);
719 for (std::size_t n = 1; n < nlev; ++n) add_block(Lm, n, n, mph, Lbusy, one);
722 const Matrix<T> Aup =
kron(c.Da1,
eye<T>(c.ns));
723 for (std::size_t n = 0; n + 1 < nlev; ++n) add_block(Lm, n, n + 1, mph, Aup, one);
727 add_block(Lm, nlev - 1, nlev - 1, mph, Aup, one);
730 if (c.lam_cat > zero)
731 for (std::size_t n = 1; n < nlev; ++n) add_identity(Lm, n, 0, mph, c.lam_cat);
732 for (
const std::pair<T, std::size_t>& ba : c.batch)
733 add_batch_removal(Lm, L.signalremdist[ba.second], ba.first, nlev, mph);
735 if (c.lam_neg > zero)
736 for (std::size_t n = 1; n < nlev; ++n) add_identity(Lm, n, n - 1, mph, c.lam_neg);
740 if (L.disabled[c.ist][c.r])
return Lm;
741 const T mu = L.rates(c.ist, c.r);
742 if (!(mu > zero))
return Lm;
748 const std::size_t node = L.node_of_station(c.ist + 1);
751 for (std::size_t jsnk : sink_nodes)
752 for (std::size_t s = 0; s < K; ++s) {
753 const std::size_t from = (node - 1) * K + c.r, to = (jsnk - 1) * K + s;
754 if (from < L.rtnodes.rows() && to < L.rtnodes.cols())
755 prob_sink += L.rtnodes(from, to);
759 prob_sink += c.prob_void;
760 const T prob_self = L.rt(c.ist * K + c.r, c.ist * K + c.r);
762 if (prob_sink > zero)
763 for (std::size_t n = 1; n < nlev; ++n) add_block(Lm, n, n - 1, mph, c.Dsvc, prob_sink);
767 if (prob_self > zero)
768 for (std::size_t n = 1; n < nlev; ++n) add_block(Lm, n, n, mph, c.Dsvc, prob_self);
774RcatModel<T> build_rcat(
const qn::NetworkStruct<T>& L, std::size_t max_states) {
776 const std::size_t M = L.nstations, K = L.nclasses;
777 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
779 m.process_map.assign(M, std::vector<std::size_t>(K, 0));
781 std::vector<std::size_t> source_stations, queue_stations, sink_nodes;
782 for (std::size_t i = 0; i < L.nof_nodes(); ++i)
783 if (L.nodes[i].nodetype == qn::NodeType::Sink) sink_nodes.push_back(i + 1);
784 for (std::size_t ist = 0; ist < M; ++ist) {
791 if (ty == qn::NodeType::Source) source_stations.push_back(ist);
792 else if (ty != qn::NodeType::Sink) queue_stations.push_back(ist);
797 std::size_t pidx = 0;
798 for (std::size_t ist : queue_stations)
799 for (std::size_t r = 0; r < K; ++r) {
800 if (is_signal_class(L, r) || L.disabled[ist][r])
continue;
801 if (!(L.rates(ist, r) > zero))
continue;
802 m.process_map[ist][r] = ++pidx;
804 m.num_processes = pidx;
805 if (m.num_processes == 0)
return m;
809 const std::vector<double> njobs = L.njobs();
810 std::vector<RcatComponent<T>> comp(m.num_processes);
811 m.N.assign(m.num_processes, 0);
812 m.nlev.assign(m.num_processes, 0);
813 m.mph.assign(m.num_processes, 1);
814 m.level.assign(m.num_processes, std::vector<std::size_t>());
815 m.svcrate.assign(m.num_processes, std::vector<T>());
816 m.svcdown.assign(m.num_processes, std::vector<T>());
817 m.is_open_proc.assign(m.num_processes,
false);
818 for (std::size_t ist : queue_stations)
819 for (std::size_t r = 0; r < K; ++r) {
820 const std::size_t p = m.process_map[ist][r];
821 if (p == 0 || m.N[p - 1] != 0)
continue;
822 RcatComponent<T>& c = comp[p - 1];
825 const std::pair<Matrix<T>, Matrix<T>> svc = proc_map(L, ist, r);
828 arrival_map(L, c, source_stations);
832 if (std::isfinite(njobs[r])) {
833 c.nlev =
static_cast<std::size_t
>(njobs[r]) + 1;
836 m.is_open_proc[p - 1] =
true;
840 c.N = c.nlev * c.mph;
843 m.nlev[p - 1] = c.nlev;
844 m.mph[p - 1] = c.mph;
845 m.level[p - 1].assign(c.N, 0);
846 for (std::size_t n = 0; n < c.nlev; ++n)
847 for (std::size_t j = 0; j < c.mph; ++j) m.level[p - 1][n * c.mph + j] = n;
848 m.svcdown[p - 1].assign(c.mph, zero);
849 for (std::size_t i = 0; i < c.mph; ++i) {
851 for (std::size_t j = 0; j < c.mph; ++j) acc += c.Dsvc(i, j);
852 m.svcdown[p - 1][i] = acc;
854 m.svcrate[p - 1].assign(c.N, zero);
855 for (std::size_t n = 1; n < c.nlev; ++n)
856 for (std::size_t j = 0; j < c.mph; ++j)
857 m.svcrate[p - 1][n * c.mph + j] = m.svcdown[p - 1][j];
860 for (std::size_t ist : queue_stations)
861 for (std::size_t r = 0; r < K; ++r) {
862 if (m.process_map[ist][r] == 0)
continue;
863 const bool is_removal =
864 is_signal_class(L, r) && (signal_type_of(L, r) == SignalType::NEGATIVE ||
865 signal_type_of(L, r) == SignalType::CATASTROPHE);
866 const bool is_cat = is_removal && signal_type_of(L, r) == SignalType::CATASTROPHE;
867 const bool has_dist =
868 is_removal && r < L.signalremdist.size() && !L.signalremdist[r].empty();
869 for (std::size_t jst : queue_stations)
870 for (std::size_t s = 0; s < K; ++s) {
871 if (m.process_map[jst][s] == 0)
continue;
872 if (ist == jst && r == s)
continue;
873 const T pr = L.rt(ist * K + r, jst * K + s);
874 if (!(pr > zero))
continue;
876 a.from_station = ist;
880 a.prob = num_traits<T>::to_double(pr);
881 a.is_negative = is_removal;
882 a.is_catastrophe = is_cat;
883 a.has_removal_dist = has_dist;
885 m.actions.push_back(a);
894 for (std::size_t jst : queue_stations)
895 for (std::size_t s = 0; s < K; ++s) {
896 if (!is_signal_class(L, s))
continue;
897 const T pr = L.rt(ist * K + r, jst * K + s);
898 if (!(pr > zero))
continue;
900 const bool s_cat = st == SignalType::CATASTROPHE;
901 if (!s_cat && st != SignalType::NEGATIVE)
903 "solver_ag: class '" + L.classes[r].name +
"' switches into signal class '" +
904 L.classes[s].name +
"' at station '" + L.stations[jst].name +
905 "', but only a NEGATIVE or CATASTROPHE signal has a G-network effect RCAT "
907 std::vector<std::size_t> targets;
908 for (std::size_t t = 0; t < K; ++t)
909 if (m.process_map[jst][t] != 0) targets.push_back(t);
910 if (targets.size() > 1)
912 "solver_ag: signal class '" + L.classes[s].name +
913 "', produced by departures of class '" + L.classes[r].name +
914 "', reaches station '" + L.stations[jst].name +
"', which serves " +
915 std::to_string(targets.size()) +
916 " positive classes. A departure synchronizes with ONE component in RCAT, "
917 "so the removal cannot be split across classes");
918 if (targets.empty()) {
920 comp[m.process_map[ist][r] - 1].prob_void += pr;
924 a.from_station = ist;
927 a.to_class = targets[0];
928 a.prob = num_traits<T>::to_double(pr);
929 a.is_negative =
true;
930 a.is_catastrophe = s_cat;
932 !s_cat && s < L.signalremdist.size() && !L.signalremdist[s].empty();
934 m.actions.push_back(a);
938 m.L.reserve(m.num_processes);
939 for (std::size_t p = 0; p < m.num_processes; ++p)
940 m.L.push_back(build_local_rates(L, comp[p], sink_nodes));
942 const std::size_t A = m.actions.size();
947 for (
const RcatAction& a : m.actions) {
948 const std::size_t pa = m.process_map[a.from_station][a.from_class] - 1;
949 const std::size_t pp = m.process_map[a.to_station][a.to_class] - 1;
953 const RcatComponent<T>& ca = comp[pa];
954 const T prob = num_traits<T>::from_double(a.prob);
955 Matrix<T> Am(ca.N, ca.N, zero);
956 for (std::size_t n = 1; n < ca.nlev; ++n)
957 add_block(Am, n, n - 1, ca.mph, ca.Dsvc, prob);
964 if (std::isfinite(njobs[a.from_class])) {
965 const std::size_t off = blk(ca.nlev - 1, ca.mph);
966 for (std::size_t i = 0; i < ca.mph; ++i)
967 Am(off + i, off + i) += T(m.svcdown[pa][i] * prob);
971 const RcatComponent<T>& cp = comp[pp];
972 Matrix<T> Bm(cp.N, cp.N, zero);
974 if (a.is_catastrophe) {
975 for (std::size_t n = 0; n < cp.nlev; ++n) set_identity(Bm, n, 0, cp.mph);
976 }
else if (a.has_removal_dist) {
977 add_batch_removal(Bm, L.signalremdist[a.removal_class], one, cp.nlev, cp.mph);
978 set_identity(Bm, 0, 0, cp.mph);
980 set_identity(Bm, 0, 0, cp.mph);
981 for (std::size_t n = 1; n < cp.nlev; ++n) set_identity(Bm, n, n - 1, cp.mph);
988 for (std::size_t n = 0; n + 1 < cp.nlev; ++n) set_identity(Bm, n, n + 1, cp.mph);
989 set_identity(Bm, cp.nlev - 1, cp.nlev - 1, cp.mph);
998Matrix<T> assemble_generator(
const RcatModel<T>& m,
const std::vector<T>& x, std::size_t k) {
999 Matrix<T> Qk = m.L[k];
1000 for (std::size_t c = 0; c < m.actions.size(); ++c) {
1001 if (m.psv[c] == k) {
1002 for (std::size_t i = 0; i < Qk.rows(); ++i)
1003 for (std::size_t j = 0; j < Qk.cols(); ++j) Qk(i, j) += T(x[c] * m.Pb[c](i, j));
1004 }
else if (m.act[c] == k) {
1005 for (std::size_t i = 0; i < Qk.rows(); ++i)
1006 for (std::size_t j = 0; j < Qk.cols(); ++j) Qk(i, j) += m.Aa[c](i, j);
1017Matrix<double> ag_as_double(
const Matrix<T>& m) {
1018 Matrix<double> out(m.rows(), m.cols(), 0.0);
1019 for (std::size_t i = 0; i < m.rows(); ++i)
1020 for (std::size_t j = 0; j < m.cols(); ++j)
1021 out(i, j) = num_traits<T>::to_double(m(i, j));
1031AgWirePayload wire_payload(
const RcatModel<T>& m, std::size_t k) {
1033 a.k =
static_cast<int>(k);
1034 a.n =
static_cast<int>(m.N[k]);
1035 a.mph =
static_cast<int>(m.mph[k]);
1036 a.nlev =
static_cast<int>(m.nlev[k]);
1037 a.level.assign(m.level[k].begin(), m.level[k].end());
1039 for (std::size_t c = 0; c < m.actions.size(); ++c) {
1040 if (m.psv[c] == k) {
1041 a.passive_c.push_back(
static_cast<int>(c));
1042 a.passive_m.push_back(
ag_triplets(ag_as_double(m.Pb[c])));
1043 }
else if (m.act[c] == k) {
1044 a.active_c.push_back(
static_cast<int>(c));
1045 a.active_m.push_back(
ag_triplets(ag_as_double(m.Aa[c])));
1061void compute_equilibrium(
const RcatModel<T>& m,
const std::vector<T>& x,
1062 std::vector<std::vector<T>>& pi, std::vector<Matrix<T>>& Q,
1063 const AgOptions* opt =
nullptr, AgWorkerPool* pool =
nullptr) {
1064 pi.assign(m.num_processes, std::vector<T>());
1065 Q.assign(m.num_processes, Matrix<T>());
1067 auto gen = [&](std::size_t k) {
return assemble_generator(m, x, k); };
1068 auto sol = [&](
const Matrix<T>& Qk, std::size_t k) {
1069 return solve_component(Qk, m.mph[k], m.nlev[k], m.level[k]);
1072 const std::string mode = (opt ==
nullptr) ? std::string(
exec_serial()) : opt->exec;
1076 auto payload = [&](std::size_t k) {
return wire_payload(m, k); };
1089void compute_equilibrium_qbd(
const RcatModel<T>& m,
const std::vector<T>& x,
1090 std::vector<std::vector<T>>& pi, std::vector<Matrix<T>>& Q,
1091 std::vector<double>& rho_proc, std::vector<bool>& is_geom,
1092 std::vector<QbdTail<T>>& geom_data) {
1093 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
1094 pi.assign(m.num_processes, std::vector<T>());
1095 Q.assign(m.num_processes, Matrix<T>());
1096 rho_proc.assign(m.num_processes, 0.0);
1097 is_geom.assign(m.num_processes,
false);
1098 geom_data.assign(m.num_processes, QbdTail<T>());
1100 for (std::size_t k = 0; k < m.num_processes; ++k) {
1101 const std::size_t Nk = m.N[k];
1102 Matrix<T> Off = m.L[k];
1103 for (std::size_t i = 0; i < Nk; ++i) Off(i, i) = zero;
1104 for (std::size_t c = 0; c < m.actions.size(); ++c) {
1105 if (m.psv[c] == k) {
1106 for (std::size_t i = 0; i < Nk; ++i)
1107 for (std::size_t j = 0; j < Nk; ++j) Off(i, j) += T(x[c] * m.Pb[c](i, j));
1108 }
else if (m.act[c] == k) {
1109 for (std::size_t i = 0; i < Nk; ++i)
1110 for (std::size_t j = 0; j < Nk; ++j) Off(i, j) += m.Aa[c](i, j);
1113 for (std::size_t i = 0; i < Nk; ++i) Off(i, i) = zero;
1116 const std::size_t mph = m.mph[k], nlev = m.nlev[k];
1117 bool solved_geom =
false;
1118 if (m.is_open_proc[k] && nlev >= 5) {
1122 const std::size_t s0 = Nk - 2;
1123 const double f = num_traits<T>::to_double(Off(s0, s0 + 1));
1124 const double b = num_traits<T>::to_double(Off(s0, s0 - 1));
1125 const double g0 = num_traits<T>::to_double(Off(s0, 0));
1129 double inter_down = 0.0;
1130 for (std::size_t j = 1; j + 3 < Nk; ++j)
1131 inter_down += num_traits<T>::to_double(Off(s0, j));
1132 if (inter_down <= 1e-11 && f > 0.0) {
1133 const double rho = qbd_scalar_rho(f, b, g0);
1134 if (std::isfinite(rho) && rho > 0.0 && rho < 1.0 - 1e-12) {
1137 pi[k].assign(Nk, zero);
1138 const T rt = num_traits<T>::from_double(rho);
1139 T acc = T(one - rt);
1140 for (std::size_t n = 0; n < Nk; ++n) {
1147 }
else if (is_block_tridiagonal(Q[k], m.level[k])) {
1151 const std::size_t s0 = nlev - 2;
1152 const Matrix<T> A0 = level_block(Q[k], s0, s0 + 1, mph);
1153 const Matrix<T> A1 = level_block(Q[k], s0, s0, mph);
1154 const Matrix<T> A2 = level_block(Q[k], s0, s0 - 1, mph);
1155 bool any_up =
false;
1156 for (std::size_t i = 0; i < mph && !any_up; ++i) {
1158 for (std::size_t j = 0; j < mph; ++j) rs += A0(i, j);
1159 if (rs > zero) any_up =
true;
1162 const QbdTail<T> g = qbd_matrix_tail(Q[k], A0, A1, A2, mph);
1166 pi[k] = qbd_tail_expand(g, nlev, mph);
1172 if (!solved_geom) pi[k] = solve_component(Q[k], mph, nlev, m.level[k]);
1178double block_norm(
const std::vector<std::vector<T>>& a,
const std::vector<std::vector<T>>& b) {
1180 for (std::size_t k = 0; k < a.size() && k < b.size(); ++k) {
1181 const std::size_t n = std::min(a[k].size(), b[k].size());
1183 for (std::size_t i = 0; i < n; ++i)
1184 s += std::fabs(num_traits<T>::to_double(T(a[k][i] - b[k][i])));
1192mva::MvaSolution<T> rcat_metrics(
const qn::NetworkStruct<T>& L,
const RcatModel<T>& m,
1193 const std::vector<std::vector<T>>& pi,
1194 const std::vector<double>& rho_proc,
1195 const std::vector<bool>& is_geom,
1196 const std::vector<QbdTail<T>>& geom_data) {
1197 const std::size_t M = L.nstations, K = L.nclasses;
1198 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
1199 mva::MvaSolution<T> s;
1204 s.C.assign(K, zero);
1205 s.X.assign(K, zero);
1207 for (std::size_t ist = 0; ist < M; ++ist)
1208 for (std::size_t r = 0; r < K; ++r) {
1209 const std::size_t p = m.process_map.empty() ? 0 : m.process_map[ist][r];
1210 if (p == 0 || p > pi.size() || pi[p - 1].empty())
continue;
1211 const bool disabled = L.disabled[ist][r];
1212 const std::size_t mph = m.mph[p - 1];
1213 if (!is_geom.empty() && p <= is_geom.size() && is_geom[p - 1]) {
1218 const T mu = disabled ? zero : L.rates(ist, r);
1219 const T rho = num_traits<T>::from_double(rho_proc[p - 1]);
1220 s.Q(ist, r) = T(rho / (one - rho));
1222 if (mu > zero) s.Tp(ist, r) = T(mu * rho);
1225 const QbdTail<T>& g = geom_data[p - 1];
1226 T busy = zero, tput = zero;
1227 for (std::size_t i = 0; i < mph; ++i) {
1229 tput += T(g.busy[i] * m.svcdown[p - 1][i]);
1231 s.Q(ist, r) = g.qlen;
1233 s.Tp(ist, r) = tput;
1236 const std::vector<T>& v = pi[p - 1];
1237 T q = zero, tput = zero;
1238 for (std::size_t n = 0; n < v.size(); ++n) {
1239 q += T(num_traits<T>::from_int(
static_cast<long>(m.level[p - 1][n])) * v[n]);
1243 tput += T(m.svcrate[p - 1][n] * v[n]);
1247 for (std::size_t i = 0; i < mph; ++i) level0 += v[i];
1248 s.U(ist, r) = T(one - level0);
1249 s.Tp(ist, r) = tput;
1253 for (std::size_t ist = 0; ist < M; ++ist)
1254 for (std::size_t r = 0; r < K; ++r)
1255 if (s.Tp(ist, r) > zero) s.R(ist, r) = T(s.Q(ist, r) / s.Tp(ist, r));
1267 for (std::size_t ist = 0; ist < M; ++ist) {
1268 if (L.stations[ist].nodetype != qn::NodeType::Source)
continue;
1269 for (std::size_t r = 0; r < K; ++r)
1270 s.Tp(ist, r) = L.disabled[ist][r] ? zero : L.rates(ist, r);
1273 const std::vector<double> njobs = L.njobs();
1274 for (std::size_t r = 0; r < K; ++r) {
1275 if (!std::isfinite(njobs[r])) {
1276 for (std::size_t ist = 0; ist < M; ++ist) {
1278 if (L.stations[ist].nodetype == qn::NodeType::Source) {
1279 s.X[r] = L.disabled[ist][r] ? zero : L.rates(ist, r);
1284 for (std::size_t ist = 0; ist < M; ++ist) c += s.R(ist, r);
1287 const std::size_t refst = L.classes[r].refstat;
1288 if (refst > 0 && refst <= M) {
1289 s.X[r] = s.Tp(refst - 1, r);
1291 s.C[r] = T(num_traits<T>::from_double(njobs[r]) / s.X[r]);
1363 std::size_t max_states = 0) {
1364 if (max_states == 0) max_states =
opt.max_states;
1367 "solver_ag: the RCAT analyzers stop on a tolerance and the matrix-geometric "
1368 "variant takes a square root, so they need transcendental arithmetic; rerun this "
1369 "model with --arith double or --arith real");
1371 using namespace ag_detail;
1375 std::string method =
opt.method;
1376 if (method ==
"default") method =
"inap";
1377 if (method ==
"exact") {
1382 if (method !=
"inap" && method !=
"inaprc" && method !=
"inapinf")
1386 out.actualmethod = method;
1395 "solver_ag: the 'cluster' execution backend does not carry the 'inapinf' agent "
1396 "solve (the matrix-geometric tail of an open agent runs on the coordinator "
1397 "only); use exec 'serial' or 'parallel' with 'inapinf', or method 'inap'/"
1398 "'inaprc' with 'cluster'");
1405 const std::string hint = (
opt.exec ==
"threads")
1406 ?
"; 'threads' was renamed to 'parallel' (alias 'para')" :
"";
1407 throw InputError(
"solver_ag: unknown execution backend '" +
opt.exec +
1408 "'; use 'serial', 'parallel' (alias 'para') or 'cluster'" + hint);
1411 std::unique_ptr<AgWorkerPool> pool;
1416 const RcatModel<T> m = build_rcat(L, max_states);
1418 if (m.num_processes == 0) {
1425 out.sol.C.assign(K, zero);
1426 out.sol.X.assign(K, zero);
1427 out.sol.method =
opt.method;
1432 const std::size_t A = m.actions.size();
1433 std::vector<std::vector<T>> pi;
1434 std::vector<Matrix<T>> Q;
1444 pi.assign(m.num_processes, std::vector<T>());
1445 for (std::size_t k = 0; k < m.num_processes; ++k) {
1447 pi[k] = solve_component(Qk, m.mph[k], m.nlev[k], m.level[k]);
1449 out.sol = rcat_metrics(L, m, pi, std::vector<double>(), std::vector<bool>(),
1450 std::vector<QbdTail<T>>());
1451 out.sol.method =
opt.method;
1469 std::vector<bool> not_birth_death(m.num_processes,
false);
1470 for (std::size_t k = 0; k < m.num_processes; ++k) {
1471 if (m.mph[k] > 1) not_birth_death[k] =
true;
1472 for (std::size_t n = 0; n < m.N[k]; ++n)
1473 for (std::size_t j = 0; j < m.N[k]; ++j) {
1474 const std::size_t
ln = m.level[k][n], lj = m.level[k][j];
1475 const std::size_t d = (
ln > lj) ? (
ln - lj) : (lj -
ln);
1476 if (d > 1 && m.L[k](n, j) > zero) not_birth_death[k] =
true;
1482 std::vector<std::vector<std::size_t>> active_cols(A);
1483 for (std::size_t a = 0; a < A; ++a)
1484 for (std::size_t j = 0; j < m.Aa[a].cols(); ++j) {
1486 for (std::size_t i = 0; i < m.Aa[a].rows(); ++i) colsum += m.Aa[a](i, j);
1487 if (colsum > zero) active_cols[a].push_back(j);
1491 std::vector<T> x(A, zero);
1492 for (std::size_t a = 0; a < A; ++a)
1494 static_cast<double>(A + 1));
1496 std::vector<double> rho_proc;
1497 std::vector<bool> is_geom;
1498 std::vector<QbdTail<T>> geom_data;
1499 const bool qbd = (method ==
"inapinf");
1500 if (qbd) compute_equilibrium_qbd(m, x, pi, Q, rho_proc, is_geom, geom_data);
1501 else compute_equilibrium(m, x, pi, Q, &
opt, pool.get());
1503 std::vector<std::vector<T>> a_rowsum(A);
1504 for (std::size_t a = 0; a < A; ++a) {
1505 a_rowsum[a].assign(m.Aa[a].rows(), zero);
1506 for (std::size_t i = 0; i < m.Aa[a].rows(); ++i)
1507 for (std::size_t j = 0; j < m.Aa[a].cols(); ++j) a_rowsum[a][i] += m.Aa[a](i, j);
1514 const double tol =
opt.tol;
1515 std::size_t iter = 0;
1516 bool converged =
false;
1517 for (iter = 1; iter <= static_cast<std::size_t>(
opt.iter_max); ++iter) {
1518 const std::vector<std::vector<T>> pi_ref = pi;
1519 for (std::size_t a = 0; a < A; ++a) {
1520 const std::size_t k = m.act[a];
1523 if (m.mph[k] == 1) {
1526 const std::size_t idx = std::min<std::size_t>(1, m.N[k] - 1);
1533 for (std::size_t i = 0; i < m.mph[k]; ++i)
1534 acc += T(geom_data[k].busy[i] * a_rowsum[a][m.mph[k] + i]);
1539 for (std::size_t i = 0; i < pi[k].size() && i < a_rowsum[a].size(); ++i)
1540 acc += T(pi[k][i] * a_rowsum[a][i]);
1545 const std::vector<T> v =
vecmul(pi[k], m.Aa[a]);
1546 if (method ==
"inaprc" || not_birth_death[k]) {
1549 for (std::size_t j = 0; j < v.size(); ++j)
sum += v[j];
1550 if (
sum > zero) x[a] =
sum;
1558 std::size_t cnt = 0;
1559 for (std::size_t j : active_cols[a])
1560 if (pi[k][j] > zero && v[j] > zero) {
1561 sum += T(v[j] / pi[k][j]);
1568 if (qbd) compute_equilibrium_qbd(m, x, pi, Q, rho_proc, is_geom, geom_data);
1569 else compute_equilibrium(m, x, pi, Q, &
opt, pool.get());
1571 if (block_norm<T>(pi, pi_ref) < tol) {
1579 if (!converged) iter =
static_cast<std::size_t
>(
opt.iter_max) + 1;
1583 for (std::size_t a = 0; a < A; ++a) {
1584 const std::size_t k = m.act[a];
1586 for (std::size_t j = 0; j < m.N[k]; ++j) {
1587 T acc = T(x[a] * pi[k][j]);
1588 for (std::size_t i = 0; i < m.N[k]; ++i) acc -= T(pi[k][i] * m.Aa[a](i, j));
1592 res = std::max(res, std::sqrt(s));
1594 out.rcat_residual = res;
1597 out.sol = rcat_metrics(L, m, pi, rho_proc, is_geom, geom_data);
1598 out.sol.method =
opt.method;
1599 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)
The (D0,D1) pair that reaches sn.proc.
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.
Conservation laws of a layered queueing network, enumerated from its structure.
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,...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
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.