LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_ctmc_transient.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_CTMC_SOLVER_CTMC_TRANSIENT_H
6#define LINE_SOLVERS_CTMC_SOLVER_CTMC_TRANSIENT_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Port of `solver_ctmc_transient_analyzer.m`: the time-dependent counterpart of
12 * `solver_ctmc_analyzer`, integrating dpi/dt = pi Q from a point mass on the
13 * initial state instead of solving pi Q = 0.
14 *
15 * WHAT IS AND IS NOT A TIME AVERAGE. The reference deliberately reports the
16 * INSTANTANEOUS occupancy pi(t), not its running mean -- the commented-out
17 * `cumsum(...)/t` lines in the reference are the time-average it decided
18 * against. Q(t) and U(t) are therefore the state of the chain at t, and their
19 * limits as t grows are the stationary values, not their averages over [0,t].
20 *
21 * THE UTILIZATION SWITCH IS NOT THE STATIONARY ONE. In steady state the carried
22 * rate is available and `T*E[S]/c` is used; here there is no stationary
23 * throughput, so utilization is read off the occupancy directly as
24 * min(n_k, c)/c, and the PS and DPS families take their capacity share. The
25 * reference WARNS for every other discipline and returns the FCFS form as an
26 * approximation, which this port reproduces rather than silently improving:
27 * a caller comparing against MATLAB must get MATLAB's number.
28 */
29
30#include <algorithm>
31#include <cmath>
32#include <cstddef>
33#include <vector>
34
41#include "line/util/error.h"
42#include "line/util/expm.h"
43#include "line/util/matrix.h"
44
45namespace line {
46namespace ctmc {
47
48namespace detail {
49
50/**
51 * Transient trajectory by FAST ADAPTIVE UNIFORMIZATION, selected with
52 * `options.config.transient_method = "fau"` (see `mc::ctmc_fau`).
53 *
54 * MARCHED, NOT RESTARTED. pi(t_{k+1}) comes from pi(t_k) over the step rather
55 * than from pi(0) over the whole horizon, which keeps the cost proportional to
56 * the grid instead of quadratic in it. Every step removes a little mass and
57 * none puts any back, so the per-step tolerance is `fau_epsilon` divided by the
58 * number of steps and the total defect stays below it. Nothing is renormalized:
59 * the point of the method is that its error is a measured quantity.
60 */
61template <class T>
62mc::TransientResult<T> ctmc_fau_transient(const Matrix<T>& Q, const std::vector<T>& pi0,
63 const T& t0, const T& t1, const CtmcOptions& opt,
64 const std::vector<T>& grid) {
65 const double t0d = num_traits<T>::to_double(t0);
66 const double t1d = num_traits<T>::to_double(t1);
67 if (!std::isfinite(t1d))
68 throw InputError(
69 "solver_ctmc_transient_analyzer: transient_method 'fau' needs a finite horizon");
70
71 std::vector<T> ts;
72 if (!grid.empty()) {
73 ts = grid;
74 } else if (opt.timestep > 0.0) {
75 const std::size_t nstep =
76 static_cast<std::size_t>(std::floor((t1d - t0d) / opt.timestep));
77 for (std::size_t i = 0; i <= nstep; ++i)
78 ts.push_back(num_traits<T>::from_double(t0d + i * opt.timestep));
79 if (num_traits<T>::to_double(ts.back()) < t1d) ts.push_back(t1);
80 } else {
81 const std::size_t ngrid = (opt.fau_ngrid > 1) ? opt.fau_ngrid : 100;
82 for (std::size_t i = 0; i < ngrid; ++i)
83 ts.push_back(num_traits<T>::from_double(
84 t0d + (t1d - t0d) * static_cast<double>(i) / static_cast<double>(ngrid - 1)));
85 }
86 const std::size_t nt = ts.size();
87 const double eps = (opt.fau_epsilon > 0.0) ? opt.fau_epsilon : 1e-6;
88 const double epsStep = eps / static_cast<double>((nt > 1) ? (nt - 1) : 1);
89
90 mc::TransientResult<T> out;
91 out.t = ts;
92 out.pi = Matrix<T>(nt, pi0.size(), num_traits<T>::from_int(0));
93 for (std::size_t j = 0; j < pi0.size(); ++j) out.pi(0, j) = pi0[j];
94 std::vector<T> cur = pi0;
95 for (std::size_t k = 1; k < nt; ++k) {
96 const T dt = ts[k] - ts[k - 1];
97 const mc::FauResult<T> r = mc::ctmc_fau(cur, Q, dt, epsStep, opt.fau_delta, -1);
98 cur = r.pit;
99 for (std::size_t j = 0; j < cur.size(); ++j) out.pi(k, j) = cur[j];
100 }
101 return out;
102}
103
104/** The rate_sched trajectory and the throughput multiplier it implies. */
105template <class T>
106struct TimeVaryingTransient {
107 mc::TransientResult<T> tr;
108 /// [station][class][time] multiplier m(t) of the scheduled pairs, 1 elsewhere
109 std::vector<std::vector<std::vector<T>>> mscale;
110};
111
112/**
113 * Port of `local_ctmc_timevarying` in `solver_ctmc_transient_analyzer.m`.
114 *
115 * Q(t) = Qbase + sum_s (m_s(t) - 1) Qhat_s, with Qhat_s the part of Qbase that
116 * is linear in the scheduled (station, class) rate. Q is linear in that rate, so
117 * ONE probe rebuild at twice the rate extracts it exactly:
118 * Qhat_s = (Q(2 mu) - Qbase) / (2 - 1). The probe time-scales the whole service
119 * law through `dist_scale_rate`, which multiplies (D0, D1) by the factor as the
120 * reference's in-place scaling of `sn.proc` and `sn.mu` does; the reachable
121 * space does not depend on the rate, so the probe's states line up with Qbase's.
122 *
123 * The forward equation is then propagated over a uniform grid of
124 * `ctmc_tv_ngrid` points (or the caller's `grid`), with the generator frozen on
125 * each step at the MIDPOINT multiplier and pi(t_{k+1}) = pi(t_k) expm(Q_k dt).
126 */
127template <class T>
128TimeVaryingTransient<T> ctmc_timevarying_transient(const NetworkStruct<T>& sn,
129 const CtmcOptions& opt, const Matrix<T>& Qbase,
130 const std::vector<T>& pi0, const T& t0,
131 const T& t1, const std::vector<T>& grid) {
132 const std::size_t M = sn.stations.size(), K = sn.nclasses;
133 const std::size_t nS = Qbase.rows();
134 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
135
136 std::vector<T> ts;
137 if (!grid.empty()) {
138 ts = grid;
139 } else {
140 const double t0d = num_traits<T>::to_double(t0), t1d = num_traits<T>::to_double(t1);
141 if (!std::isfinite(t1d))
142 throw InputError(
143 "solver_ctmc_transient_analyzer: a rate_sched transient needs a finite horizon");
144 const std::size_t ngrid = std::max<std::size_t>(2, opt.ctmc_tv_ngrid);
145 for (std::size_t i = 0; i < ngrid; ++i)
146 ts.push_back(num_traits<T>::from_double(
147 t0d + (t1d - t0d) * static_cast<double>(i) / static_cast<double>(ngrid - 1)));
148 }
149 const std::size_t nt = ts.size();
150 const std::size_t nsc = opt.rate_sched.size();
151
152 TimeVaryingTransient<T> out;
153 out.mscale.assign(M, std::vector<std::vector<T>>(K, std::vector<T>(nt, one)));
154 std::vector<Matrix<T>> Qhat(nsc);
155 std::vector<std::vector<T>> mtraj(nsc, std::vector<T>(nt, one));
156 const T probe = num_traits<T>::from_int(2);
157
158 for (std::size_t s = 0; s < nsc; ++s) {
159 const CtmcRateSched& rs = opt.rate_sched[s];
160 if (rs.station < 1 || rs.station > M || rs.cls < 1 || rs.cls > K)
161 throw InputError(
162 "solver_ctmc_transient_analyzer: a rate_sched entry names a (station, class) "
163 "outside the network");
164 if (rs.tgrid.empty() || rs.tgrid.size() != rs.rates.size())
165 throw InputError(
166 "solver_ctmc_transient_analyzer: a rate_sched entry needs tgrid and rates of the "
167 "same non-zero length");
168 for (std::size_t j = 1; j < rs.tgrid.size(); ++j)
169 if (!(rs.tgrid[j] > rs.tgrid[j - 1]))
170 throw InputError(
171 "solver_ctmc_transient_analyzer: a rate_sched tgrid must be strictly "
172 "increasing");
173 const std::size_t ist = rs.station, r = rs.cls;
174
175 NetworkStruct<T> snp = sn;
176 snp.set_service(ist, r, lang::dist_scale_rate(sn.service[ist - 1][r - 1], probe));
177 snp.refresh_rates();
178 const Matrix<T> Qp = solver_ctmc_analyzer(snp, opt).chain.Q;
179 if (Qp.rows() != nS)
180 throw InputError(
181 "solver_ctmc_transient_analyzer: the rate_sched probe changed the CTMC "
182 "state-space size; cannot build the time-varying generator");
183 Qhat[s] = Matrix<T>(nS, nS, zero);
184 for (std::size_t a = 0; a < nS; ++a)
185 for (std::size_t b = 0; b < nS; ++b)
186 Qhat[s](a, b) = T((Qp(a, b) - Qbase(a, b)) / (probe - one));
187
188 const double nominal = std::isnan(rs.nominal)
189 ? num_traits<T>::to_double(sn.rates(ist - 1, r - 1))
190 : rs.nominal;
191 if (!(nominal != 0.0) || !std::isfinite(nominal))
192 throw InputError(
193 "solver_ctmc_transient_analyzer: a rate_sched entry has a zero or undefined "
194 "nominal rate; cannot form the multiplier");
195 // interp1(tgrid, rates, clamp(t), 'linear'), with a one-point schedule constant
196 for (std::size_t k = 0; k < nt; ++k) {
197 const double tk = std::min(std::max(num_traits<T>::to_double(ts[k]), rs.tgrid.front()),
198 rs.tgrid.back());
199 double v = rs.rates.back();
200 for (std::size_t j = 0; j + 1 < rs.tgrid.size(); ++j)
201 if (tk <= rs.tgrid[j + 1]) {
202 const double w = (tk - rs.tgrid[j]) / (rs.tgrid[j + 1] - rs.tgrid[j]);
203 v = rs.rates[j] + w * (rs.rates[j + 1] - rs.rates[j]);
204 break;
205 }
206 mtraj[s][k] = num_traits<T>::from_double(v / nominal);
207 }
208 out.mscale[ist - 1][r - 1] = mtraj[s];
209 }
210
211 const T half = T(one / num_traits<T>::from_int(2));
212 out.tr.t = ts;
213 out.tr.pi = Matrix<T>(nt, nS, zero);
214 for (std::size_t j = 0; j < nS; ++j) out.tr.pi(0, j) = pi0[j];
215 for (std::size_t k = 0; k + 1 < nt; ++k) {
216 const T dt = ts[k + 1] - ts[k];
217 Matrix<T> Qk(nS, nS, zero);
218 for (std::size_t a = 0; a < nS; ++a)
219 for (std::size_t b = 0; b < nS; ++b) Qk(a, b) = T(Qbase(a, b) * dt);
220 for (std::size_t s = 0; s < nsc; ++s) {
221 const T mk = T(half * (mtraj[s][k] + mtraj[s][k + 1]));
222 const T c = T((mk - one) * dt);
223 for (std::size_t a = 0; a < nS; ++a)
224 for (std::size_t b = 0; b < nS; ++b) Qk(a, b) += T(c * Qhat[s](a, b));
225 }
226 const Matrix<T> E = expm(Qk);
227 for (std::size_t b = 0; b < nS; ++b) {
228 T acc = zero;
229 for (std::size_t a = 0; a < nS; ++a) acc += T(out.tr.pi(k, a) * E(a, b));
230 out.tr.pi(k + 1, b) = acc;
231 }
232 }
233 return out;
234}
235
236} // namespace detail
237
238/** What one transient CTMC solve produces. */
239template <class T>
241 std::vector<T> t; ///< the time grid the solver chose
242 Matrix<T> pit; ///< (ntimes x nstates) occupancy
243 std::vector<std::vector<std::vector<T>>> QNt, UNt, TNt; ///< [station][class][time]
244 CtmcSolution<T> chain; ///< the generator and its state space
245};
246
247/**
248 * Port of `solver_ctmc_transient_analyzer.m`.
249 *
250 * @param t0,t1 the timespan; the initial state is the model's default one
251 *
252 * `options.config.rate_sched` selects the time-INHOMOGENEOUS generator, where a
253 * per-(station, class) rate follows a schedule (`detail::ctmc_timevarying_transient`).
254 * It is integrated by its own piecewise-constant propagator, so it refuses
255 * `transient_method = "fau"` rather than silently answering the constant-rate
256 * question, and each scheduled pair's throughput is scaled by its m(t).
257 * @param sn the refreshed network struct
258 * @param opt CTMC options (state-space cutoff, tolerances, method)
259 */
260template <class T>
262 const CtmcOptions& opt, const T& t0, const T& t1,
263 const std::vector<T>& grid = std::vector<T>()) {
265 "solver_ctmc_transient_analyzer integrates the forward equation with an "
266 "adaptive Runge-Kutta step controller, whose error norm is transcendental; "
267 "use --arith double or real");
268 check_method(opt.method);
269 // The reference refuses this by name too (`@@SolverCTMC/runAnalyzer.m:113`).
270 // A fork-join model is solved on the TAG-AUGMENTED copy, whose per-class
271 // trajectories are indexed by the auxiliary classes; folding them back is a
272 // steady-state aggregate (`sn_fj_foldback` recomputes response time by
273 // Little's law), and Little's law does not hold pointwise in time.
275 throw UnsupportedError(
276 "SolverCTMC: transient analysis of a fork-join model is not supported. The chain is "
277 "the tag-augmented one, and folding the sibling classes back onto the original ones "
278 "is a steady-state aggregate; use the stationary solve, or SolverLDES for transients");
279
282 const std::size_t n = out.chain.chain.space.size();
283 const std::size_t M = sn.stations.size(), K = sn.nclasses;
284
285 // The model's initial DISTRIBUTION: a point mass on the default state where
286 // no prior is declared, and the product of the declared per-node priors
287 // where one is. Unlike the stationary solve, where the component alone
288 // matters, the transient answer depends on WHERE the chain starts, so a
289 // state that is not in the space is an error here.
290 std::vector<T> pi0;
291 if (!analyzer_detail::init_state_distribution(sn, out.chain.chain.space, pi0))
292 throw InputError(
293 "solver_ctmc_transient_analyzer: the initial state is not contained in the state "
294 "space, so there is no distribution to start the integration from");
295
297 std::vector<std::vector<std::vector<T>>> mscale;
298 if (!opt.rate_sched.empty()) {
299 if (opt.transient_method != "ode")
300 throw InputError(
301 "solver_ctmc_transient_analyzer: options.config.transient_method is not "
302 "available together with a rate schedule");
303 detail::TimeVaryingTransient<T> tv = detail::ctmc_timevarying_transient(
304 sn, opt, out.chain.chain.Q, pi0, t0, t1, grid);
305 tr = std::move(tv.tr);
306 mscale = std::move(tv.mscale);
307 } else if (opt.transient_method == "fau") {
308 tr = detail::ctmc_fau_transient(out.chain.chain.Q, pi0, t0, t1, opt, grid);
309 } else if (opt.transient_method == "ode") {
310 tr = mc::ctmc_transient(out.chain.chain.Q, pi0, t0, t1);
311 // `options.timestep`, applied where the reference applies it: on the way
312 // out of the integrator, not inside it. An explicit `grid` is the same
313 // resampling on points a uniform step cannot express, and it WINS: a caller
314 // that names the abscissae is integrating something against this trajectory
315 // and needs its own, which is what the environment coupling does.
316 if (!grid.empty())
317 tr = mc::ctmc_transient_on_grid(out.chain.chain.Q, tr, grid);
318 else if (opt.timestep > 0.0)
319 tr = mc::ctmc_transient_on_grid(out.chain.chain.Q, tr, t0, t1,
321 } else {
322 throw InputError("solver_ctmc_transient_analyzer: unknown transient_method '" +
323 opt.transient_method + "'; use 'ode' or 'fau'");
324 }
325 out.t = tr.t;
326 out.pit = tr.pi;
327 const std::size_t nt = out.t.size();
328
329 // Clamp the numerical dust the integrator leaves below the zero threshold,
330 // as the reference does before reading any measure off pi(t).
331 for (std::size_t i = 0; i < nt; ++i)
332 for (std::size_t s = 0; s < n; ++s)
334 out.pit(i, s) = num_traits<T>::from_int(0);
335
336 const Matrix<T> A = ctmc_state_space_aggr(sn, out.chain.chain.space);
337 const T zero = num_traits<T>::from_int(0);
338 out.QNt.assign(M, std::vector<std::vector<T>>(K, std::vector<T>(nt, zero)));
339 out.UNt.assign(M, std::vector<std::vector<T>>(K, std::vector<T>(nt, zero)));
340 out.TNt.assign(M, std::vector<std::vector<T>>(K, std::vector<T>(nt, zero)));
341
342 for (std::size_t ist = 1; ist <= M; ++ist) {
343 const std::size_t isf = sn.stateful_of_station(ist);
344 if (isf == 0) continue;
345 const bool is_source = sn.stations[ist - 1].nodetype == NodeType::Source;
346 const double S = sn.stations[ist - 1].nservers;
347 const SchedStrategy sched = sn.stations[ist - 1].sched;
348
349 for (std::size_t k = 1; k <= K; ++k)
350 for (std::size_t i = 0; i < nt; ++i) {
351 T acc = zero;
352 for (std::size_t s = 0; s < n; ++s)
353 acc += T(out.pit(i, s) * out.chain.chain.dep_rates[s][isf - 1][k - 1]);
354 if (!mscale.empty()) acc = T(acc * mscale[ist - 1][k - 1][i]);
355 out.TNt[ist - 1][k - 1][i] = acc;
356 }
357 // A Source's marginal is an encoding sentinel, so its queue length and
358 // utilization are reported as zero rather than read off it.
359 if (is_source) continue;
360
361 for (std::size_t k = 1; k <= K; ++k)
362 for (std::size_t i = 0; i < nt; ++i) {
363 T q = zero;
364 for (std::size_t s = 0; s < n; ++s) q += T(out.pit(i, s) * A(s, (ist - 1) * K + k - 1));
365 out.QNt[ist - 1][k - 1][i] = q;
366 }
367
368 if (sched == SchedStrategy::INF) {
369 for (std::size_t k = 0; k < K; ++k) out.UNt[ist - 1][k] = out.QNt[ist - 1][k];
370 continue;
371 }
372 if (sched == SchedStrategy::PS) {
373 // The capacity share of a processor-sharing station: class k takes
374 // n_k / sum_j n_j of the min(total, c) busy servers.
375 for (std::size_t k = 1; k <= K; ++k)
376 for (std::size_t i = 0; i < nt; ++i) {
377 T u = zero;
378 for (std::size_t s = 0; s < n; ++s) {
379 double tot = 0;
380 for (std::size_t j = 0; j < K; ++j)
381 tot += num_traits<T>::to_double(A(s, (ist - 1) * K + j));
382 if (tot <= 0) continue;
383 const double nk = num_traits<T>::to_double(A(s, (ist - 1) * K + k - 1));
384 u += T(out.pit(i, s) *
385 num_traits<T>::from_double(std::min(nk, S) * nk / tot / S));
386 }
387 out.UNt[ist - 1][k - 1][i] = u;
388 }
389 continue;
390 }
391 if (sched == SchedStrategy::DPS) {
392 const std::vector<T>& w = sn.stations[ist - 1].schedparam;
393 for (std::size_t k = 1; k <= K; ++k)
394 for (std::size_t i = 0; i < nt; ++i) {
395 T u = zero;
396 for (std::size_t s = 0; s < n; ++s) {
397 double wtot = 0;
398 for (std::size_t j = 0; j < K; ++j)
399 wtot += num_traits<T>::to_double(w[j]) *
400 num_traits<T>::to_double(A(s, (ist - 1) * K + j));
401 if (wtot <= 0) continue;
402 const double nk = num_traits<T>::to_double(A(s, (ist - 1) * K + k - 1));
403 u += T(out.pit(i, s) * num_traits<T>::from_double(
404 S * num_traits<T>::to_double(w[k - 1]) * nk /
405 wtot));
406 }
407 out.UNt[ist - 1][k - 1][i] = u;
408 }
409 continue;
410 }
411 // FCFS, HOL, SIRO, SEPT, LEPT, SJF -- and, as an APPROXIMATION the
412 // reference warns about, every remaining discipline.
413 for (std::size_t k = 1; k <= K; ++k) {
414 const lang::Distrib<T>& d = sn.service[ist - 1][k - 1];
415 if (d.disabled || d.D0.rows() == 0) continue;
416 for (std::size_t i = 0; i < nt; ++i) {
417 T u = zero;
418 for (std::size_t s = 0; s < n; ++s) {
419 const double nk = num_traits<T>::to_double(A(s, (ist - 1) * K + k - 1));
420 u += T(out.pit(i, s) * num_traits<T>::from_double(std::min(nk, S) / S));
421 }
422 out.UNt[ist - 1][k - 1][i] = u;
423 }
424 }
425 }
426 return out;
427}
428
429} // namespace ctmc
430} // namespace line
431
432#endif // LINE_SOLVERS_CTMC_SOLVER_CTMC_TRANSIENT_H
InputError(const std::string &what)
Definition error.h:39
UnsupportedError(const std::string &what)
Definition error.h:51
A network plus its refreshed NetworkStruct.
Transient distribution of a CTMC by fast adaptive uniformization.
Transient distribution of a CTMC over a time interval, by integrating the forward equations d pi/dt =...
Rate-scaled copy of a distribution, preserving its shape.
The exception types the port throws.
Matrix exponential by scaling and squaring with a diagonal Pade approximant.
Fork-join TAG AUGMENTATION: the fold-back half of the transform/lift pair that CTMC and SSA share.
Dense matrix and non-owning view.
void check_method(const std::string &method)
Port of runAnalyzerChecks' method gate.
CtmcSolution< T > solver_ctmc_analyzer(const NetworkStruct< T > &sn_in, const CtmcOptions &opt)
Port of solver_ctmc_analyzer.m plus the fork-join wrapper of @@SolverCTMC/runAnalyzer....
CtmcTransient< T > solver_ctmc_transient_analyzer(const NetworkStruct< T > &sn, const CtmcOptions &opt, const T &t0, const T &t1, const std::vector< T > &grid=std::vector< T >())
Port of solver_ctmc_transient_analyzer.m.
Matrix< T > ctmc_state_space_aggr(const NetworkStruct< T > &sn, const std::vector< NetState< T > > &space)
Port of StateSpaceAggr: the per-(station, class) job counts of every state, as an (nstates x nstation...
SchedStrategy
Scheduling disciplines, with the values of MATLAB SchedStrategy.
Definition lang_types.h:181
Distrib< T > dist_scale_rate(const Distrib< T > &d, const T &factor)
The law of X / factor, in the same family as d.
TransientResult< T > ctmc_transient_on_grid(const Matrix< T > &Q, const TransientResult< T > &r, const std::vector< T > &grid)
Resample an adaptive transient onto the uniform grid t0 : dt : t1.
TransientResult< T > ctmc_transient(const Matrix< T > &Q, const std::vector< T > &pi0, const T &t0, const T &t1, double rtol=1e-3, double atol=1e-6)
Transient distribution of a CTMC over a time interval, by integrating the forward equations d pi/dt =...
FauResult< T > ctmc_fau(const std::vector< T > &pi0, const Matrix< T > &Q, const T &t, double epsilon=1e-6, double delta=1e-12, long maxsteps=-1)
Transient distribution of a CTMC by fast adaptive uniformization.
Definition ctmc_fau.h:256
bool has_fork_join(const qn::NetworkStruct< T > &sn)
Whether the model needs the tag augmentation at all.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Matrix< T > expm(const Matrix< T > &A)
Matrix exponential exp(A).
Definition expm.h:141
A queueing network and its refreshed NetworkStruct.
Port of solver_ctmc_analyzer.m and the parts of @@SolverCTMC/runAnalyzer.m that surround one solve: t...
The SolverCTMC knobs this port honours.
Everything one CTMC solve produces.
What one transient CTMC solve produces.
std::vector< T > t
the time grid the solver chose
std::vector< std::vector< std::vector< T > > > QNt
Matrix< T > pit
(ntimes x nstates) occupancy
CtmcSolution< T > chain
the generator and its state space
std::vector< std::vector< std::vector< T > > > UNt
std::vector< std::vector< std::vector< T > > > TNt
[station][class][time]
static constexpr double Zero
Definition lang_types.h:762
Matrix< T > D0
The (D0,D1) pair when the type carries one directly.
Definition lang_types.h:853