LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
api_dispatch.cpp
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 *
5 * The --api dispatch table.
6 *
7 * One entry per exposed function, hand written: it pulls its named arguments
8 * out of the parsed JSON and calls the templated function at the arithmetic
9 * the caller selected. Registry-generated dispatch is the eventual goal, but a
10 * generated table cannot know that pfqn_conv takes callables it must refuse or
11 * that pfqn_comom's Z is a vector where pfqn_comomrm's is a matrix, so the
12 * table is explicit while the argument shapes are being pinned down.
13 *
14 * Compiled once into a static library rather than instantiated per translation
15 * unit: three arithmetics times a dozen algorithms is a lot of template
16 * instantiation to repeat in the CLI, the tests and a future binding.
17 */
18
20
21#include <algorithm>
22#include <cctype>
23#include <cmath>
24#include <functional>
25#include <map>
26#include <sstream>
27
59#include "line/api/fj/fj_rmax.h"
182
183namespace line {
184namespace reg {
185
186// Arithmetic selection
187
188std::string ArithSpec::str() const {
189 switch (mode) {
190 case Arith::Double: return "double";
191 case Arith::Exact: return "exact";
192 case Arith::Real: return "real:" + std::to_string(digits);
193 }
194 return "?";
195}
196
197ArithSpec parse_arith(const std::string& text) {
198 ArithSpec s;
199 if (text == "double") {
201 return s;
202 }
203 if (text == "exact") {
204 s.mode = Arith::Exact;
205 return s;
206 }
207 if (text.compare(0, 5, "real:") == 0 || text == "real") {
208 unsigned want = 50;
209 if (text != "real") {
210 const std::string d = text.substr(5);
211 if (d.empty() || d.find_first_not_of("0123456789") != std::string::npos)
212 throw InputError("--arith real:<digits> needs a decimal digit count, got '" + d +
213 "'");
214 want = static_cast<unsigned>(std::strtoul(d.c_str(), nullptr, 10));
215 if (want == 0) throw InputError("--arith real:<digits> needs at least one digit");
216 }
217 s.mode = Arith::Real;
218 // Round UP to an instantiated tier: never give less precision than asked.
219 if (want <= 50)
220 s.digits = 50;
221 else if (want <= 100)
222 s.digits = 100;
223 else if (want <= 200)
224 s.digits = 200;
225 else
226 throw UnsupportedError(ErrorKind{"Arithmetic"},
227 "--arith real:" + std::to_string(want) +
228 " exceeds the precision tiers this build instantiates (50, 100, 200 digits); "
229 "a wider tier has to be compiled in, it cannot be selected at run time.");
230 return s;
231 }
232 throw InputError("unknown --arith '" + text +
233 "'; accepted forms are: double, exact, real:<digits> "
234 "(digits rounded up to 50, 100 or 200)");
235}
236
237// ---------------------------------------------------------------------------
238// The table
239// ---------------------------------------------------------------------------
240
241namespace {
242
243/**
244 * Run an operation at the selected arithmetic. Op is a struct with a static
245 * member template run<T>(Args&) returning the results object; the arithmetic
246 * gate against the registry has already passed when this is reached, so every
247 * branch here is a mode the function genuinely supports.
248 */
249template <class Op>
250Json run_at(const ArithSpec& s, Args& a) {
251 switch (s.mode) {
252 case Arith::Double: return Op::template run<double>(a);
253 case Arith::Exact: return Op::template run<Rational>(a);
254 case Arith::Real:
255 if (s.digits <= 50) return Op::template run<Real50>(a);
256 if (s.digits <= 100) return Op::template run<Real100>(a);
257 return Op::template run<Real200>(a);
258 }
259 throw InputError("unreachable arithmetic selection");
260}
261
262/**
263 * Wrap an Op whose registry entry lists Double and Real but NOT Exact.
264 *
265 * `run_at` instantiates all three backends because the mode is a runtime value,
266 * so an Op that calls exp, log or sqrt would not COMPILE at Rational even
267 * though it can never be REACHED there -- `api_invoke` refuses the mode against
268 * the registry before dispatching. The if-constexpr makes that same fact known
269 * to the compiler. The throw is the arm the gate has already excluded, kept as
270 * a real refusal rather than a placeholder so that a registry entry mistakenly
271 * widened to Exact reports the reason instead of returning a wrong number.
272 */
273template <class Op>
274struct NeedsTranscendental {
275 template <class T>
276 static Json run(Args& a) {
277 if constexpr (num_traits<T>::has_transcendental) {
278 return Op::template run<T>(a);
279 } else {
280 (void)a;
281 throw UnsupportedError(ErrorKind{"Arithmetic"},
282 "this function evaluates transcendental terms (exp, log, sqrt or a "
283 "matrix exponential), which the exact rational field does not contain; "
284 "rerun with --arith double or --arith real:<digits>.");
285 }
286 }
287};
288
289/** G and lG, the shape most of the normalizing-constant family returns. */
290template <class T>
291Json nc_results(const pfqn::NcResult<T>& r) {
292 Json out;
293 out["G"] = encode_scalar(r.G);
294 out["lG"] = r.lG;
295 return out;
296}
297
298struct OpPfqnCa {
299 template <class T>
300 static Json run(Args& a) {
301 const Matrix<T> L = a.matrix<T>("L");
302 const std::vector<int> N = a.ints("N");
303 const Matrix<T> Z = a.matrix_or_empty<T>("Z");
304 a.done();
305 return nc_results(pfqn::pfqn_ca(L, N, Z));
306 }
307};
308
309struct OpPfqnConv {
310 template <class T>
311 static Json run(Args& a) {
312 const Matrix<T> L = a.matrix<T>("L");
313 const std::vector<int> N = a.ints("N");
314 const Matrix<T> Z = a.matrix_or_empty<T>("Z");
315 a.unsupported("cdscaling",
316 "the class-dependence scaling argument is a vector of callables, which has "
317 "no JSON representation; call pfqn_conv from the library for that case");
318 a.unsupported("options", "solver options are not carried over the --api boundary");
319 a.done();
320 return nc_results(pfqn::pfqn_conv(L, N, Z));
321 }
322};
323
324struct OpPfqnRecal {
325 template <class T>
326 static Json run(Args& a) {
327 const Matrix<T> L = a.matrix<T>("L");
328 const std::vector<int> N = a.ints("N");
329 const Matrix<T> Z = a.matrix_or_empty<T>("Z");
330 const std::vector<int> m0 = a.ints_or_empty("m0");
331 a.done();
332 return nc_results(pfqn::pfqn_recal(L, N, Z, m0));
333 }
334};
335
336struct OpPfqnGld {
337 template <class T>
338 static Json run(Args& a) {
339 const Matrix<T> L = a.matrix<T>("L");
340 const std::vector<int> N = a.ints("N");
341 const Matrix<T> mu = a.matrix<T>("mu");
342 a.unsupported("options", "solver options are not carried over the --api boundary");
343 a.done();
344 return nc_results(pfqn::pfqn_gld(L, N, mu));
345 }
346};
347
348struct OpPfqnMva {
349 template <class T>
350 static Json run(Args& a) {
351 const Matrix<T> L = a.matrix<T>("L");
352 const std::vector<int> N = a.ints("N");
353 const Matrix<T> Z = a.matrix_or_empty<T>("Z");
354 const std::vector<int> mi = a.ints_or_empty("mi");
355 a.done();
356 const pfqn::MvaResult<T> r = pfqn::pfqn_mva(L, N, Z, mi);
357 Json out;
358 out["XN"] = encode_vector(r.XN);
359 out["QN"] = encode_matrix(r.QN);
360 out["UN"] = encode_matrix(r.UN);
361 out["CN"] = encode_matrix(r.CN);
362 out["G"] = encode_scalar(r.G);
363 out["lG"] = r.lG;
364 return out;
365 }
366};
367
368/** G, lG and the CoMoM basis. */
369template <class T>
370Json comom_results(const pfqn::ComomResult<T>& r) {
371 Json out;
372 out["G"] = encode_scalar(r.G);
373 out["lG"] = r.lG;
374 out["basis"] = encode_vector(r.basis);
375 return out;
376}
377
378struct OpPfqnComom {
379 template <class T>
380 static Json run(Args& a) {
381 const Matrix<T> L = a.matrix<T>("L");
382 const std::vector<int> N = a.ints("N");
383 const std::vector<T> Z = a.vector_or_empty<T>("Z");
384 const T atol = a.scalar<T>("atol", num_traits<T>::from_int(0));
385 a.done();
386 return comom_results(pfqn::pfqn_comom(L, N, Z, atol));
387 }
388};
389
390struct OpPfqnComomrm {
391 template <class T>
392 static Json run(Args& a) {
393 const Matrix<T> L = a.matrix<T>("L");
394 const std::vector<int> N = a.ints("N");
395 const Matrix<T> Z = a.matrix_or_empty<T>("Z");
396 const int m = a.integer("m", 1);
397 a.done();
398 return comom_results(pfqn::pfqn_comomrm(L, N, Z, m));
399 }
400};
401
402struct OpPfqnComomrmOrig {
403 template <class T>
404 static Json run(Args& a) {
405 const Matrix<T> L = a.matrix<T>("L");
406 const std::vector<int> N = a.ints("N");
407 const Matrix<T> Z = a.matrix_or_empty<T>("Z");
408 const T atol = a.scalar<T>("atol", num_traits<T>::from_int(0));
409 a.done();
410 return comom_results(pfqn::pfqn_comomrm_orig(L, N, Z, atol));
411 }
412};
413
414struct OpPfqnComomrmMs {
415 template <class T>
416 static Json run(Args& a) {
417 const Matrix<T> L = a.matrix<T>("L");
418 const std::vector<int> N = a.ints("N");
419 const Matrix<T> Z = a.matrix_or_empty<T>("Z");
420 const int m = a.integer("m", 1);
421 const int S = a.integer("S", 1);
422 a.done();
423 const pfqn::ComomRmResult<T> r = pfqn::pfqn_comomrm_ms(L, N, Z, m, S);
424 Json out;
425 out["G"] = encode_scalar(r.G);
426 out["lG"] = r.lG;
427 out["prob"] = encode_vector(r.prob);
428 return out;
429 }
430};
431
432struct OpPfqnProcomom {
433 template <class T>
434 static Json run(Args& a) {
435 const Matrix<T> L = a.matrix<T>("L");
436 const std::vector<int> N = a.ints("N");
437 const std::vector<T> Z = a.vector_or_empty<T>("Z");
438 const T atol = a.scalar<T>("atol", num_traits<T>::from_int(0));
439 a.done();
440 const pfqn::ProcomomResult<T> r = pfqn::pfqn_procomom(L, N, Z, atol);
441 Json out;
442 out["Pr"] = encode_matrix(r.Pr);
443 out["Q"] = encode_vector(r.Q);
444 out["rankdef"] = r.rankdef;
445 return out;
446 }
447};
448
449struct OpCtmcSolve {
450 template <class T>
451 static Json run(Args& a) {
452 const Matrix<T> Q = a.matrix<T>("Q");
453 a.unsupported("options", "solver options are not carried over the --api boundary");
454 a.done();
455 Json out;
456 out["p"] = encode_vector(mc::ctmc_solve(Q));
457 return out;
458 }
459};
460
461struct OpDtmcSolve {
462 template <class T>
463 static Json run(Args& a) {
464 const Matrix<T> P = a.matrix<T>("P");
465 a.unsupported("options", "solver options are not carried over the --api boundary");
466 a.done();
467 Json out;
468 out["PROB"] = encode_vector(mc::dtmc_solve(P));
469 return out;
470 }
471};
472
473struct OpCtmcMakeinfgen {
474 template <class T>
475 static Json run(Args& a) {
476 const Matrix<T> Q = a.matrix<T>("Q");
477 a.done();
478 Json out;
479 out["Q"] = encode_matrix(mc::ctmc_makeinfgen(Q));
480 return out;
481 }
482};
483
484// ---------------------------------------------------------------------------
485// api/sim, the simulation output analysis family
486// ---------------------------------------------------------------------------
487
488/*
489 * EXACT ARITHMETIC IS REFUSED BY EVERY ENTRY BELOW, and the refusal has to be a
490 * runtime branch even though the registry already gates it: `run_at` names all
491 * three arithmetics in one switch, so the Rational instantiation is COMPILED
492 * whether or not it can be reached, and the family's `static_assert` would fire
493 * at build time. The `if constexpr` guard is what keeps the translation unit
494 * compilable, exactly as the mdd path in the CLI does.
495 *
496 * There is no `--api sym_*` counterpart. `api/sym` is a backend, not a family of
497 * numeric functions: it has no MATLAB `sym_*` entry points to mirror, and what
498 * it serves reaches the user through the solvers that call it (`--symbolic` on
499 * fluid, the CTMC symbolic getters). Exposing a REST client as an `--api`
500 * function would invent a signature that exists in no other codebase.
501 */
502
503/** Option overrides and whether the caller gave any, which FIRQUEST reads. */
504struct QuestOptionsRead {
505 sim::QuestOptions opt;
506 bool supplied = false;
507};
508
509/** The QUEST option overrides, read from the same keys MATLAB's struct uses. */
510QuestOptionsRead read_quest_options(Args& a) {
511 QuestOptionsRead out;
512 const char* keys[] = {"b0", "m0", "s", "beta", "eta", "theta", "weight", "force"};
513 for (std::size_t k = 0; k < sizeof(keys) / sizeof(keys[0]); ++k)
514 if (a.has(keys[k])) out.supplied = true;
515 out.opt.b0 = static_cast<long>(a.integer("b0", static_cast<int>(out.opt.b0)));
516 out.opt.m0 = static_cast<long>(a.integer("m0", static_cast<int>(out.opt.m0)));
517 const std::vector<int> s = a.ints_or_empty("s");
518 if (!s.empty()) {
519 out.opt.s.clear();
520 for (std::size_t i = 0; i < s.size(); ++i) out.opt.s.push_back(static_cast<long>(s[i]));
521 }
522 out.opt.beta = a.scalar<double>("beta", out.opt.beta);
523 out.opt.eta = a.scalar<double>("eta", out.opt.eta);
524 out.opt.theta = a.scalar<double>("theta", out.opt.theta);
525 out.opt.weight = a.scalar<double>("weight", out.opt.weight);
526 out.opt.force = a.integer("force", out.opt.force ? 1 : 0) != 0;
527 return out;
528}
529
530/** The shared shape of `sim_fquest` and `sim_firquest`. */
531template <class T>
532Json quest_results(const sim::QuestResult<T>& r) {
533 Json out;
534 out["estimate"] = encode_scalar(r.estimate);
535 out["lower"] = encode_scalar(r.lower);
536 out["upper"] = encode_scalar(r.upper);
537 out["halfwidth"] = encode_scalar(r.halfwidth);
538 out["b"] = static_cast<long long>(r.b);
539 out["m"] = static_cast<long long>(r.m);
540 out["n"] = static_cast<long long>(r.n);
541 out["R"] = static_cast<long long>(r.R);
542 out["truncated"] = static_cast<long long>(r.truncated);
543 out["Ap"] = encode_scalar(r.Ap);
544 out["Np"] = encode_scalar(r.Np);
545 out["Vp"] = encode_scalar(r.Vp);
546 out["heuristic"] = r.heuristic;
547 out["warnings"] = r.warnings;
548 return out;
549}
550
551/** The message every exact-arithmetic refusal in this family shares. */
552std::string sim_exact_refusal(const char* fn, const char* why) {
553 return std::string(fn) + ": " + why +
554 ", so exact rational arithmetic has nothing to preserve and is refused; rerun with "
555 "--arith double or --arith real";
556}
557
558struct OpSimVonneumann {
559 template <class T>
560 static Json run(Args& a) {
561 if constexpr (!num_traits<T>::has_transcendental) {
562 throw UnsupportedError(ErrorKind{"Arithmetic"},
563 sim_exact_refusal("sim_vonneumann", "the p-value is a normal tail"));
564 } else {
565 const std::vector<T> x = vector_from_json<T>(a.get("x"), "sim_vonneumann: x");
566 const double alpha = a.scalar<double>("alpha", 0.05);
567 a.done();
568 const sim::VonNeumannResult<T> r = sim::sim_vonneumann(x, alpha);
569 Json out;
570 out["ratio"] = encode_scalar(r.ratio);
571 out["zscore"] = r.zscore;
572 out["pvalue"] = r.pvalue;
573 out["reject"] = r.reject;
574 out["nobs"] = static_cast<long long>(r.nobs);
575 return out;
576 }
577 }
578};
579
580struct OpSimShapirowilk {
581 template <class T>
582 static Json run(Args& a) {
583 if constexpr (!num_traits<T>::has_transcendental) {
584 throw UnsupportedError(ErrorKind{"Arithmetic"}, sim_exact_refusal(
585 "sim_shapirowilk", "the weights and the p-value are normal-order statistics"));
586 } else {
587 const std::vector<T> x = vector_from_json<T>(a.get("x"), "sim_shapirowilk: x");
588 const double alpha = a.scalar<double>("alpha", 0.05);
589 a.done();
590 const sim::ShapiroWilkResult<T> r = sim::sim_shapirowilk(x, alpha);
591 Json out;
592 out["W"] = encode_scalar(r.W);
593 out["pvalue"] = r.pvalue;
594 out["zscore"] = r.zscore;
595 out["reject"] = r.reject;
596 out["nobs"] = static_cast<long long>(r.nobs);
597 return out;
598 }
599 }
600};
601
602struct OpSimStsQuantileAreas {
603 template <class T>
604 static Json run(Args& a) {
605 if constexpr (!num_traits<T>::has_transcendental) {
606 throw UnsupportedError(ErrorKind{"Arithmetic"}, sim_exact_refusal(
607 "sim_sts_quantile_areas", "the areas carry the irrational weight sqrt(12)"));
608 } else {
609 const std::vector<T> Y =
610 vector_from_json<T>(a.get("Y"), "sim_sts_quantile_areas: Y");
611 const int b = a.integer("b", 0);
612 const int m = a.integer("m", 0);
613 const double p = a.scalar<double>("p", 0.5);
614 const double weight = a.scalar<double>("weight", std::sqrt(12.0));
615 a.done();
616 if (b <= 0 || m <= 0)
617 throw InputError(
618 "sim_sts_quantile_areas: the batch count b and batch size m are required and "
619 "must be positive integers");
620 const sim::StsQuantileStats<T> r = sim::sim_sts_quantile_areas(
621 Y, static_cast<std::size_t>(b), static_cast<std::size_t>(m), p, weight);
622 Json out;
623 out["areas"] = encode_vector(r.areas);
624 out["bqe"] = encode_vector(r.bqe);
625 out["quantile"] = encode_scalar(r.quantile);
626 out["Ap"] = encode_scalar(r.Ap);
627 out["Np"] = encode_scalar(r.Np);
628 out["Vp"] = encode_scalar(r.Vp);
629 out["b"] = static_cast<long long>(r.b);
630 out["m"] = static_cast<long long>(r.m);
631 out["n"] = static_cast<long long>(r.n);
632 return out;
633 }
634 }
635};
636
637struct OpSimFquest {
638 template <class T>
639 static Json run(Args& a) {
640 if constexpr (!num_traits<T>::has_transcendental) {
641 throw UnsupportedError(ErrorKind{"Arithmetic"}, sim_exact_refusal(
642 "sim_fquest", "the interval is a t quantile times a square root"));
643 } else {
644 const std::vector<T> Y = vector_from_json<T>(a.get("Y"), "sim_fquest: Y");
645 const double p = a.scalar<double>("p", 0.5);
646 const double alpha = a.scalar<double>("alpha", 0.05);
647 const QuestOptionsRead o = read_quest_options(a);
648 a.done();
649 return quest_results(sim::sim_fquest(Y, p, alpha, o.opt));
650 }
651 }
652};
653
654struct OpSimFirquest {
655 template <class T>
656 static Json run(Args& a) {
657 if constexpr (!num_traits<T>::has_transcendental) {
658 throw UnsupportedError(ErrorKind{"Arithmetic"}, sim_exact_refusal(
659 "sim_firquest", "the interval is a t quantile times a square root"));
660 } else {
661 // MATLAB passes an (n x R) matrix, one column per replication; the
662 // C++ signature is replication-major, so the columns are transposed
663 // here rather than reinterpreted -- pooling order is what the
664 // procedure's stage tests act on.
665 const Matrix<T> Ym = a.matrix<T>("Y");
666 const double p = a.scalar<double>("p", 0.5);
667 const double alpha = a.scalar<double>("alpha", 0.05);
668 const QuestOptionsRead o = read_quest_options(a);
669 a.done();
670 std::vector<std::vector<T> > Y(Ym.cols(), std::vector<T>(Ym.rows()));
671 for (std::size_t i = 0; i < Ym.rows(); ++i)
672 for (std::size_t r = 0; r < Ym.cols(); ++r) Y[r][i] = Ym(i, r);
673 // A missing option set is not the same as the FQUEST defaults here:
674 // FIRQUEST's own defaults depend on R, and nullptr is how they are
675 // asked for.
676 return quest_results(
677 sim::sim_firquest(Y, p, alpha, o.supplied ? &o.opt : nullptr));
678 }
679 }
680};
681
682// ---------------------------------------------------------------------------
683// qsys: the closed-form queueing systems
684// ---------------------------------------------------------------------------
685//
686// The most mechanical part of the surface -- scalar rates and coefficients of
687// variation in, a named result struct out -- so it is exposed wholesale rather
688// than one function at a time. What is NOT exposed is exactly the set whose
689// MATLAB signature takes a FUNCTION: qsys_gig1_rq takes the interarrival law as
690// a handle, qsys_mg1k_loss a density, qsys_mapg1k / qsys_mapg1k_perflow /
691// qsys_mmapg1k a service law and qsys_ldps_workload a workload law. A callable
692// has no JSON representation, so those six report that from api_invoke's
693// not-exposed arm rather than being given an argument shape that silently
694// stands in for the caller's function.
695//
696// AN ABSENT OPTIONAL ARGUMENT CALLS THE PORT'S OWN SHORT OVERLOAD, never a
697// default written out here. Several of these functions carry a tuned default
698// (qsys_mapd1's 4096 arrival truncation, qsys_phmc's 50000 iterations,
699// qsys_phm1's 1e-16) and a plausible-looking value restated at this boundary
700// would answer a different question from the library and the reference.
701//
702// The KEYS ARE THE MATLAB PARAMETER NAMES, which is why qsys_mapmap1 reads its
703// ARRIVAL process from (C0, C1) and its SERVICE process from (D0, D1): that is
704// the order matlab/src/api/qsys/qsys_mapmap1.m declares, and the opposite
705// reading silently swaps the two processes of a stable queue for an unstable
706// one. The returned keys are the port's struct field names.
707
708/** [W, rhohat], what most of the family returns. */
709template <class T>
710Json qsys_results(const qsys::QsysResult<T>& r) {
711 Json out;
712 out["W"] = encode_scalar(r.W);
713 // MATLAB spells this `rho` in qsys_mm1/qsys_mmk and `rhohat` everywhere
714 // else; it is one quantity, the modified utilization, and it is reported
715 // under the port's single name so a host does not key on the file it came
716 // from.
717 out["rhohat"] = encode_scalar(r.rhohat);
718 return out;
719}
720
721#define LINE_QSYS_GIG1(OpName, fn) \
722 struct OpName { \
723 template <class T> \
724 static Json run(Args& a) { \
725 const T lambda = a.number<T>("lambda"); \
726 const T mu = a.number<T>("mu"); \
727 const T ca = a.number<T>("ca"); \
728 const T cs = a.number<T>("cs"); \
729 a.done(); \
730 return qsys_results(qsys::fn(lambda, mu, ca, cs)); \
731 } \
732 }
733
734LINE_QSYS_GIG1(OpQsysGig1AllenCunneen, qsys_gig1_approx_allencunneen);
735LINE_QSYS_GIG1(OpQsysGig1Gelenbe, qsys_gig1_approx_gelenbe);
736LINE_QSYS_GIG1(OpQsysGig1Heyman, qsys_gig1_approx_heyman);
737LINE_QSYS_GIG1(OpQsysGig1Kimura, qsys_gig1_approx_kimura);
738LINE_QSYS_GIG1(OpQsysGig1Klb, qsys_gig1_approx_klb);
739LINE_QSYS_GIG1(OpQsysGig1Kobayashi, qsys_gig1_approx_kobayashi);
740LINE_QSYS_GIG1(OpQsysGig1Marchal, qsys_gig1_approx_marchal);
741LINE_QSYS_GIG1(OpQsysGig1Lbnd, qsys_gig1_lbnd);
742LINE_QSYS_GIG1(OpQsysGig1UbndKingman, qsys_gig1_ubnd_kingman);
743#undef LINE_QSYS_GIG1
744
745#define LINE_QSYS_GIGK(OpName, fn) \
746 struct OpName { \
747 template <class T> \
748 static Json run(Args& a) { \
749 const T lambda = a.number<T>("lambda"); \
750 const T mu = a.number<T>("mu"); \
751 const T ca = a.number<T>("ca"); \
752 const T cs = a.number<T>("cs"); \
753 const unsigned k = a.uinteger("k"); \
754 a.done(); \
755 return qsys_results(qsys::fn(lambda, mu, ca, cs, k)); \
756 } \
757 }
758
759LINE_QSYS_GIGK(OpQsysGigkApprox, qsys_gigk_approx);
760LINE_QSYS_GIGK(OpQsysGigkCosmetatos, qsys_gigk_approx_cosmetatos);
761LINE_QSYS_GIGK(OpQsysGigkKingman, qsys_gigk_approx_kingman);
762LINE_QSYS_GIGK(OpQsysGigkWhitt, qsys_gigk_approx_whitt);
763#undef LINE_QSYS_GIGK
764
765// The abandonment and QED families. Only the entry points whose arguments are
766// JSON-expressible are exposed here: qsys_mgisrgi_whitt with a general patience
767// law, qsys_ggisgi_fluid and qsys_mtginf all take CALLABLES (a hazard, a ccdf,
768// an arrival rate), which no argument object can carry, so they stay
769// library-only. qsys_erlanga is the exponential-patience case of the first, and
770// its whole model is five numbers.
771struct OpQsysErlangA {
772 template <class T>
773 static Json run(Args& a) {
774 const T lambda = a.number<T>("lambda");
775 const T mu = a.number<T>("mu");
776 const T theta = a.number<T>("theta");
777 const unsigned s = a.uinteger("s");
778 // JSON has no infinity, so an absent r is the unbounded waiting room.
779 const double r = a.has("r") ? num_traits<double>::to_double(a.number<double>("r"))
780 : std::numeric_limits<double>::infinity();
781 qsys::MgisrgiOptions opts;
782 opts.wPoints = a.vector_or_empty<double>("wPoints");
783 opts.maxQueue = a.count("maxQueue", qsys::MgisrgiOptions().maxQueue);
784 a.done();
785 const qsys::QsysAbandonResult<T> res = qsys::qsys_erlanga(lambda, mu, theta, s, r, opts);
786 Json out;
787 out["queueLengthDist"] = encode_vector(res.queueLengthDist);
788 out["probLoss"] = encode_scalar(res.probLoss);
789 out["probNoWait"] = encode_scalar(res.probNoWait);
790 out["probServed"] = encode_scalar(res.probServed);
791 out["probAbandon"] = encode_scalar(res.probAbandon);
792 out["meanNumber"] = encode_scalar(res.meanNumber);
793 out["varNumber"] = encode_scalar(res.varNumber);
794 out["meanQueueLength"] = encode_scalar(res.meanQueueLength);
795 out["varQueueLength"] = encode_scalar(res.varQueueLength);
796 out["utilization"] = encode_scalar(res.utilization);
797 out["throughput"] = encode_scalar(res.throughput);
798 out["abandonRate"] = encode_scalar(res.abandonRate);
799 out["meanWaitServed"] = encode_scalar(res.meanWaitServed);
800 out["varWaitServed"] = encode_scalar(res.varWaitServed);
801 out["meanWaitAbandon"] = encode_scalar(res.meanWaitAbandon);
802 out["varWaitAbandon"] = encode_scalar(res.varWaitAbandon);
803 out["meanWait"] = encode_scalar(res.meanWait);
804 out["secondMomentWait"] = encode_scalar(res.secondMomentWait);
805 out["numWaitingSpaces"] = static_cast<double>(res.numWaitingSpaces);
806 if (!res.waitPoints.empty()) {
807 out["waitPoints"] = encode_vector(res.waitPoints);
808 out["cdfWaitServed"] = encode_vector(res.cdfWaitServed);
809 out["cdfWaitAbandon"] = encode_vector(res.cdfWaitAbandon);
810 out["cdfWait"] = encode_vector(res.cdfWait);
811 }
812 return out;
813 }
814};
815
816struct OpQsysGgnmDiffusion {
817 template <class T>
818 static Json run(Args& a) {
819 const T lambda = a.number<T>("lambda");
820 const T mu = a.number<T>("mu");
821 const unsigned n = a.uinteger("n");
822 // JSON has no infinity, so an absent m is the unbounded waiting room.
823 const double m = a.has("m") ? a.number<double>("m")
824 : std::numeric_limits<double>::infinity();
825 const T ca = a.number<T>("ca");
826 const T cs = a.number<T>("cs");
827 a.done();
828 // The service ccdf is a callable, so the CLI can only offer the
829 // exponential default; the library entry point takes the general law.
830 const qsys::QsysGgnmResult<T> r = qsys::qsys_ggnm_diffusion<T>(lambda, mu, n, m, ca, cs);
831 Json out;
832 out["beta"] = encode_scalar(r.beta);
833 out["peakedness"] = encode_scalar(r.peakedness);
834 out["variability"] = encode_scalar(r.variability);
835 out["probDelay"] = encode_scalar(r.probDelay);
836 out["probBlock"] = encode_scalar(r.probBlock);
837 out["meanQueueLength"] = encode_scalar(r.meanQueueLength);
838 out["meanNumber"] = encode_scalar(r.meanNumber);
839 out["meanWait"] = encode_scalar(r.meanWait);
840 out["utilization"] = encode_scalar(r.utilization);
841 out["throughput"] = encode_scalar(r.throughput);
842 return out;
843 }
844};
845
846struct OpQsysGig1BndsExtremal {
847 template <class T>
848 static Json run(Args& a) {
849 const T lambda = a.number<T>("lambda");
850 const T mu = a.number<T>("mu");
851 const T ca = a.number<T>("ca");
852 const T cs = a.number<T>("cs");
853 const std::size_t K = a.count("K", 4000);
854 const std::size_t N = a.count("N", 2000);
855 const bool skipTight = a.boolean("skipTight", false);
856 a.done();
857 const qsys::Gig1ExtremalResult<T> r =
858 qsys::qsys_gig1_bnds_extremal(lambda, mu, ca, cs, K, N, skipTight);
859 Json out;
860 out["trafficIntensity"] = encode_scalar(r.trafficIntensity);
861 out["lowerBound"] = encode_scalar(r.lowerBound);
862 out["upperBound"] = encode_scalar(r.upperBound);
863 out["upperBoundClosed"] = encode_scalar(r.upperBoundClosed);
864 out["upperBoundDaley"] = encode_scalar(r.upperBoundDaley);
865 out["upperBoundKingman"] = encode_scalar(r.upperBoundKingman);
866 out["heavyTraffic"] = encode_scalar(r.heavyTraffic);
867 out["delta"] = encode_scalar(r.delta);
868 out["relativeWidth"] = encode_scalar(r.relativeWidth);
869 out["tightComputed"] = r.tightComputed;
870 return out;
871 }
872};
873
874struct OpQsysMmkQed {
875 template <class T>
876 static Json run(Args& a) {
877 const T lambda = a.number<T>("lambda");
878 const T mu = a.number<T>("mu");
879 const unsigned s = a.uinteger("s");
880 a.done();
881 const qsys::QsysQedResult<T> r = qsys::qsys_mmk_qed(lambda, mu, s);
882 Json out;
883 out["offeredLoad"] = encode_scalar(r.offeredLoad);
884 out["trafficIntensity"] = encode_scalar(r.trafficIntensity);
885 out["beta"] = encode_scalar(r.beta);
886 out["probDelay"] = encode_scalar(r.probDelay);
887 out["meanWaitDelayed"] = encode_scalar(r.meanWaitDelayed);
888 out["meanWait"] = encode_scalar(r.meanWait);
889 out["meanQueueLength"] = encode_scalar(r.meanQueueLength);
890 out["meanNumber"] = encode_scalar(r.meanNumber);
891 out["utilization"] = encode_scalar(r.utilization);
892 return out;
893 }
894};
895
896struct OpQsysMmkQedAlpha {
897 template <class T>
898 static Json run(Args& a) {
899 const T beta = a.number<T>("beta");
900 a.done();
901 Json out;
902 out["alpha"] = encode_scalar(qsys::qsys_mmk_qed_alpha(beta));
903 return out;
904 }
905};
906
907struct OpQsysMmkQedStaffing {
908 template <class T>
909 static Json run(Args& a) {
910 const T lambda = a.number<T>("lambda");
911 const T mu = a.number<T>("mu");
912 const std::string crit = a.text("criterion", "delay");
913 const T target = a.has("target") ? a.number<T>("target") : num_traits<T>::from_int(0);
914 const T deadline = a.has("deadline") ? a.number<T>("deadline") : num_traits<T>::from_int(0);
915 const T level = a.has("level") ? a.number<T>("level") : num_traits<T>::from_int(0);
916 const bool exact = a.boolean("exact", false);
917 a.done();
919 if (crit == "meanwait") {
921 } else if (crit == "servicelevel") {
923 } else if (crit != "delay") {
924 throw InputError("qsys_mmk_qed_staffing: unknown criterion '" + crit + "'");
925 }
926 const qsys::QsysQedStaffingResult<T> r =
927 qsys::qsys_mmk_qed_staffing(lambda, mu, target, c, deadline, level, exact);
928 Json out;
929 out["numServers"] = static_cast<double>(r.numServers);
930 out["beta"] = encode_scalar(r.beta);
931 out["betaTarget"] = encode_scalar(r.betaTarget);
932 out["offeredLoad"] = encode_scalar(r.offeredLoad);
933 out["probDelay"] = encode_scalar(r.probDelay);
934 out["meanWait"] = encode_scalar(r.meanWait);
936 out["serviceLevel"] = encode_scalar(r.serviceLevel);
937 return out;
938 }
939};
940
941// The RQT family takes uncertainty-set variability parameters rather than
942// coefficients of variation, and returns the worst case alongside the bound.
943struct OpQsysGigkRqt {
944 template <class T>
945 static Json run(Args& a) {
946 const T lambda = a.number<T>("lambda");
947 const T mu = a.number<T>("mu");
948 const T Gamma_a = a.number<T>("Gamma_a");
949 const T Gamma_s = a.number<T>("Gamma_s");
950 const unsigned k = a.uinteger("k");
951 const T alpha_a = a.number<T>("alpha_a");
952 const T alpha_s = a.number<T>("alpha_s");
953 a.done();
954 const qsys::GigkRqtResult<T> r =
955 qsys::qsys_gigk_rqt(lambda, mu, Gamma_a, Gamma_s, k, alpha_a, alpha_s);
956 Json out;
957 out["W"] = encode_scalar(r.W);
958 out["rhohat"] = encode_scalar(r.rhohat);
959 out["Sworst"] = encode_scalar(r.Sworst);
960 return out;
961 }
962};
963
964struct OpQsysGig1Rqt {
965 template <class T>
966 static Json run(Args& a) {
967 const T lambda = a.number<T>("lambda");
968 const T mu = a.number<T>("mu");
969 const T Gamma_a = a.number<T>("Gamma_a");
970 const T Gamma_s = a.number<T>("Gamma_s");
971 const T alpha_a = a.number<T>("alpha_a");
972 const T alpha_s = a.number<T>("alpha_s");
973 a.done();
974 const qsys::GigkRqtResult<T> r =
975 qsys::qsys_gig1_rqt(lambda, mu, Gamma_a, Gamma_s, alpha_a, alpha_s);
976 Json out;
977 out["W"] = encode_scalar(r.W);
978 out["rhohat"] = encode_scalar(r.rhohat);
979 out["Sworst"] = encode_scalar(r.Sworst);
980 return out;
981 }
982};
983
984struct OpQsysGigkRqtGamma {
985 template <class T>
986 static Json run(Args& a) {
987 const T rho = a.number<T>("rho");
988 const T mu = a.number<T>("mu");
989 const T Gamma_a = a.number<T>("Gamma_a");
990 const T sigma_s = a.number<T>("sigma_s");
991 const unsigned k = a.uinteger("k");
992 const T alpha_a = a.number<T>("alpha_a");
993 const std::string regime = a.text("regime", "independent");
994 a.done();
995 Json out;
996 out["Gamma_s"] =
997 encode_scalar(qsys::qsys_gigk_rqt_gamma(rho, mu, Gamma_a, sigma_s, k, alpha_a, regime));
998 return out;
999 }
1000};
1001
1002#define LINE_QSYS_MYSKJA(OpName, fn) \
1003 struct OpName { \
1004 template <class T> \
1005 static Json run(Args& a) { \
1006 const T lambda = a.number<T>("lambda"); \
1007 const T mu = a.number<T>("mu"); \
1008 const T ca = a.number<T>("ca"); \
1009 const T cs = a.number<T>("cs"); \
1010 const T q0 = a.number<T>("q0"); \
1011 const T qa = a.number<T>("qa"); \
1012 a.done(); \
1013 return qsys_results(qsys::fn(lambda, mu, ca, cs, q0, qa)); \
1014 } \
1015 }
1016
1017LINE_QSYS_MYSKJA(OpQsysGig1Myskja, qsys_gig1_approx_myskja);
1018LINE_QSYS_MYSKJA(OpQsysGig1Myskja2, qsys_gig1_approx_myskja2);
1019#undef LINE_QSYS_MYSKJA
1020
1021struct OpQsysMm1 {
1022 template <class T>
1023 static Json run(Args& a) {
1024 const T lambda = a.number<T>("lambda");
1025 const T mu = a.number<T>("mu");
1026 a.done();
1027 return qsys_results(qsys::qsys_mm1(lambda, mu));
1028 }
1029};
1030
1031struct OpQsysMmk {
1032 template <class T>
1033 static Json run(Args& a) {
1034 const T lambda = a.number<T>("lambda");
1035 const T mu = a.number<T>("mu");
1036 const unsigned k = a.uinteger("k");
1037 a.done();
1038 return qsys_results(qsys::qsys_mmk(lambda, mu, k));
1039 }
1040};
1041
1042struct OpQsysMg1 {
1043 template <class T>
1044 static Json run(Args& a) {
1045 const T lambda = a.number<T>("lambda");
1046 const T mu = a.number<T>("mu");
1047 const T cs = a.number<T>("cs");
1048 a.done();
1049 return qsys_results(qsys::qsys_mg1(lambda, mu, cs));
1050 }
1051};
1052
1053struct OpQsysGg1 {
1054 template <class T>
1055 static Json run(Args& a) {
1056 const T lambda = a.number<T>("lambda");
1057 const T mu = a.number<T>("mu");
1058 const T ca2 = a.number<T>("ca2");
1059 const T cs2 = a.number<T>("cs2");
1060 a.done();
1061 return qsys_results(qsys::qsys_gg1(lambda, mu, ca2, cs2));
1062 }
1063};
1064
1065/** The one member of the family returning a bare scalar, MATLAB's `W`. */
1066struct OpQsysGm1 {
1067 template <class T>
1068 static Json run(Args& a) {
1069 const T sigma = a.number<T>("sigma");
1070 const T mu = a.number<T>("mu");
1071 a.done();
1072 Json out;
1073 out["W"] = encode_scalar(qsys::qsys_gm1(sigma, mu));
1074 return out;
1075 }
1076};
1077
1078struct OpQsysMginf {
1079 template <class T>
1080 static Json run(Args& a) {
1081 const T lambda = a.number<T>("lambda");
1082 const T mu = a.number<T>("mu");
1083 const bool has_k = a.has("k");
1084 const unsigned k = has_k ? a.uinteger("k") : 0u;
1085 a.done();
1086 const qsys::MginfResult<T> r =
1087 has_k ? qsys::qsys_mginf(lambda, mu, k) : qsys::qsys_mginf(lambda, mu);
1088 Json out;
1089 out["L"] = encode_scalar(r.L);
1090 out["Lq"] = encode_scalar(r.Lq);
1091 out["W"] = encode_scalar(r.W);
1092 out["Wq"] = encode_scalar(r.Wq);
1093 out["p0"] = encode_scalar(r.p0);
1094 // pk IS REPORTED ONLY WHEN IT EXISTS: the reference returns it for the
1095 // k the caller named and leaves it undefined otherwise, and a zero
1096 // there would read as a probability rather than as the absence of one.
1097 if (r.has_pk) out["pk"] = encode_scalar(r.pk);
1098 return out;
1099 }
1100};
1101
1102/**
1103 * qsys_mgck_sync. Unlike qsys_mapg1k its service law is read from a JSON
1104 * object, the MATLAB svc struct: {"type":"det","d":..}, {"type":"gamma",
1105 * "alpha":..,"theta":..} or {"type":"ph","alpha":[..],"T":[[..]]}. The
1106 * 'density' type takes a function handle and has no JSON form. 'tol' and
1107 * 'nmax' tune one truncation together, so naming either requires both.
1108 */
1109struct OpQsysMgckSync {
1110 template <class T>
1111 static Json run(Args& a) {
1112 const T lambda = a.number<T>("lambda");
1113 const std::size_t c = a.count("c");
1114 const std::size_t K = a.count("K");
1115 Args sv(a.get("svc"), "qsys_mgck_sync: svc");
1116 const std::string type = sv.text("type", "");
1117 qsys::ServiceLaw<T> svc;
1118 if (type == "det") {
1119 svc = qsys::ServiceLaw<T>::deterministic(sv.number<T>("d"));
1120 } else if (type == "gamma") {
1121 const T al = sv.number<T>("alpha");
1122 svc = qsys::ServiceLaw<T>::gamma(al, sv.number<T>("theta"));
1123 } else if (type == "ph") {
1124 const std::vector<T> al = sv.vector<T>("alpha");
1125 svc = qsys::ServiceLaw<T>::phase_type(al, sv.matrix<T>("T"));
1126 } else if (type == "density") {
1127 throw UnsupportedError(ErrorKind{"Api"},
1128 "qsys_mgck_sync: a 'density' service law is a function handle and has no "
1129 "named-JSON form; use 'det', 'gamma' or 'ph', or call it from the library");
1130 } else {
1131 throw UnsupportedError(ErrorKind{"Distribution"},
1132 "qsys_mgck_sync: svc.type must be 'det', 'gamma' or 'ph', got '" + type + "'");
1133 }
1134 sv.done();
1135 const std::string variant = a.text("variant", "");
1136 if (variant.empty()) throw InputError("qsys_mgck_sync: missing required argument 'variant'");
1137 const std::size_t nphases = a.count("nphases", 0);
1138 const bool any = a.has("tol") || a.has("nmax");
1139 if (any && !(a.has("tol") && a.has("nmax")))
1140 throw InputError("qsys_mgck_sync: 'tol' and 'nmax' tune one truncation together; "
1141 "give both or neither");
1142 const T tol = a.scalar<T>("tol", num_traits<T>::from_int(0));
1143 const std::size_t nmax = a.count("nmax", 0);
1144 a.done();
1145 const qsys::MgckSyncResult<T> r =
1146 any ? qsys::qsys_mgck_sync(lambda, c, K, svc, variant, nphases, tol, nmax)
1147 : qsys::qsys_mgck_sync(lambda, c, K, svc, variant, nphases);
1148 Json out;
1149 out["queueLengthDist"] = encode_vector(r.queueLengthDist);
1150 out["p0"] = encode_scalar(r.p0);
1151 out["pK"] = encode_scalar(r.pK);
1152 out["meanQueueLength"] = encode_scalar(r.meanQueueLength);
1153 out["meanQueueLengthQ"] = encode_scalar(r.meanQueueLengthQ);
1154 out["throughput"] = encode_scalar(r.throughput);
1155 out["lossProbability"] = encode_scalar(r.lossProbability);
1156 out["meanSojournTime"] = encode_scalar(r.meanSojournTime);
1157 out["meanWaitingTime"] = encode_scalar(r.meanWaitingTime);
1158 out["utilization"] = encode_scalar(r.utilization);
1159 out["variant"] = r.variant;
1160 out["nphases"] = r.nphases;
1161 out["stateSpaceSize"] = r.stateSpaceSize;
1162 out["analyzer"] = r.analyzer;
1163 return out;
1164 }
1165};
1166
1167struct OpQsysMmck {
1168 template <class T>
1169 static Json run(Args& a) {
1170 const T lambda = a.number<T>("lambda");
1171 const T mu = a.number<T>("mu");
1172 const unsigned c = a.uinteger("c");
1173 const unsigned K = a.uinteger("K");
1174 a.done();
1175 const qsys::MmckResult<T> r = qsys::qsys_mmck(lambda, mu, c, K);
1176 Json out;
1177 out["meanQueueLength"] = encode_scalar(r.meanQueueLength);
1178 out["meanQueueLengthQ"] = encode_scalar(r.meanQueueLengthQ);
1179 out["meanWaitingTime"] = encode_scalar(r.meanWaitingTime);
1180 out["meanSojournTime"] = encode_scalar(r.meanSojournTime);
1181 out["utilization"] = encode_scalar(r.utilization);
1182 out["throughput"] = encode_scalar(r.throughput);
1183 out["lossProbability"] = encode_scalar(r.lossProbability);
1184 out["queueLengthDist"] = encode_vector(r.queueLengthDist);
1185 return out;
1186 }
1187};
1188
1189struct OpQsysMm1kLoss {
1190 template <class T>
1191 static Json run(Args& a) {
1192 const T lambda = a.number<T>("lambda");
1193 const T mu = a.number<T>("mu");
1194 const unsigned K = a.uinteger("K");
1195 a.done();
1196 const qsys::Mm1kLossResult<T> r = qsys::qsys_mm1k_loss(lambda, mu, K);
1197 Json out;
1198 out["lossProbability"] = encode_scalar(r.lossProbability);
1199 out["utilization"] = encode_scalar(r.utilization);
1200 return out;
1201 }
1202};
1203
1204struct OpQsysMg1kLossMgs {
1205 template <class T>
1206 static Json run(Args& a) {
1207 const T lambda = a.number<T>("lambda");
1208 const T mu = a.number<T>("mu");
1209 const T mu_scv = a.number<T>("mu_scv");
1210 const unsigned K = a.uinteger("K");
1211 a.done();
1212 const qsys::Mg1kLossMgsResult<T> r = qsys::qsys_mg1k_loss_mgs(lambda, mu, mu_scv, K);
1213 Json out;
1214 out["lossProbability"] = encode_scalar(r.lossProbability);
1215 out["utilization"] = encode_scalar(r.utilization);
1216 return out;
1217 }
1218};
1219
1220/**
1221 * The batch-arrival M[X]/M/1. MATLAB's fourth argument is
1222 * `E_X2_or_Var_X`, whose reading depends on a trailing 'variance' flag; the two
1223 * readings are separate C++ overloads, so the boundary takes the two names
1224 * apart and refuses both at once rather than guessing which was meant.
1225 */
1226struct OpQsysMxm1 {
1227 template <class T>
1228 static Json run(Args& a) {
1229 const T lambda_batch = a.number<T>("lambda_batch");
1230 const T mu = a.number<T>("mu");
1231 const T E_X = a.number<T>("E_X");
1232 const bool has_m2 = a.has("E_X2"), has_var = a.has("Var_X");
1233 if (has_m2 == has_var)
1234 throw InputError(
1235 "qsys_mxm1: give exactly one of 'E_X2' (the second moment of the batch size) "
1236 "and 'Var_X' (its variance); MATLAB selects between them with a trailing "
1237 "'variance' flag, which has no place in a named-argument object");
1238 const T m2 = has_m2 ? a.number<T>("E_X2")
1239 : T(a.number<T>("Var_X") + E_X * E_X);
1240 a.done();
1241 const qsys::MxM1Result<T> r = qsys::qsys_mxm1(lambda_batch, mu, E_X, m2);
1242 Json out;
1243 out["W"] = encode_scalar(r.W);
1244 out["Wq"] = encode_scalar(r.Wq);
1245 out["U"] = encode_scalar(r.U);
1246 out["Q"] = encode_scalar(r.Q);
1247 return out;
1248 }
1249};
1250
1251struct OpQsysDmc {
1252 template <class T>
1253 static Json run(Args& a) {
1254 const T lambda = a.number<T>("lambda_arr");
1255 const T mu = a.number<T>("mu");
1256 const unsigned c = a.uinteger("c");
1257 const bool tuned = a.has("truncation") || a.has("quadSteps");
1258 const unsigned truncation = a.uinteger("truncation", 0u);
1259 const unsigned quadSteps = a.uinteger("quadSteps", 200u);
1260 a.done();
1261 const qsys::DmcResult<T> r = tuned ? qsys::qsys_dmc(lambda, mu, c, truncation, quadSteps)
1262 : qsys::qsys_dmc(lambda, mu, c);
1263 Json out;
1264 out["meanQueueLength"] = encode_scalar(r.meanQueueLength);
1265 out["meanWaitingQueue"] = encode_scalar(r.meanWaitingQueue);
1266 out["meanWaitingTime"] = encode_scalar(r.meanWaitingTime);
1267 out["meanSojournTime"] = encode_scalar(r.meanSojournTime);
1268 out["utilization"] = encode_scalar(r.utilization);
1269 return out;
1270 }
1271};
1272
1273struct OpQsysMmccRetrialFp {
1274 template <class T>
1275 static Json run(Args& a) {
1276 const T lambda = a.number<T>("lambda");
1277 const T mu = a.number<T>("mu");
1278 const unsigned c = a.uinteger("c");
1279 const bool tuned = a.has("tol") || a.has("maxiter");
1280 const T tol = a.scalar<T>("tol", num_traits<T>::from_int(0));
1281 const std::size_t maxiter = a.count("maxiter", 0);
1282 a.done();
1283 const qsys::MmccRetrialFpResult<T> r =
1284 tuned ? qsys::qsys_mmcc_retrial_fp(lambda, mu, c, tol, maxiter)
1285 : qsys::qsys_mmcc_retrial_fp(lambda, mu, c);
1286 Json out;
1287 out["blockingProbability"] = encode_scalar(r.blockingProbability);
1288 out["retrialRate"] = encode_scalar(r.retrialRate);
1289 out["iterations"] = encode_count(r.iterations);
1290 out["converged"] = r.converged;
1291 return out;
1292 }
1293};
1294
1295struct OpQsysMm1Dps {
1296 template <class T>
1297 static Json run(Args& a) {
1298 const std::vector<T> lambda = a.vector<T>("lambda");
1299 const std::vector<T> mu = a.vector<T>("mu");
1300 const std::vector<T> w = a.vector<T>("w");
1301 const bool tuned = a.has("tol") || a.has("maxCutoff");
1302 const T tol = a.scalar<T>("tol", num_traits<T>::from_int(0));
1303 const unsigned maxCutoff = a.uinteger("maxCutoff", 0u);
1304 a.done();
1305 const qsys::Mm1DpsResult<T> r = tuned
1306 ? qsys::qsys_mm1_dps(lambda, mu, w, tol, maxCutoff)
1307 : qsys::qsys_mm1_dps(lambda, mu, w);
1308 Json out;
1309 // The port names the per-class response times `T_` because `T` is the
1310 // arithmetic parameter; MATLAB's output is `T`, which is the name a
1311 // caller reading the reference will look for, so that is the wire key.
1312 out["T"] = encode_vector(r.T_);
1313 out["rho"] = encode_scalar(r.rho);
1314 return out;
1315 }
1316};
1317
1318/** The six M/G/1 disciplines, all {W per class, rhohat}. */
1319#define LINE_QSYS_MG1_DISC(OpName, fn, ResultT) \
1320 struct OpName { \
1321 template <class T> \
1322 static Json run(Args& a) { \
1323 const std::vector<T> lambda = a.vector<T>("lambda"); \
1324 const std::vector<T> mu = a.vector<T>("mu"); \
1325 const std::vector<T> cs = a.vector<T>("cs"); \
1326 a.done(); \
1327 const qsys::ResultT<T> r = qsys::fn(lambda, mu, cs); \
1328 Json out; \
1329 out["W"] = encode_vector(r.W); \
1330 out["rhohat"] = encode_scalar(r.rhohat); \
1331 return out; \
1332 } \
1333 }
1334
1335LINE_QSYS_MG1_DISC(OpQsysMg1Fb, qsys_mg1_fb, Mg1DisciplineResult);
1336LINE_QSYS_MG1_DISC(OpQsysMg1Lrpt, qsys_mg1_lrpt, Mg1DisciplineResult);
1337LINE_QSYS_MG1_DISC(OpQsysMg1Psjf, qsys_mg1_psjf, Mg1DisciplineResult);
1338LINE_QSYS_MG1_DISC(OpQsysMg1Setf, qsys_mg1_setf, Mg1DisciplineResult);
1339LINE_QSYS_MG1_DISC(OpQsysMg1Srpt, qsys_mg1_srpt, Mg1DisciplineResult);
1340LINE_QSYS_MG1_DISC(OpQsysMg1Prio, qsys_mg1_prio, Mg1PrioResult);
1341#undef LINE_QSYS_MG1_DISC
1342
1343/**
1344 * The two slotted systems. `convention` selects LAS-DA (late arrival, delayed
1345 * access; the reference's default) or EAS (early arrival). They are DIFFERENT
1346 * queues, not two readings of one, so an unknown name is refused rather than
1347 * falling back to the default.
1348 */
1349inline dqsys::GeoConvention read_geo_convention(Args& a) {
1350 const std::string c = a.text("convention", "LAS_DA");
1351 if (c == "LAS_DA" || c == "las_da" || c == "LASDA") return dqsys::GeoConvention::LAS_DA;
1352 if (c == "EAS" || c == "eas") return dqsys::GeoConvention::EAS;
1353 throw InputError("'convention' must be LAS_DA or EAS, got '" + c + "'");
1354}
1355
1356inline const char* geo_convention_name(dqsys::GeoConvention c) {
1357 return c == dqsys::GeoConvention::EAS ? "EAS" : "LAS_DA";
1358}
1359
1360struct OpQsysGeoGeo1 {
1361 template <class T>
1362 static Json run(Args& a) {
1363 const T arrival = a.number<T>("a");
1364 const T service = a.number<T>("s");
1365 const dqsys::GeoConvention conv = read_geo_convention(a);
1366 a.done();
1367 const dqsys::GeoGeo1Result<T> r = dqsys::dqsys_geogeo1(arrival, service, conv);
1368 Json out;
1369 out["convention"] = geo_convention_name(r.convention);
1370 out["arrivalProb"] = encode_scalar(r.arrivalProb);
1371 out["serviceProb"] = encode_scalar(r.serviceProb);
1372 out["utilization"] = encode_scalar(r.utilization);
1373 out["throughput"] = encode_scalar(r.throughput);
1374 out["emptyProb"] = encode_scalar(r.emptyProb);
1375 out["ratio"] = encode_scalar(r.ratio);
1376 out["meanQueueLength"] = encode_scalar(r.meanQueueLength);
1377 out["meanWaitingQueue"] = encode_scalar(r.meanWaitingQueue);
1378 out["meanSojournTime"] = encode_scalar(r.meanSojournTime);
1379 out["meanWaitingTime"] = encode_scalar(r.meanWaitingTime);
1380 out["meanServiceTime"] = encode_scalar(r.meanServiceTime);
1381 return out;
1382 }
1383};
1384
1385struct OpQsysGeoxGeo1 {
1386 template <class T>
1387 static Json run(Args& a) {
1388 const T arrival = a.number<T>("a");
1389 const T beta = a.number<T>("beta");
1390 const T service = a.number<T>("s");
1391 const dqsys::GeoConvention conv = read_geo_convention(a);
1392 a.done();
1393 const dqsys::GeoXGeo1Result<T> r = dqsys::dqsys_geoxgeo1(arrival, beta, service, conv);
1394 Json out;
1395 out["convention"] = geo_convention_name(r.convention);
1396 out["batchArrivalProb"] = encode_scalar(r.batchArrivalProb);
1397 out["batchMean"] = encode_scalar(r.batchMean);
1398 out["batchSecondFactorialMoment"] = encode_scalar(r.batchSecondFactorialMoment);
1399 out["serviceProb"] = encode_scalar(r.serviceProb);
1400 out["arrivalRate"] = encode_scalar(r.arrivalRate);
1401 out["throughput"] = encode_scalar(r.throughput);
1402 out["utilization"] = encode_scalar(r.utilization);
1403 out["boundaryEmptyProb"] = encode_scalar(r.boundaryEmptyProb);
1404 out["meanQueueLength"] = encode_scalar(r.meanQueueLength);
1405 out["meanWaitingQueue"] = encode_scalar(r.meanWaitingQueue);
1406 out["meanSojournTime"] = encode_scalar(r.meanSojournTime);
1407 out["meanWaitingTime"] = encode_scalar(r.meanWaitingTime);
1408 out["meanServiceTime"] = encode_scalar(r.meanServiceTime);
1409 return out;
1410 }
1411};
1412
1413/** The four means plus the level distribution, shared by the MAP-driven queues. */
1414template <class R>
1415Json qsys_map_results(const R& r) {
1416 Json out;
1417 out["meanQueueLength"] = encode_scalar(r.meanQueueLength);
1418 out["meanWaitingTime"] = encode_scalar(r.meanWaitingTime);
1419 out["meanSojournTime"] = encode_scalar(r.meanSojournTime);
1420 out["utilization"] = encode_scalar(r.utilization);
1421 out["queueLengthDist"] = encode_vector(r.queueLengthDist);
1422 return out;
1423}
1424
1425/** qsys_map_results plus the reference's queueLengthMoments / sojournTimeMoments. */
1426template <class R>
1427Json qsys_map_results_moments(const R& r) {
1428 Json out = qsys_map_results(r);
1429 out["queueLengthMoments"] = encode_vector(r.queueLengthMoments);
1430 out["sojournTimeMoments"] = encode_vector(r.sojournTimeMoments);
1431 return out;
1432}
1433
1434/** A MAP as the (D0, D1) pair the reference passes around, under any two keys. */
1435template <class T>
1436mam::Map<T> read_map(Args& a, const char* d0, const char* d1) {
1437 mam::Map<T> m;
1438 m.D0 = a.matrix<T>(d0);
1439 m.D1 = a.matrix<T>(d1);
1440 return m;
1441}
1442
1443struct OpQsysMapm1 {
1444 template <class T>
1445 static Json run(Args& a) {
1446 const mam::Map<T> arrival = read_map<T>(a, "D0", "D1");
1447 const T mu = a.number<T>("mu");
1448 const bool sized = a.has("dist_size");
1449 const std::size_t dist_size = a.count("dist_size", 0);
1450 a.done();
1451 return qsys_map_results(sized ? qsys::qsys_mapm1(arrival, mu, dist_size)
1452 : qsys::qsys_mapm1(arrival, mu));
1453 }
1454};
1455
1456struct OpQsysMapmc {
1457 template <class T>
1458 static Json run(Args& a) {
1459 const mam::Map<T> arrival = read_map<T>(a, "D0", "D1");
1460 const T mu = a.number<T>("mu");
1461 const unsigned c = a.uinteger("c");
1462 const bool sized = a.has("dist_size");
1463 const std::size_t dist_size = a.count("dist_size", 0);
1464 a.done();
1465 return qsys_map_results(sized ? qsys::qsys_mapmc(arrival, mu, c, dist_size)
1466 : qsys::qsys_mapmc(arrival, mu, c));
1467 }
1468};
1469
1470struct OpQsysMapmap1 {
1471 template <class T>
1472 static Json run(Args& a) {
1473 const mam::Map<T> arrival = read_map<T>(a, "C0", "C1");
1474 const mam::Map<T> service = read_map<T>(a, "D0", "D1");
1475 const std::size_t dist_size = a.count("dist_size", 100);
1476 const std::size_t numQLMoms = a.count("numQLMoms", 3);
1477 const std::size_t numSTMoms = a.count("numSTMoms", 3);
1478 a.done();
1479 return qsys_map_results_moments(
1480 qsys::qsys_mapmap1(arrival, service, dist_size, numQLMoms, numSTMoms));
1481 }
1482};
1483
1484struct OpQsysMapph1 {
1485 template <class T>
1486 static Json run(Args& a) {
1487 const mam::Map<T> arrival = read_map<T>(a, "D0", "D1");
1488 const std::vector<T> sigma = a.vector<T>("sigma");
1489 const Matrix<T> S = a.matrix<T>("S");
1490 const bool sized = a.has("dist_size");
1491 const std::size_t dist_size = a.count("dist_size", 0);
1492 a.done();
1493 return qsys_map_results(sized ? qsys::qsys_mapph1(arrival, sigma, S, dist_size)
1494 : qsys::qsys_mapph1(arrival, sigma, S));
1495 }
1496};
1497
1498/**
1499 * MAP/PH/c. The port offers exactly two overloads, the defaulted one and the
1500 * one taking all three tuning arguments, so naming ANY of them requires naming
1501 * ALL of them rather than mixing a caller value with defaults restated here.
1502 */
1503struct OpQsysMapphc {
1504 template <class T>
1505 static Json run(Args& a) {
1506 const mam::Map<T> arrival = read_map<T>(a, "D0", "D1");
1507 const std::vector<T> alpha = a.vector<T>("alpha");
1508 const Matrix<T> S = a.matrix<T>("S");
1509 const unsigned c = a.uinteger("c");
1510 const bool any =
1511 a.has("dist_size") || a.has("num_w_moms") || a.has("w_points");
1512 const bool all =
1513 a.has("dist_size") && a.has("num_w_moms") && a.has("w_points");
1514 if (any && !all)
1515 throw InputError(
1516 "qsys_mapphc: 'dist_size', 'num_w_moms' and 'w_points' select one tuned "
1517 "overload together; give all three or none");
1518 const std::size_t dist_size = a.count("dist_size", 0);
1519 const std::size_t num_w_moms = a.count("num_w_moms", 0);
1520 const std::vector<T> w_points = a.vector_or_empty<T>("w_points");
1521 a.done();
1522 const qsys::MapPhcResult<T> r =
1523 all ? qsys::qsys_mapphc(arrival, alpha, S, c, dist_size, num_w_moms, w_points)
1524 : qsys::qsys_mapphc(arrival, alpha, S, c);
1525 Json out = qsys_map_results(r);
1526 out["waitingTimeMoments"] = encode_vector(r.waitingTimeMoments);
1527 out["waitingTimeCCDF"] = encode_vector(r.waitingTimeCCDF);
1528 out["waitingTimePoints"] = encode_vector(r.waitingTimePoints);
1529 out["probWait"] = encode_scalar(r.probWait);
1530 // The repeating configuration count, binomial(ms+c-1,c): it is what
1531 // sizes the solve, so a caller comparing runtimes needs it reported
1532 // rather than recomputed from ms and c.
1533 out["phaseCount"] = encode_count(r.phaseCount);
1534 return out;
1535 }
1536};
1537
1538struct OpQsysPhph1 {
1539 template <class T>
1540 static Json run(Args& a) {
1541 const std::vector<T> alpha = a.vector<T>("alpha");
1542 const Matrix<T> Tm = a.matrix<T>("T");
1543 const std::vector<T> beta = a.vector<T>("beta");
1544 const Matrix<T> S = a.matrix<T>("S");
1545 const bool sized = a.has("dist_size");
1546 const std::size_t dist_size = a.count("dist_size", 0);
1547 a.done();
1548 return qsys_map_results(sized ? qsys::qsys_phph1(alpha, Tm, beta, S, dist_size)
1549 : qsys::qsys_phph1(alpha, Tm, beta, S));
1550 }
1551};
1552
1553/**
1554 * The two deterministic-service MAP queues. Their four tuning arguments move
1555 * together -- the arrival truncation bounds a sum the level iteration then
1556 * inverts -- so naming ANY of them requires naming ALL of them, rather than
1557 * mixing one caller value with three library defaults from a different regime.
1558 */
1559inline bool qsys_map_d_tuned(Args& a, const char* who) {
1560 const bool any = a.has("dist_size") || a.has("max_arrivals") || a.has("max_levels") ||
1561 a.has("tol");
1562 const bool all = a.has("dist_size") && a.has("max_arrivals") && a.has("max_levels") &&
1563 a.has("tol");
1564 if (any && !all)
1565 throw InputError(std::string(who) +
1566 ": 'dist_size', 'max_arrivals', 'max_levels' and 'tol' tune one "
1567 "truncation together; give all four or none");
1568 return all;
1569}
1570
1571struct OpQsysMapd1 {
1572 template <class T>
1573 static Json run(Args& a) {
1574 const mam::Map<T> arrival = read_map<T>(a, "D0", "D1");
1575 const T s = a.number<T>("s");
1576 const bool tuned = qsys_map_d_tuned(a, "qsys_mapd1");
1577 const std::size_t dist_size = a.count("dist_size", 0);
1578 const unsigned max_arrivals = a.uinteger("max_arrivals", 0u);
1579 const std::size_t max_levels = a.count("max_levels", 0);
1580 const T tol = a.scalar<T>("tol", num_traits<T>::from_int(0));
1581 a.done();
1582 return qsys_map_results(
1583 tuned ? qsys::qsys_mapd1(arrival, s, dist_size, max_arrivals, max_levels, tol)
1584 : qsys::qsys_mapd1(arrival, s));
1585 }
1586};
1587
1588struct OpQsysMapdc {
1589 template <class T>
1590 static Json run(Args& a) {
1591 const mam::Map<T> arrival = read_map<T>(a, "D0", "D1");
1592 const T s = a.number<T>("s");
1593 const unsigned c = a.uinteger("c");
1594 const bool tuned = qsys_map_d_tuned(a, "qsys_mapdc");
1595 const std::size_t dist_size = a.count("dist_size", 0);
1596 const unsigned max_arrivals = a.uinteger("max_arrivals", 0u);
1597 const std::size_t max_levels = a.count("max_levels", 0);
1598 const T tol = a.scalar<T>("tol", num_traits<T>::from_int(0));
1599 a.done();
1600 return qsys_map_results(
1601 tuned ? qsys::qsys_mapdc(arrival, s, c, dist_size, max_arrivals, max_levels, tol)
1602 : qsys::qsys_mapdc(arrival, s, c));
1603 }
1604};
1605
1606struct OpQsysMapg1 {
1607 template <class T>
1608 static Json run(Args& a) {
1609 const mam::Map<T> arrival = read_map<T>(a, "D0", "D1");
1610 const std::vector<T> moments = a.vector<T>("serviceMoments");
1611 const std::size_t dist_size = a.count("dist_size", 100);
1612 const std::size_t numQLMoms = a.count("numQLMoms", 3);
1613 const std::size_t numSTMoms = a.count("numSTMoms", 3);
1614 a.done();
1615 const qsys::MapG1Result<T> r =
1616 qsys::qsys_mapg1(arrival, moments, dist_size, numQLMoms, numSTMoms);
1617 Json out = qsys_map_results_moments(r);
1618 out["analyzer"] = r.analyzer;
1619 // WHICH LAW WAS FITTED IS PART OF THE ANSWER: the moments select an
1620 // exponential, an Erlang, a hyperexponential or an acyclic PH, and a
1621 // caller comparing against MATLAB has to know which branch produced the
1622 // number before it can call a difference a discrepancy.
1623 const char* kind = "Acyclic";
1624 switch (r.fitKind) {
1625 case qsys::MapG1ServiceFit::Exponential: kind = "Exponential"; break;
1626 case qsys::MapG1ServiceFit::Erlang: kind = "Erlang"; break;
1627 case qsys::MapG1ServiceFit::Hyperexponential: kind = "Hyperexponential"; break;
1628 case qsys::MapG1ServiceFit::Acyclic: kind = "Acyclic"; break;
1629 }
1630 out["fitKind"] = kind;
1631 out["servicePhases"] = encode_count(r.servicePhases);
1632 out["serviceFitD0"] = encode_matrix(r.serviceFit.D0);
1633 out["serviceFitD1"] = encode_matrix(r.serviceFit.D1);
1634 return out;
1635 }
1636};
1637
1638struct OpQsysPhm1 {
1639 template <class T>
1640 static Json run(Args& a) {
1641 const std::vector<T> alpha = a.vector<T>("alpha");
1642 const Matrix<T> Tm = a.matrix<T>("T");
1643 const T mu = a.number<T>("mu");
1644 const bool tuned = a.has("tol");
1645 const T tol = a.scalar<T>("tol", num_traits<T>::from_int(0));
1646 a.done();
1647 const qsys::PhM1Result<T> r = tuned ? qsys::qsys_phm1(alpha, Tm, mu, tol)
1648 : qsys::qsys_phm1(alpha, Tm, mu);
1649 Json out;
1650 out["meanQueueLength"] = encode_scalar(r.meanQueueLength);
1651 out["meanWaitingQueue"] = encode_scalar(r.meanWaitingQueue);
1652 out["meanWaitingTime"] = encode_scalar(r.meanWaitingTime);
1653 out["meanSojournTime"] = encode_scalar(r.meanSojournTime);
1654 out["utilization"] = encode_scalar(r.utilization);
1655 out["sigma"] = encode_scalar(r.sigma);
1656 return out;
1657 }
1658};
1659
1660struct OpQsysPhmc {
1661 template <class T>
1662 static Json run(Args& a) {
1663 const std::vector<T> alpha = a.vector<T>("alpha");
1664 const Matrix<T> Tm = a.matrix<T>("T");
1665 const T mu = a.number<T>("mu");
1666 const unsigned c = a.uinteger("c");
1667 const bool any = a.has("maxIter") || a.has("tol");
1668 const bool all = a.has("maxIter") && a.has("tol");
1669 if (any && !all)
1670 throw InputError(
1671 "qsys_phmc: 'maxIter' and 'tol' bound one fixed-point iteration together; give "
1672 "both or neither");
1673 const unsigned maxIter = a.uinteger("maxIter", 0u);
1674 const T tol = a.scalar<T>("tol", num_traits<T>::from_int(0));
1675 a.done();
1676 const qsys::PhMcResult<T> r = all ? qsys::qsys_phmc(alpha, Tm, mu, c, maxIter, tol)
1677 : qsys::qsys_phmc(alpha, Tm, mu, c);
1678 Json out;
1679 out["meanQueueLength"] = encode_scalar(r.meanQueueLength);
1680 out["meanWaitingQueue"] = encode_scalar(r.meanWaitingQueue);
1681 out["meanWaitingTime"] = encode_scalar(r.meanWaitingTime);
1682 out["meanSojournTime"] = encode_scalar(r.meanSojournTime);
1683 out["utilization"] = encode_scalar(r.utilization);
1684 return out;
1685 }
1686};
1687
1688struct OpQsysBmapphnnRetrial {
1689 template <class T>
1690 static Json run(Args& a) {
1691 const std::vector<Matrix<T> > D = a.matrices<T>("D");
1692 const std::vector<T> beta = a.vector<T>("beta");
1693 const Matrix<T> S = a.matrix<T>("S");
1694 const int N = a.required_integer("N");
1695 const T alpha = a.number<T>("alpha");
1696 const T gamma = a.number<T>("gamma");
1697 const T p = a.number<T>("p");
1698 const std::vector<int> Ri = a.ints("R");
1699 qsys::BmapPhNnRetrialOptions opt;
1700 opt.maxLevel = a.count("maxLevel", 0);
1701 if (a.has("retrialPolicy")) {
1702 const std::string pol = a.text("retrialPolicy", "LINEAR");
1703 if (pol == "CONSTANT") opt.retrialPolicy = qsys::RetrialPolicy::CONSTANT;
1704 else if (pol == "LINEAR") opt.retrialPolicy = qsys::RetrialPolicy::LINEAR;
1705 else throw InputError("qsys_bmapphnn_retrial: retrialPolicy must be LINEAR or CONSTANT");
1706 }
1707 if (a.has("tolerance")) opt.tolerance = a.number<double>("tolerance");
1708 if (a.has("tailTolerance")) opt.tailTolerance = a.number<double>("tailTolerance");
1709 if (a.has("maxDim")) opt.maxDim = a.number<double>("maxDim");
1710 if (a.has("maxBlockSize")) opt.maxBlockSize = a.number<double>("maxBlockSize");
1711 a.done();
1712 std::vector<long> R(Ri.size());
1713 for (std::size_t i = 0; i < Ri.size(); ++i) R[i] = static_cast<long>(Ri[i]);
1714 const qsys::BmapPhNnRetrialResult<T> r =
1715 qsys::qsys_bmapphnn_retrial(D, beta, S, N, alpha, gamma, p, R, opt);
1716 Json out;
1717 out["L_orbit"] = encode_scalar(r.L_orbit);
1718 out["N_server"] = encode_scalar(r.N_server);
1719 out["L_system"] = encode_scalar(r.L_system);
1720 out["utilization"] = encode_scalar(r.utilization);
1721 out["throughput"] = encode_scalar(r.throughput);
1722 out["P_idle"] = encode_scalar(r.P_idle);
1723 out["P_empty_orbit"] = encode_scalar(r.P_empty_orbit);
1724 out["P_empty_system"] = encode_scalar(r.P_empty_system);
1725 out["pi"] = encode_matrix(r.pi);
1726 out["truncLevel"] = encode_count(r.truncLevel);
1727 out["truncError"] = encode_scalar(r.truncError);
1728 out["topLevelMass"] = encode_scalar(r.topLevelMass);
1729 out["converged"] = r.converged;
1730 // WHETHER THE LEVEL WAS CLIPPED is part of the answer: a clipped
1731 // truncation makes every reported mass a lower bound on the true one.
1732 out["clipped"] = r.clipped;
1733 return out;
1734 }
1735};
1736
1737// ---------------------------------------------------------------------------
1738// moment: the moment-basis changes
1739// ---------------------------------------------------------------------------
1740//
1741// Every member is a pure change of basis on a moment sequence -- raw, central,
1742// factorial, up-factorial, binomial, negative-binomial -- so each takes one
1743// vector and returns one vector under the reference's own argument and output
1744// names. They are field operations throughout, which is why the registry lists
1745// all three arithmetics and none of them needs the transcendental guard.
1746
1747#define LINE_MOMENT_VEC(OpName, fn, in_key, out_key) \
1748 struct OpName { \
1749 template <class T> \
1750 static Json run(Args& a) { \
1751 const std::vector<T> v = a.vector<T>(in_key); \
1752 a.done(); \
1753 Json out; \
1754 out[out_key] = encode_vector(moment::fn(v)); \
1755 return out; \
1756 } \
1757 }
1758
1759LINE_MOMENT_VEC(OpMomentBinomialFromFactorial, moment_binomial_from_factorial, "f", "b");
1760LINE_MOMENT_VEC(OpMomentBinomialFromNegbinomial, moment_binomial_from_negbinomial, "bm", "b");
1761LINE_MOMENT_VEC(OpMomentBinotrans, moment_binotrans, "x", "y");
1762LINE_MOMENT_VEC(OpMomentBinotransinv, moment_binotransinv, "y", "x");
1763LINE_MOMENT_VEC(OpMomentCentralFromRaw, moment_central_from_raw, "m", "mc");
1764LINE_MOMENT_VEC(OpMomentFactorialFromBinomial, moment_factorial_from_binomial, "b", "f");
1765LINE_MOMENT_VEC(OpMomentFactorialFromRaw, moment_factorial_from_raw, "m", "f");
1766LINE_MOMENT_VEC(OpMomentFactorialFromUpfactorial, moment_factorial_from_upfactorial, "fp", "f");
1767LINE_MOMENT_VEC(OpMomentNegbinomialFromBinomial, moment_negbinomial_from_binomial, "b", "bm");
1768LINE_MOMENT_VEC(OpMomentNegbinomialFromUpfactorial, moment_negbinomial_from_upfactorial, "fp",
1769 "bm");
1770LINE_MOMENT_VEC(OpMomentRawFromFactorial, moment_raw_from_factorial, "f", "m");
1771LINE_MOMENT_VEC(OpMomentRawFromUpfactorial, moment_raw_from_upfactorial, "fp", "m");
1772LINE_MOMENT_VEC(OpMomentUpfactorialFromFactorial, moment_upfactorial_from_factorial, "f", "fp");
1773LINE_MOMENT_VEC(OpMomentUpfactorialFromNegbinomial, moment_upfactorial_from_negbinomial, "bm",
1774 "fp");
1775LINE_MOMENT_VEC(OpMomentUpfactorialFromRaw, moment_upfactorial_from_raw, "m", "fp");
1776#undef LINE_MOMENT_VEC
1777
1778/**
1779 * The inverse of moment_central_from_raw needs the MEAN as well: a central
1780 * sequence has lost it (mc(1) is 0 by construction), so it cannot be recovered
1781 * from the sequence alone and is a second argument rather than an option.
1782 */
1783struct OpMomentRawFromCentral {
1784 template <class T>
1785 static Json run(Args& a) {
1786 const std::vector<T> mc = a.vector<T>("mc");
1787 const T m1 = a.number<T>("m1");
1788 a.done();
1789 Json out;
1791 return out;
1792 }
1793};
1794
1795/** The four triangular coefficient matrices, each a function of the order n. */
1796#define LINE_MOMENT_TRI(OpName, fn, out_key) \
1797 struct OpName { \
1798 template <class T> \
1799 static Json run(Args& a) { \
1800 const int n = a.required_integer("n"); \
1801 a.done(); \
1802 Json out; \
1803 out[out_key] = encode_matrix(moment::fn<T>(n)); \
1804 return out; \
1805 } \
1806 }
1807
1808LINE_MOMENT_TRI(OpMomentLah, moment_lah, "L");
1809LINE_MOMENT_TRI(OpMomentStirling1, moment_stirling1, "s");
1810LINE_MOMENT_TRI(OpMomentStirling2, moment_stirling2, "S");
1811LINE_MOMENT_TRI(OpMomentStirlingcycle, moment_stirlingcycle, "sigma");
1812#undef LINE_MOMENT_TRI
1813
1814// ---------------------------------------------------------------------------
1815// mc: the Markov-chain solvers beyond ctmc_solve / dtmc_solve
1816// ---------------------------------------------------------------------------
1817//
1818// `ctmc_rand` is NOT exposed: it takes a random generator by reference, so its
1819// answer depends on a stream this boundary cannot carry, and a generator seeded
1820// here would return a matrix the caller cannot reproduce.
1821//
1822// AN ITERATIVE SOLVER REPORTS WHETHER IT CONVERGED, and that flag rides beside
1823// the vector rather than being folded into it. A caller that reads only the
1824// numbers from a run that hit its iteration cap would be reading a partial
1825// sweep as a stationary law.
1826
1827/** The block partition MS/MSS: a list of state-index lists, 0-based. */
1828inline std::vector<std::vector<std::size_t> > read_partition(Args& a, const char* key) {
1829 const Json& j = a.get(key);
1830 const std::string where = std::string("partition '") + key + "'";
1831 if (!j.is_array()) throw InputError(where + " must be an array of index arrays");
1832 std::vector<std::vector<std::size_t> > out;
1833 for (std::size_t b = 0; b < j.size(); ++b) {
1834 if (!j[b].is_array())
1835 throw InputError(where + ": block " + std::to_string(b) + " must be an array");
1836 std::vector<std::size_t> blk;
1837 for (std::size_t i = 0; i < j[b].size(); ++i) {
1838 const std::string text = decimal_text(j[b][i], where);
1839 const long long v = std::strtoll(text.c_str(), nullptr, 10);
1840 if (v < 0)
1841 throw InputError(where + ": a state index cannot be negative, got " + text);
1842 blk.push_back(static_cast<std::size_t>(v));
1843 }
1844 out.push_back(blk);
1845 }
1846 return out;
1847}
1848
1849/** A list of state indices, reported unchanged: an index is not a measurement. */
1850inline Json encode_indices(const std::vector<std::size_t>& v) {
1851 Json a = Json::array();
1852 for (std::size_t i = 0; i < v.size(); ++i) a.push_back(static_cast<std::uint64_t>(v[i]));
1853 return a;
1854}
1855
1856/**
1857 * The reducible solvers' five outputs. `pis` is the per-component stationary
1858 * law and `pi0` the absorption probabilities into each component, so the two
1859 * together say WHERE the mass went as well as how it is spread once there;
1860 * reporting only `pi` would lose the decomposition the function exists for.
1861 */
1862template <class T>
1863Json reducible_results(const mc::ReducibleResult<T>& r) {
1864 Json out;
1865 out["pi"] = encode_vector(r.pi);
1866 out["pis"] = encode_matrix(r.pis);
1867 out["pi0"] = encode_matrix(r.pi0);
1868 out["scc"] = encode_indices(r.scc);
1869 Json isrec = Json::array();
1870 for (std::size_t i = 0; i < r.isrec.size(); ++i) isrec.push_back(bool(r.isrec[i]));
1871 out["isrec"] = isrec;
1872 out["Pl"] = encode_matrix(r.Pl);
1873 out["pil"] = encode_matrix(r.pil);
1874 return out;
1875}
1876
1877/** The block-decomposition variant, which has no lumped chain to report. */
1878template <class T>
1879Json blkdecomp_results(const mc::BlkDecompResult<T>& r) {
1880 Json out;
1881 out["pi"] = encode_vector(r.pi);
1882 out["pis"] = encode_matrix(r.pis);
1883 out["pi0"] = encode_matrix(r.pi0);
1884 out["scc"] = encode_indices(r.scc);
1885 Json isrec = Json::array();
1886 for (std::size_t i = 0; i < r.isrec.size(); ++i) isrec.push_back(bool(r.isrec[i]));
1887 out["isrec"] = isrec;
1888 return out;
1889}
1890
1891struct OpCtmcSolveReducible {
1892 template <class T>
1893 static Json run(Args& a) {
1894 const Matrix<T> Q = a.matrix<T>("Q");
1895 const std::vector<T> pi0 = a.vector<T>("pi0");
1896 const double zeroColTol = a.scalar<double>("zeroColTol", 1e-12);
1897 a.done();
1898 const mc::ReducibleResult<T> r = mc::ctmc_solve_reducible(Q, pi0, zeroColTol);
1899 return reducible_results(r);
1900 }
1901};
1902
1903struct OpCtmcSolveReducibleBlkdecomp {
1904 template <class T>
1905 static Json run(Args& a) {
1906 const Matrix<T> Q = a.matrix<T>("Q");
1907 const std::vector<T> pin = a.vector<T>("pin");
1908 const double reachTol = a.scalar<double>("reachTol", 1e-15);
1909 const double zeroColTol = a.scalar<double>("zeroColTol", 1e-12);
1910 a.done();
1911 const mc::BlkDecompResult<T> r =
1912 mc::ctmc_solve_reducible_blkdecomp(Q, pin, reachTol, zeroColTol);
1913 return blkdecomp_results(r);
1914 }
1915};
1916
1917struct OpDtmcSolveReducible {
1918 template <class T>
1919 static Json run(Args& a) {
1920 const Matrix<T> P = a.matrix<T>("P");
1921 const std::vector<T> pin = a.vector<T>("pin");
1922 const double zeroColTol = a.scalar<double>("zeroColTol", 1e-12);
1923 a.done();
1924 const mc::ReducibleResult<T> r = mc::dtmc_solve_reducible(P, pin, zeroColTol);
1925 return reducible_results(r);
1926 }
1927};
1928
1929struct OpDtmcMakestochastic {
1930 template <class T>
1931 static Json run(Args& a) {
1932 const Matrix<T> P = a.matrix<T>("Pin");
1933 a.done();
1934 Json out;
1936 return out;
1937 }
1938};
1939
1940struct OpCtmcSens {
1941 template <class T>
1942 static Json run(Args& a) {
1943 const Matrix<T> Q = a.matrix<T>("Q");
1944 const Matrix<T> dQ = a.matrix<T>("dQ");
1945 const std::vector<T> pi = a.vector<T>("pi");
1946 a.done();
1947 Json out;
1948 out["dpi"] = encode_vector(mc::ctmc_sens(Q, dQ, pi));
1949 return out;
1950 }
1951};
1952
1953struct OpStronglyConnComp {
1954 template <class T>
1955 static Json run(Args& a) {
1956 const Matrix<T> A = a.matrix<T>("A");
1957 a.done();
1958 const mc::SccResult r = mc::stronglyconncomp(A);
1959 Json out;
1960 out["scc"] = encode_indices(r.scc);
1961 Json rec = Json::array();
1962 for (std::size_t i = 0; i < r.recurrent.size(); ++i) rec.push_back(bool(r.recurrent[i]));
1963 out["recurrent"] = rec;
1964 Json mem = Json::array();
1965 for (std::size_t i = 0; i < r.members.size(); ++i)
1966 mem.push_back(encode_indices(r.members[i]));
1967 out["members"] = mem;
1968 return out;
1969 }
1970};
1971
1972struct OpCtmcRandomization {
1973 template <class T>
1974 static Json run(Args& a) {
1975 const Matrix<T> Q = a.matrix<T>("Q");
1976 const bool given = a.has("q");
1977 const T q = a.scalar<T>("q", num_traits<T>::from_int(0));
1978 a.done();
1979 if (!given)
1980 throw InputError(
1981 "ctmc_randomization: 'q' is the uniformization rate and has no default here; "
1982 "MATLAB requires it too");
1983 const mc::RandomizationResult<T> r = mc::ctmc_randomization(Q, q);
1984 Json out;
1985 out["P"] = encode_matrix(r.P);
1986 out["q"] = encode_scalar(r.q);
1987 return out;
1988 }
1989};
1990
1991/**
1992 * The three nearly-completely-decomposable solvers. `q` is the uniformization
1993 * rate and is OPTIONAL for courtois and multi, because the port carries the
1994 * reference's own derived default (courtois_default_rate) and restating it here
1995 * would fix a value the library computes from Q and MS.
1996 */
1997struct OpCtmcCourtois {
1998 template <class T>
1999 static Json run(Args& a) {
2000 const Matrix<T> Q = a.matrix<T>("Q");
2001 const std::vector<std::vector<std::size_t> > MS = read_partition(a, "MS");
2002 const bool given = a.has("q");
2003 const T q = a.scalar<T>("q", num_traits<T>::from_int(0));
2004 a.done();
2005 const mc::CourtoisResult<T> r =
2006 given ? mc::ctmc_courtois(Q, MS, q) : mc::ctmc_courtois(Q, MS);
2007 Json out;
2008 out["p"] = encode_vector(r.p);
2009 out["v"] = encode_indices(r.v);
2010 out["Qperm"] = encode_matrix(r.Qperm);
2011 out["Qdec"] = encode_matrix(r.Qdec);
2012 out["P"] = encode_matrix(r.P);
2013 out["B"] = encode_matrix(r.B);
2014 out["C"] = encode_scalar(r.C);
2015 out["eps"] = encode_scalar(r.eps);
2016 out["epsRowMax"] = encode_scalar(r.epsRowMax);
2017 out["epsMAX"] = encode_scalar(r.epsMAX);
2018 out["q"] = encode_scalar(r.q);
2019 return out;
2020 }
2021};
2022
2023/** KMS and Takahashi return the same six outputs, under the same names. */
2024template <class R>
2025Json kms_results(const R& r) {
2026 Json out;
2027 out["p"] = encode_vector(r.p);
2028 out["p_1"] = encode_vector(r.p_1);
2029 out["pcourt"] = encode_vector(r.pcourt);
2030 out["Qperm"] = encode_matrix(r.Qperm);
2031 out["eps"] = encode_scalar(r.eps);
2032 out["epsMAX"] = encode_scalar(r.epsMAX);
2033 return out;
2034}
2035
2036struct OpCtmcKms {
2037 template <class T>
2038 static Json run(Args& a) {
2039 const Matrix<T> Q = a.matrix<T>("Q");
2040 const std::vector<std::vector<std::size_t> > MS = read_partition(a, "MS");
2041 const std::size_t numSteps = a.count("numSteps");
2042 a.done();
2043 return kms_results(mc::ctmc_kms(Q, MS, numSteps));
2044 }
2045};
2046
2047struct OpCtmcTakahashi {
2048 template <class T>
2049 static Json run(Args& a) {
2050 const Matrix<T> Q = a.matrix<T>("Q");
2051 const std::vector<std::vector<std::size_t> > MS = read_partition(a, "MS");
2052 const std::size_t numSteps = a.count("numSteps");
2053 const double massTol = a.scalar<double>("massTol", 1e-14);
2054 a.done();
2055 return kms_results(mc::ctmc_takahashi(Q, MS, numSteps, massTol));
2056 }
2057};
2058
2059struct OpCtmcMulti {
2060 template <class T>
2061 static Json run(Args& a) {
2062 const Matrix<T> Q = a.matrix<T>("Q");
2063 const std::vector<std::vector<std::size_t> > MS = read_partition(a, "MS");
2064 const std::vector<std::vector<std::size_t> > MSS = read_partition(a, "MSS");
2065 const bool given = a.has("q");
2066 const T q = a.scalar<T>("q", num_traits<T>::from_int(0));
2067 a.done();
2068 const mc::MultiResult<T> r =
2069 given ? mc::ctmc_multi(Q, MS, MSS, q) : mc::ctmc_multi(Q, MS, MSS);
2070 Json out;
2071 out["p"] = encode_vector(r.p);
2072 out["pcourt"] = encode_vector(r.pcourt);
2073 out["Qperm"] = encode_matrix(r.Qperm);
2074 out["eps"] = encode_scalar(r.eps);
2075 out["epsMAX"] = encode_scalar(r.epsMAX);
2076 return out;
2077 }
2078};
2079
2080/**
2081 * GMRES reports MATLAB's `flag` rather than a boolean: 0 is convergence and the
2082 * nonzero values distinguish an iteration cap from a stagnation, which are
2083 * different failures and are what a caller checks before using `x`.
2084 */
2085struct OpCtmcGmres {
2086 template <class T>
2087 static Json run(Args& a) {
2088 const Matrix<T> A = a.matrix<T>("A");
2089 const std::vector<T> b = a.vector<T>("b");
2090 const double tol = a.scalar<double>("tol", 1e-12);
2091 const int restart = a.integer("restart", 0);
2092 const int maxit = a.integer("maxit", 0);
2093 const std::vector<T> x0 = a.vector_or_empty<T>("x0");
2094 a.done();
2095 const mc::GmresResult<T> r = mc::ctmc_gmres(A, b, tol, restart, maxit, x0);
2096 Json out;
2097 out["x"] = encode_vector(r.x);
2098 out["flag"] = r.flag;
2099 out["relres"] = encode_scalar(r.relres);
2100 out["iter"] = static_cast<std::int64_t>(r.iter);
2101 return out;
2102 }
2103};
2104
2105struct OpCtmcGmresMulti {
2106 template <class T>
2107 static Json run(Args& a) {
2108 const Matrix<T> A = a.matrix<T>("A");
2109 const Matrix<T> B = a.matrix<T>("B");
2110 const double tol = a.scalar<double>("tol", 1e-12);
2111 const int restart = a.integer("restart", 0);
2112 const int maxit = a.integer("maxit", 0);
2113 a.done();
2114 const mc::GmresMultiResult<T> r = mc::ctmc_gmres_multi(A, B, tol, restart, maxit);
2115 Json out;
2116 out["X"] = encode_matrix(r.X);
2117 out["flag"] = r.flag;
2118 return out;
2119 }
2120};
2121
2122/**
2123 * BiCGSTAB reports MATLAB's `flag` on the same convention as GMRES, with the
2124 * addition of 4 for a scalar breakdown of the underlying Lanczos process, which
2125 * is a different failure from an iteration cap and is what a caller checks
2126 * before using `x`. `iter` counts matrix-vector products with A, so it is
2127 * directly comparable with the `iter` GMRES reports.
2128 */
2129struct OpCtmcBicgstab {
2130 template <class T>
2131 static Json run(Args& a) {
2132 const Matrix<T> A = a.matrix<T>("A");
2133 const std::vector<T> b = a.vector<T>("b");
2134 const double tol = a.scalar<double>("tol", 1e-12);
2135 const int maxit = a.integer("maxit", 0);
2136 const std::vector<T> x0 = a.vector_or_empty<T>("x0");
2137 a.done();
2138 const mc::BicgstabResult<T> r = mc::ctmc_bicgstab(A, b, tol, maxit, x0);
2139 Json out;
2140 out["x"] = encode_vector(r.x);
2141 out["flag"] = r.flag;
2142 out["relres"] = encode_scalar(r.relres);
2143 out["iter"] = static_cast<std::int64_t>(r.iter);
2144 return out;
2145 }
2146};
2147
2148struct OpCtmcBicgstabMulti {
2149 template <class T>
2150 static Json run(Args& a) {
2151 const Matrix<T> A = a.matrix<T>("A");
2152 const Matrix<T> B = a.matrix<T>("B");
2153 const double tol = a.scalar<double>("tol", 1e-12);
2154 const int maxit = a.integer("maxit", 0);
2155 a.done();
2156 const mc::BicgstabMultiResult<T> r = mc::ctmc_bicgstab_multi(A, B, tol, maxit);
2157 Json out;
2158 out["X"] = encode_matrix(r.X);
2159 out["flag"] = r.flag;
2160 return out;
2161 }
2162};
2163
2164struct OpCtmcFoxglynn {
2165 template <class T>
2166 static Json run(Args& a) {
2167 const std::vector<T> pi0 = a.vector<T>("pi0");
2168 const Matrix<T> Q = a.matrix<T>("Q");
2169 const T t = a.number<T>("t");
2170 const double tol = a.scalar<double>("tol", 1e-12);
2171 const int maxiter = a.integer("maxiter", -1);
2172 a.done();
2173 const mc::FoxGlynnResult<T> r = mc::ctmc_foxglynn(pi0, Q, t, tol, maxiter);
2174 Json out;
2175 out["pi"] = encode_vector(r.pi);
2176 // The Poisson weight window: `left` and `right` are the truncation
2177 // points the algorithm chose, and `w` the weights between them, so a
2178 // caller can see how much mass the truncation kept.
2179 out["left"] = static_cast<std::int64_t>(r.left);
2180 out["right"] = static_cast<std::int64_t>(r.right);
2181 out["w"] = encode_vector(r.w);
2182 return out;
2183 }
2184};
2185
2186struct OpCtmcSaddlepoint {
2187 template <class T>
2188 static Json run(Args& a) {
2189 const Matrix<T> D0 = a.matrix<T>("D0");
2190 const Matrix<T> D1 = a.matrix<T>("D1");
2191 const std::vector<T> t = a.vector<T>("t");
2192 const std::vector<int> ki = a.ints("k");
2193 const std::string method = a.text("method", "daniels2");
2194 const std::vector<T> pi0 = a.vector_or_empty<T>("pi0");
2195 a.done();
2197 if (method == "daniels" || method == "sp1")
2199 else if (method == "plain" || method == "bare")
2201 else if (!(method == "daniels2" || method == "sp2"))
2202 throw InputError("ctmc_saddlepoint: unknown method '" + method +
2203 "', expected daniels2, daniels or plain");
2204 std::vector<long> k(ki.size());
2205 for (std::size_t i = 0; i < ki.size(); ++i) k[i] = static_cast<long>(ki[i]);
2206 const mc::SaddlepointResult<T> r = mc::ctmc_saddlepoint(D0, D1, t, k, m, pi0);
2207 Json out;
2208 out["p"] = encode_vector(r.p);
2209 out["logp"] = encode_vector(r.logp);
2210 out["theta"] = encode_vector(r.theta);
2211 out["k2"] = encode_vector(r.k2);
2212 out["lambda"] = encode_scalar(r.lambda);
2213 // K2 = t*eta''(theta*) is the expansion parameter, so a caller that
2214 // ignores out_of_regime is reading a number outside its own validity
2215 out["outOfRegime"] = r.out_of_regime;
2216 return out;
2217 }
2218};
2219
2220struct OpCtmcFau {
2221 template <class T>
2222 static Json run(Args& a) {
2223 const std::vector<T> pi0 = a.vector<T>("pi0");
2224 const Matrix<T> Q = a.matrix<T>("Q");
2225 const T t = a.number<T>("t");
2226 const double epsilon = a.scalar<double>("epsilon", 1e-6);
2227 const double delta = a.scalar<double>("delta", 1e-12);
2228 const int maxsteps = a.integer("maxsteps", -1);
2229 a.done();
2230 const mc::FauResult<T> r = mc::ctmc_fau(pi0, Q, t, epsilon, delta, maxsteps);
2231 Json out;
2232 out["pi"] = encode_vector(r.pit);
2233 // The whole error budget, since the method removes mass and never puts
2234 // any back: errorBound is the L1 distance to the exact distribution,
2235 // and the three components say where it went.
2236 out["steps"] = static_cast<std::int64_t>(r.steps);
2237 out["lambdaMin"] = r.lambdaMin;
2238 out["lambdaMax"] = r.lambdaMax;
2239 out["uniformRate"] = r.uniformRate;
2240 out["weightTail"] = num_traits<T>::to_double(r.weightTail);
2241 out["weightWindow"] = num_traits<T>::to_double(r.weightWindow);
2242 out["droppedMass"] = num_traits<T>::to_double(r.droppedMass);
2243 out["errorBound"] = num_traits<T>::to_double(r.errorBound);
2244 out["supportMax"] = static_cast<std::int64_t>(r.supportMax);
2245 out["supportFinal"] = static_cast<std::int64_t>(r.supportFinal);
2246 out["truncated"] = r.truncated;
2247 out["absorbed"] = r.absorbed;
2248 return out;
2249 }
2250};
2251
2252struct OpCtmcTransient {
2253 template <class T>
2254 static Json run(Args& a) {
2255 const Matrix<T> Q = a.matrix<T>("Q");
2256 const std::vector<T> pi0 = a.vector<T>("pi0");
2257 const T t0 = a.number<T>("t0");
2258 const T t1 = a.number<T>("t1");
2259 const double rtol = a.scalar<double>("rtol", 1e-3);
2260 const double atol = a.scalar<double>("atol", 1e-6);
2261 a.done();
2262 const mc::TransientResult<T> r = mc::ctmc_transient(Q, pi0, t0, t1, rtol, atol);
2263 Json out;
2264 out["t"] = encode_vector(r.t);
2265 out["pi"] = encode_matrix(r.pi);
2266 return out;
2267 }
2268};
2269
2270struct OpCtmcTransientSens {
2271 template <class T>
2272 static Json run(Args& a) {
2273 const Matrix<T> Q = a.matrix<T>("Q");
2274 const Matrix<T> dQ = a.matrix<T>("dQ");
2275 const std::vector<T> pi0 = a.vector<T>("pi0");
2276 const T t0 = a.number<T>("t0");
2277 const T t1 = a.number<T>("t1");
2278 const double rtol = a.scalar<double>("rtol", 1e-3);
2279 const double atol = a.scalar<double>("atol", 1e-6);
2280 a.done();
2281 const mc::TransientSensResult<T> r =
2282 mc::ctmc_transient_sens(Q, dQ, pi0, t0, t1, rtol, atol);
2283 Json out;
2284 out["t"] = encode_vector(r.t);
2285 out["pi"] = encode_matrix(r.pi);
2286 out["dpi"] = encode_matrix(r.dpi);
2287 return out;
2288 }
2289};
2290
2291// ---------------------------------------------------------------------------
2292// aoi: the age-of-information laws
2293// ---------------------------------------------------------------------------
2294//
2295// The MATLAB signatures take the service law as a LAPLACE-STIELTJES TRANSFORM,
2296// which in C++ is a std::function and has no JSON representation. Rather than
2297// leave nine of the fifteen laws unreachable, the boundary takes a NAMED law
2298// and builds the transform with the port's own aoi_lst_* constructors -- the
2299// same four the reference offers -- so the caller states which law it means and
2300// nothing about the transform is invented here.
2301//
2302// `lstAoI` IS NOT REPORTED, because it is a transform and not a number; the
2303// header states that nothing in the family inverts one. `has_lst` says whether
2304// the law returned one at all, which is the part a caller can act on.
2305
2306/** The service law of the G/M/1 and M/G/1 age formulas, by name. */
2307template <class T>
2308aoi::Lst<T> read_aoi_lst(Args& a, T& E1, T& E2, bool& has_E2) {
2309 const std::string kind = a.text("lst", "");
2310 if (kind.empty())
2311 throw InputError(
2312 "'lst' names the service law whose Laplace-Stieltjes transform this age formula "
2313 "needs: exp, erlang, det or ph. A transform itself cannot cross a JSON boundary");
2314 has_E2 = true;
2315 if (kind == "exp") {
2316 const T mu = a.number<T>("lst_mu");
2317 E1 = T(num_traits<T>::from_int(1) / mu);
2318 E2 = T(num_traits<T>::from_int(2) / (mu * mu));
2319 return aoi::aoi_lst_exp(mu);
2320 }
2321 if (kind == "erlang") {
2322 const unsigned k = a.uinteger("lst_k");
2323 const T mu = a.number<T>("lst_mu");
2324 const T kk = num_traits<T>::from_int(static_cast<int>(k));
2325 E1 = T(kk / mu);
2326 E2 = T(kk * (kk + num_traits<T>::from_int(1)) / (mu * mu));
2327 return aoi::aoi_lst_erlang(k, mu);
2328 }
2329 if (kind == "det") {
2330 const T d = a.number<T>("lst_d");
2331 E1 = d;
2332 E2 = T(d * d);
2333 return aoi::aoi_lst_det(d);
2334 }
2335 if (kind == "ph") {
2336 const std::vector<T> alpha = a.vector<T>("lst_alpha");
2337 const Matrix<T> Tmat = a.matrix<T>("lst_T");
2338 // The two moments of a PH law are -alpha inv(T) 1 and 2 alpha inv(T)^2 1;
2339 // the caller states them rather than having this boundary invert T, so
2340 // the number the formula uses is the caller's own and not a second
2341 // inversion that could disagree with the transform beside it.
2342 E1 = a.number<T>("E_1");
2343 E2 = a.number<T>("E_2");
2344 return aoi::aoi_lst_ph(alpha, Tmat);
2345 }
2346 throw InputError("'lst' must be one of exp, erlang, det, ph; got '" + kind + "'");
2347}
2348
2349/** [meanAoI, varAoI, peakAoI], the closed-form arm of the family. */
2350template <class T>
2351Json aoi_results(const aoi::AoiResult<T>& r) {
2352 Json out;
2353 out["meanAoI"] = encode_scalar(r.meanAoI);
2354 out["varAoI"] = encode_scalar(r.varAoI);
2355 out["peakAoI"] = encode_scalar(r.peakAoI);
2356 return out;
2357}
2358
2359/** The transform arm: the two means, and whether an LST came back with them. */
2360template <class T>
2361Json aoi_lst_results(const aoi::AoiLstResult<T>& r) {
2362 Json out;
2363 out["meanAoI"] = encode_scalar(r.meanAoI);
2364 out["peakAoI"] = encode_scalar(r.peakAoI);
2365 out["has_lst"] = r.has_lst;
2366 return out;
2367}
2368
2369#define LINE_AOI_TWO(OpName, fn, k1, k2) \
2370 struct OpName { \
2371 template <class T> \
2372 static Json run(Args& a) { \
2373 const T x = a.number<T>(k1); \
2374 const T y = a.number<T>(k2); \
2375 a.done(); \
2376 return aoi_results(aoi::fn(x, y)); \
2377 } \
2378 }
2379
2380LINE_AOI_TWO(OpAoiFcfsDm1, aoi_fcfs_dm1, "tau", "mu");
2381LINE_AOI_TWO(OpAoiFcfsMd1, aoi_fcfs_md1, "lambda", "d");
2382LINE_AOI_TWO(OpAoiFcfsMm1, aoi_fcfs_mm1, "lambda", "mu");
2383LINE_AOI_TWO(OpAoiLcfsprDm1, aoi_lcfspr_dm1, "tau", "mu");
2384LINE_AOI_TWO(OpAoiLcfsprMd1, aoi_lcfspr_md1, "lambda", "d");
2385LINE_AOI_TWO(OpAoiLcfsprMm1, aoi_lcfspr_mm1, "lambda", "mu");
2386#undef LINE_AOI_TWO
2387
2388/** The two non-preemptive LCFS M/GI/1 laws: transform at lambda plus both moments. */
2389#define LINE_AOI_MGI1_TWO(OpName, fn) \
2390 struct OpName { \
2391 template <class T> \
2392 static Json run(Args& a) { \
2393 const T lambda = a.number<T>("lambda"); \
2394 T E1 = num_traits<T>::from_int(0), E2 = num_traits<T>::from_int(0); \
2395 bool has_E2 = false; \
2396 const aoi::Lst<T> H = read_aoi_lst<T>(a, E1, E2, has_E2); \
2397 a.done(); \
2398 return aoi_lst_results(aoi::fn(lambda, H, E1, E2)); \
2399 } \
2400 }
2401
2402LINE_AOI_MGI1_TWO(OpAoiLcfsdMgi1, aoi_lcfsd_mgi1);
2403LINE_AOI_MGI1_TWO(OpAoiLcfssMgi1, aoi_lcfss_mgi1);
2404#undef LINE_AOI_MGI1_TWO
2405
2406struct OpAoiFcfsMgi1 {
2407 template <class T>
2408 static Json run(Args& a) {
2409 const T lambda = a.number<T>("lambda");
2410 T E1 = num_traits<T>::from_int(0), E2 = num_traits<T>::from_int(0);
2411 bool has_E2 = false;
2412 const aoi::Lst<T> H = read_aoi_lst<T>(a, E1, E2, has_E2);
2413 a.done();
2414 return aoi_lst_results(aoi::aoi_fcfs_mgi1(lambda, H, E1, E2));
2415 }
2416};
2417
2418struct OpAoiLcfsprMgi1 {
2419 template <class T>
2420 static Json run(Args& a) {
2421 const T lambda = a.number<T>("lambda");
2422 T E1 = num_traits<T>::from_int(0), E2 = num_traits<T>::from_int(0);
2423 bool has_E2 = false;
2424 const aoi::Lst<T> H = read_aoi_lst<T>(a, E1, E2, has_E2);
2425 a.done();
2426 return aoi_lst_results(aoi::aoi_lcfspr_mgi1(lambda, H, E1));
2427 }
2428};
2429
2430struct OpAoiFcfsGim1 {
2431 template <class T>
2432 static Json run(Args& a) {
2433 const T mu = a.number<T>("mu");
2434 T E1 = num_traits<T>::from_int(0), E2 = num_traits<T>::from_int(0);
2435 bool has_E2 = false;
2436 const aoi::Lst<T> Y = read_aoi_lst<T>(a, E1, E2, has_E2);
2437 a.done();
2438 return aoi_lst_results(aoi::aoi_fcfs_gim1(Y, mu, E1, E2));
2439 }
2440};
2441
2442#define LINE_AOI_GIM1_TWO(OpName, fn) \
2443 struct OpName { \
2444 template <class T> \
2445 static Json run(Args& a) { \
2446 const T mu = a.number<T>("mu"); \
2447 T E1 = num_traits<T>::from_int(0), E2 = num_traits<T>::from_int(0); \
2448 bool has_E2 = false; \
2449 const aoi::Lst<T> Y = read_aoi_lst<T>(a, E1, E2, has_E2); \
2450 a.done(); \
2451 return aoi_lst_results(aoi::fn(Y, mu, E1, E2)); \
2452 } \
2453 }
2454
2455LINE_AOI_GIM1_TWO(OpAoiLcfsdGim1, aoi_lcfsd_gim1);
2456LINE_AOI_GIM1_TWO(OpAoiLcfssGim1, aoi_lcfss_gim1);
2457#undef LINE_AOI_GIM1_TWO
2458
2459struct OpAoiLcfsprGim1 {
2460 template <class T>
2461 static Json run(Args& a) {
2462 const T mu = a.number<T>("mu");
2463 T E1 = num_traits<T>::from_int(0), E2 = num_traits<T>::from_int(0);
2464 bool has_E2 = false;
2465 const aoi::Lst<T> Y = read_aoi_lst<T>(a, E1, E2, has_E2);
2466 a.done();
2467 return aoi_lst_results(aoi::aoi_lcfspr_gim1(Y, mu, E1, E2));
2468 }
2469};
2470
2471// ---------------------------------------------------------------------------
2472// fj: the fork-join order-statistic bounds and approximations
2473// ---------------------------------------------------------------------------
2474//
2475// `fj_order_stat` is NOT exposed: its last argument is the CDF of the branch
2476// service law as a callable, and unlike the age family there is no set of named
2477// laws the reference offers in its place, so a substitute would be this file's
2478// choice rather than the caller's.
2479
2480/** The many members returning one bare number, under the reference's name. */
2481#define LINE_FJ_K_LAMBDA_MU(OpName, fn, out_key) \
2482 struct OpName { \
2483 template <class T> \
2484 static Json run(Args& a) { \
2485 const unsigned K = a.uinteger("K"); \
2486 const T lambda = a.number<T>("lambda"); \
2487 const T mu = a.number<T>("mu"); \
2488 a.done(); \
2489 Json out; \
2490 out[out_key] = encode_scalar(fj::fn(K, lambda, mu)); \
2491 return out; \
2492 } \
2493 }
2494
2495LINE_FJ_K_LAMBDA_MU(OpFjResptNt, fj_respt_nt, "R");
2496LINE_FJ_K_LAMBDA_MU(OpFjResptVarki, fj_respt_varki, "R");
2497LINE_FJ_K_LAMBDA_MU(OpFjResptVm, fj_respt_vm, "R");
2498LINE_FJ_K_LAMBDA_MU(OpFjRmax, fj_rmax, "Rmax");
2499#undef LINE_FJ_K_LAMBDA_MU
2500
2501#define LINE_FJ_K_MU(OpName, fn, out_key) \
2502 struct OpName { \
2503 template <class T> \
2504 static Json run(Args& a) { \
2505 const unsigned K = a.uinteger("K"); \
2506 const T mu = a.number<T>("mu"); \
2507 a.done(); \
2508 Json out; \
2509 out[out_key] = encode_scalar(fj::fn(K, mu)); \
2510 return out; \
2511 } \
2512 }
2513
2514LINE_FJ_K_MU(OpFjSmTput, fj_sm_tput, "X");
2515LINE_FJ_K_MU(OpFjXmaxEmma, fj_xmax_emma, "Xmax");
2516LINE_FJ_K_MU(OpFjXmaxExp, fj_xmax_exp, "Xmax");
2517#undef LINE_FJ_K_MU
2518
2519struct OpFjHarmonic {
2520 template <class T>
2521 static Json run(Args& a) {
2522 const unsigned K = a.uinteger("K");
2523 a.done();
2524 Json out;
2525 out["HK"] = encode_scalar(fj::fj_harmonic<T>(K));
2526 return out;
2527 }
2528};
2529
2530struct OpFjQuantile {
2531 template <class T>
2532 static Json run(Args& a) {
2533 const unsigned K = a.uinteger("K");
2534 const T q = a.number<T>("q");
2535 a.done();
2536 Json out;
2537 out["x"] = encode_scalar(fj::fj_quantile(K, q));
2538 return out;
2539 }
2540};
2541
2542struct OpFjResptTwoway {
2543 template <class T>
2544 static Json run(Args& a) {
2545 const T lambda = a.number<T>("lambda");
2546 const T mu = a.number<T>("mu");
2547 a.done();
2548 Json out;
2549 out["R"] = encode_scalar(fj::fj_respt_2way(lambda, mu));
2550 return out;
2551 }
2552};
2553
2554struct OpFjSynchDelay {
2555 template <class T>
2556 static Json run(Args& a) {
2557 const T lambda = a.number<T>("lambda");
2558 const T mu = a.number<T>("mu");
2559 a.done();
2560 Json out;
2561 out["D"] = encode_scalar(fj::fj_synch_delay(lambda, mu));
2562 return out;
2563 }
2564};
2565
2566struct OpFjXmax2 {
2567 template <class T>
2568 static Json run(Args& a) {
2569 const T lambda1 = a.number<T>("lambda1");
2570 const T lambda2 = a.number<T>("lambda2");
2571 a.done();
2572 Json out;
2573 out["Xmax"] = encode_scalar(fj::fj_xmax_2(lambda1, lambda2));
2574 return out;
2575 }
2576};
2577
2578struct OpFjXmaxErlang {
2579 template <class T>
2580 static Json run(Args& a) {
2581 const unsigned K = a.uinteger("K");
2582 const unsigned k = a.uinteger("k");
2583 const T mu = a.number<T>("mu");
2584 a.done();
2585 Json out;
2586 out["Xmax"] = encode_scalar(fj::fj_xmax_erlang(K, k, mu));
2587 return out;
2588 }
2589};
2590
2591struct OpFjRmaxErlang {
2592 template <class T>
2593 static Json run(Args& a) {
2594 const unsigned K = a.uinteger("K");
2595 const unsigned k = a.uinteger("k");
2596 const T lambda = a.number<T>("lambda");
2597 const T mu = a.number<T>("mu");
2598 a.done();
2599 Json out;
2600 out["Rmax"] = encode_scalar(fj::fj_rmax_erlang(K, k, lambda, mu));
2601 return out;
2602 }
2603};
2604
2605struct OpFjXmaxHyperexp {
2606 template <class T>
2607 static Json run(Args& a) {
2608 const unsigned K = a.uinteger("K");
2609 const T p1 = a.number<T>("p1");
2610 const T mu1 = a.number<T>("mu1");
2611 const T mu2 = a.number<T>("mu2");
2612 a.done();
2613 Json out;
2614 out["Xmax"] = encode_scalar(fj::fj_xmax_hyperexp(K, p1, mu1, mu2));
2615 return out;
2616 }
2617};
2618
2619/**
2620 * The EVD response-time fit. `calibrated` selects the version whose constants
2621 * were refitted, which is a DIFFERENT approximation and not a refinement of the
2622 * same one, so it defaults to the reference's own false rather than to the
2623 * one that happens to be more accurate on any given model.
2624 */
2625struct OpFjRmaxEvd {
2626 template <class T>
2627 static Json run(Args& a) {
2628 const unsigned K = a.uinteger("K");
2629 const T R = a.number<T>("R");
2630 const T sigma_R = a.number<T>("sigma_R");
2631 const bool calibrated = a.boolean("calibrated", false);
2632 a.done();
2633 Json out;
2634 out["Rmax"] = encode_scalar(fj::fj_rmax_evd(K, R, sigma_R, calibrated));
2635 return out;
2636 }
2637};
2638
2639struct OpFjBounds {
2640 template <class T>
2641 static Json run(Args& a) {
2642 const unsigned K = a.uinteger("K");
2643 const T lambda = a.number<T>("lambda");
2644 const T mu = a.number<T>("mu");
2645 a.done();
2646 const fj::FJBoundsResult<T> r = fj::fj_bounds(K, lambda, mu);
2647 Json out;
2648 out["Rmax"] = encode_scalar(r.Rmax);
2649 out["Rmin"] = encode_scalar(r.Rmin);
2650 return out;
2651 }
2652};
2653
2654struct OpFjCharMax {
2655 template <class T>
2656 static Json run(Args& a) {
2657 const unsigned K = a.uinteger("K");
2658 const T mu = a.number<T>("mu");
2659 a.done();
2660 const fj::FJCharMaxResult<T> r = fj::fj_char_max(K, mu);
2661 Json out;
2662 out["MK"] = encode_scalar(r.MK);
2663 out["mK"] = encode_scalar(r.mK);
2664 return out;
2665 }
2666};
2667
2668struct OpFjGkBound {
2669 template <class T>
2670 static Json run(Args& a) {
2671 const unsigned K = a.uinteger("K");
2672 a.done();
2673 const fj::FJGKBoundResult<T> r = fj::fj_gk_bound<T>(K);
2674 Json out;
2675 out["K"] = encode_count(r.K);
2676 out["exponential"] = encode_scalar(r.exponential);
2677 out["uniform"] = encode_scalar(r.uniform);
2678 out["evd"] = encode_scalar(r.evd);
2679 out["upper_bound"] = encode_scalar(r.upper_bound);
2680 return out;
2681 }
2682};
2683
2684struct OpFjOrdstatExp {
2685 template <class T>
2686 static Json run(Args& a) {
2687 const std::vector<T> ri = a.vector<T>("ri");
2688 const std::size_t k = a.count("k");
2689 a.done();
2690 Json out;
2691 out["m"] = encode_scalar(fj::fj_ordstat_exp<T>(ri, k));
2692 return out;
2693 }
2694};
2695
2696struct OpFjQuorumMoments {
2697 template <class T>
2698 static Json run(Args& a) {
2699 const std::vector<T> branchMeans = a.vector<T>("branchMeans");
2700 const std::vector<T> branchVars = a.vector<T>("branchVars");
2701 const std::size_t k = a.count("k");
2702 a.done();
2703 const fj::FJQuorumMomentsResult<T> r =
2704 fj::fj_quorum_moments(branchMeans, branchVars, k);
2705 Json out;
2706 out["m"] = encode_scalar(r.m);
2707 out["v"] = encode_scalar(r.v);
2708 return out;
2709 }
2710};
2711
2712struct OpFjXmaxApprox {
2713 template <class T>
2714 static Json run(Args& a) {
2715 const unsigned K = a.uinteger("K");
2716 const T mu_X = a.number<T>("mu_X");
2717 const T sigma_X = a.number<T>("sigma_X");
2718 const std::string t = a.text("type", "Exp");
2720 if (t == "Exp") type = fj::FJDistType::Exp;
2721 else if (t == "Uniform") type = fj::FJDistType::Uniform;
2722 else if (t == "Evd") type = fj::FJDistType::Evd;
2723 else if (t == "Bound") type = fj::FJDistType::Bound;
2724 else throw InputError("'type' must be Exp, Uniform, Evd or Bound; got '" + t + "'");
2725 a.done();
2726 const fj::FJXmaxApproxResult<T> r = fj::fj_xmax_approx(K, mu_X, sigma_X, type);
2727 Json out;
2728 out["Xmax"] = encode_scalar(r.Xmax);
2729 out["GK"] = encode_scalar(r.GK);
2730 return out;
2731 }
2732};
2733
2734struct OpFjXmaxNormal {
2735 template <class T>
2736 static Json run(Args& a) {
2737 const unsigned K = a.uinteger("K");
2738 const T mu = a.number<T>("mu");
2739 const T sigma = a.number<T>("sigma");
2740 const std::string m = a.text("method", "Johnson");
2742 if (m == "Johnson") method = fj::FJNormalMethod::Johnson;
2743 else if (m == "Arnold") method = fj::FJNormalMethod::Arnold;
2744 else if (m == "Corrected") method = fj::FJNormalMethod::Corrected;
2745 else throw InputError("'method' must be Johnson, Arnold or Corrected; got '" + m + "'");
2746 a.done();
2747 const fj::FJXmaxNormalResult<T> r = fj::fj_xmax_normal(K, mu, sigma, method);
2748 Json out;
2749 out["Xmax"] = encode_scalar(r.Xmax);
2750 out["Vmax"] = encode_scalar(r.Vmax);
2751 return out;
2752 }
2753};
2754
2755struct OpFjXmaxPareto {
2756 template <class T>
2757 static Json run(Args& a) {
2758 const unsigned K = a.uinteger("K");
2759 const T beta = a.number<T>("beta");
2760 const T k = a.number<T>("k");
2761 a.done();
2762 const fj::FJXmaxParetoResult<T> r = fj::fj_xmax_pareto(K, beta, k);
2763 Json out;
2764 out["Xmax"] = encode_scalar(r.Xmax);
2765 out["MK"] = encode_scalar(r.MK);
2766 return out;
2767 }
2768};
2769
2770
2771
2772using Invoker = std::function<Json(const ArithSpec&, Args&)>;
2773
2774template <class Op>
2775Invoker make() {
2776 return [](const ArithSpec& s, Args& a) { return run_at<Op>(s, a); };
2777}
2778
2779const std::map<std::string, Invoker>& dispatch_table() {
2780 static const std::map<std::string, Invoker> table = {
2781 {"pfqn_ca", make<OpPfqnCa>()},
2782 {"pfqn_conv", make<OpPfqnConv>()},
2783 {"pfqn_recal", make<OpPfqnRecal>()},
2784 {"pfqn_gld", make<OpPfqnGld>()},
2785 {"pfqn_mva", make<OpPfqnMva>()},
2786 {"pfqn_comom", make<OpPfqnComom>()},
2787 {"pfqn_comomrm", make<OpPfqnComomrm>()},
2788 {"pfqn_comomrm_orig", make<OpPfqnComomrmOrig>()},
2789 {"pfqn_comomrm_ms", make<OpPfqnComomrmMs>()},
2790 {"pfqn_procomom", make<OpPfqnProcomom>()},
2791 {"ctmc_solve", make<OpCtmcSolve>()},
2792 {"dtmc_solve", make<OpDtmcSolve>()},
2793 {"ctmc_makeinfgen", make<OpCtmcMakeinfgen>()},
2794 {"sim_vonneumann", make<OpSimVonneumann>()},
2795 {"sim_shapirowilk", make<OpSimShapirowilk>()},
2796 {"sim_sts_quantile_areas", make<OpSimStsQuantileAreas>()},
2797 {"sim_fquest", make<OpSimFquest>()},
2798 {"sim_firquest", make<OpSimFirquest>()},
2799 {"qsys_bmapphnn_retrial", make<OpQsysBmapphnnRetrial>()},
2800 {"aoi_fcfs_dm1", make<NeedsTranscendental<OpAoiFcfsDm1>>()},
2801 {"aoi_fcfs_gim1", make<NeedsTranscendental<OpAoiFcfsGim1>>()},
2802 {"aoi_fcfs_md1", make<NeedsTranscendental<OpAoiFcfsMd1>>()},
2803 {"aoi_fcfs_mgi1", make<NeedsTranscendental<OpAoiFcfsMgi1>>()},
2804 {"aoi_fcfs_mm1", make<OpAoiFcfsMm1>()},
2805 {"aoi_lcfsd_gim1", make<NeedsTranscendental<OpAoiLcfsdGim1>>()},
2806 {"aoi_lcfsd_mgi1", make<NeedsTranscendental<OpAoiLcfsdMgi1>>()},
2807 {"aoi_lcfspr_dm1", make<NeedsTranscendental<OpAoiLcfsprDm1>>()},
2808 {"aoi_lcfspr_gim1", make<NeedsTranscendental<OpAoiLcfsprGim1>>()},
2809 {"aoi_lcfspr_md1", make<NeedsTranscendental<OpAoiLcfsprMd1>>()},
2810 {"aoi_lcfspr_mgi1", make<NeedsTranscendental<OpAoiLcfsprMgi1>>()},
2811 {"aoi_lcfspr_mm1", make<OpAoiLcfsprMm1>()},
2812 {"aoi_lcfss_gim1", make<NeedsTranscendental<OpAoiLcfssGim1>>()},
2813 {"aoi_lcfss_mgi1", make<NeedsTranscendental<OpAoiLcfssMgi1>>()},
2814 {"fj_bounds", make<OpFjBounds>()},
2815 {"fj_char_max", make<NeedsTranscendental<OpFjCharMax>>()},
2816 {"fj_gk_bound", make<NeedsTranscendental<OpFjGkBound>>()},
2817 {"fj_harmonic", make<OpFjHarmonic>()},
2818 {"fj_quantile", make<NeedsTranscendental<OpFjQuantile>>()},
2819 {"fj_ordstat_exp", make<OpFjOrdstatExp>()},
2820 {"fj_quorum_moments", make<NeedsTranscendental<OpFjQuorumMoments>>()},
2821 {"fj_respt_2way", make<OpFjResptTwoway>()},
2822 {"fj_respt_nt", make<OpFjResptNt>()},
2823 {"fj_respt_varki", make<OpFjResptVarki>()},
2824 {"fj_respt_vm", make<OpFjResptVm>()},
2825 {"fj_rmax", make<OpFjRmax>()},
2826 {"fj_rmax_erlang", make<NeedsTranscendental<OpFjRmaxErlang>>()},
2827 {"fj_rmax_evd", make<NeedsTranscendental<OpFjRmaxEvd>>()},
2828 {"fj_sm_tput", make<OpFjSmTput>()},
2829 {"fj_synch_delay", make<OpFjSynchDelay>()},
2830 {"fj_xmax_2", make<OpFjXmax2>()},
2831 {"fj_xmax_approx", make<NeedsTranscendental<OpFjXmaxApprox>>()},
2832 {"fj_xmax_emma", make<NeedsTranscendental<OpFjXmaxEmma>>()},
2833 {"fj_xmax_erlang", make<NeedsTranscendental<OpFjXmaxErlang>>()},
2834 {"fj_xmax_exp", make<OpFjXmaxExp>()},
2835 {"fj_xmax_hyperexp", make<OpFjXmaxHyperexp>()},
2836 {"fj_xmax_normal", make<NeedsTranscendental<OpFjXmaxNormal>>()},
2837 {"fj_xmax_pareto", make<NeedsTranscendental<OpFjXmaxPareto>>()},
2838 {"ctmc_courtois", make<NeedsTranscendental<OpCtmcCourtois>>()},
2839 {"ctmc_fau", make<NeedsTranscendental<OpCtmcFau>>()},
2840 {"ctmc_foxglynn", make<NeedsTranscendental<OpCtmcFoxglynn>>()},
2841 {"ctmc_saddlepoint", make<NeedsTranscendental<OpCtmcSaddlepoint>>()},
2842 {"ctmc_bicgstab", make<NeedsTranscendental<OpCtmcBicgstab>>()},
2843 {"ctmc_bicgstab_multi", make<NeedsTranscendental<OpCtmcBicgstabMulti>>()},
2844 {"ctmc_gmres", make<NeedsTranscendental<OpCtmcGmres>>()},
2845 {"ctmc_gmres_multi", make<NeedsTranscendental<OpCtmcGmresMulti>>()},
2846 {"ctmc_kms", make<NeedsTranscendental<OpCtmcKms>>()},
2847 {"ctmc_multi", make<NeedsTranscendental<OpCtmcMulti>>()},
2848 {"ctmc_randomization", make<OpCtmcRandomization>()},
2849 {"ctmc_sens", make<OpCtmcSens>()},
2850 {"ctmc_solve_reducible", make<OpCtmcSolveReducible>()},
2851 {"ctmc_solve_reducible_blkdecomp", make<OpCtmcSolveReducibleBlkdecomp>()},
2852 {"ctmc_takahashi", make<NeedsTranscendental<OpCtmcTakahashi>>()},
2853 {"ctmc_transient", make<NeedsTranscendental<OpCtmcTransient>>()},
2854 {"ctmc_transient_sens", make<NeedsTranscendental<OpCtmcTransientSens>>()},
2855 {"dtmc_makestochastic", make<OpDtmcMakestochastic>()},
2856 {"dtmc_solve_reducible", make<OpDtmcSolveReducible>()},
2857 {"moment_binomial_from_factorial", make<OpMomentBinomialFromFactorial>()},
2858 {"moment_binomial_from_negbinomial", make<OpMomentBinomialFromNegbinomial>()},
2859 {"moment_binotrans", make<OpMomentBinotrans>()},
2860 {"moment_binotransinv", make<OpMomentBinotransinv>()},
2861 {"moment_central_from_raw", make<OpMomentCentralFromRaw>()},
2862 {"moment_factorial_from_binomial", make<OpMomentFactorialFromBinomial>()},
2863 {"moment_factorial_from_raw", make<OpMomentFactorialFromRaw>()},
2864 {"moment_factorial_from_upfactorial", make<OpMomentFactorialFromUpfactorial>()},
2865 {"moment_lah", make<OpMomentLah>()},
2866 {"moment_negbinomial_from_binomial", make<OpMomentNegbinomialFromBinomial>()},
2867 {"moment_negbinomial_from_upfactorial", make<OpMomentNegbinomialFromUpfactorial>()},
2868 {"moment_raw_from_central", make<OpMomentRawFromCentral>()},
2869 {"moment_raw_from_factorial", make<OpMomentRawFromFactorial>()},
2870 {"moment_raw_from_upfactorial", make<OpMomentRawFromUpfactorial>()},
2871 {"moment_stirling1", make<OpMomentStirling1>()},
2872 {"moment_stirling2", make<OpMomentStirling2>()},
2873 {"moment_stirlingcycle", make<OpMomentStirlingcycle>()},
2874 {"moment_upfactorial_from_factorial", make<OpMomentUpfactorialFromFactorial>()},
2875 {"moment_upfactorial_from_negbinomial", make<OpMomentUpfactorialFromNegbinomial>()},
2876 {"moment_upfactorial_from_raw", make<OpMomentUpfactorialFromRaw>()},
2877 {"stronglyconncomp", make<OpStronglyConnComp>()},
2878 {"qsys_dmc", make<NeedsTranscendental<OpQsysDmc>>()},
2879 {"dqsys_geogeo1", make<OpQsysGeoGeo1>()},
2880 {"dqsys_geoxgeo1", make<OpQsysGeoxGeo1>()},
2881 {"qsys_gg1", make<NeedsTranscendental<OpQsysGg1>>()},
2882 {"qsys_gig1_approx_allencunneen", make<OpQsysGig1AllenCunneen>()},
2883 {"qsys_gig1_approx_gelenbe", make<NeedsTranscendental<OpQsysGig1Gelenbe>>()},
2884 {"qsys_gig1_approx_heyman", make<OpQsysGig1Heyman>()},
2885 {"qsys_gig1_approx_kimura", make<OpQsysGig1Kimura>()},
2886 {"qsys_gig1_approx_klb", make<NeedsTranscendental<OpQsysGig1Klb>>()},
2887 {"qsys_gig1_approx_kobayashi", make<NeedsTranscendental<OpQsysGig1Kobayashi>>()},
2888 {"qsys_gig1_approx_marchal", make<OpQsysGig1Marchal>()},
2889 {"qsys_gig1_approx_myskja", make<NeedsTranscendental<OpQsysGig1Myskja>>()},
2890 {"qsys_gig1_approx_myskja2", make<NeedsTranscendental<OpQsysGig1Myskja2>>()},
2891 {"qsys_gig1_lbnd", make<OpQsysGig1Lbnd>()},
2892 {"qsys_gig1_ubnd_kingman", make<OpQsysGig1UbndKingman>()},
2893 {"qsys_gigk_approx", make<NeedsTranscendental<OpQsysGigkApprox>>()},
2894 {"qsys_gigk_approx_cosmetatos", make<NeedsTranscendental<OpQsysGigkCosmetatos>>()},
2895 {"qsys_gigk_approx_kingman", make<OpQsysGigkKingman>()},
2896 {"qsys_gigk_approx_whitt", make<NeedsTranscendental<OpQsysGigkWhitt>>()},
2897 {"qsys_erlanga", make<NeedsTranscendental<OpQsysErlangA>>()},
2898 {"qsys_ggnm_diffusion", make<NeedsTranscendental<OpQsysGgnmDiffusion>>()},
2899 {"qsys_gig1_bnds_extremal", make<NeedsTranscendental<OpQsysGig1BndsExtremal>>()},
2900 {"qsys_mmk_qed", make<NeedsTranscendental<OpQsysMmkQed>>()},
2901 {"qsys_mmk_qed_alpha", make<NeedsTranscendental<OpQsysMmkQedAlpha>>()},
2902 {"qsys_mmk_qed_staffing", make<NeedsTranscendental<OpQsysMmkQedStaffing>>()},
2903 {"qsys_gigk_rqt", make<NeedsTranscendental<OpQsysGigkRqt>>()},
2904 {"qsys_gig1_rqt", make<NeedsTranscendental<OpQsysGig1Rqt>>()},
2905 {"qsys_gigk_rqt_gamma", make<NeedsTranscendental<OpQsysGigkRqtGamma>>()},
2906 {"qsys_gm1", make<OpQsysGm1>()},
2907 {"qsys_mapd1", make<NeedsTranscendental<OpQsysMapd1>>()},
2908 {"qsys_mapdc", make<NeedsTranscendental<OpQsysMapdc>>()},
2909 {"qsys_mapg1", make<NeedsTranscendental<OpQsysMapg1>>()},
2910 {"qsys_mapm1", make<NeedsTranscendental<OpQsysMapm1>>()},
2911 {"qsys_mapmap1", make<NeedsTranscendental<OpQsysMapmap1>>()},
2912 {"qsys_mapmc", make<NeedsTranscendental<OpQsysMapmc>>()},
2913 {"qsys_mapph1", make<NeedsTranscendental<OpQsysMapph1>>()},
2914 {"qsys_mapphc", make<NeedsTranscendental<OpQsysMapphc>>()},
2915 {"qsys_mg1", make<OpQsysMg1>()},
2916 {"qsys_mg1_fb", make<NeedsTranscendental<OpQsysMg1Fb>>()},
2917 {"qsys_mg1_lrpt", make<NeedsTranscendental<OpQsysMg1Lrpt>>()},
2918 {"qsys_mg1_prio", make<OpQsysMg1Prio>()},
2919 {"qsys_mg1_psjf", make<NeedsTranscendental<OpQsysMg1Psjf>>()},
2920 {"qsys_mg1_setf", make<NeedsTranscendental<OpQsysMg1Setf>>()},
2921 {"qsys_mg1_srpt", make<NeedsTranscendental<OpQsysMg1Srpt>>()},
2922 {"qsys_mg1k_loss_mgs", make<NeedsTranscendental<OpQsysMg1kLossMgs>>()},
2923 {"qsys_mginf", make<NeedsTranscendental<OpQsysMginf>>()},
2924 {"qsys_mm1", make<OpQsysMm1>()},
2925 {"qsys_mm1_dps", make<NeedsTranscendental<OpQsysMm1Dps>>()},
2926 {"qsys_mm1k_loss", make<OpQsysMm1kLoss>()},
2927 {"qsys_mmcc_retrial_fp", make<NeedsTranscendental<OpQsysMmccRetrialFp>>()},
2928 {"qsys_mgck_sync", make<NeedsTranscendental<OpQsysMgckSync>>()},
2929 {"qsys_mmck", make<OpQsysMmck>()},
2930 {"qsys_mmk", make<OpQsysMmk>()},
2931 {"qsys_mxm1", make<OpQsysMxm1>()},
2932 {"qsys_phm1", make<NeedsTranscendental<OpQsysPhm1>>()},
2933 {"qsys_phmc", make<NeedsTranscendental<OpQsysPhmc>>()},
2934 {"qsys_phph1", make<NeedsTranscendental<OpQsysPhph1>>()},
2935 };
2936 return table;
2937}
2938
2939std::string join(const std::vector<std::string>& v, const char* sep) {
2940 std::string s;
2941 for (std::size_t i = 0; i < v.size(); ++i) {
2942 if (i) s += sep;
2943 s += v[i];
2944 }
2945 return s;
2946}
2947
2948} // namespace
2949
2950std::vector<std::string> api_exposed_functions() {
2951 std::vector<std::string> names;
2952 for (const auto& kv : dispatch_table()) names.push_back(kv.first);
2953 return names; // std::map already orders them
2954}
2955
2956bool api_is_exposed(const std::string& name) {
2957 return dispatch_table().find(name) != dispatch_table().end();
2958}
2959
2960Json api_invoke(const std::string& name, const std::string& arith, const Json& args) {
2961 const ApiEntry* entry = find_api(name);
2962 if (entry == nullptr)
2963 throw UnsupportedError(ErrorKind{"Api"}, "API function '" + name +
2964 "' is not ported to C++ yet (--list-api shows what is).");
2965
2966 const ArithSpec spec = parse_arith(arith);
2967
2968 // arithmetic-gate-before-exposure-gate rationale: see _kb/14-cpp-multiprecision.md
2969 if (!api_supports(*entry, spec.mode)) {
2970 std::vector<std::string> modes;
2971 for (Arith m : entry->arith) modes.push_back(arith_name(m));
2972 throw UnsupportedError(ErrorKind{"Arithmetic"}, "API function '" + name + "' does not support --arith " +
2973 spec.str() + "; it supports: " + join(modes, ", ") + ".");
2974 }
2975
2976 auto it = dispatch_table().find(name);
2977 if (it == dispatch_table().end()) {
2978 // The two api domains that take a MODEL, not matrices, can never be
2979 // reached from here whatever the port does next, and telling a caller
2980 // to wait for an exposure that will never come is worse than telling it
2981 // nothing: it names a route that exists instead of the one that does.
2982 if (entry->domain == "sn" || entry->domain == "lqn")
2983 throw UnsupportedError(ErrorKind{"Api"},
2984 "API function '" + name +
2985 "' takes a NetworkStruct or a LayeredNetworkStruct, not matrices, so it has no "
2986 "named-JSON-argument form and --api cannot carry it in any future version. Reach "
2987 "the model-level answer with -s <solver> -a <analysis>, or call it from the "
2988 "library.");
2989 throw UnsupportedError(ErrorKind{"Api"},
2990 "API function '" + name +
2991 "' is ported to C++ but not yet exposed over --api; it is reachable from the library "
2992 "and its tests only. Exposed over --api so far: " +
2993 join(api_exposed_functions(), ", ") + ".");
2994 }
2995
2996 Args reader(args, name);
2997 Json out;
2998 out["function"] = name;
2999 out["arith"] = spec.str();
3000 out["results"] = it->second(spec, reader);
3001 return out;
3002}
3003
3004// ---------------------------------------------------------------------------
3005// Readable rendering
3006// ---------------------------------------------------------------------------
3007
3008namespace {
3009
3010void render_value(std::ostringstream& os, const Json& v, const std::string& indent) {
3011 // readable-vs-JSON rendering consistency: see _kb/14-cpp-multiprecision.md
3012 if (v.is_object() && v.contains("double")) {
3013 os << v["double"].dump();
3014 if (v.contains("num"))
3015 os << " = " << v["num"].get<std::string>() << " / " << v["den"].get<std::string>();
3016 else if (v.contains("dec"))
3017 os << " = " << v["dec"].get<std::string>();
3018 os << "\n";
3019 return;
3020 }
3021 if (v.is_array()) {
3022 if (!v.empty() && v[0].is_array()) {
3023 os << "\n";
3024 for (const Json& row : v) {
3025 os << indent << " ";
3026 for (std::size_t k = 0; k < row.size(); ++k) {
3027 if (k) os << " ";
3028 if (row[k].is_object() && row[k].contains("double"))
3029 os << row[k]["double"].dump();
3030 else
3031 os << row[k].dump();
3032 }
3033 os << "\n";
3034 }
3035 return;
3036 }
3037 os << "[";
3038 for (std::size_t k = 0; k < v.size(); ++k) {
3039 if (k) os << ", ";
3040 if (v[k].is_object() && v[k].contains("double"))
3041 os << v[k]["double"].dump();
3042 else
3043 os << v[k].dump();
3044 }
3045 os << "]\n";
3046 return;
3047 }
3048 os << v.dump() << "\n";
3049}
3050
3051} // namespace
3052
3053std::string api_render_readable(const Json& result) {
3054 std::ostringstream os;
3055 os << result["function"].get<std::string>() << " (arith: "
3056 << result["arith"].get<std::string>() << ")\n";
3057 const Json& res = result["results"];
3058 for (auto it = res.begin(); it != res.end(); ++it) {
3059 os << " " << it.key() << " = ";
3060 render_value(os, it.value(), " ");
3061 }
3062 return os.str();
3063}
3064
3065} // namespace reg
3066} // namespace line
Mean, variance and peak Age of Information of a D/M/1 FCFS queue.
Mean Age of Information, its transform and the peak age of a GI/M/1 FCFS queue.
Mean, variance and peak Age of Information of an M/D/1 FCFS queue.
Mean Age of Information, its transform and the peak age of an M/GI/1 FCFS queue.
Mean, variance and peak Age of Information of an M/M/1 FCFS queue.
Mean Age of Information, its transform and the peak age of a GI/M/1 non-preemptive LCFS queue with di...
Mean Age of Information, its transform and the peak age of an M/GI/1 non-preemptive LCFS queue with d...
Mean, variance and peak Age of Information of a D/M/1 preemptive LCFS queue.
Mean Age of Information, its transform and the peak age of a GI/M/1 preemptive LCFS queue.
Mean, variance and peak Age of Information of an M/D/1 preemptive LCFS queue.
Mean Age of Information, its transform and the peak age of an M/GI/1 preemptive LCFS queue.
Mean, variance and peak Age of Information of an M/M/1 preemptive LCFS queue.
Mean Age of Information, its transform and the peak age of a GI/M/1 non-preemptive LCFS queue with se...
Mean Age of Information, its transform and the peak age of an M/GI/1 non-preemptive LCFS queue with s...
Laplace-Stieltjes transform of a deterministic (constant) distribution.
Laplace-Stieltjes transform of an Erlang-k distribution.
Laplace-Stieltjes transform of an exponential distribution.
Laplace-Stieltjes transform of a phase-type distribution PH(alpha, T).
#define LINE_MOMENT_VEC(OpName, fn, in_key, out_key)
#define LINE_AOI_GIM1_TWO(OpName, fn)
#define LINE_QSYS_MG1_DISC(OpName, fn, ResultT)
The six M/G/1 disciplines, all {W per class, rhohat}.
#define LINE_FJ_K_LAMBDA_MU(OpName, fn, out_key)
The many members returning one bare number, under the reference's name.
#define LINE_QSYS_MYSKJA(OpName, fn)
#define LINE_MOMENT_TRI(OpName, fn, out_key)
The four triangular coefficient matrices, each a function of the order n.
#define LINE_FJ_K_MU(OpName, fn, out_key)
#define LINE_QSYS_GIG1(OpName, fn)
#define LINE_AOI_MGI1_TWO(OpName, fn)
The two non-preemptive LCFS M/GI/1 laws: transform at lambda plus both moments.
#define LINE_AOI_TWO(OpName, fn, k1, k2)
#define LINE_QSYS_GIGK(OpName, fn)
Direct invocation of a single API function from named JSON arguments.
InputError(const std::string &what)
Definition error.h:105
UnsupportedError(const std::string &what)
Definition error.h:119
Named-argument reader over the parsed JSON object.
Definition api_json.h:391
Preconditioned stabilized biconjugate gradients, for the linear systems a generator produces.
Courtois decomposition of a nearly completely decomposable (NCD) CTMC.
Transient distribution of a CTMC by fast adaptive uniformization.
Transient distribution of a CTMC by uniformization with Fox-Glynn Poisson weights.
Restarted GMRES with an ILUT preconditioner, for the linear systems a generator produces.
Restarted GMRES for a block of right-hand sides sharing one coefficient matrix.
Koury-McAllister-Stewart aggregation-disaggregation for a nearly completely decomposable CTMC.
Two-level multigrid aggregation-disaggregation for a nearly completely decomposable CTMC.
Uniformization (randomization) of a CTMC: the embedded DTMC P = I + Q/q.
Saddlepoint approximation of Pr{N(t)=k} for the counting process of a MAP.
Sensitivity of the steady-state distribution of a CTMC to a scalar parameter.
Steady-state distribution of a continuous-time Markov chain.
Limiting distribution of a CTMC whose generator may be reducible.
Limiting distribution of a reducible CTMC by direct block decomposition of the generator.
Takahashi's aggregation-disaggregation for a nearly completely decomposable CTMC.
Transient distribution of a CTMC over a time interval, by integrating the forward equations d pi/dt =...
Sensitivity of the transient distribution of a CTMC to a scalar parameter.
Geo/Geo/1: the discrete-time single-server queue with geometric interarrival and service times.
Geo^X/Geo/1: the discrete-time single-server queue with batch arrivals.
Normalize a non-negative matrix into a stochastic transition matrix.
Equilibrium distribution of a discrete-time Markov chain, and stochastic complementation.
Limiting distribution of a discrete-time Markov chain whose transition matrix may be reducible.
Upper and lower bounds on the mean response time of a K-way fork-join system of M/M/1 branches.
Gravey's characteristic maximum M_K, an upper bound on the expected maximum of K i....
G(K) factors for the standardized-maximum approximation X_K^max ~ mu + sigma G(K).
Harmonic number H_K = sum_{k=1..K} 1/k.
Mean of the k-th smallest of n independent EXPONENTIAL branch completion times, i....
Quantile of the maximum of K i.i.d.
Mean and variance of a k-of-n (quorum) join completion time, from the mean and variance of each branc...
Exact mean response time of a 2-way fork-join system of M/M/1 branches.
Nelson-Tantawi approximation to the mean response time of a K-way fork-join system of M/M/1 branches.
Varki approximation to the mean response time of a K-way fork-join system of M/M/1 branches.
Varma-Makowski light-traffic interpolation for the mean response time of a K-way fork-join system of ...
Pessimistic (independence) fork-join response time: the expected maximum of K independent M/M/1 respo...
Expected maximum of K M/E_k/1 branch response times.
Extreme-value approximation to the maximum of K branch response times, from their mean and standard d...
Saturated (single-message) maximum throughput of a K-way fork-join system with exponential branch ser...
Mean synchronization delay of a 2-way fork-join system of M/M/1 branches, i.e.
Expected maximum of two independent, possibly unequal-rate exponentials.
Two-moment approximation to the expected maximum of K i.i.d.
EMMA (Extreme-value Maximum Moment Approximation) to the expected maximum of K i.i....
Expected maximum of K i.i.d.
Expected maximum of K i.i.d.
Expected maximum of K i.i.d.
Expected maximum and variance of K i.i.d.
Expected maximum and characteristic maximum of K i.i.d.
Binomial moments from falling-factorial moments.
Binomial moments from negative-binomial moments.
Binomial transform with alternating signs.
Inverse binomial transform.
Central moments from raw moments.
Falling-factorial moments from binomial moments.
Factorial moments from raw moments, via the signed Stirling table.
Falling-factorial moments from rising-factorial moments, via the Lah numbers.
Unsigned Lah numbers.
Negative-binomial moments from binomial moments.
Negative-binomial moments from rising-factorial moments.
Raw moments from central moments and the mean.
Raw moments from factorial moments, via the Stirling table of the second kind.
Raw moments from rising-factorial moments.
Signed Stirling numbers of the first kind.
Stirling numbers of the second kind.
Unsigned Stirling numbers of the first kind (cycle numbers), orders 0..n.
Rising-factorial moments from falling-factorial moments, via the Lah numbers.
Rising-factorial moments from negative-binomial moments.
Rising-factorial moments from raw moments, via the cycle numbers.
AoiLstResult< T > aoi_lcfspr_gim1(const Lst< T > &Y_lst, const T &mu, const T &E_Y, const T &E_Y2)
Mean Age of Information, its transform and the peak age of a GI/M/1 preemptive LCFS queue.
std::function< T(const T &)> Lst
A Laplace-Stieltjes transform evaluated at real arguments.
Definition aoi_types.h:41
AoiLstResult< T > aoi_fcfs_gim1(const Lst< T > &Y_lst, const T &mu, const T &E_Y, const T &E_Y2)
Mean Age of Information, its transform and the peak age of a GI/M/1 FCFS queue.
Lst< T > aoi_lst_exp(const T &mu)
Laplace-Stieltjes transform of an exponential distribution.
Definition aoi_lst_exp.h:36
Lst< T > aoi_lst_erlang(unsigned k, const T &mu)
Laplace-Stieltjes transform of an Erlang-k distribution.
Lst< T > aoi_lst_ph(const std::vector< T > &alpha, const Matrix< T > &Tmat)
Laplace-Stieltjes transform of a phase-type distribution PH(alpha, T).
Definition aoi_lst_ph.h:48
AoiLstResult< T > aoi_lcfspr_mgi1(const T &lambda, const Lst< T > &H_lst, const T &E_H)
Mean Age of Information, its transform and the peak age of an M/GI/1 preemptive LCFS queue.
Lst< T > aoi_lst_det(const T &d)
Laplace-Stieltjes transform of a deterministic (constant) distribution.
Definition aoi_lst_det.h:37
AoiLstResult< T > aoi_fcfs_mgi1(const T &lambda, const Lst< T > &H_lst, const T &E_H, const T &E_H2)
Mean Age of Information, its transform and the peak age of an M/GI/1 FCFS queue.
GeoXGeo1Result< T > dqsys_geoxgeo1(const T &a, const T &beta, const T &s, GeoConvention convention=GeoConvention::LAS_DA)
Geo^X/Geo/1: the discrete-time single-server queue with batch arrivals.
GeoGeo1Result< T > dqsys_geogeo1(const T &a, const T &s, GeoConvention convention=GeoConvention::LAS_DA)
Geo/Geo/1: the discrete-time single-server queue with geometric interarrival and service times.
FJCharMaxResult< T > fj_char_max(unsigned K, const T &mu)
Exponential branch.
Definition fj_char_max.h:57
FJBoundsResult< T > fj_bounds(unsigned K, const T &lambda, const T &mu)
Upper and lower bounds on the mean response time of a K-way fork-join system of M/M/1 branches.
Definition fj_bounds.h:44
T fj_rmax_erlang(unsigned K, unsigned k, const T &lambda, const T &mu)
Expected maximum of K M/E_k/1 branch response times.
T fj_synch_delay(const T &lambda, const T &mu)
Mean synchronization delay of a 2-way fork-join system of M/M/1 branches, i.e.
FJGKBoundResult< T > fj_gk_bound(unsigned K)
G(K) factors for the standardized-maximum approximation X_K^max ~ mu.
Definition fj_gk_bound.h:44
T fj_respt_2way(const T &lambda, const T &mu)
Exact mean response time of a 2-way fork-join system of M/M/1 branches.
T fj_xmax_hyperexp(unsigned K, const T &p1, const T &mu1, const T &mu2)
Expected maximum of K i.i.d.
FJNormalMethod
Bracketing methods for the normal-maximum approximation.
Definition fj_types.h:44
T fj_rmax_evd(unsigned K, const T &R, const T &sigma_R, bool calibrated=false)
Extreme-value approximation to the maximum of K branch response times, from their mean and standard d...
Definition fj_rmax_evd.h:45
T fj_xmax_2(const T &lambda1, const T &lambda2)
Expected maximum of two independent, possibly unequal-rate exponentials.
Definition fj_xmax_2.h:39
T fj_xmax_erlang(unsigned K, unsigned k, const T &mu)
Expected maximum of K i.i.d.
FJXmaxApproxResult< T > fj_xmax_approx(unsigned K, const T &mu_X, const T &sigma_X, FJDistType type=FJDistType::Exp)
Two-moment approximation to the expected maximum of K i.i.d.
T fj_ordstat_exp(const std::vector< T > &ri, std::size_t k)
Mean of the k-th smallest of n independent EXPONENTIAL branch completion times, i....
FJDistType
Distribution families for which a G(K) standardized-maximum factor exists.
Definition fj_types.h:41
FJQuorumMomentsResult< T > fj_quorum_moments(const std::vector< T > &branchMeans, const std::vector< T > &branchVars, std::size_t k)
Mean and variance of a k-of-n (quorum) join completion time, from the mean and variance of each branc...
T fj_quantile(unsigned K, const T &q)
Gumbel approximation.
Definition fj_quantile.h:41
FJXmaxParetoResult< T > fj_xmax_pareto(unsigned K, const T &beta, const T &k)
Expected maximum and characteristic maximum of K i.i.d.
T fj_harmonic(unsigned K)
Harmonic number H_K = sum_{k=1..K} 1/k.
Definition fj_harmonic.h:37
FJXmaxNormalResult< T > fj_xmax_normal(unsigned K, const T &mu, const T &sigma, FJNormalMethod method=FJNormalMethod::Johnson)
Expected maximum and variance of K i.i.d.
double delta(double t)
std::string join(const std::vector< std::string > &v, const std::string &sep)
BicgstabResult< T > ctmc_bicgstab(const Matrix< T > &A, const std::vector< T > &b, double tol=1e-12, long maxit=0, const std::vector< T > &x0=std::vector< T >())
Preconditioned stabilized biconjugate gradients, for the linear systems a generator produces.
ReducibleResult< T > dtmc_solve_reducible(const Matrix< T > &P, const std::vector< T > &pin, double zeroColTol=1e-12)
Limiting distribution of a discrete-time Markov chain whose transition matrix may be reducible.
BlkDecompResult< T > ctmc_solve_reducible_blkdecomp(const Matrix< T > &Qin, const std::vector< T > &pin, double reachTol=1e-15, double zeroColTol=1e-12)
Limiting distribution of a reducible CTMC by direct block decomposition of the generator.
SaddlepointMethod
Which term of the steepest-descent expansion to stop at.
@ SADDLEPOINT_DANIELS2
second order, error O(1/K2^2) – the DEFAULT
@ SADDLEPOINT_PLAIN
bare first order, amplitude set to 1
@ SADDLEPOINT_DANIELS
first order with the Perron amplitude, O(1/K2)
Matrix< T > dtmc_makestochastic(const Matrix< T > &Pin)
Normalize a non-negative matrix into a stochastic transition matrix.
Matrix< T > ctmc_makeinfgen(const Matrix< T > &Q)
Set the diagonal so that every row sums to zero (ctmc_makeinfgen).
Definition ctmc_solve.h:58
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
Definition ctmc_solve.h:122
KmsResult< T > ctmc_kms(const Matrix< T > &Q, const std::vector< std::vector< std::size_t > > &MS, std::size_t numSteps)
Koury-McAllister-Stewart aggregation-disaggregation for a nearly completely decomposable CTMC.
Definition ctmc_kms.h:100
GmresMultiResult< T > ctmc_gmres_multi(const Matrix< T > &A, const Matrix< T > &B, double tol=1e-12, long restart=0, long maxit=0)
Restarted GMRES for a block of right-hand sides sharing one coefficient matrix.
TakahashiResult< T > ctmc_takahashi(const Matrix< T > &Q, const std::vector< std::vector< std::size_t > > &MS, std::size_t numSteps, double massTol=1e-14)
Takahashi's aggregation-disaggregation for a nearly completely decomposable CTMC.
ReducibleResult< T > ctmc_solve_reducible(const Matrix< T > &Q, const std::vector< T > &pi0, double zeroColTol=1e-12)
Limiting distribution of a CTMC whose generator may be reducible.
MultiResult< T > ctmc_multi(const Matrix< T > &Q, const std::vector< std::vector< std::size_t > > &MS, const std::vector< std::vector< std::size_t > > &MSS, const T &q)
Two-level multigrid aggregation-disaggregation for a nearly completely decomposable CTMC.
Definition ctmc_multi.h:61
SccResult stronglyconncomp(const Matrix< T > &A)
Strongly connected components of a directed graph, and which of them are recurrent (closed under the ...
BicgstabMultiResult< T > ctmc_bicgstab_multi(const Matrix< T > &A, const Matrix< T > &B, double tol=1e-12, long maxit=0)
Every column of B solved against the SAME equilibration, reordering and ILUT factorization,...
std::vector< T > dtmc_solve(const Matrix< T > &P)
Stationary distribution of a stochastic matrix P.
Definition dtmc_solve.h:106
SaddlepointResult< T > ctmc_saddlepoint(const Matrix< T > &D0, const Matrix< T > &D1, const std::vector< T > &t, const std::vector< long > &k, SaddlepointMethod method=SADDLEPOINT_DANIELS2, const std::vector< T > &pi0=std::vector< T >())
Pr{N(t)=k} over arrays of horizons and counts.
RandomizationResult< T > ctmc_randomization(const Matrix< T > &Q, const T &q)
Uniformization (randomization) of a CTMC: the embedded DTMC P = I + Q/q.
GmresResult< T > ctmc_gmres(const Matrix< T > &A, const std::vector< T > &b, double tol=1e-12, long restart=0, long maxit=0, const std::vector< T > &x0=std::vector< T >())
Restarted GMRES with an ILUT preconditioner, for the linear systems a generator produces.
Definition ctmc_gmres.h:653
TransientSensResult< T > ctmc_transient_sens(const Matrix< T > &Q, const Matrix< T > &dQ, const std::vector< T > &pi0, const T &t0, const T &t1, double rtol=1e-3, double atol=1e-6)
Sensitivity of the transient distribution of a CTMC to a scalar parameter.
FoxGlynnResult< T > ctmc_foxglynn(const std::vector< T > &pi0, const Matrix< T > &Q, const T &t, double tol=1e-12, long maxiter=-1)
Transient distribution of a CTMC by uniformization with Fox-Glynn Poisson weights.
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, bool stiff=false)
Transient distribution of a CTMC over a time interval, by integrating the forward equations d pi/dt =...
CourtoisResult< T > ctmc_courtois(const Matrix< T > &Q, const std::vector< std::vector< std::size_t > > &MS, const T &q)
Courtois decomposition of a nearly completely decomposable (NCD) CTMC.
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
std::vector< T > ctmc_sens(const Matrix< T > &Q, const Matrix< T > &dQ, const std::vector< T > &pi)
Sensitivity of the steady-state distribution of a CTMC to a scalar parameter.
Definition ctmc_sens.h:52
const std::map< std::string, std::vector< std::string > > & table()
std::vector< T > moment_raw_from_central(const std::vector< T > &mc, const T &m1)
m_i = sum_k C(i,k) mc_k m1^(i-k).
NcResult< T > pfqn_recal(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< int > &m0)
RECAL (REcursive CALculation) for the exact normalizing constant of a closed product-form network (Co...
Definition pfqn_recal.h:188
ProcomomResult< T > pfqn_procomom(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const T &atol)
Marginal queue-length distributions of every station.
MvaResult< T > pfqn_mva(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< int > &mi)
Exact Mean Value Analysis for closed product-form networks (Reiser and Lavenberg 1980).
Definition pfqn_mva.h:71
ComomResult< T > pfqn_comomrm_orig(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const T &atol)
Original CoMoM for the finite repairman model (matlab pfqn_comomrm_orig.m).
Definition pfqn_comom.h:285
ComomResult< T > pfqn_comomrm(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, int m)
CoMoM (class-oriented method of moments) for the finite repairman model: one queueing station of mult...
NcResult< T > pfqn_ca(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z)
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
Definition pfqn_ca.h:120
ComomRmResult< T > pfqn_comomrm_ms(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, int m, int S)
CoMoM for the MULTISERVER repairman model: one queueing station with S servers (optionally replicated...
NcResult< T > pfqn_conv(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< CdScaling< T > > &cdscaling)
Multichain convolution algorithm with class-dependent service rates (Sauer 1983, "Computational Algor...
Definition pfqn_conv.h:80
ComomResult< T > pfqn_comom(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const T &atol)
CoMoM on the general basis (matlab pfqn_comom.m).
Definition pfqn_comom.h:108
NcResult< T > pfqn_gld(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &mu)
Exact normalizing constant of a closed product-form network whose stations may be load dependent (gen...
Definition pfqn_gld.h:144
MapMcResult< T > qsys_mapmc(const mam::Map< T > &arrival, const T &mu, unsigned c, std::size_t dist_size)
MAP/M/c by the matrix-geometric solution.
Definition qsys_mapmc.h:104
GigkRqtResult< T > qsys_gigk_rqt(const T &lambda, const T &mu, const T &Gamma_a, const T &Gamma_s, std::size_t k, const T &alpha_a, const T &alpha_s)
Robust Queueing Theory (RQT) worst-case system time of a G/G/k FCFS queue.
MmccRetrialFpResult< T > qsys_mmcc_retrial_fp(const T &lambda, const T &mu, unsigned c, const T &tol, std::size_t maxiter)
M/M/c/c with retrials by the Cohen fixed point.
QsysQedResult< T > qsys_mmk_qed(const T &lambda, const T &mu, unsigned s)
Halfin-Whitt QED approximation for the M/M/s queue, and the square-root staffing rule that inverts it...
QsysResult< T > qsys_mm1(const T &lambda, const T &mu)
Exact mean response time of the M/M/1 queue.
Definition qsys_mm1.h:35
Mm1kLossResult< T > qsys_mm1k_loss(const T &lambda, const T &mu, unsigned K)
Blocking probability of the M/M/1/K queue.
Gig1ExtremalResult< T > qsys_gig1_bnds_extremal(const T &lambda, const T &mu, const T &ca, const T &cs, std::size_t K=4000, std::size_t N=2000, bool skipTight=false)
Extremal two-moment bounds for the GI/GI/1 queue.
@ CONSTANT
the orbit retries at rate alpha whenever it is non-empty
@ LINEAR
each orbiting customer retries at rate alpha (orbit rate i*alpha)
MapMap1Result< T > qsys_phph1(const std::vector< T > &alpha, const Matrix< T > &Tm, const std::vector< T > &beta, const Matrix< T > &S, std::size_t dist_size)
PH/PH/1 by the exact QBD solution of the equivalent MAP/MAP/1 queue.
Definition qsys_phph1.h:78
@ Acyclic
three or more moments
Definition qsys_mapg1.h:112
@ Exponential
one moment, or cv2 exactly 1
Definition qsys_mapg1.h:109
@ Erlang
0 < cv2 < 1, or the cv2 <= 0 branch
Definition qsys_mapg1.h:110
QsysGgnmResult< T > qsys_ggnm_diffusion(const T &lambda, const T &mu, unsigned n, double m, const T &ca, const T &cs, const std::function< T(const T &)> &serviceCcdf=std::function< T(const T &)>(), double tol=1e-12, std::size_t panels=4000)
Diffusion approximation for the G/GI/n/m queue.
MapG1Result< T > qsys_mapg1(const mam::Map< T > &arrival, const std::vector< T > &moments, std::size_t dist_size, std::size_t numQLMoms, std::size_t numSTMoms)
The MAP/G/1 FCFS queue.
Definition qsys_mapg1.h:343
MxM1Result< T > qsys_mxm1(const T &lambda_batch, const T &mu, const T &E_X, const T &E_X2)
M^X/M/1: the batch-arrival queue with exponential service.
Definition qsys_mxm1.h:59
QsysAbandonResult< T > qsys_erlanga(const T &lambda, const T &mu, const T &theta, unsigned s, double r=std::numeric_limits< double >::infinity(), const MgisrgiOptions &opts=MgisrgiOptions())
Exact analysis of the Erlang A model M/M/s/r+M.
T qsys_gm1(const T &sigma, const T &mu)
Exact mean response time of the G/M/1 queue.
Definition qsys_gm1.h:40
QsysQedStaffingResult< T > qsys_mmk_qed_staffing(const T &lambda, const T &mu, const T &target, QedCriterion crit=QedCriterion::Delay, const T &deadline=num_traits< T >::from_int(0), const T &level=num_traits< T >::from_int(0), bool exact=false)
Square-root staffing of the M/M/s queue.
MmckResult< T > qsys_mmck(const T &lambda, const T &mu, unsigned c, unsigned K)
Exact analysis of the M/M/c/K queue (truncated Erlang form).
Definition qsys_mmck.h:63
GigkRqtResult< T > qsys_gig1_rqt(const T &lambda, const T &mu, const T &Gamma_a, const T &Gamma_s, const T &alpha_a, const T &alpha_s)
Robust Queueing Theory (RQT) worst-case system time of a G/G/1 FCFS queue, the single-server case of ...
QedCriterion
Which target the staffing rule is asked to meet.
PhM1Result< T > qsys_phm1(const std::vector< T > &alpha, const Matrix< T > &Tm, const T &mu, const T &tol)
Exact PH/M/1, the GI/M/1 queue with phase-type interarrival times.
Definition qsys_phm1.h:92
Mm1DpsResult< T > qsys_mm1_dps(const std::vector< T > &lambda, const std::vector< T > &mu, const std::vector< T > &w, const T &tol, unsigned maxCutoff)
Multiclass M/M/1 under DPS (discriminatory processor sharing), solved numerically on the truncated po...
MapD1Result< T > qsys_mapd1(const mam::Map< T > &arrival, const T &s, std::size_t dist_size, unsigned max_arrivals, std::size_t max_levels, const T &tol, std::size_t num_steps)
MAP/D/1 by the exact embedded M/G/1-type chain.
Definition qsys_mapd1.h:330
T qsys_mmk_qed_alpha(const T &beta)
The Halfin-Whitt delay-probability function alpha(beta); 1 at beta <= 0.
QsysResult< T > qsys_mg1(const T &lambda, const T &mu, const T &cs)
Exact mean response time of the M/G/1 queue (Pollaczek-Khinchine).
Definition qsys_mg1.h:39
MapPhcResult< T > qsys_mapphc(const mam::Map< T > &arrival, const std::vector< T > &alpha, const Matrix< T > &S, unsigned c, std::size_t dist_size, std::size_t num_w_moms, const std::vector< T > &w_points)
MAP/PH/c FCFS, exactly.
Mg1kLossMgsResult< T > qsys_mg1k_loss_mgs(const T &lambda, const T &mu, const T &mu_scv, unsigned K)
MacGregor Smith's closed-form approximation of the M/G/1/K loss probability.
T qsys_gigk_rqt_gamma(const T &rho, const T &mu, const T &Gamma_a, const T &sigma_s, std::size_t k, const T &alpha_a, const std::string &regime="independent")
Service variability parameter of the Robust Queueing Theory (RQT) framework.
QsysResult< T > qsys_mmk(const T &lambda, const T &mu, unsigned k)
Exact mean response time of the M/M/k queue (Erlang-C).
Definition qsys_mmk.h:60
BmapPhNnRetrialResult< T > qsys_bmapphnn_retrial(const std::vector< Matrix< T > > &D, const std::vector< T > &beta, const Matrix< T > &S, int N, const T &alpha, const T &gamma, const T &p, const std::vector< long > &R, const BmapPhNnRetrialOptions &opt)
The BMAP/PH/N/N bufferless retrial queue.
QsysResult< T > qsys_gg1(const T &lambda, const T &mu, const T &ca2, const T &cs2)
G/G/1 dispatcher: exact where a two-moment description determines the answer, Allen-Cunneen otherwise...
Definition qsys_gg1.h:118
MginfResult< T > qsys_mginf(const T &lambda, const T &mu)
Exact solution of the M/G/infinity queue.
Definition qsys_mginf.h:53
MapMap1Result< T > qsys_mapmap1(const mam::Map< T > &arrival, const mam::Map< T > &service, std::size_t dist_size, std::size_t numQLMoms, std::size_t numSTMoms)
MAP/MAP/1 as the reference solves it: the service MAP collapsed to its PH marginal (mapToPh),...
MgckSyncResult< T > qsys_mgck_sync(const T &lambda, std::size_t c, std::size_t K, const ServiceLaw< T > &svc, const std::string &variant, std::size_t nphases, const T &tol, std::size_t nmaxCap)
Synchronous approximation of the M/G/c/K queue with dual bounds.
MapMcResult< T > qsys_mapm1(const mam::Map< T > &arrival, const T &mu, std::size_t dist_size)
MAP/M/1 by the matrix-geometric solution, i.e.
Definition qsys_mapm1.h:77
MapMap1Result< T > qsys_mapph1(const mam::Map< T > &arrival, const std::vector< T > &sigma, const Matrix< T > &S, std::size_t dist_size)
MAP/PH/1 by the exact QBD solution of the equivalent MAP/MAP/1 queue.
DmcResult< T > qsys_dmc(const T &lambda, const T &mu, unsigned c, unsigned truncation, unsigned quadSteps)
D/M/c: deterministic interarrival times, exponential service.
Definition qsys_dmc.h:122
PhMcResult< T > qsys_phmc(const std::vector< T > &alpha, const Matrix< T > &Tm, const T &mu, unsigned c, unsigned maxIter, const T &tol)
Exact PH/M/c by Neuts' matrix-geometric method.
Definition qsys_phmc.h:97
MapDcResult< T > qsys_mapdc(const mam::Map< T > &arrival, const T &s, unsigned c, std::size_t dist_size, unsigned max_arrivals, std::size_t max_levels, const T &tol, std::size_t num_steps)
MAP/D/c by Crommelin's exact embedded lattice chain.
Definition qsys_mapdc.h:157
nlohmann::json Json
Definition api_json.h:63
std::string api_render_readable(const Json &result)
Human-readable rendering of the object api_invoke returns, for -o readable.
ArithSpec parse_arith(const std::string &text)
Parse –arith.
std::vector< T > vector_from_json(const Json &j, const std::string &where)
A flat numeric vector: 1-D array, or a 1-row / 1-column 2-D array.
Definition api_json.h:255
Json encode_vector(const std::vector< T > &v)
Definition api_json.h:333
std::vector< std::string > api_exposed_functions()
Names exposed over this boundary, sorted; a subset of the registry.
Json encode_matrix(const Matrix< T > &m)
Definition api_json.h:340
std::string decimal_text(const Json &j, const std::string &where)
The decimal literal behind a JSON scalar, per the policy in the file header.
Definition api_json.h:199
Json encode_scalar(const T &v)
Definition api_json.h:328
Json encode_count(std::size_t n)
A count.
Definition api_json.h:378
bool api_is_exposed(const std::string &name)
True when the named function has a dispatch entry.
Json api_invoke(const std::string &name, const std::string &arith, const Json &args)
Invoke one API function.
QuestResult< T > sim_fquest(const std::vector< T > &Y, double p, double alpha=0.05, const QuestOptions &options=QuestOptions())
Fixed-sample-size confidence interval for a steady-state quantile.
Definition sim_fquest.h:130
QuestResult< T > sim_firquest(const std::vector< std::vector< T > > &Y, double p, double alpha=0.05, const QuestOptions *options=nullptr)
Fixed-sample-size quantile interval from independent replications.
StsQuantileStats< T > sim_sts_quantile_areas(const std::vector< T > &Y, std::size_t b, std::size_t m, double p, double weight=std::sqrt(12.0))
Standardized time series areas of the batched quantile process.
VonNeumannResult< T > sim_vonneumann(const std::vector< T > &x, double alpha=0.05)
Von Neumann ratio test for randomness of a sequence.
ShapiroWilkResult< T > sim_shapirowilk(const std::vector< T > &x, double alpha=0.05)
Shapiro-Wilk test for univariate normality.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Arith
Definition registry.h:26
const char * arith_name(Arith a)
Definition registry.h:28
const ApiEntry * find_api(const std::string &name)
Definition registry.h:1741
bool api_supports(const ApiEntry &e, Arith a)
Definition registry.h:1748
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
CoMoM (class-oriented method of moments), the general basis formulation, and the original repairman-m...
CoMoM (class-oriented method of moments) for the finite repairman model: one queueing station of mult...
CoMoM for the MULTISERVER repairman model: one queueing station with S servers (optionally replicated...
Multichain convolution algorithm with class-dependent service rates (Sauer 1983, "Computational Algor...
Exact normalizing constant of a closed product-form network whose stations may be load dependent (gen...
Exact Mean Value Analysis for closed product-form networks (Reiser and Lavenberg 1980).
ProCoMoM: marginal queue-length probabilities of a closed multiclass product-form network by the clas...
RECAL (REcursive CALculation) for the exact normalizing constant of a closed product-form network (Co...
The BMAP/PH/N/N bufferless retrial queue with flexible retrial admission control.
D/M/c: deterministic interarrival times, exponential service.
G/G/1 dispatcher: exact where a two-moment description determines the answer, Allen-Cunneen otherwise...
Diffusion approximation for the G/GI/n/m queue.
Allen-Cunneen approximation of the mean response time of a G/I/G/1 queue.
Gelenbe diffusion approximation with instantaneous-return boundary.
Heyman approximation of the mean response time of a G/I/G/1 queue.
Kimura diffusion-interpolation approximation for the G/I/G/1 queue.
Kraemer and Langenbach-Belz approximation for the G/I/G/1 queue.
Kobayashi diffusion approximation for the G/I/G/1 queue.
Marchal approximation of the mean response time of a G/I/G/1 queue.
Myskja's enhanced third-moment approximation of the mean response time of a G/I/G/1 queue.
Myskja's third-moment approximation of the mean response time of a G/I/G/1 queue.
Extremal two-moment bounds for the GI/GI/1 queue.
Fundamental lower bound on the mean response time of a G/G/1 queue.
Robust Queueing Theory (RQT) worst-case system time of a G/G/1 FCFS queue, the single-server case of ...
Kingman upper bound on the mean waiting time of a G/G/1 queue.
Default G/I/G/k approximation of the mean response time.
Cosmetatos / Page interpolation approximation for the GI/G/k queue.
Kingman (Lee-Longton) scaling of the exact M/M/k waiting time.
Whitt (1993) approximation for the GI/G/k queue, eqs.
Robust Queueing Theory (RQT) worst-case system time of a G/G/k FCFS queue.
Service variability parameter of the Robust Queueing Theory (RQT) framework.
Exact mean response time of the G/M/1 queue.
The MAP/D/1 FCFS queue: deterministic service of length s fed by a Markovian arrival process.
The MAP/D/c FCFS queue: c servers, deterministic service of length s, fed by a Markovian arrival proc...
The MAP/G/1 FCFS queue, by moment-matching the general service time to a phase-type distribution.
The MAP/M/1 FCFS queue, the single-server case of MAP/M/c.
The MAP/MAP/1 FCFS queue: mean number in system, waiting time, sojourn time, utilization and the queu...
The MAP/M/c FCFS queue: c identical exponential servers of rate mu fed by a Markovian arrival process...
The MAP/PH/1 FCFS queue.
The MAP/PH/c FCFS queue, solved exactly.
Exact mean response time of the M/G/1 queue (Pollaczek-Khinchine).
M/G/1 under FB (feedback), also called LAS (least attained service).
M/G/1 under LRPT (longest remaining processing time).
M/G/1 with non-preemptive head-of-line priorities: per-class mean response times from the Cobham/Klei...
M/G/1 under PSJF (preemptive shortest job first).
M/G/1 under SETF (shortest elapsed time first), the non-preemptive counterpart of FB/LAS.
M/G/1 under SRPT (shortest remaining processing time), by the Schrage-Miller formula.
MacGregor Smith's closed-form approximation of the M/G/1/K loss probability.
Synchronous approximation of the M/G/c/K queue with dual bounds (German, EPEW 2025).
Exact solution of the M/G/infinity queue.
Engineering solution of the call-center model M/GI/s/r+GI.
Exact mean response time of the M/M/1 queue.
Multiclass M/M/1 under DPS (discriminatory processor sharing), solved numerically on the truncated po...
Blocking probability of the M/M/1/K queue.
Fixed-point approximation for the M/M/c/c retrial queue.
Exact analysis of the M/M/c/K queue (truncated Erlang form).
Exact mean response time of the M/M/k queue (Erlang-C).
Halfin-Whitt QED approximation for the M/M/s queue, and the square-root staffing rule that inverts it...
M^X/M/1: the batch-arrival queue with exponential service.
Exact PH/M/1, the GI/M/1 queue with phase-type interarrival times.
Exact PH/M/c by Neuts' matrix-geometric method.
The PH/PH/1 FCFS queue.
Fixed-sample-size quantile interval from independent replications.
Fixed-sample-size confidence interval for a steady-state quantile.
Options of the QUEST procedures, with the published FQUEST defaults.
Shapiro-Wilk test for univariate normality.
Standardized time series areas of the batched quantile process.
Von Neumann ratio test for randomness of a sequence.
Strongly connected components of a directed graph, and which of them are recurrent (closed under the ...
std::vector< Arith > arith
arithmetic modes this function is instantiated for
Definition registry.h:40
std::string domain
api domain, e.g. "pfqn"
Definition registry.h:39
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
static double to_double(const double &v)
Definition number.h:137
static ServiceLaw deterministic(const T &d)
Constant service time d.
static ServiceLaw phase_type(const std::vector< T > &alpha, const Matrix< T > &Tmat)
Phase type (alpha, Tmat).
static ServiceLaw gamma(const T &shape, const T &scale)
Gamma(shape, scale).
The arithmetic a call runs at: Real also carries the precision tier.
std::string str() const
Canonical text, e.g.
unsigned digits
significant decimal digits, Real only