Beispiel #1
0
def sieve(X, bound):
    r"""
    Returns the list of all projective, rational points on scheme ``X`` of
    height up to ``bound``.

    Height of a projective point X = (x_1, x_2,..., x_n) is given by
    H_X = max(y_1, y_2,..., y_n), where H_X is height of point X and y_i's
    are the normalized coordinates such that all y_i are integers and
    gcd(y_1, y_2,..., y_n) = 1.

    ALGORITHM:

    Main idea behind the algorithm is to find points modulo primes
    and then reconstruct them using chinese remainder theorem.
    We find modulo primes parallelly and then lift them and apply
    LLL in parallel.

    For the algorithm to work correctly, sufficient primes need
    to be present, these are calculated using the bound given in
    this([Hutz2015]_) paper.

    INPUT:

    - ``X`` - a scheme with ambient space defined over projective space

    - ``bound`` - a positive integer bound

    OUTPUT:

    - a list containing the projective rational points of ``X`` of height
      up to ``bound``, sorted

    EXAMPLES::

        sage: from sage.schemes.projective.projective_rational_point import sieve
        sage: P.<x,y,z,q>=ProjectiveSpace(QQ,3)
        sage: Y=P.subscheme([x^2-3^2*y^2+z*q,x+z+4*q])
        sage: sorted(sieve(Y, 12))  # long time
        [(-4 : -4/3 : 0 : 1), (-4 : 4/3 : 0 : 1),
         (-1 : -1/3 : 1 : 0), (-1 : 1/3 : 1 : 0)]

    ::

        sage: from sage.schemes.projective.projective_rational_point import sieve
        sage: E = EllipticCurve('37a')
        sage: sorted(sieve(E, 14))  # long time
        [(-1 : -1 : 1), (-1 : 0 : 1), (0 : -1 : 1),
         (0 : 0 : 1), (0 : 1 : 0), (1/4 : -5/8 : 1),
         (1/4 : -3/8 : 1), (1 : -1 : 1), (1 : 0 : 1),
         (2 : -3 : 1), (2 : 2 : 1), (6 : 14 : 1)]

    TESTS:

    Algorithm works even if coefficients are fraction::

        sage: from sage.schemes.projective.projective_rational_point import sieve
        sage: P.<x,y,z> = ProjectiveSpace(2,QQ)
        sage: X = P.subscheme(3*x - 3/2*y)
        sage: sieve(X, 3)
        [(-1 : -2 : 1), (-1/2 : -1 : 1), (-1/3 : -2/3 : 1), (0 : 0 : 1),
         (1/3 : 2/3 : 1), (1/2 : 1 : 0), (1/2 : 1 : 1), (1 : 2 : 1)]
    """
    if bound < 1:  # no projective rational point with height less than 1
        return []

    modulo_points = []  # list to store point modulo primes
    len_modulo_points = [
    ]  # stores number of points with respect to each prime
    primes_list = []  # list of good primes

    X.normalize_defining_polynomials()

    P = X.ambient_space()
    N = P.dimension()
    dim_scheme = X.dimension()

    # bound as per preposition - 4, in preperiodic points paper
    B = RR(2**(N / 4 + 1) * bound**2 * (N + 1).sqrt())

    m = [0 for _ in range(N + 1)]

    def sufficient_primes(x):
        r"""
        Returns a list of primes whose product is > `x`
        """
        small_primes = [2, 3]
        prod_primes = 6

        while prod_primes < x:
            p = next_prime(small_primes[-1])
            small_primes.append(p)
            prod_primes *= p
        return small_primes

    def good_primes(B):
        r"""
        Given the bound returns the prime whose product is greater than ``B``
        and which would take least amount of time to run main sieve algorithm

        Complexity of finding points modulo primes is assumed to be N^2 * P_max^{N}.
        Complexity of lifting points and LLL() function is assumed to
        be close to N^5 * (alpha^dim_scheme / P_max).
        where alpha is product of all primes, and P_max is largest prime in list.
        """

        M = dict()  # stores optimal list of primes, corresponding to list size
        small_primes = sufficient_primes(B)
        max_length = len(small_primes)
        M[max_length] = small_primes
        current_count = max_length - 1

        while current_count > 1:
            current_list = []  # stores prime which are bigger than least
            updated_list = []
            best_list = []

            least = (RR(B)**(1.00 / current_count)).floor()
            for i in range(current_count):
                current_list.append(next_prime(least))
                least = current_list[-1]
            # improving list of primes by taking prime less than least
            # this part of algorithm is used to centralize primes around `least`
            prod_prime = prod(current_list)
            least = current_list[0]
            while least != 2 and prod_prime > B and len(
                    updated_list) < current_count:
                best_list = updated_list + current_list[:current_count -
                                                        len(updated_list)]
                updated_list.append(previous_prime(least))
                least = updated_list[-1]

                removed_prime = current_list[current_count - len(updated_list)]
                prod_prime = (prod_prime * least) / removed_prime

            M[current_count] = sorted(best_list)
            current_count = current_count - 1

        best_size = 2
        best_time = (N**2) * M[2][-1]**(N) + (
            N**5 * RR(prod(M[2])**dim_scheme / M[2][-1]))
        for i in range(2, max_length + 1):
            current_time = (N**2) * M[i][-1]**(N) + (
                N**5 * RR(prod(M[i])**dim_scheme / M[i][-1]))
            if current_time < best_time:
                best_size = i
                best_time = current_time

        return M[best_size]

    def parallel_function(X, p):
        r"""
        Function used in parallel computation, computes a list of
        all rational points in modulo ring.
        """
        Xp = X.change_ring(GF(p))
        L = Xp.rational_points()

        return [list(_) for _ in L]

    def points_modulo_primes(X, primes):
        r"""
        Return a list of rational points modulo all `p` in primes,
        computed parallelly.
        """
        normalized_input = []
        for p in primes_list:
            normalized_input.append(((
                X,
                p,
            ), {}))
        p_iter = p_iter_fork(ncpus())

        points_pair = list(p_iter(parallel_function, normalized_input))
        points_pair.sort()
        return [pair[1] for pair in points_pair]

    def parallel_function_combination(point_p_max):
        r"""
        Function used in parallel computation, computes rational
        points lifted.
        """
        rat_points = set()
        for tupl in xmrange(len_modulo_points):
            point = []
            for k in range(N + 1):
                # lift all coordinates of given point using chinese remainder theorem
                L = [
                    modulo_points[j][tupl[j]][k].lift()
                    for j in range(len_primes - 1)
                ]
                L.append(point_p_max[k].lift())
                point.append(crt(L, primes_list))

            for i in range(N + 1):
                m[i] = point[i]

            M = matrix(ZZ, N + 2, N + 1, m)
            A = M.LLL()
            point = list(A[1])

            # check if all coordinates of this point satisfy height bound
            bound_satisfied = True
            for coordinate in point:
                if coordinate.abs() > bound:
                    bound_satisfied = False
                    break
            if not bound_satisfied:
                continue

            try:
                pt = X(list(A[1]))
            except TypeError:
                pass
            else:
                rat_points.add(pt)

        return [list(_) for _ in rat_points]

    def lift_all_points():
        r"""
        Return list of all rational points lifted parallelly.
        """
        normalized_input = []
        points = modulo_points.pop(
        )  # remove the list of points corresponding to largest prime
        len_modulo_points.pop()

        for point in points:
            normalized_input.append(((point, ), {}))
        p_iter = p_iter_fork(ncpus())
        points_satisfying = list(
            p_iter(parallel_function_combination, normalized_input))

        lifted_points = set()
        for pair in points_satisfying:
            L = pair[1]
            for point in L:
                lifted_points.add(X(tuple(point)))

        return list(lifted_points)

    # start of main algorithm

    primes_list = good_primes(B.ceil())

    modulo_points = points_modulo_primes(X, primes_list)
    len_modulo_points = [len(pt) for pt in modulo_points]
    len_primes = len(primes_list)
    prod_primes = prod(primes_list)

    # stores final result

    for i in range(N + 1):
        w = [0 for _ in range(N + 1)]
        w[i] = prod_primes
        m.extend(w)

    rat_points = lift_all_points()

    return sorted(rat_points)
Beispiel #2
0
def sieve(X, bound):
    r"""
    Returns the list of all rational points on scheme
    ``X`` of height up to ``bound``.

    ALGORITHM:

    Main idea behind the algorithm is to find points modulo primes
    and then reconstruct them using chinese remainder theorem.
    We compute the points modulo primes parallely and then lift
    them via chinese remainder theorem in parallel. The LLL reduction
    algorithm is applied to each component of the points, and finally
    the result is merged and converted to a point on the subscheme.

    For the algorithm to work correctly, sufficient primes need
    to be chosen, these are determined using the bounds dependent
    on the bound given in [Hutz2015]_.

    INPUT:

    - ``X`` - a scheme with ambient space defined over a product of projective spaces

    - ``bound`` - a positive integer bound

    OUTPUT:

    - a list containing the rational points of ``X`` of height
      up to ``bound``, sorted

    EXAMPLES::

        sage: from sage.schemes.product_projective.rational_point import sieve
        sage: PP.<x,y,z,u,v> = ProductProjectiveSpaces([2,1], QQ)
        sage: X = PP.subscheme([x^2 + y^2 - x*z, u*u-v*u])
        sage: sieve(X, 2)
        [(0 : 0 : 1 , 0 : 1), (0 : 0 : 1 , 1 : 1), (1/2 : -1/2 : 1 , 0 : 1),
         (1/2 : -1/2 : 1 , 1 : 1), (1/2 : 1/2 : 1 , 0 : 1), (1/2 : 1/2 : 1 , 1 : 1),
         (1 : 0 : 1 , 0 : 1), (1 : 0 : 1 , 1 : 1)]
    """

    if bound < 1:
        return []

    modulo_points = []
    len_modulo_points = []
    primes_list = []

    P = X.ambient_space()
    N = P.ngens()
    dim_scheme = X.dimension()

    num_comp = P.num_components()
    comp_dim_relative = [P[i].dimension_relative() + 1 for i in range(num_comp)]

    dim_prefix = [0, comp_dim_relative[0]] # prefixes dim list
    for i in range(1, len(comp_dim_relative)):
        dim_prefix.append(dim_prefix[i] + comp_dim_relative[i])

    dim_max = max(P[i].dimension() for i in range(num_comp))
    B = RR(2**(dim_max/4+1)*bound**2*(dim_max+1).sqrt())
    m = []

    def sufficient_primes(x):
        r"""
        Returns a list of primes whose product is > `x`
        """
        small_primes = [2,3]
        prod_primes = 6

        while prod_primes < x:
            p = next_prime(small_primes[-1])
            small_primes.append(p)
            prod_primes *= p
        return small_primes

    def good_primes(B):
        r"""
        Given the bound, returns the primes whose product is greater than ``B``
        and which would take the least amount of time to run the main sieve algorithm

        Complexity of finding points modulo primes is assumed to be N^2 * P_max^{N}.
        Complexity of lifting points and the LLL() function is assumed to
        be close to (dim_max^5) * (alpha / P_max)^dim_scheme.
        where alpha is the product of all primes, P_max is the largest prime in
        the list, dim_max is the max dimension of all components, and N is the dimension
        of the ambient space.
        """

        M = dict() # stores optimal list of primes, corresponding to list size
        small_primes = sufficient_primes(B)
        max_length = len(small_primes)
        M[max_length] = small_primes
        current_count = max_length - 1
        dim = X.ambient_space().dimension()

        while current_count > 1:
            current_list = [] # stores prime which are bigger than least
            updated_list = []
            best_list = []

            least = (RR(B)**(1.00/current_count)).floor()
            for i in range(current_count):
                current_list.append(next_prime(least))
                least = current_list[-1]
            # improving list of primes by taking primes less than least
            # this part of algorithm is used to centralize primes around `least`
            prod_prime = prod(current_list)
            least = current_list[0]
            while least != 2 and prod_prime > B and len(updated_list) < current_count:
                best_list = updated_list + current_list[:current_count - len(updated_list)]
                updated_list.append(previous_prime(least))
                least = updated_list[-1]

                removed_prime = current_list[current_count - len(updated_list)]
                prod_prime = (prod_prime * least) / removed_prime

            M[current_count] = sorted(best_list)
            current_count = current_count - 1

        best_size = 2
        best_time = (dim**2)*M[2][-1]**(dim) + (dim_max**5 * (prod(M[2])/M[2][-1])**dim_scheme)
        for i in range(2, max_length + 1):
            current_time = (dim**2)*M[i][-1]**(dim) + (dim_max**5 * (prod(M[i])/M[i][-1])**dim_scheme)
            if current_time < best_time:
                best_size = i
                best_time = current_time

        return M[best_size]

    def parallel_function(X, p):
        r"""
        Function used in parallel computation, computes a list of
        all rational points in modulo p.
        """
        Xp = X.change_ring(GF(p))
        L = Xp.rational_points()

        return [list(_) for _ in L]

    def points_modulo_primes(X, primes):
        r"""
        Return a list of rational points modulo all `p` in primes,
        computed parallely.
        """
        normalized_input = []
        for p in primes_list:
            normalized_input.append(((X, p, ), {}))
        p_iter = p_iter_fork(ncpus())

        points_pair = list(p_iter(parallel_function, normalized_input))
        points_pair.sort()
        modulo_points = []
        for pair in points_pair:
            modulo_points.append(pair[1])

        return modulo_points

    def parallel_function_combination(point_p_max):
        r"""
        Function used in parallel computation, computes rational
        points lifted.
        """
        rat_points = set()
        for tupl in xmrange(len_modulo_points):
            point = []
            for k in range(N):
                # lift all coordinates of given point using chinese remainder theorem
                L = [modulo_points[j][tupl[j]][k].lift() for j in range(len_primes - 1)]
                L.append(point_p_max[k].lift())
                point.append( crt(L, primes_list) )

            for i in range(num_comp):
                for j in range(comp_dim_relative[i]):
                    m[i][j] = point[dim_prefix[i] + j]

            # generating matrix to compute LLL reduction for each component
            M = [matrix(ZZ, comp_dim_relative[i] + 1, comp_dim_relative[i], m[i]) \
                                                                for i in range(num_comp)]
            A = [M[i].LLL() for i in range(num_comp)]
            point = []
            for i in range(num_comp):
                point.extend(A[i][1])

            # check if all coordinates of this point satisfy height bound
            bound_satisfied = True
            for coordinate in point:
                if coordinate.abs() > bound:
                    bound_satisfied = False
                    break
            if not bound_satisfied:
                continue

            try:
                rat_points.add(X(point)) # checks if this point lies on X or not
            except:
                pass

        return [list(_) for _ in rat_points]

    def lift_all_points():
        r"""
        Returns list of all rational points lifted parallely.
        """
        normalized_input = []
        points = modulo_points.pop() # remove the list of points corresponding to largest prime
        len_modulo_points.pop()

        for point in points:
            normalized_input.append(( (point, ), {}))
        p_iter = p_iter_fork(ncpus())
        points_satisfying = list(p_iter(parallel_function_combination, normalized_input))

        lifted_points = set()
        for pair in points_satisfying:
            L = pair[1]
            for point in L:
                lifted_points.add(X(tuple(point)))

        return list(lifted_points)

    primes_list = good_primes(B.ceil())
    modulo_points = points_modulo_primes(X, primes_list)
    len_modulo_points = [len(_) for _ in modulo_points]
    len_primes = len(primes_list)
    prod_primes = prod(primes_list)

    # construct m for each component projective space
    for i in range(num_comp):
        dim = comp_dim_relative[i]
        temp = [0 for _ in range(dim)]
        for j in range(dim):
            w = [0 for _ in range(dim)]
            w[j] = prod_primes
            temp.extend(w)
        m.append(temp)

    rat_points = lift_all_points()
    
    return sorted(rat_points)