def p_adic_normal_form(G, p, precision=None, partial=False, debug=False): r""" Return the transformation to the `p`-adic normal form of a symmetric matrix. Two ``p-adic`` quadratic forms are integrally equivalent if and only if their Gram matrices have the same normal form. Let `p` be odd and `u` be the smallest non-square modulo `p`. The normal form is a block diagonal matrix with blocks `p^k G_k` such that `G_k` is either the identity matrix or the identity matrix with the last diagonal entry replaced by `u`. If `p=2` is even, define the `1` by `1` matrices:: sage: W1 = Matrix([1]); W1 [1] sage: W3 = Matrix([3]); W3 [3] sage: W5 = Matrix([5]); W5 [5] sage: W7 = Matrix([7]); W7 [7] and the `2` by `2` matrices:: sage: U = Matrix(2,[0,1,1,0]); U [0 1] [1 0] sage: V = Matrix(2,[2,1,1,2]); V [2 1] [1 2] For `p=2` the partial normal form is a block diagonal matrix with blocks `2^k G_k` such that $G_k$ is a block diagonal matrix of the form `[U`, ... , `U`, `V`, `Wa`, `Wb]` where we allow `V`, `Wa`, `Wb` to be `0 \times 0` matrices. Further restrictions to the full normal form apply. We refer to [MirMor2009]_ IV Definition 4.6. for the details. INPUT: - ``G`` -- a symmetric `n` by `n` matrix in `\QQ` - ``p`` -- a prime number -- it is not checked whether it is prime - ``precision`` -- if not set, the minimal possible is taken - ``partial`` -- boolean (default: ``False``) if set, only the partial normal form is returned. OUTPUT: - ``D`` -- the jordan matrix over `\QQ_p` - ``B`` -- invertible transformation matrix over `\ZZ_p`, i.e, ``D = B * G * B^T`` EXAMPLES:: sage: from sage.quadratic_forms.genera.normal_form import p_adic_normal_form sage: D4 = Matrix(ZZ, 4, [2,-1,-1,-1,-1,2,0,0,-1,0,2,0,-1,0,0,2]) sage: D4 [ 2 -1 -1 -1] [-1 2 0 0] [-1 0 2 0] [-1 0 0 2] sage: D, B = p_adic_normal_form(D4, 2) sage: D [ 2 1 0 0] [ 1 2 0 0] [ 0 0 2^2 2] [ 0 0 2 2^2] sage: D == B * D4 * B.T True sage: A4 = Matrix(ZZ, 4, [2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2, -1, 0, 0, -1, 2]) sage: A4 [ 2 -1 0 0] [-1 2 -1 0] [ 0 -1 2 -1] [ 0 0 -1 2] sage: D, B = p_adic_normal_form(A4, 2) sage: D [0 1 0 0] [1 0 0 0] [0 0 2 1] [0 0 1 2] We can handle degenerate forms:: sage: A4_extended = Matrix(ZZ, 5, [2, -1, 0, 0, -1, -1, 2, -1, 0, 0, 0, -1, 2, -1, 0, 0, 0, -1, 2, -1, -1, 0, 0, -1, 2]) sage: D, B = p_adic_normal_form(A4_extended, 5) sage: D [1 0 0 0 0] [0 1 0 0 0] [0 0 1 0 0] [0 0 0 5 0] [0 0 0 0 0] and denominators:: sage: A4dual = A4.inverse() sage: D, B = p_adic_normal_form(A4dual, 5) sage: D [5^-1 0 0 0] [ 0 1 0 0] [ 0 0 1 0] [ 0 0 0 1] TESTS:: sage: Z = Matrix(ZZ,0,[]) sage: p_adic_normal_form(Z, 3) ([], []) sage: Z = matrix.zero(10) sage: p_adic_normal_form(Z, 3)[0] == 0 True """ p = ZZ(p) # input checks!! G0, denom = G._clear_denom() d = denom.valuation(p) r = G0.rank() if r != G0.ncols(): U = G0.hermite_form(transformation=True)[1] else: U = G0.parent().identity_matrix() kernel = U[r:, :] nondeg = U[:r, :] # continue with the non-degenerate part G = nondeg * G * nondeg.T * p**d if precision is None: # in Zp(2) we have to calculate at least mod 8 for things to make sense. precision = G.det().valuation(p) + 4 R = Zp(p, prec=precision, type='fixed-mod') G = G.change_ring(R) G.set_immutable() # is not changed during computation D = copy(G) # is transformed into jordan form n = G.ncols() # The trivial case if n == 0: return G.parent().zero(), G.parent().zero() # the transformation matrix is called B B = Matrix.identity(R, n) if p == 2: D, B = _jordan_2_adic(G) else: D, B = _jordan_odd_adic(G) D, B1 = _normalize(D) B = B1 * B # we have reached a normal form for p != 2 # for p == 2 extra work is necessary if p == 2: D, B1 = _two_adic_normal_forms(D, partial=partial) B = B1 * B nondeg = B * nondeg B = nondeg.stack(kernel) D = Matrix.block_diagonal([D, Matrix.zero(kernel.nrows())]) if debug: assert B.determinant().valuation() == 0 # B is invertible! if p == 2: assert B * G * B.T == Matrix.block_diagonal( collect_small_blocks(D)) else: assert B * G * B.T == Matrix.diagonal(D.diagonal()) return D / p**d, B
def normal_form(self, partial=False): r""" Return the normal form of this torsion quadratic module. Two torsion quadratic modules are isomorphic if and only if they have the same value modules and the same normal form. A torsion quadratic module `(T,q)` with values in `\QQ/n\ZZ` is in normal form if the rescaled quadratic module `(T, q/n)` with values in `\QQ/\ZZ` is in normal form. For the definition of normal form see [MirMor2009]_ IV Definition 4.6. Below are some of its properties. Let `p` be odd and `u` be the smallest non-square modulo `p`. The normal form is a diagonal matrix with diagonal entries either `p^n` or `u p^n`. If `p = 2` is even, then the normal form consists of 1 x 1 blocks of the form .. MATH:: (0), \quad 2^n(1),\quad 2^n(3),\quad 2^n(5) ,\quad 2^n(7) or of `2 \times 2` blocks of the form .. MATH:: 2^n \left(\begin{matrix} 2 & 1\\ 1 & 2 \end{matrix}\right), \quad 2^n \left(\begin{matrix} 0 & 1\\ 1 & 0 \end{matrix}\right). The blocks are ordered by their valuation. INPUT: - partial - bool (default: ``False``) return only a partial normal form it is not unique but still useful to extract invariants OUTPUT: - a torsion quadratic module EXAMPLES:: sage: L1=IntegralLattice(matrix([[-2,0,0],[0,1,0],[0,0,4]])) sage: L1.discriminant_group().normal_form() Finite quadratic module over Integer Ring with invariants (2, 4) Gram matrix of the quadratic form with values in Q/Z: [1/2 0] [ 0 1/4] sage: L2=IntegralLattice(matrix([[-2,0,0],[0,1,0],[0,0,-4]])) sage: L2.discriminant_group().normal_form() Finite quadratic module over Integer Ring with invariants (2, 4) Gram matrix of the quadratic form with values in Q/Z: [1/2 0] [ 0 1/4] We check that :trac:`24864` is fixed:: sage: L1=IntegralLattice(matrix([[-4,0,0],[0,4,0],[0,0,-2]])) sage: AL1=L1.discriminant_group() sage: L2=IntegralLattice(matrix([[-4,0,0],[0,-4,0],[0,0,2]])) sage: AL2=L2.discriminant_group() sage: AL1.normal_form() Finite quadratic module over Integer Ring with invariants (2, 4, 4) Gram matrix of the quadratic form with values in Q/2Z: [1/2 0 0] [ 0 1/4 0] [ 0 0 5/4] sage: AL2.normal_form() Finite quadratic module over Integer Ring with invariants (2, 4, 4) Gram matrix of the quadratic form with values in Q/2Z: [1/2 0 0] [ 0 1/4 0] [ 0 0 5/4] Some exotic cases:: sage: from sage.modules.torsion_quadratic_module import TorsionQuadraticModule sage: D4_gram = Matrix(ZZ,4,4,[2,0,0,-1,0,2,0,-1,0,0,2,-1,-1,-1,-1,2]) sage: D4 = FreeQuadraticModule(ZZ,4,D4_gram) sage: D4dual = D4.span(D4_gram.inverse()) sage: T = TorsionQuadraticModule((1/6)*D4dual,D4) sage: T Finite quadratic module over Integer Ring with invariants (6, 6, 12, 12) Gram matrix of the quadratic form with values in Q/(1/3)Z: [ 1/18 1/12 5/36 1/36] [ 1/12 1/6 1/36 1/9] [ 5/36 1/36 1/36 11/72] [ 1/36 1/9 11/72 1/36] sage: T.normal_form() Finite quadratic module over Integer Ring with invariants (6, 6, 12, 12) Gram matrix of the quadratic form with values in Q/(1/3)Z: [ 1/6 1/12 0 0 0 0 0 0] [1/12 1/6 0 0 0 0 0 0] [ 0 0 1/12 1/24 0 0 0 0] [ 0 0 1/24 1/12 0 0 0 0] [ 0 0 0 0 1/9 0 0 0] [ 0 0 0 0 0 1/9 0 0] [ 0 0 0 0 0 0 1/9 0] [ 0 0 0 0 0 0 0 1/9] TESTS: A degenerate case:: sage: T = TorsionQuadraticModule((1/6)*D4dual, D4, modulus=1/36) sage: T.normal_form() Finite quadratic module over Integer Ring with invariants (6, 6, 12, 12) Gram matrix of the quadratic form with values in Q/(1/18)Z: [1/36 1/72 0 0 0 0 0 0] [1/72 1/36 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0] """ gens = [] from sage.quadratic_forms.genera.normal_form import p_adic_normal_form, _normalize for p in self.annihilator().gen().prime_divisors(): D_p = self.primary_part(p) q_p = D_p.gram_matrix_quadratic() q_p = q_p / D_p._modulus_qf # continue with the non-degenerate part r = q_p.rank() if r != q_p.ncols(): U = q_p._clear_denom()[0].hermite_form(transformation=True)[1] else: U = q_p.parent().identity_matrix() kernel = U[r:, :] nondeg = U[:r, :] q_p = nondeg * q_p * nondeg.T # the normal form is implemented for p-adic lattices # so we should work with the lattice q_p --> q_p^-1 q_p1 = q_p.inverse() prec = self.annihilator().gen().valuation(p) + 5 D, U = p_adic_normal_form(q_p1, p, precision=prec + 5, partial=partial) # if we compute the inverse in the p-adics everything explodes --> go to ZZ U = U.change_ring(ZZ).inverse().transpose() # the inverse is in normal form - so to get a normal form for the original one # it is enough to massage each 1x1 resp. 2x2 block. U = U.change_ring(Zp(p, type='fixed-mod', prec=prec)).change_ring(ZZ) D = U * q_p * U.T * p**q_p.denominator().valuation(p) D = D.change_ring(Zp(p, type='fixed-mod', prec=prec)) _, U1 = _normalize(D, normal_odd=False) U = U1.change_ring(ZZ) * U # reattach the degenerate part nondeg = U * nondeg U = nondeg.stack(kernel) # apply U to the generators n = U.ncols() gens_p = [] for i in range(n): g = self.V().zero() for j in range(n): g += D_p.gens()[j].lift() * U[i, j] gens_p.append(g) gens += gens_p return self.submodule_with_gens(gens)