5#ifndef LINE_SOLVERS_CTMC_SOLVER_CTMC_TRANSIENT_H
6#define LINE_SOLVERS_CTMC_SOLVER_CTMC_TRANSIENT_H
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))
69 "solver_ctmc_transient_analyzer: transient_method 'fau' needs a finite horizon");
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);
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)));
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);
90 mc::TransientResult<T> out;
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);
99 for (std::size_t j = 0; j < cur.size(); ++j) out.pi(k, j) = cur[j];
106struct TimeVaryingTransient {
107 mc::TransientResult<T> tr;
109 std::vector<std::vector<std::vector<T>>> mscale;
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);
140 const double t0d = num_traits<T>::to_double(t0), t1d = num_traits<T>::to_double(t1);
141 if (!std::isfinite(t1d))
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)));
149 const std::size_t nt = ts.size();
150 const std::size_t nsc = opt.rate_sched.size();
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);
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)
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())
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]))
171 "solver_ctmc_transient_analyzer: a rate_sched tgrid must be strictly "
173 const std::size_t ist = rs.station, r = rs.cls;
175 NetworkStruct<T> snp = sn;
181 "solver_ctmc_transient_analyzer: the rate_sched probe changed the CTMC "
182 "state-space size; cannot build the time-varying generator");
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));
188 const double nominal = std::isnan(rs.nominal)
189 ? num_traits<T>::to_double(sn.rates(ist - 1, r - 1))
191 if (!(nominal != 0.0) || !std::isfinite(nominal))
193 "solver_ctmc_transient_analyzer: a rate_sched entry has a zero or undefined "
194 "nominal rate; cannot form the multiplier");
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()),
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]);
206 mtraj[s][k] = num_traits<T>::from_double(v / nominal);
208 out.mscale[ist - 1][r - 1] = mtraj[s];
211 const T half = T(one / num_traits<T>::from_int(2));
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));
226 const Matrix<T> E =
expm(Qk);
227 for (std::size_t b = 0; b < nS; ++b) {
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;
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");
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");
282 const std::size_t n = out.
chain.chain.space.size();
283 const std::size_t M =
sn.stations.size(), K =
sn.nclasses;
291 if (!analyzer_detail::init_state_distribution(
sn, out.
chain.chain.space, pi0))
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");
297 std::vector<std::vector<std::vector<T>>> mscale;
298 if (!
opt.rate_sched.empty()) {
299 if (
opt.transient_method !=
"ode")
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") {
318 else if (
opt.timestep > 0.0)
322 throw InputError(
"solver_ctmc_transient_analyzer: unknown transient_method '" +
323 opt.transient_method +
"'; use 'ode' or 'fau'");
327 const std::size_t nt = out.
t.size();
331 for (std::size_t i = 0; i < nt; ++i)
332 for (std::size_t s = 0; s < n; ++s)
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)));
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;
349 for (std::size_t k = 1; k <= K; ++k)
350 for (std::size_t i = 0; i < nt; ++i) {
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;
359 if (is_source)
continue;
361 for (std::size_t k = 1; k <= K; ++k)
362 for (std::size_t i = 0; i < nt; ++i) {
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;
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];
372 if (sched == SchedStrategy::PS) {
375 for (std::size_t k = 1; k <= K; ++k)
376 for (std::size_t i = 0; i < nt; ++i) {
378 for (std::size_t s = 0; s < n; ++s) {
380 for (std::size_t j = 0; j < K; ++j)
382 if (tot <= 0)
continue;
384 u += T(out.
pit(i, s) *
387 out.
UNt[ist - 1][k - 1][i] = u;
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) {
396 for (std::size_t s = 0; s < n; ++s) {
398 for (std::size_t j = 0; j < K; ++j)
401 if (wtot <= 0)
continue;
407 out.
UNt[ist - 1][k - 1][i] = u;
413 for (std::size_t k = 1; k <= K; ++k) {
416 for (std::size_t i = 0; i < nt; ++i) {
418 for (std::size_t s = 0; s < n; ++s) {
422 out.
UNt[ist - 1][k - 1][i] = u;
UnsupportedError(const std::string &what)
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.
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.
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.
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.
Matrix< T > expm(const Matrix< T > &A)
Matrix exponential exp(A).
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
Matrix< T > D0
The (D0,D1) pair when the type carries one directly.