def __init__(self, expo, values, dop=None, check=True): r""" TESTS:: sage: from ore_algebra import * sage: from ore_algebra.analytic.naive_sum import * sage: Dops, x, Dx = DifferentialOperators() sage: LogSeriesInitialValues(0, {0: (1, 0)}, x*Dx^3 + 2*Dx^2 + x*Dx) Traceback (most recent call last): ... ValueError: invalid initial data for x*Dx^3 + 2*Dx^2 + x*Dx at 0 """ try: self.expo = QQ.coerce(expo) except TypeError: self.expo = QQbar.coerce(expo) if isinstance(values, dict): all_values = sum(values.values(), ()) # concatenation of tuples else: all_values = values values = dict((n, (values[n], )) for n in xrange(len(values))) self.universe = Sequence(all_values).universe() if not utilities.is_numeric_parent(self.universe): raise ValueError("initial values must coerce into a ball field") self.shift = { s: tuple(self.universe(a) for a in ini) for s, ini in values.iteritems() } try: if check and dop is not None and not self.is_valid_for(dop): raise ValueError( "invalid initial data for {} at 0".format(dop)) except TypeError: # coercion problems btw QQbar and number fields pass
def __init__(self, expo, values, dop=None, check=True, mults=None): r""" TESTS:: sage: from ore_algebra import * sage: from ore_algebra.analytic.naive_sum import * sage: from ore_algebra.analytic.differential_operator import DifferentialOperator sage: Dops, x, Dx = DifferentialOperators() sage: LogSeriesInitialValues(0, {0: (1, 0)}, ....: DifferentialOperator(x*Dx^3 + 2*Dx^2 + x*Dx)) Traceback (most recent call last): ... ValueError: invalid initial data for x*Dx^3 + 2*Dx^2 + x*Dx at 0 """ try: self.expo = QQ.coerce(expo) except TypeError: try: self.expo = QQbar.coerce(expo) except TypeError: # symbolic; won't be sortable self.expo = expo if isinstance(values, dict): all_values = tuple( chain.from_iterable(ini if isinstance(ini, tuple) else (ini, ) for ini in values.values())) else: all_values = values values = dict((n, (values[n], )) for n in range(len(values))) self.universe = Sequence(all_values).universe() if not utilities.is_numeric_parent(self.universe): raise ValueError("initial values must coerce into a ball field") self.shift = {} if mults is not None: for s, m in mults: self.shift[s] = [self.universe.zero()] * m for k, ini in values.items(): if isinstance(k, tuple): # requires mult != None s, m = k s = int(s) self.shift[s][m] = self.universe(ini) else: s = int(k) self.shift[s] = tuple(self.universe(a) for a in ini) self.shift = {s: tuple(ini) for s, ini in self.shift.items()} try: if check and dop is not None and not self.is_valid_for(dop): raise ValueError( "invalid initial data for {} at 0".format(dop)) except TypeError: # coercion problems btw QQbar and number fields pass
def as_embedded_number_field_element(alg): from sage.rings.number_field.number_field import NumberField nf, elt, emb = alg.as_number_field_element() if nf is QQ: res = elt else: embnf = NumberField(nf.polynomial(), nf.variable_name(), embedding=emb(nf.gen())) res = elt.polynomial()(embnf.gen()) assert QQbar.coerce(res) == alg return res
def as_embedded_number_field_elements(algs): try: nf, elts, _ = number_field_elements_from_algebraics(algs, embedded=True) except NotImplementedError: # compatibility with Sage <= 9.3 nf, elts, emb = number_field_elements_from_algebraics(algs) if nf is not QQ: nf = NumberField(nf.polynomial(), nf.variable_name(), embedding=emb(nf.gen())) elts = [elt.polynomial()(nf.gen()) for elt in elts] nf, hom = good_number_field(nf) elts = [hom(elt) for elt in elts] assert [QQbar.coerce(elt) == alg for alg, elt in zip(algs, elts)] return nf, elts
def _try_merge_conjugate_singularities(dop, sing, base, todo): if any(c not in QQ for pol in dop for c in pol): return False need_conjugates = False sgn = 1 if QQbar.coerce(base.value).imag() >= 0 else -1 for x in sing: if sgn * x.imag() < 0: need_conjugates = True del todo[x] xconj = x.conjugate() if xconj not in todo: todo[xconj] = LocalMonodromyData() todo[xconj].want_conj = True return need_conjugates
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 __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 _monodromy_matrices(dop, base, eps=1e-16, sing=None): r""" EXAMPLES:: sage: from ore_algebra import * sage: from ore_algebra.analytic.monodromy import _monodromy_matrices sage: Dops, x, Dx = DifferentialOperators() sage: rat = 1/(x^2-1) sage: dop = (rat*Dx - rat.derivative()).lclm(Dx*x*Dx) sage: [rec.point for rec in _monodromy_matrices(dop, 0) if not rec.is_scalar] [0] TESTS:: sage: from ore_algebra.examples import fcc sage: mon = list(_monodromy_matrices(fcc.dop5, -1, 2**(-2**7))) # long time (2.3 s) sage: [rec.monodromy[0][0] for rec in mon if rec.point == -5/3] # long time [[1.01088578589319884254557667137848...]] Thanks to Alexandre Goyer for this example:: sage: L1 = ((x^5 - x^4 + x^3)*Dx^3 + (27/8*x^4 - 25/9*x^3 + 8*x^2)*Dx^2 ....: + (37/24*x^3 - 25/9*x^2 + 14*x)*Dx - 2*x^2 - 3/4*x + 4) sage: L2 = ((x^5 - 9/4*x^4 + x^3)*Dx^3 + (11/6*x^4 - 31/4*x^3 + 7*x^2)*Dx^2 ....: + (7/30*x^3 - 101/20*x^2 + 10*x)*Dx + 4/5*x^2 + 5/6*x + 2) sage: L = L1*L2 sage: L = L.parent()(L.annihilator_of_composition(x+1)) sage: mon = list(_monodromy_matrices(L, 0, eps=1e-30)) # long time (1.3-1.7 s) sage: mon[-1][0], mon[-1][1][0][0] # long time (0.6403882032022075?, [1.15462187280628880820271...] + [-0.018967673022432256251718...]*I) """ dop = DifferentialOperator(dop) base = QQbar.coerce(base) eps = RBF(eps) if sing is None: sing = dop._singularities(QQbar) else: sing = [QQbar.coerce(s) for s in sing] todo = {x: TodoItem(x, dop, want_self=True, want_conj=False) for x in sing} base = todo.setdefault(base, TodoItem(base, dop)) if not base.point().is_regular(): raise ValueError("irregular singular base point") # If the coefficients are rational, reduce to handling singularities in the # same half-plane as the base point, and share some computations between # Galois conjugates. need_conjugates = False crit_cache = None if all(c in QQ for pol in dop for c in pol): need_conjugates = _merge_conjugate_singularities(dop, sing, base, todo) # TODO: do something like that even over number fields? # XXX this is actually a bit costly: do it only after checking that the # monodromy is not scalar? # XXX keep the cache from one run to the next when increasing prec? crit_cache = {} Scalars = ComplexBallField(utilities.prec_from_eps(eps)) id_mat = matrix.identity_matrix(Scalars, dop.order()) def matprod(elts): return prod(reversed(elts), id_mat) for key, todoitem in list(todo.items()): point = todoitem.point() # We could call _local_monodromy_loop() if point is irregular, but # delaying it may allow us to start returning results earlier. if point.is_regular(): if crit_cache is None or point.algdeg() == 1: crit = _critical_monomials(dop.shift(point)) emb = point.value.parent().hom(Scalars) else: mpol = point.value.minpoly() try: NF, crit = crit_cache[mpol] except KeyError: NF = point.value.parent() crit = _critical_monomials(dop.shift(point)) # Only store the critical monomials for reusing when all # local exponents are rational. We need to restrict to this # case because we do not have the technology in place to # follow algebraic exponents along the embedding of NF in ℂ. # (They are represented as elements of "new" number fields # given by as_embedded_number_field_element(), even when # they actually lie in NF itself as opposed to a further # algebraic extension. XXX: Ideally, LocalBasisMapper should # give us access to the tower of extensions in which the # exponents "naturally" live.) if all(sol.leftmost.parent() is QQ for sol in crit): crit_cache[mpol] = NF, crit emb = NF.hom([Scalars(point.value.parent().gen())], check=False) mon, scalar = _formal_monodromy_from_critical_monomials(crit, emb) if scalar: # No need to compute the connection matrices then! # XXX When we do need them, though, it would be better to get # the formal monodromy as a byproduct of their computation. if todoitem.want_self: yield LocalMonodromyData(key, mon, True) if todoitem.want_conj: conj = key.conjugate() logger.info( "Computing local monodromy around %s by " "complex conjugation", conj) conj_mat = ~mon.conjugate() yield LocalMonodromyData(conj, conj_mat, True) if todoitem is not base: del todo[key] continue todoitem.local_monodromy = [mon] todoitem.polygon = [point] if need_conjugates: base_conj_mat = dop.numerical_transition_matrix( [base.point(), base.point().conjugate()], eps, assume_analytic=True) def conjugate_monodromy(mat): return ~base_conj_mat * ~mat.conjugate() * base_conj_mat tree = _spanning_tree(base, todo.values()) def dfs(x, path, path_mat): logger.info("Computing local monodromy around %s via %s", x, path) local_mat = matprod(x.local_monodromy) based_mat = (~path_mat) * local_mat * path_mat if x.want_self: yield LocalMonodromyData(x.alg, based_mat, False) if x.want_conj: conj = x.alg.conjugate() logger.info( "Computing local monodromy around %s by complex " "conjugation", conj) conj_mat = conjugate_monodromy(based_mat) yield LocalMonodromyData(conj, conj_mat, False) x.done = True for y in tree.neighbors(x): if y.done: continue if y.local_monodromy is None: y.polygon, y.local_monodromy = _local_monodromy_loop( dop, y.point(), eps) new_path_mat = _extend_path_mat(dop, path_mat, x, y, eps, matprod) yield from dfs(y, path + [y], new_path_mat) yield from dfs(base, [base], id_mat)
def _monodromy_matrices(dop, base, eps=1e-16, sing=None): r""" EXAMPLES:: sage: from ore_algebra import * sage: from ore_algebra.analytic.monodromy import _monodromy_matrices sage: Dops, x, Dx = DifferentialOperators() sage: rat = 1/(x^2-1) sage: dop = (rat*Dx - rat.derivative()).lclm(Dx*x*Dx) sage: [rec.point for rec in _monodromy_matrices(dop, 0) if not rec.is_scalar] [0] TESTS:: sage: from ore_algebra.examples import fcc sage: mon = list(_monodromy_matrices(fcc.dop5, -1, 2**(-2**7))) # long time (2.3 s) sage: [rec.monodromy[0][0] for rec in mon if rec.point == -5/3] # long time [[1.01088578589319884254557667137848...]] """ dop = DifferentialOperator(dop) base = QQbar.coerce(base) eps = RBF(eps) if sing is None: sing = dop._singularities(QQbar) else: sing = [QQbar.coerce(s) for s in sing] todo = {x: TodoItem(x, dop, want_self=True, want_conj=False) for x in sing} base = todo.setdefault(base, TodoItem(base, dop)) if not base.point().is_regular(): raise ValueError("irregular singular base point") # If the coefficients are rational, reduce to handling singularities in the # same half-plane as the base point, and share some computations between # Galois conjugates. need_conjugates = False crit_cache = None if all(c in QQ for pol in dop for c in pol): need_conjugates = _merge_conjugate_singularities(dop, sing, base, todo) # TODO: do something like that even over number fields? # XXX this is actually a bit costly: do it only after checking that the # monodromy is not scalar? # XXX keep the cache from one run to the next when increasing prec? crit_cache = {} Scalars = ComplexBallField(utilities.prec_from_eps(eps)) id_mat = matrix.identity_matrix(Scalars, dop.order()) def matprod(elts): return prod(reversed(elts), id_mat) for key, todoitem in list(todo.items()): point = todoitem.point() # We could call _local_monodromy_loop() if point is irregular, but # delaying it may allow us to start returning results earlier. if point.is_regular(): if crit_cache is None or point.algdeg() == 1: crit = _critical_monomials(dop.shift(point)) emb = point.value.parent().hom(Scalars) else: mpol = point.value.minpoly() try: NF, crit = crit_cache[mpol] except KeyError: NF = point.value.parent() crit = _critical_monomials(dop.shift(point)) crit_cache[mpol] = NF, crit emb = NF.hom([Scalars(point.value.parent().gen())], check=False) mon, scalar = _formal_monodromy_from_critical_monomials(crit, emb) if scalar: # No need to compute the connection matrices then! # XXX When we do need them, though, it would be better to get # the formal monodromy as a byproduct of their computation. if todoitem.want_self: yield LocalMonodromyData(key, mon, True) if todoitem.want_conj: conj = key.conjugate() logger.info( "Computing local monodromy around %s by " "complex conjugation", conj) conj_mat = ~mon.conjugate() yield LocalMonodromyData(conj, conj_mat, True) if todoitem is not base: del todo[key] continue todoitem.local_monodromy = [mon] todoitem.polygon = [point] if need_conjugates: base_conj_mat = dop.numerical_transition_matrix( [base.point(), base.point().conjugate()], eps, assume_analytic=True) def conjugate_monodromy(mat): return ~base_conj_mat * ~mat.conjugate() * base_conj_mat tree = _spanning_tree(base, todo.values()) def dfs(x, path, path_mat): logger.info("Computing local monodromy around %s via %s", x, path) local_mat = matprod(x.local_monodromy) based_mat = (~path_mat) * local_mat * path_mat if x.want_self: yield LocalMonodromyData(x.alg, based_mat, False) if x.want_conj: conj = x.alg.conjugate() logger.info( "Computing local monodromy around %s by complex " "conjugation", conj) conj_mat = conjugate_monodromy(based_mat) yield LocalMonodromyData(conj, conj_mat, False) x.done = True for y in tree.neighbors(x): if y.done: continue if y.local_monodromy is None: y.polygon, y.local_monodromy = _local_monodromy_loop( dop, y.point(), eps) new_path_mat = _extend_path_mat(dop, path_mat, x, y, eps, matprod) yield from dfs(y, path + [y], new_path_mat) yield from dfs(base, [base], id_mat)
def _monodromy_matrices(dop, base, eps=1e-16, sing=None): r""" EXAMPLES:: sage: from ore_algebra import * sage: from ore_algebra.analytic.monodromy import _monodromy_matrices sage: Dops, x, Dx = DifferentialOperators() sage: rat = 1/(x^2-1) sage: dop = (rat*Dx - rat.derivative()).lclm(Dx*x*Dx) sage: [rec.point for rec in _monodromy_matrices(dop, 0) if not rec.is_scalar] [0] """ dop = DifferentialOperator(dop) base = QQbar.coerce(base) eps = RBF(eps) if sing is None: sing = dop._singularities(QQbar) else: sing = [QQbar.coerce(s) for s in sing] todo = { x: PointWithMonodromyData(x, dop, want_self=True, want_conj=False) for x in sing } base = todo.setdefault(base, PointWithMonodromyData(base, dop)) if not base.is_regular(): raise ValueError("irregular singular base point") # If the coefficients are rational, reduce to handling singularities in the # same half-plane as the base point. need_conjugates = _try_merge_conjugate_singularities(dop, sing, base, todo) Scalars = ComplexBallField(utilities.prec_from_eps(eps)) id_mat = matrix.identity_matrix(Scalars, dop.order()) def matprod(elts): return prod(reversed(elts), id_mat) for key, point in list(todo.items()): # We could call _local_monodromy_loop() if point is irregular, but # delaying it may allow us to start returning results earlier. if point.is_regular(): mon, scalar = _formal_monodromy_naive(dop.shift(point), Scalars) if scalar: # No need to compute the connection matrices then! # XXX When we do need them, though, it would be better to get # the formal monodromy as a byproduct of their computation. if point.want_self: yield LocalMonodromyData(QQbar(point), mon, True) if point.want_conj: conj = point.conjugate() logger.info( "Computing local monodromy around %s by " "complex conjugation", conj) conj_mat = ~mon.conjugate() yield LocalMonodromyData(QQbar(conj), conj_mat, True) if point is not base: del todo[key] continue point.local_monodromy = [mon] point.polygon = [point] if need_conjugates: base_conj_mat = dop.numerical_transition_matrix( [base, base.conjugate()], eps, assume_analytic=True) def conjugate_monodromy(mat): return ~base_conj_mat * ~mat.conjugate() * base_conj_mat tree = _spanning_tree(base, todo.values()) def dfs(x, path, path_mat): logger.info("Computing local monodromy around %s via %s", x, path) local_mat = matprod(x.local_monodromy) based_mat = (~path_mat) * local_mat * path_mat if x.want_self: yield LocalMonodromyData(QQbar(x), based_mat, False) if x.want_conj: conj = x.conjugate() logger.info( "Computing local monodromy around %s by complex " "conjugation", conj) conj_mat = conjugate_monodromy(based_mat) yield LocalMonodromyData(QQbar(x), conj_mat, False) x.done = True for y in tree.neighbors(x): if y.done: continue if y.local_monodromy is None: y.polygon, y.local_monodromy = _local_monodromy_loop( dop, y, eps) new_path_mat = _extend_path_mat(dop, path_mat, x, y, eps, matprod) yield from dfs(y, path + [y], new_path_mat) yield from dfs(base, [base], id_mat)
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 conjugate(self): value = QQbar.coerce(self.value).conjugate() return Point(value, self.dop, **self.options)