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