def _brown_indecomposable(q, p): r""" Return the Brown invariant of the indecomposable form ``q``. The values are taken from Table 2.1 in [Shim2016]_. INPUT: - ``q`` - an indecomposable quadratic form represented by a rational `1 \times 1` or `2 \times 2` matrix - ``p`` - a prime number EXAMPLES:: sage: from sage.modules.torsion_quadratic_module import _brown_indecomposable sage: q = Matrix(QQ, [1/3]) sage: _brown_indecomposable(q,3) 6 sage: q = Matrix(QQ, [2/3]) sage: _brown_indecomposable(q,3) 2 sage: q = Matrix(QQ, [5/4]) sage: _brown_indecomposable(q,2) 5 sage: q = Matrix(QQ, [7/4]) sage: _brown_indecomposable(q,2) 7 sage: q = Matrix(QQ, 2, [0,1,1,0])/2 sage: _brown_indecomposable(q,2) 0 sage: q = Matrix(QQ, 2, [2,1,1,2])/2 sage: _brown_indecomposable(q,2) 4 """ v = q.denominator().valuation(p) if p == 2: # brown(U) = 0 if q.ncols() == 2: if q[0, 0].valuation(2) > v + 1 and q[1, 1].valuation(2) > v + 1: # type U return mod(0, 8) else: # type V return mod(4 * v, 8) u = q[0, 0].numerator() return mod(u + v * (u**2 - 1) / 2, 8) if p % 4 == 1: e = -1 if p % 4 == 3: e = 1 if v % 2 == 1: u = q[0, 0].numerator() // 2 if legendre_symbol(u, p) == 1: return mod(1 + e, 8) else: return mod(-3 + e, 8) return mod(0, 8)
def _brown_indecomposable(q, p): r""" Return the Brown invariant of the indecomposable form ``q``. The values are taken from Table 2.1 in [Shim2016]_. INPUT: - ``q`` - an indecomposable quadratic form represented by a rational `1 \times 1` or `2 \times 2` matrix - ``p`` - a prime number EXAMPLES:: sage: from sage.modules.torsion_quadratic_module import _brown_indecomposable sage: q = Matrix(QQ, [1/3]) sage: _brown_indecomposable(q,3) 6 sage: q = Matrix(QQ, [2/3]) sage: _brown_indecomposable(q,3) 2 sage: q = Matrix(QQ, [5/4]) sage: _brown_indecomposable(q,2) 5 sage: q = Matrix(QQ, [7/4]) sage: _brown_indecomposable(q,2) 7 sage: q = Matrix(QQ, 2, [0,1,1,0])/2 sage: _brown_indecomposable(q,2) 0 sage: q = Matrix(QQ, 2, [2,1,1,2])/2 sage: _brown_indecomposable(q,2) 4 """ v = q.denominator().valuation(p) if p == 2: # brown(U) = 0 if q.ncols() == 2: if q[0,0].valuation(2)>v+1 and q[1,1].valuation(2)>v+1: # type U return mod(0, 8) else: # type V return mod(4*v, 8) u = q[0,0].numerator() return mod(u + v*(u**2 - 1)/2, 8) if p % 4 == 1: e = -1 if p % 4 == 3: e = 1 if v % 2 == 1: u = q[0,0].numerator()//2 if legendre_symbol(u,p) == 1: return mod(1 + e, 8) else: return mod(-3 + e, 8) return mod(0, 8)
def quadratic_defect(self, a, p, check=True): r""" Return the valuation of the quadratic defect of `a` at `p`. INPUT: - ``a`` -- an element of ``self`` - ``p`` -- a prime ideal or a prime number - ``check`` -- (default: ``True``); check if `p` is prime REFERENCE: [Kir2016]_ EXAMPLES:: sage: QQ.quadratic_defect(0, 7) +Infinity sage: QQ.quadratic_defect(5, 7) 0 sage: QQ.quadratic_defect(5, 2) 2 sage: QQ.quadratic_defect(5, 5) 1 """ from sage.rings.all import Infinity from sage.arith.misc import legendre_symbol if not a in self: raise TypeError(str(a) + " must be an element of " + str(self)) if p.parent() == ZZ.ideal_monoid(): p = p.gen() if check and not p.is_prime(): raise ValueError(str(p) + " must be prime") if a.is_zero(): return Infinity v, u = self(a).val_unit(p) if v % 2 == 1: return v if p != 2: if legendre_symbol(u, p) == 1: return Infinity else: return v if p == 2: if u % 8 == 1: return Infinity if u % 8 == 5: return v + 2 if u % 8 in [3, 7]: return v + 1
def quadratic_defect(self, a, p): r""" Return the valuation of the quadratic defect of `a` at `p`. INPUT: - ``a`` an element of ``self`` - ``p`` a prime ideal or a prime number REFERENCE: [Kir2016]_ EXAMPLES:: sage: QQ.quadratic_defect(0, 7) +Infinity sage: QQ.quadratic_defect(5, 7) 0 sage: QQ.quadratic_defect(5, 2) 2 sage: QQ.quadratic_defect(5, 5) 1 """ from sage.rings.all import Infinity from sage.arith.misc import legendre_symbol if not a in self: raise TypeError(str(a) + " must be an element of " + str(self)) if p.parent() == ZZ.ideal_monoid(): p = p.gen() if not p.is_prime(): raise ValueError(str(p) + " must be prime") if a.is_zero(): return Infinity v = self(a).valuation(p) if v % 2 == 1: return v a = a / (p**v) if p != 2: if legendre_symbol(a, p) == 1: return Infinity else: return v if p == 2: if a % 8 == 1: return Infinity if a % 8 == 5: return v + 2 if a % 8 in [3, 7]: return v + 1
def is_genus(self, signature_pair, even=True): r""" Return ``True`` if there is a lattice with this signature and discriminant form. .. TODO:: implement the same for odd lattices INPUT: - signature_pair -- a tuple of non negative integers ``(s_plus, s_minus)`` - even -- bool (default: ``True``) EXAMPLES:: sage: L = IntegralLattice("D4").direct_sum(IntegralLattice(3 * Matrix(ZZ,2,[2,1,1,2]))) sage: D = L.discriminant_group() sage: D.is_genus((6,0)) True Let us see if there is a lattice in the genus defined by the same discriminant form but with a different signature:: sage: D.is_genus((4,2)) False sage: D.is_genus((16,2)) True """ s_plus = ZZ(signature_pair[0]) s_minus = ZZ(signature_pair[1]) if s_plus < 0 or s_minus < 0: raise ValueError("signature invariants must be non negative") rank = s_plus + s_minus signature = s_plus - s_minus D = self.cardinality() det = (-1)**s_minus * D if rank < len(self.invariants()): return False if even and self._modulus_qf != 2: raise ValueError("the discriminant form of an even lattice has" "values modulo 2.") if (not even) and not (self._modulus == self._modulus_qf == 1): raise ValueError("the discriminant form of an odd lattice has" "values modulo 1.") if not even: raise NotImplementedError("at the moment sage knows how to do this only for even genera. " + " Help us to implement this for odd genera.") for p in D.prime_divisors(): # check the determinant conditions Q_p = self.primary_part(p) gram_p = Q_p.gram_matrix_quadratic() length_p = len(Q_p.invariants()) u = det.prime_to_m_part(p) up = gram_p.det().numerator().prime_to_m_part(p) if p != 2 and length_p == rank: if legendre_symbol(u, p) != legendre_symbol(up, p): return False if p == 2: if rank % 2 != length_p % 2: return False n = (rank - length_p) / 2 if u % 4 != (-1)**(n % 2) * up % 4: return False if rank == length_p: a = QQ(1) / QQ(2) b = QQ(3) / QQ(2) diag = gram_p.diagonal() if not (a in diag or b in diag): if u % 8 != up % 8: return False if self.brown_invariant() != signature: return False return True
def genus(self, signature_pair): r""" Return the genus defined by ``self`` and the ``signature_pair``. If no such genus exists, raise a ``ValueError``. REFERENCES: [Nik1977]_ Corollary 1.9.4 and 1.16.3. EXAMPLES:: sage: L = IntegralLattice("D4").direct_sum(IntegralLattice("A2")) sage: D = L.discriminant_group() sage: genus = D.genus(L.signature_pair()) sage: genus Genus of None Signature: (6, 0) Genus symbol at 2: 1^4:2^-2 Genus symbol at 3: 1^-5 3^-1 sage: genus == L.genus() True Let `H` be an even unimodular lattice of signature `(9, 1)`. Then `L = D_4 + A_2` is primitively embedded in `H`. We compute the discriminant form of the orthogonal complement of `L` in `H`:: sage: DK = D.twist(-1) sage: DK Finite quadratic module over Integer Ring with invariants (2, 6) Gram matrix of the quadratic form with values in Q/2Z: [ 1 1/2] [1/2 1/3] We know that `K` has signature `(5, 1)` and thus we can compute the genus of `K` as:: sage: DK.genus((3,1)) Genus of None Signature: (3, 1) Genus symbol at 2: 1^2:2^-2 Genus symbol at 3: 1^-3 3^1 We can also compute the genus of an odd lattice from its discriminant form:: sage: L = IntegralLattice(matrix.diagonal(range(1,5))) sage: D = L.discriminant_group() sage: D.genus((4,0)) Genus of None Signature: (4, 0) Genus symbol at 2: [1^-2 2^1 4^1]_6 Genus symbol at 3: 1^-3 3^1 TESTS:: sage: L.genus() == D.genus((4,0)) True sage: D.genus((1,0)) Traceback (most recent call last): ... ValueError: this discriminant form and signature do not define a genus A systematic test of lattices of small ranks and determinants:: sage: from sage.quadratic_forms.genera.genus import genera sage: signatures = [(1,0),(1,1),(1,2),(3,0),(0,4)] sage: dets = range(1,33) sage: genera = flatten([genera(s, d, even=False) for d in dets for s in signatures]) # long time sage: all(g == g.discriminant_form().genus(g.signature_pair()) for g in genera) # long time True """ from sage.quadratic_forms.genera.genus import (Genus_Symbol_p_adic_ring, GenusSymbol_global_ring, p_adic_symbol, is_GlobalGenus, _blocks) from sage.misc.misc_c import prod s_plus = signature_pair[0] s_minus = signature_pair[1] rank = s_plus + s_minus if len(self.invariants()) > rank: raise ValueError("this discriminant form and " + "signature do not define a genus") disc = self.cardinality() determinant = (-1)**s_minus * disc local_symbols = [] for p in (2 * disc).prime_divisors(): D = self.primary_part(p) if len(D.invariants()) != 0: G_p = D.gram_matrix_quadratic().inverse() # get rid of denominators without changing the local equivalence class G_p *= G_p.denominator()**2 G_p = G_p.change_ring(ZZ) local_symbol = p_adic_symbol(G_p, p, D.invariants()[-1].valuation(p)) else: local_symbol = [] rk = rank - len(D.invariants()) if rk > 0: if p == 2: det = determinant.prime_to_m_part(2) det *= prod([di[2] for di in local_symbol]) det = det % 8 local_symbol.append([ZZ(0), rk, det, ZZ(0), ZZ(0)]) else: det = legendre_symbol(determinant.prime_to_m_part(p), p) det = (det * prod([di[2] for di in local_symbol])) local_symbol.append([ZZ(0), rk, det]) local_symbol.sort() local_symbol = Genus_Symbol_p_adic_ring(p, local_symbol) local_symbols.append(local_symbol) # This genus has the right discriminant group # but it may be empty genus = GenusSymbol_global_ring(signature_pair, local_symbols) sym2 = local_symbols[0].symbol_tuple_list() if sym2[0][0] != 0: sym2 = [[ZZ(0), ZZ(0), ZZ(1), ZZ(0), ZZ(0)]] + sym2 if len(sym2) <= 1 or sym2[1][0] != 1: sym2 = sym2[:1] + [[ZZ(1), ZZ(0), ZZ(1), ZZ(0), ZZ(0)]] + sym2[1:] if len(sym2) <= 2 or sym2[2][0] != 2: sym2 = sym2[:2] + [[ZZ(2), ZZ(0), ZZ(1), ZZ(0), ZZ(0)]] + sym2[2:] if self.value_module_qf().n == 1: # in this case the blocks of scales 1, 2, 4 are under determined # make sure the first 3 symbols are of scales 1, 2, 4 # i.e. their valuations are 0, 1, 2 # the form is odd block0 = [b for b in _blocks(sym2[0]) if b[3] == 1] o = sym2[1][3] # no restrictions on determinant and # oddity beyond existence # but we know if even or odd block1 = [b for b in _blocks(sym2[1]) if b[3] == o] d = sym2[2][2] o = sym2[2][3] t = sym2[2][4] # if the jordan block of scale 2 is even we know it if o == 0: block2 = [sym2[2]] # if it is odd we know det and oddity mod 4 at least else: block2 = [b for b in _blocks(sym2[2]) if b[3] == o and (b[2] - d) % 4 == 0 and (b[4] - t) % 4 == 0 and (b[2] - d) % 8 == (b[4] - t) % 8 # if the oddity is altered by 4 then so is the determinant ] elif self.value_module_qf().n == 2: # the form is even block0 = [b for b in _blocks(sym2[0]) if b[3] == 0] # if the jordan block of scale 2 is even we know it d = sym2[1][2] o = sym2[1][3] t = sym2[1][4] if o == 0: block1 = [sym2[1]] else: # the block is odd and we know det and oddity mod 4 block1 = [b for b in _blocks(sym2[1]) if b[3] == o and (b[2] - d) % 4 == 0 and (b[4] - t) % 4 == 0 and (b[2] - d) % 8 == (b[4] - t) % 8 # if the oddity is altered by 4 then so is the determinant ] # this is completely determined block2 = [sym2[2]] else: raise ValueError("this is not a discriminant form") # figure out which symbol defines a genus and return that for b0 in block0: for b1 in block1: for b2 in block2: sym2[:3] = [b0, b1, b2] local_symbols[0] = Genus_Symbol_p_adic_ring(2, sym2) genus = GenusSymbol_global_ring(signature_pair, local_symbols) if is_GlobalGenus(genus): # make the symbol sparse again. i = 0 k = 0 while i < 3: if sym2[k][1] == 0: sym2.pop(k) else: k = k + 1 i = i + 1 local_symbols[0] = Genus_Symbol_p_adic_ring(2, sym2) genus = GenusSymbol_global_ring(signature_pair, local_symbols) return genus raise ValueError("this discriminant form and signature do not define a genus")
def is_genus(self, signature_pair, even=True): r""" Return ``True`` if there is a lattice with this signature and discriminant form. TODO: - implement the same for odd lattices INPUT: - signature_pair -- a tuple of non negative integers ``(s_plus, s_minus)`` - even -- bool (default: ``True``) EXAMPLES:: sage: L = IntegralLattice("D4").direct_sum(IntegralLattice(3 * Matrix(ZZ,2,[2,1,1,2]))) sage: D = L.discriminant_group() sage: D.is_genus((6,0)) True Let us see if there is a lattice in the genus defined by the same discriminant form but with a different signature:: sage: D.is_genus((4,2)) False sage: D.is_genus((16,2)) True """ s_plus = ZZ(signature_pair[0]) s_minus = ZZ(signature_pair[1]) if s_plus<0 or s_minus<0: raise ValueError("signature invariants must be non negative") rank = s_plus + s_minus signature = s_plus - s_minus D = self.cardinality() det = (-1)**s_minus * D if rank < len(self.invariants()): return False if even and self._modulus_qf != 2: raise ValueError("the discriminant form of an even lattice has" "values modulo 2.") if (not even) and not (self.modulus == self._modulus_qf == 1): raise ValueError("the discriminant form of an odd lattice has" "values modulo 1.") if not even: raise NotImplementedError("at the moment sage knows how to do this only for even genera. " + " Help us to implement this for odd genera.") for p in D.prime_divisors(): # check the determinat conditions Q_p = self.primary_part(p) gram_p = Q_p.gram_matrix_quadratic() length_p = len(Q_p.invariants()) u = det.prime_to_m_part(p) up = gram_p.det().numerator().prime_to_m_part(p) if p!=2 and length_p==rank: if legendre_symbol(u, p) != legendre_symbol(up, p): return False if p == 2: if rank % 2 != length_p % 2: return False n = (rank - length_p) / 2 if u % 4 != (-1)**(n % 2) * up % 4: return False if rank == length_p: a = QQ(1) / QQ(2) b = QQ(3) / QQ(2) diag = gram_p.diagonal() if not (a in diag or b in diag): if u % 8 != up % 8: return False if self.brown_invariant() != signature: return False return True