def clifford_invariant(self, p): """ This is the Clifford invariant, i.e. the class in the Brauer group of the Clifford algebra for even dimension, of the even Clifford Algebra for odd dimension. See Lam (AMS GSM 67) p. 117 for the definition, and p. 119 for the formula relating it to the Hasse invariant. EXAMPLES: For hyperbolic spaces, the clifford invariant is +1:: sage: H = QuadraticForm(ZZ, 2, [0, 1, 0]) sage: H.clifford_invariant(2) 1 sage: (H + H).clifford_invariant(2) 1 sage: (H + H + H).clifford_invariant(2) 1 sage: (H + H + H + H).clifford_invariant(2) 1 """ n = self.dim() % 8 if n == 1 or n == 2: s = 1 elif n == 3 or n == 4: s = hilbert_symbol(-1, -self.disc(), p) elif n == 5 or n == 6: s = hilbert_symbol(-1, -1, p) elif n == 7 or n == 0: s = hilbert_symbol(-1, self.disc(), p) return s * self.hasse_invariant(p)
def is_locally_solvable(self, p) -> bool: r""" Return ``True`` if and only if ``self`` has a solution over the `p`-adic numbers. Here `p` is a prime number or equals `-1`, infinity, or `\RR` to denote the infinite place. EXAMPLES:: sage: C = Conic(QQ, [1,2,3]) sage: C.is_locally_solvable(-1) False sage: C.is_locally_solvable(2) False sage: C.is_locally_solvable(3) True sage: C.is_locally_solvable(QQ.hom(RR)) False sage: D = Conic(QQ, [1, 2, -3]) sage: D.is_locally_solvable(infinity) True sage: D.is_locally_solvable(RR) True """ from sage.categories.map import Map from sage.categories.all import Rings D, T = self.diagonal_matrix() abc = [D[j, j] for j in range(3)] if abc[2] == 0: return True a = -abc[0] / abc[2] b = -abc[1] / abc[2] if isinstance(p, sage.rings.abc.RealField) or isinstance( p, InfinityElement): p = -1 elif isinstance(p, Map) and p.category_for().is_subcategory(Rings()): # p is a morphism of Rings if p.domain() is QQ and isinstance(p.codomain(), sage.rings.abc.RealField): p = -1 else: raise TypeError("p (=%s) needs to be a prime of base field " "B ( =`QQ`) in is_locally_solvable" % p) if hilbert_symbol(a, b, p) == -1: if self._local_obstruction is None: self._local_obstruction = p return False return True
def is_locally_solvable(self, p): r""" Returns True if and only if ``self`` has a solution over the `p`-adic numbers. Here `p` is a prime number or equals `-1`, infinity, or `\RR` to denote the infinite place. EXAMPLES:: sage: C = Conic(QQ, [1,2,3]) sage: C.is_locally_solvable(-1) False sage: C.is_locally_solvable(2) False sage: C.is_locally_solvable(3) True sage: C.is_locally_solvable(QQ.hom(RR)) False sage: D = Conic(QQ, [1, 2, -3]) sage: D.is_locally_solvable(infinity) True sage: D.is_locally_solvable(RR) True """ from sage.categories.map import Map from sage.categories.all import Rings D, T = self.diagonal_matrix() abc = [D[j, j] for j in range(3)] if abc[2] == 0: return True a = -abc[0]/abc[2] b = -abc[1]/abc[2] if is_RealField(p) or is_InfinityElement(p): p = -1 elif isinstance(p, Map) and p.category_for().is_subcategory(Rings()): # p is a morphism of Rings if p.domain() is QQ and is_RealField(p.codomain()): p = -1 else: raise TypeError("p (=%s) needs to be a prime of base field " \ "B ( =`QQ`) in is_locally_solvable" % p) if hilbert_symbol(a, b, p) == -1: if self._local_obstruction is None: self._local_obstruction = p return False return True
def is_locally_solvable(self, p): r""" Returns True if and only if ``self`` has a solution over the `p`-adic numbers. Here `p` is a prime number or equals `-1`, infinity, or `\RR` to denote the infinite place. EXAMPLES:: sage: C = Conic(QQ, [1,2,3]) sage: C.is_locally_solvable(-1) False sage: C.is_locally_solvable(2) False sage: C.is_locally_solvable(3) True sage: C.is_locally_solvable(QQ.hom(RR)) False sage: D = Conic(QQ, [1, 2, -3]) sage: D.is_locally_solvable(infinity) True sage: D.is_locally_solvable(RR) True """ D, T = self.diagonal_matrix() abc = [D[j, j] for j in range(3)] if abc[2] == 0: return True a = -abc[0] / abc[2] b = -abc[1] / abc[2] if is_RealField(p) or is_InfinityElement(p): p = -1 elif is_RingHomomorphism(p): if p.domain() is QQ and is_RealField(p.codomain()): p = -1 else: raise TypeError("p (=%s) needs to be a prime of base field " \ "B ( =`QQ`) in is_locally_solvable" % p) if hilbert_symbol(a, b, p) == -1: if self._local_obstruction is None: self._local_obstruction = p return False return True
def has_equivalent_Jordan_decomposition_at_prime(self, other, p): """ Determines if the given quadratic form has a Jordan decomposition equivalent to that of self. INPUT: a QuadraticForm OUTPUT: boolean EXAMPLES:: sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 1, 0, 3]) sage: Q2 = QuadraticForm(ZZ, 3, [1, 0, 0, 2, -2, 6]) sage: Q3 = QuadraticForm(ZZ, 3, [1, 0, 0, 1, 0, 11]) sage: [Q1.level(), Q2.level(), Q3.level()] [44, 44, 44] sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,2) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,11) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,2) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,11) True sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,2) True sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,11) False """ ## Sanity Checks #if not isinstance(other, QuadraticForm): if not isinstance(other, type(self)): raise TypeError("Oops! The first argument must be of type QuadraticForm.") if not is_prime(p): raise TypeError("Oops! The second argument must be a prime number.") ## Get the relevant local normal forms quickly self_jordan = self.jordan_blocks_by_scale_and_unimodular(p, safe_flag= False) other_jordan = other.jordan_blocks_by_scale_and_unimodular(p, safe_flag=False) ## Check for the same number of Jordan components if len(self_jordan) != len(other_jordan): return False ## Deal with odd primes: Check that the Jordan component scales, dimensions, and discriminants are the same if p != 2: for i in range(len(self_jordan)): if (self_jordan[i][0] != other_jordan[i][0]) \ or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \ or (legendre_symbol(self_jordan[i][1].det() * other_jordan[i][1].det(), p) != 1): return False ## All tests passed for an odd prime. return True ## For p = 2: Check that all Jordan Invariants are the same. elif p == 2: ## Useful definition t = len(self_jordan) ## Define t = Number of Jordan components ## Check that all Jordan Invariants are the same (scale, dim, and norm) for i in range(t): if (self_jordan[i][0] != other_jordan[i][0]) \ or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \ or (valuation(GCD(self_jordan[i][1].coefficients()), p) != valuation(GCD(other_jordan[i][1].coefficients()), p)): return False ## Use O'Meara's isometry test 93:29 on p277. ## ------------------------------------------ ## List of norms, scales, and dimensions for each i scale_list = [ZZ(2)**self_jordan[i][0] for i in range(t)] norm_list = [ZZ(2)**(self_jordan[i][0] + valuation(GCD(self_jordan[i][1].coefficients()), 2)) for i in range(t)] dim_list = [(self_jordan[i][1].dim()) for i in range(t)] ## List of Hessian determinants and Hasse invariants for each Jordan (sub)chain ## (Note: This is not the same as O'Meara's Gram determinants, but ratios are the same!) -- NOT SO GOOD... ## But it matters in condition (ii), so we multiply all by 2 (instead of dividing by 2 since only square-factors matter, and it's easier.) j = 0 self_chain_det_list = [ self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])] other_chain_det_list = [ other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])] self_hasse_chain_list = [ self_jordan[j][1].scale_by_factor(ZZ(2)**self_jordan[j][0]).hasse_invariant__OMeara(2) ] other_hasse_chain_list = [ other_jordan[j][1].scale_by_factor(ZZ(2)**other_jordan[j][0]).hasse_invariant__OMeara(2) ] for j in range(1, t): self_chain_det_list.append(self_chain_det_list[j-1] * self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])) other_chain_det_list.append(other_chain_det_list[j-1] * other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])) self_hasse_chain_list.append(self_hasse_chain_list[j-1] \ * hilbert_symbol(self_chain_det_list[j-1], self_jordan[j][1].Gram_det(), 2) \ * self_jordan[j][1].hasse_invariant__OMeara(2)) other_hasse_chain_list.append(other_hasse_chain_list[j-1] \ * hilbert_symbol(other_chain_det_list[j-1], other_jordan[j][1].Gram_det(), 2) \ * other_jordan[j][1].hasse_invariant__OMeara(2)) ## SANITY CHECK -- check that the scale powers are strictly increasing for i in range(1, len(scale_list)): if scale_list[i-1] >= scale_list[i]: raise RuntimeError("Oops! There is something wrong with the Jordan Decomposition -- the given scales are not strictly increasing!") ## Test O'Meara's two conditions for i in range(t-1): ## Condition (i): Check that their (unit) ratio is a square (but it suffices to check at most mod 8). modulus = norm_list[i] * norm_list[i+1] / (scale_list[i] ** 2) if modulus > 8: modulus = 8 if (modulus > 1) and (((self_chain_det_list[i] / other_chain_det_list[i]) % modulus) != 1): return False ## Check O'Meara's condition (ii) when appropriate if norm_list[i+1] % (4 * norm_list[i]) == 0: if self_hasse_chain_list[i] * hilbert_symbol(norm_list[i] * other_chain_det_list[i], -self_chain_det_list[i], 2) \ != other_hasse_chain_list[i] * hilbert_symbol(norm_list[i], -other_chain_det_list[i], 2): ## Nipp conditions return False ## All tests passed for the prime 2. return True else: raise TypeError("Oops! This should not have happened.")
def is_anisotropic(self, p): r""" Check if the quadratic form is anisotropic over the p-adic numbers `\QQ_p` or `\RR`. INPUT: - `p` -- a prime number > 0 or `-1` for the infinite place OUTPUT: boolean EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1]) sage: Q.is_anisotropic(2) True sage: Q.is_anisotropic(3) True sage: Q.is_anisotropic(5) False :: sage: Q = DiagonalQuadraticForm(ZZ, [1,-1]) sage: Q.is_anisotropic(2) False sage: Q.is_anisotropic(3) False sage: Q.is_anisotropic(5) False :: sage: [DiagonalQuadraticForm(ZZ, [1, -least_quadratic_nonresidue(p)]).is_anisotropic(p) for p in prime_range(3, 30)] [True, True, True, True, True, True, True, True, True] :: sage: [DiagonalQuadraticForm(ZZ, [1, -least_quadratic_nonresidue(p), p, -p*least_quadratic_nonresidue(p)]).is_anisotropic(p) for p in prime_range(3, 30)] [True, True, True, True, True, True, True, True, True] """ ## TO DO: Should check that p is prime if p == -1: return self.is_definite() n = self.dim() D = self.det() if n >= 5: return False if n == 4: return (QQ(D).is_padic_square(p) and (self.hasse_invariant(p) == -hilbert_symbol(-1, -1, p))) if n == 3: return self.hasse_invariant(p) != hilbert_symbol(-1, -D, p) if n == 2: return not QQ(-D).is_padic_square(p) if n == 1: return self[0, 0] != 0 raise NotImplementedError( "Oops! We haven't established a convention for 0-dim'l quadratic forms... =(" )
def hasse_invariant__OMeara(self, p): r""" Compute the O'Meara Hasse invariant at a prime `p`. This is defined on p167 of O'Meara's book. If Q is diagonal with coefficients `a_i`, then the (Cassels) Hasse invariant is given by .. MATH:: c_p = \prod_{i <= j} (a_i, a_j)_p where `(a,b)_p` is the Hilbert symbol at `p`. .. WARNING:: This is different from the (Cassels) Hasse invariant, which only allows `i < j` in the product. That is given by the method hasse_invariant(p). INPUT: - `p` -- a prime number > 0 or `-1` for the infinite place OUTPUT: 1 or -1 EXAMPLES:: sage: Q = QuadraticForm(ZZ, 2, [1,2,3]) sage: Q.rational_diagonal_form() Quadratic form in 2 variables over Rational Field with coefficients: [ 1 0 ] [ * 2 ] sage: [Q.hasse_invariant(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] :: sage: Q = DiagonalQuadraticForm(ZZ, [1,-1]) sage: [Q.hasse_invariant(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)] [-1, 1, 1, 1, 1, 1, 1, 1] :: sage: Q = DiagonalQuadraticForm(ZZ,[1,-1,-1]) sage: [Q.hasse_invariant(p) for p in prime_range(20)] [-1, 1, 1, 1, 1, 1, 1, 1] sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)] [-1, 1, 1, 1, 1, 1, 1, 1] :: sage: K.<a>=NumberField(x^2-23) sage: Q = DiagonalQuadraticForm(K,[-a,a+2]) sage: [Q.hasse_invariant__OMeara(p) for p in K.primes_above(19)] [1, 1] """ ## TO DO: Need to deal with the case n=1 separately somewhere! Diag = self.rational_diagonal_form() R = Diag.base_ring() ## DIAGNOSTIC #print "\n Q = " + str(self) #print "\n Q diagonalized at p = " + str(p) + " gives " + str(Diag) hasse_temp = 1 n = Diag.dim() if R == QQ: for j in range(n): for k in range(j, n): hasse_temp = hasse_temp * hilbert_symbol( Diag[j, j], Diag[k, k], p) else: for j in range(n): for k in range(j, n): hasse_temp = hasse_temp * R.hilbert_symbol( Diag[j, j], Diag[k, k], p) return hasse_temp
def hasse_invariant(self, p): """ Computes the Hasse invariant at a prime `p`, as given on p55 of Cassels's book. If Q is diagonal with coefficients `a_i`, then the (Cassels) Hasse invariant is given by .. MATH:: c_p = \prod_{i < j} (a_i, a_j)_p where `(a,b)_p` is the Hilbert symbol at `p`. The underlying quadratic form must be non-degenerate over `Q_p` for this to make sense. WARNING: This is different from the O'Meara Hasse invariant, which allows `i <= j` in the product. That is given by the method hasse_invariant__OMeara(p). NOTE: We should really rename this hasse_invariant__Cassels(), and set hasse_invariant() as a front-end to it. INPUT: `p` -- a prime number > 0 OUTPUT: 1 or -1 EXAMPLES:: sage: Q = QuadraticForm(ZZ, 2, [1,2,3]) sage: Q.rational_diagonal_form() Quadratic form in 2 variables over Rational Field with coefficients: [ 1 0 ] [ * 2 ] sage: [Q.hasse_invariant(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] :: sage: Q = DiagonalQuadraticForm(ZZ, [1,-1]) sage: [Q.hasse_invariant(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)] [-1, 1, 1, 1, 1, 1, 1, 1] :: sage: Q = DiagonalQuadraticForm(ZZ, [1,-1,5]) sage: [Q.hasse_invariant(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)] [-1, 1, 1, 1, 1, 1, 1, 1] :: sage: K.<a>=NumberField(x^2-23) sage: Q=DiagonalQuadraticForm(K,[-a,a+2]) sage: [Q.hasse_invariant(p) for p in K.primes_above(19)] [-1, 1] """ ## TO DO: Need to deal with the case n=1 separately somewhere! Diag = self.rational_diagonal_form() R = Diag.base_ring() ## DIAGNOSTIC #print "\n Q = " + str(self) #print "\n Q diagonalized at p = " + str(p) + " gives " + str(Diag) hasse_temp = 1 n = Diag.dim() if R == QQ: for j in range(n - 1): for k in range(j + 1, n): hasse_temp = hasse_temp * hilbert_symbol( Diag[j, j], Diag[k, k], p) else: for j in range(n - 1): for k in range(j + 1, n): hasse_temp = hasse_temp * R.hilbert_symbol( Diag[j, j], Diag[k, k], p) return hasse_temp
def is_anisotropic(self, p): """ Checks if the quadratic form is anisotropic over the p-adic numbers `Q_p`. INPUT: `p` -- a prime number > 0 OUTPUT: boolean EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1]) sage: Q.is_anisotropic(2) True sage: Q.is_anisotropic(3) True sage: Q.is_anisotropic(5) False :: sage: Q = DiagonalQuadraticForm(ZZ, [1,-1]) sage: Q.is_anisotropic(2) False sage: Q.is_anisotropic(3) False sage: Q.is_anisotropic(5) False :: sage: [DiagonalQuadraticForm(ZZ, [1, -least_quadratic_nonresidue(p)]).is_anisotropic(p) for p in prime_range(3, 30)] [True, True, True, True, True, True, True, True, True] :: sage: [DiagonalQuadraticForm(ZZ, [1, -least_quadratic_nonresidue(p), p, -p*least_quadratic_nonresidue(p)]).is_anisotropic(p) for p in prime_range(3, 30)] [True, True, True, True, True, True, True, True, True] """ n = self.dim() D = self.det() ## TO DO: Should check that p is prime if (n >= 5): return False; if (n == 4): return ( QQ(D).is_padic_square(p) and (self.hasse_invariant(p) == - hilbert_symbol(-1,-1,p)) ) if (n == 3): return (self.hasse_invariant(p) != hilbert_symbol(-1, -D, p)) if (n == 2): return (not QQ(-D).is_padic_square(p)) if (n == 1): return (self[0,0] != 0) raise NotImplementedError("Oops! We haven't established a convention for 0-dim'l quadratic forms... =(")
def hasse_invariant__OMeara(self, p): """ Computes the O'Meara Hasse invariant at a prime `p`, as given on p167 of O'Meara's book. If Q is diagonal with coefficients `a_i`, then the (Cassels) Hasse invariant is given by .. math:: c_p = \prod_{i <= j} (a_i, a_j)_p where `(a,b)_p` is the Hilbert symbol at `p`. WARNING: This is different from the (Cassels) Hasse invariant, which only allows `i < j` in the product. That is given by the method hasse_invariant(p). INPUT: `p` -- a prime number > 0 OUTPUT: 1 or -1 EXAMPLES:: sage: Q = QuadraticForm(ZZ, 2, [1,2,3]) sage: Q.rational_diagonal_form() Quadratic form in 2 variables over Rational Field with coefficients: [ 1 0 ] [ * 2 ] sage: [Q.hasse_invariant(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] :: sage: Q = DiagonalQuadraticForm(ZZ, [1,-1]) sage: [Q.hasse_invariant(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)] [-1, 1, 1, 1, 1, 1, 1, 1] :: sage: Q=DiagonalQuadraticForm(ZZ,[1,-1,-1]) sage: [Q.hasse_invariant(p) for p in prime_range(20)] [-1, 1, 1, 1, 1, 1, 1, 1] sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)] [-1, 1, 1, 1, 1, 1, 1, 1] :: sage: K.<a>=NumberField(x^2-23) sage: Q=DiagonalQuadraticForm(K,[-a,a+2]) sage: [Q.hasse_invariant__OMeara(p) for p in K.primes_above(19)] [1, 1] """ ## TO DO: Need to deal with the case n=1 separately somewhere! Diag = self.rational_diagonal_form() R = Diag.base_ring() ## DIAGNOSTIC #print "\n Q = " + str(self) #print "\n Q diagonalized at p = " + str(p) + " gives " + str(Diag) hasse_temp = 1 n = Diag.dim() if R == QQ: for j in range(n): for k in range(j, n): hasse_temp = hasse_temp * hilbert_symbol(Diag[j,j], Diag[k,k], p) else: for j in range(n): for k in range(j, n): hasse_temp = hasse_temp * R.hilbert_symbol(Diag[j,j], Diag[k,k], p) return hasse_temp
def has_equivalent_Jordan_decomposition_at_prime(self, other, p): """ Determines if the given quadratic form has a Jordan decomposition equivalent to that of self. INPUT: a QuadraticForm OUTPUT: boolean EXAMPLES:: sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 1, 0, 3]) sage: Q2 = QuadraticForm(ZZ, 3, [1, 0, 0, 2, -2, 6]) sage: Q3 = QuadraticForm(ZZ, 3, [1, 0, 0, 1, 0, 11]) sage: [Q1.level(), Q2.level(), Q3.level()] [44, 44, 44] sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,2) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,11) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,2) False sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,11) True sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,2) True sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,11) False """ ## Sanity Checks #if not isinstance(other, QuadraticForm): if not isinstance(other, type(self)): raise TypeError( "Oops! The first argument must be of type QuadraticForm.") if not is_prime(p): raise TypeError("Oops! The second argument must be a prime number.") ## Get the relevant local normal forms quickly self_jordan = self.jordan_blocks_by_scale_and_unimodular(p, safe_flag=False) other_jordan = other.jordan_blocks_by_scale_and_unimodular(p, safe_flag=False) ## DIAGNOSTIC #print "self_jordan = ", self_jordan #print "other_jordan = ", other_jordan ## Check for the same number of Jordan components if len(self_jordan) != len(other_jordan): return False ## Deal with odd primes: Check that the Jordan component scales, dimensions, and discriminants are the same if p != 2: for i in range(len(self_jordan)): if (self_jordan[i][0] != other_jordan[i][0]) \ or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \ or (legendre_symbol(self_jordan[i][1].det() * other_jordan[i][1].det(), p) != 1): return False ## All tests passed for an odd prime. return True ## For p = 2: Check that all Jordan Invariants are the same. elif p == 2: ## Useful definition t = len(self_jordan) ## Define t = Number of Jordan components ## Check that all Jordan Invariants are the same (scale, dim, and norm) for i in range(t): if (self_jordan[i][0] != other_jordan[i][0]) \ or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \ or (valuation(GCD(self_jordan[i][1].coefficients()), p) != valuation(GCD(other_jordan[i][1].coefficients()), p)): return False ## DIAGNOSTIC #print "Passed the Jordan invariant test." ## Use O'Meara's isometry test 93:29 on p277. ## ------------------------------------------ ## List of norms, scales, and dimensions for each i scale_list = [ZZ(2)**self_jordan[i][0] for i in range(t)] norm_list = [ ZZ(2)**(self_jordan[i][0] + valuation(GCD(self_jordan[i][1].coefficients()), 2)) for i in range(t) ] dim_list = [(self_jordan[i][1].dim()) for i in range(t)] ## List of Hessian determinants and Hasse invariants for each Jordan (sub)chain ## (Note: This is not the same as O'Meara's Gram determinants, but ratios are the same!) -- NOT SO GOOD... ## But it matters in condition (ii), so we multiply all by 2 (instead of dividing by 2 since only square-factors matter, and it's easier.) j = 0 self_chain_det_list = [ self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j]) ] other_chain_det_list = [ other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j]) ] self_hasse_chain_list = [ self_jordan[j][1].scale_by_factor( ZZ(2)**self_jordan[j][0]).hasse_invariant__OMeara(2) ] other_hasse_chain_list = [ other_jordan[j][1].scale_by_factor( ZZ(2)**other_jordan[j][0]).hasse_invariant__OMeara(2) ] for j in range(1, t): self_chain_det_list.append(self_chain_det_list[j - 1] * self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])) other_chain_det_list.append(other_chain_det_list[j - 1] * other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])) self_hasse_chain_list.append(self_hasse_chain_list[j-1] \ * hilbert_symbol(self_chain_det_list[j-1], self_jordan[j][1].Gram_det(), 2) \ * self_jordan[j][1].hasse_invariant__OMeara(2)) other_hasse_chain_list.append(other_hasse_chain_list[j-1] \ * hilbert_symbol(other_chain_det_list[j-1], other_jordan[j][1].Gram_det(), 2) \ * other_jordan[j][1].hasse_invariant__OMeara(2)) ## SANITY CHECK -- check that the scale powers are strictly increasing for i in range(1, len(scale_list)): if scale_list[i - 1] >= scale_list[i]: raise RuntimeError( "Oops! There is something wrong with the Jordan Decomposition -- the given scales are not strictly increasing!" ) ## DIAGNOSTIC #print "scale_list = ", scale_list #print "norm_list = ", norm_list #print "dim_list = ", dim_list #print #print "self_chain_det_list = ", self_chain_det_list #print "other_chain_det_list = ", other_chain_det_list #print "self_hasse_chain_list = ", self_hasse_chain_list #print "other_hasse_chain_det_list = ", other_hasse_chain_list ## Test O'Meara's two conditions for i in range(t - 1): ## Condition (i): Check that their (unit) ratio is a square (but it suffices to check at most mod 8). modulus = norm_list[i] * norm_list[i + 1] / (scale_list[i]**2) if modulus > 8: modulus = 8 if (modulus > 1) and (( (self_chain_det_list[i] / other_chain_det_list[i]) % modulus) != 1): #print "Failed when i =", i, " in condition 1." return False ## Check O'Meara's condition (ii) when appropriate if norm_list[i + 1] % (4 * norm_list[i]) == 0: if self_hasse_chain_list[i] * hilbert_symbol(norm_list[i] * other_chain_det_list[i], -self_chain_det_list[i], 2) \ != other_hasse_chain_list[i] * hilbert_symbol(norm_list[i], -other_chain_det_list[i], 2): ## Nipp conditions #print "Failed when i =", i, " in condition 2." return False ## All tests passed for the prime 2. return True else: raise TypeError("Oops! This should not have happened.")
def is_anisotropic(self, p): r""" Check if the quadratic form is anisotropic over the p-adic numbers `\QQ_p` or `\RR`. INPUT: - `p` -- a prime number > 0 or `-1` for the infinite place OUTPUT: boolean EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ, [1,1]) sage: Q.is_anisotropic(2) True sage: Q.is_anisotropic(3) True sage: Q.is_anisotropic(5) False :: sage: Q = DiagonalQuadraticForm(ZZ, [1,-1]) sage: Q.is_anisotropic(2) False sage: Q.is_anisotropic(3) False sage: Q.is_anisotropic(5) False :: sage: [DiagonalQuadraticForm(ZZ, [1, -least_quadratic_nonresidue(p)]).is_anisotropic(p) for p in prime_range(3, 30)] [True, True, True, True, True, True, True, True, True] :: sage: [DiagonalQuadraticForm(ZZ, [1, -least_quadratic_nonresidue(p), p, -p*least_quadratic_nonresidue(p)]).is_anisotropic(p) for p in prime_range(3, 30)] [True, True, True, True, True, True, True, True, True] """ ## TO DO: Should check that p is prime if p == -1: return self.is_definite() n = self.dim() D = self.det() if n >= 5: return False if n == 4: return (QQ(D).is_padic_square(p) and (self.hasse_invariant(p) == - hilbert_symbol(-1, -1, p))) if n == 3: return self.hasse_invariant(p) != hilbert_symbol(-1, -D, p) if n == 2: return not QQ(-D).is_padic_square(p) if n == 1: return self[0, 0] != 0 raise NotImplementedError("Oops! We haven't established a convention for 0-dim'l quadratic forms... =(")
def hasse_invariant(self, p): """ Computes the Hasse invariant at a prime `p` or at infinity, as given on p55 of Cassels's book. If Q is diagonal with coefficients `a_i`, then the (Cassels) Hasse invariant is given by .. MATH:: c_p = \prod_{i < j} (a_i, a_j)_p where `(a,b)_p` is the Hilbert symbol at `p`. The underlying quadratic form must be non-degenerate over `Q_p` for this to make sense. .. WARNING:: This is different from the O'Meara Hasse invariant, which allows `i <= j` in the product. That is given by the method hasse_invariant__OMeara(p). .. NOTE:: We should really rename this hasse_invariant__Cassels(), and set hasse_invariant() as a front-end to it. INPUT: - `p` -- a prime number > 0 or `-1` for the infinite place OUTPUT: 1 or -1 EXAMPLES:: sage: Q = QuadraticForm(ZZ, 2, [1,2,3]) sage: Q.rational_diagonal_form() Quadratic form in 2 variables over Rational Field with coefficients: [ 1 0 ] [ * 2 ] sage: [Q.hasse_invariant(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] :: sage: Q = DiagonalQuadraticForm(ZZ, [1,-1]) sage: [Q.hasse_invariant(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)] [-1, 1, 1, 1, 1, 1, 1, 1] :: sage: Q = DiagonalQuadraticForm(ZZ, [1,-1,5]) sage: [Q.hasse_invariant(p) for p in prime_range(20)] [1, 1, 1, 1, 1, 1, 1, 1] sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)] [-1, 1, 1, 1, 1, 1, 1, 1] :: sage: K.<a>=NumberField(x^2-23) sage: Q=DiagonalQuadraticForm(K,[-a,a+2]) sage: [Q.hasse_invariant(p) for p in K.primes_above(19)] [-1, 1] """ ## TO DO: Need to deal with the case n=1 separately somewhere! Diag = self.rational_diagonal_form() R = Diag.base_ring() ## DIAGNOSTIC #print "\n Q = " + str(self) #print "\n Q diagonalized at p = " + str(p) + " gives " + str(Diag) hasse_temp = 1 n = Diag.dim() if R == QQ: for j in range(n-1): for k in range(j+1, n): hasse_temp = hasse_temp * hilbert_symbol(Diag[j,j], Diag[k,k], p) else: for j in range(n-1): for k in range(j+1, n): hasse_temp = hasse_temp * R.hilbert_symbol(Diag[j,j], Diag[k,k], p) return hasse_temp