LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sn_nonmarkov_toph.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_SN_SN_NONMARKOV_TOPH_H
6#define LINE_API_SN_SN_NONMARKOV_TOPH_H
7
8/**
9 * @file
10 * @ingroup api_sn
11 * Replace every non-Markovian service and firing law by a Markovian surrogate.
12 *
13 * Templated port of matlab/src/api/sn/sn_nonmarkov_toph.m, mirrored by
14 * jline.api.sn.SnNonmarkovToPh and the native Python sn_nonmarkov_toph. The
15 * MAM, CTMC, Fluid and SSA analyzers all run it on their own copy of the struct
16 * before any state space is built, so a Gamma, Weibull, Lognormal, Pareto,
17 * Uniform or Det service law reaches an algorithm that only understands
18 * generators.
19 *
20 * IT IS NOT `convertToMAP`. The struct refresh already lowers those families to
21 * an Erlang of ceil(1/scv) phases (`dist_to_map` in lang/distribution.h); that
22 * fit matches the mean and, above SCV 1, nothing else. This is the SOLVER-side
23 * conversion, with an explicit phase budget (20 by default) and a choice of fit:
24 *
25 * - phfit = Cme, the default: a concentrated matrix exponential convolved with
26 * an exponential (`dist_fit_me`), which matches the first TWO moments exactly
27 * whenever the SCV is in (0,1). It is what makes an M/G/1 mean come out
28 * right: on M/Gamma/1 at rho 0.5 the two-moment fit lands on the exact mean
29 * queue length where the density fit lands 2.7e-2 away.
30 * - phfit = Ph: the Bernstein density fit (`map_bernstein`), kept because it
31 * carries SHAPE information a two-moment fit cannot, and because SSA, Fluid
32 * and JMT cannot consume a matrix exponential at all.
33 *
34 * DET IS ERLANG, WHATEVER phfit ASKS FOR. A concentrated ME matches a Det's
35 * moments far better (SCV 5.7e-3 against Erlang-20's 0.05) but is not a
36 * generator: its off-diagonal entries are not rates, so a CTMC built from it
37 * does not describe the model. Measured on the reference's test_OQN_DM1 (Det(1)
38 * arrivals, Exp(2) service, rho = 0.5), the ME surrogate reported U = 0.993 and
39 * a departure rate of 2.0 against a mean interarrival of 1.0, where JMT gives
40 * 0.497, LDES 0.502 and the golden 0.500.
41 *
42 * WHAT IS NOT TOUCHED. The Markovian families, DISABLED, IMMEDIATE, and the
43 * three time-inhomogeneous families NHPP / MAPt / PHt, whose whole content is
44 * the schedule -- collapsing one to a single homogeneous surrogate would erase
45 * the time dependence that is the reason it was declared.
46 *
47 * NO STATE SURGERY IS NEEDED HERE. The MATLAB reference rewrites sn.phases,
48 * phasessz, phaseshift, mu, phi, pie and then splices extra columns into any
49 * pre-initialized sn.state and sn.space, because those are stored arrays that
50 * would otherwise disagree with the new phase count. In this port every one of
51 * them is DERIVED from `sn.service[i][r]` on demand (`phases_of`, `dist_pie`,
52 * `dist_to_map`), so replacing the distribution updates all of them at once and
53 * there is nothing left to splice. Callers run this before generating states,
54 * exactly as the reference's analyzers do.
55 *
56 * ARITHMETIC: transcendental. The fits evaluate densities and take square
57 * roots, so this does not instantiate under Rational.
58 */
59
60#include <cmath>
61#include <cstddef>
62#include <functional>
63#include <map>
64#include <vector>
65
66#include "line/api/mam/cme.h"
75#include "line/num/number.h"
76#include "line/util/error.h"
77
78namespace line {
79namespace api {
80
81/** Which Markovian surrogate to fit; `options.config.phfit`. */
82enum class PhFit {
83 Cme, ///< concentrated ME plus exponential tail: two moments, exactly
84 Ph, ///< Bernstein density fit: a genuine phase-type, shape-carrying
85 /**
86 * Mixture of exponentials fitted to the ccdf ITSELF across decades of time
87 * scale (`hyperexp_fit_longtail`, Feldmann and Whitt 1998).
88 *
89 * The only one of the three that says anything about a LONG TAIL: a Pareto
90 * with tail index below 2 has no finite variance, so a two-moment fit does
91 * not exist at all, and even where the moments are finite they say nothing
92 * about the several orders of magnitude over which such a law acts. It
93 * applies only to the families with a closed-form tail below; a light-tailed
94 * law falls through to the two-moment surrogate.
95 */
97};
98
99/** `options.config.nonmkv` and friends. */
101 bool enabled = true; ///< false is the reference's nonmkv = 'none'
102 std::size_t order = 20; ///< `nonmkvorder`, the phase budget
103 PhFit phfit = PhFit::Cme; ///< which surrogate family
104 /**
105 * Leave Det alone for the exact MAP/D/c branch, `options.config.preserveDet`.
106 * The MAM analyzer sets it; every other caller converts.
107 */
108 bool preserve_det = false;
109};
110
111namespace detail {
112
113/** True for the families that already carry a Markovian representation. */
114inline bool nonmkv_is_markovian(lang::ProcessType p) {
115 using lang::ProcessType;
116 switch (p) {
117 case ProcessType::EXP:
118 case ProcessType::ERLANG:
119 case ProcessType::HYPEREXP:
120 case ProcessType::PH:
121 case ProcessType::APH:
122 case ProcessType::MAP:
123 case ProcessType::DMAP:
124 case ProcessType::MMAP:
125 case ProcessType::BMAP:
126 case ProcessType::ME:
127 case ProcessType::RAP:
128 case ProcessType::COXIAN:
129 case ProcessType::COX2:
130 case ProcessType::MMPP2:
131 case ProcessType::IMMEDIATE:
132 case ProcessType::DISABLED:
133 return true;
134 default:
135 return false;
136 }
137}
138
139/**
140 * True for the families whose content IS a schedule.
141 *
142 * A single homogeneous surrogate has no way to carry a piecewise-constant
143 * D0(t)/D1(t), so converting one would silently answer for a stationary model.
144 */
145inline bool nonmkv_is_scheduled(lang::ProcessType p) {
146 using lang::ProcessType;
147 return p == ProcessType::NHPP || p == ProcessType::MAPT || p == ProcessType::PHT;
148}
149
150/**
151 * The density of the five families the reference fits by shape, or an empty
152 * function for anything else (which then takes the moment-only surrogate).
153 */
154template <class T>
155std::function<double(double)> nonmkv_density(const lang::Distrib<T>& d) {
156 using lang::ProcessType;
157 std::vector<double> p;
158 for (std::size_t i = 0; i < d.params.size(); ++i)
159 p.push_back(num_traits<T>::to_double(d.params[i]));
160 switch (d.type) {
161 case ProcessType::GAMMA: { // params (shape, scale)
162 if (p.size() < 2) break;
163 const double k = p[0], th = p[1];
164 return [k, th](double x) {
165 if (!(x > 0.0)) return 0.0;
166 return std::exp((k - 1.0) * std::log(x) - x / th - k * std::log(th) -
167 std::lgamma(k));
168 };
169 }
170 case ProcessType::WEIBULL: { // params (scale, shape), the C++ builder order
171 if (p.size() < 2) break;
172 const double a = p[0], r = p[1];
173 return [a, r](double x) {
174 if (!(x > 0.0)) return 0.0;
175 return (r / a) * std::pow(x / a, r - 1.0) * std::exp(-std::pow(x / a, r));
176 };
177 }
178 case ProcessType::LOGNORMAL: { // params (mu, sigma) of the log
179 if (p.size() < 2) break;
180 const double mu = p[0], sg = p[1];
181 return [mu, sg](double x) {
182 if (!(x > 0.0)) return 0.0;
183 const double z = (std::log(x) - mu) / sg;
184 return std::exp(-0.5 * z * z) / (x * sg * std::sqrt(2.0 * 3.14159265358979323846));
185 };
186 }
187 case ProcessType::PARETO: { // params (shape alpha, scale k)
188 if (p.size() < 2) break;
189 const double al = p[0], k = p[1];
190 return [al, k](double x) {
191 if (!(x >= k) || !(x > 0.0)) return 0.0;
192 return al * std::pow(k, al) / std::pow(x, al + 1.0);
193 };
194 }
195 case ProcessType::UNIFORM: { // params (a, b)
196 if (p.size() < 2) break;
197 const double a = p[0], b = p[1];
198 return [a, b](double x) { return (x >= a && x <= b) ? 1.0 / (b - a) : 0.0; };
199 }
200 default:
201 break;
202 }
203 return std::function<double(double)>();
204}
205
206/**
207 * The declared law's complementary cdf, for the families whose tail the
208 * long-tail fit is stated for. Empty for every other law, which is what makes
209 * `phfit = Hyperexp` fall through to the two-moment surrogate there.
210 *
211 * Built on `dist_cdf`, which already carries the closed form of each of these
212 * four families -- the Gamma from the regularized incomplete gamma, the Pareto
213 * from 1 - (k/x)^alpha -- so there is no second copy of any of them here.
214 */
215template <class T>
216std::function<double(double)> nonmkv_ccdf(const lang::Distrib<T>& d) {
217 using lang::ProcessType;
218 if (d.type != ProcessType::GAMMA && d.type != ProcessType::WEIBULL &&
219 d.type != ProcessType::LOGNORMAL && d.type != ProcessType::PARETO)
220 return std::function<double(double)>();
221 const lang::Distrib<T> law = d;
222 return [law](double x) {
223 if (!(x > 0.0)) return 1.0;
224 return 1.0 - num_traits<T>::to_double(lang::dist_cdf(law, num_traits<T>::from_double(x)));
225 };
226}
227
228/**
229 * A hyperexponential fitted to the ccdf across decades, as its MAP pair and
230 * rescaled to the mean the struct carries.
231 *
232 * `ok` comes back false when the recursion declines the law: the components have
233 * to dominate one another at their own time scales, which a light-tailed law
234 * does not provide, and answering with a fit that does not hold is worse than
235 * falling through to the two-moment surrogate.
236 */
237template <class T>
238mam::Map<T> nonmkv_longtail(const std::function<double(double)>& ccdf, double mean, bool& ok) {
239 ok = false;
240 mam::Map<T> out;
241 // The fit itself runs in double: it bisects for quantiles and takes
242 // logarithms of the ccdf, which is a `double(double)` here whatever T is.
243 mam::HyperexpLongtailResult<double> fit;
244 try {
246 } catch (const std::exception&) {
247 return out;
248 }
249 const std::size_t n = fit.p.size();
250 if (n == 0) return out;
251 Matrix<T> D0(n, n, num_traits<T>::from_int(0));
252 Matrix<T> D1(n, n, num_traits<T>::from_int(0));
253 for (std::size_t i = 0; i < n; ++i) {
254 if (!std::isfinite(fit.lambda[i]) || fit.lambda[i] <= 0.0) return out;
255 D0(i, i) = num_traits<T>::from_double(-fit.lambda[i]);
256 for (std::size_t j = 0; j < n; ++j)
257 D1(i, j) = num_traits<T>::from_double(fit.lambda[i] * fit.p[j]);
258 }
259 mam::Map<T> m;
260 m.D0 = D0;
261 m.D1 = D1;
262 out = mam::map_scale(m, num_traits<T>::from_double(mean));
263 ok = true;
264 return out;
265}
266
267/**
268 * The moment-only surrogate: a concentrated ME under phfit = Cme, an Erlang of
269 * the full budget otherwise. Port of the reference's fitConcentratedSurrogate.
270 */
271template <class T>
272mam::Map<T> nonmkv_concentrated(double mean, double scv, std::size_t order, PhFit phfit) {
273 if (phfit == PhFit::Cme) {
274 // A Det has SCV 0, which no ME attains; the budget-limited branch of the
275 // fitter then returns the most concentrated member that fits.
276 const double s = scv > 1e-12 ? scv : 1e-12;
277 if (s < 1.0) return mam::dist_fit_me<T>(mean, s < 1.0 - 1e-12 ? s : 1.0 - 1e-12, order);
278 }
279 return mam::map_erlang(num_traits<T>::from_double(mean), static_cast<unsigned>(order));
280}
281
282/** Install a fitted (D0,D1) as the law of a station-class pair or a firing mode. */
283template <class T>
284void nonmkv_install(lang::Distrib<T>& d, const mam::Map<T>& m) {
285 std::vector<Matrix<T>> blocks;
286 blocks.push_back(m.D0);
287 blocks.push_back(m.D1);
288 const bool isph = sn_is_phasetype(blocks, std::vector<T>());
289 // The declared law is kept alongside the fit. Everything downstream reads
290 // the surrogate, as it did; MMAP[K]/G[K]/1 evaluates the ORIGINAL transform
291 // and would otherwise find only the fit it exists to avoid.
292 std::shared_ptr<lang::Distrib<T>> declared(new lang::Distrib<T>(d));
293 declared->declared.reset();
294 // A concentrated-ME surrogate is NOT a phase-type, so it is tagged ME: the
295 // CTMC then assembles a rational generator and the PH-only consumers refuse
296 // it. sn_is_phasetype is the single test, exactly as in the reference.
297 d = lang::Distrib<T>::map_dist(m.D0, m.D1,
300 d.declared = declared;
301}
302
303/**
304 * True when mode `m`'s firing law is ALREADY a usable Markovian one and needs
305 * no fit.
306 *
307 * A POSITIVE PHASE COUNT IS NOT ENOUGH, and reading it as such is what left
308 * `spn_pareto_service` absorbing. `network_reader` records
309 * `firingphases[m] = dist_to_map(proc).order()` at read time, so a Pareto mode
310 * arrives with a phase count of the fit that WOULD be made while `firingproc[m]`
311 * is still the Pareto, whose (D0,D1) are empty. The old guard read that count,
312 * skipped the conversion, and `after_global_event` then found no firing matrix:
313 * the chain reached the enabled state and stopped there, filling the place to
314 * the cutoff and reporting Tput 0 with QLen equal to the cutoff at every cutoff.
315 * The matrices themselves are the test.
316 */
317template <class T>
318bool nonmkv_firing_ready(const qn::TransitionParam<T>& np, std::size_t m) {
319 if (m >= np.firingphases.size() || np.firingphases[m] <= 0) return false;
320 if (m >= np.firingproc.size()) return false;
321 return np.firingproc[m].D0.rows() == static_cast<std::size_t>(np.firingphases[m]);
322}
323
324} // namespace detail
325
326/**
327 * Whether any law in the struct would be replaced, so a caller can skip copying
328 * the struct when there is nothing to convert.
329 *
330 * @param sn the struct to inspect
331 * @param preserve_det leave Det alone, as the MAM analyzer does
332 */
333template <class T>
334bool sn_has_nonmarkov(const qn::NetworkStruct<T>& sn, bool preserve_det = false) {
335 using lang::ProcessType;
336 for (std::size_t ist = 0; ist < sn.nstations; ++ist)
337 for (std::size_t r = 0; r < sn.nclasses; ++r) {
338 if (ist < sn.disabled.size() && r < sn.disabled[ist].size() && sn.disabled[ist][r])
339 continue;
340 const lang::Distrib<T>& d = sn.service[ist][r];
341 if (d.disabled || d.is_prior()) continue;
342 if (detail::nonmkv_is_markovian(d.type) || detail::nonmkv_is_scheduled(d.type))
343 continue;
344 if (d.type == ProcessType::DET && preserve_det) continue;
345 return true;
346 }
347 for (typename std::map<std::size_t, qn::TransitionParam<T>>::const_iterator it =
348 sn.transparam.begin();
349 it != sn.transparam.end(); ++it) {
350 const qn::TransitionParam<T>& np = it->second;
351 for (std::size_t m = 0; m < np.firingproc.size(); ++m) {
352 if (detail::nonmkv_firing_ready(np, m)) continue;
353 const lang::Distrib<T>& d = np.firingproc[m];
354 if (d.disabled || d.is_prior()) continue;
355 if (detail::nonmkv_is_markovian(d.type) || detail::nonmkv_is_scheduled(d.type))
356 continue;
357 if (d.type == ProcessType::DET && preserve_det) continue;
358 return true;
359 }
360 }
361 return false;
362}
363
364/**
365 * @brief Replace every non-Markovian service and firing law by a Markovian
366 * surrogate.
367 *
368 * @param sn the struct to convert IN PLACE; callers pass their own copy
369 * @param opts the conversion method, order and fit family
370 */
371template <class T>
374 "sn_nonmarkov_toph evaluates densities and fits moments");
375 using lang::ProcessType;
376 if (!opts.enabled) return;
377 if (opts.order == 0) throw InputError("sn_nonmarkov_toph: the phase budget must be positive");
378
379 for (std::size_t ist = 0; ist < sn.nstations; ++ist) {
380 for (std::size_t r = 0; r < sn.nclasses; ++r) {
381 if (ist < sn.disabled.size() && r < sn.disabled[ist].size() && sn.disabled[ist][r])
382 continue;
383 lang::Distrib<T>& d = sn.service[ist][r];
384 if (d.disabled || d.is_prior()) continue;
385 const ProcessType p = d.type;
386 if (detail::nonmkv_is_markovian(p) || detail::nonmkv_is_scheduled(p)) continue;
387 if (p == ProcessType::DET && opts.preserve_det) continue;
388
389 const double mean = num_traits<T>::to_double(d.mean);
390 const double scv = num_traits<T>::to_double(d.scv);
391 if (!std::isfinite(mean) || mean <= 0.0) continue;
392
393 if (p == ProcessType::DET) {
394 detail::nonmkv_install(
396 static_cast<unsigned>(opts.order)));
397 continue;
398 }
399
400 // The long-tail fit, when it was asked for and the law has a tail
401 // to fit. It matches the ccdf at points spread over decades rather
402 // than matching two moments, so it is the route for a Pareto, a
403 // Weibull with shape below one or a Lognormal with a large sigma.
404 if (opts.phfit == PhFit::Hyperexp) {
405 const std::function<double(double)> ccdf = detail::nonmkv_ccdf(d);
406 if (ccdf) {
407 bool ok = false;
408 const mam::Map<T> lt = detail::nonmkv_longtail<T>(ccdf, mean, ok);
409 if (ok) {
410 detail::nonmkv_install(d, lt);
411 continue;
412 }
413 }
414 }
415
416 const std::function<double(double)> pdf = detail::nonmkv_density(d);
417 if (!pdf) { // no density to fit by shape: moments only
418 detail::nonmkv_install(d,
419 detail::nonmkv_concentrated<T>(mean, scv, opts.order,
420 opts.phfit));
421 continue;
422 }
423 if (opts.phfit == PhFit::Cme && scv >= 0.0 && scv < 1.0) {
424 detail::nonmkv_install(d,
425 detail::nonmkv_concentrated<T>(mean, scv, opts.order,
426 opts.phfit));
427 continue;
428 }
429 const mam::Map<T> fit =
430 mam::map_scale(mam::map_bernstein<T>(pdf, static_cast<unsigned>(opts.order)),
432 detail::nonmkv_install(d, fit);
433 }
434 }
435
436 // ---- SPN transition firing laws -------------------------------------
437 for (std::size_t ind = 0; ind < sn.nodes.size(); ++ind) {
438 if (sn.nodes[ind].nodetype != qn::NodeType::Transition) continue;
439 typename std::map<std::size_t, qn::TransitionParam<T>>::iterator it =
440 sn.transparam.find(ind + 1);
441 if (it == sn.transparam.end()) continue;
442 qn::TransitionParam<T>* np = &it->second;
443 for (std::size_t m = 0; m < np->firingproc.size(); ++m) {
444 // refreshPetriNetNodes already gives a Markovian mode a valid
445 // (D0,D1) and a positive phase count; those are left alone. The
446 // MATRICES decide, not the count -- see `nonmkv_firing_ready`.
447 if (detail::nonmkv_firing_ready(*np, m)) continue;
448 lang::Distrib<T>& d = np->firingproc[m];
449 if (d.disabled || d.is_prior()) continue;
450 const ProcessType p = d.type;
451 if (detail::nonmkv_is_markovian(p) || detail::nonmkv_is_scheduled(p)) continue;
452 if (p == ProcessType::DET && opts.preserve_det) continue;
453
454 const double mean = num_traits<T>::to_double(d.mean);
455 const double scv = num_traits<T>::to_double(d.scv);
456 mam::Map<T> fit;
457 if (p == ProcessType::DET || !std::isfinite(mean) || mean <= 0.0) {
458 // A firing law with no usable mean falls back to the unit-mean
459 // Erlang, as the reference does rather than refusing the model.
460 const double mu = (std::isfinite(mean) && mean > 0.0) ? mean : 1.0;
462 static_cast<unsigned>(opts.order));
463 } else {
464 const std::function<double(double)> pdf = detail::nonmkv_density(d);
465 if (!pdf || (opts.phfit == PhFit::Cme && scv >= 0.0 && scv < 1.0))
466 fit = detail::nonmkv_concentrated<T>(mean, scv, opts.order, opts.phfit);
467 else
468 fit = mam::map_scale(
469 mam::map_bernstein<T>(pdf, static_cast<unsigned>(opts.order)),
471 }
472 detail::nonmkv_install(d, fit);
473 if (m < np->firingphases.size()) np->firingphases[m] = fit.order();
474 }
475 }
476}
477
478} // namespace api
479} // namespace line
480
481#endif // LINE_API_SN_SN_NONMARKOV_TOPH_H
InputError(const std::string &what)
Definition error.h:39
A network plus its refreshed NetworkStruct.
Concentrated matrix exponentials, and the two-moment fit built on them.
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
Fitting a hyperexponential to a long-tail distribution.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
Acyclic phase-type approximation of an arbitrary density by Bernstein exponentials.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
MAP constructors and structural transformations.
bool sn_has_nonmarkov(const qn::NetworkStruct< T > &sn, bool preserve_det=false)
Whether any law in the struct would be replaced, so a caller can skip copying the struct when there i...
PhFit
Which Markovian surrogate to fit; options.config.phfit.
@ Ph
Bernstein density fit: a genuine phase-type, shape-carrying.
@ Hyperexp
Mixture of exponentials fitted to the ccdf ITSELF across decades of time scale (hyperexp_fit_longtail...
@ Cme
concentrated ME plus exponential tail: two moments, exactly
bool sn_is_phasetype(const std::vector< Matrix< T > > &maps, const std::vector< T > &pie)
Whether a (D0, D1, ...) list is a valid phase-type / MAP representation.
void sn_nonmarkov_toph(qn::NetworkStruct< T > &sn, const NonmarkovOptions &opts=NonmarkovOptions())
Replace every non-Markovian service and firing law by a Markovian surrogate.
T dist_cdf(const Distrib< T > &d, const T &x)
F(x) = P{X <= x}, MATLAB's Distribution.evalCDF.
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
Definition lang_types.h:483
void dist_refresh_moments(Distrib< T > &d)
Fill in the first two moments of a distribution given by its matrices.
Map< T > map_bernstein(const std::function< double(double)> &f, unsigned order=20)
Acyclic phase-type approximation of an arbitrary density by Bernstein exponentials.
HyperexpLongtailResult< T > hyperexp_fit_longtail(Ccdf &&ccdf, const T &b=num_traits< T >::from_rational(3, 2), const T &decade=num_traits< T >::from_int(4))
The fit with the component count chosen automatically: one per decade between the 0....
Map< T > dist_fit_me(double mean, double scv, std::size_t maxPhases=0)
Two-moment matrix-exponential fit for 0 < scv < 1, a port of dist_fit_me.m.
Definition cme.h:168
Map< T > map_erlang(const T &mean, unsigned k)
Erlang-k renewal MAP with the given mean (map_erlang.m).
Map< T > map_scale(const Map< T > &in, const T &new_mean)
Rescale time so that the mean inter-arrival time becomes new_mean.
A queueing network and its refreshed NetworkStruct.
Number-type abstraction for the templated API port.
Whether a (D0, D1, ...) list is a valid phase-type / MAP representation.
options.config.nonmkv and friends.
std::size_t order
nonmkvorder, the phase budget
PhFit phfit
which surrogate family
bool preserve_det
Leave Det alone for the exact MAP/D/c branch, options.config.preserveDet.
bool enabled
false is the reference's nonmkv = 'none'
static Distrib map_dist(const Matrix< T > &D0, const Matrix< T > &D1, ProcessType tag)
A MAP given by its two matrices; the moments are those of its stationary phase.
bool is_prior() const
Definition lang_types.h:776
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
std::size_t order() const
Definition map_moment.h:57
The parameters of a Cache node, MATLAB's sn.nodeparam{ind} for a Cache.
std::vector< lang::Distrib< T > > firingproc
firing distribution per mode
std::vector< std::size_t > firingphases
phase count per mode, 0 when non-Markovian