def _coerce_map_from_(self, P): r""" Return whether ``P`` coerces into this symbolic subring. INPUT: - ``P`` -- a parent. OUTPUT: A boolean or ``None``. TESTS:: sage: from sage.symbolic.subring import GenericSymbolicSubring sage: GenericSymbolicSubring(vars=tuple()).has_coerce_map_from(SR) # indirect doctest # not tested see #19231 False :: sage: from sage.symbolic.subring import SymbolicSubring sage: C = SymbolicSubring(no_variables=True) sage: C.has_coerce_map_from(ZZ) # indirect doctest True sage: C.has_coerce_map_from(QQ) # indirect doctest True sage: C.has_coerce_map_from(RR) # indirect doctest True sage: C.has_coerce_map_from(RIF) # indirect doctest True sage: C.has_coerce_map_from(CC) # indirect doctest True sage: C.has_coerce_map_from(CIF) # indirect doctest True sage: C.has_coerce_map_from(AA) # indirect doctest True sage: C.has_coerce_map_from(QQbar) # indirect doctest True sage: C.has_coerce_map_from(SR) # indirect doctest False """ if P == SR: # Workaround; can be deleted once #19231 is fixed return False from sage.rings.real_mpfr import mpfr_prec_min from sage.rings.all import (ComplexField, RLF, CLF, AA, QQbar, InfinityRing) from sage.rings.real_mpfi import is_RealIntervalField from sage.rings.complex_interval_field import is_ComplexIntervalField if isinstance(P, type): return SR._coerce_map_from_(P) elif RLF.has_coerce_map_from(P) or \ CLF.has_coerce_map_from(P) or \ AA.has_coerce_map_from(P) or \ QQbar.has_coerce_map_from(P): return True elif (P is InfinityRing or is_RealIntervalField(P) or is_ComplexIntervalField(P)): return True elif ComplexField(mpfr_prec_min()).has_coerce_map_from(P): return P not in (RLF, CLF, AA, QQbar)
def __getitem__(self, arg): """ Extend this ring by one or several elements to create a polynomial ring, a power series ring, or an algebraic extension. This is a convenience method intended primarily for interactive use. .. SEEALSO:: :func:`~sage.rings.polynomial.polynomial_ring_constructor.PolynomialRing`, :func:`~sage.rings.power_series_ring.PowerSeriesRing`, :meth:`~sage.rings.ring.Ring.extension`, :meth:`sage.rings.integer_ring.IntegerRing_class.__getitem__`, :meth:`sage.rings.matrix_space.MatrixSpace.__getitem__`, :meth:`sage.structure.parent.Parent.__getitem__` EXAMPLES: We create several polynomial rings:: sage: ZZ['x'] Univariate Polynomial Ring in x over Integer Ring sage: QQ['x'] Univariate Polynomial Ring in x over Rational Field sage: GF(17)['abc'] Univariate Polynomial Ring in abc over Finite Field of size 17 sage: GF(17)['a,b,c'] Multivariate Polynomial Ring in a, b, c over Finite Field of size 17 sage: GF(17)['a']['b'] Univariate Polynomial Ring in b over Univariate Polynomial Ring in a over Finite Field of size 17 We can create skew polynomial rings:: sage: k.<t> = GF(5^3) sage: Frob = k.frobenius_endomorphism() sage: k['x',Frob] Skew Polynomial Ring in x over Finite Field in t of size 5^3 twisted by t |--> t^5 We can also create power series rings by using double brackets:: sage: QQ[['t']] Power Series Ring in t over Rational Field sage: ZZ[['W']] Power Series Ring in W over Integer Ring sage: ZZ[['x,y,z']] Multivariate Power Series Ring in x, y, z over Integer Ring sage: ZZ[['x','T']] Multivariate Power Series Ring in x, T over Integer Ring Use :func:`~sage.rings.fraction_field.Frac` or :meth:`~sage.rings.ring.CommutativeRing.fraction_field` to obtain the fields of rational functions and Laurent series:: sage: Frac(QQ['t']) Fraction Field of Univariate Polynomial Ring in t over Rational Field sage: Frac(QQ[['t']]) Laurent Series Ring in t over Rational Field sage: QQ[['t']].fraction_field() Laurent Series Ring in t over Rational Field Note that the same syntax can be used to create number fields:: sage: QQ[I] Number Field in I with defining polynomial x^2 + 1 sage: QQ[I].coerce_embedding() Generic morphism: From: Number Field in I with defining polynomial x^2 + 1 To: Complex Lazy Field Defn: I -> 1*I :: sage: QQ[sqrt(2)] Number Field in sqrt2 with defining polynomial x^2 - 2 sage: QQ[sqrt(2)].coerce_embedding() Generic morphism: From: Number Field in sqrt2 with defining polynomial x^2 - 2 To: Real Lazy Field Defn: sqrt2 -> 1.414213562373095? :: sage: QQ[sqrt(2),sqrt(3)] Number Field in sqrt2 with defining polynomial x^2 - 2 over its base field and orders in number fields:: sage: ZZ[I] Order in Number Field in I with defining polynomial x^2 + 1 sage: ZZ[sqrt(5)] Order in Number Field in sqrt5 with defining polynomial x^2 - 5 sage: ZZ[sqrt(2)+sqrt(3)] Order in Number Field in a with defining polynomial x^4 - 10*x^2 + 1 Embeddings are found for simple extensions (when that makes sense):: sage: QQi.<i> = QuadraticField(-1, 'i') sage: QQ[i].coerce_embedding() Generic morphism: From: Number Field in i with defining polynomial x^2 + 1 To: Complex Lazy Field Defn: i -> 1*I TESTS: A few corner cases:: sage: QQ[()] Multivariate Polynomial Ring in no variables over Rational Field sage: QQ[[]] Traceback (most recent call last): ... TypeError: power series rings must have at least one variable These kind of expressions do not work:: sage: QQ['a,b','c'] Traceback (most recent call last): ... ValueError: variable name 'a,b' is not alphanumeric sage: QQ[['a,b','c']] Traceback (most recent call last): ... ValueError: variable name 'a,b' is not alphanumeric sage: QQ[[['x']]] Traceback (most recent call last): ... TypeError: expected R[...] or R[[...]], not R[[[...]]] Extension towers are built as follows and use distinct generator names:: sage: K = QQ[2^(1/3), 2^(1/2), 3^(1/3)] sage: K Number Field in a with defining polynomial x^3 - 2 over its base field sage: K.base_field() Number Field in sqrt2 with defining polynomial x^2 - 2 over its base field sage: K.base_field().base_field() Number Field in b with defining polynomial x^3 - 3 Embeddings:: sage: QQ[I](I.pyobject()) I sage: a = 10^100; expr = (2*a + sqrt(2))/(2*a^2-1) sage: QQ[expr].coerce_embedding() is None False sage: QQ[sqrt(5)].gen() > 0 True sage: expr = sqrt(2) + I*(cos(pi/4, hold=True) - sqrt(2)/2) sage: QQ[expr].coerce_embedding() Generic morphism: From: Number Field in a with defining polynomial x^2 - 2 To: Real Lazy Field Defn: a -> 1.414213562373095? """ def normalize_arg(arg): if isinstance(arg, (tuple, list)): # Allowing arbitrary iterables would create confusion, but we # may want to support a few more. return tuple(arg) elif isinstance(arg, str): return tuple(arg.split(',')) else: return (arg,) # 1. If arg is a list, try to return a power series ring. if isinstance(arg, list): if arg == []: raise TypeError("power series rings must have at least one variable") elif len(arg) == 1: # R[["a,b"]], R[[(a,b)]]... if isinstance(arg[0], list): raise TypeError("expected R[...] or R[[...]], not R[[[...]]]") elts = normalize_arg(arg[0]) else: elts = normalize_arg(arg) from sage.rings.power_series_ring import PowerSeriesRing return PowerSeriesRing(self, elts) if isinstance(arg, tuple): from sage.categories.morphism import Morphism if len(arg) == 2 and isinstance(arg[1], Morphism): from sage.rings.polynomial.skew_polynomial_ring_constructor import SkewPolynomialRing return SkewPolynomialRing(self, arg[1], names=arg[0]) # 2. Otherwise, if all specified elements are algebraic, try to # return an algebraic extension elts = normalize_arg(arg) try: minpolys = [a.minpoly() for a in elts] except (AttributeError, NotImplementedError, ValueError, TypeError): minpolys = None if minpolys: # how to pass in names? names = tuple(_gen_names(elts)) if len(elts) == 1: from sage.rings.all import CIF, CLF, RIF, RLF elt = elts[0] try: iv = CIF(elt) except (TypeError, ValueError): emb = None else: # First try creating an ANRoot manually, because # extension(..., embedding=CLF(expr)) (or # ...QQbar(expr)) would normalize the expression in # QQbar, which currently is VERY slow in many cases. # This may fail when minpoly has close roots or elt is # a complicated symbolic expression. # TODO: Rewrite using #19362 and/or #17886 and/or # #15600 once those issues are solved. from sage.rings.qqbar import AlgebraicNumber, ANRoot try: elt = AlgebraicNumber(ANRoot(minpolys[0], iv)) except ValueError: pass # Force a real embedding when possible, to get the # right ordered ring structure. if (iv.imag().is_zero() or iv.imag().contains_zero() and elt.imag().is_zero()): emb = RLF(elt) else: emb = CLF(elt) return self.extension(minpolys[0], names[0], embedding=emb) try: # Doing the extension all at once is best, if possible... return self.extension(minpolys, names) except (TypeError, ValueError): # ...but we can also construct it iteratively return reduce(lambda R, ext: R.extension(*ext), zip(minpolys, names), self) # 2. Otherwise, try to return a polynomial ring from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing return PolynomialRing(self, elts)
def __init__(self, point, dop=None): """ TESTS:: sage: from ore_algebra import * sage: from ore_algebra.analytic.path import Point sage: Dops, x, Dx = DifferentialOperators() sage: [Point(z, Dx) ....: for z in [1, 1/2, 1+I, QQbar(I), RIF(1/3), CIF(1/3), pi, ....: RDF(1), CDF(I), 0.5r, 0.5jr, 10r, QQbar(1), AA(1/3)]] [1, 1/2, I + 1, I, [0.333333333333333...], [0.333333333333333...], 3.141592653589794?, 1.000000000000000, 1.000000000000000*I, 0.5000000000000000, 0.5000000000000000*I, 10, 1, 1/3] sage: Point(sqrt(2), Dx).iv() [1.414...] """ SageObject.__init__(self) from sage.rings.complex_double import ComplexDoubleField_class from sage.rings.complex_field import ComplexField_class from sage.rings.complex_interval_field import ComplexIntervalField_class from sage.rings.real_double import RealDoubleField_class from sage.rings.real_mpfi import RealIntervalField_class from sage.rings.real_mpfr import RealField_class point = sage.structure.coerce.py_scalar_to_element(point) try: parent = point.parent() except AttributeError: raise TypeError("unexpected value for point: " + repr(point)) if isinstance(point, Point): self.value = point.value elif isinstance( parent, (number_field_base.NumberField, RealBallField, ComplexBallField)): self.value = point elif QQ.has_coerce_map_from(parent): self.value = QQ.coerce(point) # must come before QQbar, due to a bogus coerce map (#14485) elif parent is sage.symbolic.ring.SR: try: return self.__init__(point.pyobject(), dop) except TypeError: pass try: return self.__init__(QQbar(point), dop) except (TypeError, ValueError, NotImplementedError): pass try: self.value = RLF(point) except (TypeError, ValueError): self.value = CLF(point) elif QQbar.has_coerce_map_from(parent): alg = QQbar.coerce(point) NF, val, hom = alg.as_number_field_element() if NF is QQ: self.value = QQ.coerce(val) # parent may be ZZ else: embNF = number_field.NumberField(NF.polynomial(), NF.variable_name(), embedding=hom(NF.gen())) self.value = val.polynomial()(embNF.gen()) elif isinstance( parent, (RealField_class, RealDoubleField_class, RealIntervalField_class)): self.value = RealBallField(point.prec())(point) elif isinstance(parent, (ComplexField_class, ComplexDoubleField_class, ComplexIntervalField_class)): self.value = ComplexBallField(point.prec())(point) else: try: self.value = RLF.coerce(point) except TypeError: self.value = CLF.coerce(point) parent = self.value.parent() assert (isinstance( parent, (number_field_base.NumberField, RealBallField, ComplexBallField)) or parent is RLF or parent is CLF) self.dop = dop or point.dop self.keep_value = False
def _coerce_map_from_(self, P): r""" Return whether ``P`` coerces into this symbolic subring. INPUT: - ``P`` -- a parent. OUTPUT: A boolean or ``None``. TESTS:: sage: from sage.symbolic.subring import GenericSymbolicSubring sage: GenericSymbolicSubring(vars=tuple()).has_coerce_map_from(SR) # indirect doctest # not tested see #19231 False :: sage: from sage.symbolic.subring import SymbolicSubring sage: C = SymbolicSubring(no_variables=True) sage: C.has_coerce_map_from(ZZ) # indirect doctest True sage: C.has_coerce_map_from(QQ) # indirect doctest True sage: C.has_coerce_map_from(RR) # indirect doctest True sage: C.has_coerce_map_from(RIF) # indirect doctest True sage: C.has_coerce_map_from(CC) # indirect doctest True sage: C.has_coerce_map_from(CIF) # indirect doctest True sage: C.has_coerce_map_from(AA) # indirect doctest True sage: C.has_coerce_map_from(QQbar) # indirect doctest True sage: C.has_coerce_map_from(SR) # indirect doctest False """ if P == SR: # Workaround; can be deleted once #19231 is fixed return False from sage.rings.real_mpfr import mpfr_prec_min from sage.rings.all import (ComplexField, RLF, CLF, AA, QQbar, InfinityRing) from sage.rings.real_mpfi import is_RealIntervalField from sage.rings.complex_interval_field import is_ComplexIntervalField if isinstance(P, type): return SR._coerce_map_from_(P) elif RLF.has_coerce_map_from(P) or \ CLF.has_coerce_map_from(P) or \ AA.has_coerce_map_from(P) or \ QQbar.has_coerce_map_from(P): return True elif (P is InfinityRing or is_RealIntervalField(P) or is_ComplexIntervalField(P)): return True elif ComplexField(mpfr_prec_min()).has_coerce_map_from(P): return P not in (RLF, CLF, AA, QQbar)
def __init__(self, point, dop=None, singular=None, **kwds): """ INPUT: - ``singular``: can be set to True to force this point to be considered a singular point, even if this cannot be checked (e.g. because we only have an enclosure) TESTS:: sage: from ore_algebra import * sage: from ore_algebra.analytic.path import Point sage: Dops, x, Dx = DifferentialOperators() sage: [Point(z, Dx) ....: for z in [1, 1/2, 1+I, QQbar(I), RIF(1/3), CIF(1/3), pi, ....: RDF(1), CDF(I), 0.5r, 0.5jr, 10r, QQbar(1), AA(1/3)]] [1, 1/2, I + 1, I, [0.333333333333333...], [0.333333333333333...], 3.141592653589794?, ~1.0000, ~1.0000*I, ~0.50000, ~0.50000*I, 10, 1, 1/3] sage: Point(sqrt(2), Dx).iv() [1.414...] sage: Point(RBF(0), (x-1)*x*Dx, singular=True).dist_to_sing() 1.000000000000000 """ SageObject.__init__(self) from sage.rings.complex_double import ComplexDoubleField_class from sage.rings.complex_field import ComplexField_class from sage.rings.complex_interval_field import ComplexIntervalField_class from sage.rings.real_double import RealDoubleField_class from sage.rings.real_mpfi import RealIntervalField_class from sage.rings.real_mpfr import RealField_class point = sage.structure.coerce.py_scalar_to_element(point) try: parent = point.parent() except AttributeError: raise TypeError("unexpected value for point: " + repr(point)) if isinstance(point, Point): self.value = point.value elif isinstance(parent, (RealBallField, ComplexBallField)): self.value = point elif isinstance(parent, number_field_base.NumberField): _, hom = good_number_field(point.parent()) self.value = hom(point) elif QQ.has_coerce_map_from(parent): self.value = QQ.coerce(point) elif QQbar.has_coerce_map_from(parent): alg = QQbar.coerce(point) NF, val, hom = alg.as_number_field_element() if NF is QQ: self.value = QQ.coerce(val) # parent may be ZZ else: embNF = number_field.NumberField(NF.polynomial(), NF.variable_name(), embedding=hom(NF.gen())) self.value = val.polynomial()(embNF.gen()) elif isinstance( parent, (RealField_class, RealDoubleField_class, RealIntervalField_class)): self.value = RealBallField(point.prec())(point) elif isinstance(parent, (ComplexField_class, ComplexDoubleField_class, ComplexIntervalField_class)): self.value = ComplexBallField(point.prec())(point) elif parent is sage.symbolic.ring.SR: try: return self.__init__(point.pyobject(), dop) except TypeError: pass try: return self.__init__(QQbar(point), dop) except (TypeError, ValueError, NotImplementedError): pass try: self.value = RLF(point) except (TypeError, ValueError): self.value = CLF(point) else: try: self.value = RLF.coerce(point) except TypeError: self.value = CLF.coerce(point) parent = self.value.parent() assert (isinstance( parent, (number_field_base.NumberField, RealBallField, ComplexBallField)) or parent is RLF or parent is CLF) if dop is None: # TBI if isinstance(point, Point): self.dop = point.dop else: self.dop = DifferentialOperator(dop.numerator()) self._force_singular = bool(singular) self.options = kwds
class Point(SageObject): r""" A point on the complex plane with an associated differential operator. A point can be exact (a number field element) or inexact (a real or complex interval or ball). It can be classified as ordinary, regular singular, etc. The main reason for making the operator part of the definition of Points is that this gives a convenient place to cache information that depend on both, with an appropriate lifetime. Note however that the point is considered to lie on the complex plane, not on the Riemann surface of the operator. """ def __init__(self, point, dop=None, singular=None, **kwds): """ INPUT: - ``singular``: can be set to True to force this point to be considered a singular point, even if this cannot be checked (e.g. because we only have an enclosure) TESTS:: sage: from ore_algebra import * sage: from ore_algebra.analytic.path import Point sage: Dops, x, Dx = DifferentialOperators() sage: [Point(z, Dx) ....: for z in [1, 1/2, 1+I, QQbar(I), RIF(1/3), CIF(1/3), pi, ....: RDF(1), CDF(I), 0.5r, 0.5jr, 10r, QQbar(1), AA(1/3)]] [1, 1/2, I + 1, I, [0.333333333333333...], [0.333333333333333...], 3.141592653589794?, ~1.0000, ~1.0000*I, ~0.50000, ~0.50000*I, 10, 1, 1/3] sage: Point(sqrt(2), Dx).iv() [1.414...] sage: Point(RBF(0), (x-1)*x*Dx, singular=True).dist_to_sing() 1.000000000000000 """ SageObject.__init__(self) from sage.rings.complex_double import ComplexDoubleField_class from sage.rings.complex_field import ComplexField_class from sage.rings.complex_interval_field import ComplexIntervalField_class from sage.rings.real_double import RealDoubleField_class from sage.rings.real_mpfi import RealIntervalField_class from sage.rings.real_mpfr import RealField_class point = sage.structure.coerce.py_scalar_to_element(point) try: parent = point.parent() except AttributeError: raise TypeError("unexpected value for point: " + repr(point)) if isinstance(point, Point): self.value = point.value elif isinstance(parent, (RealBallField, ComplexBallField)): self.value = point elif isinstance(parent, number_field_base.NumberField): _, hom = good_number_field(point.parent()) self.value = hom(point) elif QQ.has_coerce_map_from(parent): self.value = QQ.coerce(point) elif QQbar.has_coerce_map_from(parent): alg = QQbar.coerce(point) NF, val, hom = alg.as_number_field_element() if NF is QQ: self.value = QQ.coerce(val) # parent may be ZZ else: embNF = number_field.NumberField(NF.polynomial(), NF.variable_name(), embedding=hom(NF.gen())) self.value = val.polynomial()(embNF.gen()) elif isinstance( parent, (RealField_class, RealDoubleField_class, RealIntervalField_class)): self.value = RealBallField(point.prec())(point) elif isinstance(parent, (ComplexField_class, ComplexDoubleField_class, ComplexIntervalField_class)): self.value = ComplexBallField(point.prec())(point) elif parent is sage.symbolic.ring.SR: try: return self.__init__(point.pyobject(), dop) except TypeError: pass try: return self.__init__(QQbar(point), dop) except (TypeError, ValueError, NotImplementedError): pass try: self.value = RLF(point) except (TypeError, ValueError): self.value = CLF(point) else: try: self.value = RLF.coerce(point) except TypeError: self.value = CLF.coerce(point) parent = self.value.parent() assert (isinstance( parent, (number_field_base.NumberField, RealBallField, ComplexBallField)) or parent is RLF or parent is CLF) if dop is None: # TBI if isinstance(point, Point): self.dop = point.dop else: self.dop = DifferentialOperator(dop.numerator()) self._force_singular = bool(singular) self.options = kwds def _repr_(self, size=False): """ TESTS:: sage: from ore_algebra import * sage: from ore_algebra.analytic.path import Point sage: Dops, x, Dx = DifferentialOperators() sage: Point(10**20, Dx) ~1.0000e20 """ if self.is_exact(): try: len = (self.value.parent().precision() if isinstance( self.value, (RealBall, ComplexBall)) else self.nbits()) if len > 50: res = repr(self.value.n(digits=5)) if size: return "~[{}b]{}".format(self.nbits(), res) else: return "~" + res except AttributeError: pass return repr(self.value) def keep_value(self): return bool(self.options.get("keep_value")) def nbits(self): if isinstance(self.value, (RealBall, ComplexBall)): return self.value.nbits() else: res = self.value.denominator().nbits() res += max(self.value.numerator().real().numerator().nbits(), self.value.numerator().imag().numerator().nbits()) return res @cached_method def is_fast(self): return isinstance(self.value, (RealBall, ComplexBall, rings.Integer, rings.Rational)) or is_QQi(self.value.parent()) def bit_burst_bits(self, tgt_prec): if self.is_fast(): return self.nbits() else: # RLF, CLF, other number fields (debatable!) return tgt_prec # Numeric representations @cached_method def iv(self): """ EXAMPLES:: sage: from ore_algebra import * sage: from ore_algebra.analytic.path import Point sage: Dops, x, Dx = DifferentialOperators() sage: [Point(z, Dx).iv() ....: for z in [1, 1/2, 1+I, QQbar(I), RIF(1/3), CIF(1/3), pi]] [1.000000000000000, 0.5000000000000000, 1.000000000000000 + 1.000000000000000*I, 1.000000000000000*I, [0.333333333333333 +/- 3.99e-16], [0.333333333333333 +/- 3.99e-16], [3.141592653589793 +/- 7.83e-16]] """ return IC(self.value) def exact(self): r""" EXAMPLES:: sage: from ore_algebra import * sage: from ore_algebra.analytic.path import Point sage: Dops, x, Dx = DifferentialOperators() sage: QQi.<i> = QuadraticField(-1) sage: [Point(z, Dx).exact() for z in [1, 1/2, 1+i, QQbar(I)]] [1, 1/2, i + 1, I] sage: [Point(z, Dx).exact() for z in [RBF(3/4), RBF(1) + I]] [3/4, i + 1] sage: Point(RIF(1/3), Dx).exact() Traceback (most recent call last): ... ValueError """ if self.value.parent().is_exact(): return self elif isinstance(self.value, RealBall) and self.value.is_exact(): return Point(QQ(self.value), self.dop, **self.options) elif isinstance(self.value, ComplexBall) and self.value.is_exact(): value = QQi((QQ(self.value.real()), QQ(self.value.imag()))) return Point(value, self.dop, **self.options) raise ValueError def approx_abs_real(self, prec): r""" Compute an approximation with absolute error about 2^(-prec). """ if isinstance(self.value.parent(), RealBallField): return self.value elif self.value.is_zero(): return RealBallField(max(2, prec)).zero() elif self.is_real(): expo = ZZ(IR(self.value).abs().log(2).upper().ceil()) rel_prec = max(2, prec + expo + 10) val = RealBallField(rel_prec)(self.value) return val else: raise ValueError("point may not be real") def is_real(self): return is_real_parent(self.value.parent()) def is_exact(self): r""" Is this point exact in the sense that we can use it in the coefficients of an operator? """ return (isinstance( self.value, (rings.Integer, rings.Rational, rings.NumberFieldElement)) or isinstance(self.value, (RealBall, ComplexBall)) and self.value.is_exact()) def rationalize(self): a = self.iv() if any(a.overlaps(s) for s in self.dop._singularities(IC)): raise PathPrecisionError else: return Point(_rationalize(a), self.dop) def truncate(self, prec, tgt_prec): Ivs = RealBallField if self.is_real() else ComplexBallField approx = Ivs(prec)(self.value).round() lc = self.dop.leading_coefficient() if lc(approx).contains_zero(): raise PathPrecisionError # appropriate? approx = approx.squash() return Point(Ivs(tgt_prec)(approx), self.dop) def __complex__(self): return complex(self.value) # Point equality is identity def __eq__(self, other): return self is other def __hash__(self): return id(self) def __lt__(self, other): r""" Temporary kludge (Sage graphs require vertices to be comparable). """ return id(self) < id(other) ### Methods that depend on dop @cached_method def is_ordinary(self): if self._force_singular: return False lc = self.dop.leading_coefficient() if not lc(self.iv()).contains_zero(): return True if self.is_exact(): try: val = lc(self.value) except TypeError: # work around coercion weaknesses val = lc.change_ring(QQbar)(QQbar.coerce(self.value)) return not val.is_zero() else: raise ValueError("can't tell if inexact point is singular") def is_singular(self): return not self.is_ordinary() @cached_method def is_regular(self): try: if self.is_ordinary(): return True except ValueError: # we could handle balls containing no irregular singular point... raise NotImplementedError("can't tell if inexact point is regular") assert self.is_exact() # Fuchs criterion dop, pt = self.dop.extend_scalars(self.value) Pols = dop.base_ring() lin = Pols([pt, -1]) ref = dop.leading_coefficient().valuation(lin) - dop.order() return all( coef.valuation(lin) - k >= ref for k, coef in enumerate(dop)) def is_regular_singular(self): return not self.is_ordinary() and self.is_regular() def is_irregular(self): return not self.is_regular() def singularity_type(self, short=False): r""" EXAMPLES:: sage: from ore_algebra import * sage: from ore_algebra.analytic.path import Point sage: Dops, x, Dx = DifferentialOperators() sage: dop = (x^2 + 1)*Dx^2 + 2*x*Dx sage: Point(1, dop).singularity_type() 'ordinary point' sage: Point(i, dop).singularity_type() 'regular singular point' sage: Point(0, x^2*Dx + 1).singularity_type() 'irregular singular point' sage: Point(CIF(1/3), x^2*Dx + 1).singularity_type() 'ordinary point' sage: Point(CIF(1/3)-1/3, x^2*Dx + 1).singularity_type() 'point of unknown singularity type' """ try: if self.is_ordinary(): return "" if short else "ordinary point" elif self.is_regular(): return "regular singular point" else: return "irregular singular point" except (ValueError, NotImplementedError): return "point of unknown singularity type" def descr(self): t = self.singularity_type(short=True) if t == "": return repr(self) else: return t + " " + repr(self) def dist_to_sing(self): """ Distance of self to the singularities of self.dop *other than self*. TESTS:: sage: from ore_algebra import * sage: from ore_algebra.analytic.path import Point sage: Dops, x, Dx = DifferentialOperators() sage: dop = (x^2 + 1)*Dx^2 + 2*x*Dx sage: Point(1, dop).dist_to_sing() [1.41421356237309...] sage: Point(i, dop).dist_to_sing() 2.00... sage: Point(1+i, dop).dist_to_sing() 1.00... """ sing = self.dop._singularities(IC) close, distant = split(lambda s: s.overlaps(self.iv()), sing) if (len(close) >= 2 or len(close) == 1 and not self.is_singular()): raise NotImplementedError # refine? dist = [(self.iv() - s).abs() for s in distant] min_dist = IR(rings.infinity).min(*dist) if min_dist.contains_zero(): raise NotImplementedError # refine??? return IR(min_dist.lower()) def local_basis_structure(self): r""" EXAMPLES:: sage: from ore_algebra import * sage: from ore_algebra.analytic.path import Point sage: Dops, x, Dx = DifferentialOperators() sage: Point(0, x*Dx^2 + Dx + x).local_basis_structure() [FundamentalSolution(leftmost=0, shift=0, log_power=1, value=None), FundamentalSolution(leftmost=0, shift=0, log_power=0, value=None)] sage: Point(0, Dx^3 + x*Dx + x).local_basis_structure() [FundamentalSolution(leftmost=0, shift=0, log_power=0, value=None), FundamentalSolution(leftmost=0, shift=1, log_power=0, value=None), FundamentalSolution(leftmost=0, shift=2, log_power=0, value=None)] """ # TODO: provide a way to compute the first terms of the series. First # need a good way to share code with fundamental_matrix_regular. Or # perhaps modify generalized_series_solutions() to agree with our # definition of the basis? if self.is_ordinary(): # support inexact points in this case return [ FundamentalSolution(QQbar.zero(), ZZ(expo), ZZ.zero(), None) for expo in range(self.dop.order()) ] elif not self.is_regular(): raise NotImplementedError("irregular singular point") return LocalBasisMapper(self.dop.shift(self)).run() @cached_method def simple_approx(self, ctx=dctx): r""" Return an approximation of this point suitable as a starting point for the next analytic continuation step. For intermediate steps via simple points to reach points of large bit size or with irrational coordinates in the context of binary splitting, see bit_burst_split(). For intermediate steps where thick balls are shrinked to their center, see exact_approx(). """ # Point options become meaningless (and are lost) when not returning # self. thr = ctx.simple_approx_thr if (self.is_singular() or self.is_fast() and self.is_exact() and self.nbits() <= thr): return self else: rad = RBF.one().min(self.dist_to_sing() / 16) ball = self.iv().add_error(rad) if any(s.overlaps(ball) for s in self.dop._singularities(IC)): return self rat = _rationalize(ball, real=self.is_real()) return Point(rat, self.dop) @cached_method def exact_approx(self): if isinstance(self.value, (RealBall, ComplexBall)): if not self.value.is_exact(): return Point(self.value.trim().squash(), self.dop) return self