5#ifndef LINE_SOLVERS_MAM_SOLVER_MAM_TRAFFIC_H
6#define LINE_SOLVERS_MAM_SOLVER_MAM_TRAFFIC_H
142namespace traffic_detail {
160 if (v.size() < 3)
throw InputError(
"solver_mam_traffic: a split flow carries no marking");
164 m.Dc.assign(v.begin() + 2, v.end());
170Mmap<T> compress_or_refuse(
const Mmap<T>& m) {
171 if constexpr (num_traits<T>::has_transcendental) {
175 "solver_mam_traffic: the superposition passed config.space_max and must be compressed, "
176 "which needs transcendental arithmetic (aph2_fit); raise space_max or run at double");
182Mmap<T> silent_mmap(std::size_t nclasses) {
183 const T zero = num_traits<T>::from_int(0);
187 m.Dc.assign(nclasses, Matrix<T>(1, 1, zero));
199Mmap<T> dep_cell(
const DepTable<T>& DEP, std::size_t i, std::size_t r) {
200 if (i >=
DEP.size() || r >= DEP[i].size())
return silent_mmap<T>(1);
201 const Map<T>& d =
DEP[i][r];
202 if (d.order() == 0)
return silent_mmap<T>(1);
203 for (std::size_t a = 0; a < d.D0.rows(); ++a)
204 for (std::size_t b = 0; b < d.D0.cols(); ++b)
205 if (npfqn::detail::num_isnan(d.D0(a, b)))
return silent_mmap<T>(1);
209 m.Dc.assign(1, d.D1);
223T total_rate(
const Mmap<T>& m) {
224 const T zero = num_traits<T>::from_int(0);
226 for (std::size_t i = 0; i < m.D1.rows() && silent; ++i)
227 for (std::size_t j = 0; j < m.D1.cols(); ++j)
228 if (!(m.D1(i, j) == zero)) {
232 if (silent)
return zero;
240void reject_opaque_node(
const qn::NetworkStruct<T>& sn, std::size_t a,
bool fj_aware) {
242 const char* what =
nullptr;
244 case qn::NodeType::Router: what =
"Router";
break;
245 case qn::NodeType::Logger: what =
"Logger";
break;
246 case qn::NodeType::Cache: what =
"Cache";
break;
247 case qn::NodeType::Place: what =
"Place";
break;
248 case qn::NodeType::Transition: what =
"Transition";
break;
249 case qn::NodeType::Region: what =
"Region";
break;
250 case qn::NodeType::Fork:
251 case qn::NodeType::Join:
252 if (fj_aware)
return;
254 std::string(
"solver_mam_traffic: node '") + sn.nodes[a].name +
255 "' is a Fork or a Join, which the plain traffic step has no branch for; the "
256 "fork-join variant is solver_mam_traffic_mmap, which synchronizes the join with "
260 throw UnsupportedError(std::string(
"solver_mam_traffic: node '") + sn.nodes[a].name +
"' is a " +
262 " node. The reference's node switch has no branch for it, so it emits "
263 "no outgoing link and every node downstream of it is described as "
264 "receiving no traffic at all; that is a wrong traffic table with no "
265 "symptom, not a conservative approximation");
288 if (k == 0)
throw InputError(
"mmap_max: the synchronization queue must hold at least one job");
290 throw InputError(
"mmap_max: the two flows carry different numbers of classes");
292 const std::size_t na = a.
order(), nbp = b.
order(), n = na * nbp, nblk = 1 + 2 * k;
301 for (std::size_t i = 0; i < B.rows(); ++i)
302 for (std::size_t j = 0; j < B.cols(); ++j) M(rb * n + i, cb * n + j) = B(i, j);
309 for (std::size_t blk = 1; blk + 2 <= 2 * k; ++blk) put(M0, blk, blk, A0B0);
310 put(M0, 2 * k - 1, 2 * k - 1, IAB0);
311 put(M0, 2 * k, 2 * k, A0IB);
312 for (std::size_t i = 2; i <= k; ++i) {
313 const std::size_t rb = 1 + 2 * (i - 2), cb = 3 + 2 * (i - 2);
314 put(M0, rb, cb, A1IB);
315 put(M0, rb + 1, cb + 1, IAB1);
324 for (std::size_t i = 2; i <= k; ++i) {
325 const std::size_t rb = 1 + 2 * (i - 1), cb = 1 + 2 * (i - 2);
326 put(M, rb, cb, bArr);
327 put(M, rb + 1, cb + 1, aArr);
334 out.
D1 = emissions(IAB1, A1IB);
336 for (std::size_t c = 0; c < a.
classes(); ++c)
337 out.
Dc.push_back(emissions(
kron(Ia, b.
Dc[c]),
kron(a.
Dc[c], Ib)));
341namespace traffic_detail {
345 std::vector<bool> is_ncs;
346 std::vector<std::size_t> to_ncs;
347 std::vector<std::size_t> keep;
348 std::size_t inc_count = 0;
352NcsIndex build_ncs_index(
const qn::NetworkStruct<T>& sn) {
353 const std::size_t I = sn.nof_nodes(), R = sn.nclasses;
355 x.is_ncs.assign(I,
false);
356 x.to_ncs.assign(I, 0);
357 for (std::size_t a = 0; a < I; ++a) {
358 if (sn.nodes[a].nodetype == qn::NodeType::ClassSwitch)
continue;
360 x.to_ncs[a] = ++x.inc_count;
361 for (std::size_t r = 0; r < R; ++r) x.keep.push_back(a * R + r);
368Mmap<T> superpose_classes(
const DepTable<T>& DEP, std::size_t row, std::size_t R,
369 const TrafficConfig& config) {
370 Mmap<T> d = dep_cell(DEP, row, 0);
371 for (std::size_t r = 1; r < R; ++r) {
373 if (d.order() > config.space_max) d = compress_or_refuse(d);
380Matrix<T> split_probabilities(
const Matrix<T>& rtncs, std::size_t inc, std::size_t Inc,
382 Matrix<T> P(R, Inc * R, num_traits<T>::from_int(0));
383 for (std::size_t r = 0; r < R; ++r)
384 for (std::size_t jnc = 0; jnc < Inc; ++jnc)
385 for (std::size_t s = 0; s < R; ++s)
386 P(r, jnc * R + s) = rtncs(inc * R + r, jnc * R + s);
395Mmap<T> no_flow_arrival(
const std::vector<std::vector<Mmap<T>>>& LINKS, std::size_t inc,
397 for (std::size_t jnc = 0; jnc < LINKS.size(); ++jnc)
398 if (LINKS[jnc][inc].order() != 0)
return LINKS[jnc][inc];
399 return silent_mmap<T>(R);
414 const std::size_t I =
sn.nof_nodes(), K =
sn.nclasses;
417 m.
node_sync.assign(I, std::vector<std::size_t>(I, 0));
418 for (
const std::pair<std::size_t, std::size_t>& fjp :
sn.fj) {
419 const std::size_t f = fjp.first - 1, j = fjp.second - 1;
420 const std::size_t gid = ++m.
ngroups;
423 for (std::size_t a = 0; a < I; ++a) {
424 if (a == f || a == j)
continue;
425 bool from_fork =
false, to_join =
false;
426 for (std::size_t k = 0; k < K; ++k) {
427 if (
sn.rtnodes(f * K + k, a * K + k) > zero) from_fork =
true;
428 if (
sn.rtnodes(a * K + k, j * K + k) > zero) to_join =
true;
430 if (from_fork && to_join) m.
node_sync[j][a] = gid;
450 namespace td = traffic_detail;
451 const std::size_t I =
sn.nof_nodes(), R =
sn.nclasses;
452 if (R == 0)
throw InputError(
"solver_mam_traffic: the model has no classes");
454 const td::NcsIndex x = td::build_ncs_index(
sn);
455 const std::size_t Inc = x.inc_count;
460 std::vector<std::vector<Mmap<T>>> LINKS(Inc, std::vector<
Mmap<T>>(Inc));
461 for (std::size_t a = 0; a < I; ++a) {
462 if (!x.is_ncs[a])
continue;
464 if (ty != qn::NodeType::Source && ty != qn::NodeType::Delay && ty != qn::NodeType::Queue) {
465 td::reject_opaque_node(
sn, a,
false);
468 const std::size_t inc = x.to_ncs[a] - 1;
469 const std::size_t ist =
sn.nodes[a].station;
471 throw InputError(
"solver_mam_traffic: node '" +
sn.nodes[a].name +
472 "' serves jobs but carries no station index");
473 const Mmap<T> dep = td::superpose_classes(DEP, ist - 1, R, config);
474 const Matrix<T> Psplit = td::split_probabilities(rtncs, inc, Inc, R);
475 const std::vector<npfqn::Mmap<T>> F =
477 for (std::size_t jnc = 0; jnc < Inc; ++jnc)
482 std::vector<Mmap<T>> ARV(I);
483 for (std::size_t a = 0; a < I; ++a) {
484 if (!x.is_ncs[a] ||
sn.nodes[a].nodetype == qn::NodeType::Source)
continue;
485 const std::size_t inc = x.to_ncs[a] - 1;
486 std::vector<Mmap<T>> flows;
487 for (std::size_t jnc = 0; jnc < Inc; ++jnc) {
488 const Mmap<T>& lk = LINKS[jnc][inc];
489 if (lk.
order() == 0)
continue;
495 else if (flows.size() == 1) ARV[a] = flows[0];
496 else ARV[a] = td::no_flow_arrival(LINKS, inc, R);
519 namespace td = traffic_detail;
520 const std::size_t I =
sn.nof_nodes(), R =
sn.nclasses;
521 if (R == 0)
throw InputError(
"solver_mam_traffic_mmap: the model has no classes");
523 throw InputError(
"solver_mam_traffic_mmap: the sync map is not indexed over the nodes");
525 const td::NcsIndex x = td::build_ncs_index(
sn);
526 const std::size_t Inc = x.inc_count;
529 std::vector<std::vector<std::size_t>> syncNCS(Inc, std::vector<std::size_t>(Inc, 0));
530 for (std::size_t a = 0; a < I; ++a) {
531 if (!x.is_ncs[a])
continue;
532 for (std::size_t b = 0; b < I; ++b)
533 if (x.is_ncs[b] && fjSyncMap.
node_sync[a][b] > 0)
534 syncNCS[x.to_ncs[a] - 1][x.to_ncs[b] - 1] = fjSyncMap.
node_sync[a][b];
537 std::vector<std::vector<Mmap<T>>> LINKS(Inc, std::vector<
Mmap<T>>(Inc));
538 for (std::size_t a = 0; a < I; ++a) {
539 if (!x.is_ncs[a])
continue;
541 if (ty != qn::NodeType::Source && ty != qn::NodeType::Delay &&
542 ty != qn::NodeType::Queue && ty != qn::NodeType::Fork && ty != qn::NodeType::Join) {
543 td::reject_opaque_node(
sn, a,
true);
546 const std::size_t inc = x.to_ncs[a] - 1;
547 const Mmap<T> dep = td::superpose_classes(DEP, a, R, config);
548 const Matrix<T> Psplit = td::split_probabilities(rtncs, inc, Inc, R);
549 const std::vector<npfqn::Mmap<T>> F =
551 for (std::size_t jnc = 0; jnc < Inc; ++jnc)
555 std::vector<Mmap<T>> ARV(I);
556 for (std::size_t a = 0; a < I; ++a) {
557 if (!x.is_ncs[a] ||
sn.nodes[a].nodetype == qn::NodeType::Source)
continue;
558 const std::size_t inc = x.to_ncs[a] - 1;
561 std::map<std::size_t, std::vector<Mmap<T>>> groups;
562 std::vector<Mmap<T>> independent;
563 for (std::size_t jnc = 0; jnc < Inc; ++jnc) {
564 const Mmap<T>& lk = LINKS[jnc][inc];
565 if (lk.
order() == 0)
continue;
568 const std::size_t gid = syncNCS[inc][jnc];
569 if (gid == 0) independent.push_back(lk);
570 else groups[gid].push_back(lk);
573 std::vector<Mmap<T>> flows;
574 for (
const auto& g : groups) {
575 Mmap<T> synced = g.second.front();
576 for (std::size_t f = 1; f < g.second.size(); ++f) {
578 if (synced.
order() > config.
space_max) synced = td::compress_or_refuse(synced);
580 flows.push_back(synced);
582 flows.insert(flows.end(), independent.begin(), independent.end());
585 else if (flows.size() == 1) ARV[a] = flows[0];
586 else ARV[a] = td::no_flow_arrival(LINKS, inc, R);
UnsupportedError(const std::string &what)
A network plus its refreshed NetworkStruct.
Stochastic complement of a DTMC partition, a port of matlab/lib/kpctoolbox/mc/dtmc_stochcomp....
The exception types the port throws.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
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...
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.
Mmap< T > mmap_normalize(const Mmap< T > &in)
Clamp negative off-diagonal and per-class entries to zero and rebuild D1 and the diagonal of D0 from ...
Mmap< T > mmap_max(const Mmap< T > &a, const Mmap< T > &b, unsigned k)
MMAP of the maximum over k synchronization rounds of two independent MMAPs.
@ 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.
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.
Matrix< T > krons(const Matrix< T > &A, const Matrix< T > &B)
Kronecker sum, MATLAB's krons: kron(A, I_nb) + kron(I_na, B).
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.
Matrix< T > kron(const Matrix< T > &A, const Matrix< T > &B)
Kronecker product.
Mmap< T > mmap_compress(const Mmap< T > &in, MmapCompressMethod method)
Compress an MMAP (mmap_compress.m).
Mmap< T > mmap_super(const Mmap< T > &a, const Mmap< T > &b)
Superposition of two MMAPs: the phase process is the product chain, and the class list of the result ...
std::vector< Mmap< T > > solver_mam_traffic(const qn::NetworkStruct< T > &sn, const DepTable< T > &DEP, const TrafficConfig &config)
Port of solver_mam_traffic.m.
Matrix< T > dtmc_stochcomp(const Matrix< T > &P, const std::vector< std::size_t > &keep)
Stochastic complement of a DTMC partition, a port of matlab/lib/kpctoolbox/mc/dtmc_stochcomp....
std::vector< Mmap< T > > npfqn_traffic_split_cs(const Mmap< T > &MMAP, const Matrix< T > &P)
Splitting of a marked MAP departure flow with class switching.
std::vector< Matrix< T > > Mmap
An MMAP as the MATLAB cell {D0, D1, D1^(1), ..., D1^(R)}.
mam::Mmap< T > npfqn_traffic_merge(const std::vector< mam::Mmap< T > > &flows, const MergeConfig &config=MergeConfig())
Merge a list of MMAPs carrying the same classes.
Matrix< T > eye(std::size_t n)
Identity of order n.
A queueing network and its refreshed NetworkStruct.
Superposition of several marked arrival flows into one.
Splitting of a marked MAP departure flow with class switching.
static constexpr double FineTol
sn_build_fj_sync_map: which incoming flows at a Join must be synchronized.
std::vector< std::size_t > join_of_group
1-based node index, by group
std::vector< std::size_t > fork_of_group
1-based node index, by group
std::vector< std::vector< std::size_t > > node_sync
The options SolverMAM reads.
An MMAP: the underlying MAP plus the per-class arrival matrices.
std::size_t classes() const
std::vector< Matrix< T > > Dc
per-class matrices, sum_c Dc = D1
std::size_t order() const
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.