Normal forms for \(p\)-adic quadratic and bilinear forms#

We represent a quadratic or bilinear form by its \(n \times n\) Gram matrix \(G\). Then two \(p\)-adic forms \(G\) and \(G'\) are integrally equivalent if and only if there is a matrix \(B\) in \(GL(n,\ZZ_p)\) such that \(G' = B G B^T\).

This module allows the computation of a normal form. This means that two \(p\)-adic forms are integrally equivalent if and only if they have the same normal form. Further, we can compute a transformation into normal form (up to finite precision).

EXAMPLES:

sage: from sage.quadratic_forms.genera.normal_form import p_adic_normal_form
sage: G1 = Matrix(ZZ,4, [2, 0, 0, 1, 0, 2, 0, 1, 0, 0, 4, 2, 1, 1, 2, 6])
sage: G1
[2 0 0 1]
[0 2 0 1]
[0 0 4 2]
[1 1 2 6]
sage: G2 = Matrix(ZZ,4, [2, 1, 1, 0, 1, 2, 0, 0, 1, 0, 2, 0, 0, 0, 0, 16])
sage: G2
[ 2  1  1  0]
[ 1  2  0  0]
[ 1  0  2  0]
[ 0  0  0 16]
>>> from sage.all import *
>>> from sage.quadratic_forms.genera.normal_form import p_adic_normal_form
>>> G1 = Matrix(ZZ,Integer(4), [Integer(2), Integer(0), Integer(0), Integer(1), Integer(0), Integer(2), Integer(0), Integer(1), Integer(0), Integer(0), Integer(4), Integer(2), Integer(1), Integer(1), Integer(2), Integer(6)])
>>> G1
[2 0 0 1]
[0 2 0 1]
[0 0 4 2]
[1 1 2 6]
>>> G2 = Matrix(ZZ,Integer(4), [Integer(2), Integer(1), Integer(1), Integer(0), Integer(1), Integer(2), Integer(0), Integer(0), Integer(1), Integer(0), Integer(2), Integer(0), Integer(0), Integer(0), Integer(0), Integer(16)])
>>> G2
[ 2  1  1  0]
[ 1  2  0  0]
[ 1  0  2  0]
[ 0  0  0 16]

A computation reveals that both forms are equivalent over \(\ZZ_2\):

sage: D1, U1 = p_adic_normal_form(G1,2, precision=30)
sage: D2, U2 = p_adic_normal_form(G1,2, precision=30)
sage: D1
[        2         1         0         0]
[        1         2         0         0]
[        0         0 2^2 + 2^3         0]
[        0         0         0       2^4]
sage: D2
[        2         1         0         0]
[        1         2         0         0]
[        0         0 2^2 + 2^3         0]
[        0         0         0       2^4]
>>> from sage.all import *
>>> D1, U1 = p_adic_normal_form(G1,Integer(2), precision=Integer(30))
>>> D2, U2 = p_adic_normal_form(G1,Integer(2), precision=Integer(30))
>>> D1
[        2         1         0         0]
[        1         2         0         0]
[        0         0 2^2 + 2^3         0]
[        0         0         0       2^4]
>>> D2
[        2         1         0         0]
[        1         2         0         0]
[        0         0 2^2 + 2^3         0]
[        0         0         0       2^4]

Moreover, we have computed the \(2\)-adic isomorphism:

sage: U = U2.inverse()*U1
sage: U*G1*U.T
[          2 2^31 + 2^32 2^32 + 2^33           1]
[2^31 + 2^32           2        2^32           1]
[2^32 + 2^33        2^32         2^2           2]
[          1           1           2     2 + 2^2]
>>> from sage.all import *
>>> U = U2.inverse()*U1
>>> U*G1*U.T
[          2 2^31 + 2^32 2^32 + 2^33           1]
[2^31 + 2^32           2        2^32           1]
[2^32 + 2^33        2^32         2^2           2]
[          1           1           2     2 + 2^2]

As you can see this isomorphism is only up to the precision we set before:

sage: (U*G1*U.T).change_ring(IntegerModRing(2^30))
[2 0 0 1]
[0 2 0 1]
[0 0 4 2]
[1 1 2 6]
>>> from sage.all import *
>>> (U*G1*U.T).change_ring(IntegerModRing(Integer(2)**Integer(30)))
[2 0 0 1]
[0 2 0 1]
[0 0 4 2]
[1 1 2 6]

If you are only interested if the forms are isomorphic, there are much faster ways:

sage: q1 = QuadraticForm(G1)
sage: q2 = QuadraticForm(G2)
sage: q1.is_locally_equivalent_to(q2,2)
True
>>> from sage.all import *
>>> q1 = QuadraticForm(G1)
>>> q2 = QuadraticForm(G2)
>>> q1.is_locally_equivalent_to(q2,Integer(2))
True

SEEALSO:

:mod:`~sage.quadratic_forms.genera.genus`
:meth:`~sage.quadratic_forms.quadratic_form.QuadraticForm.is_locally_equivalent_to`
:meth:`~sage.modules.torsion_quadratic_module.TorsionQuadraticModule.normal_form`

AUTHORS:

  • Simon Brandhorst (2018-01): initial version

sage.quadratic_forms.genera.normal_form.collect_small_blocks(G)[source]#

Return the blocks as list.

INPUT:

  • G – a block_diagonal matrix consisting of \(1\) by \(1\) and \(2\) by \(2\) blocks

OUTPUT:

  • a list of \(1\) by \(1\) and \(2\) by \(2\) matrices – the blocks

EXAMPLES:

sage: from sage.quadratic_forms.genera.normal_form import collect_small_blocks
sage: W1 = Matrix([1])
sage: V = Matrix(ZZ, 2, [2, 1, 1, 2])
sage: L = [W1, V, V, W1, W1, V, W1, V]
sage: G = Matrix.block_diagonal(L)
sage: L == collect_small_blocks(G)
True
>>> from sage.all import *
>>> from sage.quadratic_forms.genera.normal_form import collect_small_blocks
>>> W1 = Matrix([Integer(1)])
>>> V = Matrix(ZZ, Integer(2), [Integer(2), Integer(1), Integer(1), Integer(2)])
>>> L = [W1, V, V, W1, W1, V, W1, V]
>>> G = Matrix.block_diagonal(L)
>>> L == collect_small_blocks(G)
True
sage.quadratic_forms.genera.normal_form.p_adic_normal_form(G, p, precision=None, partial=False, debug=False)[source]#

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]
>>> from sage.all import *
>>> W1 = Matrix([Integer(1)]); W1
[1]
>>> W3 = Matrix([Integer(3)]); W3
[3]
>>> W5 = Matrix([Integer(5)]); W5
[5]
>>> W7 = Matrix([Integer(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]
>>> from sage.all import *
>>> U = Matrix(Integer(2),[Integer(0),Integer(1),Integer(1),Integer(0)]); U
[0 1]
[1 0]
>>> V = Matrix(Integer(2),[Integer(2),Integer(1),Integer(1),Integer(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]
>>> from sage.all import *
>>> from sage.quadratic_forms.genera.normal_form import p_adic_normal_form
>>> D4 = Matrix(ZZ, Integer(4), [Integer(2),-Integer(1),-Integer(1),-Integer(1),-Integer(1),Integer(2),Integer(0),Integer(0),-Integer(1),Integer(0),Integer(2),Integer(0),-Integer(1),Integer(0),Integer(0),Integer(2)])
>>> D4
[ 2 -1 -1 -1]
[-1  2  0  0]
[-1  0  2  0]
[-1  0  0  2]
>>> D, B = p_adic_normal_form(D4, Integer(2))
>>> D
[  2   1   0   0]
[  1   2   0   0]
[  0   0 2^2   2]
[  0   0   2 2^2]
>>> D == B * D4 * B.T
True
>>> A4 = Matrix(ZZ, Integer(4), [Integer(2), -Integer(1), Integer(0), Integer(0), -Integer(1), Integer(2), -Integer(1), Integer(0), Integer(0), -Integer(1), Integer(2), -Integer(1), Integer(0), Integer(0), -Integer(1), Integer(2)])
>>> A4
[ 2 -1  0  0]
[-1  2 -1  0]
[ 0 -1  2 -1]
[ 0  0 -1  2]
>>> D, B = p_adic_normal_form(A4, Integer(2))
>>> 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]
>>> from sage.all import *
>>> A4_extended = Matrix(ZZ, Integer(5), [Integer(2), -Integer(1), Integer(0), Integer(0), -Integer(1), -Integer(1), Integer(2), -Integer(1), Integer(0), Integer(0), Integer(0), -Integer(1), Integer(2), -Integer(1), Integer(0), Integer(0), Integer(0), -Integer(1), Integer(2), -Integer(1), -Integer(1), Integer(0), Integer(0), -Integer(1), Integer(2)])
>>> D, B = p_adic_normal_form(A4_extended, Integer(5))
>>> 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]
>>> from sage.all import *
>>> A4dual = A4.inverse()
>>> D, B = p_adic_normal_form(A4dual, Integer(5))
>>> D
[5^-1    0    0    0]
[   0    1    0    0]
[   0    0    1    0]
[   0    0    0    1]