carl  24.04
Computer ARithmetic Library
RealRootIsolation.h
Go to the documentation of this file.
1 #pragma once
2 
4 #include "../Ran.h"
5 
14 
15 namespace carl::ran::interval {
16 
17 using carl::operator<<;
18 
19 /**
20  * Compact class to isolate real roots from a univariate polynomial using bisection.
21  *
22  * After some rather easy preprocessing (make polynomial square-free, eliminate zero roots, solve low-degree polynomial trivially, use root bounds to shrink the interval)
23  * we employ bisection which can optionally be initialized by approximations.
24  */
25 template<typename Number>
27  /// Initialize bisection intervals using approximations.
28  static constexpr bool initialize_bisection_by_approximation = true;
29  /// Factorize polynomial and handle factors individually.
30  static constexpr bool simplify_by_factorization = false;
31 
32  /// The polynomial.
34  /// The list of roots.
35  std::vector<IntRepRealAlgebraicNumber<Number>> mRoots;
36  /// The bounding interval.
38  /// The sturm sequence for mPolynomial.
39  // std::optional<std::vector<UnivariatePolynomial<Number>>> mSturmSequence;
40 
41  /// Return the sturm sequence for mPolynomial, create it if necessary.
42  // const auto& sturm_sequence() {
43  // if (!mSturmSequence) {
44  // mSturmSequence = carl::sturm_sequence(mPolynomial);
45  // }
46  // return *mSturmSequence;
47  // }
48  /// Reset the sturm sequence, used if the polynomial was modified.
49  // void reset_sturm_sequence() {
50  // mSturmSequence.reset();
51  // }
52 
53  /// Handle zero roots (p(0) == 0)
55  if (mPolynomial.zero_is_root()) {
56  if (mInterval.contains(0)) {
57  mRoots.emplace_back(0);
58  }
60  }
61  }
62 
63  /// Directly solve low-degree polynomials.
65  CARL_LOG_DEBUG("carl.ran.interval", "Trying to trivially solve mPolynomial " << mPolynomial);
66  switch (mPolynomial.degree()) {
67  case 0: {
68  CARL_LOG_TRACE("carl.ran.interval", "Constant polynomial, thus no roots");
69  break;
70  }
71  case 1: {
72  CARL_LOG_DEBUG("carl.ran.interval", "Trivially solving linear mPolynomial " << mPolynomial);
73  const auto& a = mPolynomial.coefficients()[1];
74  const auto& b = mPolynomial.coefficients()[0];
75  assert(!carl::is_zero(a));
76  add_trivial_root(-b / a);
77  break;
78  }
79  case 2: {
80  CARL_LOG_DEBUG("carl.ran.interval", "Trivially solving quadratic mPolynomial " << mPolynomial);
81  const auto& a = mPolynomial.coefficients()[2];
82  const auto& b = mPolynomial.coefficients()[1];
83  const auto& c = mPolynomial.coefficients()[0];
84  assert(!carl::is_zero(a));
85  /* Use this formulation of p-q-formula:
86  * x = ( -b +- \sqrt{ b*b - 4*a*c } ) / (2*a)
87  */
88  Number rad = b*b - 4*a*c;
89  if (rad == 0) {
90  add_trivial_root(-b / (2*a));
91  } else if (rad > 0) {
92  std::pair<Number, Number> res = carl::sqrt_fast(rad);
93  if (res.first == res.second) {
94  // Root could be calculated exactly
95  add_trivial_root((-b - res.first) / (2*a));
96  add_trivial_root((-b + res.first) / (2*a));
97  } else {
98  // Root is within interval (res.first, res.second)
99  Interval<Number> r(res.first, BoundType::STRICT, res.second, BoundType::STRICT);
100  add_trivial_root((Number(-b) - r) / Number(2*a));
101  add_trivial_root((Number(-b) + r) / Number(2*a));
102  }
103  } else {
104  // No root.
105  }
106  break;
107  }
108  default:
109  return false;
110  }
111  return true;
112  }
113 
114  void add_trivial_root(const Number& n) {
115  CARL_LOG_TRACE("carl.ran.interval", "Add trivial root " << n);
116  assert(carl::is_root_of(mPolynomial, n));
117  mRoots.emplace_back(n);
118  }
119 
121  CARL_LOG_TRACE("carl.ran.interval", "Add trivial root " << i);
122  mRoots.emplace_back(mPolynomial, i);
123  }
124 
125  /// Use root bounds to shrink mInterval.
127  auto bound = carl::lagrangeBound(mPolynomial);
129  CARL_LOG_DEBUG("carl.ran.interval", "Updated bounds to " << mInterval);
130  }
131 
132  /// Add a root to mRoots and simplify polynomial accordingly (essentially divide by x-n)
133  void add_root(const Number& n) {
134  CARL_LOG_TRACE("carl.ran.interval", "Add root " << n);
135  assert(carl::is_root_of(mPolynomial, n));
136  // reset_sturm_sequence();
138  mRoots.emplace_back(n);
139  }
140  /// Add a root to mRoots, based on an isolating interval.
141  void add_root(const Interval<Number>& i) {
142  CARL_LOG_TRACE("carl.ran.interval", "Add root " << i);
143  mRoots.emplace_back(mPolynomial, i);
144  }
145 
146  /// Check whether the interval bounds are roots.
148  bool found_root = false;
149  if (mInterval.lower_bound_type() == BoundType::WEAK) {
150  if (carl::is_root_of(mPolynomial, mInterval.lower())) {
151  add_root(mInterval.lower());
152  found_root = true;
153  }
154  }
155  if (mInterval.upper_bound_type() == BoundType::WEAK) {
156  if (carl::is_root_of(mPolynomial, mInterval.upper())) {
157  add_root(mInterval.upper());
158  found_root = true;
159  }
160  }
161  return found_root;
162  }
163 
164  /**
165  * Initialize the bisection queue using approximations.
166  *
167  * The main idea is that the eigenvalues of the companion matrix are the root of a polynomial.
168  * This is implemented in eigen::root_approximation.
169  * We do:
170  * - convert coefficients to doubles
171  * - call eigen::root_approximation
172  * - make approximations smaller, sort them, remove duplicates
173  * - convert approximations to rationals
174  * - create interval endpoints so that each interval contains a single approximation
175  * - initialize queue from these endpoints
176  */
177  void bisect_by_approximation(std::deque<Interval<Number>>& queue) {
178  assert(queue.empty());
179  // Convert polynomial coeffs to double
180  std::vector<double> coeffs;
181  for (const auto& n: mPolynomial.coefficients()) {
182  coeffs.emplace_back(to_double(n));
183  }
184  CARL_LOG_DEBUG("carl.ran.interval", "Double coeffs: " << coeffs);
185 
186  // Get approximations of the roots
187  std::vector<double> approx = carl::roots::eigen::root_approximation(coeffs);
188  // Sort and make them unique
189  for (double& a: approx) {
190  a = std::round(a*1000) / 1000;
191  }
192  std::sort(approx.begin(), approx.end());
193  approx.erase(std::unique(approx.begin(), approx.end()), approx.end());
194  CARL_LOG_DEBUG("carl.ran.interval", "Double approximations: " << approx);
195  // Convert to rationals
196  std::vector<Number> roots;
197  for (double r: approx) {
198  if (!is_number(r)) continue;
199  Number n = carl::rationalize<Number>(r);
200  if (!mInterval.contains(n)) continue;
201  if (carl::is_root_of(mPolynomial, n)) {
202  add_root(n);
203  }
204  roots.emplace_back(n);
205  }
206  CARL_LOG_DEBUG("carl.ran.interval", "Approx roots: " << roots);
207 
208  // Get interval endpoints
209  std::vector<Number> endpoints;
210  endpoints.emplace_back(mInterval.lower());
211  if (roots.size() > 0) {
212  for (std::size_t i = 0; i < roots.size() - 1; ++i) {
213  auto tmp = carl::sample(Interval<Number>(roots[i], BoundType::STRICT, roots[i+1], BoundType::STRICT));
214  if (carl::is_root_of(mPolynomial, tmp)) {
215  add_root(tmp);
216  }
217  endpoints.emplace_back(tmp);
218  }
219  }
220  endpoints.emplace_back(mInterval.upper());
221  CARL_LOG_DEBUG("carl.ran.interval", "Endpoints: " << endpoints);
222 
223  // Fill queue based on the endpoints
224  for (std::size_t i = 0; i < endpoints.size() - 1; ++i) {
225  queue.emplace_back(endpoints[i], BoundType::STRICT, endpoints[i+1], BoundType::STRICT);
226  }
227  CARL_LOG_DEBUG("carl.ran.interval", "Queue: " << queue);
228  }
229 
230  /// Perform bisection
232  std::deque<Interval<Number>> queue;
235  } else {
236  queue.emplace_back(mInterval);
237  }
238 
239  while (!queue.empty()) {
240  auto cur = queue.front();
241  queue.pop_front();
242 
243  auto variations = carl::sign_variations(mPolynomial, cur);
244 
245  if (variations == 0) {
246  CARL_LOG_DEBUG("carl.ran.interval", "No root within " << cur);
247  continue;
248  }
249  if (variations == 1) {
250  CARL_LOG_DEBUG("carl.ran.interval", "A single root within " << cur);
251  assert(!carl::is_root_of(mPolynomial, cur.lower()));
252  assert(!carl::is_root_of(mPolynomial, cur.upper()));
253  assert(count_real_roots(mPolynomial, cur) == 1);
254  add_root(cur);
255  continue;
256  }
257 
258  auto pivot = carl::sample(cur);
259  if (carl::is_root_of(mPolynomial, pivot)) {
260  add_root(pivot);
261  }
262  CARL_LOG_DEBUG("carl.ran.interval", "Splitting " << cur << " at " << pivot);
263  queue.emplace_back(cur.lower(), BoundType::STRICT, pivot, BoundType::STRICT);
264  queue.emplace_back(pivot, BoundType::STRICT, cur.upper(), BoundType::STRICT);
265  }
266  }
267 
268  /// Do actual root isolation.
269  void compute_roots() {
270  // Handle zero polynomial
271  if (carl::is_zero(mPolynomial)) {
272  return;
273  }
274  // Check for p(0) == 0
276  assert(!carl::is_zero(mPolynomial));
277  // Handle other easy cases
278  while (true) {
279  // Degree of at most 2 -> use p-q-formula
280  if (isolate_roots_trivially()) {
281  return;
282  }
283  // Use bounds to make interval smaller
285  // Check whether interval bounds are roots
286  if (!check_interval_bounds()) {
287  break;
288  }
289  }
290 
291  // Now do actual bisection
293  }
294 
295 public:
296  RealRootIsolation(const UnivariatePolynomial<Number>& polynomial, const Interval<Number>& interval): mPolynomial(carl::squareFreePart(polynomial)), mInterval(interval) {
297  CARL_LOG_DEBUG("carl.ran.interval", "Reduced " << polynomial << " to " << mPolynomial);
298  }
299 
300  /// Compute and sort the roots of mPolynomial within mInterval.
301  std::vector<IntRepRealAlgebraicNumber<Number>> get_roots() {
303  auto factors = carl::factorization(mPolynomial);
304  CARL_LOG_DEBUG("carl.ran.interval", "Factorized " << mPolynomial << " to " << factors);
305  auto interval = mInterval;
306  for (const auto& factor: factors) {
307  CARL_LOG_DEBUG("carl.ran.interval", "Coputing root of factor " << factor);
308  mPolynomial = factor.first;
309  mInterval = interval;
310  // reset_sturm_sequence();
311  compute_roots();
312  }
313  } else {
314  compute_roots();
315  }
316  std::sort(mRoots.begin(), mRoots.end());
317  return mRoots;
318  }
319 
320 };
321 
322 }
#define CARL_LOG_TRACE(channel, msg)
Definition: carl-logging.h:44
#define CARL_LOG_DEBUG(channel, msg)
Definition: carl-logging.h:43
carl is the main namespace for the library.
MultivariatePolynomial< C, O, P > squareFreePart(const MultivariatePolynomial< C, O, P > &polynomial)
Number sample(const Interval< Number > &i, bool includingBounds=true)
Searches for some point in this interval, preferably near the midpoint and with a small representatio...
Definition: Sampling.h:30
std::pair< cln::cl_RA, cln::cl_RA > sqrt_fast(const cln::cl_RA &a)
Compute square root in a fast but less precise way.
int count_real_roots(const std::vector< UnivariatePolynomial< Coefficient >> &seq, const Interval< Coefficient > &i)
Calculate the number of real roots of a polynomial within a given interval based on a sturm sequence ...
Definition: RootCounting.h:19
std::size_t sign_variations(InputIterator begin, InputIterator end)
Counts the number of sign variations in the given object range.
Definition: Sign.h:69
bool is_zero(const Interval< Number > &i)
Check if this interval is a point-interval containing 0.
Definition: Interval.h:1453
bool is_root_of(const UnivariatePolynomial< Coeff > &p, const Coeff &value)
Definition: Evaluation.h:62
Interval< Number > set_intersection(const Interval< Number > &lhs, const Interval< Number > &rhs)
Intersects two intervals in a set-theoretic manner.
Definition: SetTheory.h:99
void eliminate_root(UnivariatePolynomial< Coeff > &p, const Coeff &root)
Reduces the polynomial such that the given root is not a root anymore.
double to_double(const cln::cl_RA &n)
Converts the given fraction to a double.
Definition: operations.h:113
cln::cl_I round(const cln::cl_RA &n)
Round a fraction to next integer.
Definition: operations.h:255
Coeff lagrangeBound(const UnivariatePolynomial< Coeff > &p)
Definition: RootBounds.h:55
@ WEAK
the given bound is compared by a weak ordering relation
@ STRICT
the given bound is compared by a strict ordering relation
bool is_number(double d)
Definition: operations.h:54
void eliminate_zero_root(UnivariatePolynomial< Coeff > &p)
Reduces the given polynomial such that zero is not a root anymore.
Factors< MultivariatePolynomial< C, O, P > > factorization(const MultivariatePolynomial< C, O, P > &p, bool includeConstants=true)
Try to factorize a multivariate polynomial.
Definition: Factorization.h:31
std::vector< double > root_approximation(const std::vector< double > &coeffs)
Compute approximations of the real roots of the univariate polynomials with the given coefficients.
The class which contains the interval arithmetic including trigonometric functions.
Definition: Interval.h:134
const std::vector< Coefficient > & coefficients() const &
Retrieves the coefficients defining this polynomial.
uint degree() const
Get the maximal exponent of the main variable.
bool zero_is_root() const
Checks if zero is a real root of this polynomial.
Compact class to isolate real roots from a univariate polynomial using bisection.
UnivariatePolynomial< Number > mPolynomial
The polynomial.
static constexpr bool simplify_by_factorization
Factorize polynomial and handle factors individually.
std::vector< IntRepRealAlgebraicNumber< Number > > mRoots
The list of roots.
Interval< Number > mInterval
The bounding interval.
bool check_interval_bounds()
Check whether the interval bounds are roots.
void add_root(const Number &n)
Add a root to mRoots and simplify polynomial accordingly (essentially divide by x-n)
void bisect_by_approximation(std::deque< Interval< Number >> &queue)
Initialize the bisection queue using approximations.
void compute_roots()
Do actual root isolation.
void eliminate_zero_roots()
The sturm sequence for mPolynomial.
void isolate_by_bisection()
Perform bisection.
bool isolate_roots_trivially()
Directly solve low-degree polynomials.
std::vector< IntRepRealAlgebraicNumber< Number > > get_roots()
Compute and sort the roots of mPolynomial within mInterval.
void add_trivial_root(const Interval< Number > &i)
void add_root(const Interval< Number > &i)
Add a root to mRoots, based on an isolating interval.
void update_root_bounds()
Use root bounds to shrink mInterval.
static constexpr bool initialize_bisection_by_approximation
Initialize bisection intervals using approximations.
RealRootIsolation(const UnivariatePolynomial< Number > &polynomial, const Interval< Number > &interval)