5#ifndef LINE_API_MDD_MDD_MCD_H
6#define LINE_API_MDD_MDD_MCD_H
57std::vector<T> mcd_node_marginal(
const std::vector<std::pair<int, int>>& rows,
58 const std::vector<T>& pk,
int nnodes) {
59 std::vector<T> pr(
static_cast<std::size_t
>(nnodes), num_traits<T>::from_int(0));
60 for (std::size_t r = 0; r < rows.size(); ++r) pr[rows[r].first - 1] += pk[r];
65std::vector<std::vector<T>> mcd_identity(std::size_t n) {
66 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
67 std::vector<std::vector<T>> I(n, std::vector<T>(n, zero));
68 for (std::size_t i = 0; i < n; ++i) I[i][i] = one;
74std::vector<std::vector<T>> mcd_generator(
const std::vector<std::vector<T>>& R) {
75 const std::size_t n = R.size();
76 const T zero = num_traits<T>::from_int(0);
77 std::vector<std::vector<T>> Q = R;
78 for (std::size_t i = 0; i < n; ++i) {
80 for (std::size_t j = 0; j < n; ++j) s += R[i][j];
95std::vector<T> mcd_lstsq(
const std::vector<std::vector<T>>& A,
const std::vector<T>& b) {
97 const std::size_t m = A.size(), n = A[0].size();
98 const T zero = num_traits<T>::from_int(0), two = num_traits<T>::from_int(2);
99 std::vector<std::vector<T>> R = A;
100 std::vector<T> y = b;
101 for (std::size_t k = 0; k < n; ++k) {
103 for (std::size_t i = k; i < m; ++i) norm2 += T(R[i][k] * R[i][k]);
104 if (norm2 == zero)
continue;
105 T norm = T(sqrt(norm2));
106 if (R[k][k] > zero) norm = T(-norm);
107 std::vector<T> v(m, zero);
108 for (std::size_t i = k; i < m; ++i) v[i] = R[i][k];
111 for (std::size_t i = k; i < m; ++i) vtv += T(v[i] * v[i]);
112 if (vtv == zero)
continue;
113 for (std::size_t j = k; j < n; ++j) {
115 for (std::size_t i = k; i < m; ++i) dot += T(v[i] * R[i][j]);
116 const T f = T(two * dot / vtv);
117 for (std::size_t i = k; i < m; ++i) R[i][j] -= T(f * v[i]);
120 for (std::size_t i = k; i < m; ++i) dot += T(v[i] * y[i]);
121 const T f = T(two * dot / vtv);
122 for (std::size_t i = k; i < m; ++i) y[i] -= T(f * v[i]);
124 std::vector<T> x(n, zero);
125 for (std::size_t ii = n; ii > 0; --ii) {
126 const std::size_t i = ii - 1;
128 for (std::size_t j = i + 1; j < n; ++j) s -= T(R[i][j] * x[j]);
129 x[i] = R[i][i] == zero ? zero : T(s / R[i][i]);
153std::vector<T> mcd_solve_stat(
const std::vector<std::vector<T>>& Q) {
154 const std::size_t n = Q.size();
155 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
156 if (n == 1)
return std::vector<T>(1, one);
157 std::vector<std::vector<T>> A(n + 1, std::vector<T>(n, zero));
158 for (std::size_t i = 0; i < n; ++i)
159 for (std::size_t j = 0; j < n; ++j) A[j][i] = Q[i][j];
160 for (std::size_t j = 0; j < n; ++j) A[n][j] = one;
161 std::vector<T> rhs(n + 1, zero);
164 if constexpr (num_traits<T>::is_exact) {
165 Matrix<T> Am(n + 1, n, zero);
166 for (std::size_t i = 0; i <= n; ++i)
167 for (std::size_t j = 0; j < n; ++j) Am(i, j) = A[i][j];
170 p = mcd_lstsq(A, rhs);
173 for (std::size_t i = 0; i < n; ++i) {
174 if (p[i] < zero) p[i] = zero;
177 const double sd = num_traits<T>::to_double(s);
178 if (!(sd > 0) || std::isnan(sd) || std::isinf(sd))
179 throw NumericError(
"mdd_mcd: level CTMC of order " + std::to_string(n) +
180 " admits no proper stationary distribution (the level generator is "
181 "reducible or numerically degenerate).");
182 for (std::size_t i = 0; i < n; ++i) p[i] = T(p[i] / s);
200inline void mcd_path_counts(
const MddStruct& mdds, std::size_t K,
201 std::vector<std::vector<double>>& above,
202 std::vector<std::vector<double>>& below) {
203 below.assign(K, std::vector<double>());
204 above.assign(K, std::vector<double>());
205 for (std::size_t oo = K; oo > 0; --oo) {
206 const std::size_t oL = oo - 1;
207 std::vector<double> nb(
static_cast<std::size_t
>(mdds.nnodes[oL]), 0.0);
208 for (std::size_t p = 0; p < nb.size(); ++p) {
210 for (
int v = 0; v < mdds.domain[oL]; ++v) {
211 const int ch = mdds.node[oL][p][v];
215 s += below[oL + 1][ch - 1];
222 for (std::size_t oL = 0; oL < K; ++oL)
223 above[oL].assign(
static_cast<std::size_t
>(mdds.nnodes[oL]), 0.0);
224 above[0][mdds.root - 1] = 1.0;
225 for (std::size_t oL = 0; oL + 1 < K; ++oL) {
226 for (std::size_t p = 0; p < above[oL].size(); ++p) {
227 const double w = above[oL][p];
228 if (w == 0)
continue;
229 for (
int v = 0; v < mdds.domain[oL]; ++v) {
230 const int ch = mdds.node[oL][p][v];
231 if (ch > 0) above[oL + 1][ch - 1] += w;
250std::vector<std::vector<T>> mcd_uniform_init(
251 const MddStruct& mdds,
const std::vector<std::vector<std::pair<int, int>>>& Mrows,
252 std::size_t K,
const std::vector<std::vector<double>>& above,
253 const std::vector<std::vector<double>>& below) {
254 std::vector<std::vector<T>> pik(K);
255 for (std::size_t k = 0; k < K; ++k) {
256 const std::size_t oL = K - 1 - k;
257 const std::vector<std::pair<int, int>>& rows = Mrows[k];
258 std::vector<double> w(rows.size(), 0.0);
260 for (std::size_t r = 0; r < rows.size(); ++r) {
261 const int p = rows[r].first, v = rows[r].second;
264 val = above[oL][p - 1];
266 const int ch = mdds.node[oL][p - 1][v];
267 val = above[oL][p - 1] * below[oL + 1][ch - 1];
272 pik[k].assign(rows.size(), num_traits<T>::from_int(0));
273 for (std::size_t r = 0; r < rows.size(); ++r)
274 pik[k][r] = num_traits<T>::from_double(w[r] / sum);
289std::vector<std::vector<std::vector<T>>> mcd_compute_as(
290 std::size_t k,
const std::vector<std::vector<std::vector<T>>>& Aup,
291 const std::vector<std::vector<std::vector<int>>>& Pnode,
292 const std::vector<std::vector<T>>& pik,
const std::vector<std::vector<MddLocalMatrix<T>>>& W,
293 const std::vector<std::vector<std::pair<int, int>>>& Mrows,
const std::vector<int>& nn,
295 const T zero = num_traits<T>::from_int(0);
296 const std::vector<std::pair<int, int>>& rows1 = Mrows[k + 1];
297 std::vector<T> pr_above(
static_cast<std::size_t
>(nn[k]), zero);
298 for (std::size_t r = 0; r < rows1.size(); ++r) {
299 const int child = Pnode[k + 1][rows1[r].first - 1][rows1[r].second];
300 if (child > 0) pr_above[child - 1] += pik[k + 1][r];
302 std::vector<std::vector<std::vector<T>>> Ak(
303 E, std::vector<std::vector<T>>(
static_cast<std::size_t
>(nn[k]),
304 std::vector<T>(
static_cast<std::size_t
>(nn[k]), zero)));
305 for (std::size_t r = 0; r < rows1.size(); ++r) {
306 const int p = rows1[r].first;
307 const int v = rows1[r].second;
308 const int childp = Pnode[k + 1][p - 1][v];
309 if (childp <= 0 || !(pr_above[childp - 1] > zero))
continue;
310 const T adjust = T(pik[k + 1][r] / pr_above[childp - 1]);
311 for (std::size_t e = 0; e < E; ++e) {
312 const std::vector<std::size_t>& wcols = W[e][k + 1].cols[v];
313 if (wcols.empty())
continue;
314 const std::vector<T>& wvals = W[e][k + 1].vals[v];
315 const std::vector<T>& arow = Aup[e][p - 1];
316 for (std::size_t wi = 0; wi < wcols.size(); ++wi) {
317 const std::size_t w = wcols[wi];
318 const T wv = wvals[wi];
319 for (std::size_t q = 0; q < arow.size(); ++q) {
320 if (arow[q] == zero)
continue;
321 const int childq = Pnode[k + 1][q][w];
322 if (childq <= 0)
continue;
323 Ak[e][childp - 1][childq - 1] += T(arow[q] * wv * adjust);
336std::vector<std::vector<T>> mcd_compute_mc(
337 std::size_t k,
const std::vector<std::vector<std::vector<T>>>& Ak,
338 const std::vector<std::vector<std::vector<T>>>& bcell,
339 const std::vector<std::vector<std::vector<int>>>& Pnode,
340 const std::vector<std::vector<MddLocalMatrix<T>>>& W,
341 const std::vector<std::vector<std::pair<int, int>>>& Mrows,
342 const std::vector<std::vector<int>>& Midx,
const std::vector<std::size_t>& level_sizes,
343 const std::vector<int>& dom, std::size_t E) {
344 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
345 const std::size_t nm = level_sizes[k];
346 const std::vector<std::pair<int, int>>& rows = Mrows[k];
347 std::vector<std::vector<T>> Rk(nm, std::vector<T>(nm, zero));
348 for (std::size_t r = 0; r < nm; ++r) {
349 const int p = rows[r].first;
350 const int v = rows[r].second;
351 for (std::size_t e = 0; e < E; ++e) {
352 const std::vector<std::size_t>& wcols = W[e][k].cols[v];
353 if (wcols.empty())
continue;
355 if (k > 0) bfac = bcell[k - 1][Pnode[k][p - 1][v] - 1][e];
356 if (bfac == zero)
continue;
357 const std::vector<T>& wvals = W[e][k].vals[v];
358 const std::vector<T>& arow = Ak[e][p - 1];
359 for (std::size_t wi = 0; wi < wcols.size(); ++wi) {
360 const std::size_t w = wcols[wi];
361 const T wv = wvals[wi];
362 for (std::size_t q = 0; q < arow.size(); ++q) {
363 if (arow[q] == zero)
continue;
364 const int di = Midx[k][q *
static_cast<std::size_t
>(dom[k]) + w];
365 if (di == 0)
continue;
366 Rk[r][di - 1] += T(arow[q] * wv * bfac);
387 const std::size_t K = mdds.
K;
388 if (K == 0)
throw InputError(
"mdd_mcd: the diagram has no levels");
391 std::vector<std::vector<std::vector<int>>> Pnode(K);
392 std::vector<int> nn(K, 0), dom(K, 0);
393 for (std::size_t k = 0; k < K; ++k) {
394 const std::size_t oL = K - 1 - k;
395 Pnode[k] = mdds.
node[oL];
402 const std::size_t E = desc.
events.size();
403 std::vector<std::vector<MddLocalMatrix<T>>> W(E, std::vector<
MddLocalMatrix<T>>(K));
404 std::vector<std::vector<bool>> touched(E, std::vector<bool>(K,
false));
405 for (std::size_t e = 0; e < E; ++e) {
407 for (std::size_t t = 0; t < ev.
lev.size(); ++t) {
408 const std::size_t pk = K - 1 - ev.
lev[t];
410 touched[e][pk] =
true;
412 for (std::size_t k = 0; k < K; ++k)
418 std::vector<std::vector<std::pair<int, int>>> Mrows(K);
419 std::vector<std::vector<int>> Midx(K);
420 std::vector<std::size_t> level_sizes(K, 0);
421 for (std::size_t k = 0; k < K; ++k) {
423 for (
int v = 0; v < dom[k]; ++v) {
424 for (
int p = 0; p < nn[k]; ++p) {
425 const int ch = Pnode[k][p][v];
426 const bool live = (k == 0) ? (ch ==
TERM_TRUE) : (ch > 0);
427 if (live) Mrows[k].push_back(std::make_pair(p + 1, v));
430 level_sizes[k] = Mrows[k].size();
431 Midx[k].assign(
static_cast<std::size_t
>(nn[k]) *
static_cast<std::size_t
>(dom[k]), 0);
432 for (std::size_t r = 0; r < Mrows[k].size(); ++r)
433 Midx[k][
static_cast<std::size_t
>(Mrows[k][r].first - 1) *
434 static_cast<std::size_t
>(dom[k]) +
435 static_cast<std::size_t
>(Mrows[k][r].second)] =
static_cast<int>(r + 1);
439 std::vector<std::vector<double>> above, below;
440 detail::mcd_path_counts(mdds, K, above, below);
441 std::vector<std::vector<T>> pik;
442 if (!options.initpik.empty()) {
443 pik.assign(K, std::vector<T>());
444 for (std::size_t k = 0; k < K; ++k) {
445 pik[k].assign(options.initpik[k].size(), zero);
446 for (std::size_t r = 0; r < options.initpik[k].size(); ++r)
450 pik = detail::mcd_uniform_init<T>(mdds, Mrows, K, above, below);
452 std::vector<std::vector<T>> Prp(K);
453 for (std::size_t k = 0; k < K; ++k) Prp[k] = detail::mcd_node_marginal(Mrows[k], pik[k], nn[k]);
457 bool converged =
false;
459 for (
int it = 1; it <= options.maxiter; ++it) {
461 const std::vector<std::vector<T>> piold = pik;
465 std::vector<std::vector<std::vector<T>>> bcell(K);
466 for (std::size_t k = 0; k < K; ++k) {
467 std::vector<std::vector<T>> bk(
static_cast<std::size_t
>(nn[k]),
468 std::vector<T>(E, zero));
469 const std::vector<std::pair<int, int>>& rows = Mrows[k];
470 for (std::size_t r = 0; r < rows.size(); ++r) {
471 const int p = rows[r].first;
472 const int v = rows[r].second;
473 if (!(Prp[k][p - 1] > zero))
continue;
474 const T adjust = T(pik[k][r] / Prp[k][p - 1]);
475 for (std::size_t e = 0; e < E; ++e) {
476 const T le = W[e][k].row_sum[v];
477 if (le == zero)
continue;
479 if (k > 0) down = bcell[k - 1][Pnode[k][p - 1][v] - 1][e];
480 bk[p - 1][e] += T(adjust * down * le);
487 std::vector<std::vector<std::vector<std::vector<T>>>> Acell(K);
488 Acell[K - 1].assign(E, std::vector<std::vector<T>>());
489 for (std::size_t e = 0; e < E; ++e)
490 Acell[K - 1][e] = detail::mcd_identity<T>(
static_cast<std::size_t
>(nn[K - 1]));
491 for (std::size_t kk = K; kk > 0; --kk) {
492 const std::size_t k = kk - 1;
494 Acell[k] = detail::mcd_compute_as(k, Acell[k + 1], Pnode, pik, W, Mrows, nn, E);
496 const std::vector<std::vector<T>> Rk =
497 detail::mcd_compute_mc(k, Acell[k], bcell, Pnode, W, Mrows, Midx, level_sizes,
499 pik[k] = detail::mcd_solve_stat(detail::mcd_generator(Rk));
500 Prp[k] = detail::mcd_node_marginal(Mrows[k], pik[k], nn[k]);
506 for (std::size_t k = 0; k < K; ++k) {
508 for (std::size_t r = 0; r < pik[k].size(); ++r) {
510 dk = std::max(dk, d < 0 ? -d : d);
512 if (std::isnan(dk) || std::isinf(dk))
513 throw NumericError(
"mdd_mcd: level " + std::to_string(k + 1) +
514 " iterate is not finite at iteration " + std::to_string(it) +
515 "; the level CTMC did not yield a proper stationary vector.");
516 delta = std::max(delta, dk);
518 if (delta < options.tol) {
524 throw NumericError(
"mdd_mcd: the coupled level iteration did not converge in " +
525 std::to_string(options.maxiter) +
" sweeps (last change " +
526 std::to_string(delta) +
" against tol " + std::to_string(options.tol) +
527 "); the level marginals returned would not be a fixed point. Raise "
528 "maxiter or relax tol.");
533 const bool is_qn = !desc.
mu.empty() && !desc.
servers.empty();
535 out.
QLen.assign(K, zero);
537 out.
X.assign(K, zero);
538 out.
U.assign(K, zero);
540 for (std::size_t s = 0; s < K; ++s) {
541 const std::size_t k = K - 1 - s;
542 const std::vector<std::pair<int, int>>& rows = Mrows[k];
543 T q = zero, busy = zero;
544 for (std::size_t r = 0; r < rows.size(); ++r) {
545 const double vd = desc.
valuemap.empty()
546 ?
static_cast<double>(rows[r].second)
547 : desc.
valuemap[s][
static_cast<std::size_t
>(rows[r].second)];
549 q += T(v * pik[k][r]);
551 const double srv = desc.
servers[s];
553 busy += T(cap * pik[k][r]);
558 out.
X[s] = T(desc.
mu[s] * busy);
559 out.
U[s] = std::isinf(desc.
servers[s])
572 if (winv.empty() && desc.
N > 0) {
578 for (std::size_t s = 0; s < K; ++s)
580 const double scale = std::max(1.0, std::fabs(vinv));
581 if (std::fabs(got - vinv) > 1e-6 * scale)
582 throw NumericError(
"mdd_mcd: the level marginals converged to an invariant value of " +
583 std::to_string(got) +
" against the model value " +
584 std::to_string(vinv) +
", so the fixed point reached is degenerate "
585 "(the level chains are mutually inconsistent). Supply initpik with "
586 "a consistent starting law.");
595 for (std::size_t k = 0; k < K; ++k) {
596 const std::size_t oL = K - 1 - k;
598 for (std::size_t p = 0; p < above[oL].size(); ++p) mx = std::max(mx, above[oL][p]);
600 if (mx > 1.0 + 1e-12) no_agg =
false;
NumericError(const std::string &what)
The exception types the port throws.
Least squares for a rectangular system, exact-capable.
Dense matrix and non-owning view.
Quasi-reduced ordered Multi-valued Decision Diagram.
The rate side of the decision-diagram domain: local matrices, events, the Kronecker descriptor,...
MddMcdResult< T > mdd_mcd(const MddStruct &mdds, const MddDescriptor< T > &desc, const MddMcdOptions &options=MddMcdOptions())
Approximate stationary measures by decision-diagram-guided aggregation.
const int TERM_TRUE
Terminal node "1": a completed path is accepted.
double dot(const std::vector< double > &a, const std::vector< double > &b)
The inner product of a row vector with a column held as a vector.
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.
Number-type abstraction for the templated API port.
Kronecker rate descriptor of a structured model, the input of mdd_mcd.
std::vector< std::vector< double > > valuemap
valuemap[i][idx] is the physical occupancy of level i in local state idx.
std::vector< double > servers
Servers per station; infinite for a delay station.
int N
Closed population; the conservation law the level marginals must satisfy.
std::vector< T > mu
Station service rates, 1/E[S]; empty for a descriptor with no queueing parameters.
std::vector< double > invariant_weights
Optional conservation law as weights' * QLen = value, overriding the closed-population test.
double invariant_value
Value of the invariant when invariant_weights is set.
std::vector< MddEvent< T > > events
The events of the descriptor.
One event of the Kronecker rate descriptor.
std::vector< MddLocalMatrix< T > > W
Local matrices at the levels named by lev.
std::vector< std::size_t > lev
Levels the event touches, as 0-based level indices, aligned with W.
A local rate matrix W_k^e of the Kronecker descriptor, held row-compressed.
static MddLocalMatrix< T > identity(std::size_t d)
The identity of the given order, used for a level an event does not touch.
Knobs of the level iteration in mdd_mcd.
Result of the Miner-Ciardo-Donatelli level aggregation.
std::vector< std::size_t > level_sizes
|M_k| per paper level.
bool no_aggregation
True certifies the result is EXACT with no reference solve needed; false means "not certified by this...
std::vector< std::vector< T > > pik
pik[k] is the level-k stationary vector over M_k, in paper orientation.
std::vector< std::vector< std::pair< int, int > > > Mrows
Mrows[k][r] = {node id, local value} of row r of M_k.
int iters
Fixed-point iterations performed.
std::vector< T > QLen
Mean occupancy per station (or place), in station order.
std::vector< double > paths_per_level
max |A(p)| per paper level: the largest number of distinct root-to-node paths at that level.
std::vector< T > X
Per-station throughput; empty when the descriptor carries no queueing parameters.
std::vector< T > U
Per-station utilization; empty when the descriptor carries no queueing parameters.
Plain-array export of an MDD, the input contract of mdd_mcd.
std::vector< std::vector< std::vector< int > > > node
node[k][p][v] is the child of arc v of level-k node id p+1: a level-(k+1) node id when k < K-1,...
std::vector< int > domain
domain[k] is the number of local states at level k.
std::vector< int > nnodes
nnodes[k] is the live node count at level k.
std::size_t K
Number of variable levels.