LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
npfqn_traffic_split_cs.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_API_NPFQN_TRAFFIC_SPLIT_CS_H
6#define LINE_API_NPFQN_TRAFFIC_SPLIT_CS_H
7
8/**
9 * @file
10 * @ingroup api_npfqn
11 * Splitting of a marked MAP departure flow with class switching.
12 *
13 * Templated port of matlab/src/api/npfqn/npfqn_traffic_split_cs.m,
14 * cross-checked against
15 * jar/src/main/java/jline/api/npfqn/Npfqn_traffic_split_cs.java (identical).
16 *
17 * An MMAP is the cell {D0, D1, D1^(1), ..., D1^(R)}: the hidden generator D0,
18 * the aggregate arrival matrix D1 = sum_r D1^(r), and one marking matrix per
19 * class. P(r, (j-1)R + s) is the probability that a class-r departure flows to
20 * destination j in class s. For each destination j the port builds
21 *
22 * D1^(s)_j = sum_r D1^(r) P(r, (j-1)R + s)
23 * D1_j = sum_s D1^(s)_j
24 * D0_j = D0 + D1 - D1_j
25 *
26 * i.e. everything not routed to j is folded back into the hidden part, which
27 * is exactly the MATLAB accumulation written out.
28 *
29 * Arithmetic. Only additions and multiplications by routing probabilities,
30 * plus the max(.,0) clipping of mmap_normalize, so the algorithm is exact at
31 * T = Rational and needs no transcendental function.
32 *
33 * mmap_normalize (matlab/lib/m3a/m3a/mmap/mmap_normalize.m, JAR
34 * jline.api.mam.Mmap_normalize) is inlined here as a detail helper because the
35 * mam/M3A domain is not part of this port. It is a verbatim transcription: the
36 * NaN test mirrors MATLAB's "if isnan(X)" on a matrix, which is true only when
37 * every entry is NaN, and is vacuously false in an exact field.
38 */
39
40#include <cstddef>
41#include <vector>
42
44#include "line/num/number.h"
45#include "line/util/error.h"
46#include "line/util/matrix.h"
47
48namespace line {
49namespace npfqn {
50
51/** An MMAP as the MATLAB cell {D0, D1, D1^(1), ..., D1^(R)}. */
52template <class T>
53using Mmap = std::vector<Matrix<T>>;
54
55namespace detail {
56
57/**
58 * Port of mmap_normalize: clip the off-diagonal of D0 and every marking matrix
59 * at zero, rebuild D1 as the sum of the markings, and reset the diagonal of D0
60 * so that D0 + D1 is a generator.
61 */
62template <class T>
63void mmap_normalize(Mmap<T>& M) {
64 if (M.empty()) return;
65 const T zero = num_traits<T>::from_int(0);
66 const std::size_t K = M[0].rows();
67 const std::size_t C = M.size() - 2;
68
69 for (std::size_t i = 0; i < K; ++i)
70 for (std::size_t j = 0; j < K; ++j)
71 if (i != j && M[0](i, j) < zero) M[0](i, j) = zero;
72
73 M[1] = Matrix<T>(M[0].rows(), M[0].cols(), zero);
74 for (std::size_t c = 0; c < C; ++c) {
75 Matrix<T>& Dc = M[2 + c];
76 bool allNaN = Dc.rows() > 0 && Dc.cols() > 0;
77 for (std::size_t i = 0; i < Dc.rows(); ++i)
78 for (std::size_t j = 0; j < Dc.cols(); ++j) {
79 if (!num_isnan(Dc(i, j))) allNaN = false;
80 if (Dc(i, j) < zero) Dc(i, j) = zero;
81 }
82 if (allNaN) Dc = Matrix<T>(Dc.rows(), Dc.cols(), zero);
83 for (std::size_t i = 0; i < Dc.rows(); ++i)
84 for (std::size_t j = 0; j < Dc.cols(); ++j) M[1](i, j) += Dc(i, j);
85 }
86
87 for (std::size_t k = 0; k < K; ++k) {
88 M[0](k, k) = zero;
89 T s = zero;
90 for (std::size_t j = 0; j < M[0].cols(); ++j) s += M[0](k, j);
91 for (std::size_t j = 0; j < M[1].cols(); ++j) s += M[1](k, j);
92 M[0](k, k) = -s;
93 }
94}
95
96} // namespace detail
97
98/**
99 * @brief Splitting of a marked MAP departure flow with class switching.
100 *
101 * @param MMAP the departure MMAP {D0, D1, D1^(1), ..., D1^(R)}
102 * @param P (R x J) with J = M*R, P(r, (j-1)R + s) in MATLAB 1-based terms
103 * @return one MMAP per destination station, in destination order
104 */
105template <class T>
106std::vector<Mmap<T>> npfqn_traffic_split_cs(const Mmap<T>& MMAP, const Matrix<T>& P) {
107 if (MMAP.size() < 3) throw InputError("npfqn_traffic_split_cs: MMAP has no marking matrices");
108 const std::size_t R = P.rows();
109 const std::size_t J = P.cols();
110 if (R == 0 || J % R != 0)
111 throw InputError("npfqn_traffic_split_cs: the class-switching matrix is not R x (M R)");
112 if (MMAP.size() - 2 != R)
113 throw InputError("npfqn_traffic_split_cs: MMAP and P disagree on the class count");
114 const std::size_t M = J / R;
115
116 const T zero = num_traits<T>::from_int(0);
117 const std::size_t n = MMAP[0].rows();
118
119 std::vector<Mmap<T>> SMMAP(M);
120 for (std::size_t jst = 0; jst < M; ++jst) {
121 Mmap<T>& S = SMMAP[jst];
122 S.assign(2 + R, Matrix<T>(n, MMAP[0].cols(), zero));
123 // D0 starts as D0 + D1, with every arrival folded into the hidden part
124 for (std::size_t a = 0; a < n; ++a)
125 for (std::size_t b = 0; b < MMAP[0].cols(); ++b)
126 S[0](a, b) = MMAP[0](a, b) + MMAP[1](a, b);
127 for (std::size_t s = 0; s < R; ++s) {
128 for (std::size_t r = 0; r < R; ++r) {
129 const T& p = P(r, jst * R + s);
130 for (std::size_t a = 0; a < n; ++a)
131 for (std::size_t b = 0; b < MMAP[2 + r].cols(); ++b) {
132 const T v = MMAP[2 + r](a, b) * p;
133 S[2 + s](a, b) += v;
134 S[1](a, b) += v;
135 S[0](a, b) -= v;
136 }
137 }
138 }
139 detail::mmap_normalize(S);
140 }
141 return SMMAP;
142}
143
144} // namespace npfqn
145} // namespace line
146
147#endif // LINE_API_NPFQN_TRAFFIC_SPLIT_CS_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Dense matrix and non-owning view.
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)}.
Shared arithmetic helpers for the templated npfqn port.
Number-type abstraction for the templated API port.