LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mdd_types.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_TYPES_H
6#define LINE_API_MDD_MDD_TYPES_H
7
8/**
9 * @file
10 * @ingroup api_mdd
11 * The rate side of the decision-diagram domain: local matrices, events, the
12 * Kronecker descriptor, and the options/result of the level aggregation.
13 *
14 * Port of the JAR classes MddLocalMatrix, MddEvent, MddServiceLaw,
15 * MddDescriptor, MddMcdOptions and MddMcdResult, which the MATLAB and python
16 * twins carry as struct fields of the same names.
17 *
18 * The rate matrix of a structured model is R = sum_e (kron_k W_k^e) restricted
19 * to the reachable set, with W_k^e[i,j] = lambda_k^e[i] * Prob_k^e(i,j) (Eq. 1
20 * of Miner-Ciardo-Donatelli, SIGMETRICS 2000). An event touches only the levels
21 * it names; every other level carries the identity, which `mdd_mcd` supplies
22 * rather than storing.
23 */
24
25#include <cstddef>
26#include <functional>
27#include <map>
28#include <vector>
29
30#include "line/num/number.h"
31#include "line/util/error.h"
32#include "line/util/matrix.h"
33
34namespace line {
35namespace mdd {
36
37/**
38 * A local rate matrix W_k^e of the Kronecker descriptor, held row-compressed.
39 *
40 * The aggregation only ever walks a row of W and needs its row sums (the local
41 * enabling rates lambda_k^e), so the rows are stored as parallel column/value
42 * arrays rather than as a general sparse matrix. Duplicate triplets are summed
43 * when the matrix is built, so a caller may emit the same (i,j) more than once.
44 */
45template <class T>
47 /** Order of the (square) local matrix, i.e. the level domain. */
48 std::size_t dim = 0;
49 /** cols[i] holds the column indices of the nonzeros of row i. */
50 std::vector<std::vector<std::size_t>> cols;
51 /** vals[i] holds the values of the nonzeros of row i, aligned with cols[i]. */
52 std::vector<std::vector<T>> vals;
53 /** row_sum[i] is the local enabling rate lambda[i]. */
54 std::vector<T> row_sum;
55 /** Total number of stored nonzeros. */
56 std::size_t nnz = 0;
57
58 /** The identity of the given order, used for a level an event does not touch. */
59 static MddLocalMatrix<T> identity(std::size_t d) {
60 const T one = num_traits<T>::from_int(1);
62 m.dim = d;
63 m.cols.assign(d, std::vector<std::size_t>());
64 m.vals.assign(d, std::vector<T>());
65 m.row_sum.assign(d, one);
66 for (std::size_t i = 0; i < d; ++i) {
67 m.cols[i].push_back(i);
68 m.vals[i].push_back(one);
69 }
70 m.nnz = d;
71 return m;
72 }
73
74 /** Incremental triplet builder; duplicate entries are accumulated. */
75 class Builder {
76 public:
77 explicit Builder(std::size_t d) : dim_(d), rows_(d) {}
78
79 /** Accumulate value into entry (i,j). A zero value is dropped. */
80 Builder& add(std::size_t i, std::size_t j, const T& value) {
81 const T zero = num_traits<T>::from_int(0);
82 if (value == zero) return *this;
83 if (i >= dim_ || j >= dim_)
84 throw InputError("MddLocalMatrix::Builder: index outside the level domain");
85 typename std::map<std::size_t, T>::iterator it = rows_[i].find(j);
86 if (it == rows_[i].end())
87 rows_[i][j] = value;
88 else
89 it->second = T(it->second + value);
90 return *this;
91 }
92
94 const T zero = num_traits<T>::from_int(0);
96 m.dim = dim_;
97 m.cols.assign(dim_, std::vector<std::size_t>());
98 m.vals.assign(dim_, std::vector<T>());
99 m.row_sum.assign(dim_, zero);
100 for (std::size_t i = 0; i < dim_; ++i) {
101 T s = zero;
102 // std::map iterates in ascending key order, so two builds of the
103 // same matrix agree entry for entry
104 for (typename std::map<std::size_t, T>::const_iterator it = rows_[i].begin();
105 it != rows_[i].end(); ++it) {
106 m.cols[i].push_back(it->first);
107 m.vals[i].push_back(it->second);
108 s += it->second;
109 }
110 m.row_sum[i] = s;
111 m.nnz += rows_[i].size();
112 }
113 return m;
114 }
115
116 private:
117 std::size_t dim_;
118 std::vector<std::map<std::size_t, T>> rows_;
119 };
120};
121
122/** One event of the Kronecker rate descriptor. */
123template <class T>
124struct MddEvent {
125 /** Station (or transition node) the event departs from, 0-based. */
126 std::size_t a = 0;
127 /** Station (or mode) the event arrives at, 0-based; equals a for an internal event. */
128 std::size_t b = 0;
129 /** Levels the event touches, as 0-based level indices, aligned with W. */
130 std::vector<std::size_t> lev;
131 /** Local matrices at the levels named by lev. */
132 std::vector<MddLocalMatrix<T>> W;
133};
134
135/**
136 * Phase-type service law of one station, as a Markovian (D0,D1) pair.
137 *
138 * D0 holds the phase transitions that do not complete a service and D1 those
139 * that do, so the exit-rate vector is t0 = D1*1. For a renewal law D1 = t0*pie,
140 * and the entry law pie is then DERIVED from D1 rather than assumed (see
141 * `mdd_entry_law`). Set `pie` only to override that.
142 */
143template <class T>
147 /** Optional entry law; empty to derive it from D1. */
148 std::vector<T> pie;
149 bool present = false; ///< false marks "this station is exponential"
150
151 std::size_t phases() const { return D0.rows(); }
152};
153
154/** Successor function over local indices, for `mdd_reachset`. */
155typedef std::function<std::vector<std::vector<int>>(const std::vector<int>&)> MddNextState;
156
157/**
158 * Kronecker rate descriptor of a structured model, the input of `mdd_mcd`.
159 *
160 * Built by `mdd_descriptor` (count-plus-in-service-phase local states,
161 * non-preemptive), `mdd_ps` (per-phase-count local states, shared servers) or
162 * `spn::spn_mdd` (a stochastic Petri net).
163 */
164template <class T>
166 /** Number of levels, i.e. stations or places. */
167 std::size_t K = 0;
168 /** Closed population; the conservation law the level marginals must satisfy. */
169 int N = 0;
170 /** Local domain per level. */
171 std::vector<int> domain;
172 /** Station service rates, 1/E[S]; empty for a descriptor with no queueing parameters. */
173 std::vector<T> mu;
174 /** Servers per station; infinite for a delay station. */
175 std::vector<double> servers;
176 /** Station-to-station routing matrix. */
178 /** Phases per station, 1 when exponential. */
179 std::vector<std::size_t> nphases;
180 /**
181 * valuemap[i][idx] is the physical occupancy of level i in local state idx.
182 *
183 * A level whose local state encodes more than a count (a station holding
184 * both a population and a service phase) needs this map; without one the
185 * index would be the quantity.
186 */
187 std::vector<std::vector<double>> valuemap;
188 /** Initial local index per level. */
189 std::vector<int> init;
190 /** Successor function over local indices. */
192 /** The events of the descriptor. */
193 std::vector<MddEvent<T>> events;
194 /**
195 * Optional conservation law as weights' * QLen = value, overriding the
196 * closed-population test. Empty when the population N is the invariant.
197 */
198 std::vector<double> invariant_weights;
199 /** Value of the invariant when invariant_weights is set. */
200 double invariant_value = 0.0;
201};
202
203/**
204 * Knobs of the level iteration in `mdd_mcd`.
205 *
206 * The defaults are deliberately much tighter than a solver-level fixed-point
207 * tolerance: the level iteration is an INNER numerical solve and `mdd_mcd`
208 * verifies the population invariant at 1e-6, so a loose tolerance converges
209 * short of the fixed point and trips that guard. Do not wire an AMVA-sized
210 * iter_tol into these.
211 */
213 /** Convergence tolerance on the level marginals. */
214 double tol = 1e-12;
215 /** Maximum coupled sweeps before the iteration is declared non-convergent. */
216 int maxiter = 500;
217 /**
218 * The reference's 'verbose' knob is NOT carried: it is a console trace of
219 * the level sizes and the iteration count, and this port keeps the api layer
220 * silent, as `infer_lqn_ekf` does. `MddMcdResult` returns level_sizes and
221 * iters, so the same numbers are available to a caller that wants them.
222 */
223 /** Optional warm-start level vectors, one per paper level; empty to start uniform. */
224 std::vector<std::vector<double>> initpik;
225};
226
227/** Result of the Miner-Ciardo-Donatelli level aggregation. */
228template <class T>
230 /** Mean occupancy per station (or place), in station order. */
231 std::vector<T> QLen;
232 /** Per-station throughput; empty when the descriptor carries no queueing parameters. */
233 std::vector<T> X;
234 /** Per-station utilization; empty when the descriptor carries no queueing parameters. */
235 std::vector<T> U;
236 /** pik[k] is the level-k stationary vector over M_k, in paper orientation. */
237 std::vector<std::vector<T>> pik;
238 /** Mrows[k][r] = {node id, local value} of row r of M_k. */
239 std::vector<std::vector<std::pair<int, int>>> Mrows;
240 /** |M_k| per paper level. */
241 std::vector<std::size_t> level_sizes;
242 /** Fixed-point iterations performed. */
243 int iters = 0;
244 /**
245 * max |A(p)| per paper level: the largest number of distinct root-to-node
246 * paths at that level. 1 means no node there is shared, so conditioning on
247 * the node equals conditioning on the whole path above it.
248 */
249 std::vector<double> paths_per_level;
250 /**
251 * True certifies the result is EXACT with no reference solve needed; false
252 * means "not certified by this test", never "approximate" -- a product-form
253 * model is exact however much its diagram shares.
254 */
255 bool no_aggregation = false;
256};
257
258/**
259 * Entry law of a phase-type station, taken as given or derived from D1.
260 *
261 * A {D0,D1} pair carries its own restart law: for a renewal process D1 = t0*pie,
262 * so every row with a positive exit rate is proportional to pie. Deriving it is
263 * not optional -- defaulting to e_1 instead silently replaces a hyperexponential
264 * (whose D0 is diagonal, so a job entering phase 1 can never leave it) by an
265 * exponential at the phase-1 rate.
266 */
267template <class T>
268std::vector<T> mdd_entry_law(const std::vector<T>& given, const Matrix<T>& D1, std::size_t h,
269 std::size_t i, const std::string& caller) {
270 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
271 if (!given.empty()) {
272 std::vector<T> v = given;
273 T s = zero;
274 for (std::size_t a = 0; a < v.size(); ++a) s += v[a];
275 for (std::size_t a = 0; a < v.size(); ++a) v[a] = T(v[a] / s);
276 return v;
277 }
278 std::vector<T> t0(h, zero);
279 long live = -1;
280 std::size_t nlive = 0;
281 for (std::size_t a = 0; a < h; ++a) {
282 T s = zero;
283 for (std::size_t b = 0; b < h; ++b) s += D1(a, b);
284 t0[a] = s;
285 if (s > zero) {
286 ++nlive;
287 if (live < 0) live = static_cast<long>(a);
288 }
289 }
290 if (live < 0) {
291 std::vector<T> pie(h, zero);
292 pie[0] = one;
293 return pie;
294 }
295 const std::size_t l = static_cast<std::size_t>(live);
296 std::vector<T> first(h, zero);
297 for (std::size_t b = 0; b < h; ++b) first[b] = T(D1(l, b) / t0[l]);
298 // A non-renewal MAP restarts in a law that depends on the phase it left
299 // from, which one entry vector cannot express; the composite level would
300 // silently model the renewal process instead.
301 if (nlive > 1) {
302 for (std::size_t a = l + 1; a < h; ++a) {
303 if (!(t0[a] > zero)) continue;
304 for (std::size_t b = 0; b < h; ++b) {
305 const double d = num_traits<T>::to_double(T(D1(a, b) / t0[a])) -
306 num_traits<T>::to_double(first[b]);
307 if (d > 1e-9 || d < -1e-9)
308 throw InputError(caller + ": station " + std::to_string(i + 1) +
309 " carries a service law whose restart distribution depends "
310 "on the completing phase (a non-renewal MAP); the local "
311 "state names one entry law, so this encoding cannot "
312 "represent it");
313 }
314 }
315 }
316 return first;
317}
318
319} // namespace mdd
320} // namespace line
321
322#endif // LINE_API_MDD_MDD_TYPES_H
InputError(const std::string &what)
Definition error.h:39
std::size_t rows() const
Definition matrix.h:89
Builder & add(std::size_t i, std::size_t j, const T &value)
Accumulate value into entry (i,j).
Definition mdd_types.h:80
MddLocalMatrix< T > build() const
Definition mdd_types.h:93
The exception types the port throws.
Dense matrix and non-owning view.
std::vector< T > mdd_entry_law(const std::vector< T > &given, const Matrix< T > &D1, std::size_t h, std::size_t i, const std::string &caller)
Entry law of a phase-type station, taken as given or derived from D1.
Definition mdd_types.h:268
std::function< std::vector< std::vector< int > >(const std::vector< int > &)> MddNextState
Successor function over local indices, for mdd_reachset.
Definition mdd_types.h:155
Number-type abstraction for the templated API port.
Kronecker rate descriptor of a structured model, the input of mdd_mcd.
Definition mdd_types.h:165
MddNextState nextfun
Successor function over local indices.
Definition mdd_types.h:191
std::vector< int > domain
Local domain per level.
Definition mdd_types.h:171
std::vector< std::vector< double > > valuemap
valuemap[i][idx] is the physical occupancy of level i in local state idx.
Definition mdd_types.h:187
std::vector< double > servers
Servers per station; infinite for a delay station.
Definition mdd_types.h:175
std::vector< std::size_t > nphases
Phases per station, 1 when exponential.
Definition mdd_types.h:179
int N
Closed population; the conservation law the level marginals must satisfy.
Definition mdd_types.h:169
std::vector< T > mu
Station service rates, 1/E[S]; empty for a descriptor with no queueing parameters.
Definition mdd_types.h:173
std::vector< double > invariant_weights
Optional conservation law as weights' * QLen = value, overriding the closed-population test.
Definition mdd_types.h:198
std::vector< int > init
Initial local index per level.
Definition mdd_types.h:189
double invariant_value
Value of the invariant when invariant_weights is set.
Definition mdd_types.h:200
std::vector< MddEvent< T > > events
The events of the descriptor.
Definition mdd_types.h:193
std::size_t K
Number of levels, i.e.
Definition mdd_types.h:167
Matrix< T > P
Station-to-station routing matrix.
Definition mdd_types.h:177
One event of the Kronecker rate descriptor.
Definition mdd_types.h:124
std::size_t b
Station (or mode) the event arrives at, 0-based; equals a for an internal event.
Definition mdd_types.h:128
std::size_t a
Station (or transition node) the event departs from, 0-based.
Definition mdd_types.h:126
std::vector< MddLocalMatrix< T > > W
Local matrices at the levels named by lev.
Definition mdd_types.h:132
std::vector< std::size_t > lev
Levels the event touches, as 0-based level indices, aligned with W.
Definition mdd_types.h:130
A local rate matrix W_k^e of the Kronecker descriptor, held row-compressed.
Definition mdd_types.h:46
static MddLocalMatrix< T > identity(std::size_t d)
The identity of the given order, used for a level an event does not touch.
Definition mdd_types.h:59
std::vector< std::vector< T > > vals
vals[i] holds the values of the nonzeros of row i, aligned with cols[i].
Definition mdd_types.h:52
std::size_t dim
Order of the (square) local matrix, i.e.
Definition mdd_types.h:48
std::vector< std::vector< std::size_t > > cols
cols[i] holds the column indices of the nonzeros of row i.
Definition mdd_types.h:50
std::size_t nnz
Total number of stored nonzeros.
Definition mdd_types.h:56
std::vector< T > row_sum
row_sum[i] is the local enabling rate lambda[i].
Definition mdd_types.h:54
Knobs of the level iteration in mdd_mcd.
Definition mdd_types.h:212
std::vector< std::vector< double > > initpik
The reference's 'verbose' knob is NOT carried: it is a console trace of the level sizes and the itera...
Definition mdd_types.h:224
double tol
Convergence tolerance on the level marginals.
Definition mdd_types.h:214
int maxiter
Maximum coupled sweeps before the iteration is declared non-convergent.
Definition mdd_types.h:216
Result of the Miner-Ciardo-Donatelli level aggregation.
Definition mdd_types.h:229
std::vector< std::size_t > level_sizes
|M_k| per paper level.
Definition mdd_types.h:241
bool no_aggregation
True certifies the result is EXACT with no reference solve needed; false means "not certified by this...
Definition mdd_types.h:255
std::vector< std::vector< T > > pik
pik[k] is the level-k stationary vector over M_k, in paper orientation.
Definition mdd_types.h:237
std::vector< std::vector< std::pair< int, int > > > Mrows
Mrows[k][r] = {node id, local value} of row r of M_k.
Definition mdd_types.h:239
int iters
Fixed-point iterations performed.
Definition mdd_types.h:243
std::vector< T > QLen
Mean occupancy per station (or place), in station order.
Definition mdd_types.h:231
std::vector< double > paths_per_level
max |A(p)| per paper level: the largest number of distinct root-to-node paths at that level.
Definition mdd_types.h:249
std::vector< T > X
Per-station throughput; empty when the descriptor carries no queueing parameters.
Definition mdd_types.h:233
std::vector< T > U
Per-station utilization; empty when the descriptor carries no queueing parameters.
Definition mdd_types.h:235
Phase-type service law of one station, as a Markovian (D0,D1) pair.
Definition mdd_types.h:144
std::size_t phases() const
Definition mdd_types.h:151
std::vector< T > pie
Optional entry law; empty to derive it from D1.
Definition mdd_types.h:148
bool present
false marks "this station is exponential"
Definition mdd_types.h:149