LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mdd_closedqn.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_CLOSEDQN_H
6#define LINE_API_MDD_MDD_CLOSEDQN_H
7
8/**
9 * @file
10 * @ingroup api_mdd
11 * Exact solve of a single-class closed exponential queueing network whose CTMC
12 * state space (reachable occupancy vectors) is stored in a Multi-valued
13 * Decision Diagram instead of an explicit state list.
14 *
15 * Port of matlab/src/api/mdd/mdd_closedqn.m, jline.api.mdd.Mdd_closedqn and
16 * python/line_solver/api/mdd/closedqn.py. The reachable set is generated with
17 * `mdd_reachset` and the generator matrix is assembled using the MDD's O(K)
18 * state indexing (`MDD::index`), so no explicit (|S| x width) state matrix is
19 * ever materialised during assembly -- the diagram is the store. For
20 * single-class exponential stations the aggregated (occupancy) chain is exact:
21 * the rate from n to n-e_i+e_j is mu_i * min(n_i, c_i) * P(i,j) for n_i > 0,
22 * matching SolverCTMC on the same model, which makes this the live exact
23 * oracle the `mdd_mcd` aggregation is validated against.
24 *
25 * The reference's 'verbose' knob is NOT carried (this api layer is silent, as
26 * `MddMcdOptions` documents); `stats` returns the same storage numbers. The
27 * reference's 'ctmcmethod' knob is NOT carried either: the C++ `ctmc_solve`
28 * has a single direct backend.
29 */
30
31#include <chrono>
32#include <cmath>
33#include <cstddef>
34#include <vector>
35
37#include "line/api/mdd/mdd.h"
40#include "line/num/number.h"
41#include "line/util/error.h"
42#include "line/util/matrix.h"
43
44namespace line {
45namespace mdd {
46
47/** Result of the MDD-stored exact closed-network solve. */
48template <class T>
50 /** The MDD holding the reachable occupancy set. */
51 MDD mdd = MDD(std::vector<int>());
52 /** CTMC generator, rows aligned to MDD::index order. */
54 /** Stationary distribution over the reachable states, MDD::index order. */
55 std::vector<T> pi;
56 /** |S| x M occupancy states, in MDD::index order. */
57 std::vector<std::vector<int>> states;
58 /** Mean number of jobs per station. */
59 std::vector<T> QLen;
60 /** Utilisation: busy servers / servers, or mean busy jobs for a delay. */
61 std::vector<T> U;
62 /** Per-station throughput. */
63 std::vector<T> X;
64 /** MDD storage statistics of the reachable set. */
66 /** Phase timings in seconds: reachable-set build (0 when the diagram was
67 * supplied), generator assembly, ctmc_solve, performance measures. */
68 double time_reach = 0.0, time_gen = 0.0, time_solve = 0.0, time_metrics = 0.0;
69};
70
71/**
72 * @brief Exact solve of a single-class closed exponential queueing network
73 * whose CTMC state space (reachable occupancy vectors) is stored in a
74 * Multi-valued Decision Diagram instead of an explicit state list.
75 *
76 * @param mu per-station exponential service rates, length M
77 * @param P M x M Markovian routing matrix (row-stochastic, irreducible)
78 * @param servers servers per station; infinite for a delay/IS station
79 * @param N closed population
80 * @param reuse an already-built reachable set (the `mdd` of a previous result
81 * on the same mu/P/servers/N) to skip regeneration, or nullptr
82 */
83template <class T>
84MddClosedQnResult<T> mdd_closedqn(const std::vector<T>& mu, const Matrix<T>& P,
85 const std::vector<double>& servers, int N,
86 const MDD* reuse = nullptr) {
87 typedef std::chrono::steady_clock clock;
88 const std::size_t M = mu.size();
89 if (P.rows() != M || P.cols() != M)
90 throw InputError("mdd_closedqn: routing matrix must be M x M");
91 if (servers.size() != M)
92 throw InputError("mdd_closedqn: servers must have one entry per station");
93 if (N < 1) throw InputError("mdd_closedqn: the closed population must be positive");
94 const T zero = num_traits<T>::from_int(0);
95
96 // events: a completion at station i (rate mu_i * min(n_i, c_i)) routes to
97 // station j with probability P(i,j); self-routing i == j leaves the
98 // occupancy vector unchanged and is skipped.
99 std::vector<std::size_t> ii, jj;
100 std::vector<T> pij;
101 for (std::size_t i = 0; i < M; ++i)
102 for (std::size_t j = 0; j < M; ++j)
103 if (i != j && P(i, j) != zero) {
104 ii.push_back(i);
105 jj.push_back(j);
106 pij.push_back(P(i, j));
107 }
108 const std::size_t E = ii.size();
109
110 // all jobs start at station 1; an irreducible routing chain makes every
111 // composition of N over M stations reachable.
112 const std::vector<int> domain(M, N + 1);
113 std::vector<int> init(M, 0);
114 init[0] = N;
115 const MddNextState nextfun = [&ii, &jj, E](const std::vector<int>& s) {
116 std::vector<std::vector<int>> succ;
117 for (std::size_t a = 0; a < E; ++a) {
118 if (s[ii[a]] > 0) {
119 std::vector<int> t = s;
120 --t[ii[a]];
121 ++t[jj[a]];
122 succ.push_back(t);
123 }
124 }
125 return succ;
126 };
127
129 std::chrono::time_point<clock> t0 = clock::now();
130 if (reuse != nullptr) {
131 out.mdd = *reuse;
132 out.time_reach = 0.0;
133 } else {
134 out.mdd = mdd_reachset(domain, init, nextfun);
135 out.time_reach = std::chrono::duration<double>(clock::now() - t0).count();
136 }
137
138 t0 = clock::now();
139 const long long n = out.mdd.cardinality();
140 out.states = out.mdd.enumerate();
141
142 // assemble the generator directly from the MDD state indexing
143 out.Q = Matrix<T>(static_cast<std::size_t>(n), static_cast<std::size_t>(n));
144 for (long long s = 0; s < n; ++s) {
145 const std::vector<int>& st = out.states[static_cast<std::size_t>(s)];
146 const long long row = out.mdd.index(st);
147 for (std::size_t a = 0; a < E; ++a) {
148 const std::size_t i = ii[a];
149 if (st[i] > 0) {
150 const int busy = std::isinf(servers[i])
151 ? st[i]
152 : std::min(st[i], static_cast<int>(servers[i]));
153 const T rate = mu[i] * num_traits<T>::from_int(busy) * pij[a];
154 std::vector<int> t = st;
155 --t[i];
156 ++t[jj[a]];
157 const long long col = out.mdd.index(t);
158 out.Q(static_cast<std::size_t>(row), static_cast<std::size_t>(col)) += rate;
159 }
160 }
161 }
162 out.Q = mc::ctmc_makeinfgen(out.Q);
163 out.time_gen = std::chrono::duration<double>(clock::now() - t0).count();
164
165 t0 = clock::now();
166 out.pi = mc::ctmc_solve(out.Q);
167 out.time_solve = std::chrono::duration<double>(clock::now() - t0).count();
168
169 // performance metrics
170 t0 = clock::now();
171 out.QLen.assign(M, zero);
172 out.U.assign(M, zero);
173 out.X.assign(M, zero);
174 for (std::size_t i = 0; i < M; ++i) {
175 T qi = zero, busyMean = zero;
176 for (long long s = 0; s < n; ++s) {
177 const int ni = out.states[static_cast<std::size_t>(s)][i];
178 const int busy = std::isinf(servers[i]) ? ni : std::min(ni, static_cast<int>(servers[i]));
179 qi += out.pi[static_cast<std::size_t>(s)] * num_traits<T>::from_int(ni);
180 busyMean += out.pi[static_cast<std::size_t>(s)] * num_traits<T>::from_int(busy);
181 }
182 out.QLen[i] = qi;
183 out.X[i] = mu[i] * busyMean; // throughput = mean completion rate
184 if (std::isinf(servers[i]))
185 out.U[i] = qi; // mean number busy (IS station)
186 else
187 out.U[i] = busyMean / num_traits<T>::from_int(static_cast<int>(servers[i]));
188 }
189 out.time_metrics = std::chrono::duration<double>(clock::now() - t0).count();
190
191 out.stats = out.mdd.stats();
192 return out;
193}
194
195} // namespace mdd
196} // namespace line
197
198#endif // LINE_API_MDD_MDD_CLOSEDQN_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 diagram: insert / member / index / enumerate / cardinality.
Definition mdd.h:97
long long cardinality() const
|S|, the number of stored states.
Definition mdd.h:145
MDD(const std::vector< int > &domain)
An empty set over the given per-level domains.
Definition mdd.h:100
std::vector< std::vector< int > > enumerate() const
All stored states as rows, in index() order.
Definition mdd.h:172
MddStats stats() const
Storage description of the current set; only reachable nodes are counted.
Definition mdd.h:233
long long index(const std::vector< int > &state) const
0-based lexicographic rank of state among the stored set, level 0 most significant,...
Definition mdd.h:157
Steady-state distribution of a continuous-time Markov chain.
The exception types the port throws.
Dense matrix and non-owning view.
Quasi-reduced ordered Multi-valued Decision Diagram.
Reachability set generation into a decision diagram.
The rate side of the decision-diagram domain: local matrices, events, the Kronecker descriptor,...
Matrix< T > ctmc_makeinfgen(const Matrix< T > &Q)
Set the diagonal so that every row sums to zero (ctmc_makeinfgen).
Definition ctmc_solve.h:58
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
Definition ctmc_solve.h:122
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
MddClosedQnResult< T > mdd_closedqn(const std::vector< T > &mu, const Matrix< T > &P, const std::vector< double > &servers, int N, const MDD *reuse=nullptr)
Exact solve of a single-class closed exponential queueing network whose CTMC state space (reachable o...
MDD mdd_reachset(const std::vector< int > &domain, const std::vector< int > &init, const MddNextState &nextfun)
Generate and store the reachability set into a quasi-reduced ordered MDD.
Number-type abstraction for the templated API port.
Result of the MDD-stored exact closed-network solve.
std::vector< T > X
Per-station throughput.
Matrix< T > Q
CTMC generator, rows aligned to MDD::index order.
MddStats stats
MDD storage statistics of the reachable set.
double time_reach
Phase timings in seconds: reachable-set build (0 when the diagram was supplied), generator assembly,...
MDD mdd
The MDD holding the reachable occupancy set.
std::vector< T > QLen
Mean number of jobs per station.
std::vector< T > U
Utilisation: busy servers / servers, or mean busy jobs for a delay.
std::vector< std::vector< int > > states
|S| x M occupancy states, in MDD::index order.
std::vector< T > pi
Stationary distribution over the reachable states, MDD::index order.
Storage description of the set held in an MDD.
Definition mdd.h:74