def _test_fun_approx(pol, ref, disk_rad=None, interval_rad=None, prec=53, test_count=100): r""" EXAMPLES:: sage: from ore_algebra.analytic.polynomial_approximation import _test_fun_approx sage: _test_fun_approx(lambda x: x.exp(), lambda x: x.exp() + x/1000, ....: interval_rad=1) Traceback (most recent call last): ... AssertionError: z = ..., ref(z) = ... not in pol(z) = ... """ from sage.rings.real_mpfr import RealField from sage.rings.real_arb import RealBallField from sage.rings.complex_arb import ComplexBallField my_RR = RealField(prec) my_RBF = RealBallField(prec) my_CBF = ComplexBallField(prec) if bool(disk_rad) == bool(interval_rad): raise ValueError rad = disk_rad or interval_rad for _ in range(test_count): rho = my_RBF(my_RR.random_element(-rad, rad)) if disk_rad: exp_i_theta = my_CBF(my_RR.random_element(0, 1)).exppii() z = rho*exp_i_theta elif interval_rad: z = rho ref_z = ref(z) pol_z = pol(z) if not ref_z.overlaps(pol_z): fmt = "z = {}, ref(z) = {} not in pol(z) = {}" raise AssertionError(fmt.format(z, ref_z, pol_z))
def _volume_from_shape(z): """ Computes the Bloch-Wigner dilogarithm for z which gives the volume of a tetrahedron of the given shape. """ if _within_sage: CIF = z.parent() if is_ComplexIntervalField(CIF): # A different bug in sage: # Depending on the sage version, an element in a # ComplexIntervalField wouldn't support dilog/polylog, or, even # worse, would convert the element to ComplexField first!!! # # Thus, we convert to ComplexBallField here since the arblib # supports a verified interval polylog (albeit giving an interval # that seems to be 300 times larger than necessary). CBF = ComplexBallField(CIF.precision()) RIF = RealIntervalField(CIF.precision()) return RIF(_unprotected_volume_from_shape(CBF(z))) else: z = Number(z) # Use implementation in number.py that overcomes the cypari bug that you # have to explicitly give a precision to dilog, otherwise you lose # precision. return z.volume()
def prec(self, tgt_prec): myIC = ComplexBallField(tgt_prec + 10) # not ideal... len = IC(myIC(self.end.value) - myIC(self.start.value)).abs() if len.contains_zero(): return ZZ(sys.maxsize) else: return -ZZ(len.log(2).upper().ceil())
def analytic_continuation(ctx, ini=None, post=None): """ INPUT: - ``ini`` (constant matrix, optional) - initial values, one column per solution - ``post`` (matrix of polynomial/rational functions, optional) - linear combinations of the first Taylor coefficients to take, as a function of the evaluation point TESTS:: sage: from ore_algebra import DifferentialOperators sage: _, x, Dx = DifferentialOperators() sage: (Dx^2 + 2*x*Dx).numerical_solution([0, 2/sqrt(pi)], [0,i]) [+/- ...] + [1.65042575879754...]*I """ logger.info("path: %s", ctx.path) eps1 = (ctx.eps / (1 + len(ctx.path))) >> 2 # TBI, +: move to ctx? prec = utilities.prec_from_eps(eps1) if ini is not None: if not isinstance(ini, Matrix): # should this be here? try: ini = matrix(ctx.dop.order(), 1, list(ini)) except (TypeError, ValueError): raise ValueError("incorrect initial values: {}".format(ini)) try: ini = ini.change_ring(RealBallField(prec)) except (TypeError, ValueError): ini = ini.change_ring(ComplexBallField(prec)) res = [] path_mat = identity_matrix(ZZ, ctx.dop.order()) def store_value_if_wanted(point): if point.options.get('keep_value'): value = path_mat if ini is not None: value = value * ini if post is not None: value = post(point.value) * value res.append((point.value, value)) store_value_if_wanted(ctx.path.vert[0]) for step in ctx.path: step_mat = step_transition_matrix(step, eps1, ctx=ctx) path_mat = step_mat * path_mat store_value_if_wanted(step.end) cm = sage.structure.element.get_coercion_model() OutputIntervals = cm.common_parent( utilities.ball_field(ctx.eps, ctx.real()), *[mat.base_ring() for pt, mat in res]) return [(pt, mat.change_ring(OutputIntervals)) for pt, mat in res]
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
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): """ 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 _repr_(self): """ 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 """ try: len = (self.value.numerator().real().numerator().nbits() + self.value.numerator().imag().numerator().nbits() + self.value.denominator().nbits()) if len > 50: return '~' + repr(self.value.n(digits=5)) except AttributeError: pass return repr(self.value) # Numeric representations @cached_method def iv(self): """ 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""" 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.is_exact(): return self elif isinstance(self.value, RealBall) and self.value.is_exact(): return Point(QQ(self.value), self.dop) 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) 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): # XXX: also include exact balls? return isinstance( self.value, (rings.Integer, rings.Rational, rings.NumberFieldElement)) ### Methods that depend on dop @cached_method def is_ordinary(self): lc = self.dop.leading_coefficient() if self.is_exact(): return bool(lc(self.value)) elif not lc(self.iv()).contains_zero(): return True else: raise ValueError("can't tell if inexact point is singular") def is_singular(self): return not is_ordinary(self) @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 Pols = self.dop.base_ring().change_ring(self.value.parent()) def val(pol): return Pols(pol).valuation(Pols([self.value, -1])) ref = val(self.dop.leading_coefficient()) - self.dop.order() return all(val(coef) - k >= ref for k, coef in enumerate(self.dop)) def is_regular_singular(self): return not self.is_ordinary() and self.is_regular() def is_irregular(self): return not is_regular(self) 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... """ # TODO - solve over CBF directly; perhaps with arb's own poly solver sing = dop_singularities(self.dop, CIF) sing = [IC(s) for s in sing] close, distant = split(lambda s: s.overlaps(self.iv()), sing) if (len(close) >= 2 or len(close) == 1 and not self.dop.leading_coefficient()(self.value).is_zero()): 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_diffop(self): # ? r""" TESTS:: sage: from ore_algebra import DifferentialOperators sage: from ore_algebra.analytic.path import Point sage: Dops, x, Dx = DifferentialOperators() sage: Point(1, x*Dx - 1).local_diffop() (x + 1)*Dx - 1 sage: Point(RBF(1/2), x*Dx - 1).local_diffop() (x + 1/2)*Dx - 1 """ Pols_dop = self.dop.base_ring() # NOTE: pushout(QQ[x], K) doesn't handle embeddings well, and creates # an L equal but not identical to K. But then other constructors like # PolynomialRing(L, x) sometimes return objects over K found in cache, # leading to endless headaches with slow coercions. But the version here # may be closer to what I really want in any case. # XXX: This seems to work in the usual trivial case where we are looking # for a scalar domain containing QQ and QQ[i], but probably won't be # enough if we really have two different number fields with embeddings ex = self.exact() Scalars = pushout.pushout(Pols_dop.base_ring(), ex.value.parent()) Pols = Pols_dop.change_ring(Scalars) A, B = self.dop.base_ring().base_ring(), ex.value.parent() C = Pols.base_ring() assert C is A or C != A assert C is B or C != B dop_P = self.dop.change_ring(Pols) return dop_P.annihilator_of_composition(Pols([ex.value, 1])) 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") sols = map_local_basis(self.local_diffop(), lambda ini, bwrec: None, lambda leftmost, shift: {}) sols.sort(key=sort_key_by_asympt) return sols
def analytic_continuation(dop, path, eps, ctx=dctx, ini=None, post=None, return_local_bases=False): """ INPUT: - ``ini`` (constant matrix, optional) - initial values, one column per solution - ``post`` (matrix of polynomial/rational functions, optional) - linear combinations of the first Taylor coefficients to take, as a function of the evaluation point - ``return_local_bases`` (boolean) - if True, also compute and return the structure of local bases at all points where we are computing values of the solution OUTPUT: A list of dictionaries with information on the computed solution(s) at each evaluation point. TESTS:: sage: from ore_algebra import DifferentialOperators sage: _, x, Dx = DifferentialOperators() sage: (Dx^2 + 2*x*Dx).numerical_solution([0, 2/sqrt(pi)], [0,i]) [+/- ...] + [1.65042575879754...]*I """ if dop.is_zero(): raise ValueError("operator must be nonzero") _, _, _, dop = dop._normalize_base_ring() path = _process_path(dop, path, ctx) logger.info("path: %s", path) eps = bounds.IR(eps) eps1 = (eps/(1 + len(path))) >> 2 prec = utilities.prec_from_eps(eps1) if ini is not None: if not isinstance(ini, Matrix): # should this be here? try: ini = matrix(dop.order(), 1, list(ini)) except (TypeError, ValueError): raise ValueError("incorrect initial values: {}".format(ini)) try: ini = ini.change_ring(RealBallField(prec)) except (TypeError, ValueError): ini = ini.change_ring(ComplexBallField(prec)) def point_dict(point, value): if ini is not None: value = value*ini if post is not None and not post.is_one(): value = post(point.value)*value rec = {"point": point.value, "value": value} if return_local_bases: rec["structure"] = point.local_basis_structure() return rec res = [] z0 = path.vert[0] # XXX still imperfect in the case of a high-precision starting point with # relatively large radius... (do we care?) main = Step(z0, z0.simple_approx(ctx=ctx)) path_mat = step_transition_matrix(dop, main, eps1, ctx=ctx) if z0.keep_value(): res.append(point_dict(z0, identity_matrix(ZZ, dop.order()))) for step in path: main, dev = step.chain_simple(main.end, ctx=ctx) main_mat = step_transition_matrix(dop, main, eps1, ctx=ctx) path_mat = main_mat*path_mat if dev is not None: dev_mat = path_mat for sub in dev: sub_mat = step_transition_matrix(dop, sub, eps1, ctx=ctx) dev_mat = sub_mat*dev_mat res.append(point_dict(step.end, dev_mat)) cm = sage.structure.element.get_coercion_model() real = (rings.RIF.has_coerce_map_from(dop.base_ring().base_ring()) and all(v.is_real() for v in path.vert)) OutputIntervals = cm.common_parent( utilities.ball_field(eps, real), *[rec["value"].base_ring() for rec in res]) for rec in res: rec["value"] = rec["value"].change_ring(OutputIntervals) return res
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.000000000000000, 1.000000000000000*I, 0.5000000000000000, 0.5000000000000000*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, (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) 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): """ 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 """ try: len = (self.value.numerator().real().numerator().nbits() + self.value.numerator().imag().numerator().nbits() + self.value.denominator().nbits()) if len > 50: return '~' + repr(self.value.n(digits=5)) except AttributeError: pass return repr(self.value) # Numeric representations @cached_method def iv(self): """ 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""" 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) 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) 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): 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() lc = self.dop.leading_coefficient() if lc(a).contains_zero(): raise PathPrecisionError else: return Point(_rationalize(a), self.dop) # Point equality is identity def __eq__(self, other): return self is other def __hash__(self): return id(self) ### 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 Pols = self.dop.base_ring().change_ring(self.value.parent()) def val(pol): return Pols(pol).valuation(Pols([self.value, -1])) ref = val(self.dop.leading_coefficient()) - self.dop.order() return all(val(coef) - k >= ref for k, coef in enumerate(self.dop)) def is_regular_singular(self): return not self.is_ordinary() and self.is_regular() def is_irregular(self): return not is_regular(self) 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") sols = LocalBasisMapper().run(self.dop.shift(self)) sols.sort(key=sort_key_by_asympt) return sols