def cardinality(self): """ Returns the number of Lyndon words with the evaluation e. EXAMPLES:: sage: LyndonWords([]).cardinality() 0 sage: LyndonWords([2,2]).cardinality() 1 sage: LyndonWords([2,3,2]).cardinality() 30 Check to make sure that the count matches up with the number of Lyndon words generated. :: sage: comps = [[],[2,2],[3,2,7],[4,2]]+Compositions(4).list() sage: lws = [ LyndonWords(comp) for comp in comps] sage: all( [ lw.cardinality() == len(lw.list()) for lw in lws] ) True """ evaluation = self.e le = __builtin__.list(evaluation) if len(evaluation) == 0: return 0 n = sum(evaluation) return sum([moebius(j)*factorial(n/j) / prod([factorial(ni/j) for ni in evaluation]) for j in divisors(gcd(le))])/n
def _cycle_type(self, s): """ EXAMPLES:: sage: cis = species.PartitionSpecies().cycle_index_series() sage: [cis._cycle_type(p) for p in Partitions(3)] [[3, 1, 1], [2, 1, 1, 1], [1, 1, 1, 1, 1]] sage: cis = species.PermutationSpecies().cycle_index_series() sage: [cis._cycle_type(p) for p in Partitions(3)] [[3, 1, 1, 1], [2, 2, 1, 1], [1, 1, 1, 1, 1, 1]] sage: cis = species.SetSpecies().cycle_index_series() sage: [cis._cycle_type(p) for p in Partitions(3)] [[1], [1], [1]] """ if s == []: return self._card(0) res = [] for k in range(1, self._upper_bound_for_longest_cycle(s)+1): e = 0 for d in divisors(k): m = moebius(d) if m == 0: continue u = s.power(k/d) e += m*self.count(u) res.extend([k]*int(e/k)) res.reverse() return Partition(res)
def cardinality(self): """ Returns the number of Lyndon words with the evaluation e. EXAMPLES:: sage: LyndonWords([]).cardinality() 0 sage: LyndonWords([2,2]).cardinality() 1 sage: LyndonWords([2,3,2]).cardinality() 30 Check to make sure that the count matches up with the number of Lyndon words generated. :: sage: comps = [[],[2,2],[3,2,7],[4,2]]+Compositions(4).list() sage: lws = [ LyndonWords(comp) for comp in comps] sage: all( [ lw.cardinality() == len(lw.list()) for lw in lws] ) True """ evaluation = self.e le = __builtin__.list(evaluation) if len(evaluation) == 0: return 0 n = sum(evaluation) return sum([ moebius(j) * factorial(n / j) / prod([factorial(ni / j) for ni in evaluation]) for j in divisors(gcd(le)) ]) / n
def _cycle_type(self, s): """ EXAMPLES:: sage: cis = species.PartitionSpecies().cycle_index_series() sage: [cis._cycle_type(p) for p in Partitions(3)] [[3, 1, 1], [2, 1, 1, 1], [1, 1, 1, 1, 1]] sage: cis = species.PermutationSpecies().cycle_index_series() sage: [cis._cycle_type(p) for p in Partitions(3)] [[3, 1, 1, 1], [2, 2, 1, 1], [1, 1, 1, 1, 1, 1]] sage: cis = species.SetSpecies().cycle_index_series() sage: [cis._cycle_type(p) for p in Partitions(3)] [[1], [1], [1]] """ if s == []: return self._card(0) res = [] for k in range(1, self._upper_bound_for_longest_cycle(s) + 1): e = 0 for d in divisors(k): m = moebius(d) if m == 0: continue u = s.power(k / d) e += m * self.count(u) res.extend([k] * int(e / k)) res.reverse() return Partition(res)
def cardinality(self): """ TESTS:: sage: [ LyndonWords(3,i).cardinality() for i in range(1, 11) ] [3, 3, 8, 18, 48, 116, 312, 810, 2184, 5880] """ if self.k == 0: return 1 else: s = 0 for d in divisors(self.k): s += moebius(d) * (self.n**(self.k / d)) return s / self.k
def cardinality(self): """ TESTS:: sage: [ LyndonWords(3,i).cardinality() for i in range(1, 11) ] [3, 3, 8, 18, 48, 116, 312, 810, 2184, 5880] """ if self.k == 0: return 1 else: s = 0 for d in divisors(self.k): s += moebius(d)*(self.n**(self.k/d)) return s/self.k
def dimension_eis(self, k=2, eps=None, algorithm="CohenOesterle"): r""" Return the dimension of the space of Eisenstein series forms for self, or the dimension of the subspace corresponding to the given character if one is supplied. INPUT: - ``k`` - an integer (default: 2), the weight. - ``eps`` - either None or a Dirichlet character modulo N, where N is the level of this group. If this is None, then the dimension of the whole space is returned; otherwise, the dimension of the subspace of Eisenstein series of character eps. - ``algorithm`` -- either "CohenOesterle" (the default) or "Quer". This specifies the method to use in the case of nontrivial character: either the Cohen--Oesterle formula as described in Stein's book, or by Moebius inversion using the subgroups GammaH (a method due to Jordi Quer). AUTHORS: - William Stein - Cohen--Oesterle algorithm - Jordi Quer - algorithm based on GammaH subgroups - David Loeffler (2009) - code refactoring EXAMPLES: The following two computations use different algorithms: :: sage: [Gamma1(36).dimension_eis(1,eps) for eps in DirichletGroup(36)] [0, 4, 3, 0, 0, 2, 6, 0, 0, 2, 3, 0] sage: [Gamma1(36).dimension_eis(1,eps,algorithm="Quer") for eps in DirichletGroup(36)] [0, 4, 3, 0, 0, 2, 6, 0, 0, 2, 3, 0] So do these: :: sage: [Gamma1(48).dimension_eis(3,eps) for eps in DirichletGroup(48)] [0, 12, 0, 4, 0, 8, 0, 4, 12, 0, 4, 0, 8, 0, 4, 0] sage: [Gamma1(48).dimension_eis(3,eps,algorithm="Quer") for eps in DirichletGroup(48)] [0, 12, 0, 4, 0, 8, 0, 4, 12, 0, 4, 0, 8, 0, 4, 0] """ from all import Gamma0 # first deal with special cases if eps is None: return GammaH_class.dimension_eis(self, k) N = self.level() eps = DirichletGroup(N)(eps) if eps.is_trivial(): return Gamma0(N).dimension_eis(k) # Note case of k = 0 and trivial character already dealt with separately, so k <= 0 here is valid: if (k <= 0) or ((k % 2) == 1 and eps.is_even()) or ((k%2) == 0 and eps.is_odd()): return ZZ(0) if algorithm == "Quer": n = eps.order() dim = ZZ(0) for d in n.divisors(): G = GammaH_constructor(N,(eps**d).kernel()) dim = dim + moebius(d)*G.dimension_eis(k) return dim//phi(n) elif algorithm == "CohenOesterle": from sage.modular.dims import CohenOesterle K = eps.base_ring() j = 2-k # We use the Cohen-Oesterle formula in a subtle way to # compute dim M_k(N,eps) (see Ch. 6 of William Stein's book on # computing with modular forms). alpha = -ZZ( K(Gamma0(N).index()*(j-1)/ZZ(12)) + CohenOesterle(eps,j) ) if k == 1: return alpha else: return alpha - self.dimension_cusp_forms(k, eps) else: #algorithm not in ["CohenOesterle", "Quer"]: raise ValueError, "Unrecognised algorithm in dimension_eis"
def dimension_cusp_forms(self, k=2, eps=None, algorithm="CohenOesterle"): r""" Return the dimension of the space of cusp forms for self, or the dimension of the subspace corresponding to the given character if one is supplied. INPUT: - ``k`` - an integer (default: 2), the weight. - ``eps`` - either None or a Dirichlet character modulo N, where N is the level of this group. If this is None, then the dimension of the whole space is returned; otherwise, the dimension of the subspace of forms of character eps. - ``algorithm`` -- either "CohenOesterle" (the default) or "Quer". This specifies the method to use in the case of nontrivial character: either the Cohen--Oesterle formula as described in Stein's book, or by Moebius inversion using the subgroups GammaH (a method due to Jordi Quer). EXAMPLES: We compute the same dimension in two different ways :: sage: K = CyclotomicField(3) sage: eps = DirichletGroup(7*43,K).0^2 sage: G = Gamma1(7*43) Via Cohen--Oesterle: :: sage: Gamma1(7*43).dimension_cusp_forms(2, eps) 28 Via Quer's method: :: sage: Gamma1(7*43).dimension_cusp_forms(2, eps, algorithm="Quer") 28 Some more examples: :: sage: G.<eps> = DirichletGroup(9) sage: [Gamma1(9).dimension_cusp_forms(k, eps) for k in [1..10]] [0, 0, 1, 0, 3, 0, 5, 0, 7, 0] sage: [Gamma1(9).dimension_cusp_forms(k, eps^2) for k in [1..10]] [0, 0, 0, 2, 0, 4, 0, 6, 0, 8] """ from all import Gamma0 # first deal with special cases if eps is None: return GammaH_class.dimension_cusp_forms(self, k) N = self.level() if eps.base_ring().characteristic() != 0: raise ValueError eps = DirichletGroup(N, eps.base_ring())(eps) if eps.is_trivial(): return Gamma0(N).dimension_cusp_forms(k) if (k <= 0) or ((k % 2) == 1 and eps.is_even()) or ((k%2) == 0 and eps.is_odd()): return ZZ(0) if k == 1: try: n = self.dimension_cusp_forms(1) if n == 0: return ZZ(0) else: # never happens at present raise NotImplementedError, "Computations of dimensions of spaces of weight 1 cusp forms not implemented at present" except NotImplementedError: raise # now the main part if algorithm == "Quer": n = eps.order() dim = ZZ(0) for d in n.divisors(): G = GammaH_constructor(N,(eps**d).kernel()) dim = dim + moebius(d)*G.dimension_cusp_forms(k) return dim//phi(n) elif algorithm == "CohenOesterle": K = eps.base_ring() from sage.modular.dims import CohenOesterle from all import Gamma0 return ZZ( K(Gamma0(N).index() * (k-1)/ZZ(12)) + CohenOesterle(eps,k) ) else: #algorithm not in ["CohenOesterle", "Quer"]: raise ValueError, "Unrecognised algorithm in dimension_cusp_forms"
def dimension_eis(self, k=2, eps=None, algorithm="CohenOesterle"): r""" Return the dimension of the space of Eisenstein series forms for self, or the dimension of the subspace corresponding to the given character if one is supplied. INPUT: - ``k`` - an integer (default: 2), the weight. - ``eps`` - either None or a Dirichlet character modulo N, where N is the level of this group. If this is None, then the dimension of the whole space is returned; otherwise, the dimension of the subspace of Eisenstein series of character eps. - ``algorithm`` -- either "CohenOesterle" (the default) or "Quer". This specifies the method to use in the case of nontrivial character: either the Cohen--Oesterle formula as described in Stein's book, or by Moebius inversion using the subgroups GammaH (a method due to Jordi Quer). AUTHORS: - William Stein - Cohen--Oesterle algorithm - Jordi Quer - algorithm based on GammaH subgroups - David Loeffler (2009) - code refactoring EXAMPLES: The following two computations use different algorithms: :: sage: [Gamma1(36).dimension_eis(1,eps) for eps in DirichletGroup(36)] [0, 4, 3, 0, 0, 2, 6, 0, 0, 2, 3, 0] sage: [Gamma1(36).dimension_eis(1,eps,algorithm="Quer") for eps in DirichletGroup(36)] [0, 4, 3, 0, 0, 2, 6, 0, 0, 2, 3, 0] So do these: :: sage: [Gamma1(48).dimension_eis(3,eps) for eps in DirichletGroup(48)] [0, 12, 0, 4, 0, 8, 0, 4, 12, 0, 4, 0, 8, 0, 4, 0] sage: [Gamma1(48).dimension_eis(3,eps,algorithm="Quer") for eps in DirichletGroup(48)] [0, 12, 0, 4, 0, 8, 0, 4, 12, 0, 4, 0, 8, 0, 4, 0] """ from all import Gamma0 # first deal with special cases if eps is None: return GammaH_class.dimension_eis(self, k) N = self.level() eps = DirichletGroup(N)(eps) if eps.is_trivial(): return Gamma0(N).dimension_eis(k) # Note case of k = 0 and trivial character already dealt with separately, so k <= 0 here is valid: if (k <= 0) or ((k % 2) == 1 and eps.is_even()) or ((k % 2) == 0 and eps.is_odd()): return ZZ(0) if algorithm == "Quer": n = eps.order() dim = ZZ(0) for d in n.divisors(): G = GammaH_constructor(N, (eps**d).kernel()) dim = dim + moebius(d) * G.dimension_eis(k) return dim // phi(n) elif algorithm == "CohenOesterle": from sage.modular.dims import CohenOesterle K = eps.base_ring() j = 2 - k # We use the Cohen-Oesterle formula in a subtle way to # compute dim M_k(N,eps) (see Ch. 6 of William Stein's book on # computing with modular forms). alpha = -ZZ( K(Gamma0(N).index() * (j - 1) / ZZ(12)) + CohenOesterle(eps, j)) if k == 1: return alpha else: return alpha - self.dimension_cusp_forms(k, eps) else: #algorithm not in ["CohenOesterle", "Quer"]: raise ValueError, "Unrecognised algorithm in dimension_eis"
def dimension_cusp_forms(self, k=2, eps=None, algorithm="CohenOesterle"): r""" Return the dimension of the space of cusp forms for self, or the dimension of the subspace corresponding to the given character if one is supplied. INPUT: - ``k`` - an integer (default: 2), the weight. - ``eps`` - either None or a Dirichlet character modulo N, where N is the level of this group. If this is None, then the dimension of the whole space is returned; otherwise, the dimension of the subspace of forms of character eps. - ``algorithm`` -- either "CohenOesterle" (the default) or "Quer". This specifies the method to use in the case of nontrivial character: either the Cohen--Oesterle formula as described in Stein's book, or by Moebius inversion using the subgroups GammaH (a method due to Jordi Quer). EXAMPLES: We compute the same dimension in two different ways :: sage: K = CyclotomicField(3) sage: eps = DirichletGroup(7*43,K).0^2 sage: G = Gamma1(7*43) Via Cohen--Oesterle: :: sage: Gamma1(7*43).dimension_cusp_forms(2, eps) 28 Via Quer's method: :: sage: Gamma1(7*43).dimension_cusp_forms(2, eps, algorithm="Quer") 28 Some more examples: :: sage: G.<eps> = DirichletGroup(9) sage: [Gamma1(9).dimension_cusp_forms(k, eps) for k in [1..10]] [0, 0, 1, 0, 3, 0, 5, 0, 7, 0] sage: [Gamma1(9).dimension_cusp_forms(k, eps^2) for k in [1..10]] [0, 0, 0, 2, 0, 4, 0, 6, 0, 8] """ from all import Gamma0 # first deal with special cases if eps is None: return GammaH_class.dimension_cusp_forms(self, k) N = self.level() if eps.base_ring().characteristic() != 0: raise ValueError eps = DirichletGroup(N, eps.base_ring())(eps) if eps.is_trivial(): return Gamma0(N).dimension_cusp_forms(k) if (k <= 0) or ((k % 2) == 1 and eps.is_even()) or ((k % 2) == 0 and eps.is_odd()): return ZZ(0) if k == 1: try: n = self.dimension_cusp_forms(1) if n == 0: return ZZ(0) else: # never happens at present raise NotImplementedError, "Computations of dimensions of spaces of weight 1 cusp forms not implemented at present" except NotImplementedError: raise # now the main part if algorithm == "Quer": n = eps.order() dim = ZZ(0) for d in n.divisors(): G = GammaH_constructor(N, (eps**d).kernel()) dim = dim + moebius(d) * G.dimension_cusp_forms(k) return dim // phi(n) elif algorithm == "CohenOesterle": K = eps.base_ring() from sage.modular.dims import CohenOesterle from all import Gamma0 return ZZ( K(Gamma0(N).index() * (k - 1) / ZZ(12)) + CohenOesterle(eps, k)) else: #algorithm not in ["CohenOesterle", "Quer"]: raise ValueError, "Unrecognised algorithm in dimension_cusp_forms"
def dimension__jacobi_scalar_f(k, m, f) : if moebius(f) != (-1)**k : return 0 ## We use chapter 6 of Skoruppa's thesis ts = filter(lambda t: gcd(t, m // t) == 1, m.divisors()) ## Eisenstein part eis_dimension = 0 for t in ts : eis_dimension += moebius(gcd(m // t, f)) \ * (t // t.squarefree_part()).isqrt() \ * (2 if (m // t) % 4 == 0 else 1) eis_dimension = eis_dimension // len(ts) if k == 2 and f == 1 : eis_dimension -= len( (m // m.squarefree_part()).isqrt().divisors() ) ## Cuspidal part cusp_dimension = 0 tmp = ZZ(0) for t in ts : tmp += moebius(gcd(m // t, f)) * t tmp = tmp / len(ts) cusp_dimension += tmp * (2 * k - 3) / ZZ(12) print "1: ", cusp_dimension if m % 2 == 0 : tmp = ZZ(0) for t in ts : tmp += moebius(gcd(m // t, f)) * kronecker_symbol(-4, t) tmp = tmp / len(ts) cusp_dimension += 1/ZZ(2) * kronecker_symbol(8, 2 * k - 1) * tmp print "2: ", 1/ZZ(2) * kronecker_symbol(8, 2 * k - 1) * tmp tmp = ZZ(0) for t in ts : tmp += moebius(gcd(m // t, f)) * kronecker_symbol(t, 3) tmp = tmp / len(ts) if m % 3 != 0 : cusp_dimension += 1 / ZZ(3) * kronecker_symbol(k, 3) * tmp print ": ", 1 / ZZ(3) * kronecker_symbol(k, 3) * tmp elif k % 3 == 0 : cusp_dimension += 2 / ZZ(3) * (-1)**k * tmp print "3: ", 2 / ZZ(3) * (-1)**k * tmp else : cusp_dimension += 1 / ZZ(3) * (kronecker_symbol(k, 3) + (-1)**(k - 1)) * tmp print "3: ", 1 / ZZ(3) * (kronecker_symbol(k, 3) + (-1)**(k - 1)) * tmp tmp = ZZ(0) for t in ts : tmp += moebius(gcd(m // t, f)) \ * (t // t.squarefree_part()).isqrt() \ * (2 if (m // t) % 4 == 0 else 1) tmp = tmp / len(ts) cusp_dimension -= 1 / ZZ(2) * tmp print "4: ", -1 / ZZ(2) * tmp tmp = ZZ(0) for t in ts : tmp += moebius(gcd(m // t, f)) \ * sum( (( len(BinaryQF_reduced_representatives(-d, True)) if d not in [3, 4] else ( 1 / ZZ(3) if d == 3 else 1 / ZZ(2) )) if d % 4 == 0 or d % 4 == 3 else 0 ) * kronecker_symbol(-d, m // t) * ( 1 if (m // t) % 2 != 0 else ( 4 if (m // t) % 4 == 0 else 2 * kronecker_symbol(-d, 2) )) for d in (4 * m).divisors() ) tmp = tmp / len(ts) cusp_dimension -= 1 / ZZ(2) * tmp print "5: ", -1 / ZZ(2) * tmp if k == 2 : cusp_dimension += len( (m // f // (m // f).squarefree_part()).isqrt().divisors() ) return eis_dimension + cusp_dimension
def dimension__jacobi_scalar_f(k, m, f): if moebius(f) != (-1)**k: return 0 ## We use chapter 6 of Skoruppa's thesis ts = filter(lambda t: gcd(t, m // t) == 1, m.divisors()) ## Eisenstein part eis_dimension = 0 for t in ts: eis_dimension += moebius(gcd(m // t, f)) \ * (t // t.squarefree_part()).isqrt() \ * (2 if (m // t) % 4 == 0 else 1) eis_dimension = eis_dimension // len(ts) if k == 2 and f == 1: eis_dimension -= len((m // m.squarefree_part()).isqrt().divisors()) ## Cuspidal part cusp_dimension = 0 tmp = ZZ(0) for t in ts: tmp += moebius(gcd(m // t, f)) * t tmp = tmp / len(ts) cusp_dimension += tmp * (2 * k - 3) / ZZ(12) print "1: ", cusp_dimension if m % 2 == 0: tmp = ZZ(0) for t in ts: tmp += moebius(gcd(m // t, f)) * kronecker_symbol(-4, t) tmp = tmp / len(ts) cusp_dimension += 1 / ZZ(2) * kronecker_symbol(8, 2 * k - 1) * tmp print "2: ", 1 / ZZ(2) * kronecker_symbol(8, 2 * k - 1) * tmp tmp = ZZ(0) for t in ts: tmp += moebius(gcd(m // t, f)) * kronecker_symbol(t, 3) tmp = tmp / len(ts) if m % 3 != 0: cusp_dimension += 1 / ZZ(3) * kronecker_symbol(k, 3) * tmp print ": ", 1 / ZZ(3) * kronecker_symbol(k, 3) * tmp elif k % 3 == 0: cusp_dimension += 2 / ZZ(3) * (-1)**k * tmp print "3: ", 2 / ZZ(3) * (-1)**k * tmp else: cusp_dimension += 1 / ZZ(3) * (kronecker_symbol(k, 3) + (-1)**(k - 1)) * tmp print "3: ", 1 / ZZ(3) * (kronecker_symbol(k, 3) + (-1)**(k - 1)) * tmp tmp = ZZ(0) for t in ts: tmp += moebius(gcd(m // t, f)) \ * (t // t.squarefree_part()).isqrt() \ * (2 if (m // t) % 4 == 0 else 1) tmp = tmp / len(ts) cusp_dimension -= 1 / ZZ(2) * tmp print "4: ", -1 / ZZ(2) * tmp tmp = ZZ(0) for t in ts: tmp += moebius(gcd(m // t, f)) \ * sum( (( len(BinaryQF_reduced_representatives(-d, True)) if d not in [3, 4] else ( 1 / ZZ(3) if d == 3 else 1 / ZZ(2) )) if d % 4 == 0 or d % 4 == 3 else 0 ) * kronecker_symbol(-d, m // t) * ( 1 if (m // t) % 2 != 0 else ( 4 if (m // t) % 4 == 0 else 2 * kronecker_symbol(-d, 2) )) for d in (4 * m).divisors() ) tmp = tmp / len(ts) cusp_dimension -= 1 / ZZ(2) * tmp print "5: ", -1 / ZZ(2) * tmp if k == 2: cusp_dimension += len( (m // f // (m // f).squarefree_part()).isqrt().divisors()) return eis_dimension + cusp_dimension
def dynatomic_polynomial(self,period): r""" For a map `f:\mathbb{P}^1 \to \mathbb{P}^1` this function computes the dynatomic polynomial. The dynatomic polynomial is the analog of the cyclotomic polynomial and its roots are the points of formal period `n`. ALGORITHM: For a positive integer `n`, let `[F_n,G_n]` be the coordinates of the `nth` iterate of `f`. Then construct .. MATH:: \Phi^{\ast}_n(f)(x,y) = \sum_{d \mid n} (yF_d(x,y) - xG_d(x,y))^{\mu(n/d)} where `\mu` is the Moebius function. For a pair `[m,n]`, let `f^m = [F_m,G_m]`. Compute .. MATH:: \Phi^{\ast}_{m,n}(f)(x,y) = \Phi^{\ast}_n(f)(F_m,G_m)/\Phi^{\ast}_n(f)(F_{m-1},G_{m-1}) REFERENCES: .. - B. Hutz. Efficient determination of rational preperiodic points for endomorphisms of projective space. arxiv:1210.6246, 2012. - P. Morton and P. Patel. The Galois theory of periodic points of polynomial maps. Proc. London Math. Soc., 68 (1994), 225-263. INPUT: - ``period`` -- a positive integer or a list/tuple `[m,n]` where `m` is the preperiod and `n` is the period OUTPUT: - If possible, a two variable polynomial in the coordinate ring of ``self``. Otherwise a fraction field element of the coordinate ring of ``self`` .. TODO:: Do the division when the base ring is p-adic or a function field so that the output is a polynomial. EXAMPLES:: sage: P.<x,y>=ProjectiveSpace(QQ,1) sage: H=Hom(P,P) sage: f=H([x^2+y^2,y^2]) sage: f.dynatomic_polynomial(2) x^2 + x*y + 2*y^2 :: sage: P.<x,y>=ProjectiveSpace(ZZ,1) sage: H=Hom(P,P) sage: f=H([x^2+y^2,x*y]) sage: f.dynatomic_polynomial(4) 2*x^12 + 18*x^10*y^2 + 57*x^8*y^4 + 79*x^6*y^6 + 48*x^4*y^8 + 12*x^2*y^10 + y^12 :: sage: P.<x,y>=ProjectiveSpace(CC,1) sage: H=Hom(P,P) sage: f=H([x^2+y^2,3*x*y]) sage: f.dynatomic_polynomial(3) 13.0000000000000*x^6 + 117.000000000000*x^4*y^2 + 78.0000000000000*x^2*y^4 + y^6 :: sage: P.<x,y>=ProjectiveSpace(QQ,1) sage: H=Hom(P,P) sage: f=H([x^2-10/9*y^2,y^2]) sage: f.dynatomic_polynomial([2,1]) x^4*y^2 - 11/9*x^2*y^4 - 80/81*y^6 :: sage: P.<x,y>=ProjectiveSpace(QQ,1) sage: H=Hom(P,P) sage: f=H([x^2-29/16*y^2,y^2]) sage: f.dynatomic_polynomial([2,3]) x^12 - 95/8*x^10*y^2 + 13799/256*x^8*y^4 - 119953/1024*x^6*y^6 + 8198847/65536*x^4*y^8 - 31492431/524288*x^2*y^10 + 172692729/16777216*y^12 :: sage: P.<x,y>=ProjectiveSpace(ZZ,1) sage: H=Hom(P,P) sage: f=H([x^2-y^2,y^2]) sage: f.dynatomic_polynomial([1,2]) x^2 - x*y :: sage: P.<x,y>=ProjectiveSpace(QQ,1) sage: H=Hom(P,P) sage: f=H([x^3-y^3,3*x*y^2]) sage: f.dynatomic_polynomial([0,4])==f.dynatomic_polynomial(4) True :: sage: P.<x,y,z>=ProjectiveSpace(QQ,2) sage: H=Hom(P,P) sage: f=H([x^2+y^2,x*y,z^2]) sage: f.dynatomic_polynomial(2) Traceback (most recent call last): ... TypeError: Does not make sense in dimension >1 :: #TODO: it would be nice to get this to actually be a polynomial sage: P.<x,y>=ProjectiveSpace(Qp(5),1) sage: H=Hom(P,P) sage: f=H([x^2+y^2,y^2]) sage: f.dynatomic_polynomial(2) (x^4*y + (2 + O(5^20))*x^2*y^3 - x*y^4 + (2 + O(5^20))*y^5)/(x^2*y - x*y^2 + y^3) :: sage: L.<t>=PolynomialRing(QQ) sage: P.<x,y>=ProjectiveSpace(L,1) sage: H=Hom(P,P) sage: f=H([x^2+t*y^2,y^2]) sage: f.dynatomic_polynomial(2) x^2 + x*y + (t + 1)*y^2 :: sage: K.<c>=PolynomialRing(ZZ) sage: P.<x,y>=ProjectiveSpace(K,1) sage: H=Hom(P,P) sage: f=H([x^2+ c*y^2,y^2]) sage: f.dynatomic_polynomial([1,2]) x^2 - x*y + (c + 1)*y^2 """ if self.domain() != self.codomain(): raise TypeError("Must have same domain and codomain to iterate") from sage.schemes.projective.projective_space import is_ProjectiveSpace if is_ProjectiveSpace(self.domain())==False: raise NotImplementedError("Not implemented for subschemes") if self.domain().dimension_relative()>1: raise TypeError("Does not make sense in dimension >1") if (isinstance(period,(list,tuple))==False): period=[0,period] try: period[0]=ZZ(period[0]) period[1]=ZZ(period[1]) except TypeError: raise TypeError("Period and preperiod must be integers") if period[1]<=0: raise AttributeError("Period must be at least 1") if period[0]!=0: m=period[0] fm=self.nth_iterate_map(m) fm1=self.nth_iterate_map(m-1) n=period[1] PHI=1; x=self.domain().gen(0) y=self.domain().gen(1) F=self._polys f=F for d in range(1,n+1): if n%d ==0: PHI=PHI*((y*F[0]-x*F[1])**moebius(n/d)) if d !=n: #avoid extra iteration F=[f[0](F[0],F[1]),f[1](F[0],F[1])] if m!=0: PHI=PHI(fm._polys)/PHI(fm1._polys) else: PHI=1; x=self.domain().gen(0) y=self.domain().gen(1) F=self._polys f=F for d in range(1,period[1]+1): if period[1]%d ==0: PHI=PHI*((y*F[0]-x*F[1])**moebius(period[1]/d)) if d !=period[1]: #avoid extra iteration F=[f[0](F[0],F[1]),f[1](F[0],F[1])] from sage.rings.polynomial.polynomial_ring import PolynomialRing_general from sage.rings.polynomial.multi_polynomial_ring_generic import MPolynomialRing_generic if self.domain().base_ring()==RealField() or self.domain().base_ring()==ComplexField(): PHI=PHI.numerator()._maxima_().divide(PHI.denominator())[0].sage() elif isinstance(self.domain().base_ring(),(PolynomialRing_general,MPolynomialRing_generic)): from sage.rings.padics.generic_nodes import is_pAdicField, is_pAdicRing from sage.rings.function_field.function_field import is_FunctionField BR=self.domain().base_ring().base_ring() if is_pAdicField(BR) or is_pAdicRing(BR) or is_FunctionField(BR): raise NotImplementedError("Not implemented") PHI=PHI.numerator()._maxima_().divide(PHI.denominator())[0].sage() #do it again to divide out by denominators of coefficients PHI=PHI.numerator()._maxima_().divide(PHI.denominator())[0].sage() if PHI.denominator()==1: PHI=self.coordinate_ring()(PHI) return(PHI)