LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
sym_number.h
Go to the documentation of this file.
1/**
2 * @file
3 * @ingroup line_num
4 * An exact symbolic scalar, as a `num_traits` numeric type.
5 *
6 * Copyright (c) 2012-2026, Imperial College London
7 * All rights reserved.
8 */
9#ifndef LINE_NUM_SYM_NUMBER_H
10#define LINE_NUM_SYM_NUMBER_H
11
12#ifdef LINE_MP_USE_SYMENGINE
13
14#include <cmath>
15#include <limits>
16#include <string>
17
18#include <symengine/basic.h>
19#include <symengine/eval_double.h>
20#include <symengine/expression.h>
21#include <symengine/symbol.h>
22#include <symengine/visitor.h>
23
24#include "line/num/number.h"
25
26namespace line {
27
28/**
29 * A symbolic scalar: a SymEngine expression carrying `+ - * /` and powers.
30 *
31 * WHY THIS IS A `num_traits` TYPE AND NOT A NEW CODE PATH. Every routine that
32 * is to accept symbolic input here is already `template <class T>` over
33 * `Matrix<T>`, and `num_traits<T>` is the one place a numeric type declares
34 * itself: `double`, `Rational`, `Real<D>` and `Complex` are already four such
35 * declarations. Adding a fifth makes `pfqn_gld` and `pfqn_gldsingle` symbolic
36 * WITHOUT EDITING EITHER OF THEM, which is also what keeps the symbolic and
37 * numeric arms from drifting: there is only one arm.
38 *
39 * `is_exact` is true and `has_transcendental` is FALSE, matching `Rational`.
40 * The second is what steers the gld family off its log-domain path, which is
41 * correct here for the reason it is correct there: the log domain exists to stop
42 * a double underflowing, and an exact expression cannot underflow. SymEngine
43 * itself does carry `log`, so unlike the JAR's Rings backend this type CAN
44 * represent `lG = log(G)`; `log_as_double` is nonetheless a double-valued
45 * accessor by the trait's contract, so it evaluates and is defined only for a
46 * fully substituted expression.
47 *
48 * COMPARISONS ARE STRUCTURAL, never decisions about the values a symbol may
49 * take. `operator==` compares normal forms; there is deliberately no
50 * `operator<`, because ordering a symbol has no meaning and the algorithms that
51 * would want one (the lld threshold scan) are the very ones the reference
52 * refuses on symbolic input.
53 */
54class Sym {
55 public:
56 Sym() : e_(SymEngine::integer(0)) {}
57 explicit Sym(const SymEngine::Expression& e) : e_(e) {}
58 Sym(int v) : e_(SymEngine::integer(v)) {} // NOLINT: implicit by design
59 Sym(long v) : e_(SymEngine::integer(v)) {} // NOLINT
60 Sym(const SymEngine::RCP<const SymEngine::Basic>& b) : e_(b) {} // NOLINT
61
62 /** A named symbol. */
63 static Sym var(const std::string& name) {
64 return Sym(SymEngine::Expression(SymEngine::symbol(name)));
65 }
66
67 /** A double carried in as the exact dyadic rational it already is. */
68 static Sym from_double_exact(double v) {
69 return Sym(SymEngine::Expression(SymEngine::real_double(v)));
70 }
71
72 const SymEngine::Expression& expr() const { return e_; }
73
74 Sym operator+(const Sym& o) const { return Sym(e_ + o.e_); }
75 Sym operator-(const Sym& o) const { return Sym(e_ - o.e_); }
76 Sym operator*(const Sym& o) const { return Sym(e_ * o.e_); }
77 Sym operator/(const Sym& o) const { return Sym(e_ / o.e_); }
78 Sym operator-() const { return Sym(-e_); }
79
80 Sym& operator+=(const Sym& o) { e_ = e_ + o.e_; return *this; }
81 Sym& operator-=(const Sym& o) { e_ = e_ - o.e_; return *this; }
82 Sym& operator*=(const Sym& o) { e_ = e_ * o.e_; return *this; }
83 Sym& operator/=(const Sym& o) { e_ = e_ / o.e_; return *this; }
84
85 /** Structural equality of the normal form, not a claim about values. */
86 bool operator==(const Sym& o) const { return e_ == o.e_; }
87 bool operator!=(const Sym& o) const { return !(*this == o); }
88
89 std::string str() const { return SymEngine::str(e_); }
90
91 private:
92 SymEngine::Expression e_;
93};
94
95inline std::string to_string(const Sym& v) { return v.str(); }
96
97template <>
98struct num_traits<Sym> {
99 using type = Sym;
100 static constexpr bool is_exact = true;
101 /**
102 * FALSE, as for `Rational`, and for the same reason: it is what steers a
103 * routine off its log-domain arm. SymEngine can represent `log`, but the
104 * trait means "may this type be handed to std::log and give a value of its
105 * own kind", and an expression cannot answer that without a substitution.
106 */
107 static constexpr bool has_transcendental = false;
108 static const char* name() { return "symbolic"; }
109
110 static Sym from_int(long v) { return Sym(v); }
111 static Sym from_rational(long num, long den) {
112 return Sym(SymEngine::Expression(SymEngine::integer(num)) /
113 SymEngine::Expression(SymEngine::integer(den)));
114 }
115 static Sym from_double(double v) { return Sym::from_double_exact(v); }
116
117 /**
118 * Defined only for a FULLY SUBSTITUTED expression: a symbol left in it has
119 * no double, and SymEngine raises rather than guessing one. That raise is
120 * the right behaviour for a caller who asked for a number.
121 */
122 static double to_double(const Sym& v) {
123 return SymEngine::eval_double(*v.expr().get_basic());
124 }
125
126 /**
127 * NaN ON A FREE SYMBOL, rather than a raise, and this is the one place the
128 * two differ.
129 *
130 * `NcResult<T>` carries `lG` as a plain double and every return path of the
131 * gld family fills it by calling this, unconditionally, on a value it has
132 * just computed. Under `T = Sym` that value is usually symbolic, so a raise
133 * here would make the SUCCESSFUL symbolic result unreachable: `G` is the
134 * answer and `lG` is a convenience beside it. NaN says "there is no double
135 * for this" in the field's own vocabulary and leaves `G` intact.
136 *
137 * A fully substituted expression still gets its real logarithm, so a caller
138 * who substituted before asking loses nothing.
139 */
140 static double log_as_double(const Sym& v) {
141 if (!SymEngine::free_symbols(*v.expr().get_basic()).empty()) {
142 return std::numeric_limits<double>::quiet_NaN();
143 }
144 return std::log(to_double(v));
145 }
146 static std::string to_string(const Sym& v) { return v.str(); }
147};
148
149} // namespace line
150
151#endif // LINE_MP_USE_SYMENGINE
152#endif // LINE_NUM_SYM_NUMBER_H
Number-type abstraction for the templated API port.