Beispiel #1
0
    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)
Beispiel #2
0
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]]
Beispiel #3
0
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]]
Beispiel #4
0
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]]