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 <cctype>
61#include <cmath>
62#include <cstddef>
63#include <functional>
64#include <map>
65#include <string>
66#include <vector>
67
68#include "line/api/mam/cme.h"
77#include "line/num/number.h"
78#include "line/util/error.h"
79
80namespace line {
81namespace api {
82
83/** Which Markovian surrogate to fit; `options.config.phfit`. */
84enum class PhFit {
85 Cme, ///< concentrated ME plus exponential tail: two moments, exactly
86 Ph, ///< Bernstein density fit: a genuine phase-type, shape-carrying
87 /**
88 * Mixture of exponentials fitted to the ccdf ITSELF across decades of time
89 * scale (`hyperexp_fit_longtail`, Feldmann and Whitt 1998).
90 *
91 * The only one of the three that says anything about a LONG TAIL: a Pareto
92 * with tail index below 2 has no finite variance, so a two-moment fit does
93 * not exist at all, and even where the moments are finite they say nothing
94 * about the several orders of magnitude over which such a law acts. It
95 * applies only to the families with a closed-form tail below; a light-tailed
96 * law falls through to the two-moment surrogate.
97 */
99};
100
101/** `options.config.nonmkv` and friends. */
103 bool enabled = true; ///< false is the reference's nonmkv = 'none'
104 std::size_t order = 20; ///< `nonmkvorder`, the phase budget
105 PhFit phfit = PhFit::Cme; ///< which surrogate family
106 /**
107 * Leave Det alone for the exact MAP/D/c branch, `options.config.preserveDet`.
108 * The MAM analyzer sets it; every other caller converts.
109 */
110 bool preserve_det = false;
111};
112
113/**
114 * Resolve `options.config.nonmkv` to `NonmarkovOptions::enabled`.
115 *
116 * Case-insensitive, as in the reference: `'none'` leaves the struct unchanged
117 * (the refresh-time `dist_to_map` fit is what the solver then reads) and
118 * `'bernstein'`, the default, runs the conversion. Any other name is refused as
119 * `LINE:input:ConfigInvalid`, the id every codebase's options.config validator
120 * raises for a value outside the schema's list (lineConfigValidate.m).
121 */
122inline bool nonmkv_enabled(const std::string& method) {
123 std::string m = method;
124 for (char& c : m) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
125 if (m == "none") return false;
126 if (m == "bernstein") return true;
127 throw InputError(ErrorKind{"ConfigInvalid"},
128 "options.config.nonmkv expects one of 'bernstein', 'none', got '" + method + "'.");
129}
130
131namespace detail {
132
133/** True for the families that already carry a Markovian representation. */
134inline bool nonmkv_is_markovian(lang::ProcessType p) {
135 using lang::ProcessType;
136 switch (p) {
137 case ProcessType::EXP:
138 case ProcessType::ERLANG:
139 case ProcessType::HYPEREXP:
140 case ProcessType::PH:
141 case ProcessType::APH:
142 case ProcessType::MAP:
143 case ProcessType::DMAP:
144 case ProcessType::MMAP:
145 // The marked families already carry an exact matrix representation, so
146 // the Bernstein conversion has nothing to add and would drop the marks.
147 case ProcessType::MPH:
148 case ProcessType::MMAPT:
149 case ProcessType::MPHT:
150 case ProcessType::BMMAPT:
151 case ProcessType::BMAP:
152 case ProcessType::ME:
153 case ProcessType::RAP:
154 case ProcessType::COXIAN:
155 case ProcessType::COX2:
156 case ProcessType::MMPP2:
157 case ProcessType::IMMEDIATE:
158 case ProcessType::DISABLED:
159 return true;
160 default:
161 return false;
162 }
163}
164
165/**
166 * True for the families whose content IS a schedule.
167 *
168 * A single homogeneous surrogate has no way to carry a piecewise-constant
169 * D0(t)/D1(t), so converting one would silently answer for a stationary model.
170 */
171inline bool nonmkv_is_scheduled(lang::ProcessType p) {
172 using lang::ProcessType;
173 return p == ProcessType::NHPP || p == ProcessType::MAPT || p == ProcessType::PHT;
174}
175
176/**
177 * The density of the five families the reference fits by shape, or an empty
178 * function for anything else (which then takes the moment-only surrogate).
179 */
180template <class T>
181std::function<double(double)> nonmkv_density(const lang::Distrib<T>& d) {
182 using lang::ProcessType;
183 std::vector<double> p;
184 for (std::size_t i = 0; i < d.params.size(); ++i)
185 p.push_back(num_traits<T>::to_double(d.params[i]));
186 switch (d.type) {
187 case ProcessType::GAMMA: { // params (shape, scale)
188 if (p.size() < 2) break;
189 const double k = p[0], th = p[1];
190 return [k, th](double x) {
191 if (!(x > 0.0)) return 0.0;
192 return std::exp((k - 1.0) * std::log(x) - x / th - k * std::log(th) -
193 std::lgamma(k));
194 };
195 }
196 case ProcessType::WEIBULL: { // params (scale, shape), the C++ builder order
197 if (p.size() < 2) break;
198 const double a = p[0], r = p[1];
199 return [a, r](double x) {
200 if (!(x > 0.0)) return 0.0;
201 return (r / a) * std::pow(x / a, r - 1.0) * std::exp(-std::pow(x / a, r));
202 };
203 }
204 case ProcessType::LOGNORMAL: { // params (mu, sigma) of the log
205 if (p.size() < 2) break;
206 const double mu = p[0], sg = p[1];
207 return [mu, sg](double x) {
208 if (!(x > 0.0)) return 0.0;
209 const double z = (std::log(x) - mu) / sg;
210 return std::exp(-0.5 * z * z) / (x * sg * std::sqrt(2.0 * 3.14159265358979323846));
211 };
212 }
213 case ProcessType::PARETO: { // params (shape alpha, scale k)
214 if (p.size() < 2) break;
215 const double al = p[0], k = p[1];
216 return [al, k](double x) {
217 if (!(x >= k) || !(x > 0.0)) return 0.0;
218 return al * std::pow(k, al) / std::pow(x, al + 1.0);
219 };
220 }
221 case ProcessType::UNIFORM: { // params (a, b)
222 if (p.size() < 2) break;
223 const double a = p[0], b = p[1];
224 return [a, b](double x) { return (x >= a && x <= b) ? 1.0 / (b - a) : 0.0; };
225 }
226 default:
227 break;
228 }
229 return std::function<double(double)>();
230}
231
232/**
233 * The declared law's complementary cdf, for the families whose tail the
234 * long-tail fit is stated for. Empty for every other law, which is what makes
235 * `phfit = Hyperexp` fall through to the two-moment surrogate there.
236 *
237 * Built on `dist_cdf`, which already carries the closed form of each of these
238 * four families -- the Gamma from the regularized incomplete gamma, the Pareto
239 * from 1 - (k/x)^alpha -- so there is no second copy of any of them here.
240 */
241template <class T>
242std::function<double(double)> nonmkv_ccdf(const lang::Distrib<T>& d) {
243 using lang::ProcessType;
244 if (d.type != ProcessType::GAMMA && d.type != ProcessType::WEIBULL &&
245 d.type != ProcessType::LOGNORMAL && d.type != ProcessType::PARETO)
246 return std::function<double(double)>();
247 const lang::Distrib<T> law = d;
248 return [law](double x) {
249 if (!(x > 0.0)) return 1.0;
250 return 1.0 - num_traits<T>::to_double(lang::dist_cdf(law, num_traits<T>::from_double(x)));
251 };
252}
253
254/**
255 * A hyperexponential fitted to the ccdf across decades, as its MAP pair and
256 * rescaled to the mean the struct carries.
257 *
258 * `ok` comes back false when the recursion declines the law: the components have
259 * to dominate one another at their own time scales, which a light-tailed law
260 * does not provide, and answering with a fit that does not hold is worse than
261 * falling through to the two-moment surrogate.
262 */
263template <class T>
264mam::Map<T> nonmkv_longtail(const std::function<double(double)>& ccdf, double mean, bool& ok) {
265 ok = false;
266 mam::Map<T> out;
267 // The fit itself runs in double: it bisects for quantiles and takes
268 // logarithms of the ccdf, which is a `double(double)` here whatever T is.
269 mam::HyperexpLongtailResult<double> fit;
270 try {
272 } catch (const std::exception&) {
273 return out;
274 }
275 const std::size_t n = fit.p.size();
276 if (n == 0) return out;
277 Matrix<T> D0(n, n, num_traits<T>::from_int(0));
278 Matrix<T> D1(n, n, num_traits<T>::from_int(0));
279 for (std::size_t i = 0; i < n; ++i) {
280 if (!std::isfinite(fit.lambda[i]) || fit.lambda[i] <= 0.0) return out;
281 D0(i, i) = num_traits<T>::from_double(-fit.lambda[i]);
282 for (std::size_t j = 0; j < n; ++j)
283 D1(i, j) = num_traits<T>::from_double(fit.lambda[i] * fit.p[j]);
284 }
285 mam::Map<T> m;
286 m.D0 = D0;
287 m.D1 = D1;
288 out = mam::map_scale(m, num_traits<T>::from_double(mean));
289 ok = true;
290 return out;
291}
292
293/**
294 * The moment-only surrogate: a concentrated ME under phfit = Cme, an Erlang of
295 * the full budget otherwise. Port of the reference's fitConcentratedSurrogate.
296 */
297template <class T>
298mam::Map<T> nonmkv_concentrated(double mean, double scv, std::size_t order, PhFit phfit) {
299 if (phfit == PhFit::Cme) {
300 // A Det has SCV 0, which no ME attains; the budget-limited branch of the
301 // fitter then returns the most concentrated member that fits.
302 const double s = scv > 1e-12 ? scv : 1e-12;
303 if (s < 1.0) return mam::dist_fit_me<T>(mean, s < 1.0 - 1e-12 ? s : 1.0 - 1e-12, order);
304 }
305 return mam::map_erlang(num_traits<T>::from_double(mean), static_cast<unsigned>(order));
306}
307
308/** Install a fitted (D0,D1) as the law of a station-class pair or a firing mode. */
309template <class T>
310void nonmkv_install(lang::Distrib<T>& d, const mam::Map<T>& m) {
311 std::vector<Matrix<T>> blocks;
312 blocks.push_back(m.D0);
313 blocks.push_back(m.D1);
314 const bool isph = sn_is_phasetype(blocks, std::vector<T>());
315 // The declared law is kept alongside the fit. Everything downstream reads
316 // the surrogate, as it did; MMAP[K]/G[K]/1 evaluates the ORIGINAL transform
317 // and would otherwise find only the fit it exists to avoid.
318 std::shared_ptr<lang::Distrib<T>> declared(new lang::Distrib<T>(d));
319 declared->declared.reset();
320 // A concentrated-ME surrogate is NOT a phase-type, so it is tagged ME: the
321 // CTMC then assembles a rational generator and the PH-only consumers refuse
322 // it. sn_is_phasetype is the single test, exactly as in the reference.
323 d = lang::Distrib<T>::map_dist(m.D0, m.D1,
326 d.declared = declared;
327}
328
329/**
330 * True when mode `m`'s firing law is ALREADY a usable Markovian one and needs
331 * no fit.
332 *
333 * A POSITIVE PHASE COUNT IS NOT ENOUGH, and reading it as such is what left
334 * `spn_pareto_service` absorbing. `network_reader` records
335 * `firingphases[m] = dist_to_map(proc).order()` at read time, so a Pareto mode
336 * arrives with a phase count of the fit that WOULD be made while `firingproc[m]`
337 * is still the Pareto, whose (D0,D1) are empty. The old guard read that count,
338 * skipped the conversion, and `after_global_event` then found no firing matrix:
339 * the chain reached the enabled state and stopped there, filling the place to
340 * the cutoff and reporting Tput 0 with QLen equal to the cutoff at every cutoff.
341 * The matrices themselves are the test.
342 */
343template <class T>
344bool nonmkv_firing_ready(const qn::TransitionParam<T>& np, std::size_t m) {
345 if (m >= np.firingphases.size() || np.firingphases[m] <= 0) return false;
346 if (m >= np.firingproc.size()) return false;
347 return np.firingproc[m].D0.rows() == static_cast<std::size_t>(np.firingphases[m]);
348}
349
350} // namespace detail
351
352/**
353 * Whether any law in the struct would be replaced, so a caller can skip copying
354 * the struct when there is nothing to convert.
355 *
356 * @param sn the struct to inspect
357 * @param preserve_det leave Det alone, as the MAM analyzer does
358 */
359template <class T>
360bool sn_has_nonmarkov(const qn::NetworkStruct<T>& sn, bool preserve_det = false) {
361 using lang::ProcessType;
362 for (std::size_t ist = 0; ist < sn.nstations; ++ist)
363 for (std::size_t r = 0; r < sn.nclasses; ++r) {
364 if (ist < sn.disabled.size() && r < sn.disabled[ist].size() && sn.disabled[ist][r])
365 continue;
366 const lang::Distrib<T>& d = sn.service[ist][r];
367 if (d.disabled || d.is_prior()) continue;
368 if (detail::nonmkv_is_markovian(d.type) || detail::nonmkv_is_scheduled(d.type))
369 continue;
370 if (d.type == ProcessType::DET && preserve_det) continue;
371 return true;
372 }
373 for (typename std::map<std::size_t, qn::TransitionParam<T>>::const_iterator it =
374 sn.transparam.begin();
375 it != sn.transparam.end(); ++it) {
376 const qn::TransitionParam<T>& np = it->second;
377 for (std::size_t m = 0; m < np.firingproc.size(); ++m) {
378 if (detail::nonmkv_firing_ready(np, m)) continue;
379 const lang::Distrib<T>& d = np.firingproc[m];
380 if (d.disabled || d.is_prior()) continue;
381 if (detail::nonmkv_is_markovian(d.type) || detail::nonmkv_is_scheduled(d.type))
382 continue;
383 if (d.type == ProcessType::DET && preserve_det) continue;
384 return true;
385 }
386 }
387 return false;
388}
389
390/**
391 * @brief Replace every non-Markovian service and firing law by a Markovian
392 * surrogate.
393 *
394 * @param sn the struct to convert IN PLACE; callers pass their own copy
395 * @param opts the conversion method, order and fit family
396 */
397template <class T>
400 "sn_nonmarkov_toph evaluates densities and fits moments");
401 using lang::ProcessType;
402 if (!opts.enabled) return;
403 if (opts.order == 0) throw InputError("sn_nonmarkov_toph: the phase budget must be positive");
404
405 for (std::size_t ist = 0; ist < sn.nstations; ++ist) {
406 for (std::size_t r = 0; r < sn.nclasses; ++r) {
407 if (ist < sn.disabled.size() && r < sn.disabled[ist].size() && sn.disabled[ist][r])
408 continue;
409 lang::Distrib<T>& d = sn.service[ist][r];
410 if (d.disabled || d.is_prior()) continue;
411 const ProcessType p = d.type;
412 if (detail::nonmkv_is_markovian(p) || detail::nonmkv_is_scheduled(p)) continue;
413 if (p == ProcessType::DET && opts.preserve_det) continue;
414
415 const double mean = num_traits<T>::to_double(d.mean);
416 const double scv = num_traits<T>::to_double(d.scv);
417 if (!std::isfinite(mean) || mean <= 0.0) continue;
418
419 if (p == ProcessType::DET) {
420 detail::nonmkv_install(
422 static_cast<unsigned>(opts.order)));
423 continue;
424 }
425
426 // The long-tail fit, when it was asked for and the law has a tail
427 // to fit. It matches the ccdf at points spread over decades rather
428 // than matching two moments, so it is the route for a Pareto, a
429 // Weibull with shape below one or a Lognormal with a large sigma.
430 if (opts.phfit == PhFit::Hyperexp) {
431 const std::function<double(double)> ccdf = detail::nonmkv_ccdf(d);
432 if (ccdf) {
433 bool ok = false;
434 const mam::Map<T> lt = detail::nonmkv_longtail<T>(ccdf, mean, ok);
435 if (ok) {
436 detail::nonmkv_install(d, lt);
437 continue;
438 }
439 }
440 }
441
442 const std::function<double(double)> pdf = detail::nonmkv_density(d);
443 if (!pdf) { // no density to fit by shape: moments only
444 detail::nonmkv_install(d,
445 detail::nonmkv_concentrated<T>(mean, scv, opts.order,
446 opts.phfit));
447 continue;
448 }
449 if (opts.phfit == PhFit::Cme && scv >= 0.0 && scv < 1.0) {
450 detail::nonmkv_install(d,
451 detail::nonmkv_concentrated<T>(mean, scv, opts.order,
452 opts.phfit));
453 continue;
454 }
455 const mam::Map<T> fit =
456 mam::map_scale(mam::map_bernstein<T>(pdf, static_cast<unsigned>(opts.order)),
458 detail::nonmkv_install(d, fit);
459 }
460 }
461
462 // ---- SPN transition firing laws -------------------------------------
463 for (std::size_t ind = 0; ind < sn.nodes.size(); ++ind) {
464 if (sn.nodes[ind].nodetype != qn::NodeType::Transition) continue;
465 typename std::map<std::size_t, qn::TransitionParam<T>>::iterator it =
466 sn.transparam.find(ind + 1);
467 if (it == sn.transparam.end()) continue;
468 qn::TransitionParam<T>* np = &it->second;
469 for (std::size_t m = 0; m < np->firingproc.size(); ++m) {
470 // refreshPetriNetNodes already gives a Markovian mode a valid
471 // (D0,D1) and a positive phase count; those are left alone. The
472 // MATRICES decide, not the count -- see `nonmkv_firing_ready`.
473 if (detail::nonmkv_firing_ready(*np, m)) continue;
474 lang::Distrib<T>& d = np->firingproc[m];
475 if (d.disabled || d.is_prior()) continue;
476 const ProcessType p = d.type;
477 if (detail::nonmkv_is_markovian(p) || detail::nonmkv_is_scheduled(p)) continue;
478 if (p == ProcessType::DET && opts.preserve_det) continue;
479
480 const double mean = num_traits<T>::to_double(d.mean);
481 mam::Map<T> fit;
482 if (p == ProcessType::DET) {
483 // the reference: map_erlang(targetMean_t, nPhases), no surrogate
484 const double mu = (std::isfinite(mean) && mean > 0.0) ? mean : 1.0;
486 static_cast<unsigned>(opts.order));
487 } else {
488 // The reference's switch knows five families (Gamma, Weibull,
489 // Lognormal, Pareto, Uniform) and `continue`s on any other, and
490 // for those five ALWAYS fits by shape with map_bernstein: the
491 // cme / concentrated and the long-tail branches of the station
492 // loop do not exist for a firing law.
493 const std::function<double(double)> pdf = detail::nonmkv_density(d);
494 if (!pdf) continue;
495 if (!std::isfinite(mean) || mean <= 0.0) {
496 // A firing law with no usable mean (a Pareto with alpha <= 1)
497 // falls back to the unit-mean Erlang, as the reference does
498 // rather than refusing the model.
500 static_cast<unsigned>(opts.order));
501 } else {
502 try {
503 fit = mam::map_scale(
504 mam::map_bernstein<T>(pdf, static_cast<unsigned>(opts.order)),
506 } catch (const std::exception&) {
508 static_cast<unsigned>(opts.order));
509 }
510 }
511 }
512 detail::nonmkv_install(d, fit);
513 if (m < np->firingphases.size()) np->firingphases[m] = fit.order();
514 }
515 }
516}
517
518} // namespace api
519} // namespace line
520
521#endif // LINE_API_SN_SN_NONMARKOV_TOPH_H
InputError(const std::string &what)
Definition error.h:105
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...
bool nonmkv_enabled(const std::string &method)
Resolve options.config.nonmkv to NonmarkovOptions::enabled.
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:490
void dist_refresh_moments(Distrib< T > &d)
Fill in the first two moments of a distribution given by its matrices.
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), const T &c1=num_traits< T >::from_int(0))
The fit with the component count chosen automatically: one per decade between the 0....
Map< T > map_bernstein(const std::function< double(double)> &f, unsigned order=20)
Acyclic phase-type approximation of an arbitrary density by Bernstein exponentials.
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.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
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.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
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:888
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 Transition node: MATLAB sn.nodeparam{ind} for an SPN transition,...
std::vector< lang::Distrib< T > > firingproc
firing distribution per mode
std::vector< std::size_t > firingphases
phase count per mode, 0 when non-Markovian