def fft(self): """ Wraps the gsl ``FastFourierTransform.forward()`` in :mod:`~sage.gsl.fft`. If the length is a power of 2 then this automatically uses the radix2 method. If the number of sample points in the input is a power of 2 then the wrapper for the GSL function ``gsl_fft_complex_radix2_forward()`` is automatically called. Otherwise, ``gsl_fft_complex_forward()`` is used. EXAMPLES:: sage: J = range(5) sage: A = [RR(1) for i in J] sage: s = IndexedSequence(A,J) sage: t = s.fft(); t Indexed sequence: [5.00000000000000, 0.000000000000000, 0.000000000000000, 0.000000000000000, 0.000000000000000] indexed by [0, 1, 2, 3, 4] """ from sage.rings.all import CC I = CC.gen() # elements must be coercible into RR J = self.index_object() ## must be = range(N) N = len(J) S = self.list() a = FastFourierTransform(N) for i in range(N): a[i] = S[i] a.forward_transform() return IndexedSequence([a[j][0]+I*a[j][1] for j in J],J)
def smallest_dynamical(f, dynatomic=True, start_n=1, prec=53, emb=None, algorithm='HS', check_minimal=True): r""" Determine the poly with smallest coefficients in `SL(2,\ZZ)` orbit of ``F`` Smallest is in the sense of global height. The method is the algorithm in Hutz-Stoll [HS2018]_. A binary form defining the periodic points is associated to ``f``. From this polynomial a bound on the search space can be determined. ``f`` should already be a minimal model or finding the orbit representatives may give wrong results. INPUT: - ``f`` -- a dynamical system on `P^1` - ``dynatomic`` -- boolean. whether ``F`` is the periodic points or the formal periodic points of period ``m`` for ``f`` - ``start_n`` - positive integer. the period used to start trying to create associate binary form ``F`` - ``prec``-- positive integer. precision to use in CC - ``emb`` -- embedding into CC - ``algorithm`` -- (optional) string; either ``'BM'`` for the Bruin-Molnar algorithm or ``'HS'`` for the Hutz-Stoll algorithm. If not specified, properties of the map are utilized to choose how to compute minimal orbit representatives - ``check_minimal`` -- (default: True), boolean, whether to check if this map is a minimal model OUTPUT: pair [dynamical system, matrix] EXAMPLES:: sage: from sage.dynamics.arithmetic_dynamics.endPN_minimal_model import smallest_dynamical sage: P.<x,y> = ProjectiveSpace(QQ,1) sage: f = DynamicalSystem([50*x^2 + 795*x*y + 2120*y^2, 265*x^2 + 106*y^2]) sage: smallest_dynamical(f) #long time [ Dynamical System of Projective Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (x : y) to (-480*x^2 - 1125*x*y + 1578*y^2 : 265*x^2 + 1060*x*y + 1166*y^2), <BLANKLINE> [1 2] [0 1] ] """ def insert_item(pts, item, index): # binary insertion to maintain list of points left to consider N = len(pts) if N == 0: return [item] elif N == 1: if item[index] > pts[0][index]: pts.insert(0, item) else: pts.append(item) return pts else: # binary insertion left = 1 right = N mid = (left + right) // 2 # these are ints so this is .floor() if item[index] > pts[mid][index]: # item goes into first half return insert_item(pts[:mid], item, index) + pts[mid:N] else: # item goes into second half return pts[:mid] + insert_item(pts[mid:N], item, index) def coshdelta(z): # The cosh of the hyperbolic distance from z = t+uj to j return (z.norm() + 1) / (2 * z.imag()) # can't be smaller if height 0 f.normalize_coordinates() if f.global_height(prec=prec) == 0: return [f, matrix(ZZ, 2, 2, [1, 0, 0, 1])] all_min = f.all_minimal_models(return_transformation=True, algorithm=algorithm, check_minimal=check_minimal) current_min = None current_size = None # search for minimum over all orbits for g, M in all_min: PS = g.domain() CR = PS.coordinate_ring() x, y = CR.gens() n = start_n # sometimes you get a problem later with 0,infty as roots if dynatomic: pts_poly = g.dynatomic_polynomial(n) else: gn = g.nth_iterate_map(n) pts_poly = y * gn[0] - x * gn[1] d = ZZ(pts_poly.degree()) max_mult = max([ex for p, ex in pts_poly.factor()]) while ((d < 3) or (max_mult >= d / 2) and (n < 5)): n = n + 1 if dynatomic: pts_poly = g.dynatomic_polynomial(n) else: gn = g.nth_iterate_map(n) pts_poly = y * gn[0] - x * gn[1] d = ZZ(pts_poly.degree()) max_mult = max([ex for p, ex in pts_poly.factor()]) assert (n <= 4), "n > 4, failed to find usable poly" R = get_bound_dynamical(pts_poly, g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) # search starts in fundamental domain G, MG = pts_poly.reduced_form(prec=prec, emb=emb, smallest_coeffs=False) red_g = f.conjugate(M * MG) if G != pts_poly: R2 = get_bound_dynamical(G, red_g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) if R2 < R: # use the better bound R = R2 red_g.normalize_coordinates() if red_g.global_height(prec=prec) == 0: return [red_g, M * MG] # height if current_size is None: current_size = e**red_g.global_height(prec=prec) v0, th = covariant_z0(G, prec=prec, emb=emb) rep = 2 * CC.gen(0) from math import isnan if isnan(v0.abs()): raise ValueError("invalid covariant: %s" % v0) # get orbit S = matrix(ZZ, 2, 2, [0, -1, 1, 0]) T = matrix(ZZ, 2, 2, [1, 1, 0, 1]) TI = matrix(ZZ, 2, 2, [1, -1, 0, 1]) count = 0 pts = [[G, red_g, v0, rep, M * MG, coshdelta(v0), 0]] # label - 0:None, 1:S, 2:T, 3:T^(-1) if current_min is None: current_min = [G, red_g, v0, rep, M * MG, coshdelta(v0)] while pts != []: G, g, v, rep, M, D, label = pts.pop() # apply ST and keep z, Sz if D > R: break #all remaining pts are too far away # check if it is smaller. If so, we can improve the bound count += 1 new_size = e**g.global_height(prec=prec) if new_size < current_size: current_min = [G, g, v, rep, M, coshdelta(v)] current_size = new_size if new_size == 1: # early exit return [current_min[1], current_min[4]] new_R = get_bound_dynamical(G, g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) if new_R < R: R = new_R # add new points to check if label != 1 and min((rep + 1).norm(), (rep - 1).norm()) >= 1: # don't undo S # the 2nd condition is equivalent to |\Re(-1/rep)| <= 1/2 # this means that rep can have resulted from an inversion step in # the shift-and-invert procedure, so don't invert # do inversion z = -1 / v new_pt = [ G.subs({ x: -y, y: x }), g.conjugate(S), z, -1 / rep, M * S, coshdelta(z), 1 ] pts = insert_item(pts, new_pt, 5) if label != 3: # don't undo T on g # do right shift z = v - 1 new_pt = [ G.subs({x: x + y}), g.conjugate(TI), z, rep - 1, M * TI, coshdelta(z), 2 ] pts = insert_item(pts, new_pt, 5) if label != 2: # don't undo TI on g # do left shift z = v + 1 new_pt = [ G.subs({x: x - y}), g.conjugate(T), z, rep + 1, M * T, coshdelta(z), 3 ] pts = insert_item(pts, new_pt, 5) return [current_min[1], current_min[4]]
def smallest_dynamical(f, dynatomic=True, start_n=1, prec=53, emb=None, algorithm='HS', check_minimal=True): r""" Determine the poly with smallest coefficients in `SL(2,\ZZ)` orbit of ``F`` Smallest is in the sense of global height. The method is the algorithm in Hutz-Stoll [HS2018]_. A binary form defining the periodic points is associated to ``f``. From this polynomial a bound on the search space can be determined. ``f`` should already be a minimal model or finding the orbit representatives may give wrong results. INPUT: - ``f`` -- a dynamical system on `P^1` - ``dyantomic`` -- boolean. whether ``F`` is the periodic points or the formal periodic points of period ``m`` for ``f`` - ``start_n`` - positive integer. the period used to start trying to create associate binary form ``F`` - ``prec``-- positive integer. precision to use in CC - ``emb`` -- embedding into CC - ``algorithm`` -- (optional) string; either ``'BM'`` for the Bruin-Molnar algorithm or ``'HS'`` for the Hutz-Stoll algorithm. If not specified, properties of the map are utilized to choose how to compute minimal orbit representatives - ``check_minimal`` -- (default: True), boolean, whether to check if this map is a minimal model OUTPUT: pair [dynamical system, matrix] EXAMPLES:: sage: from sage.dynamics.arithmetic_dynamics.endPN_minimal_model import smallest_dynamical sage: P.<x,y> = ProjectiveSpace(QQ,1) sage: f = DynamicalSystem([50*x^2 + 795*x*y + 2120*y^2, 265*x^2 + 106*y^2]) sage: smallest_dynamical(f) #long time [ Dynamical System of Projective Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (x : y) to (-480*x^2 - 1125*x*y + 1578*y^2 : 265*x^2 + 1060*x*y + 1166*y^2), <BLANKLINE> [1 2] [0 1] ] """ def insert_item(pts, item, index): # binary insertion to maintain list of points left to consider N = len(pts) if N == 0: return [item] elif N == 1: if item[index] > pts[0][index]: pts.insert(0,item) else: pts.append(item) return pts else: # binary insertion left = 1 right = N mid = (left + right) // 2 # these are ints so this is .floor() if item[index] > pts[mid][index]: # item goes into first half return insert_item(pts[:mid], item, index) + pts[mid:N] else: # item goes into second half return pts[:mid] + insert_item(pts[mid:N], item, index) def coshdelta(z): # The cosh of the hyperbolic distance from z = t+uj to j return (z.norm() + 1)/(2*z.imag()) # can't be smaller if height 0 f.normalize_coordinates() if f.global_height(prec=prec) == 0: return [f, matrix(ZZ,2,2,[1,0,0,1])] all_min = f.all_minimal_models(return_transformation=True, algorithm=algorithm, check_minimal=check_minimal) current_min = None current_size = None # search for minimum over all orbits for g,M in all_min: PS = g.domain() CR = PS.coordinate_ring() x,y = CR.gens() n = start_n # sometimes you get a problem later with 0,infty as roots if dynatomic: pts_poly = g.dynatomic_polynomial(n) else: gn = g.nth_iterate_map(n) pts_poly = y*gn[0] - x*gn[1] d = ZZ(pts_poly.degree()) max_mult = max([ex for p,ex in pts_poly.factor()]) while ((d < 3) or (max_mult >= d/2) and (n < 5)): n = n+1 if dynatomic: pts_poly = g.dynatomic_polynomial(n) else: gn = g.nth_iterate_map(n) pts_poly = y*gn[0] - x*gn[1] d = ZZ(pts_poly.degree()) max_mult = max([ex for p,ex in pts_poly.factor()]) assert(n<=4), "n > 4, failed to find usable poly" R = get_bound_dynamical(pts_poly, g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) # search starts in fundamental domain G,MG = pts_poly.reduced_form(prec=prec, emb=emb, smallest_coeffs=False) red_g = f.conjugate(M*MG) if G != pts_poly: R2 = get_bound_dynamical(G, red_g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) if R2 < R: # use the better bound R = R2 red_g.normalize_coordinates() if red_g.global_height(prec=prec) == 0: return [red_g, M*MG] # height if current_size is None: current_size = e**red_g.global_height(prec=prec) v0, th = covariant_z0(G, prec=prec, emb=emb) rep = 2*CC.gen(0) from math import isnan if isnan(v0.abs()): raise ValueError("invalid covariant: %s"%v0) # get orbit S = matrix(ZZ,2,2,[0,-1,1,0]) T = matrix(ZZ,2,2,[1,1,0,1]) TI = matrix(ZZ,2,2,[1,-1,0,1]) count = 0 pts = [[G, red_g, v0, rep, M*MG, coshdelta(v0), 0]] # label - 0:None, 1:S, 2:T, 3:T^(-1) if current_min is None: current_min = [G, red_g, v0, rep, M*MG, coshdelta(v0)] while pts != []: G, g, v, rep, M, D, label = pts.pop() # apply ST and keep z, Sz if D > R: break #all remaining pts are too far away # check if it is smaller. If so, we can improve the bound count += 1 new_size = e**g.global_height(prec=prec) if new_size < current_size: current_min = [G ,g, v, rep, M, coshdelta(v)] current_size = new_size if new_size == 1: # early exit return [current_min[1], current_min[4]] new_R = get_bound_dynamical(G, g, m=n, dynatomic=dynatomic, prec=prec, emb=emb) if new_R < R: R = new_R # add new points to check if label != 1 and min((rep+1).norm(), (rep-1).norm()) >= 1: # don't undo S # the 2nd condition is equivalent to |\Re(-1/rep)| <= 1/2 # this means that rep can have resulted from an inversion step in # the shift-and-invert procedure, so don't invert # do inversion z = -1/v new_pt = [G.subs({x:-y, y:x}), g.conjugate(S), z, -1/rep, M*S, coshdelta(z), 1] pts = insert_item(pts, new_pt, 5) if label != 3: # don't undo T on g # do right shift z = v-1 new_pt = [G.subs({x:x+y}), g.conjugate(TI), z, rep-1, M*TI, coshdelta(z), 2] pts = insert_item(pts, new_pt, 5) if label != 2: # don't undo TI on g # do left shift z = v+1 new_pt = [G.subs({x:x-y}), g.conjugate(T), z, rep+1, M*T, coshdelta(z), 3] pts = insert_item(pts, new_pt, 5) return [current_min[1], current_min[4]]
def smallest_poly(F, prec=53, norm_type='norm', emb=None): r""" Determine the poly with smallest coefficients in `SL(2,\Z)` orbit of ``F`` Smallest can be in the sense of `L_2` norm or height. The method is the algorithm in Hutz-Stoll [HS2018]_. ``F`` needs to be a binary form with no multiple roots of degree at least 3. It should already be reduced in the sense of Cremona-Stoll [CS2003]_. INPUT: - ``F`` -- binary form of degree at least 3 with no multiple roots - ``norm_type`` -- string - ``norm`` or ``height`` controlling what ``smallest`` means for the coefficients. OUTPUT: pair [poly, matrix] EXAMPLES:: sage: from sage.rings.polynomial.binary_form_reduce import smallest_poly sage: R.<x,y> = QQ[] sage: F = -x^8 + 6*x^7*y - 7*x^6*y^2 - 12*x^5*y^3 + 27*x^4*y^4\ ....: - 4*x^3*y^5 - 19*x^2*y^6 + 10*x*y^7 - 5*y^8 sage: smallest_poly(F, prec=100) #long time [ -x^8 - 2*x^7*y + 7*x^6*y^2 + 16*x^5*y^3 + 2*x^4*y^4 - 2*x^3*y^5 + 4*x^2*y^6 - 5*y^8, <BLANKLINE> [1 1] [0 1] ] :: sage: R.<x,y> = QQ[] sage: F = -2*x^3 + 2*x^2*y + 3*x*y^2 + 127*y^3 sage: smallest_poly(F) [ [1 4] -2*x^3 - 22*x^2*y - 77*x*y^2 + 43*y^3, [0 1] ] sage: smallest_poly(F, norm_type='height') [ [5 4] -58*x^3 - 47*x^2*y + 52*x*y^2 + 43*y^3, [1 1] ] an example with a multiple root:: sage: R.<x,y> = QQ[] sage: F = -16*x^7 - 114*x^6*y - 345*x^5*y^2 - 599*x^4*y^3 - 666*x^3*y^4\ ....: - 481*x^2*y^5 - 207*x*y^6 - 40*y^7 sage: F.reduced_form() ( [-1 -1] -x^5*y^2 - 24*x^3*y^4 - 3*x^2*y^5 - 2*x*y^6 + 16*y^7, [ 1 0] ) """ def insert_item(pts, item, index): #binary insertion to maintain list of points left to consider N = len(pts) if N == 0: return [item] elif N == 1: if item[index] > pts[0][index]: pts.insert(0, item) else: pts.append(item) return pts else: # binary insertion left = 1 right = N mid = (left + right) // 2 # these are ints so this is .floor() if item[index] > pts[mid][index]: # item goes into first half return insert_item(pts[:mid], item, index) + pts[mid:N] else: # item goes into second half return pts[:mid] + insert_item(pts[mid:N], item, index) def coshdelta(z): #The cosh of the hyperbolic distance from z = t+uj to j return (z.norm() + 1) / (2 * z.imag() ) #reduce in the sense of Cremona-Stoll G = F MG = matrix(ZZ, 2, 2, [1, 0, 0, 1]) x, y = G.parent().gens() if norm_type == 'norm': current_size = sum([abs(i)**2 for i in G.coefficients() ]) #euclidean norm squared elif norm_type == 'height': #height current_size = e**max( [c.global_height(prec=prec) for c in G.coefficients()]) else: raise ValueError('type must be norm or height') v0, th = covariant_z0(G, prec=prec, emb=emb) rep = 2 * CC.gen(0) #representative point in fundamental domain from math import isnan if isnan(v0.abs()): raise ValueError("invalid covariant: %s" % v0) R = get_bound_poly(G, prec=prec, norm_type=norm_type) #check orbit S = matrix(ZZ, 2, 2, [0, -1, 1, 0]) T = matrix(ZZ, 2, 2, [1, 1, 0, 1]) TI = matrix(ZZ, 2, 2, [1, -1, 0, 1]) count = 0 pts = [[G, v0, rep, MG, coshdelta(v0), 0]] #label - 0:None, 1:S, 2:T, 3:T^(-1) current_min = [G, v0, rep, MG, coshdelta(v0)] while pts != []: G, v, rep, M, D, label = pts.pop() #apply ST and keep z, Sz if D > R: break #all remaining pts are too far away #check if it is smaller. If so, we can improve the bound count += 1 if norm_type == 'norm': new_size = sum([abs(i)**2 for i in G.coefficients() ]) #euclidean norm squared else: #height new_size = e**max( [c.global_height(prec=prec) for c in G.coefficients()]) if new_size < current_size: current_min = [G, v, rep, M, coshdelta(v)] current_size = new_size R = get_bound_poly(G, norm_type=norm_type, prec=prec, emb=emb) #add new points to check if label != 1 and min((rep + 1).norm(), (rep - 1).norm()) >= 1: #don't undo S # the 2nd condition is equivalent to |\Re(-1/rep)| <= 1/2 # this means that rep can have resulted from an inversion step in # the shift-and-invert procedure, so don't invert # do inversion z = -1 / v new_pt = [ G.subs({ x: -y, y: x }), z, -1 / rep, M * S, coshdelta(z), 1 ] pts = insert_item(pts, new_pt, 4) if label != 3: #don't undo TI #do right shift z = v - 1 new_pt = [G.subs({x: x + y}), z, rep - 1, M * T, coshdelta(z), 2] pts = insert_item(pts, new_pt, 4) if label != 2: #don't undo T #do left shift z = v + 1 new_pt = [G.subs({x: x - y}), z, rep + 1, M * TI, coshdelta(z), 3] pts = insert_item(pts, new_pt, 4) return [current_min[0], current_min[3]]