LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mdd_rec.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_MDD_MDD_REC_H
6#define LINE_API_MDD_MDD_REC_H
7
8/**
9 * @file
10 * @ingroup api_mdd
11 * MDD-rec: the normalising constant of a product-form model whose reachable set
12 * is held in a decision diagram.
13 *
14 * S. Balsamo, A. Marin, I. Stojic, "Computation of the normalising constant for
15 * product-form models of distributed systems with synchronisation", Future
16 * Generation Computer Systems 111 (2020) 475-490, Sec. 4.
17 *
18 * A product-form model has P(s) = (1/G) prod_k g_k(s_k) over its levels, and
19 *
20 * G = sum_{s in S} prod_k g_k(s_k).
21 *
22 * Summing state by state is exponential and numerically unstable. MDD-rec
23 * instead walks the diagram that already encodes S, accumulating the
24 * unnormalised mass of each node ONCE (Def. 4.4, Algorithm 1):
25 *
26 * M(<l.p>) = sum_{v in S_l} g_l(v) * M(<l.p>[v]), M(TRUE) = 1, M(FALSE) = 0
27 *
28 * so the cost is O(sum_l |nodes_l| * |S_l|) rather than O(|S|), and G = M(root).
29 *
30 * FORMALISM-AGNOSTIC. Nothing here knows what a level is: the paper's Appendix B
31 * shows that on the lattice sum_k s_k = n of a closed queueing network this
32 * collapses to Buzen's convolution, and Sec. 5 that on an S-invariant reachable
33 * Petri net it collapses to the Coleman-Henderson-Taylor convolution
34 * (`spn::spn_conv`). Unlike either, it needs only that the reachable set be
35 * finite and encoded -- no lattice, no S-invariant reachability.
36 *
37 * THE MASK is how Sec. 5.3 computes measures. Restricting the sum at level l to
38 * a subset of its local values gives the unnormalised mass of the corresponding
39 * subset of S, so P(m_l = k) and P(e_j >= k) are the same recursion under a
40 * different mask rather than three separate algorithms.
41 *
42 * WHAT IS NOT HERE. The g_l themselves, and the test that the model has a
43 * product form at all, are the caller's: the paper declares that out of scope
44 * (Sec. 3.2) and refers to the per-formalism conditions instead. Passing g_l
45 * that do not describe a product-form model returns a number that is not the
46 * normalising constant of anything, and nothing here can detect it.
47 */
48
49#include <cstddef>
50#include <vector>
51
52#include "line/api/mdd/mdd.h"
53#include "line/num/number.h"
54#include "line/util/error.h"
55
56namespace line {
57namespace mdd {
58
59/**
60 * Per-level admissible local values, the restriction of Sec. 5.3.
61 *
62 * mask[l][v] false drops local value v of level l from the sum. An EMPTY mask
63 * admits everything, which is the plain MDD-rec of Algorithm 1.
64 */
65typedef std::vector<std::vector<bool>> MddMask;
66
67namespace detail {
68
69/** Unnormalised mass below one node, memoised per (level, node). */
70template <class T>
71T mdd_rec_node(const MddStruct& mdds, const std::vector<std::vector<T>>& g, const MddMask& mask,
72 std::size_t l, int id, std::vector<std::vector<T>>& memo,
73 std::vector<std::vector<bool>>& done) {
74 if (done[l][id - 1]) return memo[l][id - 1];
75 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
76 T acc = zero;
77 const std::vector<int>& a = mdds.node[l][id - 1];
78 for (int v = 0; v < mdds.domain[l]; ++v) {
79 if (!mask.empty() && !mask[l][static_cast<std::size_t>(v)]) continue;
80 const T gv = g[l][static_cast<std::size_t>(v)];
81 if (gv == zero) continue;
82 const int ch = a[v];
83 T below;
84 if (l + 1 == mdds.K) {
85 if (ch != TERM_TRUE) continue;
86 below = one;
87 } else {
88 if (ch == TERM_FALSE) continue;
89 below = mdd_rec_node(mdds, g, mask, l + 1, ch, memo, done);
90 }
91 acc += T(gv * below);
92 }
93 memo[l][id - 1] = acc;
94 done[l][id - 1] = true;
95 return acc;
96}
97
98template <class T>
99void mdd_rec_check(const MddStruct& mdds, const std::vector<std::vector<T>>& g,
100 const MddMask& mask, const char* caller) {
101 if (g.size() != mdds.K)
102 throw InputError(std::string(caller) + ": one g_l per level is required");
103 for (std::size_t l = 0; l < mdds.K; ++l) {
104 if (g[l].size() != static_cast<std::size_t>(mdds.domain[l]))
105 throw InputError(std::string(caller) + ": g_l must have one entry per local state");
106 if (!mask.empty() && mask[l].size() != static_cast<std::size_t>(mdds.domain[l]))
107 throw InputError(std::string(caller) + ": the mask must have one entry per local state");
108 }
109 if (!mask.empty() && mask.size() != mdds.K)
110 throw InputError(std::string(caller) + ": the mask must have one row per level");
111}
112
113} // namespace detail
114
115/**
116 * Unnormalised mass of the masked subset of the reachable set (Algorithm 1).
117 *
118 * @param mdds the reachable set, in MDD orientation (level 0 is the root)
119 * @param g g[l][v] is g_l(v), the per-level factor of the product form
120 * @param mask per-level admissible values; empty admits everything and returns G
121 */
122template <class T>
123T mdd_rec_masked(const MddStruct& mdds, const std::vector<std::vector<T>>& g,
124 const MddMask& mask) {
125 detail::mdd_rec_check(mdds, g, mask, "mdd_rec");
126 const T zero = num_traits<T>::from_int(0);
127 if (mdds.root == TERM_FALSE) return zero;
128 std::vector<std::vector<T>> memo(mdds.K);
129 std::vector<std::vector<bool>> done(mdds.K);
130 for (std::size_t l = 0; l < mdds.K; ++l) {
131 memo[l].assign(static_cast<std::size_t>(mdds.nnodes[l]), zero);
132 done[l].assign(static_cast<std::size_t>(mdds.nnodes[l]), false);
133 }
134 return detail::mdd_rec_node(mdds, g, mask, 0, mdds.root, memo, done);
135}
136
137/** The normalising constant G = sum_{s in S} prod_l g_l(s_l). */
138template <class T>
139T mdd_rec(const MddStruct& mdds, const std::vector<std::vector<T>>& g) {
140 return mdd_rec_masked(mdds, g, MddMask());
141}
142
143/**
144 * Unnormalised masses of {s in S : s_l = k}, one per local value k of level l.
145 *
146 * Divided by G these are P(m_l = k) of Sec. 5.3: the mean occupancy of a level
147 * is sum_k k * P(m_l = k), and its utilization 1 - P(m_l = 0).
148 */
149template <class T>
150std::vector<T> mdd_rec_marginal(const MddStruct& mdds, const std::vector<std::vector<T>>& g,
151 std::size_t l) {
152 if (l >= mdds.K) throw InputError("mdd_rec_marginal: level index is out of range");
153 const std::size_t d = static_cast<std::size_t>(mdds.domain[l]);
154 std::vector<T> out(d, num_traits<T>::from_int(0));
155 for (std::size_t k = 0; k < d; ++k) {
156 MddMask mask(mdds.K);
157 for (std::size_t j = 0; j < mdds.K; ++j)
158 mask[j].assign(static_cast<std::size_t>(mdds.domain[j]), true);
159 for (std::size_t v = 0; v < d; ++v) mask[l][v] = (v == k);
160 out[k] = mdd_rec_masked(mdds, g, mask);
161 }
162 return out;
163}
164
165} // namespace mdd
166} // namespace line
167
168#endif // LINE_API_MDD_MDD_REC_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Quasi-reduced ordered Multi-valued Decision Diagram.
std::vector< std::vector< bool > > MddMask
Per-level admissible local values, the restriction of Sec.
Definition mdd_rec.h:65
T mdd_rec(const MddStruct &mdds, const std::vector< std::vector< T > > &g)
The normalising constant G = sum_{s in S} prod_l g_l(s_l).
Definition mdd_rec.h:139
const int TERM_TRUE
Terminal node "1": a completed path is accepted.
Definition mdd.h:48
T mdd_rec_masked(const MddStruct &mdds, const std::vector< std::vector< T > > &g, const MddMask &mask)
Unnormalised mass of the masked subset of the reachable set (Algorithm 1).
Definition mdd_rec.h:123
std::vector< T > mdd_rec_marginal(const MddStruct &mdds, const std::vector< std::vector< T > > &g, std::size_t l)
Unnormalised masses of {s in S : s_l = k}, one per local value k of level l.
Definition mdd_rec.h:150
const int TERM_FALSE
Terminal node "0": empty subgraph.
Definition mdd.h:50
Number-type abstraction for the templated API port.
Plain-array export of an MDD, the input contract of mdd_mcd.
Definition mdd.h:57
int root
Id of the top (level-0) node; TERM_FALSE for the empty set.
Definition mdd.h:63
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,...
Definition mdd.h:70
std::vector< int > domain
domain[k] is the number of local states at level k.
Definition mdd.h:61
std::vector< int > nnodes
nnodes[k] is the live node count at level k.
Definition mdd.h:65
std::size_t K
Number of variable levels.
Definition mdd.h:59