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