LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
number.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_NUM_NUMBER_H
6#define LINE_NUM_NUMBER_H
7
8/**
9 * @file
10 * @ingroup line_num
11 * Number-type abstraction for the templated API port.
12 *
13 * Three arithmetic modes are exposed to callers:
14 * double - IEEE 754, what MATLAB / the JAR / native Python use today
15 * exact - arbitrary-precision rationals, no rounding at all
16 * real - fixed high-precision binary floating point
17 *
18 * The default backends are header-only Boost.Multiprecision types
19 * (Boost Software License 1.0), so the default build carries no LGPL
20 * obligation and a redistributable Python wheel remains possible. Defining
21 * LINE_MP_USE_GMP switches the exact and real backends to GMP mpq and MPFR,
22 * which are faster but LGPL; the algorithms are unchanged, only the typedef.
23 *
24 * Every algorithm is a template on the number type T and consults
25 * num_traits<T> for what T can do. Algorithms that need log/exp/pow assert
26 * num_traits<T>::has_transcendental at compile time, so instantiating an
27 * inherently inexact algorithm at exact arithmetic is a build error rather
28 * than a silent fallback.
29 */
30
31#include <cmath>
32#include <string>
33
34#include <boost/multiprecision/cpp_bin_float.hpp>
35#include <boost/multiprecision/cpp_int.hpp>
36
37#ifdef LINE_MP_USE_GMP
38#include <boost/multiprecision/gmp.hpp>
39#include <boost/multiprecision/mpfr.hpp>
40#endif
41
42#include <boost/math/special_functions/zeta.hpp>
43
44/* NO EAGER ZETA WARM-UP AT MULTIPRECISION. Boost.Math gives each special
45 * function an `*_initializer` whose static member calls the function once
46 * BEFORE main(), for every type it is instantiated at. At arbitrary precision
47 * `lgamma` falls back to a Taylor series through `polygamma`, which reaches
48 * `zeta`, and `zeta(5)` then fills a 50-entry table of odd zeta values at full
49 * precision. With `Real<N>` instantiated at 16..256 digits that cost `line-cli`
50 * 4.4 s of startup on every call, `--version` included (measured 2026-09-23).
51 *
52 * The warm-up buys nothing here: that table is BOOST_MATH_THREAD_LOCAL, so the
53 * main thread filling it does not serve the threads that solve, and C++11 makes
54 * the remaining function-local statics thread-safe on first use. The table is
55 * therefore built lazily, by the first zeta call on each thread, with the same
56 * values. Only multiprecision `number<>` is exempted; double keeps Boost's. */
57namespace boost {
58namespace math {
59namespace detail {
60template <class Backend, boost::multiprecision::expression_template_option ET, class Policy, class Tag>
61struct zeta_initializer<boost::multiprecision::number<Backend, ET>, Policy, Tag> {
62 static void force_instantiate() {}
63};
64} // namespace detail
65} // namespace math
66} // namespace boost
67
68namespace line {
69
70// Backend typedefs
71
72/* `Rational` IS PINNED TO et_off ON PURPOSE, and the `et_off` is the whole
73 * point of spelling these out rather than using Boost's `cpp_rational` /
74 * `mpq_rational` aliases, which are `et_on`.
75 *
76 * With expression templates on, `a + b` does not evaluate: it builds an
77 * `expression<...>` node holding REFERENCES to its operands. That is a
78 * use-after-free the moment the node outlives them, and a DEDUCED return type
79 * is how it escapes -- `[](Rational x) { return Rational(4) * x - Rational(1); }`
80 * returns a node over two temporaries that die with the return statement.
81 * It cost two SIGSEGVs (test_pfqn_mvac_oi_clw.cpp 2026-08-25,
82 * test_rootfind.cpp 2026-08-26), and both were invisible on the 22.04
83 * development host: Boost 1.72 added rvalue-reference handling to the
84 * operators, so 1.74 collapses that expression eagerly while 1.71 (every 20.04
85 * worker) does not. doctest cannot catch a SIGSEGV, so each one aborted the
86 * binary mid-run and left several hundred cases with no verdict at all.
87 *
88 * Turning the templates off removes the entire class rather than the two sites
89 * that happened to be found. It cannot change a single digit: rational
90 * arithmetic here is exact, so expression templates were only ever eliding
91 * temporaries, never altering a value. Do NOT "restore" the Boost aliases.
92 *
93 * BigInt and Real are left alone: `Real` is already et_off for these backends,
94 * and no BigInt expression is returned through a deduced type. */
95#ifdef LINE_MP_USE_GMP
96using Rational =
97 boost::multiprecision::number<boost::multiprecision::gmp_rational, boost::multiprecision::et_off>;
98using BigInt = boost::multiprecision::mpz_int;
99template <unsigned Digits10>
100using Real = boost::multiprecision::number<boost::multiprecision::mpfr_float_backend<Digits10>>;
101#else
102using Rational = boost::multiprecision::number<boost::multiprecision::cpp_rational_backend,
103 boost::multiprecision::et_off>;
104using BigInt = boost::multiprecision::cpp_int;
105template <unsigned Digits10>
106using Real = boost::multiprecision::number<boost::multiprecision::cpp_bin_float<Digits10>>;
107#endif
108
109/** Precision tiers offered by the CLI's --arith real:`<digits>` flag. */
113
114// ---------------------------------------------------------------------------
115// log of a big integer, without ever forming the value as a double
116// ---------------------------------------------------------------------------
117
118/**
119 * log(v) for a positive arbitrary-precision integer. Keeps only the leading
120 * 53 bits of the mantissa and accounts for the discarded bits in the exponent,
121 * so the result is finite for values far outside the double range.
122 */
123inline double log_bigint(const BigInt& v) {
124 if (v == 0) return -std::numeric_limits<double>::infinity();
125 BigInt a = v < 0 ? BigInt(-v) : v;
126 long bits = static_cast<long>(boost::multiprecision::msb(a)) + 1;
127 long shift = bits > 53 ? bits - 53 : 0;
128 BigInt mant = a >> shift;
129 return std::log(static_cast<double>(mant)) + static_cast<double>(shift) * std::log(2.0);
130}
131
132// ---------------------------------------------------------------------------
133// num_traits
134// ---------------------------------------------------------------------------
135
136template <class T>
137struct num_traits; // intentionally undefined for unsupported types
138
139template <>
140struct num_traits<double> {
141 using type = double;
142 static constexpr bool is_exact = false;
143 static constexpr bool has_transcendental = true;
144 static const char* name() { return "double"; }
145
146 static double from_int(long v) { return static_cast<double>(v); }
147 static double from_rational(long num, long den) {
148 return static_cast<double>(num) / static_cast<double>(den);
149 }
150 static double from_double(double v) { return v; }
151 static double to_double(const double& v) { return v; }
152 /** log of the value, always returned as a double. */
153 static double log_as_double(const double& v) { return std::log(v); }
154 static std::string to_string(const double& v) { return std::to_string(v); }
155};
156
157template <>
159 using type = Rational;
160 static constexpr bool is_exact = true;
161 static constexpr bool has_transcendental = false;
162 static const char* name() { return "exact"; }
163
164 static Rational from_int(long v) { return Rational(v); }
165 static Rational from_rational(long num, long den) { return Rational(num, den); }
166 /** Exact: a double is a dyadic rational, so this conversion loses nothing. */
167 static Rational from_double(double v) { return Rational(v); }
168 static double to_double(const Rational& v) { return static_cast<double>(v); }
169 static double log_as_double(const Rational& v) {
170 return log_bigint(BigInt(numerator(v))) - log_bigint(BigInt(denominator(v)));
171 }
172 static std::string to_string(const Rational& v) { return v.str(); }
173 static std::string numerator_str(const Rational& v) { return BigInt(numerator(v)).str(); }
174 static std::string denominator_str(const Rational& v) { return BigInt(denominator(v)).str(); }
175};
176
177template <unsigned D>
178struct num_traits<Real<D>> {
179 using type = Real<D>;
180 static constexpr bool is_exact = false;
181 static constexpr bool has_transcendental = true;
182 static constexpr unsigned digits10 = D;
183 static const char* name() { return "real"; }
184
185 static Real<D> from_int(long v) { return Real<D>(v); }
186 static Real<D> from_rational(long num, long den) { return Real<D>(num) / Real<D>(den); }
187 static Real<D> from_double(double v) { return Real<D>(v); }
188 static double to_double(const Real<D>& v) { return static_cast<double>(v); }
189 static double log_as_double(const Real<D>& v) { return static_cast<double>(log(v)); }
190 static std::string to_string(const Real<D>& v) { return v.str(); }
191};
192
193// ---------------------------------------------------------------------------
194// Generic helpers usable from any algorithm
195// ---------------------------------------------------------------------------
196
197template <class T>
198inline T num_abs(const T& v) {
199 using std::abs;
200 return abs(v);
201}
202
203template <>
204inline double num_abs<double>(const double& v) {
205 return std::fabs(v);
206}
207
208/** Factorial as a value of T. Exact for Rational and for BigInt-backed types. */
209template <class T>
210inline T num_factorial(unsigned n) {
212 for (unsigned k = 2; k <= n; ++k) f *= num_traits<T>::from_int(static_cast<long>(k));
213 return f;
214}
215
216/** Integer power, valid in any field (no transcendental requirement). */
217template <class T>
218inline T num_pow_int(const T& base, unsigned e) {
220 T b = base;
221 unsigned k = e;
222 while (k > 0) {
223 if (k & 1u) r *= b;
224 b *= b;
225 k >>= 1;
226 }
227 return r;
228}
229
230} // namespace line
231
232#endif // LINE_NUM_NUMBER_H
Definition number.h:57
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
T num_factorial(unsigned n)
Factorial as a value of T.
Definition number.h:210
T num_abs(const T &v)
Definition number.h:198
Real< 50 > Real50
Precision tiers offered by the CLI's –arith real:<digits> flag.
Definition number.h:110
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Definition number.h:218
boost::multiprecision::cpp_int BigInt
Definition number.h:104
Real< 200 > Real200
Definition number.h:112
boost::multiprecision::number< boost::multiprecision::cpp_rational_backend, boost::multiprecision::et_off > Rational
Definition number.h:102
boost::multiprecision::number< boost::multiprecision::cpp_bin_float< Digits10 > > Real
Definition number.h:106
Real< 100 > Real100
Definition number.h:111
double log_bigint(const BigInt &v)
log(v) for a positive arbitrary-precision integer.
Definition number.h:123
static const char * name()
Definition number.h:162
static std::string numerator_str(const Rational &v)
Definition number.h:173
static Rational from_int(long v)
Definition number.h:164
static constexpr bool has_transcendental
Definition number.h:161
static std::string to_string(const Rational &v)
Definition number.h:172
static double to_double(const Rational &v)
Definition number.h:168
static Rational from_rational(long num, long den)
Definition number.h:165
static Rational from_double(double v)
Exact: a double is a dyadic rational, so this conversion loses nothing.
Definition number.h:167
static std::string denominator_str(const Rational &v)
Definition number.h:174
static double log_as_double(const Rational &v)
Definition number.h:169
static constexpr bool is_exact
Definition number.h:160
static std::string to_string(const Real< D > &v)
Definition number.h:190
static const char * name()
Definition number.h:183
static constexpr unsigned digits10
Definition number.h:182
static Real< D > from_int(long v)
Definition number.h:185
static Real< D > from_rational(long num, long den)
Definition number.h:186
static Real< D > from_double(double v)
Definition number.h:187
static constexpr bool has_transcendental
Definition number.h:181
static double log_as_double(const Real< D > &v)
Definition number.h:189
static constexpr bool is_exact
Definition number.h:180
static double to_double(const Real< D > &v)
Definition number.h:188
static double from_double(double v)
Definition number.h:150
static std::string to_string(const double &v)
Definition number.h:154
static constexpr bool is_exact
Definition number.h:142
static double log_as_double(const double &v)
log of the value, always returned as a double.
Definition number.h:153
static const char * name()
Definition number.h:144
static double from_int(long v)
Definition number.h:146
static double from_rational(long num, long den)
Definition number.h:147
static constexpr bool has_transcendental
Definition number.h:143
static double to_double(const double &v)
Definition number.h:151