LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
api_dispatch_detail.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_REG_API_DISPATCH_DETAIL_H
6#define LINE_REG_API_DISPATCH_DETAIL_H
7
8/**
9 * @file
10 * Internal to the --api dispatcher: what a per-domain translation unit needs
11 * to add entries to the table that api_invoke consults.
12 *
13 * The table is split across translation units (api_dispatch.cpp plus one
14 * api_dispatch_<group>.cpp per family of domains) because every entry
15 * instantiates its algorithm at five arithmetics, and one file carrying all of
16 * them is both the slowest object in the build and a file no two changes can
17 * touch at once.
18 *
19 * REGISTRATION IS EXPLICIT, NOT BY STATIC INITIALIZER. line_mp_api is a static
20 * archive, and a translation unit whose only reference is a self-registering
21 * global is exactly what the linker drops from one: the table would come up
22 * short with nothing to say so. Each group therefore exports two named
23 * functions, listed in api_dispatch_registrars() and
24 * api_library_only_registrars() below, and the link fails loudly if one is
25 * missing.
26 */
27
28#include <cstdint>
29#include <functional>
30#include <map>
31#include <string>
32#include <type_traits>
33#include <vector>
34
37#include "line/reg/api_json.h"
38
39namespace line {
40namespace reg {
41
42using Invoker = std::function<Json(const ArithSpec&, Args&)>;
43using DispatchTable = std::map<std::string, Invoker>;
44
45/** name -> why the function has no named-JSON-argument form. */
46using LibraryOnlyTable = std::map<std::string, std::string>;
47
48/**
49 * Run an operation at the selected arithmetic. Op is a struct with a static
50 * member template run<T>(Args&) returning the results object; the arithmetic
51 * gate against the registry has already passed when this is reached, so every
52 * branch here is a mode the function genuinely supports.
53 */
54template <class Op>
55Json run_at(const ArithSpec& s, Args& a) {
56 switch (s.mode) {
57 case Arith::Double: return Op::template run<double>(a);
58 case Arith::Exact: return Op::template run<Rational>(a);
59 case Arith::Real:
60 if (s.digits <= 50) return Op::template run<Real50>(a);
61 if (s.digits <= 100) return Op::template run<Real100>(a);
62 return Op::template run<Real200>(a);
63 }
64 throw InputError("unreachable arithmetic selection");
65}
66
67/**
68 * Wrap an Op whose registry entry lists Double and Real but NOT Exact.
69 *
70 * `run_at` instantiates all three backends because the mode is a runtime value,
71 * so an Op that calls exp, log or sqrt would not COMPILE at Rational even
72 * though it can never be REACHED there -- `api_invoke` refuses the mode against
73 * the registry before dispatching. The if-constexpr makes that same fact known
74 * to the compiler. The throw is the arm the gate has already excluded, kept as
75 * a real refusal rather than a placeholder so that a registry entry mistakenly
76 * widened to Exact reports the reason instead of returning a wrong number.
77 */
78template <class Op>
80 template <class T>
81 static Json run(Args& a) {
83 return Op::template run<T>(a);
84 } else {
85 (void)a;
86 throw UnsupportedError(ErrorKind{"Arithmetic"},
87 "this function evaluates transcendental terms (exp, log, sqrt or a "
88 "matrix exponential), which the exact rational field does not contain; "
89 "rerun with --arith double or --arith real:<digits>.");
90 }
91 }
92};
93
94/**
95 * Wrap an Op whose registry entry lists Double ONLY. The port of such a
96 * function is not templated on the scalar (it calls a double-only kernel:
97 * LAPACK, HiGHS, an ODE integrator, a random stream), so only the double arm
98 * may be instantiated. The other arms are refused by the registry gate before
99 * dispatch; the throw is the same safety net NeedsTranscendental keeps.
100 */
101template <class Op>
103 template <class T>
104 static Json run(Args& a) {
105 if constexpr (std::is_same<T, double>::value) {
106 return Op::run(a);
107 } else {
108 (void)a;
109 throw UnsupportedError(ErrorKind{"Arithmetic"},
110 "this function is implemented in double precision only; rerun with "
111 "--arith double.");
112 }
113 }
114};
115
116/**
117 * The seed of a function whose port draws from a caller-owned generator.
118 *
119 * REQUIRED, NEVER DEFAULTED. The MATLAB reference falls back to the session's
120 * global RNG state, which has no JSON form; a silent default seed would make
121 * every call that forgot it return the same "random" draw and look like an
122 * answer. So the caller names the stream, and the same seed reproduces the
123 * same result at the same arithmetic (lossn_mci set the convention).
124 */
125inline std::uint64_t required_seed(Args& a, const std::string& fn, const char* spelled = "seed") {
126 if (!a.has("seed"))
127 throw InputError(fn + ": " + spelled + " is required; the port draws from a seeded "
128 "generator and has no session RNG to fall back on");
129 return static_cast<std::uint64_t>(a.count("seed"));
130}
131
132/**
133 * MATLAB's `res` struct of the exact queueing-system functions: one key per
134 * measure, in the reference's field order, scalars as scalars and row vectors
135 * as lists, plus `analyzer`.
136 */
137template <class T>
139 Json out = Json::object();
140 for (const qsys::QsysResFieldT<T>& f : r.fields) {
141 if (f.scalar && f.value.size() == 1) out[f.name] = encode_scalar(f.value[0]);
142 else out[f.name] = encode_vector(f.value);
143 }
144 out["analyzer"] = r.analyzer;
145 return out;
146}
147
148/** The 'tPoints', 'percentiles' and 'nmax' options those functions share. */
149inline qsys::QsysResOptions read_qsys_res_options(Args& a, bool with_nmax = true) {
151 if (a.has("tPoints")) o.tPoints = a.vector<double>("tPoints");
152 if (a.has("percentiles")) o.percentiles = a.vector<double>("percentiles");
153 if (with_nmax && a.has("nmax")) o.nmax = static_cast<long>(a.count("nmax"));
154 return o;
155}
156
157template <class Op>
159 return [](const ArithSpec& s, Args& a) { return run_at<Op>(s, a); };
160}
161
162// One pair per group translation unit. Adding a group means a line here AND an
163// entry in each of the two lists below.
164#define LINE_API_GROUP(g) \
165 void register_api_##g(DispatchTable& t); \
166 void register_library_only_##g(LibraryOnlyTable& t);
167
168LINE_API_GROUP(pfqn_a)
169LINE_API_GROUP(pfqn_b)
171LINE_API_GROUP(trace)
172LINE_API_GROUP(misc)
173LINE_API_GROUP(qsys)
175#undef LINE_API_GROUP
176
177inline const std::vector<void (*)(DispatchTable&)>& api_dispatch_registrars() {
178 static const std::vector<void (*)(DispatchTable&)> v = {
182 };
183 return v;
184}
185
195
196} // namespace reg
197} // namespace line
198
199#endif // LINE_REG_API_DISPATCH_DETAIL_H
Direct invocation of a single API function from named JSON arguments.
#define LINE_API_GROUP(g)
The one conversion policy between JSON and the templated API layer.
InputError(const std::string &what)
Definition error.h:105
UnsupportedError(const std::string &what)
Definition error.h:119
Named-argument reader over the parsed JSON object.
Definition api_json.h:391
std::size_t count(const char *key)
A required nonnegative count.
Definition api_json.h:475
bool has(const char *key) const
Definition api_json.h:400
std::vector< T > vector(const char *key)
Required vector argument; absent is an error, not an empty vector.
Definition api_json.h:436
nlohmann::json Json
Definition api_json.h:63
const std::vector< void(*)(LibraryOnlyTable &)> & api_library_only_registrars()
void register_library_only_trace(LibraryOnlyTable &t)
std::uint64_t required_seed(Args &a, const std::string &fn, const char *spelled="seed")
The seed of a function whose port draws from a caller-owned generator.
void register_library_only_mam(LibraryOnlyTable &t)
void register_library_only_misc(LibraryOnlyTable &t)
void register_api_qsys(DispatchTable &t)
Json run_at(const ArithSpec &s, Args &a)
Run an operation at the selected arithmetic.
void register_api_mam(DispatchTable &t)
void register_library_only_pfqn_a(LibraryOnlyTable &t)
void register_library_only_mc(LibraryOnlyTable &t)
qsys::QsysResOptions read_qsys_res_options(Args &a, bool with_nmax=true)
The 'tPoints', 'percentiles' and 'nmax' options those functions share.
Json encode_vector(const std::vector< T > &v)
Definition api_json.h:333
void register_api_mc(DispatchTable &t)
void register_api_trace(DispatchTable &t)
void register_library_only_pfqn_b(LibraryOnlyTable &t)
void register_library_only_qsys(LibraryOnlyTable &t)
Json encode_scalar(const T &v)
Definition api_json.h:328
std::function< Json(const ArithSpec &, Args &)> Invoker
void register_api_misc(DispatchTable &t)
void register_api_pfqn_b(DispatchTable &t)
std::map< std::string, std::string > LibraryOnlyTable
name -> why the function has no named-JSON-argument form.
Json encode_qsys_res(const qsys::QsysResT< T > &r)
MATLAB's res struct of the exact queueing-system functions: one key per measure, in the reference's f...
std::map< std::string, Invoker > DispatchTable
const std::vector< void(*)(DispatchTable &)> & api_dispatch_registrars()
void register_api_pfqn_a(DispatchTable &t)
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
The res struct the exact queueing-system functions return, and the numeric helpers those functions sh...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
One measure of a res struct: a scalar or a row vector.
Definition qsys_res.h:45
std::vector< T > value
Definition qsys_res.h:47
The name-value options shared by the exact functions: tPoints, percentiles and nmax (a negative nmax ...
Definition qsys_res.h:102
std::vector< double > percentiles
Definition qsys_res.h:104
std::vector< double > tPoints
Definition qsys_res.h:103
MATLAB's res struct: named measures in insertion order, plus res.analyzer.
Definition qsys_res.h:57
std::vector< QsysResFieldT< T > > fields
Definition qsys_res.h:58
std::string analyzer
Definition qsys_res.h:59
The arithmetic a call runs at: Real also carries the precision tier.
unsigned digits
significant decimal digits, Real only
Wrap an Op whose registry entry lists Double ONLY.
static Json run(Args &a)
Wrap an Op whose registry entry lists Double and Real but NOT Exact.