| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190 |
- """Real and complex root isolation and refinement algorithms. """
- from sympy.polys.densearith import (
- dup_neg, dup_rshift, dup_rem,
- dup_l2_norm_squared)
- from sympy.polys.densebasic import (
- dup_LC, dup_TC, dup_degree,
- dup_strip, dup_reverse,
- dup_convert,
- dup_terms_gcd)
- 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.euclidtools import (
- dup_discriminant)
- from sympy.polys.factortools import (
- dup_factor_list)
- from sympy.polys.polyerrors import (
- RefinementFailed,
- DomainError,
- PolynomialError)
- from sympy.polys.sqfreetools import (
- dup_sqf_part, dup_sqf_list)
- 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.is_Field:
- raise DomainError("Cannot 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.
- References
- ==========
- .. [1] 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.
- """
- n, P = len(f), []
- t = n * [K.one]
- if dup_LC(f, K) < 0:
- f = dup_neg(f, K)
- f = list(reversed(f))
- for i in range(0, n):
- if f[i] >= 0:
- continue
- a, QL = K.log(-f[i], 2), []
- for j in range(i + 1, n):
- if f[j] <= 0:
- continue
- q = t[j] + a - K.log(f[j], 2)
- QL.append([q // (j - i), j])
- if not QL:
- continue
- q = min(QL)
- t[q[1]] = t[q[1]] + 1
- P.append(q[0])
- if not P:
- return None
- else:
- return K.get_field()(2)**(max(P) + 1)
- 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.
- References
- ==========
- .. [1] 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.
- """
- bound = dup_root_upper_bound(dup_reverse(f), K)
- if bound is not None:
- return 1/bound
- else:
- return None
- def dup_cauchy_upper_bound(f, K):
- """
- Compute the Cauchy upper bound on the absolute value of all roots of f,
- real or complex.
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Geometrical_properties_of_polynomial_roots#Lagrange's_and_Cauchy's_bounds
- """
- n = dup_degree(f)
- if n < 1:
- raise PolynomialError('Polynomial has no roots.')
- if K.is_ZZ:
- L = K.get_field()
- f, K = dup_convert(f, K, L), L
- elif not K.is_QQ or K.is_RR or K.is_CC:
- # We need to compute absolute value, and we are not supporting cases
- # where this would take us outside the domain (or its quotient field).
- raise DomainError('Cauchy bound not supported over %s' % K)
- else:
- f = f[:]
- while K.is_zero(f[-1]):
- f.pop()
- if len(f) == 1:
- # Monomial. All roots are zero.
- return K.zero
- lc = f[0]
- return K.one + max(abs(n / lc) for n in f[1:])
- def dup_cauchy_lower_bound(f, K):
- """Compute the Cauchy lower bound on the absolute value of all non-zero
- roots of f, real or complex."""
- g = dup_reverse(f)
- if len(g) < 2:
- raise PolynomialError('Polynomial has no non-zero roots.')
- if K.is_ZZ:
- K = K.get_field()
- b = dup_cauchy_upper_bound(g, K)
- return K.one / b
- def dup_mignotte_sep_bound_squared(f, K):
- """
- Return the square of the Mignotte lower bound on separation between
- distinct roots of f. The square is returned so that the bound lies in
- K or its quotient field.
- References
- ==========
- .. [1] Mignotte, Maurice. "Some useful bounds." Computer algebra.
- Springer, Vienna, 1982. 259-263.
- https://people.dm.unipi.it/gianni/AC-EAG/Mignotte.pdf
- """
- n = dup_degree(f)
- if n < 2:
- raise PolynomialError('Polynomials of degree < 2 have no distinct roots.')
- if K.is_ZZ:
- L = K.get_field()
- f, K = dup_convert(f, K, L), L
- elif not K.is_QQ or K.is_RR or K.is_CC:
- # We need to compute absolute value, and we are not supporting cases
- # where this would take us outside the domain (or its quotient field).
- raise DomainError('Mignotte bound not supported over %s' % K)
- D = dup_discriminant(f, K)
- l2sq = dup_l2_norm_squared(f, K)
- return K(3)*K.abs(D) / ( K(n)**(n+1) * l2sq**(n-1) )
- def _mobius_from_interval(I, field):
- """Convert an open interval to a Mobius transform. """
- s, t = I
- a, c = field.numer(s), field.denom(s)
- b, d = field.numer(t), field.denom(t)
- return a, b, c, d
- def _mobius_to_interval(M, field):
- """Convert a Mobius transform to an open interval. """
- a, b, c, d = M
- s, t = field(a, c), field(b, d)
- if s <= t:
- return (s, t)
- else:
- return (t, s)
- def dup_step_refine_real_root(f, M, K, fast=False):
- """One step of positive real root refinement algorithm. """
- a, b, c, d = M
- if a == b and c == d:
- return f, (a, b, c, d)
- A = dup_root_lower_bound(f, K)
- if A is not None:
- A = K(int(A))
- else:
- A = K.zero
- if fast and A > 16:
- f = dup_scale(f, A, K)
- a, c, A = A*a, A*c, K.one
- if A >= K.one:
- f = dup_shift(f, A, K)
- b, d = A*a + b, A*c + d
- if not dup_eval(f, K.zero, K):
- return f, (b, b, d, d)
- f, g = dup_shift(f, K.one, K), f
- a1, b1, c1, d1 = a, a + b, c, c + d
- if not dup_eval(f, K.zero, K):
- return f, (b1, b1, d1, d1)
- k = dup_sign_variations(f, K)
- if k == 1:
- a, b, c, d = a1, b1, c1, d1
- else:
- f = dup_shift(dup_reverse(g), K.one, K)
- if not dup_eval(f, K.zero, K):
- f = dup_rshift(f, 1, K)
- a, b, c, d = b, a + b, d, c + d
- return f, (a, b, c, d)
- 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. """
- F = K.get_field()
- if len(M) == 2:
- a, b, c, d = _mobius_from_interval(M, F)
- else:
- a, b, c, d = M
- while not c:
- f, (a, b, c, d) = dup_step_refine_real_root(f, (a, b, c,
- d), K, fast=fast)
- if eps is not None and steps is not None:
- 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:
- if eps is not None:
- 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)
- if steps is not None:
- for i in range(0, steps):
- f, (a, b, c, d) = dup_step_refine_real_root(f, (a, b, c, d), K, fast=fast)
- if disjoint is not None:
- while True:
- u, v = _mobius_to_interval((a, b, c, d), F)
- if v <= disjoint or disjoint <= u:
- break
- else:
- f, (a, b, c, d) = dup_step_refine_real_root(f, (a, b, c, d), K, fast=fast)
- if not mobius:
- return _mobius_to_interval((a, b, c, d), F)
- else:
- return f, (a, b, c, d)
- 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)`. """
- a, b, c, d = _mobius_from_interval((s, t), K.get_field())
- f = dup_transform(f, dup_strip([a, b]),
- dup_strip([c, d]), K)
- if dup_sign_variations(f, K) != 1:
- raise RefinementFailed("there should be exactly one root in (%s, %s) interval" % (s, t))
- return dup_inner_refine_real_root(f, (a, b, c, d), K, eps=eps, steps=steps, disjoint=disjoint, fast=fast)
- 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. """
- if K.is_QQ:
- (_, f), K = dup_clear_denoms(f, K, convert=True), K.get_ring()
- elif not K.is_ZZ:
- raise DomainError("real root refinement not supported over %s" % K)
- if s == t:
- return (s, t)
- if s > t:
- s, t = t, s
- negative = False
- if s < 0:
- if t <= 0:
- f, s, t, negative = dup_mirror(f, K), -t, -s, True
- else:
- raise ValueError("Cannot refine a real root in (%s, %s)" % (s, t))
- if negative and disjoint is not None:
- if disjoint < 0:
- disjoint = -disjoint
- else:
- disjoint = None
- s, t = dup_outer_refine_real_root(
- f, s, t, K, eps=eps, steps=steps, disjoint=disjoint, fast=fast)
- if negative:
- return (-t, -s)
- else:
- return ( s, t)
- 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.
- """
- a, b, c, d = K.one, K.zero, K.zero, K.one
- k = dup_sign_variations(f, K)
- if k == 0:
- return []
- if k == 1:
- roots = [dup_inner_refine_real_root(
- f, (a, b, c, d), K, eps=eps, fast=fast, mobius=True)]
- else:
- roots, stack = [], [(a, b, c, d, f, k)]
- while stack:
- a, b, c, d, f, k = stack.pop()
- A = dup_root_lower_bound(f, K)
- if A is not None:
- A = K(int(A))
- else:
- A = K.zero
- if fast and A > 16:
- f = dup_scale(f, A, K)
- a, c, A = A*a, A*c, K.one
- if A >= K.one:
- f = dup_shift(f, A, K)
- b, d = A*a + b, A*c + d
- if not dup_TC(f, K):
- roots.append((f, (b, b, d, d)))
- f = dup_rshift(f, 1, K)
- k = dup_sign_variations(f, K)
- if k == 0:
- continue
- if k == 1:
- roots.append(dup_inner_refine_real_root(
- f, (a, b, c, d), K, eps=eps, fast=fast, mobius=True))
- continue
- f1 = dup_shift(f, K.one, K)
- a1, b1, c1, d1, r = a, a + b, c, c + d, 0
- if not dup_TC(f1, K):
- roots.append((f1, (b1, b1, d1, d1)))
- f1, r = dup_rshift(f1, 1, K), 1
- k1 = dup_sign_variations(f1, K)
- k2 = k - k1 - r
- a2, b2, c2, d2 = b, a + b, d, c + d
- if k2 > 1:
- f2 = dup_shift(dup_reverse(f), K.one, K)
- if not dup_TC(f2, K):
- f2 = dup_rshift(f2, 1, K)
- k2 = dup_sign_variations(f2, K)
- else:
- f2 = None
- if k1 < k2:
- a1, a2, b1, b2 = a2, a1, b2, b1
- c1, c2, d1, d2 = c2, c1, d2, d1
- f1, f2, k1, k2 = f2, f1, k2, k1
- if not k1:
- continue
- if f1 is None:
- f1 = dup_shift(dup_reverse(f), K.one, K)
- if not dup_TC(f1, K):
- f1 = dup_rshift(f1, 1, K)
- if k1 == 1:
- roots.append(dup_inner_refine_real_root(
- f1, (a1, b1, c1, d1), K, eps=eps, fast=fast, mobius=True))
- else:
- stack.append((a1, b1, c1, d1, f1, k1))
- if not k2:
- continue
- if f2 is None:
- f2 = dup_shift(dup_reverse(f), K.one, K)
- if not dup_TC(f2, K):
- f2 = dup_rshift(f2, 1, K)
- if k2 == 1:
- roots.append(dup_inner_refine_real_root(
- f2, (a2, b2, c2, d2), K, eps=eps, fast=fast, mobius=True))
- else:
- stack.append((a2, b2, c2, d2, f2, k2))
- return roots
- def _discard_if_outside_interval(f, M, inf, sup, K, negative, fast, mobius):
- """Discard an isolating interval if outside ``(inf, sup)``. """
- F = K.get_field()
- while True:
- u, v = _mobius_to_interval(M, F)
- if negative:
- u, v = -v, -u
- if (inf is None or u >= inf) and (sup is None or v <= sup):
- if not mobius:
- return u, v
- else:
- return f, M
- elif (sup is not None and u > sup) or (inf is not None and v < inf):
- return None
- else:
- f, M = dup_step_refine_real_root(f, M, K, fast=fast)
- 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. """
- if sup is not None and sup < 0:
- return []
- roots = dup_inner_isolate_real_roots(f, K, eps=eps, fast=fast)
- F, results = K.get_field(), []
- if inf is not None or sup is not None:
- for f, M in roots:
- result = _discard_if_outside_interval(f, M, inf, sup, K, False, fast, mobius)
- if result is not None:
- results.append(result)
- elif not mobius:
- results.extend(_mobius_to_interval(M, F) for _, M in roots)
- else:
- results = roots
- return results
- 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. """
- if inf is not None and inf >= 0:
- return []
- roots = dup_inner_isolate_real_roots(dup_mirror(f, K), K, eps=eps, fast=fast)
- F, results = K.get_field(), []
- if inf is not None or sup is not None:
- for f, M in roots:
- result = _discard_if_outside_interval(f, M, inf, sup, K, True, fast, mobius)
- if result is not None:
- results.append(result)
- elif not mobius:
- for f, M in roots:
- u, v = _mobius_to_interval(M, F)
- results.append((-v, -u))
- else:
- results = roots
- return results
- def _isolate_zero(f, K, inf, sup, basis=False, sqf=False):
- """Handle special case of CF algorithm when ``f`` is homogeneous. """
- j, f = dup_terms_gcd(f, K)
- if j > 0:
- 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
- return [], 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.
- """
- 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=False, sqf=True)
- 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)
- roots = sorted(I_neg + I_zero + I_pos)
- if not blackbox:
- return roots
- else:
- return [ RealInterval((a, b), f, K) for (a, b) in roots ]
- 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 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.
- """
- if K.is_QQ:
- K, F, polys = K.get_ring(), K, polys[:]
- for i, p in enumerate(polys):
- polys[i] = dup_clear_denoms(p, F, K, convert=True)[1]
- elif not K.is_ZZ:
- raise DomainError("isolation of real roots not supported over %s" % K)
- zeros, factors_dict = False, {}
- if (inf is None or inf <= 0) and (sup is None or 0 <= sup):
- zeros, zero_indices = True, {}
- for i, p in enumerate(polys):
- j, p = dup_terms_gcd(p, K)
- if zeros and j > 0:
- zero_indices[i] = j
- for f, k in dup_factor_list(p, K)[1]:
- f = tuple(f)
- if f not in factors_dict:
- factors_dict[f] = {i: k}
- else:
- factors_dict[f][i] = k
- factors_list = [(list(f), indices) for f, indices in factors_dict.items()]
- I_neg, I_pos = _real_isolate_and_disjoin(factors_list, K, eps=eps,
- inf=inf, sup=sup, strict=strict, basis=basis, fast=fast)
- F = K.get_field()
- if not zeros or not zero_indices:
- I_zero = []
- else:
- if not basis:
- I_zero = [((F.zero, F.zero), zero_indices)]
- else:
- I_zero = [((F.zero, F.zero), zero_indices, [K.one, K.zero])]
- return sorted(I_neg + I_zero + I_pos)
- def _disjoint_p(M, N, strict=False):
- """Check if Mobius transforms define disjoint intervals. """
- a1, b1, c1, d1 = M
- a2, b2, c2, d2 = N
- a1d1, b1c1 = a1*d1, b1*c1
- a2d2, b2c2 = a2*d2, b2*c2
- if a1d1 == b1c1 and a2d2 == b2c2:
- return True
- if a1d1 > b1c1:
- a1, c1, b1, d1 = b1, d1, a1, c1
- if a2d2 > b2c2:
- a2, c2, b2, d2 = b2, d2, a2, c2
- if not strict:
- return a2*d1 >= c2*b1 or b2*c1 <= d2*a1
- else:
- return a2*d1 > c2*b1 or b2*c1 < d2*a1
- 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_pos, I_neg = [], []
- for i, (f, k) in enumerate(factors):
- for F, M in dup_inner_isolate_positive_roots(f, K, eps=eps, inf=inf, sup=sup, fast=fast, mobius=True):
- I_pos.append((F, M, k, f))
- for G, N in dup_inner_isolate_negative_roots(f, K, eps=eps, inf=inf, sup=sup, fast=fast, mobius=True):
- I_neg.append((G, N, k, f))
- for i, (f, M, k, F) in enumerate(I_pos):
- for j, (g, N, m, G) in enumerate(I_pos[i + 1:]):
- while not _disjoint_p(M, N, strict=strict):
- f, M = dup_inner_refine_real_root(f, M, K, steps=1, fast=fast, mobius=True)
- g, N = dup_inner_refine_real_root(g, N, K, steps=1, fast=fast, mobius=True)
- I_pos[i + j + 1] = (g, N, m, G)
- I_pos[i] = (f, M, k, F)
- for i, (f, M, k, F) in enumerate(I_neg):
- for j, (g, N, m, G) in enumerate(I_neg[i + 1:]):
- while not _disjoint_p(M, N, strict=strict):
- f, M = dup_inner_refine_real_root(f, M, K, steps=1, fast=fast, mobius=True)
- g, N = dup_inner_refine_real_root(g, N, K, steps=1, fast=fast, mobius=True)
- I_neg[i + j + 1] = (g, N, m, G)
- I_neg[i] = (f, M, k, F)
- if strict:
- for i, (f, M, k, F) in enumerate(I_neg):
- if not M[0]:
- while not M[0]:
- f, M = dup_inner_refine_real_root(f, M, K, steps=1, fast=fast, mobius=True)
- I_neg[i] = (f, M, k, F)
- break
- for j, (g, N, m, G) in enumerate(I_pos):
- if not N[0]:
- while not N[0]:
- g, N = dup_inner_refine_real_root(g, N, K, steps=1, fast=fast, mobius=True)
- I_pos[j] = (g, N, m, G)
- break
- field = K.get_field()
- I_neg = [ (_mobius_to_interval(M, field), k, f) for (_, M, k, f) in I_neg ]
- I_pos = [ (_mobius_to_interval(M, field), k, f) for (_, M, k, f) in I_pos ]
- I_neg = [((-v, -u), k, f) for ((u, v), k, f) in I_neg]
- if not basis:
- I_neg = [((u, v), k) for ((u, v), k, _) in I_neg]
- I_pos = [((u, v), k) for ((u, v), k, _) in I_pos]
- return I_neg, I_pos
- 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.is_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. """
- if not re and not im:
- return OO
- if not re:
- if im > 0:
- return A2
- else:
- return A4
- elif not im:
- if re > 0:
- return A1
- else:
- return A3
- def _intervals_to_quadrants(intervals, f1, f2, s, t, F):
- """Generate a sequence of extended quadrants from a list of critical points. """
- if not intervals:
- return []
- Q = []
- if not f1:
- (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 not f2:
- (a, b), _, _ = intervals[0]
- if a == b == s:
- 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:
- if dup_eval(f1, s, F) > 0:
- Q.append(A1)
- f1_sgn = +1
- else:
- Q.append(A3)
- f1_sgn = -1
- for (a, _), indices, _ in intervals:
- Q.append(OO)
- if indices[0] % 2 == 1:
- f1_sgn = -f1_sgn
- if a != t:
- if f1_sgn > 0:
- Q.append(A1)
- else:
- Q.append(A3)
- return Q
- re = dup_eval(f1, s, F)
- im = dup_eval(f2, s, F)
- if not re or not im:
- Q.append(_classify_point(re, im))
- if len(intervals) == 1:
- re = dup_eval(f1, t, F)
- im = dup_eval(f2, t, F)
- else:
- (a, _), _, _ = intervals[1]
- re = dup_eval(f1, (s + a)/2, F)
- im = dup_eval(f2, (s + a)/2, F)
- intervals = intervals[1:]
- if re > 0:
- f1_sgn = +1
- else:
- f1_sgn = -1
- if im > 0:
- f2_sgn = +1
- else:
- f2_sgn = -1
- sgn = {
- (+1, +1): Q1,
- (-1, +1): Q2,
- (-1, -1): Q3,
- (+1, -1): Q4,
- }
- Q.append(sgn[(f1_sgn, f2_sgn)])
- for (a, b), indices, _ in intervals:
- if a == b:
- re = dup_eval(f1, a, F)
- im = dup_eval(f2, a, F)
- cls = _classify_point(re, im)
- if cls is not None:
- Q.append(cls)
- if 0 in indices:
- if indices[0] % 2 == 1:
- f1_sgn = -f1_sgn
- if 1 in indices:
- if indices[1] % 2 == 1:
- f2_sgn = -f2_sgn
- if not (a == b and b == t):
- Q.append(sgn[(f1_sgn, f2_sgn)])
- return Q
- def _traverse_quadrants(Q_L1, Q_L2, Q_L3, Q_L4, exclude=None):
- """Transform sequences of quadrants to a sequence of rules. """
- if exclude is True:
- edges = [1, 1, 0, 0]
- corners = {
- (0, 1): 1,
- (1, 2): 1,
- (2, 3): 0,
- (3, 0): 1,
- }
- else:
- edges = [0, 0, 0, 0]
- corners = {
- (0, 1): 0,
- (1, 2): 0,
- (2, 3): 0,
- (3, 0): 0,
- }
- if exclude is not None and exclude is not True:
- 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
- QQ, rules = [Q_L1, Q_L2, Q_L3, Q_L4], []
- for i, Q in enumerate(QQ):
- if not Q:
- continue
- if Q[-1] == OO:
- Q = Q[:-1]
- if Q[0] == OO:
- 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))
- q1, k = Q[0], 1
- while k < len(Q):
- q2, k = Q[k], k + 1
- if q2 != OO:
- qq = (q1, q2)
- if qq in _rules_simple:
- rules.append((_rules_simple[qq], 0))
- elif qq in _rules_ambiguous:
- rules.append((_rules_ambiguous[qq], edges[i]))
- else:
- raise NotImplementedError("2 element rule (inside): " + str(qq))
- else:
- qq, k = (q1, q2, Q[k]), k + 1
- if qq in _rules_ambiguous:
- rules.append((_rules_ambiguous[qq], edges[i]))
- else:
- raise NotImplementedError("3 element rule (edge): " + str(qq))
- q1 = qq[-1]
- return rules
- def _reverse_intervals(intervals):
- """Reverse intervals for traversal from right to left and from top to bottom. """
- return [ ((b, a), indices, f) for (a, b), indices, f in reversed(intervals) ]
- def _winding_number(T, field):
- """Compute the winding number of the input polynomial, i.e. the number of roots. """
- return int(sum(field(*_values[t][i]) for t, i in T) / field(2))
- 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:
- _, 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. """
- (u, v), (s, t) = a, b
- I_L1, I_L2, I_L3, I_L4 = I
- Q_L1, Q_L2, Q_L3, Q_L4 = Q
- f1L1F, f1L2F, f1L3F, f1L4F = F1
- f2L1F, f2L2F, f2L3F, f2L4F = F2
- x = (u + s) / 2
- f1V = dmp_eval_in(f1, x, 0, 1, F)
- f2V = dmp_eval_in(f2, x, 0, 1, F)
- I_V = dup_isolate_real_roots_list([f1V, f2V], F, inf=v, sup=t, fast=True, strict=True, basis=True)
- I_L1_L, I_L1_R = [], []
- I_L2_L, I_L2_R = I_V, I_L2
- I_L3_L, I_L3_R = [], []
- I_L4_L, I_L4_R = I_L4, _reverse_intervals(I_V)
- for I in I_L1:
- (a, b), indices, h = I
- if a == b:
- if a == x:
- I_L1_L.append(I)
- I_L1_R.append(I)
- elif a < x:
- I_L1_L.append(I)
- else:
- I_L1_R.append(I)
- else:
- if b <= x:
- I_L1_L.append(I)
- elif a >= x:
- I_L1_R.append(I)
- 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))
- for I in I_L3:
- (b, a), indices, h = I
- if a == b:
- if a == x:
- I_L3_L.append(I)
- I_L3_R.append(I)
- elif a < x:
- I_L3_L.append(I)
- else:
- I_L3_R.append(I)
- else:
- if b <= x:
- I_L3_L.append(I)
- elif a >= x:
- I_L3_R.append(I)
- 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))
- Q_L1_L = _intervals_to_quadrants(I_L1_L, f1L1F, f2L1F, u, x, F)
- Q_L2_L = _intervals_to_quadrants(I_L2_L, f1V, f2V, v, t, F)
- Q_L3_L = _intervals_to_quadrants(I_L3_L, f1L3F, f2L3F, x, u, F)
- Q_L4_L = Q_L4
- Q_L1_R = _intervals_to_quadrants(I_L1_R, f1L1F, f2L1F, x, s, F)
- Q_L2_R = Q_L2
- Q_L3_R = _intervals_to_quadrants(I_L3_R, f1L3F, f2L3F, s, x, F)
- Q_L4_R = _intervals_to_quadrants(I_L4_R, f1V, f2V, t, v, F)
- T_L = _traverse_quadrants(Q_L1_L, Q_L2_L, Q_L3_L, Q_L4_L, exclude=True)
- T_R = _traverse_quadrants(Q_L1_R, Q_L2_R, Q_L3_R, Q_L4_R, exclude=True)
- N_L = _winding_number(T_L, F)
- N_R = _winding_number(T_R, F)
- I_L = (I_L1_L, I_L2_L, I_L3_L, I_L4_L)
- Q_L = (Q_L1_L, Q_L2_L, Q_L3_L, Q_L4_L)
- I_R = (I_L1_R, I_L2_R, I_L3_R, I_L4_R)
- Q_R = (Q_L1_R, Q_L2_R, Q_L3_R, Q_L4_R)
- F1_L = (f1L1F, f1V, f1L3F, f1L4F)
- F2_L = (f2L1F, f2V, f2L3F, f2L4F)
- F1_R = (f1L1F, f1L2F, f1L3F, f1V)
- F2_R = (f2L1F, f2L2F, f2L3F, f2V)
- a, b = (u, v), (x, t)
- c, d = (x, v), (s, t)
- D_L = (N_L, a, b, I_L, Q_L, F1_L, F2_L)
- D_R = (N_R, c, d, I_R, Q_R, F1_R, F2_R)
- return D_L, D_R
- def _horizontal_bisection(N, a, b, I, Q, F1, F2, f1, f2, F):
- """Horizontal bisection step in Collins-Krandick root isolation algorithm. """
- (u, v), (s, t) = a, b
- I_L1, I_L2, I_L3, I_L4 = I
- Q_L1, Q_L2, Q_L3, Q_L4 = Q
- f1L1F, f1L2F, f1L3F, f1L4F = F1
- f2L1F, f2L2F, f2L3F, f2L4F = F2
- y = (v + t) / 2
- f1H = dmp_eval_in(f1, y, 1, 1, F)
- f2H = dmp_eval_in(f2, y, 1, 1, F)
- I_H = dup_isolate_real_roots_list([f1H, f2H], F, inf=u, sup=s, fast=True, strict=True, basis=True)
- I_L1_B, I_L1_U = I_L1, I_H
- I_L2_B, I_L2_U = [], []
- I_L3_B, I_L3_U = _reverse_intervals(I_H), I_L3
- I_L4_B, I_L4_U = [], []
- for I in I_L2:
- (a, b), indices, h = I
- if a == b:
- if a == y:
- I_L2_B.append(I)
- I_L2_U.append(I)
- elif a < y:
- I_L2_B.append(I)
- else:
- I_L2_U.append(I)
- else:
- if b <= y:
- I_L2_B.append(I)
- elif a >= y:
- I_L2_U.append(I)
- else:
- a, b = dup_refine_real_root(h, a, b, F.get_ring(), disjoint=y, fast=True)
- if b <= y:
- I_L2_B.append(((a, b), indices, h))
- if a >= y:
- I_L2_U.append(((a, b), indices, h))
- for I in I_L4:
- (b, a), indices, h = I
- if a == b:
- if a == y:
- I_L4_B.append(I)
- I_L4_U.append(I)
- elif a < y:
- I_L4_B.append(I)
- else:
- I_L4_U.append(I)
- else:
- if b <= y:
- I_L4_B.append(I)
- elif a >= y:
- I_L4_U.append(I)
- else:
- a, b = dup_refine_real_root(h, a, b, F.get_ring(), disjoint=y, fast=True)
- if b <= y:
- I_L4_B.append(((b, a), indices, h))
- if a >= y:
- I_L4_U.append(((b, a), indices, h))
- Q_L1_B = Q_L1
- Q_L2_B = _intervals_to_quadrants(I_L2_B, f1L2F, f2L2F, v, y, F)
- Q_L3_B = _intervals_to_quadrants(I_L3_B, f1H, f2H, s, u, F)
- Q_L4_B = _intervals_to_quadrants(I_L4_B, f1L4F, f2L4F, y, v, F)
- Q_L1_U = _intervals_to_quadrants(I_L1_U, f1H, f2H, u, s, F)
- Q_L2_U = _intervals_to_quadrants(I_L2_U, f1L2F, f2L2F, y, t, F)
- Q_L3_U = Q_L3
- Q_L4_U = _intervals_to_quadrants(I_L4_U, f1L4F, f2L4F, t, y, F)
- T_B = _traverse_quadrants(Q_L1_B, Q_L2_B, Q_L3_B, Q_L4_B, exclude=True)
- T_U = _traverse_quadrants(Q_L1_U, Q_L2_U, Q_L3_U, Q_L4_U, exclude=True)
- N_B = _winding_number(T_B, F)
- N_U = _winding_number(T_U, F)
- I_B = (I_L1_B, I_L2_B, I_L3_B, I_L4_B)
- Q_B = (Q_L1_B, Q_L2_B, Q_L3_B, Q_L4_B)
- I_U = (I_L1_U, I_L2_U, I_L3_U, I_L4_U)
- Q_U = (Q_L1_U, Q_L2_U, Q_L3_U, Q_L4_U)
- F1_B = (f1L1F, f1L2F, f1H, f1L4F)
- F2_B = (f2L1F, f2L2F, f2H, f2L4F)
- F1_U = (f1H, f1L2F, f1L3F, f1L4F)
- F2_U = (f2H, f2L2F, f2L3F, f2L4F)
- a, b = (u, v), (s, y)
- c, d = (u, y), (s, t)
- D_B = (N_B, a, b, I_B, Q_B, F1_B, F2_B)
- D_U = (N_U, c, d, I_U, Q_U, F1_U, F2_U)
- return D_B, D_U
- def _depth_first_select(rectangles):
- """Find a rectangle of minimum area for bisection. """
- min_area, j = None, None
- for i, (_, (u, v), (s, t), _, _, _, _) in enumerate(rectangles):
- area = (s - u)*(t - v)
- if min_area is None or area < min_area:
- min_area, j = area, i
- return rectangles.pop(j)
- def _rectangle_small_p(a, b, eps):
- """Return ``True`` if the given rectangle is small enough. """
- (u, v), (s, t) = a, b
- if eps is not None:
- return s - u < eps and t - v < eps
- else:
- return True
- 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. """
- if not K.is_ZZ and not K.is_QQ:
- raise DomainError("isolation of complex roots is not supported over %s" % K)
- if dup_degree(f) <= 0:
- return []
- if K.is_ZZ:
- F = K.get_field()
- else:
- F = K
- f = dup_convert(f, K, F)
- lc = abs(dup_LC(f, F))
- B = 2*max(F.quo(abs(c), lc) for c in f)
- (u, v), (s, t) = (-B, F.zero), (B, B)
- if inf is not None:
- u = inf
- if sup is not None:
- s = sup
- if v < 0 or t <= v or s <= u:
- raise ValueError("not a valid complex isolation rectangle")
- f1, f2 = dup_real_imag(f, F)
- f1L1 = dmp_eval_in(f1, v, 1, 1, F)
- f2L1 = dmp_eval_in(f2, v, 1, 1, F)
- f1L2 = dmp_eval_in(f1, s, 0, 1, F)
- f2L2 = dmp_eval_in(f2, s, 0, 1, F)
- f1L3 = dmp_eval_in(f1, t, 1, 1, F)
- f2L3 = dmp_eval_in(f2, t, 1, 1, F)
- f1L4 = dmp_eval_in(f1, u, 0, 1, F)
- f2L4 = dmp_eval_in(f2, u, 0, 1, F)
- S_L1 = [f1L1, f2L1]
- S_L2 = [f1L2, f2L2]
- S_L3 = [f1L3, f2L3]
- S_L4 = [f1L4, f2L4]
- I_L1 = dup_isolate_real_roots_list(S_L1, F, inf=u, sup=s, fast=True, strict=True, basis=True)
- I_L2 = dup_isolate_real_roots_list(S_L2, F, inf=v, sup=t, fast=True, strict=True, basis=True)
- I_L3 = dup_isolate_real_roots_list(S_L3, F, inf=u, sup=s, fast=True, strict=True, basis=True)
- I_L4 = dup_isolate_real_roots_list(S_L4, F, inf=v, sup=t, fast=True, strict=True, basis=True)
- I_L3 = _reverse_intervals(I_L3)
- I_L4 = _reverse_intervals(I_L4)
- Q_L1 = _intervals_to_quadrants(I_L1, f1L1, f2L1, u, s, F)
- Q_L2 = _intervals_to_quadrants(I_L2, f1L2, f2L2, v, t, F)
- Q_L3 = _intervals_to_quadrants(I_L3, f1L3, f2L3, s, u, F)
- Q_L4 = _intervals_to_quadrants(I_L4, f1L4, f2L4, t, v, F)
- T = _traverse_quadrants(Q_L1, Q_L2, Q_L3, Q_L4)
- N = _winding_number(T, F)
- if not N:
- return []
- I = (I_L1, I_L2, I_L3, I_L4)
- Q = (Q_L1, Q_L2, Q_L3, Q_L4)
- F1 = (f1L1, f1L2, f1L3, f1L4)
- F2 = (f2L1, f2L2, f2L3, f2L4)
- rectangles, roots = [(N, (u, v), (s, t), I, Q, F1, F2)], []
- while rectangles:
- N, (u, v), (s, t), I, Q, F1, F2 = _depth_first_select(rectangles)
- if s - u > t - v:
- D_L, D_R = _vertical_bisection(N, (u, v), (s, t), I, Q, F1, F2, f1, f2, F)
- N_L, a, b, I_L, Q_L, F1_L, F2_L = D_L
- N_R, c, d, I_R, Q_R, F1_R, F2_R = D_R
- if N_L >= 1:
- if N_L == 1 and _rectangle_small_p(a, b, eps):
- roots.append(ComplexInterval(a, b, I_L, Q_L, F1_L, F2_L, f1, f2, F))
- else:
- rectangles.append(D_L)
- if N_R >= 1:
- if N_R == 1 and _rectangle_small_p(c, d, eps):
- roots.append(ComplexInterval(c, d, I_R, Q_R, F1_R, F2_R, f1, f2, F))
- else:
- rectangles.append(D_R)
- else:
- D_B, D_U = _horizontal_bisection(N, (u, v), (s, t), I, Q, F1, F2, f1, f2, F)
- N_B, a, b, I_B, Q_B, F1_B, F2_B = D_B
- N_U, c, d, I_U, Q_U, F1_U, F2_U = D_U
- if N_B >= 1:
- if N_B == 1 and _rectangle_small_p(a, b, eps):
- roots.append(ComplexInterval(
- a, b, I_B, Q_B, F1_B, F2_B, f1, f2, F))
- else:
- rectangles.append(D_B)
- if N_U >= 1:
- if N_U == 1 and _rectangle_small_p(c, d, eps):
- roots.append(ComplexInterval(
- c, d, I_U, Q_U, F1_U, F2_U, f1, f2, F))
- else:
- rectangles.append(D_U)
- _roots, roots = sorted(roots, key=lambda r: (r.ax, r.ay)), []
- for root in _roots:
- roots.extend([root.conjugate(), root])
- if blackbox:
- return roots
- 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:
- """A fully qualified representation of a real isolation interval. """
- def __init__(self, data, f, dom):
- """Initialize new real interval with complete information. """
- if len(data) == 2:
- s, t = data
- self.neg = False
- if s < 0:
- if t <= 0:
- f, s, t, self.neg = dup_mirror(f, dom), -t, -s, True
- else:
- raise ValueError("Cannot refine a real root in (%s, %s)" % (s, t))
- a, b, c, d = _mobius_from_interval((s, t), dom.get_field())
- f = dup_transform(f, dup_strip([a, b]),
- dup_strip([c, d]), dom)
- self.mobius = a, b, c, d
- else:
- self.mobius = data[:-1]
- self.neg = data[-1]
- self.f, self.dom = f, dom
- @property
- def func(self):
- return RealInterval
- @property
- def args(self):
- i = self
- return (i.mobius + (i.neg,), i.f, i.dom)
- def __eq__(self, other):
- if type(other) is not type(self):
- return False
- return self.args == other.args
- @property
- def a(self):
- """Return the position of the left end. """
- field = self.dom.get_field()
- a, b, c, d = self.mobius
- if not self.neg:
- if a*d < b*c:
- return field(a, c)
- return field(b, d)
- else:
- if a*d > b*c:
- return -field(a, c)
- return -field(b, d)
- @property
- def b(self):
- """Return the position of the right end. """
- was = self.neg
- self.neg = not was
- rv = -self.a
- self.neg = was
- return rv
- @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. """
- return (self.a + self.b)/2
- @property
- def max_denom(self):
- """Return the largest denominator occurring in either endpoint. """
- return max(self.a.denominator, self.b.denominator)
- 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 __contains__(self, item):
- """
- Say whether a complex number belongs to this real interval.
- Parameters
- ==========
- item : pair (re, im) or number re
- Either a pair giving the real and imaginary parts of the number,
- or else a real number.
- """
- if isinstance(item, tuple):
- re, im = item
- else:
- re, im = item, 0
- return im == 0 and self.a <= re <= self.b
- def is_disjoint(self, other):
- """Return ``True`` if two isolation intervals are disjoint. """
- if isinstance(other, RealInterval):
- return (self.b < other.a or other.b < self.a)
- assert isinstance(other, ComplexInterval)
- return (self.b < other.ax or other.bx < self.a
- or other.ay*other.by > 0)
- 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 = self
- while not expr.is_disjoint(other):
- expr, other = expr._inner_refine(), other._inner_refine()
- return expr, other
- 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:
- """A fully qualified representation of a complex isolation interval.
- The printed form is shown as (ax, bx) x (ay, by) where (ax, ay)
- and (bx, by) are the coordinates of the southwest and northeast
- corners of the interval's rectangle, respectively.
- Examples
- ========
- >>> from sympy import CRootOf, S
- >>> from sympy.abc import x
- >>> CRootOf.clear_cache() # for doctest reproducibility
- >>> root = CRootOf(x**10 - 2*x + 3, 9)
- >>> i = root._get_interval(); i
- (3/64, 3/32) x (9/8, 75/64)
- The real part of the root lies within the range [0, 3/4] while
- the imaginary part lies within the range [9/8, 3/2]:
- >>> root.n(3)
- 0.0766 + 1.14*I
- The width of the ranges in the x and y directions on the complex
- plane are:
- >>> i.dx, i.dy
- (3/64, 3/64)
- The center of the range is
- >>> i.center
- (9/128, 147/128)
- The northeast coordinate of the rectangle bounding the root in the
- complex plane is given by attribute b and the x and y components
- are accessed by bx and by:
- >>> i.b, i.bx, i.by
- ((3/32, 75/64), 3/32, 75/64)
- The southwest coordinate is similarly given by i.a
- >>> i.a, i.ax, i.ay
- ((3/64, 9/8), 3/64, 9/8)
- Although the interval prints to show only the real and imaginary
- range of the root, all the information of the underlying root
- is contained as properties of the interval.
- For example, an interval with a nonpositive imaginary range is
- considered to be the conjugate. Since the y values of y are in the
- range [0, 1/4] it is not the conjugate:
- >>> i.conj
- False
- The conjugate's interval is
- >>> ic = i.conjugate(); ic
- (3/64, 3/32) x (-75/64, -9/8)
- NOTE: the values printed still represent the x and y range
- in which the root -- conjugate, in this case -- is located,
- but the underlying a and b values of a root and its conjugate
- are the same:
- >>> assert i.a == ic.a and i.b == ic.b
- What changes are the reported coordinates of the bounding rectangle:
- >>> (i.ax, i.ay), (i.bx, i.by)
- ((3/64, 9/8), (3/32, 75/64))
- >>> (ic.ax, ic.ay), (ic.bx, ic.by)
- ((3/64, -75/64), (3/32, -9/8))
- The interval can be refined once:
- >>> i # for reference, this is the current interval
- (3/64, 3/32) x (9/8, 75/64)
- >>> i.refine()
- (3/64, 3/32) x (9/8, 147/128)
- Several refinement steps can be taken:
- >>> i.refine_step(2) # 2 steps
- (9/128, 3/32) x (9/8, 147/128)
- It is also possible to refine to a given tolerance:
- >>> tol = min(i.dx, i.dy)/2
- >>> i.refine_size(tol)
- (9/128, 21/256) x (9/8, 291/256)
- A disjoint interval is one whose bounding rectangle does not
- overlap with another. An interval, necessarily, is not disjoint with
- itself, but any interval is disjoint with a conjugate since the
- conjugate rectangle will always be in the lower half of the complex
- plane and the non-conjugate in the upper half:
- >>> i.is_disjoint(i), i.is_disjoint(i.conjugate())
- (False, True)
- The following interval j is not disjoint from i:
- >>> close = CRootOf(x**10 - 2*x + 300/S(101), 9)
- >>> j = close._get_interval(); j
- (75/1616, 75/808) x (225/202, 1875/1616)
- >>> i.is_disjoint(j)
- False
- The two can be made disjoint, however:
- >>> newi, newj = i.refine_disjoint(j)
- >>> newi
- (39/512, 159/2048) x (2325/2048, 4653/4096)
- >>> newj
- (3975/51712, 2025/25856) x (29325/25856, 117375/103424)
- Even though the real ranges overlap, the imaginary do not, so
- the roots have been resolved as distinct. Intervals are disjoint
- when either the real or imaginary component of the intervals is
- distinct. In the case above, the real components have not been
- resolved (so we do not know, yet, which root has the smaller real
- part) but the imaginary part of ``close`` is larger than ``root``:
- >>> close.n(3)
- 0.0771 + 1.13*I
- >>> root.n(3)
- 0.0766 + 1.14*I
- """
- def __init__(self, a, b, I, Q, F1, F2, f1, f2, dom, conj=False):
- """Initialize new complex interval with complete information. """
- # a and b are the SW and NE corner of the bounding interval,
- # (ax, ay) and (bx, by), respectively, for the NON-CONJUGATE
- # root (the one with the positive imaginary part); when working
- # with the conjugate, the a and b value are still non-negative
- # but the ay, by are reversed and have oppositite sign
- self.a, self.b = a, b
- self.I, self.Q = I, Q
- self.f1, self.F1 = f1, F1
- self.f2, self.F2 = f2, F2
- self.dom = dom
- self.conj = conj
- @property
- def func(self):
- return ComplexInterval
- @property
- def args(self):
- i = self
- return (i.a, i.b, i.I, i.Q, i.F1, i.F2, i.f1, i.f2, i.dom, i.conj)
- def __eq__(self, other):
- if type(other) is not type(self):
- return False
- return self.args == other.args
- @property
- def ax(self):
- """Return ``x`` coordinate of south-western corner. """
- return self.a[0]
- @property
- def ay(self):
- """Return ``y`` coordinate of south-western corner. """
- if not self.conj:
- return self.a[1]
- else:
- return -self.b[1]
- @property
- def bx(self):
- """Return ``x`` coordinate of north-eastern corner. """
- return self.b[0]
- @property
- def by(self):
- """Return ``y`` coordinate of north-eastern corner. """
- if not self.conj:
- return self.b[1]
- else:
- return -self.a[1]
- @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)
- @property
- def max_denom(self):
- """Return the largest denominator occurring in either endpoint. """
- return max(self.ax.denominator, self.bx.denominator,
- self.ay.denominator, self.by.denominator)
- def as_tuple(self):
- """Return tuple representation of the complex isolating
- interval's SW and NE corners, respectively. """
- 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. """
- return ComplexInterval(self.a, self.b, self.I, self.Q,
- self.F1, self.F2, self.f1, self.f2, self.dom, conj=True)
- def __contains__(self, item):
- """
- Say whether a complex number belongs to this complex rectangular
- region.
- Parameters
- ==========
- item : pair (re, im) or number re
- Either a pair giving the real and imaginary parts of the number,
- or else a real number.
- """
- if isinstance(item, tuple):
- re, im = item
- else:
- re, im = item, 0
- return self.ax <= re <= self.bx and self.ay <= im <= self.by
- def is_disjoint(self, other):
- """Return ``True`` if two isolation intervals are disjoint. """
- if isinstance(other, RealInterval):
- return other.is_disjoint(self)
- if self.conj != other.conj: # above and below real axis
- return True
- re_distinct = (self.bx < other.ax or other.bx < self.ax)
- if re_distinct:
- return True
- im_distinct = (self.by < other.ay or other.by < self.ay)
- return im_distinct
- def _inner_refine(self):
- """Internal one step complex root refinement procedure. """
- (u, v), (s, t) = self.a, self.b
- I, Q = self.I, self.Q
- f1, F1 = self.f1, self.F1
- f2, F2 = self.f2, self.F2
- dom = self.dom
- if s - u > t - v:
- D_L, D_R = _vertical_bisection(1, (u, v), (s, t), I, Q, F1, F2, f1, f2, dom)
- if D_L[0] == 1:
- _, a, b, I, Q, F1, F2 = D_L
- else:
- _, a, b, I, Q, F1, F2 = D_R
- else:
- D_B, D_U = _horizontal_bisection(1, (u, v), (s, t), I, Q, F1, F2, f1, f2, dom)
- if D_B[0] == 1:
- _, a, b, I, Q, F1, F2 = D_B
- else:
- _, a, b, I, Q, F1, F2 = D_U
- return ComplexInterval(a, b, I, Q, F1, F2, f1, f2, dom, self.conj)
- def refine_disjoint(self, other):
- """Refine an isolating interval until it is disjoint with another one. """
- expr = self
- while not expr.is_disjoint(other):
- expr, other = expr._inner_refine(), other._inner_refine()
- return expr, other
- 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()
|