def adjoint(self): """ This gives the adjoint (integral) quadratic form associated to the given form, essentially defined by taking the adjoint of the matrix. EXAMPLES:: sage: Q = QuadraticForm(ZZ, 2, [1,2,5]) sage: Q.adjoint() Quadratic form in 2 variables over Integer Ring with coefficients: [ 5 -2 ] [ * 1 ] :: sage: Q = QuadraticForm(ZZ, 3, [1, 0, -1, 2, -1, 5]) sage: Q.adjoint() Quadratic form in 3 variables over Integer Ring with coefficients: [ 39 2 8 ] [ * 19 4 ] [ * * 8 ] """ if is_odd(self.dim()): return QuadraticForm(self.matrix().adjoint()*2) else: return QuadraticForm(self.matrix().adjoint())
def HyperbolicPlane_quadratic_form(R, r=1): """ Constructs the direct sum of `r` copies of the quadratic form `xy` representing a hyperbolic plane defined over the base ring `R`. INPUT: - `R`: a ring - `n` (integer, default 1) number of copies EXAMPLES:: sage: HyperbolicPlane_quadratic_form(ZZ) Quadratic form in 2 variables over Integer Ring with coefficients: [ 0 1 ] [ * 0 ] """ r = ZZ(r) ## Check that the multiplicity is a natural number if r < 1: raise TypeError("The multiplicity r must be a natural number.") H = QuadraticForm(R, 2, [0, 1, 0]) return sum([H for i in range(r - 1)], H)
def signature(self, new_convention=True): """ Returns the signature of the link, computed from the Goeritz matrix using the algorithm of Gordon and Litherland:: sage: K = Link('4a1') sage: K.signature() 0 sage: L = Link('9^3_12') sage: Lbar = L.mirror() sage: L.signature() + Lbar.signature() 0 sage: M = Link(braid_closure=[1, 2, 1, 2, 1, 2, 1, 2]) sage: M.signature() -6 SnapPy 3.0 switched the sign convention for the signature so that "positive knots have negative signatures". You can recover the previous default by:: sage: L = Link('3a1') sage: L.signature() -2 sage: L.signature(new_convention=False) 2 """ m, G = self.goeritz_matrix(return_graph=True) correction = sum([ e[2]['sign'] for e in G.edges(sort=False) if e[2]['sign'] == e[2]['crossing'].sign ]) ans = QuadraticForm(QQ, m).signature() + correction if new_convention: ans = -ans return ans
def BezoutianQuadraticForm(f, g): r""" Compute the Bezoutian of two polynomials defined over a common base ring. This is defined by .. MATH:: {\rm Bez}(f, g) := \frac{f(x) g(y) - f(y) g(x)}{y - x} and has size defined by the maximum of the degrees of `f` and `g`. INPUT: - `f`, `g` -- polynomials in `R[x]`, for some ring `R` OUTPUT: a quadratic form over `R` EXAMPLES:: sage: R = PolynomialRing(ZZ, 'x') sage: f = R([1,2,3]) sage: g = R([2,5]) sage: Q = BezoutianQuadraticForm(f, g) ; Q Quadratic form in 2 variables over Integer Ring with coefficients: [ 1 -12 ] [ * -15 ] AUTHORS: - Fernando Rodriguez-Villegas, Jonathan Hanke -- added on 11/9/2008 """ ## Check that f and g are polynomials with a common base ring if not is_Polynomial(f) or not is_Polynomial(g): raise TypeError("Oops! One of your inputs is not a polynomial. =(") if f.base_ring() != g.base_ring( ): ## TO DO: Change this to allow coercion! raise TypeError( "Oops! These polynomials are not defined over the same coefficient ring." ) ## Initialize the quadratic form R = f.base_ring() P = PolynomialRing(R, ['x', 'y']) a, b = P.gens() n = max(f.degree(), g.degree()) Q = QuadraticForm(R, n) ## Set the coefficients of Bezoutian bez_poly = (f(a) * g(b) - f(b) * g(a)) // ( b - a) ## Truncated (exact) division here for i in range(n): for j in range(i, n): if i == j: Q[i, j] = bez_poly.coefficient({a: i, b: j}) else: Q[i, j] = bez_poly.coefficient({a: i, b: j}) * 2 return Q
def _norm_form(self): r""" Return the norm N_{K/QQ} as a integral quadratic form. """ w = self._w() wc = w.galois_conjugate() wtr = Integer(w + wc) return QuadraticForm(Matrix(ZZ, [[2, wtr], [wtr, 2 * w * wc]]))
def make_random_instance(n, m): solution = VectorSpace(ZZ2, n).random_element() results = [] quad_forms = [] for _ in range(m): e = [] for _ in range(n * (n + 1) / 2): e.append(ZZ2.random_element()) quad_form = QuadraticForm(ZZ2, n, e) quad_forms.append(quad_form) results.append(quad_form(solution)) instance = Instance(n, quad_forms, results) prover = Prover(instance, solution) return (instance, prover)
def signature(self): r""" Return the signature of this lattice, which is defined as the difference between the number of positive eigenvalues and the number of negative eigenvalues in the Gram matrix. EXAMPLES:: sage: from sage.modules.free_quadratic_module_integer_symmetric import IntegralLattice sage: U = IntegralLattice(Matrix(ZZ,2,2,[0,1,1,0])) sage: U.signature() 0 """ from sage.quadratic_forms.quadratic_form import QuadraticForm return QuadraticForm(QQ, self.gram_matrix()).signature()
def signature_pair(self): r""" Return the signature tuple `(n_+,n_-)` of this lattice. Here `n_+` (resp. `n_-`) is the number of positive (resp. negative) eigenvalues of the Gram matrix. EXAMPLES:: sage: A2 = IntegralLattice("A2") sage: A2.signature_pair() (2, 0) """ from sage.quadratic_forms.quadratic_form import QuadraticForm return QuadraticForm(QQ, self.gram_matrix()).signature_vector()[:2]
def signature_pair(self): r""" Return the signature tuple `(n_+,n_-)` of this lattice. Here `n_+` (resp. `n_-`) is the number of positive (resp. negative) eigenvalues of the Gram matrix. EXAMPLES:: sage: from sage.modules.free_quadratic_module_integer_symmetric import IntegralLattice sage: A2 = IntegralLattice(Matrix(ZZ,2,2,[2,-1,-1,2])) sage: A2.signature_pair() (2, 0) """ from sage.quadratic_forms.quadratic_form import QuadraticForm return QuadraticForm(QQ, self.gram_matrix()).signature_vector()[:2]
def signature(self): """ Returns the signature of the link, computed from the Goeritz matrix using the algorithm of Gordon and Litherland:: sage: K = Link('4a1') sage: K.signature() 0 sage: L = Link('9^3_12') sage: Lbar = L.mirror() sage: L.signature() + Lbar.signature() 0 """ m, G = self.goeritz_matrix(return_graph=True) correction = sum([e[2]['sign'] for e in G.edges(sort=False) if e[2]['sign'] == e[2]['crossing'].sign]) return QuadraticForm(QQ, m).signature() + correction
def orthogonal_group(self, gens=None, is_finite=None): """ Return the orthogonal group of this lattice as a matrix group. The elements are isometries of the ambient vector space which preserve this lattice. They are represented by matrices with respect to the standard basis. INPUT: - ``gens`` -- a list of matrices (default:``None``) - ``is_finite`` -- bool (default: ``None``) If set to ``True``, then the group is placed in the category of finite groups. Sage does not check this. OUTPUT: The matrix group generated by ``gens``. If ``gens`` is not specified, then generators of the full orthogonal group of this lattice are computed. They are continued as the identity on the orthogonal complement of the lattice in its ambient space. Currently, we can only compute the orthogonal group for positive definite lattices. EXAMPLES:: sage: A4 = IntegralLattice("A4") sage: Aut = A4.orthogonal_group() sage: Aut Group of isometries with 5 generators ( [-1 0 0 0] [0 0 0 1] [-1 -1 -1 0] [ 1 0 0 0] [ 1 0 0 0] [ 0 -1 0 0] [0 0 1 0] [ 0 0 0 -1] [-1 -1 -1 -1] [ 0 1 0 0] [ 0 0 -1 0] [0 1 0 0] [ 0 0 1 1] [ 0 0 0 1] [ 0 0 1 1] [ 0 0 0 -1], [1 0 0 0], [ 0 1 0 0], [ 0 0 1 0], [ 0 0 0 -1] ) The group acts from the right on the lattice and its discriminant group:: sage: x = A4.an_element() sage: g = Aut.an_element() sage: g [ 1 1 1 0] [ 0 0 -1 0] [ 0 0 1 1] [ 0 -1 -1 -1] sage: x*g (1, 1, 1, 0) sage: (x*g).parent()==A4 True sage: (g*x).parent() Vector space of dimension 4 over Rational Field sage: y = A4.discriminant_group().an_element() sage: y*g (1) If the group is finite we can compute the usual things:: sage: Aut.order() 240 sage: conj = Aut.conjugacy_classes_representatives() sage: len(conj) 14 sage: Aut.structure_description() 'C2 x S5' The lattice can live in a larger ambient space:: sage: A2 = IntegralLattice(matrix.identity(3),Matrix(ZZ,2,3,[1,-1,0,0,1,-1])) sage: A2.orthogonal_group() Group of isometries with 3 generators ( [-1/3 2/3 2/3] [ 2/3 2/3 -1/3] [1 0 0] [ 2/3 -1/3 2/3] [ 2/3 -1/3 2/3] [0 0 1] [ 2/3 2/3 -1/3], [-1/3 2/3 2/3], [0 1 0] ) It can be negative definite as well:: sage: A2m = IntegralLattice(-Matrix(ZZ,2,[2,1,1,2])) sage: G = A2m.orthogonal_group() sage: G.order() 12 If the lattice is indefinite, sage does not know how to compute generators. Can you teach it?:: sage: U = IntegralLattice(Matrix(ZZ,2,[0,1,1,0])) sage: U.orthogonal_group() Traceback (most recent call last): ... NotImplementedError: currently, we can only compute generators for orthogonal groups over definite lattices. But we can define subgroups:: sage: S = IntegralLattice(Matrix(ZZ,2,[2, 3, 3, 2])) sage: f = Matrix(ZZ,2,[0,1,-1,3]) sage: S.orthogonal_group([f]) Group of isometries with 1 generator ( [ 0 1] [-1 3] ) TESTS: We can handle the trivial group:: sage: S = IntegralLattice(Matrix(ZZ,2,[2, 3, 3, 2])) sage: S.orthogonal_group([]) Group of isometries with 1 generator ( [1 0] [0 1] ) """ from sage.categories.groups import Groups from sage.groups.matrix_gps.isometries import GroupOfIsometries sig = self.signature_pair() if gens is None: gens = [] if sig[1] == 0 or sig[0] == 0: # definite from sage.quadratic_forms.quadratic_form import QuadraticForm is_finite = True # Compute transformation matrix to the ambient module. L = self.overlattice(self.ambient_module().gens()) Orthogonal = L.orthogonal_complement(self) B = self.basis_matrix().stack(Orthogonal.basis_matrix()) if sig[0] == 0: # negative definite q = QuadraticForm(ZZ, -2 * self.gram_matrix()) else: # positive definite q = QuadraticForm(ZZ, 2 * self.gram_matrix()) identity = matrix.identity(Orthogonal.rank()) for g in q.automorphism_group().gens(): g = g.matrix().T # We continue g as identity on the orthogonal complement. g = matrix.block_diagonal([g, identity]) g = B.inverse() * g * B gens.append(g) else: # indefinite raise NotImplementedError( "currently, we can only compute generators " "for orthogonal groups over definite lattices.") deg = self.degree() base = self.ambient_vector_space().base_ring() inv_bil = self.inner_product_matrix() if is_finite: cat = Groups().Finite() else: cat = Groups() D = self.discriminant_group() G = GroupOfIsometries(deg, base, gens, inv_bil, category=cat, invariant_submodule=self, invariant_quotient_module=D) return G
def random_quadraticform(R, n, rand_arg_list=[]): """ Create a random quadratic form in `n` variables defined over the ring `R`. The last (and optional) argument ``rand_arg_list`` is a list of at most 3 elements which is passed (as at most 3 separate variables) into the method ``R.random_element()``. INPUT: - `R` -- a ring. - `n` -- an integer `\ge 0` - ``rand_arg_list`` -- a list of at most 3 arguments which can be taken by ``R.random_element()``. OUTPUT: A quadratic form over the ring `R`. EXAMPLES:: sage: random_quadraticform(ZZ, 3, [1,5]) ## RANDOM Quadratic form in 3 variables over Integer Ring with coefficients: [ 3 2 3 ] [ * 1 4 ] [ * * 3 ] :: sage: random_quadraticform(ZZ, 3, [-5,5]) ## RANDOM Quadratic form in 3 variables over Integer Ring with coefficients: [ 3 2 -5 ] [ * 2 -2 ] [ * * -5 ] :: sage: random_quadraticform(ZZ, 3, [-50,50]) ## RANDOM Quadratic form in 3 variables over Integer Ring with coefficients: [ 1 8 -23 ] [ * 0 0 ] [ * * 6 ] """ ## Sanity Checks: We have a ring and there are at most 3 parameters for randomness! if len(rand_arg_list) > 3: raise TypeError( "Oops! The list of randomness arguments can have at most 3 elements." ) if not is_Ring(R): raise TypeError("Oops! The first argument must be a ring.") ## Create a list of upper-triangular entries for the quadratic form L = len(rand_arg_list) nn = int(n * (n + 1) / 2) if L == 0: rand_list = [R.random_element() for _ in range(nn)] elif L == 1: rand_list = [R.random_element(rand_arg_list[0]) for _ in range(nn)] elif L == 2: rand_list = [ R.random_element(rand_arg_list[0], rand_arg_list[1]) for _ in range(nn) ] elif L == 3: rand_list = [ R.random_element(rand_arg_list[0], rand_arg_list[1], rand_arg_list[2]) for _ in range(nn) ] ## Return the Quadratic Form return QuadraticForm(R, n, rand_list)
def minkowski_reduction(self): """ Find a Minkowski-reduced form equivalent to the given one. This means that .. MATH:: Q(v_k) <= Q(s_1 * v_1 + ... + s_n * v_n) for all `s_i` where GCD`(s_k, ... s_n) = 1`. Note: When Q has dim <= 4 we can take all `s_i` in {1, 0, -1}. References: Schulze-Pillot's paper on "An algorithm for computing genera of ternary and quaternary quadratic forms", p138. Donaldson's 1979 paper "Minkowski Reduction of Integral Matrices", p203. EXAMPLES:: sage: Q = QuadraticForm(ZZ,4,[30, 17, 11, 12, 29, 25, 62, 64, 25, 110]) sage: Q Quadratic form in 4 variables over Integer Ring with coefficients: [ 30 17 11 12 ] [ * 29 25 62 ] [ * * 64 25 ] [ * * * 110 ] sage: Q.minkowski_reduction() ( Quadratic form in 4 variables over Integer Ring with coefficients: [ 30 17 11 -5 ] [ * 29 25 4 ] [ * * 64 0 ] [ * * * 77 ] , <BLANKLINE> [ 1 0 0 0] [ 0 1 0 -1] [ 0 0 1 0] [ 0 0 0 1] ) :: sage: Q=QuadraticForm(ZZ,4,[1, -2, 0, 0, 2, 0, 0, 2, 0, 2]) sage: Q Quadratic form in 4 variables over Integer Ring with coefficients: [ 1 -2 0 0 ] [ * 2 0 0 ] [ * * 2 0 ] [ * * * 2 ] sage: Q.minkowski_reduction() ( Quadratic form in 4 variables over Integer Ring with coefficients: [ 1 0 0 0 ] [ * 1 0 0 ] [ * * 2 0 ] [ * * * 2 ] , <BLANKLINE> [1 1 0 0] [0 1 0 0] [0 0 1 0] [0 0 0 1] ) :: sage: Q=QuadraticForm(ZZ,5,[2,2,0,0,0,2,2,0,0,2,2,0,2,2,2]) sage: Q.Gram_matrix() [2 1 0 0 0] [1 2 1 0 0] [0 1 2 1 0] [0 0 1 2 1] [0 0 0 1 2] sage: Q.minkowski_reduction() Traceback (most recent call last): ... NotImplementedError: This algorithm is only for dimensions less than 5 """ from sage.quadratic_forms.quadratic_form import QuadraticForm from sage.quadratic_forms.quadratic_form import matrix if not self.is_positive_definite(): raise TypeError( "Minkowksi reduction only works for positive definite forms") if self.dim() > 4: raise NotImplementedError( "This algorithm is only for dimensions less than 5") R = self.base_ring() n = self.dim() Q = deepcopy(self) M = matrix(R, n, n) for i in range(n): M[i, i] = 1 ## Begin the reduction done_flag = False while not done_flag: ## Loop through possible shorted vectors until done_flag = True #print " j_range = ", range(n-1, -1, -1) for j in range(n - 1, -1, -1): for a_first in mrange([3 for i in range(j)]): y = [x - 1 for x in a_first] + [1] + [0 for k in range(n - 1 - j)] e_j = [0 for k in range(n)] e_j[j] = 1 #print "j = ", j ## Reduce if a shorter vector is found #print "y = ", y, " e_j = ", e_j, "\n" if Q(y) < Q(e_j): ## Create the transformation matrix M_new = matrix(R, n, n) for k in range(n): M_new[k, k] = 1 for k in range(n): M_new[k, j] = y[k] ## Perform the reduction and restart the loop #print "Q_before = ", Q Q = QuadraticForm(M_new.transpose() * Q.matrix() * M_new) M = M * M_new done_flag = False ## DIAGNOSTIC #print "Q(y) = ", Q(y) #print "Q(e_j) = ", Q(e_j) #print "M_new = ", M_new #print "Q_after = ", Q #print if not done_flag: break if not done_flag: break ## Return the results return Q, M
def orthogonal_group(self, gens=None, is_finite=None): """ Return the orthogonal group of this lattice as a matrix group. The elements are isometries of the ambient vector space which preserve this lattice. They are represented by matrices with respect to the standard basis. INPUT: - ``gens`` -- a list of matrices (default:``None``) - ``is_finite`` -- bool (default: ``None``) If set to ``True``, then the group is placed in the category of finite groups. Sage does not check this. OUTPUT: The matrix group generated by ``gens``. If ``gens`` is not specified, then generators of the full orthogonal group of this lattice are computed. They are continued as the identity on the orthogonal complement of the lattice in its ambient space. Currently, we can only compute the orthogonal group for positive definite lattices. EXAMPLES:: sage: A4 = IntegralLattice("A4") sage: Aut = A4.orthogonal_group() sage: Aut Group of isometries with 5 generators ( [-1 0 0 0] [0 0 0 1] [-1 -1 -1 0] [ 1 0 0 0] [ 1 0 0 0] [ 0 -1 0 0] [0 0 1 0] [ 0 0 0 -1] [-1 -1 -1 -1] [ 0 1 0 0] [ 0 0 -1 0] [0 1 0 0] [ 0 0 1 1] [ 0 0 0 1] [ 0 0 1 1] [ 0 0 0 -1], [1 0 0 0], [ 0 1 0 0], [ 0 0 1 0], [ 0 0 0 -1] ) The group acts from the right on the lattice and its discriminant group:: sage: x = A4.an_element() sage: g = Aut.an_element() sage: g [ 1 1 1 0] [ 0 0 -1 0] [ 0 0 1 1] [ 0 -1 -1 -1] sage: x*g (1, 1, 1, 0) sage: (x*g).parent()==A4 True sage: (g*x).parent() Vector space of dimension 4 over Rational Field sage: y = A4.discriminant_group().an_element() sage: y*g (1) If the group is finite we can compute the usual things:: sage: Aut.order() 240 sage: conj = Aut.conjugacy_classes_representatives() sage: len(conj) 14 sage: Aut.structure_description() # optional - database_gap 'C2 x S5' The lattice can live in a larger ambient space:: sage: A2 = IntegralLattice(matrix.identity(3),Matrix(ZZ,2,3,[1,-1,0,0,1,-1])) sage: A2.orthogonal_group() Group of isometries with 3 generators ( [-1/3 2/3 2/3] [ 2/3 2/3 -1/3] [1 0 0] [ 2/3 -1/3 2/3] [ 2/3 -1/3 2/3] [0 0 1] [ 2/3 2/3 -1/3], [-1/3 2/3 2/3], [0 1 0] ) It can be negative definite as well:: sage: A2m = IntegralLattice(-Matrix(ZZ,2,[2,1,1,2])) sage: G = A2m.orthogonal_group() sage: G.order() 12 If the lattice is indefinite, sage does not know how to compute generators. Can you teach it?:: sage: U = IntegralLattice(Matrix(ZZ,2,[0,1,1,0])) sage: U.orthogonal_group() Traceback (most recent call last): ... NotImplementedError: currently, we can only compute generators for orthogonal groups over definite lattices. But we can define subgroups:: sage: S = IntegralLattice(Matrix(ZZ,2,[2, 3, 3, 2])) sage: f = Matrix(ZZ,2,[0,1,-1,3]) sage: S.orthogonal_group([f]) Group of isometries with 1 generator ( [ 0 1] [-1 3] ) TESTS: We can handle the trivial group:: sage: S = IntegralLattice(Matrix(ZZ,2,[2, 3, 3, 2])) sage: S.orthogonal_group([]) Group of isometries with 1 generator ( [1 0] [0 1] ) """ from sage.categories.groups import Groups from sage.groups.matrix_gps.isometries import GroupOfIsometries sig = self.signature_pair() if gens is None: gens = [] if sig[1]==0 or sig[0]==0: #definite from sage.quadratic_forms.quadratic_form import QuadraticForm is_finite = True # Compute transformation matrix to the ambient module. L = self.overlattice(self.ambient_module().gens()) Orthogonal = L.orthogonal_complement(self) B = self.basis_matrix().stack(Orthogonal.basis_matrix()) if sig[0] == 0: #negative definite q = QuadraticForm(ZZ, -2*self.gram_matrix()) else: # positve definite q = QuadraticForm(ZZ, 2*self.gram_matrix()) identity = matrix.identity(Orthogonal.rank()) for g in q.automorphism_group().gens(): g = g.matrix().T # We continue g as identity on the orthogonal complement. g = matrix.block_diagonal([g, identity]) g = B.inverse()*g*B gens.append(g) else: #indefinite raise NotImplementedError( "currently, we can only compute generators " "for orthogonal groups over definite lattices.") deg = self.degree() base = self.ambient_vector_space().base_ring() inv_bil = self.inner_product_matrix() if is_finite: cat = Groups().Finite() else: cat = Groups() D = self.discriminant_group() G = GroupOfIsometries(deg, base, gens, inv_bil, category=cat, invariant_submodule=self, invariant_quotient_module=D) return G
def weight_two_basis_from_theta_blocks(N, prec, dim, jacobiforms=None, verbose=False): r""" Look for theta blocks of weight two and given index among the infinite families of weight two theta blocks associated to the root systems A_4, B_2+G_2, A_1+B_3, A_1+C_3 This is not meant to be called directly. Use JacobiForms(N).basis(2) with the optional parameter try_theta_blocks = True. INPUT: - ``N`` -- the index - ``prec`` -- precision - ``dim`` -- the dimension of the space of Jacobi forms. - ``jacobiforms`` -- a JacobiForms instance for this index. (If none is provided then we construct one now.) - ``verbose`` -- boolean (default False); if True then we comment on the computations. OUTPUT: a list of JacobiForm's """ if not jacobiforms: jacobiforms = JacobiForms(N) rank = 0 #the four families of weight two theta blocks from root lattices thetablockQ_1 = QuadraticForm( matrix([[4, 3, 2, 1], [3, 6, 4, 2], [2, 4, 6, 3], [1, 2, 3, 4]])) thetablockQ_2 = QuadraticForm( matrix([[24, 12, 0, 0], [12, 8, 0, 0], [0, 0, 12, 6], [0, 0, 6, 6]])) thetablockQ_3 = QuadraticForm( matrix([[4, 0, 0, 0], [0, 20, 10, 20], [0, 10, 10, 20], [0, 20, 20, 60]])) thetablockQ_4 = QuadraticForm( matrix([[4, 0, 0, 0], [0, 8, 8, 4], [0, 8, 16, 8], [0, 4, 8, 6]])) thetablock_tuple = thetablockQ_1, thetablockQ_2, thetablockQ_3, thetablockQ_4 from .jacobi_forms_class import theta_block thetablock_1 = lambda a, b, c, d, prec: theta_block( [a, a + b, a + b + c, a + b + c + d, b, b + c, b + c + d, c, c + d, d], -6, prec, jacobiforms=jacobiforms) thetablock_2 = lambda a, b, c, d, prec: theta_block([ a, 3 * a + b, 3 * a + b + b, a + a + b, a + b, b, c + c, c + c + d, 2 * (c + d), d ], -6, prec, jacobiforms=jacobiforms ) thetablock_3 = lambda a, b, c, d, prec: theta_block([ a + a, b + b, b + b + c, 2 * (b + c + d + d), b + b + c + d + d, b + b + c + 4 * d, c, c + d + d, c + 4 * d, d + d ], -6, prec, jacobiforms=jacobiforms ) thetablock_4 = lambda a, b, c, d, prec: theta_block([ a + a, b, b + b + c + c + d, b + c, b + c + c + d, b + c + d, c, c + c + d, c + d, d ], -6, prec, jacobiforms=jacobiforms ) thetablocks = thetablock_1, thetablock_2, thetablock_3, thetablock_4 basis = [] basis_vectors = [] pivots = [] div_N = divisors(N) div_N.reverse() for i, Q in enumerate(thetablock_tuple): v_list = Q.short_vector_list_up_to_length(N + 1, up_to_sign_flag=True) if verbose: print( 'I am looking through the theta block family of the root system %s.' % ['A_4', 'B_2+G_2', 'A_1+B_3', 'A_1+C_3'][i]) for _d in div_N: prec_d = prec * (N // _d) for v in v_list[_d]: a, b, c, d = v try: j = thetablocks[i](a, b, c, d, prec_d).hecke_V(N // _d) old_rank = 0 + rank basis, basis_vectors, pivots, rank = update_echelon_form_with( j, basis, basis_vectors, pivots, rank, sage_one_eighth) if verbose and old_rank < rank: if i == 0: L = [ abs(x) for x in (a, a + b, a + b + c, a + b + c + d, b, b + c, b + c + d, c, c + d, d) ] elif i == 1: L = [ abs(x) for x in (a, 3 * a + b, 3 * a + b + b, a + a + b, a + b, b, c + c, c + c + d, 2 * (c + d), d) ] elif i == 2: L = [ abs(x) for x in (a + a, b + b, b + b + c, 2 * (b + c + d + d), b + b + c + d + d, b + b + c + 4 * d, c, c + d + d, c + 4 * d, d + d) ] else: L = [ abs(x) for x in (a + a, b, b + b + c + c + d, b + c, b + c + c + d, b + c + d, c, c + c + d, c + d, d) ] L.sort() print('I found the theta block Th_' + str(L) + ' / eta^6.') if rank == dim: return basis except ValueError: pass if verbose: print( 'I did not find enough theta blocks. Time to try something else.') return jacobiforms.basis(2, prec, try_theta_blocks=False, verbose=verbose)
def weight_three_basis_from_theta_blocks(N, prec, dim, jacobiforms=None, verbose=False): r""" Look for theta blocks of weight three and given index among the infinite families of weight three theta blocks associated to the root systems B_3, C_3, A_2+A_3, 3A_2, 2A_1 + A_2 + B_2, 3A_1 + A_3, A_6, A_1+D_5. This is not meant to be called directly. Use JacobiForms(N).basis(3) with the optional parameter try_theta_blocks = True. INPUT: - ``N`` -- the index - ``prec`` -- precision - ``dim`` -- the dimension of the space of Jacobi forms. - ``jacobiforms`` -- a JacobiForms instance for this index. (If none is provided then we construct one now.) - ``verbose`` -- boolean (default False); if True then we comment on the computations. OUTPUT: a list of JacobiForm's """ if not jacobiforms: jacobiforms = JacobiForms(N) rank = 0 thetablockQ_1 = QuadraticForm( matrix([[20, 10, 20], [10, 10, 20], [20, 20, 60]])) #B3 thetablockQ_2 = QuadraticForm(matrix([[8, 8, 4], [8, 16, 8], [4, 8, 6]])) #C3 thetablockQ_3 = QuadraticForm( matrix([[2, 1, 0, 0, 0], [1, 2, 0, 0, 0], [0, 0, 12, 4, 4], [0, 0, 4, 4, 4], [0, 0, 4, 4, 12]])) #A2 + A3 thetablockQ_4 = QuadraticForm( matrix([[2, 1, 0, 0, 0, 0], [1, 2, 0, 0, 0, 0], [0, 0, 2, 1, 0, 0], [0, 0, 1, 2, 0, 0], [0, 0, 0, 0, 2, 1], [0, 0, 0, 0, 1, 2]])) #3 A2 thetablockQ_5 = QuadraticForm( matrix([[4, 0, 0, 0, 0, 0], [0, 4, 0, 0, 0, 0], [0, 0, 2, 1, 0, 0], [0, 0, 1, 2, 0, 0], [0, 0, 0, 0, 6, 6], [0, 0, 0, 0, 6, 12]])) #2A1 + A2 + B2 thetablockQ_6 = QuadraticForm( matrix([[4, 0, 0, 0, 0, 0], [0, 4, 0, 0, 0, 0], [0, 0, 4, 0, 0, 0], [0, 0, 0, 12, 4, 4], [0, 0, 0, 4, 4, 4], [0, 0, 0, 4, 4, 12]])) #3A1 + A3 thetablockQ_7 = QuadraticForm( matrix([[6, 5, 4, 3, 2, 1], [5, 10, 8, 6, 4, 2], [4, 8, 12, 9, 6, 3], [3, 6, 9, 12, 8, 4], [2, 4, 6, 8, 10, 5], [1, 2, 3, 4, 5, 6]])) #A6 thetablockQ_8 = QuadraticForm( matrix([[4, 0, 0, 0, 0, 0], [0, 10, 6, 12, 8, 4], [0, 6, 10, 12, 8, 4], [0, 12, 12, 24, 16, 8], [0, 8, 8, 16, 16, 8], [0, 4, 4, 8, 8, 8]])) #A1 + D5 thetablock_tuple = thetablockQ_1, thetablockQ_2, thetablockQ_3, thetablockQ_4, thetablockQ_5, thetablockQ_6, thetablockQ_7, thetablockQ_8 args1 = lambda b, c, d: [ b + b, b + b + c, 2 * (b + c + d + d), b + b + c + d + d, b + b + c + 4 * d, c, c + d + d, c + 4 * d, d + d ] args2 = lambda b, c, d: [ b, b + b + c + c + d, b + c, b + c + c + d, b + c + d, c, c + c + d, c + d, d ] args3 = lambda a, b, d, e, f: [ a, a + b, b, d + d, e, d + d + e, f + f, e + f + f, d + d + e + f + f ] args4 = lambda a, b, c, d, e, f: [a, a + b, b, c, c + d, d, e, e + f, f] args5 = lambda a, b, c, d, e, f: [ a + a, b + b, c, c + d, d, e, f + f, e + f + f, e + e + f + f ] args6 = lambda a, b, c, d, e, f: [ a + a, b + b, c + c, d + d, e, d + d + e, f + f, e + f + f, d + d + e + f + f ] args7 = lambda a, b, c, d, e, f: [ a, a + b, a + b + c, a + b + c + d, a + b + c + d + e, a + b + c + d + e + f, b, b + c, b + c + d, b + c + d + e, b + c + d + e + f, c, c + d, c + d + e, c + d + e + f, d, d + e, d + e + f, e, e + f, f ] args8 = lambda a, b, c, d, e, f: [ a + a, b, c, d, b + d, c + d, b + c + d, e, d + e, b + d + e, c + d + e, b + c + d + e, b + c + 2 * d + e, f, e + f, d + e + f, b + d + e + f, c + d + e + f, b + c + d + e + f, b + c + 2 * d + e + f, b + c + 2 * d + 2 * e + f ] args_tuple = args1, args2, args3, args4, args5, args6, args7, args8 from .jacobi_forms_class import theta_block thetablock_1 = lambda b, c, d, prec: theta_block( args1(b, c, d), -3, prec, jacobiforms=jacobiforms) thetablock_2 = lambda b, c, d, prec: theta_block( args2(b, c, d), -3, prec, jacobiforms=jacobiforms) thetablock_3 = lambda a, b, d, e, f, prec: theta_block( args3(a, b, d, e, f), -3, prec, jacobiforms=jacobiforms) thetablock_4 = lambda a, b, c, d, e, f, prec: theta_block( args4(a, b, c, d, e, f), -3, prec, jacobiforms=jacobiforms) thetablock_5 = lambda a, b, c, d, e, f, prec: theta_block( args5(a, b, c, d, e, f), -3, prec, jacobiforms=jacobiforms) thetablock_6 = lambda a, b, c, d, e, f, prec: theta_block( args6(a, b, c, d, e, f), -3, prec, jacobiforms=jacobiforms) thetablock_7 = lambda a, b, c, d, e, f, prec: theta_block( args7(a, b, c, d, e, f), -15, prec, jacobiforms=jacobiforms) thetablock_8 = lambda a, b, c, d, e, f, prec: theta_block( args8(a, b, c, d, e, f), -15, prec, jacobiforms=jacobiforms) thetablocks = thetablock_1, thetablock_2, thetablock_3, thetablock_4, thetablock_5, thetablock_6, thetablock_7, thetablock_8 basis = [] basis_vectors = [] pivots = [] div_N = divisors(N) div_N.reverse() for i, Q in enumerate(thetablock_tuple): v_list = Q.short_vector_list_up_to_length(N + 1, up_to_sign_flag=True) if verbose: print( 'I am looking through the theta block family of the root system %s.' % [ 'B_3', 'C_3', 'A_2+A_3', '3A_2', '2A_1+A_2+B_2', '3A_1 + A_3', 'A_6', 'A_1+D_5' ][i]) for _d in div_N: prec_d = prec * (N // _d) for v in v_list[_d]: if all(a for a in args_tuple[i](*v)): try: j = thetablocks[i](*(list(v) + [prec])).hecke_V(N // _d) old_rank = 0 + rank basis, basis_vectors, pivots, rank = update_echelon_form_with( j, basis, basis_vectors, pivots, rank, sage_one_eighth) if verbose and old_rank < rank: L = [abs(x) for x in args_tuple[i](*v)] L.sort() if _d == N: print('I found the theta block Th_' + str(L) + [' / eta^3.', ' / eta^15.'][i >= 6]) else: print( 'I applied the Hecke operator V_%d to the theta block Th_' % (N // _d) + str(L) + [' / eta^3.', ' / eta^15.'][i >= 6]) if rank == dim: return basis except ValueError: pass if verbose: print( 'I did not find enough theta blocks. Time to try something else.') return jacobiforms.basis(2, prec, try_theta_blocks=False, verbose=verbose)