5#ifndef LINE_SOLVERS_MAM_SOLVER_MAM_FJ_H
6#define LINE_SOLVERS_MAM_SOLVER_MAM_FJ_H
137 for (std::size_t r = 1; r <= L.
nclasses; ++r)
138 for (std::size_t s = 1; s <= L.
nclasses; ++s)
139 if (L.
get_route(r, s, i, j) > zero)
return true;
145bool same_map(
const Map<T>& a,
const Map<T>& b) {
146 if (a.order() != b.order())
return false;
148 for (std::size_t i = 0; i < a.order(); ++i)
149 for (std::size_t j = 0; j < a.order(); ++j) {
171 default:
return false;
193 info.
why =
"FJ_codes supports open queueing models only";
197 info.
why =
"the network contains no Fork-Join structure";
201 std::vector<std::size_t> forks, joins;
202 for (std::size_t i = 1; i <= L.
nodes.size(); ++i) {
203 if (L.
nodes[i - 1].nodetype == qn::NodeType::Fork) forks.push_back(i);
204 if (L.
nodes[i - 1].nodetype == qn::NodeType::Join) joins.push_back(i);
206 if (forks.empty() || joins.empty()) {
207 info.
why =
"the network must contain both a Fork and a Join node";
210 if (forks.size() > 1 || joins.size() > 1) {
211 info.
why =
"FJ_codes supports a single Fork-Join pair; this model has " +
212 std::to_string(forks.size()) +
" forks and " + std::to_string(joins.size()) +
224 info.
why =
"the Join fires on a quorum; FJ_codes synchronises on every branch and "
225 "has no quorum, so use the order-statistic tail fj_tail_ordstat instead";
233 for (std::size_t p = 0; p < L.
fj.size(); ++p)
236 info.
why =
"the Fork and the Join of this model are not paired with each other";
241 for (std::size_t i = 1; i <= L.
nodes.size(); ++i) {
242 if (L.
nodes[i - 1].nodetype != qn::NodeType::Queue)
continue;
243 if (fj_detail::routes_between(L, info.
forkNode, i) &&
244 fj_detail::routes_between(L, i, info.
joinNode))
248 info.
why =
"no Queue node lies between the Fork and the Join";
257 for (std::size_t r = 1; r <= L.
nclasses; ++r) {
258 if (st0 == 0 || L.
disabled[st0 - 1][r - 1]) {
260 "' has no service distribution for class '" + L.
classes[r - 1].name +
"'";
264 for (std::size_t k = 1; k < info.
K; ++k) {
266 if (st == 0 || L.
disabled[st - 1][r - 1]) {
268 "' has no service distribution for class '" + L.
classes[r - 1].name +
273 info.
why =
"the parallel queues have heterogeneous service distributions for "
274 "class '" + L.
classes[r - 1].name +
"'; FJ_codes requires homogeneous "
281 for (std::size_t k = 0; k < info.
K; ++k) {
284 if (sc != SchedStrategy::FCFS && sc != SchedStrategy::PS) {
286 "' uses a scheduling strategy FJ_codes does not support; it supports FCFS "
307 if (!info.
ok)
throw InputError(
"mam_fj_extract_params: the topology gate has not passed");
310 for (std::size_t i = 1; i <= L.
nstations; ++i)
311 if (L.
stations[i - 1].nodetype == qn::NodeType::Source) {
315 if (src == 0)
throw InputError(
"SolverMAM: the fork-join model has no Source node");
322 for (std::size_t r = 1; r <= L.
nclasses; ++r) {
324 if (!fj_detail::to_fj_proc_type(L.
service[src - 1][r - 1].type, at))
326 "SolverMAM: the arrival process of class '" + L.
classes[r - 1].name +
328 ", and the FJ_codes approximation is defined for Exp, HyperExp(2), Erlang(2) and "
329 "MAP(2) arrivals only");
330 if (!fj_detail::to_fj_proc_type(L.
service[q0 - 1][r - 1].type, st))
332 "SolverMAM: the branch service process of class '" + L.
classes[r - 1].name +
334 ", and the FJ_codes approximation is defined for Exp, HyperExp(2) and Erlang(2) "
346 "SolverMAM: class '" + L.
classes[r - 1].name +
347 "' offers each fork-join branch a load of at least one (arrival rate " +
350 "); the response-time tail of an unstable branch does not exist");
359 static const std::vector<double> p = {0.50, 0.90, 0.95, 0.99};
369 static const std::vector<double> p = [] {
370 std::vector<double> v;
371 for (
int k = 0; k < 19; ++k) v.push_back(0.01 + 0.05 *
static_cast<double>(k));
382inline std::size_t fj_accuracy(
const MamOptions&
opt) {
383 if (
opt.fj_accuracy < 1)
384 throw InputError(
"SolverMAM: config.fj_accuracy is the FJ_codes truncation C of the "
385 "queue-length difference and must be at least 1");
386 return opt.fj_accuracy;
397inline double fj_mean_from_percentiles(
const std::vector<double>& pers,
398 const std::vector<double>& rtp) {
399 std::vector<double> p, v;
402 for (std::size_t i = 0; i < pers.size(); ++i) {
403 p.push_back(pers[i]);
407 v.push_back(rtp.empty() ? 0.0 : rtp.back() * 1.1);
409 for (std::size_t i = 0; i + 1 < p.size(); ++i)
410 acc += 0.5 * (p[i + 1] - p[i]) * (v[i + 1] + v[i]);
438 std::vector<std::vector<T> >* percentiles_out) {
442 "SolverMAM: model '" + L.
name +
443 "' is not a homogeneous fork-join network (" + info.
why +
444 "). The reference routes such a model to solver_mam_basic_mmap, the MMAP fork-join "
445 "decomposition; select it with method 'dec.source.mmap'");
446 if constexpr (!std::is_same<T, double>::value) {
448 (void)percentiles_out;
450 "SolverMAM: the FJ_codes fork-join engine solves a non-symmetric algebraic Riccati "
451 "equation by an ORDERED REAL SCHUR factorization and two Sylvester equations of order "
452 "(C + 1) m^2 ma by Bartels-Stewart, all of which are LAPACK and therefore double only. "
453 "Re-run this model at double; there is no exact or extended-precision route to the "
454 "Qiu-Perez-Harrison approximation");
457 const std::size_t C = fj_detail::fj_accuracy(
opt);
462 std::vector<double> grid = dense;
463 grid.insert(grid.end(), stored.begin(), stored.end());
476 if (percentiles_out !=
nullptr)
477 percentiles_out->assign(K, std::vector<T>(stored.size(),
480 std::size_t src = 0, snk = 0;
481 for (std::size_t i = 1; i <= M; ++i)
482 if (L.
stations[i - 1].nodetype == qn::NodeType::Source) src = i;
483 for (std::size_t i = 1; i <= L.
nodes.size(); ++i)
484 if (L.
nodes[i - 1].nodetype == qn::NodeType::Sink) snk = i;
486 for (std::size_t r = 1; r <= K; ++r) {
487 const std::vector<std::size_t> Kv(1, info.
K);
488 const std::vector<std::size_t> Cv(1, C);
489 const std::vector<fj::FjCodesPercentiles> res =
492 std::vector<double> dense_rt(dense.size()), stored_rt(stored.size());
493 for (std::size_t i = 0; i < dense.size(); ++i) dense_rt[i] = res[0].RTp[i];
494 for (std::size_t i = 0; i < stored.size(); ++i)
495 stored_rt[i] = res[0].RTp[dense.size() + i];
496 if (percentiles_out !=
nullptr)
497 for (std::size_t i = 0; i < stored.size(); ++i)
498 (*percentiles_out)[r - 1][i] = stored_rt[i];
500 const double mean_fj_rt = fj_detail::fj_mean_from_percentiles(dense, dense_rt);
501 const double lambda = par.
arrival[r - 1].lambda;
502 const double mu = par.
service[r - 1].mu;
503 const double rho = lambda / mu;
504 const double branch_rt = 1.0 / (mu - lambda);
506 for (std::size_t k = 0; k < info.
K; ++k) {
508 sol.
U(st - 1, r - 1) = rho;
509 sol.
Tp(st - 1, r - 1) = lambda;
510 sol.
Q(st - 1, r - 1) = rho / (1.0 - rho);
511 sol.
R(st - 1, r - 1) = branch_rt;
514 if (fst != 0) sol.
Tp(fst - 1, r - 1) = lambda;
517 sol.
Tp(jst - 1, r - 1) = lambda;
518 const double sync = mean_fj_rt - branch_rt;
519 sol.
R(jst - 1, r - 1) = sync;
520 sol.
Q(jst - 1, r - 1) = lambda * sync;
522 if (src != 0) sol.
Tp(src - 1, r - 1) = lambda;
524 const std::size_t sst = L.
nodes[snk - 1].station;
525 if (sst != 0) sol.
Tp(sst - 1, r - 1) = lambda;
555 const std::vector<double>& percentiles) {
557 std::vector<std::vector<T> > all;
560 std::vector<std::vector<T> > out(all.size(), std::vector<T>(percentiles.size(),
562 for (std::size_t r = 0; r < all.size(); ++r)
563 for (std::size_t i = 0; i < percentiles.size(); ++i) {
564 const double p = percentiles[i];
568 while (s + 2 < stored.size() && p > stored[s + 1]) ++s;
569 const double p0 = stored[s], p1 = stored[s + 1];
570 const T v0 = all[r][s], v1 = all[r][s + 1];
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
T get_route(std::size_t r, std::size_t s, std::size_t i, std::size_t j) const
P{r,s}(i,j), AS THE USER SET IT.
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
std::vector< std::vector< bool > > disabled
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
std::vector< NodeDef > nodes
every node, in creation order
bool is_open_model() const
sn_is_open_model: EVERY class is open, which is not has_open_classes.
std::vector< std::pair< std::size_t, std::size_t > > fj
fj(f,j): the Join node j that closes the Fork node f, 1-based.
std::size_t join_siblings(std::size_t joinNode, std::size_t r=0) const
Port of MNetwork.refreshCapacity.
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
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...
The option and result types SolverMAM shares with its analyzers.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
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'.
FjDist< T > fj_dist2fj(const mam::Map< T > &m, FjDistKind kind, FjProcType procType)
Build the fork-join descriptor of a MAP.
FjTMode
Which route computeT.m takes to the T matrix.
FjProcType
The subset of ProcessType that the fork-join algorithm accepts.
mam::Map< T > dist_to_map(const Distrib< T > &d)
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
const char * process_to_text(ProcessType p)
The MATLAB ProcessType name, as sn.procid prints it.
mva::MvaSolution< T > solver_mam_fj(const qn::NetworkStruct< T > &L, const MamOptions &opt, std::vector< std::vector< T > > *percentiles_out)
Port of solver_mam_fj.m.
const std::vector< double > & mam_fj_stored_percentiles()
The four percentiles solver_mam_fj.m stores for getPerctRespT.
MamFjParams< T > mam_fj_extract_params(const qn::NetworkStruct< T > &L, const MamFjInfo &info)
Port of fj_extract_params.m: the arrival descriptor from the Source and the service descriptor from t...
MamFjInfo mam_fj_is_homogeneous(const qn::NetworkStruct< T > &L)
Port of fj_is_homogeneous.m.
const std::vector< double > & mam_fj_dense_percentiles()
The 21-point grid solver_mam_fj.m inverts for the MEAN, [0.01:0.05:0.95, 0.99, 0.999].
std::vector< std::vector< T > > solver_mam_fj_percentiles(const qn::NetworkStruct< T > &L, const MamOptions &opt, const std::vector< double > &percentiles)
@@SolverMAM/getPerctRespT.m's fork-join path: the percentiles solver_mam_fj.m stores in percResults....
std::size_t sn_join_quorum(const qn::NetworkStruct< T > &sn, std::size_t joinNode, std::size_t nbranches)
The number of siblings the Join node joinNode (1-based) fires on, out of nbranches forked.
A queueing network and its refreshed NetworkStruct.
Quorum joins: how many siblings a Join fires on, and what that drops.
Descriptor of an arrival or a service process.
static constexpr double FineTol
What fj_is_homogeneous.m returns: the fork-join pair, or why there is none.
std::size_t joinNode
1-based node index
std::size_t forkNode
1-based node index
std::string why
the reference's errorMsg, empty when ok
std::vector< std::size_t > queueNodes
the K parallel branches, 1-based node indices
What fj_extract_params.m returns: one arrival and one service per class.
std::vector< fj::FjDist< T > > arrival
std::vector< fj::FjDist< T > > service
The options SolverMAM reads.
A MAP as the pair of matrices (D0, D1).
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.