Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
"""Real and complex root isolation and refinement algorithms. """
from __future__ import print_function, division
from sympy.polys.densebasic import ( dup_LC, dup_TC, dup_degree, dup_strip, dup_reverse, dup_convert, dup_terms_gcd)
from sympy.polys.densearith import ( dup_neg, dup_rshift, dup_rem)
from sympy.polys.densetools import ( dup_clear_denoms, dup_mirror, dup_scale, dup_shift, dup_transform, dup_diff, dup_eval, dmp_eval_in, dup_sign_variations, dup_real_imag)
from sympy.polys.sqfreetools import ( dup_sqf_part, dup_sqf_list)
from sympy.polys.factortools import ( dup_factor_list)
from sympy.polys.polyerrors import ( RefinementFailed, DomainError)
from sympy.core.compatibility import range
def dup_sturm(f, K): """ Computes the Sturm sequence of ``f`` in ``F[x]``.
Given a univariate, square-free polynomial ``f(x)`` returns the associated Sturm sequence ``f_0(x), ..., f_n(x)`` defined by::
f_0(x), f_1(x) = f(x), f'(x) f_n = -rem(f_{n-2}(x), f_{n-1}(x))
Examples ========
>>> from sympy.polys import ring, QQ >>> R, x = ring("x", QQ)
>>> R.dup_sturm(x**3 - 2*x**2 + x - 3) [x**3 - 2*x**2 + x - 3, 3*x**2 - 4*x + 1, 2/9*x + 25/9, -2079/4]
References ==========
1. [Davenport88]_
""" if not K.has_Field: raise DomainError("can't compute Sturm sequence over %s" % K)
f = dup_sqf_part(f, K)
sturm = [f, dup_diff(f, 1, K)]
while sturm[-1]: s = dup_rem(sturm[-2], sturm[-1], K) sturm.append(dup_neg(s, K))
return sturm[:-1]
def dup_root_upper_bound(f, K): """Compute the LMQ upper bound for the positive roots of `f`; LMQ (Local Max Quadratic) was developed by Akritas-Strzebonski-Vigklas.
Reference: ========== Alkiviadis G. Akritas: "Linear and Quadratic Complexity Bounds on the Values of the Positive Roots of Polynomials" Journal of Universal Computer Science, Vol. 15, No. 3, 523-537, 2009. """
continue
return None else:
def dup_root_lower_bound(f, K): """Compute the LMQ lower bound for the positive roots of `f`; LMQ (Local Max Quadratic) was developed by Akritas-Strzebonski-Vigklas.
Reference: ========== Alkiviadis G. Akritas: "Linear and Quadratic Complexity Bounds on the Values of the Positive Roots of Polynomials" Journal of Universal Computer Science, Vol. 15, No. 3, 523-537, 2009. """
else: return None
def _mobius_from_interval(I, field): """Convert an open interval to a Mobius transform. """
def _mobius_to_interval(M, field): """Convert a Mobius transform to an open interval. """
else:
def dup_step_refine_real_root(f, M, K, fast=False): """One step of positive real root refinement algorithm. """
else: A = K.zero
return f, (b, b, d, d)
else:
f = dup_rshift(f, 1, K)
def dup_inner_refine_real_root(f, M, K, eps=None, steps=None, disjoint=None, fast=False, mobius=False): """Refine a positive root of `f` given a Mobius transform or an interval. """
a, b, c, d = _mobius_from_interval(M, F) else:
d), K, fast=fast)
for i in range(0, steps): if abs(F(a, c) - F(b, d)) >= eps: f, (a, b, c, d) = dup_step_refine_real_root(f, (a, b, c, d), K, fast=fast) else: break else: while abs(F(a, c) - F(b, d)) >= eps: f, (a, b, c, d) = dup_step_refine_real_root(f, (a, b, c, d), K, fast=fast)
else:
else:
def dup_outer_refine_real_root(f, s, t, K, eps=None, steps=None, disjoint=None, fast=False): """Refine a positive root of `f` given an interval `(s, t)`. """
dup_strip([c, d]), K)
raise RefinementFailed("there should be exactly one root in (%s, %s) interval" % (s, t))
def dup_refine_real_root(f, s, t, K, eps=None, steps=None, disjoint=None, fast=False): """Refine real root's approximating interval to the given precision. """ (_, f), K = dup_clear_denoms(f, K, convert=True), K.get_ring() raise DomainError("real root refinement not supported over %s" % K)
return (s, t)
s, t = t, s
if t <= 0: f, s, t, negative = dup_mirror(f, K), -t, -s, True else: raise ValueError("can't refine a real root in (%s, %s)" % (s, t))
if disjoint < 0: disjoint = -disjoint else: disjoint = None
f, s, t, K, eps=eps, steps=steps, disjoint=disjoint, fast=fast)
return (-t, -s) else:
def dup_inner_isolate_real_roots(f, K, eps=None, fast=False): """Internal function for isolation positive roots up to given precision.
References: =========== 1. Alkiviadis G. Akritas and Adam W. Strzebonski: A Comparative Study of Two Real Root Isolation Methods . Nonlinear Analysis: Modelling and Control, Vol. 10, No. 4, 297-304, 2005. 2. Alkiviadis G. Akritas, Adam W. Strzebonski and Panagiotis S. Vigklas: Improving the Performance of the Continued Fractions Method Using new Bounds of Positive Roots. Nonlinear Analysis: Modelling and Control, Vol. 13, No. 3, 265-279, 2008. """
f, (a, b, c, d), K, eps=eps, fast=fast, mobius=True)] else:
else: A = K.zero
f = dup_scale(f, A, K) a, c, A = A*a, A*c, K.one
roots.append((f, (b, b, d, d))) f = dup_rshift(f, 1, K)
roots.append(dup_inner_refine_real_root( f, (a, b, c, d), K, eps=eps, fast=fast, mobius=True)) continue
roots.append((f1, (b1, b1, d1, d1))) f1, r = dup_rshift(f1, 1, K), 1
f2 = dup_rshift(f2, 1, K)
else:
f1 = dup_shift(dup_reverse(f), K.one, K)
if not dup_TC(f1, K): f1 = dup_rshift(f1, 1, K)
f1, (a1, b1, c1, d1), K, eps=eps, fast=fast, mobius=True)) else:
f2 = dup_rshift(f2, 1, K)
f2, (a2, b2, c2, d2), K, eps=eps, fast=fast, mobius=True)) else: stack.append((a2, b2, c2, d2, f2, k2))
def _discard_if_outside_interval(f, M, inf, sup, K, negative, fast, mobius): """Discard an isolating interval if outside ``(inf, sup)``. """
return u, v else: else:
def dup_inner_isolate_positive_roots(f, K, eps=None, inf=None, sup=None, fast=False, mobius=False): """Iteratively compute disjoint positive root isolation intervals. """ return []
else: results = roots
def dup_inner_isolate_negative_roots(f, K, inf=None, sup=None, eps=None, fast=False, mobius=False): """Iteratively compute disjoint negative root isolation intervals. """
else: results = roots
def _isolate_zero(f, K, inf, sup, basis=False, sqf=False): """Handle special case of CF algorithm when ``f`` is homogeneous. """
F = K.get_field()
if (inf is None or inf <= 0) and (sup is None or 0 <= sup): if not sqf: if not basis: return [((F.zero, F.zero), j)], f else: return [((F.zero, F.zero), j, [K.one, K.zero])], f else: return [(F.zero, F.zero)], f
def dup_isolate_real_roots_sqf(f, K, eps=None, inf=None, sup=None, fast=False, blackbox=False): """Isolate real roots of a square-free polynomial using the Vincent-Akritas-Strzebonski (VAS) CF approach.
References: =========== 1. Alkiviadis G. Akritas and Adam W. Strzebonski: A Comparative Study of Two Real Root Isolation Methods. Nonlinear Analysis: Modelling and Control, Vol. 10, No. 4, 297-304, 2005. 2. Alkiviadis G. Akritas, Adam W. Strzebonski and Panagiotis S. Vigklas: Improving the Performance of the Continued Fractions Method Using New Bounds of Positive Roots. Nonlinear Analysis: Modelling and Control, Vol. 13, No. 3, 265-279, 2008. """ (_, f), K = dup_clear_denoms(f, K, convert=True), K.get_ring() raise DomainError("isolation of real roots not supported over %s" % K)
return []
return roots else:
def dup_isolate_real_roots(f, K, eps=None, inf=None, sup=None, basis=False, fast=False): """Isolate real roots using Vincent-Akritas-Strzebonski (VAS) continued fractions approach.
References: =========== 1. Alkiviadis G. Akritas and Adam W. Strzebonski: A Comparative Study of Two Real Root Isolation Methods. Nonlinear Analysis: Modelling and Control, Vol. 10, No. 4, 297-304, 2005. 2. Alkiviadis G. Akritas, Adam W. Strzebonski and Panagiotis S. Vigklas: Improving the Performance of the Continued Fractions Method Using New Bounds of Positive Roots. Nonlinear Analysis: Modelling and Control, Vol. 13, No. 3, 265-279, 2008. """ if K.is_QQ: (_, f), K = dup_clear_denoms(f, K, convert=True), K.get_ring() elif not K.is_ZZ: raise DomainError("isolation of real roots not supported over %s" % K)
if dup_degree(f) <= 0: return []
I_zero, f = _isolate_zero(f, K, inf, sup, basis=basis, sqf=False)
_, factors = dup_sqf_list(f, K)
if len(factors) == 1: ((f, k),) = factors
I_neg = dup_inner_isolate_negative_roots(f, K, eps=eps, inf=inf, sup=sup, fast=fast) I_pos = dup_inner_isolate_positive_roots(f, K, eps=eps, inf=inf, sup=sup, fast=fast)
I_neg = [ ((u, v), k) for u, v in I_neg ] I_pos = [ ((u, v), k) for u, v in I_pos ] else: I_neg, I_pos = _real_isolate_and_disjoin(factors, K, eps=eps, inf=inf, sup=sup, basis=basis, fast=fast)
return sorted(I_neg + I_zero + I_pos)
def dup_isolate_real_roots_list(polys, K, eps=None, inf=None, sup=None, strict=False, basis=False, fast=False): """Isolate real roots of a list of square-free polynomial using Vincent-Akritas-Strzebonski (VAS) CF approach.
References: =========== 1. Alkiviadis G. Akritas and Adam W. Strzebonski: A Comparative Study of Two Real Root Isolation Methods. Nonlinear Analysis: Modelling and Control, Vol. 10, No. 4, 297-304, 2005. 2. Alkiviadis G. Akritas, Adam W. Strzebonski and Panagiotis S. Vigklas: Improving the Performance of the Continued Fractions Method Using New Bounds of Positive Roots. Nonlinear Analysis: Modelling and Control, Vol. 13, No. 3, 265-279, 2008. """
elif not K.is_ZZ: raise DomainError("isolation of real roots not supported over %s" % K)
else: factors_dict[f][i] = k
inf=inf, sup=sup, strict=strict, basis=basis, fast=fast)
else: I_zero = [((F.zero, F.zero), zero_indices)] else:
def _disjoint_p(M, N, strict=False): """Check if Mobius transforms define disjoint intervals. """
return True
return a2*d1 >= c2*b1 or b2*c1 <= d2*a1 else:
def _real_isolate_and_disjoin(factors, K, eps=None, inf=None, sup=None, strict=False, basis=False, fast=False): """Isolate real roots of a list of polynomials and disjoin intervals. """
I_neg = [ ((-v, -u), k) for ((u, v), k, _) in I_neg ] I_pos = [ (( u, v), k) for ((u, v), k, _) in I_pos ] else:
def dup_count_real_roots(f, K, inf=None, sup=None): """Returns the number of distinct real roots of ``f`` in ``[inf, sup]``. """ if dup_degree(f) <= 0: return 0
if not K.has_Field: R, K = K, K.get_field() f = dup_convert(f, R, K)
sturm = dup_sturm(f, K)
if inf is None: signs_inf = dup_sign_variations([ dup_LC(s, K)*(-1)**dup_degree(s) for s in sturm ], K) else: signs_inf = dup_sign_variations([ dup_eval(s, inf, K) for s in sturm ], K)
if sup is None: signs_sup = dup_sign_variations([ dup_LC(s, K) for s in sturm ], K) else: signs_sup = dup_sign_variations([ dup_eval(s, sup, K) for s in sturm ], K)
count = abs(signs_inf - signs_sup)
if inf is not None and not dup_eval(f, inf, K): count += 1
return count
OO = 'OO' # Origin of (re, im) coordinate system
Q1 = 'Q1' # Quadrant #1 (++): re > 0 and im > 0 Q2 = 'Q2' # Quadrant #2 (-+): re < 0 and im > 0 Q3 = 'Q3' # Quadrant #3 (--): re < 0 and im < 0 Q4 = 'Q4' # Quadrant #4 (+-): re > 0 and im < 0
A1 = 'A1' # Axis #1 (+0): re > 0 and im = 0 A2 = 'A2' # Axis #2 (0+): re = 0 and im > 0 A3 = 'A3' # Axis #3 (-0): re < 0 and im = 0 A4 = 'A4' # Axis #4 (0-): re = 0 and im < 0
_rules_simple = { # Q --> Q (same) => no change (Q1, Q1): 0, (Q2, Q2): 0, (Q3, Q3): 0, (Q4, Q4): 0,
# A -- CCW --> Q => +1/4 (CCW) (A1, Q1): 1, (A2, Q2): 1, (A3, Q3): 1, (A4, Q4): 1,
# A -- CW --> Q => -1/4 (CCW) (A1, Q4): 2, (A2, Q1): 2, (A3, Q2): 2, (A4, Q3): 2,
# Q -- CCW --> A => +1/4 (CCW) (Q1, A2): 3, (Q2, A3): 3, (Q3, A4): 3, (Q4, A1): 3,
# Q -- CW --> A => -1/4 (CCW) (Q1, A1): 4, (Q2, A2): 4, (Q3, A3): 4, (Q4, A4): 4,
# Q -- CCW --> Q => +1/2 (CCW) (Q1, Q2): +5, (Q2, Q3): +5, (Q3, Q4): +5, (Q4, Q1): +5,
# Q -- CW --> Q => -1/2 (CW) (Q1, Q4): -5, (Q2, Q1): -5, (Q3, Q2): -5, (Q4, Q3): -5, }
_rules_ambiguous = { # A -- CCW --> Q => { +1/4 (CCW), -9/4 (CW) } (A1, OO, Q1): -1, (A2, OO, Q2): -1, (A3, OO, Q3): -1, (A4, OO, Q4): -1,
# A -- CW --> Q => { -1/4 (CCW), +7/4 (CW) } (A1, OO, Q4): -2, (A2, OO, Q1): -2, (A3, OO, Q2): -2, (A4, OO, Q3): -2,
# Q -- CCW --> A => { +1/4 (CCW), -9/4 (CW) } (Q1, OO, A2): -3, (Q2, OO, A3): -3, (Q3, OO, A4): -3, (Q4, OO, A1): -3,
# Q -- CW --> A => { -1/4 (CCW), +7/4 (CW) } (Q1, OO, A1): -4, (Q2, OO, A2): -4, (Q3, OO, A3): -4, (Q4, OO, A4): -4,
# A -- OO --> A => { +1 (CCW), -1 (CW) } (A1, A3): 7, (A2, A4): 7, (A3, A1): 7, (A4, A2): 7,
(A1, OO, A3): 7, (A2, OO, A4): 7, (A3, OO, A1): 7, (A4, OO, A2): 7,
# Q -- DIA --> Q => { +1 (CCW), -1 (CW) } (Q1, Q3): 8, (Q2, Q4): 8, (Q3, Q1): 8, (Q4, Q2): 8,
(Q1, OO, Q3): 8, (Q2, OO, Q4): 8, (Q3, OO, Q1): 8, (Q4, OO, Q2): 8,
# A --- R ---> A => { +1/2 (CCW), -3/2 (CW) } (A1, A2): 9, (A2, A3): 9, (A3, A4): 9, (A4, A1): 9,
(A1, OO, A2): 9, (A2, OO, A3): 9, (A3, OO, A4): 9, (A4, OO, A1): 9,
# A --- L ---> A => { +3/2 (CCW), -1/2 (CW) } (A1, A4): 10, (A2, A1): 10, (A3, A2): 10, (A4, A3): 10,
(A1, OO, A4): 10, (A2, OO, A1): 10, (A3, OO, A2): 10, (A4, OO, A3): 10,
# Q --- 1 ---> A => { +3/4 (CCW), -5/4 (CW) } (Q1, A3): 11, (Q2, A4): 11, (Q3, A1): 11, (Q4, A2): 11,
(Q1, OO, A3): 11, (Q2, OO, A4): 11, (Q3, OO, A1): 11, (Q4, OO, A2): 11,
# Q --- 2 ---> A => { +5/4 (CCW), -3/4 (CW) } (Q1, A4): 12, (Q2, A1): 12, (Q3, A2): 12, (Q4, A3): 12,
(Q1, OO, A4): 12, (Q2, OO, A1): 12, (Q3, OO, A2): 12, (Q4, OO, A3): 12,
# A --- 1 ---> Q => { +5/4 (CCW), -3/4 (CW) } (A1, Q3): 13, (A2, Q4): 13, (A3, Q1): 13, (A4, Q2): 13,
(A1, OO, Q3): 13, (A2, OO, Q4): 13, (A3, OO, Q1): 13, (A4, OO, Q2): 13,
# A --- 2 ---> Q => { +3/4 (CCW), -5/4 (CW) } (A1, Q2): 14, (A2, Q3): 14, (A3, Q4): 14, (A4, Q1): 14,
(A1, OO, Q2): 14, (A2, OO, Q3): 14, (A3, OO, Q4): 14, (A4, OO, Q1): 14,
# Q --> OO --> Q => { +1/2 (CCW), -3/2 (CW) } (Q1, OO, Q2): 15, (Q2, OO, Q3): 15, (Q3, OO, Q4): 15, (Q4, OO, Q1): 15,
# Q --> OO --> Q => { +3/2 (CCW), -1/2 (CW) } (Q1, OO, Q4): 16, (Q2, OO, Q1): 16, (Q3, OO, Q2): 16, (Q4, OO, Q3): 16,
# A --> OO --> A => { +2 (CCW), 0 (CW) } (A1, OO, A1): 17, (A2, OO, A2): 17, (A3, OO, A3): 17, (A4, OO, A4): 17,
# Q --> OO --> Q => { +2 (CCW), 0 (CW) } (Q1, OO, Q1): 18, (Q2, OO, Q2): 18, (Q3, OO, Q3): 18, (Q4, OO, Q4): 18, }
_values = { 0: [( 0, 1)], 1: [(+1, 4)], 2: [(-1, 4)], 3: [(+1, 4)], 4: [(-1, 4)], -1: [(+9, 4), (+1, 4)], -2: [(+7, 4), (-1, 4)], -3: [(+9, 4), (+1, 4)], -4: [(+7, 4), (-1, 4)], +5: [(+1, 2)], -5: [(-1, 2)], 7: [(+1, 1), (-1, 1)], 8: [(+1, 1), (-1, 1)], 9: [(+1, 2), (-3, 2)], 10: [(+3, 2), (-1, 2)], 11: [(+3, 4), (-5, 4)], 12: [(+5, 4), (-3, 4)], 13: [(+5, 4), (-3, 4)], 14: [(+3, 4), (-5, 4)], 15: [(+1, 2), (-3, 2)], 16: [(+3, 2), (-1, 2)], 17: [(+2, 1), ( 0, 1)], 18: [(+2, 1), ( 0, 1)], }
def _classify_point(re, im): """Return the half-axis (or origin) on which (re, im) point is located. """ return OO
return A2 else: else:
def _intervals_to_quadrants(intervals, f1, f2, s, t, F): """Generate a sequence of extended quadrants from a list of critical points. """
(a, b), _, _ = intervals[0]
if a == b == s: if len(intervals) == 1: if dup_eval(f2, t, F) > 0: return [OO, A2] else: return [OO, A4] else: (a, _), _, _ = intervals[1]
if dup_eval(f2, (s + a)/2, F) > 0: Q.extend([OO, A2]) f2_sgn = +1 else: Q.extend([OO, A4]) f2_sgn = -1
intervals = intervals[1:] else: if dup_eval(f2, s, F) > 0: Q.append(A2) f2_sgn = +1 else: Q.append(A4) f2_sgn = -1
for (a, _), indices, _ in intervals: Q.append(OO)
if indices[1] % 2 == 1: f2_sgn = -f2_sgn
if a != t: if f2_sgn > 0: Q.append(A2) else: Q.append(A4)
return Q
if len(intervals) == 1: if dup_eval(f1, t, F) > 0: return [OO, A1] else: return [OO, A3] else: (a, _), _, _ = intervals[1]
if dup_eval(f1, (s + a)/2, F) > 0: Q.extend([OO, A1]) f1_sgn = +1 else: Q.extend([OO, A3]) f1_sgn = -1
intervals = intervals[1:] else: else:
else:
else:
else:
else:
(+1, +1): Q1, (-1, +1): Q2, (-1, -1): Q3, (+1, -1): Q4, }
def _traverse_quadrants(Q_L1, Q_L2, Q_L3, Q_L4, exclude=None): """Transform sequences of quadrants to a sequence of rules. """
(0, 1): 1, (1, 2): 1, (2, 3): 0, (3, 0): 1, } else:
(0, 1): 0, (1, 2): 0, (2, 3): 0, (3, 0): 0, }
exclude = set(exclude)
for i, edge in enumerate(['S', 'E', 'N', 'W']): if edge in exclude: edges[i] = 1
for i, corner in enumerate(['SW', 'SE', 'NE', 'NW']): if corner in exclude: corners[((i - 1) % 4, i)] = 1
Q = Q[:-1]
j, Q = (i - 1) % 4, Q[1:] qq = (QQ[j][-2], OO, Q[0])
if qq in _rules_ambiguous: rules.append((_rules_ambiguous[qq], corners[(j, i)])) else: raise NotImplementedError("3 element rule (corner): " + str(qq))
elif qq in _rules_ambiguous: rules.append((_rules_ambiguous[qq], edges[i])) else: raise NotImplementedError("2 element rule (inside): " + str(qq)) else:
else: raise NotImplementedError("3 element rule (edge): " + str(qq))
def _reverse_intervals(intervals): """Reverse intervals for traversal from right to left and from top to bottom. """
def _winding_number(T, field): """Compute the winding number of the input polynomial, i.e. the number of roots. """
def dup_count_complex_roots(f, K, inf=None, sup=None, exclude=None): """Count all roots in [u + v*I, s + t*I] rectangle using Collins-Krandick algorithm. """ if not K.is_ZZ and not K.is_QQ: raise DomainError("complex root counting is not supported over %s" % K)
if K.is_ZZ: R, F = K, K.get_field() else: R, F = K.get_ring(), K
f = dup_convert(f, K, F)
if inf is None or sup is None: n, lc = dup_degree(f), abs(dup_LC(f, F)) B = 2*max([ F.quo(abs(c), lc) for c in f ])
if inf is None: (u, v) = (-B, -B) else: (u, v) = inf
if sup is None: (s, t) = (+B, +B) else: (s, t) = sup
f1, f2 = dup_real_imag(f, F)
f1L1F = dmp_eval_in(f1, v, 1, 1, F) f2L1F = dmp_eval_in(f2, v, 1, 1, F)
_, f1L1R = dup_clear_denoms(f1L1F, F, R, convert=True) _, f2L1R = dup_clear_denoms(f2L1F, F, R, convert=True)
f1L2F = dmp_eval_in(f1, s, 0, 1, F) f2L2F = dmp_eval_in(f2, s, 0, 1, F)
_, f1L2R = dup_clear_denoms(f1L2F, F, R, convert=True) _, f2L2R = dup_clear_denoms(f2L2F, F, R, convert=True)
f1L3F = dmp_eval_in(f1, t, 1, 1, F) f2L3F = dmp_eval_in(f2, t, 1, 1, F)
_, f1L3R = dup_clear_denoms(f1L3F, F, R, convert=True) _, f2L3R = dup_clear_denoms(f2L3F, F, R, convert=True)
f1L4F = dmp_eval_in(f1, u, 0, 1, F) f2L4F = dmp_eval_in(f2, u, 0, 1, F)
_, f1L4R = dup_clear_denoms(f1L4F, F, R, convert=True) _, f2L4R = dup_clear_denoms(f2L4F, F, R, convert=True)
S_L1 = [f1L1R, f2L1R] S_L2 = [f1L2R, f2L2R] S_L3 = [f1L3R, f2L3R] S_L4 = [f1L4R, f2L4R]
I_L1 = dup_isolate_real_roots_list(S_L1, R, inf=u, sup=s, fast=True, basis=True, strict=True) I_L2 = dup_isolate_real_roots_list(S_L2, R, inf=v, sup=t, fast=True, basis=True, strict=True) I_L3 = dup_isolate_real_roots_list(S_L3, R, inf=u, sup=s, fast=True, basis=True, strict=True) I_L4 = dup_isolate_real_roots_list(S_L4, R, inf=v, sup=t, fast=True, basis=True, strict=True)
I_L3 = _reverse_intervals(I_L3) I_L4 = _reverse_intervals(I_L4)
Q_L1 = _intervals_to_quadrants(I_L1, f1L1F, f2L1F, u, s, F) Q_L2 = _intervals_to_quadrants(I_L2, f1L2F, f2L2F, v, t, F) Q_L3 = _intervals_to_quadrants(I_L3, f1L3F, f2L3F, s, u, F) Q_L4 = _intervals_to_quadrants(I_L4, f1L4F, f2L4F, t, v, F)
T = _traverse_quadrants(Q_L1, Q_L2, Q_L3, Q_L4, exclude=exclude)
return _winding_number(T, F)
def _vertical_bisection(N, a, b, I, Q, F1, F2, f1, f2, F): """Vertical bisection step in Collins-Krandick root isolation algorithm. """
else: else: else: a, b = dup_refine_real_root(h, a, b, F.get_ring(), disjoint=x, fast=True)
if b <= x: I_L1_L.append(((a, b), indices, h)) if a >= x: I_L1_R.append(((a, b), indices, h))
else: else: else: a, b = dup_refine_real_root(h, a, b, F.get_ring(), disjoint=x, fast=True)
if b <= x: I_L3_L.append(((b, a), indices, h)) if a >= x: I_L3_R.append(((b, a), indices, h))
def _horizontal_bisection(N, a, b, I, Q, F1, F2, f1, f2, F): """Horizontal bisection step in Collins-Krandick root isolation algorithm. """
else: else: else:
else: else: else:
def _depth_first_select(rectangles): """Find a rectangle of minimum area for bisection. """
def _rectangle_small_p(a, b, eps): """Return ``True`` if the given rectangle is small enough. """
return s - u < eps and t - v < eps else:
def dup_isolate_complex_roots_sqf(f, K, eps=None, inf=None, sup=None, blackbox=False): """Isolate complex roots of a square-free polynomial using Collins-Krandick algorithm. """ raise DomainError("isolation of complex roots is not supported over %s" % K)
return []
else: R, F = K.get_ring(), K
u = inf
s = sup
raise ValueError("not a valid complex isolation rectangle")
return []
else:
else: else:
a, b, I_B, Q_B, F1_B, F2_B, f1, f2, F)) else:
c, d, I_U, Q_U, F1_U, F2_U, f1, f2, F)) else: rectangles.append(D_U)
else: return [ r.as_tuple() for r in roots ]
def dup_isolate_all_roots_sqf(f, K, eps=None, inf=None, sup=None, fast=False, blackbox=False): """Isolate real and complex roots of a square-free polynomial ``f``. """ return ( dup_isolate_real_roots_sqf( f, K, eps=eps, inf=inf, sup=sup, fast=fast, blackbox=blackbox), dup_isolate_complex_roots_sqf(f, K, eps=eps, inf=inf, sup=sup, blackbox=blackbox))
def dup_isolate_all_roots(f, K, eps=None, inf=None, sup=None, fast=False): """Isolate real and complex roots of a non-square-free polynomial ``f``. """ if not K.is_ZZ and not K.is_QQ: raise DomainError("isolation of real and complex roots is not supported over %s" % K)
_, factors = dup_sqf_list(f, K)
if len(factors) == 1: ((f, k),) = factors
real_part, complex_part = dup_isolate_all_roots_sqf( f, K, eps=eps, inf=inf, sup=sup, fast=fast)
real_part = [ ((a, b), k) for (a, b) in real_part ] complex_part = [ ((a, b), k) for (a, b) in complex_part ]
return real_part, complex_part else: raise NotImplementedError( "only trivial square-free polynomials are supported")
class RealInterval(object): """A fully qualified representation of a real isolation interval. """
def __init__(self, data, f, dom): """Initialize new real interval with complete information. """
else: raise ValueError("can't refine a real root in (%s, %s)" % (s, t))
dup_strip([c, d]), dom)
else: self.mobius = data[:-1] self.neg = data[-1]
@property def a(self): """Return the position of the left end. """
else: return -field(a, c)
@property def b(self): """Return the position of the right end. """
@property def dx(self): """Return width of the real isolating interval. """ return self.b - self.a
@property def center(self): """Return the center of the real isolating interval. """
def as_tuple(self): """Return tuple representation of real isolating interval. """ return (self.a, self.b)
def __repr__(self): return "(%s, %s)" % (self.a, self.b)
def is_disjoint(self, other): """Return ``True`` if two isolation intervals are disjoint. """
def _inner_refine(self): """Internal one step real root refinement procedure. """ if self.mobius is None: return self
f, mobius = dup_inner_refine_real_root( self.f, self.mobius, self.dom, steps=1, mobius=True)
return RealInterval(mobius + (self.neg,), f, self.dom)
def refine_disjoint(self, other): """Refine an isolating interval until it is disjoint with another one. """ expr, other = expr._inner_refine(), other._inner_refine()
def refine_size(self, dx): """Refine an isolating interval until it is of sufficiently small size. """ expr = self while not (expr.dx < dx): expr = expr._inner_refine()
return expr
def refine_step(self, steps=1): """Perform several steps of real root refinement algorithm. """ expr = self for _ in range(steps): expr = expr._inner_refine()
return expr
def refine(self): """Perform one step of real root refinement algorithm. """ return self._inner_refine()
class ComplexInterval(object): """A fully qualified representation of a complex isolation interval. The printed form is shown as (x1, y1) x (x2, y2): the southwest x northeast coordinates of the interval's rectangle."""
def __init__(self, a, b, I, Q, F1, F2, f1, f2, dom, conj=False): """Initialize new complex interval with complete information. """
@property def ax(self): """Return ``x`` coordinate of south-western corner. """
@property def ay(self): """Return ``y`` coordinate of south-western corner. """ else:
@property def bx(self): """Return ``x`` coordinate of north-eastern corner. """
@property def by(self): """Return ``y`` coordinate of north-eastern corner. """ else:
@property def dx(self): """Return width of the complex isolating interval. """ return self.b[0] - self.a[0]
@property def dy(self): """Return height of the complex isolating interval. """ return self.b[1] - self.a[1]
@property def center(self): """Return the center of the complex isolating interval. """ return ((self.ax + self.bx)/2, (self.ay + self.by)/2)
def as_tuple(self): """Return tuple representation of complex isolating interval. """ return ((self.ax, self.ay), (self.bx, self.by))
def __repr__(self): return "(%s, %s) x (%s, %s)" % (self.ax, self.bx, self.ay, self.by)
def conjugate(self): """This complex interval really is located in lower half-plane. """ self.F1, self.F2, self.f1, self.f2, self.dom, conj=True)
def is_disjoint(self, other): """Return ``True`` if two isolation intervals are disjoint. """
def _inner_refine(self): """Internal one step complex root refinement procedure. """
else: else:
else:
def refine_disjoint(self, other): """Refine an isolating interval until it is disjoint with another one. """ expr, other = expr._inner_refine(), other._inner_refine()
def refine_size(self, dx, dy=None): """Refine an isolating interval until it is of sufficiently small size. """ if dy is None: dy = dx expr = self while not (expr.dx < dx and expr.dy < dy): expr = expr._inner_refine()
return expr
def refine_step(self, steps=1): """Perform several steps of complex root refinement algorithm. """ expr = self for _ in range(steps): expr = expr._inner_refine()
return expr
def refine(self): """Perform one step of complex root refinement algorithm. """ return self._inner_refine() |