5#ifndef LINE_SOLVERS_MAM_SOLVER_MAM_BASIC_MMAP_H
6#define LINE_SOLVERS_MAM_SOLVER_MAM_BASIC_MMAP_H
117namespace basic_mmap_detail {
153 std::vector<std::vector<Map<T>>> PH;
154 std::vector<std::vector<PhService<T>>> svc;
155 std::vector<std::vector<bool>> known;
164 t.PH.assign(M, std::vector<Map<T>>(K));
165 t.svc.assign(M, std::vector<PhService<T>>(K));
166 t.known.assign(M, std::vector<bool>(K,
false));
167 for (std::size_t i = 0; i < M; ++i) {
180 const double ns = L.
stations[i].nservers;
181 for (std::size_t r = 0; r < K; ++r) {
184 t.svc[i][r].sigma.assign(1, one);
185 t.svc[i][r].S =
Matrix<T>(1, 1, T(-imm));
189 if (divide && std::isfinite(ns) && ns > 0.0)
192 t.svc[i][r].sigma =
map_pie(ph);
193 t.svc[i][r].S = ph.D0;
194 t.known[i][r] =
true;
213T exp_max_mean(
const std::vector<T>& rate) {
214 const std::size_t n = rate.size();
217 "solver_mam_basic_mmap: the join synchronizes " + std::to_string(n) +
218 " branches, and the reference's expected-maximum formula is an alternating sum over "
219 "all 2^n subsets of them; that is not evaluable at this width");
220 const T zero = num_traits<T>::from_int(0);
222 for (std::size_t mask = 1; mask < (std::size_t(1) << n); ++mask) {
224 std::size_t bits = 0;
225 for (std::size_t i = 0; i < n; ++i)
226 if (mask & (std::size_t(1) << i)) {
230 if (!(s > zero))
continue;
231 const T term = T(num_traits<T>::from_int(1) / s);
232 if (bits % 2 == 1) acc += term;
240Mmap<T> compress_arrival(
const Mmap<T>& m) {
241 if constexpr (num_traits<T>::has_transcendental) {
246 "solver_mam_basic_mmap: the arrival superposition passed config.space_max and must be "
247 "compressed, which fits an APH(2) and needs transcendental arithmetic");
265 const std::vector<T>& lambda,
266 std::size_t* totiter) {
268 (void)L; (void)
opt; (void)cfg; (void)lambda; (void)totiter;
270 "solver_mam_basic_mmap_inner: the departure-process fixed point stops on a tolerance, "
271 "MMAP[K]/PH[K]/1 runs the ADDA doubling iteration and the ETAQA departure process "
272 "needs transcendental arithmetic; rerun with --arith double or --arith real");
274 using namespace basic_mmap_detail;
275 using basic_detail::station_visits;
276 using basic_detail::truncate_renorm;
277 using basic_detail::zero_nans;
282 if (lambda.size() != K)
283 throw InputError(
"solver_mam_basic_mmap_inner: lambda is not indexed over the classes");
287 for (std::size_t i = 0; i < M; ++i)
288 for (std::size_t r = 0; r < K; ++r)
291 const MmapPhTable<T> ph = mmap_ph_table(L);
296 Matrix<T> QN(M, K, zero), UN(M, K, zero), RN(M, K, zero), TN(M, K, zero);
299 for (std::size_t i = 0; i < M; ++i)
300 if (L.
stations[i].sched == SchedStrategy::EXT)
301 for (std::size_t r = 0; r < K; ++r)
306 auto sweep = [&](
const std::vector<T>&,
307 std::size_t itnum) -> std::pair<std::vector<T>, std::vector<T>> {
310 for (std::size_t ind = 0; ind < I; ++ind) {
312 const bool isfj = (ty == qn::NodeType::Fork || ty == qn::NodeType::Join);
313 if (L.
nodes[ind].station != 0 && !isfj) {
314 const std::size_t ist = L.
nodes[ind].station - 1;
315 for (std::size_t r = 0; r < K; ++r) {
316 if (V(ist, r) > zero && lambda[r] > zero)
318 map_scale(ph.PH[ist][r], T(one / T(lambda[r] * V(ist, r))));
320 DEP[ind][r] = ph.PH[ist][r];
323 for (std::size_t r = 0; r < K; ++r)
334 std::vector<T> xref(M * K, zero);
335 for (std::size_t i = 0; i < M; ++i)
336 for (std::size_t r = 0; r < K; ++r) xref[i * K + r] = QN(i, r);
339 for (std::size_t i = 0; i < M; ++i) {
343 const double ns = L.
stations[i].nservers;
345 if (ty == qn::NodeType::Join) {
348 for (std::size_t r = 0; r < K; ++r) {
349 TN(i, r) = lambda[r];
356 if (ty != qn::NodeType::Queue) {
357 if (sc == SchedStrategy::INF) {
358 if (ARV[ind].order() > 0) {
360 for (std::size_t r = 0; r < K; ++r) TN(i, r) = lam[r];
362 for (std::size_t r = 0; r < K; ++r)
363 if (TN(i, r) > zero) {
364 UN(i, r) = T(S(i, r) * TN(i, r));
365 QN(i, r) = T(TN(i, r) * S(i, r));
372 if (ARV[ind].order() == 0)
continue;
373 if (ARV[ind].order() > tcfg.
space_max) ARV[ind] = compress_arrival(ARV[ind]);
375 bool finiteCapUsed =
false;
376 std::vector<PhService<T>> sl;
377 for (std::size_t r = 0; r < K; ++r) sl.push_back(ph.svc[i][r]);
379 if (sc == SchedStrategy::FCFS || sc == SchedStrategy::HOL) {
380 if (std::isfinite(L.
stations[i].cap)) {
381 const std::size_t capK =
382 static_cast<std::size_t
>(std::llround(L.
stations[i].cap));
383 T meanQ = zero, lossProb = zero;
388 for (
const T& v : lam)
391 lamTot, det.
muRate,
static_cast<unsigned>(std::llround(ns)),
392 static_cast<unsigned>(capK));
396 const basic_detail::TruncRenorm<T>
tr = truncate_renorm(ARV[ind], sl, capK);
398 lossProb =
tr.lossProb;
401 std::vector<T> eff(K, zero), Sact(K, zero);
403 for (std::size_t r = 0; r < K; ++r) {
406 eff[r] = T(inflow * T(one - lossProb));
415 for (std::size_t r = 0; r < K; ++r) {
416 const T c = T(eff[r] * Sact[r]);
419 const T w = T(T(meanQ / sumTN) - T(sw / sumTN));
420 Wq = (w > zero) ? w : zero;
422 for (std::size_t r = 0; r < K; ++r) {
424 UN(i, r) = T(TN(i, r) *
map_mean(ph.PH[i][r]));
425 if (TN(i, r) > zero) {
426 RN(i, r) = T(Wq + Sact[r]);
427 QN(i, r) = T(TN(i, r) * RN(i, r));
433 finiteCapUsed =
true;
437 for (std::size_t r = 0; r < K; ++r) {
438 const T u = T(lam[r] *
map_mean(ph.PH[i][r]));
441 if (rho < T(one - ftol)) {
446 (K == 1) && (ns == 1.0) && ph.known[i][0] &&
448 map_acf(ph.PH[i][0], std::vector<unsigned>{1})[0])) >
449 GlobalConstants::CoarseTol;
452 arv.
D0 = ARV[ind].D0;
453 arv.
D1 = ARV[ind].Dc[0];
457 for (std::size_t r = 0; r < K; ++r)
458 QN(i, r) = m[ARV[ind].classes() == 1 ? 0 : r];
464 for (std::size_t r = 0; r < K; ++r)
465 QN(i, r) = std::isfinite(L.
classes[r].population)
469 for (std::size_t r = 0; r < K; ++r) TN(i, r) = lam[r];
471 }
else if (sc == SchedStrategy::PS) {
473 for (std::size_t r = 0; r < K; ++r) {
479 UN(i, r) = T(TN(i, r) * S(i, r));
482 for (std::size_t r = 0; r < K; ++r) usum += UN(i, r);
483 const T uden = (usum < T(one - ftol)) ? usum : T(one - ftol);
484 for (std::size_t r = 0; r < K; ++r) QN(i, r) = T(UN(i, r) / T(one - uden));
487 if (!finiteCapUsed) {
488 for (std::size_t r = 0; r < K; ++r) {
489 UN(i, r) = T(TN(i, r) *
map_mean(ph.PH[i][r]));
492 if (std::isfinite(ns))
493 QN(i, r) = T(QN(i, r) + TN(i, r) *
497 RN(i, r) = T(QN(i, r) / TN(i, r));
503 for (std::size_t i = 0; i < M; ++i) {
507 if (ty == qn::NodeType::Join) {
508 for (std::size_t r = 0; r < K; ++r)
512 if (ty != qn::NodeType::Queue || ARV[ind].order() == 0)
continue;
513 const bool fcfs = (sc == SchedStrategy::FCFS || sc == SchedStrategy::HOL);
514 if (!fcfs && sc != SchedStrategy::PS)
continue;
517 for (std::size_t r = 0; r < K; ++r) rho += UN(i, r);
518 for (std::size_t r = 0; r < K; ++r) {
519 const bool scalable = (V(i, r) > zero && lambda[r] > zero);
523 if (!fcfs && !scalable)
continue;
525 const Map<T>& Srv = ph.PH[i][r];
526 const std::size_t etaqa_sz =
529 if (etaqa_sz <= tcfg.
space_max && rho < T(one - ftol)) {
536 }
catch (
const Error&) {
540 if (scalable) dep =
map_scale(dep, T(one / T(lambda[r] * V(i, r))));
545 std::vector<T> xnew(M * K, zero);
546 for (std::size_t i = 0; i < M; ++i)
547 for (std::size_t r = 0; r < K; ++r) xnew[i * K + r] = QN(i, r);
548 return std::make_pair(xnew, xref);
552 fo.
iter_max =
static_cast<std::size_t
>(
opt.iter_max);
564 for (std::size_t j = 0; j < I; ++j) {
565 if (L.
nodes[j].nodetype != qn::NodeType::Join)
continue;
566 if (L.
nodes[j].station == 0)
continue;
567 const std::size_t jst = L.
nodes[j].station - 1;
568 std::map<std::size_t, std::vector<std::size_t>> groups;
569 for (std::size_t b = 0; b < I; ++b) {
570 const std::size_t gid =
fj.node_sync[j][b];
571 if (gid > 0) groups[gid].push_back(b);
573 for (std::size_t r = 0; r < K; ++r) {
574 if (!(TN(jst, r) > zero))
continue;
575 T syncDelay = zero, joinArrivalRate = zero;
576 for (
const std::pair<
const std::size_t, std::vector<std::size_t>>& g : groups) {
577 std::vector<T> branchRt, branchTput;
578 for (std::size_t b : g.second) {
579 if (L.
nodes[b].station == 0)
continue;
580 const std::size_t bst = L.
nodes[b].station - 1;
581 if (!(RN(bst, r) > zero))
continue;
582 branchRt.push_back(RN(bst, r));
583 branchTput.push_back(TN(bst, r));
585 if (branchRt.size() < 2)
continue;
586 std::vector<T> rate(branchRt.size(), zero);
588 for (std::size_t b = 0; b < branchRt.size(); ++b) {
589 rate[b] = T(one / branchRt[b]);
590 meanRt += branchRt[b];
593 const T excess = T(exp_max_mean(rate) - meanRt);
594 if (excess > zero) syncDelay += excess;
595 for (
const T& t : branchTput) joinArrivalRate += t;
597 RN(jst, r) = syncDelay;
598 QN(jst, r) = T(joinArrivalRate * syncDelay);
608 out.
C.assign(K, zero);
609 for (std::size_t r = 0; r < K; ++r)
610 for (std::size_t i = 0; i < M; ++i) out.
C[r] += RN(i, r);
612 out.
X.assign(K, zero);
617 for (std::size_t r = 0; r < K; ++r)
619 out.
method =
"dec.source.mmap";
634 std::size_t* totiter) {
636 (void)L; (void)
opt; (void)cfg; (void)totiter;
638 "solver_mam_basic_mmap_closed: the throughput bisection wraps the inner MMAP "
639 "decomposition, which needs transcendental arithmetic; rerun with --arith double or "
642 using namespace basic_mmap_detail;
643 using basic_detail::zero_nans;
655 "solver_mam_basic_mmap_closed: the reference renormalizes chain c's queue lengths with "
656 "the class-indexed sn.njobs(c), which is only correct when each chain holds exactly "
657 "one class; this model has " +
658 std::to_string(C) +
" chains over " + std::to_string(K) +
" classes");
664 std::vector<T> lambda_lb(K, zero), lambda_ub(K, zero);
665 for (std::size_t r = 0; r < K; ++r) {
668 for (std::size_t i = 0; i < M; ++i) {
669 if (!std::isfinite(L.
stations[i].nservers))
continue;
672 if (!any || v < best) { best = v; any =
true; }
675 for (std::size_t i = 0; i < M; ++i) {
676 if (std::isfinite(L.
stations[i].nservers))
continue;
679 if (!any || v > best) { best = v; any =
true; }
684 std::vector<T> lambda = lambda_ub;
686 std::vector<T> QNc(K, zero);
687 for (std::size_t r = 0; r < K; ++r)
689 QNc[r] = std::isfinite(L.
classes[r].population)
692 std::vector<T> QN_chain(K, zero);
695 inner.
iter_max = std::max(20, (
opt.iter_max + 9) / 10);
702 bool have_good =
false, algorithm_ok =
false;
703 const double bisect_tol = std::max(
opt.tol, 1e-3);
708 for (std::size_t r = 0; r < K; ++r)
710 if (!(gap > bisect_tol) || it_out >=
opt.iter_max)
break;
713 bool bracket_collapsed =
true;
714 for (std::size_t r = 0; r < K; ++r) {
715 if (!std::isfinite(L.
classes[r].population) || !(QNc[r] > zero))
continue;
716 if (QN_chain[r] < QNc[r]) lambda_lb[r] = lambda[r];
717 else lambda_ub[r] = lambda[r];
724 if (width > GlobalConstants::FineTol *
726 bracket_collapsed =
false;
728 if (bracket_collapsed) {
735 std::size_t inner_iter = 0;
738 }
catch (
const Error&) {
742 algorithm_ok =
false;
746 for (std::size_t r = 0; r < K; ++r) {
748 for (std::size_t i = 0; i < M; ++i) QN_chain[r] += sol.
Q(i, r);
755 for (std::size_t r = 0; r < K; ++r)
761 if (!algorithm_ok && have_good) sol = last;
762 if (sol.
Q.rows() != M) {
769 sol.
C.assign(K, zero);
770 sol.
X.assign(K, zero);
774 for (std::size_t c = 0; c < C; ++c) {
775 if (!std::isfinite(L.
classes[c].population))
continue;
777 for (std::size_t k : L.
inchain[c])
778 for (std::size_t i = 0; i < M; ++i) sumQ += sol.
Q(i, k - 1);
779 if (!(sumQ > zero))
continue;
781 for (std::size_t k : L.
inchain[c])
782 for (std::size_t i = 0; i < M; ++i) sol.
Q(i, k - 1) = T(Nc * sol.
Q(i, k - 1) / sumQ);
786 for (std::size_t i = 0; i < M; ++i)
787 if (L.
stations[i].sched == SchedStrategy::INF)
788 for (std::size_t r = 0; r < K; ++r) sol.
U(i, r) = sol.
Q(i, r);
790 sol.
C.assign(K, zero);
791 for (std::size_t r = 0; r < K; ++r)
792 for (std::size_t i = 0; i < M; ++i) sol.
C[r] += sol.
R(i, r);
797 for (std::size_t r = 0; r < K; ++r)
800 if (totiter) *totiter =
static_cast<std::size_t
>(it_out);
816 std::size_t iter = 0;
822 std::vector<T> lambda(L.
nclasses, zero);
823 for (std::size_t c = 0; c < L.
nchains; ++c) {
824 if (L.
inchain[c].empty())
continue;
825 const std::size_t rs = L.
classes[L.
inchain[c][0] - 1].refstat - 1;
827 for (std::size_t k : L.
inchain[c]) {
828 if (L.
disabled[rs][k - 1])
continue;
830 if (!std::isfinite(v))
continue;
831 tot += L.
rates(rs, k - 1);
833 for (std::size_t k : L.
inchain[c]) lambda[k - 1] = tot;
835 std::size_t iter = 0;
Base error for the multiprecision C++ port.
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
std::size_t nof_nodes() const
std::vector< std::vector< Distrib< T > > > service
service[i][r], 0-based station and class; a disabled entry marks a pair never visited.
bool has_service_law(std::size_t i, std::size_t r) const
Does (station i, class r) have a service law an analyzer may convert?
std::vector< std::vector< bool > > disabled
std::vector< JobClass > classes
std::vector< Station< T > > stations
stations[k-1] is the k-th station
Matrix< T > rates
(nstations x nclasses) service rates and SCVs, with a PARALLEL disabled flag instead of MATLAB's NaN ...
std::vector< std::vector< std::size_t > > inchain
1-based class indices per chain
std::size_t node_of_station(std::size_t st) const
1-based node index of a station, and the reverse; 0 when absent.
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.
Damped fixed-point iteration, the shared driver of the decomposition algorithms.
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
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.
Compression of a marked MAP into a smaller representation, and the two M3A primitives it is built fro...
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...
FpiResult< T > da_fpi(const std::function< std::pair< std::vector< T >, std::vector< T > >(const std::vector< T > &, std::size_t)> &iterfun, const std::vector< T > &x0, const FpiOptions &options=FpiOptions())
Damped fixed-point iteration, the shared driver of the decomposition algorithms.
mam::Map< T > dist_to_map(const Distrib< T > &d)
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
NodeType
Node kinds, with the values of MATLAB NodeType.
FjSyncMap sn_build_fj_sync_map(const qn::NetworkStruct< T > &sn)
sn_build_fj_sync_map.
std::vector< T > map_acf(const Map< T > &m, const std::vector< unsigned > &lags)
Autocorrelation coefficients of the inter-arrival times at the given lags,.
std::vector< T > mmapph1fcfs_ncmean(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc)
Per-class mean number of customers in the system, BUTools' 'ncMoms', 1.
MmckDetection< T > mam_detect_mmck(const qn::NetworkStruct< T > &L, std::size_t ist, const Mmap< T > &arv)
Port of mam_detect_mmck.m: is the exact M/M/c/K closed form legitimate at this station?
@ MixtureOrder1
'default', 'mixture', 'mixture.order1'
std::vector< std::vector< Map< T > > > DepTable
DEP{i,r}, the departure process of class r from i in (D0,D1) form.
mva::MvaSolution< T > solver_mam_basic_mmap_inner(const qn::NetworkStruct< T > &L, const MamOptions &opt, const MmapDecConfig &cfg, const std::vector< T > &lambda, std::size_t *totiter)
Port of solver_mam_basic_mmap_inner.m.
TrafficConfig traffic_config(const MamOptions &opt)
The traffic step's view of SolverOptions('MAM').
std::vector< T > mmap_lambda(const Mmap< T > &m)
Alias kept for parity with the MATLAB name.
Mmap< T > mmap_hide_but(const Mmap< T > &in, std::size_t keep)
mmap_hide(m, setdiff(1:K, keep)): keep ONE mark, hide every other.
mva::MvaSolution< T > solver_mam_basic_mmap(const qn::NetworkStruct< T > &L, const MamOptions &opt)
Port of solver_mam_basic_mmap.m, the top-level dispatcher of the MMAP fork-join decomposition: an ope...
Map< T > qbd_depproc_etaqa(const Map< T > &arrival, const Map< T > &service, std::size_t n)
MAP descriptor of the departure process of a MAP/MAP/1-FCFS queue, ETAQA-truncated at level n (qbd_de...
QbdMapMap1Result< T > qbd_mapmap1(const Map< T > &arrival, const Map< T > &service_in, const T &util, std::size_t max_levels)
MAP/MAP/1 queue (qbd_mapmap1.m).
std::vector< Mmap< T > > solver_mam_traffic_mmap(const qn::NetworkStruct< T > &sn, const DepTable< T > &DEP, const TrafficConfig &config, const FjSyncMap &fjSyncMap)
Port of solver_mam_traffic_mmap.m, the fork-join aware traffic step.
T map_mean(const Map< T > &m)
Mean inter-arrival time, 1/lambda.
Map< T > qbd_depproc_etaqa_ps(const Map< T > &arrival, const Map< T > &service, std::size_t n)
MAP descriptor of the departure process of a MAP/MAP/1-PS queue, ETAQA-truncated at level n (qbd_depp...
Mmap< T > mmap_compress(const Mmap< T > &in, MmapCompressMethod method)
Compress an MMAP (mmap_compress.m).
Map< T > map_exponential_mean(const T &mean)
Poisson process with the given mean inter-arrival time (map_exponential.m).
Map< T > map_scale(const Map< T > &in, const T &new_mean)
Rescale time so that the mean inter-arrival time becomes new_mean.
mva::MvaSolution< T > solver_mam_basic_mmap_closed(const qn::NetworkStruct< T > &L, const MamOptions &opt, const MmapDecConfig &cfg, std::size_t *totiter)
Port of solver_mam_basic_mmap_closed.m: the per-class bisection on the surrogate arrival rate that ma...
std::vector< T > map_pie(const Map< T > &m)
Phase distribution seen by an arriving job, pie = pi D1 / (pi D1 e).
Map< T > map_normalize(const Map< T > &in)
Clamp negative off-diagonal entries of D0 and negative entries of D1 to zero, then rebuild the diagon...
MmckResult< T > qsys_mmck(const T &lambda, const T &mu, unsigned c, unsigned K)
Exact analysis of the M/M/c/K queue (truncated Erlang form).
A queueing network and its refreshed NetworkStruct.
Departure process of a MAP/MAP/1 queue: the ETAQA-truncated MAP descriptor under FCFS and under PS,...
The MAP/MAP/1 queue solved as a quasi-birth-death process.
Exact analysis of the M/M/c/K queue (truncated Erlang form).
Port of solver_mam_basic.m, the dec.source analyzer and the default algorithm of SolverMAM.
The batch-arrival and batch-service queues of the MAM solver, and the two finite-capacity helpers sol...
Port of solver_mam_traffic.m and solver_mam_traffic_mmap.m: the traffic step of the dec....
Options mirroring the fields MATLAB reads off the options struct.
std::size_t miniter
iterations before the stopping test applies
bool relative_norm
config.da_norm, the increment norm.
The MATLAB GlobalConstants, as reported by lineStart at its defaults.
sn_build_fj_sync_map: which incoming flows at a Join must be synchronized.
The options SolverMAM reads.
int iter_max
SolverOptions('MAM') lowers this from the global 1000 to 100.
std::size_t space_max
options.config.space_max: the order budget of the per-station arrival superposition,...
A MAP as the pair of matrices (D0, D1).
The options.config fields the MMAP decomposition reads on top of MamOptions.
std::size_t etaqa_trunc
config.etaqa_trunc, the ETAQA level truncation of the departure process.
std::size_t fj_sync_q_len
config.fj_sync_q_len, the synchronization queue at a join.
An MMAP: the underlying MAP plus the per-class arrival matrices.
std::size_t order() const
What mam_detect_mmck returns; muRate is meaningful only when isMmck.
The fields of options.config the traffic step reads.
std::size_t fj_sync_q_len
config.fj_sync_q_len, the join's synchronization queue; MATLAB's default.
Class-level results, the [Q,U,R,T,C,X] of the MATLAB analyzers.
double lG
log of the normalizing constant, the reference's lG.
T meanQueueLength
L, mean number in system.