5#ifndef LINE_API_FJ_FJ_CODES_H
6#define LINE_API_FJ_FJ_CODES_H
147inline std::vector<double> rdivide_row(
const std::vector<double>& a,
const Matrix<double>& B) {
155 for (std::size_t i = 0; i < A.
rows(); ++i)
156 for (std::size_t j = 0; j < A.
cols(); ++j) {
157 const double x = std::fabs(A(i, j) - B(i, j));
164inline double inf_norm(
const Matrix<double>& A) {
166 for (std::size_t i = 0; i < A.rows(); ++i) {
168 for (std::size_t j = 0; j < A.cols(); ++j) s += std::fabs(A(i, j));
169 if (s > best) best = s;
174inline Matrix<double> madd(
const Matrix<double>& A,
const Matrix<double>& B) {
175 Matrix<double> C(A.rows(), A.cols());
176 for (std::size_t i = 0; i < A.rows(); ++i)
177 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = A(i, j) + B(i, j);
181inline Matrix<double> msub(
const Matrix<double>& A,
const Matrix<double>& B) {
182 Matrix<double> C(A.rows(), A.cols());
183 for (std::size_t i = 0; i < A.rows(); ++i)
184 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = A(i, j) - B(i, j);
189inline Matrix<double> block(
const Matrix<double>& A, std::size_t r0, std::size_t r1,
190 std::size_t c0, std::size_t c1) {
191 Matrix<double> B(r1 - r0, c1 - c0, 0.0);
192 for (std::size_t i = r0; i < r1; ++i)
193 for (std::size_t j = c0; j < c1; ++j) B(i - r0, j - c0) = A(i, j);
217 const std::size_t m = S.
rows();
218 const std::size_t ma = D0.
rows();
219 if (ma == 0 || m % ma != 0)
220 throw InputError(
"fj_compute_t_nare: the phase space is not a multiple of the arrival "
222 const std::size_t ms = m / ma;
230 for (std::size_t i = 0; i < m; ++i)
231 for (std::size_t j = 0; j < m; ++j) {
232 H(i, j) = ImsD0(i, j);
233 H(i, m + j) = ImsD1(i, j);
234 H(m + i, j) = -AjIma(i, j);
235 H(m + i, m + j) = -S(i, j);
241 const std::size_t n = 2 * m;
242 std::vector<double> re(n, 0.0);
243 std::vector<int> blk(n, 1);
244 for (std::size_t i = 0; i < n;) {
245 const bool pair = (i + 1 < n) && sc.
T(i + 1, i) != 0.0;
247 const double r = 0.5 * (sc.
T(i, i) + sc.
T(i + 1, i + 1));
259 std::vector<std::size_t> order(n);
260 for (std::size_t i = 0; i < n; ++i) order[i] = i;
261 std::stable_sort(order.begin(), order.end(),
262 [&re](std::size_t a, std::size_t b) { return re[a] < re[b]; });
263 std::vector<double> key(n, 0.0);
264 for (std::size_t i = 0; i < m; ++i) key[order[i]] = 1.0;
265 for (std::size_t i = 0; i + 1 < n; ++i)
266 if (blk[i] == 2 && key[i] != key[i + 1])
268 "fj_compute_t_nare: the m eigenvalues of smallest real part split a complex "
269 "conjugate pair, so the stable invariant subspace of the Riccati pencil is not "
270 "real and the T matrix of this model is not defined");
277 if (residual !=
nullptr)
278 *residual = fjdetail::inf_norm(
279 fjdetail::madd(fjdetail::madd(
matmul(T, X),
matmul(X, ImsD0)), AjIma));
296 const std::size_t d0 = arrival.
lambda0.rows();
306 const std::size_t ms = sa.
S.
rows();
307 const std::size_t m = ms * d0;
317 const std::size_t max_iter = 500;
319 while (fjdetail::max_abs_diff(Told, Tnew) > 1e-10) {
322 "fj_compute_t: the Sylvester iteration of computeT.m did not reach 1e-10 in " +
323 std::to_string(max_iter) +
324 " steps; solve this model with the NARE route (config.fj_tmode = 'NARE')");
333 fjdetail::madd(fjdetail::madd(
matmul(out.
T, L),
matmul(L, ID0)), Im));
340 for (std::size_t i = 0; i < A_jump_Arr.
rows(); ++i) {
342 for (std::size_t j = 0; j < A_jump_Arr.
cols(); ++j) s += A_jump_Arr(i, j);
360 const std::size_t nd = pi0mat.
rows();
361 if (pi0mat.
cols() != nd || T.
rows() != nd || T.
cols() != nd)
362 throw InputError(
"fj_boundary_solve: the boundary blocks are not conformable");
365 for (std::size_t i = 0; i < nd; ++i) {
366 for (std::size_t j = 0; j < nd; ++j) M(i, j) = pi0mat(i, j) - (i == j ? 1.0 : 0.0);
368 for (std::size_t j = 0; j < nd; ++j) s += iT(i, j);
371 std::vector<double> b(nd + 1, 0.0);
393 const std::size_t
da = arrival.
lambda0.rows();
396 if (service.
choice == 1) {
397 const std::size_t ms = S.
cols();
402 "fj_compute_pi: the not-all-busy space and the all-busy space have different "
403 "dimensions, which the exponential branch of computePi.m assumes they do not");
411 for (
double v : out.
pi0) sp += v;
412 const std::vector<double> pm =
vecmul(out.
pi0, pi0mat);
414 for (
double v : pm) spm += v;
420 const std::size_t dtmat = T.
rows() / arrival.
lambda0.cols();
421 const std::size_t dsexp = srk.
Se.
rows();
422 const std::size_t dnb = dsexp - dtmat;
424 const Matrix<double> Sedash = fjdetail::block(srk.
Se, dtmat, dsexp, dtmat, dsexp);
425 const Matrix<double> Rbusy = fjdetail::block(srk.
R0, dtmat, dsexp, 0, dsexp);
427 for (std::size_t i = 0; i < dnb; ++i) Iidle_small(dtmat + i, i) = 1.0;
436 for (std::size_t i = 0; i < M.
rows(); ++i)
437 for (std::size_t j = 0; j < M.
cols(); ++j) N(i, j) = -M(i, j);
449 for (
double v : out.
pi0) sp += v;
452 std::vector<double> row =
vecmul(out.
pi0, Igral);
453 for (
double& v : row) v = -v;
455 row = fjdetail::rdivide_row(row, Qidle);
458 for (
double v : row) sr += v;
466 const std::vector<double>& sum_Ajump) {
467 const std::size_t ds = phi.size();
469 out.
alfa = fjdetail::rdivide_row(pi0, T);
470 for (
double& v : out.
alfa) v = -v;
473 for (std::size_t i = 0; i < ds; ++i) ap += out.
alfa[i] * phi[i];
474 std::vector<double> rhos(ds, 0.0);
475 for (std::size_t i = 0; i < ds; ++i) rhos[i] = phi[i] * out.
alfa[i] / ap;
478 for (
double v : pi0) sp += v;
480 for (std::size_t i = 0; i < ds; ++i) asum += out.
alfa[i] * sum_Ajump[i];
481 const double En0 = asum / sp;
483 out.
prob_wait = (En0 - 1.0) / (En0 - 1.0 + En1);
488 for (std::size_t i = 0; i < ds; ++i)
489 for (std::size_t j = 0; j < ds; ++j)
508 const std::vector<double>& pers) {
510 const std::size_t m = A.
cols();
511 if (vec.size() != m || A.
rows() != m)
512 throw InputError(
"fj_return_per: the phase-type pair is not conformable");
514 const std::vector<double> negvA = [&] {
516 for (
double& x : v) x = -x;
520 for (
double v : negvA) meanRT += v;
522 throw NumericError(
"fj_return_per: the phase-type law has a non-positive mean, so its "
523 "percentiles are not defined");
526 for (std::size_t i = 0; i < m; ++i) c = std::max(c, -A(i, i));
528 throw NumericError(
"fj_return_per: the phase-type generator has no negative diagonal");
530 for (std::size_t i = 0; i < m; ++i)
531 for (std::size_t j = 0; j < m; ++j) P(i, j) = A(i, j) / c + (i == j ? 1.0 : 0.0);
534 for (std::size_t i = 0; i < m; ++i)
535 for (std::size_t j = 0; j < m; ++j) ImP(i, j) = (i == j ? 1.0 : 0.0) - P(i, j);
538 for (
double v : vImP) M += v;
541 for (
double v : vec) a0 += v;
543 std::vector<double> ak;
544 std::vector<double> vP(m, 0.0);
545 for (std::size_t i = 0; i < m; ++i) {
547 for (std::size_t j = 0; j < m; ++j) s += P(i, j);
550 while (std::fabs(sum_a - M) >= 1e-10) {
552 for (std::size_t i = 0; i < m; ++i) t += vec[i] * vP[i];
556 if (ak.size() > 2000000)
557 throw NumericError(
"fj_return_per: the uniformized Poisson series did not reach the "
558 "total absorption mass, so the percentile scan cannot terminate");
560 const std::size_t K1 = ak.size();
563 const auto cdf_at = [&](
double t) {
564 double pM = exp(-c * t);
566 for (std::size_t k = 1; k <= K1; ++k) {
567 pM = c * t * pM /
static_cast<double>(k);
573 std::vector<double> out(pers.size(), 0.0);
574 for (std::size_t p = 0; p < pers.size(); ++p) {
575 if (pers[p] < 1.0 - a0)
continue;
576 double MaxTime = 3.0 * meanRT;
578 if (cdf_at(MaxTime) < pers[p]) {
579 MaxTime += 0.5 * meanRT;
585 const std::size_t steps =
static_cast<std::size_t
>(MaxTime / 0.001);
586 for (std::size_t k = 0; k <= steps; ++k) {
587 const double t = MaxTime -
static_cast<double>(k) * 0.001;
588 if (cdf_at(t) < pers[p]) {
596 "fj_return_per: the downward scan reached t = 0 without the response-time CDF "
597 "falling below " + std::to_string(pers[p]) +
598 ", so this percentile has no bracket; the reference silently reports the "
599 "previous percentile here");
612 const std::vector<double>& pers) {
618 std::vector<mam::PhService<double>> svc(1);
619 svc[0].sigma = service.
tau_st;
620 svc[0].S = service.
ST;
624 throw NumericError(
"fj_return_rt1: MMAPPH1FCFS returned no sojourn-time law");
652 const std::vector<double>& pers, std::size_t C,
FjTMode mode) {
656 const std::size_t n = ct.
T.
rows();
658 std::vector<double> phi(n, 0.0);
659 for (std::size_t i = 0; i < n; ++i) {
661 for (std::size_t j = 0; j < n; ++j) s += ct.
T(i, j) - ct.
S_Arr(i, j);
670 std::vector<double> pi0n = pi.
pi0;
672 for (
double v : pi0n) sp += v;
673 for (
double& v : pi0n) v /= sp;
675 const std::size_t dim = arrival.
ma * gs.
newdim;
677 const std::size_t dim_service = dim + dim_notbusy;
678 const std::size_t Tr = ST.
rows();
680 if (Tr != dim_service)
681 throw NumericError(
"fj_return_rt2: the tagged-job service space and the phase blocks "
684 std::vector<double> notbusy_start(dim_service, 0.0);
685 for (std::size_t i = 0; i < dim; ++i)
686 notbusy_start[i] = (1.0 - w.
prob_wait) * pi0n[i];
691 std::vector<double> busy_start(dim_service, 0.0);
693 const std::vector<double> aTS =
vecmul(w.
alfa, TS);
694 if (aTS.size() != dim)
695 throw NumericError(
"fj_return_rt2: the all-busy phase space and the tagged job's "
696 "all-busy service block have different sizes");
698 for (
double v : aTS) s += v;
699 for (std::size_t i = 0; i < dim; ++i) busy_start[i] = w.
prob_wait * aTS[i] / s;
701 for (std::size_t i = 0; i < TS.
rows(); ++i) {
703 for (std::size_t j = 0; j < TS.
cols(); ++j) s += TS(i, j);
704 for (std::size_t j = 0; j < TS.
cols(); ++j) TS(i, j) /= s;
707 const std::vector<double> stat = [&] {
709 for (std::size_t i = 0; i < Tr; ++i)
710 for (std::size_t j = 0; j < Tr; ++j) negST(i, j) = -ST(i, j);
711 return fjdetail::rdivide_row(busy_start, negST);
713 std::vector<bool> nz(Tr,
false);
714 for (std::size_t i = 0; i < Tr; ++i) nz[i] = stat[i] > 0.0;
716 std::vector<double> tr_start(Tr, 0.0);
717 for (std::size_t j = 0; j < Tr; ++j) {
719 for (std::size_t k = 0; k < Tr; ++k) s += ST(j, k);
720 tr_start[j] = -s * stat[j];
724 for (std::size_t i = 0; i < Tr; ++i) {
725 if (!nz[i])
continue;
726 for (std::size_t j = 0; j < Tr; ++j)
727 if (nz[j]) tr_ST(i, j) = ST(j, i) * stat[j] / stat[i];
729 std::vector<double> tr_exit(Tr, 0.0);
730 for (std::size_t i = 0; i < Tr; ++i) {
732 for (std::size_t j = 0; j < Tr; ++j) s += tr_ST(i, j);
738 for (std::size_t i = 0; i < Sc; ++i)
739 for (std::size_t j = 0; j < Sc; ++j) TS2(i, j) = TS(j, i) * w.
alfa[j];
740 for (std::size_t i = 0; i < Sc; ++i) {
742 for (std::size_t j = 0; j < Sc; ++j) s += TS2(i, j);
743 if (std::fabs(s) < 10e-12) s = 1.0;
744 for (std::size_t j = 0; j < Sc; ++j) TS2(i, j) /= s;
748 for (std::size_t i = 0; i < Sc; ++i)
749 for (std::size_t j = 0; j < Sc; ++j) tildeP(i, j) = tr_exit[i] * TS2(i, j);
751 std::vector<std::size_t> keep;
752 for (std::size_t i = 0; i < Tr; ++i)
753 if (nz[i]) keep.push_back(i);
754 const std::size_t m_tr = keep.size();
756 const std::size_t total = Tr + m_tr + Sc;
757 std::vector<double> gamma(total, 0.0);
758 for (std::size_t i = 0; i < Tr; ++i) gamma[i] = notbusy_start[i];
759 for (std::size_t i = 0; i < m_tr; ++i) gamma[Tr + i] = tr_start[keep[i]];
762 for (std::size_t i = 0; i < Tr; ++i)
763 for (std::size_t j = 0; j < Tr; ++j) Cres(i, j) = ST(i, j);
764 for (std::size_t i = 0; i < m_tr; ++i) {
765 for (std::size_t j = 0; j < m_tr; ++j) Cres(Tr + i, Tr + j) = tr_ST(keep[i], keep[j]);
766 for (std::size_t j = 0; j < Sc; ++j) Cres(Tr + i, Tr + m_tr + j) = tildeP(keep[i], j);
768 for (std::size_t i = 0; i < Sc; ++i)
769 for (std::size_t j = 0; j < Sc; ++j)
770 Cres(Tr + m_tr + i, Tr + m_tr + j) = w.
wait_Smat(i, j);
794 const std::vector<double>& pers,
795 const std::vector<std::size_t>& K,
796 const std::vector<std::size_t>& Cs,
FjTMode mode) {
798 if (!(arrival.
lambda / service.
mu < 1.0))
799 throw InputError(
"mainFJ: the system is not stable, the mean arrival rate " +
800 std::to_string(arrival.
lambda) +
801 " is not below the mean service rate " + std::to_string(service.
mu));
802 if (Cs.empty())
throw InputError(
"mainFJ: no truncation level C was given");
803 if (pers.empty())
throw InputError(
"mainFJ: no percentile was requested");
805 std::vector<double> rt1, rt2;
806 for (std::size_t c = 0; c < Cs.size(); ++c) {
811 std::vector<FjCodesPercentiles> out(K.size());
812 for (std::size_t k = 0; k < K.size(); ++k) {
814 out[k].percentiles.resize(pers.size());
815 out[k].RTp.assign(pers.size(), 0.0);
816 for (std::size_t p = 0; p < pers.size(); ++p) {
817 out[k].percentiles[p] = 100.0 * pers[p];
818 out[k].RTp[p] = rt1[p] + (rt2[p] - rt1[p]) * log(
static_cast<double>(K[k])) / log(2.0);
NumericError(const std::string &what)
Eigenvalues and singular values, backed by LAPACK.
The exception types the port throws.
The state-space construction of FJ_codes, the fork-join response-time-tail approximation of Z.
Conversion of a LINE MAP into the arrival or service descriptor of the fork-join response-time-tail a...
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
Least squares for a rectangular system, exact-capable.
Dense matrix and non-owning view.
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
The MMAP[K]/PH[K]/1 FCFS queue: per-class mean number in system and per-class queue-length distributi...
std::vector< double > fj_return_rt1(const FjDist< double > &arrival, const FjDist< double > &service, const std::vector< double > &pers)
Port of returnRT1.m: the response-time percentiles of the ONE-node queue, which are exact.
std::vector< double > fj_return_per(const std::vector< double > &vec, const Matrix< double > &A, const std::vector< double > &pers)
Port of returnPer.m: the percentiles of a (possibly defective) phase-type law, by uniformization.
std::vector< FjCodesPercentiles > fj_main(const FjDist< double > &arrival, const FjDist< double > &service, const std::vector< double > &pers, const std::vector< std::size_t > &K, const std::vector< std::size_t > &Cs, FjTMode mode)
Port of mainFJ.m: the response-time percentiles of a K-node fork-join queue, interpolated between the...
FjTMode fj_parse_tmode(const std::string &s)
Parse the reference's T_Mode string, whose default is 'NARE'.
std::vector< double > fj_boundary_solve(const Matrix< double > &pi0mat, const Matrix< double > &T)
The boundary solve both branches of computePi.m end with:
FjCodesSRK fj_construct_srk(std::size_t C, const FjDist< double > &service, const FjCodesServiceH &h, const Matrix< double > &S)
Port of constructSRK.m.
Matrix< double > fj_compute_t_nare(const Matrix< double > &D0, const Matrix< double > &D1, const Matrix< double > &S, const Matrix< double > &A_jump, double *residual)
Port of computeT_NARE.m: the T matrix as the stable invariant subspace of.
FjCodesServiceH fj_build_service_h(const FjDist< double > &service)
Port of build_Service_h.m.
FjCodesGenService fj_generate_service(const FjDist< double > &service, const FjCodesServiceH &h, std::size_t C, const Matrix< double > &S)
Port of generateService.m.
FjCodesT fj_compute_t(const FjDist< double > &arrival, const FjDist< double > &service, const FjCodesServiceH &h, std::size_t C, FjTMode mode)
Port of computeT.m.
FjCodesRT2 fj_return_rt2(const FjDist< double > &arrival, const FjDist< double > &service, const std::vector< double > &pers, std::size_t C, FjTMode mode)
Port of returnRT2.m: the response-time percentiles of the TWO-node fork-join queue,...
FjTMode
Which route computeT.m takes to the T matrix.
Matrix< double > fj_construct_not_all_busy(std::size_t C, const FjDist< double > &service, const FjCodesServiceH &h)
Port of constructNotAllBusy.m.
FjCodesSA fj_build_sa(const FjDist< double > &service, const FjCodesServiceH &h, std::size_t C)
Port of build_SA.m.
FjCodesPi fj_compute_pi(const Matrix< double > &T, const FjDist< double > &arrival, const FjDist< double > &service, const FjCodesServiceH &h, std::size_t C, const Matrix< double > &S, const Matrix< double > &A_jump)
Port of computePi.m: the all-busy boundary vector and E[n1].
FjCodesWait fj_return_wait(double En1, const std::vector< double > &pi0, const Matrix< double > &T, const std::vector< double > &phi, const std::vector< double > &sum_Ajump)
Port of returnWait.m: the stationary waiting time as a phase-type law.
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.
std::vector< StDistrPh< T > > mmapph1fcfs_stdistr_ph(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc, double precision=1e-14)
Per-class SOJOURN TIME as a continuous phase-type law, BUTools' 'stDistrPH'.
LstsqResult< T > lstsq(const Matrix< T > &A, const std::vector< T > &b, const T &tol)
Least-squares solution of A x = b, minimum-norm when A is rank deficient.
std::vector< T > vecmul(const std::vector< T > &v, const Matrix< T > &A)
Row vector times matrix, v A.
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
RealSchur schur_reorder(const RealSchur &s, const std::vector< double > &key)
Reorder the diagonal blocks of a real Schur form into DESCENDING key order, stably,...
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.
Matrix< double > sylvester_schur(const Matrix< double > &A, const Matrix< double > &B, const Matrix< double > &C)
A X + X B = C by Bartels-Stewart, at double.
Matrix< T > eye(std::size_t n)
Identity of order n.
RealSchur schur_decomposition(const Matrix< double > &A)
Real Schur factorization of a general square matrix (LAPACK dgees, unsorted).
Matrix< double > lyap_schur(const Matrix< double > &A, const Matrix< double > &B, const Matrix< double > &C)
MATLAB lyap(A,B,C) at double via Bartels-Stewart: A X + X B + C = 0.
Outcome of lstsq: the solution and whether the system was rank deficient.
Real Schur factorization A = Z T Z^T, with Z orthogonal and T upper quasi-triangular: 1 x 1 diagonal ...
Matrix< double > Z
orthogonal Schur vectors
Matrix< double > T
upper quasi-triangular factor
generateService.m: the service process seen by a tagged job.
Matrix< double > T
(newdim + dim_notbusy) square
std::size_t newdim
the all-busy part
std::size_t dim_notbusy
the not-all-busy part
One line of mainFJ's output cell: the percentiles of a K-node queue.
std::vector< double > percentiles
in PERCENT, as the reference stores them
std::vector< double > RTp
What computePi.m returns.
std::vector< double > pi0
unnormalized, length newdim * ma
double En1
mean number of arrivals in a not-all-busy period
What returnRT2.m produces, plus the waiting-time law it discards.
std::vector< double > RTp
response-time percentiles
std::vector< double > wait_alpha
the waiting-time law, defective
Matrix< double > wait_Smat
double residual
the T-matrix residual of computeT
build_SA.m: the level-constant generator and the head-of-line jump.
Matrix< double > A_jump
a job completes and the next enters service
Matrix< double > S
no job completes, ((C+1) m^2) square
constructSRK.m: the extended generator and the busy/idle projectors.
Matrix< double > R0
not-busy to busy, on an arrival
Matrix< double > Se
busy and not-busy phases together
Matrix< double > Sestar
the busy-to-not-busy block of Se, in place
Matrix< double > Ke
newdim x (newdim + dim_notbusy), the busy rows
Matrix< double > Kc
(newdim + dim_notbusy) x newdim, the busy columns
build_Service_h.m: the two-subtask phase process of one fork-join job.
Matrix< double > T
the all-busy generator, (newdim * ma) square
Matrix< double > S
build_SA's S, newdim square
double residual
inf-norm the reference prints
std::vector< double > sum_Ajump
row sums of kron(A_jump, I_ma)
Matrix< double > S_Arr
kron(S, I_ma)
std::size_t iterations
Sylvester mode only.
Matrix< double > A_jump
build_SA's A_jump, newdim square
What returnWait.m returns: the waiting time as a phase-type law.
std::vector< double > wait_alpha
defective, mass prob_wait
std::vector< double > alfa
-pi0 T^-1, the all-busy occupancy
Matrix< double > wait_Smat
Descriptor of an arrival or a service process.
An MMAP: the underlying MAP plus the per-class arrival matrices.
std::vector< Matrix< T > > Dc
per-class matrices, sum_c Dc = D1
The Sylvester equation A X + X B = C, and MATLAB's lyap(A,B,C).