def basis_of_short_vectors(self, show_lengths=False, safe_flag=True): """ Return a basis for `ZZ^n` made of vectors with minimal lengths Q(`v`). The safe_flag allows us to select whether we want a copy of the output, or the original output. By default safe_flag = True, so we return a copy of the cached information. If this is set to False, then the routine is much faster but the return values are vulnerable to being corrupted by the user. OUTPUT: a list of vectors, and optionally a list of values for each vector. EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q.basis_of_short_vectors() [(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)] sage: Q.basis_of_short_vectors(True) ([(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)], [1, 3, 5, 7]) """ ## Try to use the cached results try: ## Return the appropriate result if show_lengths: if safe_flag: return deep_copy(self.__basis_of_short_vectors), deepcopy(self.__basis_of_short_vectors_lengths) else: return self.__basis_of_short_vectors, self.__basis_of_short_vectors_lengths else: if safe_flag: return deepcopy(self.__basis_of_short_vectors) else: return deepcopy(self.__basis_of_short_vectors) except Exception: pass ## Set an upper bound for the number of vectors to consider Max_number_of_vectors = 10000 ## Generate a PARI matrix string for the associated Hessian matrix M_str = str(gp(self.matrix())) ## Run through all possible minimal lengths to find a spanning set of vectors n = self.dim() # MS = MatrixSpace(QQ, n) M1 = Matrix([[0]]) vec_len = 0 while M1.rank() < n: ## DIAGONSTIC # print # print "Starting with vec_len = ", vec_len # print "M_str = ", M_str vec_len += 1 gp_mat = gp.qfminim(M_str, vec_len, Max_number_of_vectors)[3].mattranspose() number_of_vecs = ZZ(gp_mat.matsize()[1]) vector_list = [] for i in range(number_of_vecs): # print "List at", i, ":", list(gp_mat[i+1,]) new_vec = vector([ZZ(x) for x in list(gp_mat[i + 1,])]) vector_list.append(new_vec) ## DIAGNOSTIC # print "number_of_vecs = ", number_of_vecs # print "vector_list = ", vector_list ## Make a matrix from the short vectors if len(vector_list) > 0: M1 = Matrix(vector_list) ## DIAGNOSTIC # print "matrix of vectors = \n", M1 # print "rank of the matrix = ", M1.rank() # print " vec_len = ", vec_len # print M1 ## Organize these vectors by length (and also introduce their negatives) max_len = vec_len / 2 vector_list_by_length = [[] for _ in range(max_len + 1)] for v in vector_list: l = self(v) vector_list_by_length[l].append(v) vector_list_by_length[l].append(vector([-x for x in v])) ## Make a matrix from the column vectors (in order of ascending length). sorted_list = [] for i in range(len(vector_list_by_length)): for v in vector_list_by_length[i]: sorted_list.append(v) sorted_matrix = Matrix(sorted_list).transpose() ## Determine a basis of vectors of minimal length pivots = sorted_matrix.pivots() basis = [sorted_matrix.column(i) for i in pivots] pivot_lengths = [self(v) for v in basis] ## DIAGNOSTIC # print "basis = ", basis # print "pivot_lengths = ", pivot_lengths ## Cache the result self.__basis_of_short_vectors = basis self.__basis_of_short_vectors_lenghts = pivot_lengths ## Return the appropriate result if show_lengths: return basis, pivot_lengths else: return basis
def basis_of_short_vectors(self, show_lengths=False, safe_flag=True): """ Return a basis for `ZZ^n` made of vectors with minimal lengths Q(`v`). The safe_flag allows us to select whether we want a copy of the output, or the original output. By default safe_flag = True, so we return a copy of the cached information. If this is set to False, then the routine is much faster but the return values are vulnerable to being corrupted by the user. OUTPUT: a list of vectors, and optionally a list of values for each vector. EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q.basis_of_short_vectors() [(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)] sage: Q.basis_of_short_vectors(True) ([(1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)], [1, 3, 5, 7]) """ ## Try to use the cached results try: ## Return the appropriate result if show_lengths: if safe_flag: return deep_copy(self.__basis_of_short_vectors), deepcopy(self.__basis_of_short_vectors_lengths) else: return self.__basis_of_short_vectors, self.__basis_of_short_vectors_lengths else: if safe_flag: return deepcopy(self.__basis_of_short_vectors) else: return deepcopy(self.__basis_of_short_vectors) except Exception: pass ## Set an upper bound for the number of vectors to consider Max_number_of_vectors = 10000 ## Generate a PARI matrix string for the associated Hessian matrix M_str = str(gp(self.matrix())) ## Run through all possible minimal lengths to find a spanning set of vectors n = self.dim() #MS = MatrixSpace(QQ, n) M1 = Matrix([[0]]) vec_len = 0 while M1.rank() < n: ## DIAGONSTIC #print #print "Starting with vec_len = ", vec_len #print "M_str = ", M_str vec_len += 1 gp_mat = gp.qfminim(M_str, vec_len, Max_number_of_vectors)[3].mattranspose() number_of_vecs = ZZ(gp_mat.matsize()[1]) vector_list = [] for i in range(number_of_vecs): #print "List at", i, ":", list(gp_mat[i+1,]) new_vec = vector([ZZ(x) for x in list(gp_mat[i+1,])]) vector_list.append(new_vec) ## DIAGNOSTIC #print "number_of_vecs = ", number_of_vecs #print "vector_list = ", vector_list ## Make a matrix from the short vectors if len(vector_list) > 0: M1 = Matrix(vector_list) ## DIAGNOSTIC #print "matrix of vectors = \n", M1 #print "rank of the matrix = ", M1.rank() #print " vec_len = ", vec_len #print M1 ## Organize these vectors by length (and also introduce their negatives) max_len = vec_len // 2 vector_list_by_length = [[] for _ in range(max_len + 1)] for v in vector_list: l = self(v) vector_list_by_length[l].append(v) vector_list_by_length[l].append(vector([-x for x in v])) ## Make a matrix from the column vectors (in order of ascending length). sorted_list = [] for i in range(len(vector_list_by_length)): for v in vector_list_by_length[i]: sorted_list.append(v) sorted_matrix = Matrix(sorted_list).transpose() ## Determine a basis of vectors of minimal length pivots = sorted_matrix.pivots() basis = [sorted_matrix.column(i) for i in pivots] pivot_lengths = [self(v) for v in basis] ## DIAGNOSTIC #print "basis = ", basis #print "pivot_lengths = ", pivot_lengths ## Cache the result self.__basis_of_short_vectors = basis self.__basis_of_short_vectors_lenghts = pivot_lengths ## Return the appropriate result if show_lengths: return basis, pivot_lengths else: return basis
def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): """ Return a list of lists of short vectors `v`, sorted by length, with Q(`v`) < len_bound. The list in output `[i]` indexes all vectors of length `i`. If the up_to_sign_flag is set to True, then only one of the vectors of the pair `[v, -v]` is listed. Note: This processes the PARI/GP output to always give elements of type `ZZ`. OUTPUT: a list of lists of vectors. EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q.short_vector_list_up_to_length(3) [[(0, 0, 0, 0)], [(1, 0, 0, 0), (-1, 0, 0, 0)], []] sage: Q.short_vector_list_up_to_length(4) [[(0, 0, 0, 0)], [(1, 0, 0, 0), (-1, 0, 0, 0)], [], [(0, 1, 0, 0), (0, -1, 0, 0)]] sage: Q.short_vector_list_up_to_length(5) [[(0, 0, 0, 0)], [(1, 0, 0, 0), (-1, 0, 0, 0)], [], [(0, 1, 0, 0), (0, -1, 0, 0)], [(1, 1, 0, 0), (-1, -1, 0, 0), (-1, 1, 0, 0), (1, -1, 0, 0), (2, 0, 0, 0), (-2, 0, 0, 0)]] sage: Q.short_vector_list_up_to_length(5, True) [[(0, 0, 0, 0)], [(1, 0, 0, 0)], [], [(0, 1, 0, 0)], [(1, 1, 0, 0), (-1, 1, 0, 0), (2, 0, 0, 0)]] sage: Q = QuadraticForm(matrix(6, [2, 1, 1, 1, -1, -1, 1, 2, 1, 1, -1, -1, 1, 1, 2, 0, -1, -1, 1, 1, 0, 2, 0, -1, -1, -1, -1, 0, 2, 1, -1, -1, -1, -1, 1, 2])) sage: vs = Q.short_vector_list_up_to_length(40) #long time The cases of ``len_bound < 2`` led to exception or infinite runtime before. :: sage: Q.short_vector_list_up_to_length(0) [] sage: Q.short_vector_list_up_to_length(1) [[(0, 0, 0, 0, 0, 0)]] In the case of quadratic forms that are not positive definite an error is raised. :: sage: QuadraticForm(matrix(2, [2, 0, 0, -2])).short_vector_list_up_to_length(3) Traceback (most recent call last): ... ValueError: Quadratic form must be positive definite in order to enumerate short vectors Sometimes, Pari does not compute short vectors correctly. It returns too long vectors. :: sage: mat = matrix(2, [72, 12, 12, 120]) #long time sage: len_bound = 22953421 #long time sage: gp_mat = gp.qfminim(str(gp(mat)), 2 * len_bound - 2)[3] #long time sage: rows = [ map(ZZ, str(gp_mat[i,])[1:-1].split(',')) for i in range(1, gp_mat.matsize()[1] + 1) ] #long time sage: vec_list = map(vector, zip(*rows)) #long time sage: eval_v_cython = cython_lambda( ", ".join( "int a{0}".format(i) for i in range(2) ), " + ".join( "{coeff} * a{i} * a{j}".format(coeff = mat[i,j], i = i, j = j) for i in range(2) for j in range(2) ) ) #long time sage: any( eval_v_cython(*v) == 2 * 22955664 for v in vec_list ) # 22955664 > 22953421 = len_bound #long time True """ if not self.is_positive_definite(): raise ValueError( "Quadratic form must be positive definite in order to enumerate short vectors" ) ## Generate a PARI matrix string for the associated Hessian matrix M_str = str(gp(self.matrix())) if len_bound <= 0: return list() elif len_bound == 1: return [[(vector([ZZ(0) for _ in range(self.dim())]))]] ## Generate the short vectors gp_mat = gp.qfminim(M_str, 2 * len_bound - 2)[3] ## We read all n-th entries at once so that not too many sage[...] variables are ## used. This is important when to many vectors are returned. rows = [ map(ZZ, str(gp_mat[i, ])[1:-1].split(',')) for i in range(1, gp_mat.matsize()[1] + 1) ] vec_list = map(vector, zip(*rows)) if len(vec_list) > 500: eval_v_cython = cython_lambda( ", ".join("int a{0}".format(i) for i in range(self.dim())), " + ".join( "{coeff} * a{i} * a{j}".format(coeff=self[i, j], i=i, j=j) for i in range(self.dim()) for j in range(i, self.dim()))) eval_v = lambda v: eval_v_cython(*v) else: eval_v = self ## Sort the vectors into lists by their length vec_sorted_list = [list() for i in range(len_bound)] for v in vec_list: v_evaluated = eval_v(v) try: vec_sorted_list[v_evaluated].append(v) if not up_to_sign_flag: vec_sorted_list[v_evaluated].append(-v) except IndexError: ## We deal with a Pari but, that returns longer vectors that requested. ## E.g. : self.matrix() == matrix(2, [72, 12, 12, 120]) ## len_bound = 22953421 ## gives maximal length 22955664 pass ## Add the zero vector by hand zero_vec = vector([ZZ(0) for _ in range(self.dim())]) vec_sorted_list[0].append(zero_vec) ## Return the sorted list return vec_sorted_list
def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): """ Return a list of lists of short vectors `v`, sorted by length, with Q(`v`) < len_bound. The list in output `[i]` indexes all vectors of length `i`. If the up_to_sign_flag is set to True, then only one of the vectors of the pair `[v, -v]` is listed. Note: This processes the PARI/GP output to always give elements of type `ZZ`. OUTPUT: a list of lists of vectors. EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q.short_vector_list_up_to_length(3) [[(0, 0, 0, 0)], [(1, 0, 0, 0), (-1, 0, 0, 0)], []] sage: Q.short_vector_list_up_to_length(4) [[(0, 0, 0, 0)], [(1, 0, 0, 0), (-1, 0, 0, 0)], [], [(0, 1, 0, 0), (0, -1, 0, 0)]] sage: Q.short_vector_list_up_to_length(5) [[(0, 0, 0, 0)], [(1, 0, 0, 0), (-1, 0, 0, 0)], [], [(0, 1, 0, 0), (0, -1, 0, 0)], [(1, 1, 0, 0), (-1, -1, 0, 0), (-1, 1, 0, 0), (1, -1, 0, 0), (2, 0, 0, 0), (-2, 0, 0, 0)]] sage: Q.short_vector_list_up_to_length(5, True) [[(0, 0, 0, 0)], [(1, 0, 0, 0)], [], [(0, 1, 0, 0)], [(1, 1, 0, 0), (-1, 1, 0, 0), (2, 0, 0, 0)]] sage: Q = QuadraticForm(matrix(6, [2, 1, 1, 1, -1, -1, 1, 2, 1, 1, -1, -1, 1, 1, 2, 0, -1, -1, 1, 1, 0, 2, 0, -1, -1, -1, -1, 0, 2, 1, -1, -1, -1, -1, 1, 2])) sage: vs = Q.short_vector_list_up_to_length(40) #long time The cases of ``len_bound < 2`` led to exception or infinite runtime before. :: sage: Q.short_vector_list_up_to_length(0) [] sage: Q.short_vector_list_up_to_length(1) [[(0, 0, 0, 0, 0, 0)]] In the case of quadratic forms that are not positive definite an error is raised. :: sage: QuadraticForm(matrix(2, [2, 0, 0, -2])).short_vector_list_up_to_length(3) Traceback (most recent call last): ... ValueError: Quadratic form must be positive definite in order to enumerate short vectors Sometimes, Pari does not compute short vectors correctly. It returns too long vectors. :: sage: mat = matrix(2, [72, 12, 12, 120]) #long time sage: len_bound = 22953421 #long time sage: gp_mat = gp.qfminim(str(gp(mat)), 2 * len_bound - 2)[3] #long time sage: rows = [ map(ZZ, str(gp_mat[i,])[1:-1].split(',')) for i in range(1, gp_mat.matsize()[1] + 1) ] #long time sage: vec_list = map(vector, zip(*rows)) #long time sage: eval_v_cython = cython_lambda( ", ".join( "int a{0}".format(i) for i in range(2) ), " + ".join( "{coeff} * a{i} * a{j}".format(coeff = mat[i,j], i = i, j = j) for i in range(2) for j in range(2) ) ) #long time sage: any( eval_v_cython(*v) == 2 * 22955664 for v in vec_list ) # 22955664 > 22953421 = len_bound #long time True """ if not self.is_positive_definite() : raise ValueError( "Quadratic form must be positive definite in order to enumerate short vectors" ) ## Generate a PARI matrix string for the associated Hessian matrix M_str = str(gp(self.matrix())) if len_bound <= 0 : return list() elif len_bound == 1 : return [ [(vector([ZZ(0) for _ in range(self.dim())]))] ] ## Generate the short vectors gp_mat = gp.qfminim(M_str, 2*len_bound - 2)[3] ## We read all n-th entries at once so that not too many sage[...] variables are ## used. This is important when to many vectors are returned. rows = [ map(ZZ, str(gp_mat[i,])[1:-1].split(',')) for i in range(1, gp_mat.matsize()[1] + 1) ] vec_list = map(vector, zip(*rows)) if len(vec_list) > 500 : eval_v_cython = cython_lambda( ", ".join( "int a{0}".format(i) for i in range(self.dim()) ), " + ".join( "{coeff} * a{i} * a{j}".format(coeff = self[i,j], i = i, j = j) for i in range(self.dim()) for j in range(i, self.dim()) ) ) eval_v = lambda v: eval_v_cython(*v) else : eval_v = self ## Sort the vectors into lists by their length vec_sorted_list = [list() for i in range(len_bound)] for v in vec_list: v_evaluated = eval_v(v) try : vec_sorted_list[v_evaluated].append(v) if not up_to_sign_flag : vec_sorted_list[v_evaluated].append(-v) except IndexError : ## We deal with a Pari but, that returns longer vectors that requested. ## E.g. : self.matrix() == matrix(2, [72, 12, 12, 120]) ## len_bound = 22953421 ## gives maximal length 22955664 pass ## Add the zero vector by hand zero_vec = vector([ZZ(0) for _ in range(self.dim())]) vec_sorted_list[0].append(zero_vec) ## Return the sorted list return vec_sorted_list
def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): """ Return a list of lists of short vectors `v`, sorted by length, with Q(`v`) < len_bound. The list in output `[i]` indexes all vectors of length `i`. If the up_to_sign_flag is set to True, then only one of the vectors of the pair `[v, -v]` is listed. Note: This processes the PARI/GP output to always give elements of type `ZZ`. OUTPUT: a list of lists of vectors. EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q.short_vector_list_up_to_length(3) [[(0, 0, 0, 0)], [(1, 0, 0, 0), [-1, 0, 0, 0]], []] sage: Q.short_vector_list_up_to_length(4) [[(0, 0, 0, 0)], [(1, 0, 0, 0), [-1, 0, 0, 0]], [], [(0, 1, 0, 0), [0, -1, 0, 0]]] sage: Q.short_vector_list_up_to_length(5) [[(0, 0, 0, 0)], [(1, 0, 0, 0), [-1, 0, 0, 0]], [], [(0, 1, 0, 0), [0, -1, 0, 0]], [(1, 1, 0, 0), [-1, -1, 0, 0], (-1, 1, 0, 0), [1, -1, 0, 0], (2, 0, 0, 0), [-2, 0, 0, 0]]] sage: Q.short_vector_list_up_to_length(5, True) [[(0, 0, 0, 0)], [(1, 0, 0, 0)], [], [(0, 1, 0, 0)], [(1, 1, 0, 0), (-1, 1, 0, 0), (2, 0, 0, 0)]] """ ## Set an upper bound for the number of vectors to consider Max_number_of_vectors = 10000 ## Generate a PARI matrix string for the associated Hessian matrix M_str = str(gp(self.matrix())) ## Generate the short vectors gp_mat = gp.qfminim(M_str, 2 * len_bound - 2, Max_number_of_vectors)[3].mattranspose() number_of_rows = gp_mat.matsize()[1] gp_mat_vector_list = [ vector([ZZ(x) for x in gp_mat[i + 1, ]]) for i in range(number_of_rows) ] ## Sort the vectors into lists by their length vec_list = [[] for i in range(len_bound)] for v in gp_mat_vector_list: vec_list[self(v)].append(v) if up_to_sign_flag == False: vec_list[self(v)].append([-x for x in v]) ## Add the zero vector by hand zero_vec = vector([ZZ(0) for _ in range(self.dim())]) vec_list[0].append(zero_vec) ## Return the sorted list return vec_list
def short_vector_list_up_to_length(self, len_bound, up_to_sign_flag=False): """ Return a list of lists of short vectors `v`, sorted by length, with Q(`v`) < len_bound. The list in output `[i]` indexes all vectors of length `i`. If the up_to_sign_flag is set to True, then only one of the vectors of the pair `[v, -v]` is listed. Note: This processes the PARI/GP output to always give elements of type `ZZ`. OUTPUT: a list of lists of vectors. EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7]) sage: Q.short_vector_list_up_to_length(3) [[(0, 0, 0, 0)], [(1, 0, 0, 0), [-1, 0, 0, 0]], []] sage: Q.short_vector_list_up_to_length(4) [[(0, 0, 0, 0)], [(1, 0, 0, 0), [-1, 0, 0, 0]], [], [(0, 1, 0, 0), [0, -1, 0, 0]]] sage: Q.short_vector_list_up_to_length(5) [[(0, 0, 0, 0)], [(1, 0, 0, 0), [-1, 0, 0, 0]], [], [(0, 1, 0, 0), [0, -1, 0, 0]], [(1, 1, 0, 0), [-1, -1, 0, 0], (-1, 1, 0, 0), [1, -1, 0, 0], (2, 0, 0, 0), [-2, 0, 0, 0]]] sage: Q.short_vector_list_up_to_length(5, True) [[(0, 0, 0, 0)], [(1, 0, 0, 0)], [], [(0, 1, 0, 0)], [(1, 1, 0, 0), (-1, 1, 0, 0), (2, 0, 0, 0)]] """ ## Set an upper bound for the number of vectors to consider Max_number_of_vectors = 10000 ## Generate a PARI matrix string for the associated Hessian matrix M_str = str(gp(self.matrix())) ## Generate the short vectors gp_mat = gp.qfminim(M_str, 2*len_bound-2, Max_number_of_vectors)[3].mattranspose() number_of_rows = gp_mat.matsize()[1] gp_mat_vector_list = [vector([ZZ(x) for x in gp_mat[i+1,]]) for i in range(number_of_rows)] ## Sort the vectors into lists by their length vec_list = [[] for i in range(len_bound)] for v in gp_mat_vector_list: vec_list[self(v)].append(v) if up_to_sign_flag == False: vec_list[self(v)].append([-x for x in v]) ## Add the zero vector by hand zero_vec = vector([ZZ(0) for _ in range(self.dim())]) vec_list[0].append(zero_vec) ## Return the sorted list return vec_list