LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
env_generator.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_SOLVERS_ENV_ENV_GENERATOR_H
6#define LINE_SOLVERS_ENV_ENV_GENERATOR_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Port of `@@SolverENV/getGenerator.m`: the infinitesimal generator of the
12 * joint (environment, network) chain, assembled from CTMC stage generators.
13 *
14 * BLOCK LAYOUT, exactly as the reference builds it. Block (e,e) starts as stage
15 * e's generator and is Kronecker-summed with the D0 of every outgoing arc
16 * e -> h in increasing h. Block (e,h) starts as a reset matrix (identity on the
17 * first min(n_e, n_h) states) and, for each h' != e in increasing order, is
18 * Kronecker-multiplied by `D1_(e,h) 1 pie_(h,e)` when h' = h and by
19 * `1 pie_(h',h)` otherwise. The flattened matrix is closed with
20 * `ctmc_makeinfgen`. The column factors of an off-diagonal block follow the
21 * reference's loop order, not the target block's; that is the reference's
22 * construction and is reproduced as is.
23 *
24 * A MISSING ARC is `Exp(0)`, as the SolverENV constructor substitutes for
25 * `Disabled`: one phase, D0 = D1 = [0], and a NaN `map_pie` read as ones. The
26 * stage solvers must be CTMC, which here is not a choice: the stage generator
27 * is always `ctmc_get_generator` on the stage struct.
28 */
29
30#include <cmath>
31#include <cstddef>
32#include <vector>
33
38#include "line/util/error.h"
39#include "line/util/matrix.h"
40
41namespace line {
42namespace env {
43
44/** One environment transition event, the reference's `Event(STAGE, node, NaN, NaN, [e,h])`. */
46 std::size_t node; ///< 1-based node of stage `from`'s model
47 std::size_t from; ///< 0-based source stage
48 std::size_t to; ///< 0-based target stage
49};
50
51/** The outputs of `getGenerator`, in the reference's order. */
52template <class T>
54 Matrix<T> Q; ///< renvInfGen, flattened and closed
55 std::vector<Matrix<T>> stage_Q; ///< stageInfGen
56 std::vector<std::vector<Matrix<T>>> filt; ///< renvEventFilt[e][h]
57 std::vector<ctmc::CtmcGenerator<T>> stage; ///< stageEventFilt / stageEvents
58 std::vector<EnvStageEvent> events; ///< renvEvents
59};
60
61namespace gen_detail {
62
63template <class T>
64Matrix<T> kron(const Matrix<T>& A, const Matrix<T>& B) {
65 Matrix<T> K(A.rows() * B.rows(), A.cols() * B.cols(), num_traits<T>::from_int(0));
66 for (std::size_t i = 0; i < A.rows(); ++i)
67 for (std::size_t j = 0; j < A.cols(); ++j) {
68 const T a = A(i, j);
69 if (a == num_traits<T>::from_int(0)) continue;
70 for (std::size_t k = 0; k < B.rows(); ++k)
71 for (std::size_t l = 0; l < B.cols(); ++l)
72 K(i * B.rows() + k, j * B.cols() + l) = T(a * B(k, l));
73 }
74 return K;
75}
76
77template <class T>
78Matrix<T> eye(std::size_t n) {
80 for (std::size_t i = 0; i < n; ++i) I(i, i) = num_traits<T>::from_int(1);
81 return I;
82}
83
84/** `krons(A,B) = kron(A, I) + kron(I, B)`. */
85template <class T>
86Matrix<T> krons(const Matrix<T>& A, const Matrix<T>& B) {
87 Matrix<T> S = kron(A, eye<T>(B.rows()));
88 const Matrix<T> R = kron(eye<T>(A.rows()), B);
89 for (std::size_t i = 0; i < S.rows(); ++i)
90 for (std::size_t j = 0; j < S.cols(); ++j) S(i, j) = T(S(i, j) + R(i, j));
91 return S;
92}
93
94/** The arc's (D0, D1), or `Exp(0)` when the arc is not declared. */
95template <class T>
96void arc_process(const Environment<T>& env, std::size_t e, std::size_t h, Matrix<T>& D0,
97 Matrix<T>& D1) {
98 const EnvArc<T>& a = env.arc(e, h);
99 if (!a.enabled) {
100 D0 = Matrix<T>(1, 1, num_traits<T>::from_int(0));
101 D1 = D0;
102 return;
103 }
104 if (!a.dist.has_map())
105 throw UnsupportedError("SolverENV.getGenerator: the transition " + env.stage(e).name +
106 " -> " + env.stage(h).name +
107 " has no Markovian (D0,D1) representation");
108 D0 = a.dist.D0;
109 D1 = a.dist.D1;
110}
111
112/** `map_pie` of the arc as a row, or ones(1, n) when it is NaN (an `Exp(0)` arc). */
113template <class T>
114Matrix<T> arc_pie(const Environment<T>& env, std::size_t f, std::size_t h, std::size_t n) {
115 Matrix<T> D0, D1;
116 arc_process(env, f, h, D0, D1);
117 T rate = num_traits<T>::from_int(0);
118 for (std::size_t i = 0; i < D1.rows(); ++i)
119 for (std::size_t j = 0; j < D1.cols(); ++j) rate += D1(i, j);
121 if (rate == num_traits<T>::from_int(0)) return p; // pi D1 e = 0: map_pie is NaN
122 mam::Map<T> m;
123 m.D0 = D0;
124 m.D1 = D1;
125 const std::vector<T> v = mam::map_pie(m);
126 p = Matrix<T>(1, v.size(), num_traits<T>::from_int(0));
127 for (std::size_t j = 0; j < v.size(); ++j) p(0, j) = v[j];
128 return p;
129}
130
131} // namespace gen_detail
132
133/**
134 * Port of `@@SolverENV/getGenerator.m`.
135 *
136 * @param env the random environment; every stage must be a queueing network
137 * @param opt the CTMC options of the stage solvers (`CTMC(model, 'exact', soptions)`)
138 */
139template <class T>
141 using namespace gen_detail;
142 env.reject_lqn_stages("SolverENV.getGenerator",
143 "the joint generator is assembled from CTMC stage generators");
144 const std::size_t E = env.nstages();
145 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
147 std::vector<std::size_t> nstates(E);
148 for (std::size_t e = 0; e < E; ++e) {
149 g.stage.push_back(ctmc::ctmc_get_generator(env.stage(e).model, opt));
150 g.stage_Q.push_back(g.stage.back().Q);
151 nstates[e] = g.stage_Q[e].rows();
152 }
153 // nphases(i,j) for i != j; the diagonal is never read.
154 std::vector<std::vector<std::size_t>> nph(E, std::vector<std::size_t>(E, 1));
155 for (std::size_t i = 0; i < E; ++i)
156 for (std::size_t j = 0; j < E; ++j)
157 if (i != j && env.arc(i, j).enabled) nph[i][j] = env.arc(i, j).dist.phases();
158
159 std::vector<std::vector<Matrix<T>>> B(E, std::vector<Matrix<T>>(E));
160 for (std::size_t e = 0; e < E; ++e)
161 for (std::size_t h = 0; h < E; ++h) {
162 if (h == e) {
163 B[e][e] = g.stage_Q[e];
164 continue;
165 }
166 B[e][h] = Matrix<T>(nstates[e], nstates[h], zero);
167 for (std::size_t i = 0; i < std::min(nstates[e], nstates[h]); ++i) B[e][h](i, i) = one;
168 }
169
170 for (std::size_t e = 0; e < E; ++e)
171 for (std::size_t h = 0; h < E; ++h) {
172 if (h == e) continue;
173 Matrix<T> D0, D1;
174 arc_process(env, e, h, D0, D1);
175 B[e][e] = krons(B[e][e], D0);
176 const Matrix<T> pie = arc_pie(env, h, e, nph[h][e]);
177 // D1 * ones(nph(e,h),1) * pie
178 Matrix<T> arg(D1.rows(), pie.cols(), zero);
179 for (std::size_t i = 0; i < D1.rows(); ++i) {
180 T s = zero;
181 for (std::size_t j = 0; j < D1.cols(); ++j) s += D1(i, j);
182 for (std::size_t j = 0; j < pie.cols(); ++j) arg(i, j) = T(s * pie(0, j));
183 }
184 B[e][h] = kron(B[e][h], arg);
185 const std::size_t nodes = env.stage(e).model.nodes.size();
186 for (std::size_t i = 1; i <= nodes; ++i) g.events.push_back(EnvStageEvent{i, e, h});
187 for (std::size_t f = 0; f < E; ++f) {
188 if (f == h || f == e) continue;
189 const Matrix<T> pfh = arc_pie(env, f, h, nph[f][h]);
190 Matrix<T> ones_pie(nph[e][h], pfh.cols(), zero);
191 for (std::size_t i = 0; i < ones_pie.rows(); ++i)
192 for (std::size_t j = 0; j < pfh.cols(); ++j) ones_pie(i, j) = pfh(0, j);
193 B[e][f] = kron(B[e][f], ones_pie);
194 }
195 }
196
197 // cell2mat: every block row must agree on its height, every column on its width.
198 std::vector<std::size_t> roff(E + 1, 0), coff(E + 1, 0);
199 for (std::size_t e = 0; e < E; ++e) {
200 roff[e + 1] = roff[e] + B[e][e].rows();
201 coff[e + 1] = coff[e] + B[e][e].cols();
202 }
203 for (std::size_t e = 0; e < E; ++e)
204 for (std::size_t h = 0; h < E; ++h)
205 if (B[e][h].rows() != B[e][e].rows() || B[e][h].cols() != B[h][h].cols())
206 throw InputError("SolverENV.getGenerator: block (" + std::to_string(e + 1) + "," +
207 std::to_string(h + 1) +
208 ") does not conform to the diagonal blocks (cell2mat)");
209 auto flatten = [&](const std::vector<std::vector<Matrix<T>>>& C) {
210 Matrix<T> M(roff[E], coff[E], zero);
211 for (std::size_t e = 0; e < E; ++e)
212 for (std::size_t h = 0; h < E; ++h)
213 for (std::size_t i = 0; i < C[e][h].rows(); ++i)
214 for (std::size_t j = 0; j < C[e][h].cols(); ++j)
215 M(roff[e] + i, coff[h] + j) = C[e][h](i, j);
216 return M;
217 };
218
219 // renvEventFilt{e,h}: only block (e,h) survives, and nothing when e == h.
220 g.filt.assign(E, std::vector<Matrix<T>>(E));
221 for (std::size_t e = 0; e < E; ++e)
222 for (std::size_t h = 0; h < E; ++h) {
223 std::vector<std::vector<Matrix<T>>> C(E, std::vector<Matrix<T>>(E));
224 for (std::size_t a = 0; a < E; ++a)
225 for (std::size_t b = 0; b < E; ++b)
226 C[a][b] = (a != b && a == e && b == h)
227 ? B[a][b]
228 : Matrix<T>(B[a][b].rows(), B[a][b].cols(), zero);
229 g.filt[e][h] = flatten(C);
230 }
231
232 g.Q = flatten(B);
234 return g;
235}
236
237} // namespace env
238} // namespace line
239
240#endif // LINE_SOLVERS_ENV_ENV_GENERATOR_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
UnsupportedError(const std::string &what)
Definition error.h:51
A random environment: a port of matlab/src/lang/Environment.m, restricted to what SolverENV reads out...
The exception types the port throws.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
CtmcGenerator< T > ctmc_get_generator(const NetworkStruct< T > &sn, const CtmcSolution< T > &d)
Port of @@SolverCTMC/getGenerator.m: the generator, its event filtration and the synchronization list...
void make_infgen(Matrix< T > &Q)
Port of ctmc_makeinfgen: turn an off-diagonal rate matrix into a generator.
Matrix< T > arc_pie(const Environment< T > &env, std::size_t f, std::size_t h, std::size_t n)
map_pie of the arc as a row, or ones(1, n) when it is NaN (an Exp(0) arc).
Matrix< T > krons(const Matrix< T > &A, const Matrix< T > &B)
krons(A,B) = kron(A, I) + kron(I, B).
void arc_process(const Environment< T > &env, std::size_t e, std::size_t h, Matrix< T > &D0, Matrix< T > &D1)
The arc's (D0, D1), or Exp(0) when the arc is not declared.
Matrix< T > eye(std::size_t n)
Matrix< T > kron(const Matrix< T > &A, const Matrix< T > &B)
EnvGenerator< T > env_get_generator(const Environment< T > &env, const ctmc::CtmcOptions &opt)
Port of @@SolverENV/getGenerator.m.
std::vector< T > map_pie(const Map< T > &m)
Phase distribution seen by an arriving job, pie = pi D1 / (pi D1 e).
Definition map_moment.h:89
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Port of solver_ctmc.m: the infinitesimal generator of a queueing network, assembled from the enumerat...
The remaining @@SolverCTMC accessors: getGenerator / getInfGen, getStateSpace / getStateSpaceAggr and...
The SolverCTMC knobs this port honours.
One arc of the environment process.
lang::Distrib< T > dist
the e -> h transition time
The outputs of getGenerator, in the reference's order.
std::vector< ctmc::CtmcGenerator< T > > stage
stageEventFilt / stageEvents
Matrix< T > Q
renvInfGen, flattened and closed
std::vector< Matrix< T > > stage_Q
stageInfGen
std::vector< EnvStageEvent > events
renvEvents
std::vector< std::vector< Matrix< T > > > filt
renvEventFilt[e][h]
One environment transition event, the reference's Event(STAGE, node, NaN, NaN, [e,...
std::size_t to
0-based target stage
std::size_t node
1-based node of stage from's model
std::size_t from
0-based source stage
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D1
Definition map_moment.h:55
Matrix< T > D0
Definition map_moment.h:54