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)
def _formal_monodromy_from_critical_monomials(critical_monomials, ring): r""" Compute the formal monodromy matrix of the canonical system of fundamental solutions at the origin. INPUT: - ``critical_monomials``: list of ``FundamentalSolution`` objects ``sol`` such that, if ``sol = z^(λ+n)·(1 + Õ(z)`` where ``λ`` is the leftmost valuation of a group of solutions and ``s`` is another shift of ``λ`` appearing in the basis, then ``sol.value[s]`` contains the list of coefficients of ``z^(λ+s)·log(z)^k/k!``, ``k = 0, 1, ...`` in ``sol`` - ``ring`` OUTPUT: - the formal monodromy matrix, with coefficients in ``ring`` - a boolean flag indicating whether the local monodromy is scalar (useful when ``ring`` is an inexact ring!) """ mat = matrix.matrix(ring, len(critical_monomials)) twopii = 2 * pi * QQbar(QQi.gen()) expo0 = critical_monomials[0].leftmost scalar = True for j, jsol in enumerate(critical_monomials): for i, isol in enumerate(critical_monomials): if isol.leftmost != jsol.leftmost: continue for k, c in enumerate(jsol.value[isol.shift]): delta = k - isol.log_power if c.is_zero(): continue if delta >= 0: # explicit conversion sometimes necessary (Sage bug #31551) mat[i, j] += ring(c) * twopii**delta / delta.factorial() if delta >= 1: scalar = False expo = jsol.leftmost if expo != expo0: scalar = False if expo.parent() is QQ: eigv = ring(QQbar.zeta(expo.denominator())**expo.numerator()) else: # conversion via QQbar seems necessary with some number fields eigv = twopii.mul(QQbar(expo), hold=True).exp(hold=True) eigv = ring(eigv) if ring is SR: _rescale_col_hold_nontrivial(mat, j, eigv) else: mat.rescale_col(j, eigv) return mat, scalar
def is_cosine_sine_of_rational(c,s): r""" Check whether the given pair is a cosine and sine of a same rational angle. EXAMPLES:: sage: from flatsurf.geometry.matrix_2x2 import is_cosine_sine_of_rational sage: c = s = AA(sqrt(2))/2 sage: is_cosine_sine_of_rational(c,s) True sage: c = AA(sqrt(3))/2; s = AA(1/2) sage: is_cosine_sine_of_rational(c,s) True sage: c = AA(sqrt(5)/2); s = (1 - c**2).sqrt() sage: c**2 + s**2 1.000000000000000? sage: is_cosine_sine_of_rational(c,s) False sage: c = (AA(sqrt(5)) + 1)/4; s = (1 - c**2).sqrt() sage: is_cosine_sine_of_rational(c,s) True sage: K.<sqrt2> = NumberField(x**2 - 2, embedding=1.414) sage: is_cosine_sine_of_rational(K.zero(),-K.one()) True """ return (QQbar(c) + QQbar.gen() * QQbar(s)).minpoly().is_cyclotomic()
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 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 regular_polygon(self, n, exact=True, base_ring=None): """ Return a regular polygon with `n` vertices. INPUT: - ``n`` -- a positive integer, the number of vertices. - ``exact`` -- (boolean, default ``True``) if ``False`` floating point numbers are used for coordinates. - ``base_ring`` -- a ring in which the coordinates will lie. It is ``None`` by default. If it is not provided and ``exact`` is ``True`` then it will be the field of real algebraic number, if ``exact`` is ``False`` it will be the real double field. EXAMPLES:: sage: octagon = polytopes.regular_polygon(8) sage: octagon A 2-dimensional polyhedron in AA^2 defined as the convex hull of 8 vertices sage: octagon.n_vertices() 8 sage: v = octagon.volume() sage: v 2.828427124746190? sage: v == 2*QQbar(2).sqrt() True Its non exact version:: sage: polytopes.regular_polygon(3, exact=False).vertices() (A vertex at (0.0, 1.0), A vertex at (0.8660254038, -0.5), A vertex at (-0.8660254038, -0.5)) sage: polytopes.regular_polygon(25, exact=False).n_vertices() 25 """ n = ZZ(n) if n <= 2: raise ValueError( "n (={}) must be an integer greater than 2".format(n)) if base_ring is None: if exact: base_ring = AA else: base_ring = RDF try: omega = 2 * base_ring.pi() / n verts = [((i * omega).sin(), (i * omega).cos()) for i in range(n)] except AttributeError: z = QQbar.zeta(n) verts = [(base_ring((z**k).imag()), base_ring((z**k).real())) for k in range(n)] return Polyhedron(vertices=verts, base_ring=base_ring)
def regular_polygon(self, n, exact=True, base_ring=None): """ Return a regular polygon with `n` vertices. INPUT: - ``n`` -- a positive integer, the number of vertices. - ``exact`` -- (boolean, default ``True``) if ``False`` floating point numbers are used for coordinates. - ``base_ring`` -- a ring in which the coordinates will lie. It is ``None`` by default. If it is not provided and ``exact`` is ``True`` then it will be the field of real algebraic number, if ``exact`` is ``False`` it will be the real double field. EXAMPLES:: sage: octagon = polytopes.regular_polygon(8) sage: octagon A 2-dimensional polyhedron in AA^2 defined as the convex hull of 8 vertices sage: octagon.n_vertices() 8 sage: v = octagon.volume() sage: v 2.828427124746190? sage: v == 2*QQbar(2).sqrt() True Its non exact version:: sage: polytopes.regular_polygon(3, exact=False).vertices() (A vertex at (0.0, 1.0), A vertex at (0.8660254038, -0.5), A vertex at (-0.8660254038, -0.5)) sage: polytopes.regular_polygon(25, exact=False).n_vertices() 25 """ n = ZZ(n) if n <= 2: raise ValueError("n (={}) must be an integer greater than 2".format(n)) if base_ring is None: if exact: base_ring = AA else: base_ring = RDF try: omega = 2*base_ring.pi() / n verts = [((i*omega).sin(), (i*omega).cos()) for i in range(n)] except AttributeError: z = QQbar.zeta(n) verts = [(base_ring((z**k).imag()), base_ring((z**k).real())) for k in range(n)] return Polyhedron(vertices=verts, base_ring=base_ring)
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 delta(self): r""" TESTS:: sage: from ore_algebra import * sage: Dops, x, Dx = DifferentialOperators() sage: (Dx - 1).numerical_solution([1], [0, RealField(10)(.33), 1]) [2.71828182845904...] """ z0, z1 = self.start.value, self.end.value if (z0.parent() is not z1.parent() and self.start.is_exact() and self.end.is_exact()): z0 = self.start.exact().value z1 = self.end.exact().value try: return z1 - z0 except TypeError: return as_embedded_number_field_element(QQbar(z1) - QQbar(z0)) else: return z1 - z0
def rotation_matrix_angle(r, check=False): r""" Return the angle of the rotation matrix ``r`` divided by ``2 pi``. EXAMPLES:: sage: from flatsurf.geometry.matrix_2x2 import rotation_matrix_angle sage: def rot_matrix(p, q): ....: z = QQbar.zeta(q) ** p ....: c = z.real() ....: s = z.imag() ....: return matrix(AA, 2, [c,-s,s,c]) sage: [rotation_matrix_angle(rot_matrix(i, 5)) for i in range(1,5)] [1/5, 2/5, 3/5, 4/5] sage: [rotation_matrix_angle(rot_matrix(i, 5)) for i in range(1,5)] [1/5, 2/5, 3/5, 4/5] sage: [rotation_matrix_angle(rot_matrix(i,7)) for i in range(1,7)] [1/7, 2/7, 3/7, 4/7, 5/7, 6/7] Some random tests:: sage: for _ in range(100): ....: r = QQ.random_element(x=0,y=500) ....: r -= r.floor() ....: m = rot_matrix(r.numerator(), r.denominator()) ....: assert rotation_matrix_angle(m) == r .. NOTE:: This is using floating point arithmetic and might be wrong. """ e0, e1 = r.change_ring(CDF).eigenvalues() m0 = (e0.log() / 2 / CDF.pi()).imag() m1 = (e1.log() / 2 / CDF.pi()).imag() r0 = RR(m0).nearby_rational(max_denominator=10000) r1 = RR(m1).nearby_rational(max_denominator=10000) if r0 != -r1: raise RuntimeError r0 = r0.abs() if r[0][1] > 0: return QQ.one() - r0 else: return r0 if check: e = r.change_ring(AA).eigenvalues()[0] if e.minpoly() != ZZ['x'].cyclotomic_polynomial()(r.denominator()): raise RuntimeError z = QQbar.zeta(r.denominator()) if z**r.numerator() != e: raise RuntimeError return r
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 rotation_matrix_angle(r, check=False): r""" Return the angle of the rotation matrix ``r`` divided by ``2 pi``. EXAMPLES:: sage: from flatsurf.geometry.matrix_2x2 import rotation_matrix_angle sage: def rot_matrix(p, q): ....: z = QQbar.zeta(q) ** p ....: c = z.real() ....: s = z.imag() ....: return matrix(AA, 2, [c,-s,s,c]) sage: [rotation_matrix_angle(rot_matrix(i, 5)) for i in range(1,5)] [1/5, 2/5, 3/5, 4/5] sage: [rotation_matrix_angle(rot_matrix(i,7)) for i in range(1,7)] [1/7, 2/7, 3/7, 4/7, 5/7, 6/7] Some random tests:: sage: for _ in range(100): ....: r = QQ.random_element(x=0,y=500) ....: r -= r.floor() ....: m = rot_matrix(r.numerator(), r.denominator()) ....: assert rotation_matrix_angle(m) == r .. NOTE:: This is using floating point arithmetic and might be wrong. """ e0,e1 = r.change_ring(CDF).eigenvalues() m0 = (e0.log() / 2 / CDF.pi()).imag() m1 = (e1.log() / 2 / CDF.pi()).imag() r0 = RR(m0).nearby_rational(max_denominator=10000) r1 = RR(m1).nearby_rational(max_denominator=10000) if r0 != -r1: raise RuntimeError r0 = r0.abs() if r[0][1] > 0: return QQ.one() - r0 else: return r0 if check: e = r.change_ring(AA).eigenvalues()[0] if e.minpoly() != ZZ['x'].cyclotomic_polynomial()(r.denominator()): raise RuntimeError z = QQbar.zeta(r.denominator()) if z**r.numerator() != e: raise RuntimeError return r
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 is_cosine_sine_of_rational(c, s): r""" Check whether the given pair is a cosine and sine of a same rational angle. EXAMPLES:: sage: from flatsurf.geometry.matrix_2x2 import is_cosine_sine_of_rational sage: c = s = AA(sqrt(2))/2 sage: is_cosine_sine_of_rational(c,s) True sage: c = AA(sqrt(3))/2; s = AA(1/2) sage: is_cosine_sine_of_rational(c,s) True sage: c = AA(sqrt(5)/2); s = (1 - c**2).sqrt() sage: c**2 + s**2 1.000000000000000? sage: is_cosine_sine_of_rational(c,s) False sage: c = (AA(sqrt(5)) + 1)/4; s = (1 - c**2).sqrt() sage: is_cosine_sine_of_rational(c,s) True sage: K.<sqrt2> = NumberField(x**2 - 2, embedding=1.414) sage: is_cosine_sine_of_rational(K.zero(),-K.one()) True TESTS:: sage: from pyexactreal import ExactReals # optional: exactreal sage: R = ExactReals() # optional: exactreal sage: is_cosine_sine_of_rational(R.one(), R.zero()) # optional: exactreal True """ return (QQbar(c) + QQbar.gen() * QQbar(s)).minpoly().is_cyclotomic()
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 delta(self): r""" TESTS:: sage: from ore_algebra import * sage: Dops, x, Dx = DifferentialOperators() sage: (Dx - 1).numerical_solution([1], [0, RealField(10)(.33), 1]) [2.71828182845904...] """ z0, z1 = self.start.value, self.end.value if z0.parent() is z1.parent(): return z1 - z0 elif (isinstance(z0, (RealBall, ComplexBall)) and isinstance(z1, (RealBall, ComplexBall))): p0, p1 = z0.parent().precision(), z1.parent().precision() real = isinstance(z0, RealBall) and isinstance(z1, RealBall) Tgt = (RealBallField if real else ComplexBallField)(max(p0, p1)) return Tgt(z1) - Tgt(z0) else: # XXX not great when one is in a number field != QQ[i] if self.start.is_exact(): z0 = self.start.exact().value if self.end.is_exact(): z1 = self.end.exact().value try: d = z1 - z0 except TypeError: # Should be coercions, but embedded number fields currently # don't coerce into QQbar... d = QQbar(z1) - QQbar(z0) # When z0, z1 are number field elements, we want another number # field element, not an element of QQbar or AA (even though z1-z0 # may succeed and return such an element). if d.parent() is z0.parent() or d.parent() is z1.parent(): return d else: return as_embedded_number_field_element(d)
def check_tempered(self): r""" Check that this representation is tempered (after twisting by `|\det|^{j/2}`), i.e. that `|\chi_1(p)| = |\chi_2(p)| = p^{(j + 1)/2}`. This follows from the Ramanujan--Petersson conjecture, as proved by Deligne. EXAMPLE:: sage: LocalComponent(Newform('49a'), 3).check_tempered() """ c1, c2 = self.characters() K = c1.base_ring() p = self.prime() w = QQbar(p)**((1 + self.twist_factor()) / 2) for sigma in K.embeddings(QQbar): assert sigma(c1(p)).abs() == sigma(c2(p)).abs() == w
def check_tempered(self): r""" Check that this representation is tempered (after twisting by `|\det|^{j/2}` where `j` is the twist factor). Since local components of modular forms are always tempered, this is a useful check on our calculations. EXAMPLE:: sage: Pi = LocalComponent(Newforms(DirichletGroup(21)([-1, 1]), 3, names='j')[0], 7) sage: Pi.check_tempered() """ c1 = self.characters()[0] K = c1.base_ring() p = self.prime() w = QQbar(p)**(self.twist_factor() / ZZ(2)) for sigma in K.embeddings(QQbar): assert sigma(c1(p)).abs() == w
def check_tempered(self): r""" Check that this representation is tempered (after twisting by `|\det|^{j/2}` where `j` is the twist factor). Since local components of modular forms are always tempered, this is a useful check on our calculations. Since the computation of the characters attached to this representation is not implemented in the odd-conductor case, a NotImplementedError will be raised for such representations. EXAMPLES:: sage: LocalComponent(Newform("50a"), 5).check_tempered() sage: LocalComponent(Newform("27a"), 3).check_tempered() """ c1, c2 = self.characters() K = c1.base_ring() p = self.prime() w = QQbar(p)**self.twist_factor() for sigma in K.embeddings(QQbar): assert sigma(c1(p)).abs() == sigma(c2(p)).abs() == w
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] """ 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 _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 conjugate(self): value = QQbar.coerce(self.value).conjugate() return Point(value, self.dop, **self.options)
def valuation(self): return QQbar(self.leftmost + self.shift) # alg vs NFelt for re, im
def _sing_as_alg(dop, iv): pol = dop.leading_coefficient().radical() return QQbar.polynomial_root(pol, CIF(iv))
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 _test_monodromy_matrices(): r""" TESTS:: sage: from ore_algebra.analytic.monodromy import _test_monodromy_matrices sage: _test_monodromy_matrices() """ from sage.all import matrix from ore_algebra import DifferentialOperators Dops, x, Dx = DifferentialOperators() h = QQ(1) / 2 i = QQi.gen() def norm(m): return sum(c.abs()**2 for c in m.list()).sqrtpos() mon = monodromy_matrices((x**2 + 1) * Dx - 1, QQ(1000000)) assert norm(mon[0] - CBF(pi).exp()) < RBF(1e-10) assert norm(mon[1] - CBF(-pi).exp()) < RBF(1e-10) mon = monodromy_matrices((x**2 - 1) * Dx - 1, QQ(0)) assert all(m == -1 for m in mon) dop = (x**2 + 1) * Dx**2 + 2 * x * Dx mon = monodromy_matrices(dop, QQbar(i + 1)) # mon[0] <--> i assert norm(mon[0] - matrix(CBF, [[1, pi * (1 + 2 * i)], [0, 1]])) < RBF(1e-10) assert norm(mon[1] - matrix(CBF, [[1, -pi * (1 + 2 * i)], [0, 1]])) < RBF(1e-10) mon = monodromy_matrices(dop, QQbar(-i + 1)) # mon[0] <--> -i assert norm(mon[0] - matrix(CBF, [[1, pi * (-1 + 2 * i)], [0, 1]])) < RBF(1e-10) assert norm(mon[1] - matrix(CBF, [[1, pi * (1 - 2 * i)], [0, 1]])) < RBF(1e-10) mon = monodromy_matrices(dop, QQbar(i)) # mon[0] <--> i assert norm(mon[0] - matrix(CBF, [[1, 0], [2 * pi * i, 1]])) < RBF(1e-10) assert norm(mon[1] - matrix(CBF, [[1, 0], [-2 * pi * i, 1]])) < RBF(1e-10) mon = monodromy_matrices(dop, QQbar(i), sing=[QQbar(i)]) assert len(mon) == 1 assert norm(mon[0] - matrix(CBF, [[1, 0], [2 * pi * i, 1]])) < RBF(1e-10) mon = monodromy_matrices(dop, QQbar(i), sing=[QQbar(-i)]) assert len(mon) == 1 assert norm(mon[0] - matrix(CBF, [[1, 0], [-2 * pi * i, 1]])) < RBF(1e-10) mon = monodromy_matrices(dop, QQbar(-i), sing=[QQbar(i)]) assert len(mon) == 1 assert norm(mon[0] - matrix(CBF, [[1, 0], [-2 * pi * i, 1]])) < RBF(1e-10) mon = monodromy_matrices(dop, QQbar(i), sing=[]) assert mon == [] dop = (x**2 + 1) * (x**2 - 1) * Dx**2 + 1 mon = monodromy_matrices(dop, QQ(0), sing=[QQ(1), QQbar(i)]) m0 = dop.numerical_transition_matrix([0, i + 1, 2 * i, i - 1, 0]) assert norm(m0 - mon[0]) < RBF(1e-10) m1 = dop.numerical_transition_matrix([0, 1 - i, 2, 1 + i, 0]) assert norm(m1 - mon[1]) < RBF(1e-10) dop = x * (x - 3) * (x - 4) * (x**2 - 6 * x + 10) * Dx**2 - 1 mon = monodromy_matrices(dop, QQ(-1)) m0 = dop.numerical_transition_matrix([-1, -i, 1, i, -1]) assert norm(m0 - mon[0]) < RBF(1e-10) m1 = dop.numerical_transition_matrix( [-1, i / 2, 3 - i / 2, 3 + h, 3 + i / 2, i / 2, -1]) assert norm(m1 - mon[1]) < RBF(1e-10) m2 = dop.numerical_transition_matrix( [-1, i / 2, 3 + i / 2, 4 - i / 2, 4 + h, 4 + i / 2, i / 2, -1]) assert norm(m2 - mon[2]) < RBF(1e-10) m3 = dop.numerical_transition_matrix([-1, 3 + i + h, 3 + 2 * i, -1]) assert norm(m3 - mon[3]) < RBF(1e-10) m4 = dop.numerical_transition_matrix( [-1, 3 - 2 * i, 3 - i + h, 3 - i / 2, -1]) assert norm(m4 - mon[4]) < RBF(1e-10) dop = (x - i)**2 * (x + i) * Dx - 1 mon = monodromy_matrices(dop, 0) assert norm(mon[0] + i) < RBF(1e-10) assert norm(mon[1] - i) < RBF(1e-10) dop = (x - i)**2 * (x + i) * Dx**2 - 1 mon = monodromy_matrices(dop, 0) m0 = dop.numerical_transition_matrix([0, i + 1, 2 * i, i - 1, 0]) assert norm(m0 - mon[0]) < RBF(1e-10) m1 = dop.numerical_transition_matrix([0, -i - 1, -2 * i, -i + 1, 0]) assert norm(m1 - mon[1]) < RBF(1e-10)
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 _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)