passagemath-pari 10.6.32__cp314-cp314-musllinux_1_2_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of passagemath-pari might be problematic. Click here for more details.
- PARIKernel/__init__.py +2 -0
- PARIKernel/__main__.py +5 -0
- PARIKernel/io.cpython-314-x86_64-linux-musl.so +0 -0
- PARIKernel/io.pxd +7 -0
- PARIKernel/io.pyx +84 -0
- PARIKernel/kernel.cpython-314-x86_64-linux-musl.so +0 -0
- PARIKernel/kernel.pyx +260 -0
- PARIKernel/paridecl.pxd +95 -0
- PARIKernel/svg.cpython-314-x86_64-linux-musl.so +0 -0
- PARIKernel/svg.pyx +52 -0
- cypari2/__init__.py +8 -0
- cypari2/auto_paridecl.pxd +1070 -0
- cypari2/closure.cpython-314-x86_64-linux-musl.so +0 -0
- cypari2/closure.pxd +5 -0
- cypari2/closure.pyx +246 -0
- cypari2/convert.cpython-314-x86_64-linux-musl.so +0 -0
- cypari2/convert.pxd +80 -0
- cypari2/convert.pyx +613 -0
- cypari2/custom_block.cpython-314-x86_64-linux-musl.so +0 -0
- cypari2/custom_block.pyx +30 -0
- cypari2/cypari.h +13 -0
- cypari2/gen.cpython-314-x86_64-linux-musl.so +0 -0
- cypari2/gen.pxd +69 -0
- cypari2/gen.pyx +4819 -0
- cypari2/handle_error.cpython-314-x86_64-linux-musl.so +0 -0
- cypari2/handle_error.pxd +7 -0
- cypari2/handle_error.pyx +232 -0
- cypari2/pari_instance.cpython-314-x86_64-linux-musl.so +0 -0
- cypari2/pari_instance.pxd +27 -0
- cypari2/pari_instance.pyx +1438 -0
- cypari2/paridecl.pxd +5353 -0
- cypari2/paripriv.pxd +34 -0
- cypari2/pycore_long.h +98 -0
- cypari2/pycore_long.pxd +9 -0
- cypari2/stack.cpython-314-x86_64-linux-musl.so +0 -0
- cypari2/stack.pxd +27 -0
- cypari2/stack.pyx +278 -0
- cypari2/string_utils.cpython-314-x86_64-linux-musl.so +0 -0
- cypari2/string_utils.pxd +29 -0
- cypari2/string_utils.pyx +65 -0
- cypari2/types.pxd +147 -0
- passagemath_pari-10.6.32.data/data/etc/jupyter/nbconfig/notebook.d/gp-mode.json +5 -0
- passagemath_pari-10.6.32.data/data/share/jupyter/kernels/pari_jupyter/kernel.js +28 -0
- passagemath_pari-10.6.32.data/data/share/jupyter/kernels/pari_jupyter/kernel.json +6 -0
- passagemath_pari-10.6.32.data/data/share/jupyter/kernels/pari_jupyter/logo-64x64.png +0 -0
- passagemath_pari-10.6.32.data/data/share/jupyter/kernels/xeus-gp/kernel.json +13 -0
- passagemath_pari-10.6.32.data/data/share/jupyter/kernels/xeus-gp/logo-32x32.png +0 -0
- passagemath_pari-10.6.32.data/data/share/jupyter/kernels/xeus-gp/logo-64x64.png +0 -0
- passagemath_pari-10.6.32.data/data/share/jupyter/kernels/xeus-gp/logo-svg.svg +75 -0
- passagemath_pari-10.6.32.data/data/share/jupyter/nbextensions/gp-mode/gp.js +284 -0
- passagemath_pari-10.6.32.data/data/share/jupyter/nbextensions/gp-mode/main.js +15 -0
- passagemath_pari-10.6.32.dist-info/METADATA +209 -0
- passagemath_pari-10.6.32.dist-info/RECORD +331 -0
- passagemath_pari-10.6.32.dist-info/WHEEL +5 -0
- passagemath_pari-10.6.32.dist-info/top_level.txt +4 -0
- passagemath_pari.libs/libcrypto-f04afe95.so.3 +0 -0
- passagemath_pari.libs/libflint-fd6f12fc.so.21.0.0 +0 -0
- passagemath_pari.libs/libgcc_s-0cd532bd.so.1 +0 -0
- passagemath_pari.libs/libgf2x-9e30c3e3.so.3.0.0 +0 -0
- passagemath_pari.libs/libgfortran-2c33b284.so.5.0.0 +0 -0
- passagemath_pari.libs/libgivaro-9a94c711.so.9.2.1 +0 -0
- passagemath_pari.libs/libgmp-0e7fc84e.so.10.5.0 +0 -0
- passagemath_pari.libs/libgmpxx-9e08595c.so.4.7.0 +0 -0
- passagemath_pari.libs/libgsl-42cda06f.so.28.0.0 +0 -0
- passagemath_pari.libs/libmpfr-aaecbfc0.so.6.2.1 +0 -0
- passagemath_pari.libs/libncursesw-9c9e32c3.so.6.5 +0 -0
- passagemath_pari.libs/libntl-26885ca2.so.44.0.1 +0 -0
- passagemath_pari.libs/libopenblasp-r0-905cb27d.3.29.so +0 -0
- passagemath_pari.libs/libpari-gmp-tls-f31f908f.so.2.17.2 +0 -0
- passagemath_pari.libs/libquadmath-bb76a5fc.so.0.0.0 +0 -0
- passagemath_pari.libs/libreadline-06542304.so.8.2 +0 -0
- passagemath_pari.libs/libstdc++-5d72f927.so.6.0.33 +0 -0
- passagemath_pari.libs/libuuid-f3770415.so.1.3.0 +0 -0
- passagemath_pari.libs/libxeus-735780ff.so.13.1.0 +0 -0
- passagemath_pari.libs/libxeus-zmq-c68577b4.so.6.0.1 +0 -0
- passagemath_pari.libs/libzmq-1ba9a3da.so.5.2.5 +0 -0
- sage/all__sagemath_pari.py +26 -0
- sage/databases/all__sagemath_pari.py +7 -0
- sage/databases/conway.py +274 -0
- sage/ext/all__sagemath_pari.py +1 -0
- sage/ext/memory.cpython-314-x86_64-linux-musl.so +0 -0
- sage/ext/memory.pyx +98 -0
- sage/ext_data/pari/buzzard/DimensionSk.g +286 -0
- sage/ext_data/pari/buzzard/Tpprog.g +179 -0
- sage/ext_data/pari/buzzard/genusn.g +129 -0
- sage/ext_data/pari/dokchitser/computel.gp +740 -0
- sage/ext_data/pari/dokchitser/computel.gp.template +740 -0
- sage/ext_data/pari/dokchitser/ex-bsw +43 -0
- sage/ext_data/pari/dokchitser/ex-chgen +48 -0
- sage/ext_data/pari/dokchitser/ex-chqua +37 -0
- sage/ext_data/pari/dokchitser/ex-delta +35 -0
- sage/ext_data/pari/dokchitser/ex-eisen +30 -0
- sage/ext_data/pari/dokchitser/ex-gen2 +38 -0
- sage/ext_data/pari/dokchitser/ex-gen3 +49 -0
- sage/ext_data/pari/dokchitser/ex-gen4 +54 -0
- sage/ext_data/pari/dokchitser/ex-nf +48 -0
- sage/ext_data/pari/dokchitser/ex-shin +50 -0
- sage/ext_data/pari/dokchitser/ex-tau2 +30 -0
- sage/ext_data/pari/dokchitser/ex-zeta +27 -0
- sage/ext_data/pari/dokchitser/ex-zeta2 +47 -0
- sage/ext_data/pari/dokchitser/testall +13 -0
- sage/ext_data/pari/simon/ell.gp +2129 -0
- sage/ext_data/pari/simon/ellQ.gp +2151 -0
- sage/ext_data/pari/simon/ellcommon.gp +126 -0
- sage/ext_data/pari/simon/qfsolve.gp +722 -0
- sage/ext_data/pari/simon/resultant3.gp +306 -0
- sage/groups/all__sagemath_pari.py +3 -0
- sage/groups/pari_group.py +175 -0
- sage/interfaces/all__sagemath_pari.py +1 -0
- sage/interfaces/genus2reduction.py +464 -0
- sage/interfaces/gp.py +1114 -0
- sage/libs/all__sagemath_pari.py +2 -0
- sage/libs/linkages/__init__.py +1 -0
- sage/libs/linkages/padics/API.pxi +617 -0
- sage/libs/linkages/padics/Polynomial_ram.pxi +388 -0
- sage/libs/linkages/padics/Polynomial_shared.pxi +554 -0
- sage/libs/linkages/padics/__init__.py +1 -0
- sage/libs/linkages/padics/fmpz_poly_unram.pxi +869 -0
- sage/libs/linkages/padics/mpz.pxi +691 -0
- sage/libs/linkages/padics/relaxed/API.pxi +518 -0
- sage/libs/linkages/padics/relaxed/__init__.py +1 -0
- sage/libs/linkages/padics/relaxed/flint.pxi +543 -0
- sage/libs/linkages/padics/unram_shared.pxi +247 -0
- sage/libs/pari/__init__.py +210 -0
- sage/libs/pari/all.py +5 -0
- sage/libs/pari/convert_flint.cpython-314-x86_64-linux-musl.so +0 -0
- sage/libs/pari/convert_flint.pxd +14 -0
- sage/libs/pari/convert_flint.pyx +159 -0
- sage/libs/pari/convert_gmp.cpython-314-x86_64-linux-musl.so +0 -0
- sage/libs/pari/convert_gmp.pxd +14 -0
- sage/libs/pari/convert_gmp.pyx +210 -0
- sage/libs/pari/convert_sage.cpython-314-x86_64-linux-musl.so +0 -0
- sage/libs/pari/convert_sage.pxd +16 -0
- sage/libs/pari/convert_sage.pyx +588 -0
- sage/libs/pari/convert_sage_complex_double.cpython-314-x86_64-linux-musl.so +0 -0
- sage/libs/pari/convert_sage_complex_double.pxd +14 -0
- sage/libs/pari/convert_sage_complex_double.pyx +132 -0
- sage/libs/pari/convert_sage_matrix.cpython-314-x86_64-linux-musl.so +0 -0
- sage/libs/pari/convert_sage_matrix.pyx +106 -0
- sage/libs/pari/convert_sage_real_double.cpython-314-x86_64-linux-musl.so +0 -0
- sage/libs/pari/convert_sage_real_double.pxd +5 -0
- sage/libs/pari/convert_sage_real_double.pyx +14 -0
- sage/libs/pari/convert_sage_real_mpfr.cpython-314-x86_64-linux-musl.so +0 -0
- sage/libs/pari/convert_sage_real_mpfr.pxd +7 -0
- sage/libs/pari/convert_sage_real_mpfr.pyx +108 -0
- sage/libs/pari/misc.cpython-314-x86_64-linux-musl.so +0 -0
- sage/libs/pari/misc.pxd +4 -0
- sage/libs/pari/misc.pyx +26 -0
- sage/libs/pari/tests.py +1848 -0
- sage/matrix/all__sagemath_pari.py +1 -0
- sage/matrix/matrix_integer_pari.cpython-314-x86_64-linux-musl.so +0 -0
- sage/matrix/matrix_integer_pari.pyx +187 -0
- sage/matrix/matrix_rational_pari.cpython-314-x86_64-linux-musl.so +0 -0
- sage/matrix/matrix_rational_pari.pyx +160 -0
- sage/quadratic_forms/all__sagemath_pari.py +10 -0
- sage/quadratic_forms/genera/all.py +9 -0
- sage/quadratic_forms/genera/genus.py +3506 -0
- sage/quadratic_forms/genera/normal_form.py +1519 -0
- sage/quadratic_forms/genera/spinor_genus.py +243 -0
- sage/quadratic_forms/qfsolve.py +255 -0
- sage/quadratic_forms/quadratic_form__automorphisms.py +427 -0
- sage/quadratic_forms/quadratic_form__genus.py +141 -0
- sage/quadratic_forms/quadratic_form__local_density_interfaces.py +140 -0
- sage/quadratic_forms/quadratic_form__local_normal_form.py +421 -0
- sage/quadratic_forms/quadratic_form__local_representation_conditions.py +889 -0
- sage/quadratic_forms/quadratic_form__mass.py +69 -0
- sage/quadratic_forms/quadratic_form__mass__Conway_Sloane_masses.py +663 -0
- sage/quadratic_forms/quadratic_form__mass__Siegel_densities.py +373 -0
- sage/quadratic_forms/quadratic_form__siegel_product.py +198 -0
- sage/quadratic_forms/special_values.py +323 -0
- sage/rings/all__sagemath_pari.py +15 -0
- sage/rings/factorint_pari.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/factorint_pari.pyx +80 -0
- sage/rings/finite_rings/all__sagemath_pari.py +1 -0
- sage/rings/finite_rings/element_givaro.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/finite_rings/element_givaro.pxd +91 -0
- sage/rings/finite_rings/element_givaro.pyx +1769 -0
- sage/rings/finite_rings/element_ntl_gf2e.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/finite_rings/element_ntl_gf2e.pxd +22 -0
- sage/rings/finite_rings/element_ntl_gf2e.pyx +1333 -0
- sage/rings/finite_rings/element_pari_ffelt.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/finite_rings/element_pari_ffelt.pxd +13 -0
- sage/rings/finite_rings/element_pari_ffelt.pyx +1441 -0
- sage/rings/finite_rings/finite_field_givaro.py +612 -0
- sage/rings/finite_rings/finite_field_pari_ffelt.py +238 -0
- sage/rings/finite_rings/hom_finite_field_givaro.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/finite_rings/hom_finite_field_givaro.pxd +28 -0
- sage/rings/finite_rings/hom_finite_field_givaro.pyx +280 -0
- sage/rings/finite_rings/residue_field_givaro.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/finite_rings/residue_field_givaro.pyx +133 -0
- sage/rings/finite_rings/residue_field_pari_ffelt.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/finite_rings/residue_field_pari_ffelt.pyx +128 -0
- sage/rings/function_field/all__sagemath_pari.py +1 -0
- sage/rings/function_field/valuation.py +1450 -0
- sage/rings/function_field/valuation_ring.py +212 -0
- sage/rings/number_field/all__sagemath_pari.py +14 -0
- sage/rings/number_field/totallyreal.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/number_field/totallyreal.pyx +509 -0
- sage/rings/number_field/totallyreal_data.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/number_field/totallyreal_data.pxd +26 -0
- sage/rings/number_field/totallyreal_data.pyx +928 -0
- sage/rings/number_field/totallyreal_phc.py +144 -0
- sage/rings/number_field/totallyreal_rel.py +1018 -0
- sage/rings/padics/CA_template.pxi +1847 -0
- sage/rings/padics/CA_template_header.pxi +50 -0
- sage/rings/padics/CR_template.pxi +2563 -0
- sage/rings/padics/CR_template_header.pxi +57 -0
- sage/rings/padics/FM_template.pxi +1575 -0
- sage/rings/padics/FM_template_header.pxi +50 -0
- sage/rings/padics/FP_template.pxi +2176 -0
- sage/rings/padics/FP_template_header.pxi +57 -0
- sage/rings/padics/all.py +3 -0
- sage/rings/padics/all__sagemath_pari.py +11 -0
- sage/rings/padics/common_conversion.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/common_conversion.pxd +15 -0
- sage/rings/padics/common_conversion.pyx +508 -0
- sage/rings/padics/eisenstein_extension_generic.py +232 -0
- sage/rings/padics/factory.py +3623 -0
- sage/rings/padics/generic_nodes.py +1615 -0
- sage/rings/padics/lattice_precision.py +2889 -0
- sage/rings/padics/morphism.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/morphism.pxd +11 -0
- sage/rings/padics/morphism.pyx +366 -0
- sage/rings/padics/padic_base_generic.py +467 -0
- sage/rings/padics/padic_base_leaves.py +1235 -0
- sage/rings/padics/padic_capped_absolute_element.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/padic_capped_absolute_element.pxd +15 -0
- sage/rings/padics/padic_capped_absolute_element.pyx +520 -0
- sage/rings/padics/padic_capped_relative_element.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/padic_capped_relative_element.pxd +14 -0
- sage/rings/padics/padic_capped_relative_element.pyx +614 -0
- sage/rings/padics/padic_extension_generic.py +990 -0
- sage/rings/padics/padic_extension_leaves.py +738 -0
- sage/rings/padics/padic_fixed_mod_element.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/padic_fixed_mod_element.pxd +15 -0
- sage/rings/padics/padic_fixed_mod_element.pyx +584 -0
- sage/rings/padics/padic_floating_point_element.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/padic_floating_point_element.pxd +14 -0
- sage/rings/padics/padic_floating_point_element.pyx +447 -0
- sage/rings/padics/padic_generic_element.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/padic_generic_element.pxd +48 -0
- sage/rings/padics/padic_generic_element.pyx +4642 -0
- sage/rings/padics/padic_lattice_element.py +1342 -0
- sage/rings/padics/padic_printing.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/padic_printing.pxd +38 -0
- sage/rings/padics/padic_printing.pyx +1505 -0
- sage/rings/padics/padic_relaxed_element.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/padic_relaxed_element.pxd +56 -0
- sage/rings/padics/padic_relaxed_element.pyx +18 -0
- sage/rings/padics/padic_relaxed_errors.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/padic_relaxed_errors.pxd +11 -0
- sage/rings/padics/padic_relaxed_errors.pyx +71 -0
- sage/rings/padics/padic_template_element.pxi +1212 -0
- sage/rings/padics/padic_template_element_header.pxi +50 -0
- sage/rings/padics/padic_valuation.py +1423 -0
- sage/rings/padics/pow_computer_flint.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/pow_computer_flint.pxd +38 -0
- sage/rings/padics/pow_computer_flint.pyx +641 -0
- sage/rings/padics/pow_computer_relative.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/pow_computer_relative.pxd +29 -0
- sage/rings/padics/pow_computer_relative.pyx +415 -0
- sage/rings/padics/qadic_flint_CA.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/qadic_flint_CA.pxd +21 -0
- sage/rings/padics/qadic_flint_CA.pyx +130 -0
- sage/rings/padics/qadic_flint_CR.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/qadic_flint_CR.pxd +13 -0
- sage/rings/padics/qadic_flint_CR.pyx +172 -0
- sage/rings/padics/qadic_flint_FM.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/qadic_flint_FM.pxd +14 -0
- sage/rings/padics/qadic_flint_FM.pyx +111 -0
- sage/rings/padics/qadic_flint_FP.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/qadic_flint_FP.pxd +12 -0
- sage/rings/padics/qadic_flint_FP.pyx +165 -0
- sage/rings/padics/relative_extension_leaves.py +429 -0
- sage/rings/padics/relative_ramified_CA.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/relative_ramified_CA.pxd +9 -0
- sage/rings/padics/relative_ramified_CA.pyx +33 -0
- sage/rings/padics/relative_ramified_CR.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/relative_ramified_CR.pxd +8 -0
- sage/rings/padics/relative_ramified_CR.pyx +33 -0
- sage/rings/padics/relative_ramified_FM.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/relative_ramified_FM.pxd +9 -0
- sage/rings/padics/relative_ramified_FM.pyx +33 -0
- sage/rings/padics/relative_ramified_FP.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/padics/relative_ramified_FP.pxd +8 -0
- sage/rings/padics/relative_ramified_FP.pyx +33 -0
- sage/rings/padics/relaxed_template.pxi +4229 -0
- sage/rings/padics/relaxed_template_header.pxi +160 -0
- sage/rings/padics/tests.py +35 -0
- sage/rings/padics/tutorial.py +341 -0
- sage/rings/padics/unramified_extension_generic.py +335 -0
- sage/rings/padics/witt_vector.py +917 -0
- sage/rings/padics/witt_vector_ring.py +934 -0
- sage/rings/pari_ring.py +235 -0
- sage/rings/polynomial/all__sagemath_pari.py +1 -0
- sage/rings/polynomial/padics/all.py +1 -0
- sage/rings/polynomial/padics/polynomial_padic.py +360 -0
- sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py +1324 -0
- sage/rings/polynomial/padics/polynomial_padic_flat.py +72 -0
- sage/rings/power_series_pari.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/power_series_pari.pxd +6 -0
- sage/rings/power_series_pari.pyx +934 -0
- sage/rings/tate_algebra.py +1282 -0
- sage/rings/tate_algebra_element.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/tate_algebra_element.pxd +49 -0
- sage/rings/tate_algebra_element.pyx +3464 -0
- sage/rings/tate_algebra_ideal.cpython-314-x86_64-linux-musl.so +0 -0
- sage/rings/tate_algebra_ideal.pxd +7 -0
- sage/rings/tate_algebra_ideal.pyx +1307 -0
- sage/rings/valuation/all.py +7 -0
- sage/rings/valuation/augmented_valuation.py +2118 -0
- sage/rings/valuation/developing_valuation.py +362 -0
- sage/rings/valuation/gauss_valuation.py +812 -0
- sage/rings/valuation/inductive_valuation.py +1686 -0
- sage/rings/valuation/limit_valuation.py +946 -0
- sage/rings/valuation/mapped_valuation.py +656 -0
- sage/rings/valuation/scaled_valuation.py +322 -0
- sage/rings/valuation/trivial_valuation.py +382 -0
- sage/rings/valuation/valuation.py +1119 -0
- sage/rings/valuation/valuation_space.py +1615 -0
- sage/rings/valuation/valuations_catalog.py +10 -0
- sage/rings/valuation/value_group.py +697 -0
- sage/schemes/all__sagemath_pari.py +1 -0
- sage/schemes/elliptic_curves/all__sagemath_pari.py +1 -0
- sage/schemes/elliptic_curves/descent_two_isogeny_pari.cpython-314-x86_64-linux-musl.so +0 -0
- sage/schemes/elliptic_curves/descent_two_isogeny_pari.pyx +46 -0
- sage_wheels/bin/gp +0 -0
- sage_wheels/bin/gp2c +0 -0
- sage_wheels/bin/gp2c-run +57 -0
- sage_wheels/bin/xeus-gp +0 -0
- sage_wheels/share/gp2c/func.dsc +18414 -0
|
@@ -0,0 +1,3506 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-pari
|
|
2
|
+
# sage.doctest: needs sage.libs.pari sage.modules
|
|
3
|
+
r"""
|
|
4
|
+
Genus
|
|
5
|
+
|
|
6
|
+
AUTHORS:
|
|
7
|
+
|
|
8
|
+
- David Kohel & Gabriele Nebe (2007): First created
|
|
9
|
+
- Simon Brandhorst (2018): various bugfixes and printing
|
|
10
|
+
- Simon Brandhorst (2018): enumeration of genera
|
|
11
|
+
- Simon Brandhorst (2020): genus representative
|
|
12
|
+
"""
|
|
13
|
+
# ****************************************************************************
|
|
14
|
+
# Copyright (C) 2007 David Kohel <kohel@maths.usyd.edu.au>
|
|
15
|
+
# Gabriele Nebe <nebe@math.rwth-aachen.de>
|
|
16
|
+
# Simon Brandhorst <sbrandhorst@web.de>
|
|
17
|
+
#
|
|
18
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
|
19
|
+
#
|
|
20
|
+
# https://www.gnu.org/licenses/
|
|
21
|
+
# ****************************************************************************
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from copy import copy, deepcopy
|
|
24
|
+
|
|
25
|
+
from sage.misc.lazy_import import lazy_import
|
|
26
|
+
from sage.misc.misc_c import prod
|
|
27
|
+
from sage.misc.cachefunc import cached_method
|
|
28
|
+
from sage.arith.functions import lcm as LCM
|
|
29
|
+
from sage.arith.misc import fundamental_discriminant
|
|
30
|
+
from sage.matrix.matrix_space import MatrixSpace
|
|
31
|
+
from sage.matrix.constructor import matrix
|
|
32
|
+
from sage.rings.integer_ring import ZZ
|
|
33
|
+
from sage.rings.rational_field import QQ
|
|
34
|
+
from sage.rings.integer import Integer
|
|
35
|
+
from sage.misc.verbose import verbose
|
|
36
|
+
from sage.quadratic_forms.special_values import quadratic_L_function__exact
|
|
37
|
+
lazy_import('sage.quadratic_forms.genera.normal_form', '_min_nonsquare')
|
|
38
|
+
lazy_import('sage.interfaces.magma', 'magma')
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def genera(sig_pair, determinant, max_scale=None, even=False):
|
|
42
|
+
r"""
|
|
43
|
+
Return a list of all global genera with the given conditions.
|
|
44
|
+
|
|
45
|
+
Here a genus is called global if it is non-empty.
|
|
46
|
+
|
|
47
|
+
INPUT:
|
|
48
|
+
|
|
49
|
+
- ``sig_pair`` -- a pair of nonnegative integers giving the signature
|
|
50
|
+
|
|
51
|
+
- ``determinant`` -- integer; the sign is ignored
|
|
52
|
+
|
|
53
|
+
- ``max_scale`` -- (default: ``None``) an integer; the maximum scale of a
|
|
54
|
+
jordan block
|
|
55
|
+
|
|
56
|
+
- ``even`` -- boolean (default: ``False``)
|
|
57
|
+
|
|
58
|
+
OUTPUT:
|
|
59
|
+
|
|
60
|
+
A list of all (non-empty) global genera with the given conditions.
|
|
61
|
+
|
|
62
|
+
EXAMPLES::
|
|
63
|
+
|
|
64
|
+
sage: QuadraticForm.genera((4,0), 125, even=True)
|
|
65
|
+
[Genus of
|
|
66
|
+
None
|
|
67
|
+
Signature: (4, 0)
|
|
68
|
+
Genus symbol at 2: 1^-4
|
|
69
|
+
Genus symbol at 5: 1^1 5^3, Genus of
|
|
70
|
+
None
|
|
71
|
+
Signature: (4, 0)
|
|
72
|
+
Genus symbol at 2: 1^-4
|
|
73
|
+
Genus symbol at 5: 1^-2 5^1 25^-1, Genus of
|
|
74
|
+
None
|
|
75
|
+
Signature: (4, 0)
|
|
76
|
+
Genus symbol at 2: 1^-4
|
|
77
|
+
Genus symbol at 5: 1^2 5^1 25^1, Genus of
|
|
78
|
+
None
|
|
79
|
+
Signature: (4, 0)
|
|
80
|
+
Genus symbol at 2: 1^-4
|
|
81
|
+
Genus symbol at 5: 1^3 125^1]
|
|
82
|
+
"""
|
|
83
|
+
from sage.misc.mrange import mrange_iter
|
|
84
|
+
# input checks
|
|
85
|
+
determinant = ZZ(determinant)
|
|
86
|
+
sig_pair = (ZZ(sig_pair[0]), ZZ(sig_pair[1]))
|
|
87
|
+
even = bool(even)
|
|
88
|
+
if not all(s >= 0 for s in sig_pair):
|
|
89
|
+
raise ValueError("the signature vector must be a pair of nonnegative integers.")
|
|
90
|
+
if max_scale is None:
|
|
91
|
+
max_scale = determinant
|
|
92
|
+
else:
|
|
93
|
+
max_scale = ZZ(max_scale)
|
|
94
|
+
rank = sig_pair[0] + sig_pair[1]
|
|
95
|
+
genera = []
|
|
96
|
+
local_symbols = []
|
|
97
|
+
# every global genus has a 2-adic symbol
|
|
98
|
+
if determinant % 2:
|
|
99
|
+
local_symbols.append(_local_genera(2, rank, 0, 0, even=even))
|
|
100
|
+
# collect the p-adic symbols
|
|
101
|
+
for pn in determinant.factor():
|
|
102
|
+
p = pn[0]
|
|
103
|
+
det_val = pn[1]
|
|
104
|
+
mscale_p = max_scale.valuation(p)
|
|
105
|
+
local_symbol_p = _local_genera(p, rank, det_val, mscale_p, even)
|
|
106
|
+
local_symbols.append(local_symbol_p)
|
|
107
|
+
# take the cartesian product of the collection of all possible
|
|
108
|
+
# local genus symbols one for each prime
|
|
109
|
+
# and check which combinations produce a global genus
|
|
110
|
+
# TODO:
|
|
111
|
+
# we are overcounting. Find a more
|
|
112
|
+
# clever way to directly match the symbols for different primes.
|
|
113
|
+
for g in mrange_iter(local_symbols):
|
|
114
|
+
# create a Genus from a list of local symbols
|
|
115
|
+
G = GenusSymbol_global_ring(sig_pair, g, representative=None, check=True)
|
|
116
|
+
# discard the empty genera
|
|
117
|
+
if is_GlobalGenus(G):
|
|
118
|
+
genera.append(G)
|
|
119
|
+
# render the output deterministic for testing
|
|
120
|
+
genera.sort(key=lambda x: [s.symbol_tuple_list() for s in x.local_symbols()])
|
|
121
|
+
return genera
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# #35557: In Python < 3.10, a staticmethod cannot be called directly
|
|
125
|
+
_genera_staticmethod = staticmethod(genera)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _local_genera(p, rank, det_val, max_scale, even):
|
|
129
|
+
r"""
|
|
130
|
+
Return all `p`-adic genera with the given conditions.
|
|
131
|
+
|
|
132
|
+
This is a helper function for :meth:`genera`.
|
|
133
|
+
No input checks are done.
|
|
134
|
+
|
|
135
|
+
INPUT:
|
|
136
|
+
|
|
137
|
+
- ``p`` -- a prime number
|
|
138
|
+
|
|
139
|
+
- ``rank`` -- the rank of this genus
|
|
140
|
+
|
|
141
|
+
- ``det_val`` -- valuation of the determinant at `p`
|
|
142
|
+
|
|
143
|
+
- ``max_scale`` -- integer the maximal scale of a jordan block
|
|
144
|
+
|
|
145
|
+
- ``even`` -- boolean; ignored if `p` is not `2`
|
|
146
|
+
|
|
147
|
+
EXAMPLES::
|
|
148
|
+
|
|
149
|
+
sage: from sage.quadratic_forms.genera.genus import _local_genera
|
|
150
|
+
sage: _local_genera(2,3,1,2,False)
|
|
151
|
+
[Genus symbol at 2: 1^-2 [2^1]_1,
|
|
152
|
+
Genus symbol at 2: 1^2 [2^1]_1,
|
|
153
|
+
Genus symbol at 2: 1^2 [2^1]_7,
|
|
154
|
+
Genus symbol at 2: [1^2 2^1]_3,
|
|
155
|
+
Genus symbol at 2: 1^-2 [2^1]_7,
|
|
156
|
+
Genus symbol at 2: [1^-2 2^1]_7,
|
|
157
|
+
Genus symbol at 2: [1^-2 2^1]_1,
|
|
158
|
+
Genus symbol at 2: [1^2 2^1]_7,
|
|
159
|
+
Genus symbol at 2: [1^2 2^1]_5,
|
|
160
|
+
Genus symbol at 2: [1^-2 2^1]_3,
|
|
161
|
+
Genus symbol at 2: [1^-2 2^1]_5,
|
|
162
|
+
Genus symbol at 2: [1^2 2^1]_1]
|
|
163
|
+
|
|
164
|
+
Setting a maximum scale::
|
|
165
|
+
|
|
166
|
+
sage: _local_genera(5, 2, 2, 1, True)
|
|
167
|
+
[Genus symbol at 5: 5^-2, Genus symbol at 5: 5^2]
|
|
168
|
+
sage: _local_genera(5, 2, 2, 2, True)
|
|
169
|
+
[Genus symbol at 5: 1^-1 25^-1,
|
|
170
|
+
Genus symbol at 5: 1^1 25^-1,
|
|
171
|
+
Genus symbol at 5: 1^-1 25^1,
|
|
172
|
+
Genus symbol at 5: 1^1 25^1,
|
|
173
|
+
Genus symbol at 5: 5^-2,
|
|
174
|
+
Genus symbol at 5: 5^2]
|
|
175
|
+
"""
|
|
176
|
+
from sage.misc.mrange import cantor_product
|
|
177
|
+
from sage.combinat.integer_lists.invlex import IntegerListsLex
|
|
178
|
+
scales_rks = [] # contains possibilities for scales and ranks
|
|
179
|
+
for rkseq in IntegerListsLex(rank, length=max_scale + 1): # rank sequences
|
|
180
|
+
# sum(rkseq) = rank
|
|
181
|
+
# len(rkseq) = max_scale + 1
|
|
182
|
+
# now assure that we get the right determinant
|
|
183
|
+
d = 0
|
|
184
|
+
pgensymbol = []
|
|
185
|
+
for i in range(max_scale + 1):
|
|
186
|
+
d += i * rkseq[i]
|
|
187
|
+
# blocks of rank 0 are omitted
|
|
188
|
+
if rkseq[i] != 0:
|
|
189
|
+
pgensymbol.append([i, rkseq[i], 0])
|
|
190
|
+
if d == det_val:
|
|
191
|
+
scales_rks.append(pgensymbol)
|
|
192
|
+
# add possible determinant square classes
|
|
193
|
+
symbols = []
|
|
194
|
+
if p != 2:
|
|
195
|
+
for g in scales_rks:
|
|
196
|
+
n = len(g)
|
|
197
|
+
for v in cantor_product([-1, 1], repeat=n):
|
|
198
|
+
g1 = deepcopy(g)
|
|
199
|
+
for k in range(n):
|
|
200
|
+
g1[k][2] = v[k]
|
|
201
|
+
g1 = Genus_Symbol_p_adic_ring(p, g1)
|
|
202
|
+
symbols.append(g1)
|
|
203
|
+
# for p == 2 we have to include determinant, even/odd, oddity
|
|
204
|
+
# further restrictions apply and are deferred to _blocks
|
|
205
|
+
# (brute force sieving is too slow)
|
|
206
|
+
# TODO: If this is too slow, enumerate only the canonical symbols.
|
|
207
|
+
# as a drawback one has to reconstruct the symbol from the canonical symbol
|
|
208
|
+
# this is more work for the programmer
|
|
209
|
+
if p == 2:
|
|
210
|
+
for g in scales_rks:
|
|
211
|
+
poss_blocks = []
|
|
212
|
+
for b in g:
|
|
213
|
+
b += [0, 0]
|
|
214
|
+
poss_blocks.append(_blocks(b, even_only=(even and b[0] == 0)))
|
|
215
|
+
for g1 in cantor_product(*poss_blocks):
|
|
216
|
+
g1 = list(g1)
|
|
217
|
+
if is_2_adic_genus(g1):
|
|
218
|
+
g1 = Genus_Symbol_p_adic_ring(p, g1)
|
|
219
|
+
# some of our symbols have the same canonical symbol
|
|
220
|
+
# thus they are equivalent - we want only one in
|
|
221
|
+
# each equivalence class
|
|
222
|
+
if g1 not in symbols:
|
|
223
|
+
symbols.append(g1)
|
|
224
|
+
return symbols
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _blocks(b, even_only=False):
|
|
228
|
+
r"""
|
|
229
|
+
Return all viable `2`-adic jordan blocks with rank and scale given by ``b``.
|
|
230
|
+
|
|
231
|
+
This is a helper function for :meth:`_local_genera`.
|
|
232
|
+
It is based on the existence conditions for a modular `2`-adic genus symbol.
|
|
233
|
+
|
|
234
|
+
INPUT:
|
|
235
|
+
|
|
236
|
+
- ``b`` -- list of `5` nonnegative integers the first two are kept
|
|
237
|
+
and all possibilities for the remaining `3` are enumerated
|
|
238
|
+
|
|
239
|
+
- ``even_only`` -- boolean (default: ``True``); if set, the blocks are even
|
|
240
|
+
|
|
241
|
+
EXAMPLES::
|
|
242
|
+
|
|
243
|
+
sage: from sage.quadratic_forms.genera.genus import _blocks
|
|
244
|
+
sage: _blocks([15, 2, 0, 0, 0])
|
|
245
|
+
[[15, 2, 3, 0, 0],
|
|
246
|
+
[15, 2, 7, 0, 0],
|
|
247
|
+
[15, 2, 1, 1, 2],
|
|
248
|
+
[15, 2, 5, 1, 6],
|
|
249
|
+
[15, 2, 1, 1, 6],
|
|
250
|
+
[15, 2, 5, 1, 2],
|
|
251
|
+
[15, 2, 7, 1, 0],
|
|
252
|
+
[15, 2, 3, 1, 4]]
|
|
253
|
+
"""
|
|
254
|
+
blocks = []
|
|
255
|
+
rk = b[1]
|
|
256
|
+
# recall: 2-genus_symbol is [scale, rank, det, even/odd, oddity]
|
|
257
|
+
if rk == 0:
|
|
258
|
+
assert b[2] == 1
|
|
259
|
+
assert b[3] == 0
|
|
260
|
+
assert b[4] == 0
|
|
261
|
+
blocks.append(copy(b))
|
|
262
|
+
elif rk == 1 and not even_only:
|
|
263
|
+
for det in [1, 3, 5, 7]:
|
|
264
|
+
b1 = copy(b)
|
|
265
|
+
b1[2] = det
|
|
266
|
+
b1[3] = 1
|
|
267
|
+
b1[4] = det
|
|
268
|
+
blocks.append(b1)
|
|
269
|
+
elif rk == 2:
|
|
270
|
+
b1 = copy(b)
|
|
271
|
+
# even case
|
|
272
|
+
b1[3] = 0
|
|
273
|
+
b1[4] = 0
|
|
274
|
+
b1[2] = 3
|
|
275
|
+
blocks.append(b1)
|
|
276
|
+
b1 = copy(b1)
|
|
277
|
+
b1[2] = 7
|
|
278
|
+
blocks.append(b1)
|
|
279
|
+
# odd case
|
|
280
|
+
if not even_only:
|
|
281
|
+
# format (det, oddity)
|
|
282
|
+
for s in [(1, 2), (5, 6), (1, 6), (5, 2), (7, 0), (3, 4)]:
|
|
283
|
+
b1 = copy(b)
|
|
284
|
+
b1[2] = s[0]
|
|
285
|
+
b1[3] = 1
|
|
286
|
+
b1[4] = s[1]
|
|
287
|
+
blocks.append(b1)
|
|
288
|
+
elif rk % 2 == 0:
|
|
289
|
+
# the even case has even rank
|
|
290
|
+
b1 = copy(b)
|
|
291
|
+
b1[3] = 0
|
|
292
|
+
b1[4] = 0
|
|
293
|
+
d = (-1)**(rk // 2) % 8
|
|
294
|
+
for det in [d, d * (-3) % 8]:
|
|
295
|
+
b1 = copy(b1)
|
|
296
|
+
b1[2] = det
|
|
297
|
+
blocks.append(b1)
|
|
298
|
+
# odd case
|
|
299
|
+
if not even_only:
|
|
300
|
+
for s in [(1, 2), (5, 6), (1, 6), (5, 2), (7, 0), (3, 4)]:
|
|
301
|
+
b1 = copy(b)
|
|
302
|
+
b1[2] = s[0]*(-1)**(rk // 2 - 1) % 8
|
|
303
|
+
b1[3] = 1
|
|
304
|
+
b1[4] = s[1]
|
|
305
|
+
blocks.append(b1)
|
|
306
|
+
for s in [(1, 4), (5, 0)]:
|
|
307
|
+
b1 = copy(b)
|
|
308
|
+
b1[2] = s[0]*(-1)**(rk // 2 - 2) % 8
|
|
309
|
+
b1[3] = 1
|
|
310
|
+
b1[4] = s[1]
|
|
311
|
+
blocks.append(b1)
|
|
312
|
+
elif rk % 2 == 1 and not even_only:
|
|
313
|
+
# odd case
|
|
314
|
+
for t in [1, 3, 5, 7]:
|
|
315
|
+
d = (-1)**(rk//2)*t % 8
|
|
316
|
+
for det in [d, -3*d % 8]:
|
|
317
|
+
b1 = copy(b)
|
|
318
|
+
b1[2] = det
|
|
319
|
+
b1[3] = 1
|
|
320
|
+
b1[4] = t
|
|
321
|
+
blocks.append(b1)
|
|
322
|
+
# convert ints to integers
|
|
323
|
+
return [[ZZ(i) for i in bl] for bl in blocks]
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def Genus(A, factored_determinant=None):
|
|
327
|
+
r"""
|
|
328
|
+
Given a nonsingular symmetric matrix `A`, return the genus of `A`.
|
|
329
|
+
|
|
330
|
+
INPUT:
|
|
331
|
+
|
|
332
|
+
- ``A`` -- a symmetric matrix with integer coefficients
|
|
333
|
+
|
|
334
|
+
- ``factored_determinant`` -- (default: ``None``) a :class:`Factorization` object,
|
|
335
|
+
the factored determinant of ``A``
|
|
336
|
+
|
|
337
|
+
OUTPUT:
|
|
338
|
+
|
|
339
|
+
A :class:`GenusSymbol_global_ring` object, encoding the Conway-Sloane
|
|
340
|
+
genus symbol of the quadratic form whose Gram matrix is `A`.
|
|
341
|
+
|
|
342
|
+
EXAMPLES::
|
|
343
|
+
|
|
344
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 1, 1, 2])
|
|
345
|
+
sage: Genus(A)
|
|
346
|
+
Genus of
|
|
347
|
+
[1 1]
|
|
348
|
+
[1 2]
|
|
349
|
+
Signature: (2, 0)
|
|
350
|
+
Genus symbol at 2: [1^2]_2
|
|
351
|
+
|
|
352
|
+
sage: A = Matrix(ZZ, 2, 2, [2, 1, 1, 2])
|
|
353
|
+
sage: Genus(A, A.det().factor())
|
|
354
|
+
Genus of
|
|
355
|
+
[2 1]
|
|
356
|
+
[1 2]
|
|
357
|
+
Signature: (2, 0)
|
|
358
|
+
Genus symbol at 2: 1^-2
|
|
359
|
+
Genus symbol at 3: 1^-1 3^-1
|
|
360
|
+
"""
|
|
361
|
+
if factored_determinant is None:
|
|
362
|
+
D = A.determinant()
|
|
363
|
+
D = 2*D
|
|
364
|
+
D = D.factor()
|
|
365
|
+
else:
|
|
366
|
+
D = factored_determinant * 2
|
|
367
|
+
sig_pair = signature_pair_of_matrix(A)
|
|
368
|
+
local_symbols = []
|
|
369
|
+
for f in D:
|
|
370
|
+
p = f[0]
|
|
371
|
+
val = f[1]
|
|
372
|
+
symbol = p_adic_symbol(A, p, val=val)
|
|
373
|
+
G = Genus_Symbol_p_adic_ring(p, symbol)
|
|
374
|
+
local_symbols.append(G)
|
|
375
|
+
return GenusSymbol_global_ring(sig_pair, local_symbols, representative=A)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def LocalGenusSymbol(A, p):
|
|
379
|
+
r"""
|
|
380
|
+
Return the local symbol of `A` at the prime `p`.
|
|
381
|
+
|
|
382
|
+
INPUT:
|
|
383
|
+
|
|
384
|
+
- ``A`` -- a symmetric, non-singular matrix with coefficients in `\ZZ`
|
|
385
|
+
- ``p`` -- a prime number
|
|
386
|
+
|
|
387
|
+
OUTPUT:
|
|
388
|
+
|
|
389
|
+
A :class:`Genus_Symbol_p_adic_ring` object, encoding the Conway-Sloane
|
|
390
|
+
genus symbol at `p` of the quadratic form whose Gram matrix is `A`.
|
|
391
|
+
|
|
392
|
+
EXAMPLES::
|
|
393
|
+
|
|
394
|
+
sage: from sage.quadratic_forms.genera.genus import LocalGenusSymbol
|
|
395
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 1, 1, 2])
|
|
396
|
+
sage: LocalGenusSymbol(A, 2)
|
|
397
|
+
Genus symbol at 2: [1^2]_2
|
|
398
|
+
sage: LocalGenusSymbol(A, 3)
|
|
399
|
+
Genus symbol at 3: 1^2
|
|
400
|
+
|
|
401
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 0, 0, 2])
|
|
402
|
+
sage: LocalGenusSymbol(A, 2)
|
|
403
|
+
Genus symbol at 2: [1^1 2^1]_2
|
|
404
|
+
sage: LocalGenusSymbol(A, 3)
|
|
405
|
+
Genus symbol at 3: 1^-2
|
|
406
|
+
"""
|
|
407
|
+
val = A.determinant().valuation(p)
|
|
408
|
+
symbol = p_adic_symbol(A, p, val=val)
|
|
409
|
+
return Genus_Symbol_p_adic_ring(p, symbol)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def is_GlobalGenus(G) -> bool:
|
|
413
|
+
r"""
|
|
414
|
+
Return if `G` represents the genus of a global quadratic form or lattice.
|
|
415
|
+
|
|
416
|
+
INPUT:
|
|
417
|
+
|
|
418
|
+
- ``G`` -- :class:`GenusSymbol_global_ring` object
|
|
419
|
+
|
|
420
|
+
OUTPUT: boolean
|
|
421
|
+
|
|
422
|
+
EXAMPLES::
|
|
423
|
+
|
|
424
|
+
sage: from sage.quadratic_forms.genera.genus import is_GlobalGenus
|
|
425
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 1, 1, 2])
|
|
426
|
+
sage: G = Genus(A)
|
|
427
|
+
sage: is_GlobalGenus(G)
|
|
428
|
+
True
|
|
429
|
+
sage: G = Genus(matrix.diagonal([2, 2, 2, 2]))
|
|
430
|
+
sage: G._local_symbols[0]._symbol = [[0,2,3,0,0], [1,2,5,1,0]]
|
|
431
|
+
sage: G._representative=None
|
|
432
|
+
sage: is_GlobalGenus(G)
|
|
433
|
+
False
|
|
434
|
+
"""
|
|
435
|
+
D = G.determinant()
|
|
436
|
+
r, s = G.signature_pair()
|
|
437
|
+
oddity = r - s
|
|
438
|
+
for loc in G._local_symbols:
|
|
439
|
+
p = loc._prime
|
|
440
|
+
sym = loc._symbol
|
|
441
|
+
v = sum([ss[0] * ss[1] for ss in sym])
|
|
442
|
+
a = D // (p**v)
|
|
443
|
+
b = ZZ.prod(ss[2] for ss in sym)
|
|
444
|
+
if p == 2:
|
|
445
|
+
if not is_2_adic_genus(sym):
|
|
446
|
+
verbose(mesg="False in is_2_adic_genus(sym)", level=2)
|
|
447
|
+
return False
|
|
448
|
+
if (a*b).kronecker(p) != 1:
|
|
449
|
+
verbose(mesg=f"False in ({a}*{b}).kronecker({p})",
|
|
450
|
+
level=2)
|
|
451
|
+
return False
|
|
452
|
+
oddity -= loc.excess()
|
|
453
|
+
else:
|
|
454
|
+
if a.kronecker(p) != b:
|
|
455
|
+
verbose(mesg=f"False in {a}.kronecker({p}) != *{b}",
|
|
456
|
+
level=2)
|
|
457
|
+
return False
|
|
458
|
+
oddity += loc.excess()
|
|
459
|
+
if oddity % 8 != 0:
|
|
460
|
+
verbose(mesg="False in oddity", level=2)
|
|
461
|
+
return False
|
|
462
|
+
return True
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def is_2_adic_genus(genus_symbol_quintuple_list) -> bool:
|
|
466
|
+
r"""
|
|
467
|
+
Given a `2`-adic local symbol (as the underlying list of quintuples)
|
|
468
|
+
check whether it is the `2`-adic symbol of a `2`-adic form.
|
|
469
|
+
|
|
470
|
+
INPUT:
|
|
471
|
+
|
|
472
|
+
- ``genus_symbol_quintuple_list`` -- a quintuple of integers (with certain
|
|
473
|
+
restrictions)
|
|
474
|
+
|
|
475
|
+
OUTPUT: boolean
|
|
476
|
+
|
|
477
|
+
EXAMPLES::
|
|
478
|
+
|
|
479
|
+
sage: from sage.quadratic_forms.genera.genus import LocalGenusSymbol, is_2_adic_genus
|
|
480
|
+
|
|
481
|
+
sage: A = Matrix(ZZ, 2, 2, [1,1,1,2])
|
|
482
|
+
sage: G2 = LocalGenusSymbol(A, 2)
|
|
483
|
+
sage: is_2_adic_genus(G2.symbol_tuple_list())
|
|
484
|
+
True
|
|
485
|
+
|
|
486
|
+
sage: A = Matrix(ZZ, 2, 2, [1,1,1,2])
|
|
487
|
+
sage: G3 = LocalGenusSymbol(A, 3)
|
|
488
|
+
sage: is_2_adic_genus(G3.symbol_tuple_list()) # This raises an error
|
|
489
|
+
Traceback (most recent call last):
|
|
490
|
+
...
|
|
491
|
+
TypeError: The genus symbols are not quintuples,
|
|
492
|
+
so it's not a genus symbol for the prime p=2.
|
|
493
|
+
|
|
494
|
+
sage: A = Matrix(ZZ, 2, 2, [1,0,0,2])
|
|
495
|
+
sage: G2 = LocalGenusSymbol(A, 2)
|
|
496
|
+
sage: is_2_adic_genus(G2.symbol_tuple_list())
|
|
497
|
+
True
|
|
498
|
+
"""
|
|
499
|
+
# TO DO: Add explicit checking for the prime p here to ensure it's p=2... not just the quintuple checking below
|
|
500
|
+
|
|
501
|
+
for s in genus_symbol_quintuple_list:
|
|
502
|
+
|
|
503
|
+
# Check that we have a quintuple (i.e. that p=2 and not p >2)
|
|
504
|
+
if len(s) != 5:
|
|
505
|
+
raise TypeError("The genus symbols are not quintuples, so it's not a genus symbol for the prime p=2.")
|
|
506
|
+
|
|
507
|
+
# Check the Conway-Sloane conditions
|
|
508
|
+
if s[1] == 1:
|
|
509
|
+
if s[3] == 0 or s[2] != s[4]:
|
|
510
|
+
return False
|
|
511
|
+
if s[1] == 2 and s[3] == 1:
|
|
512
|
+
if s[2] % 8 in (1, 7):
|
|
513
|
+
if s[4] not in (0, 2, 6):
|
|
514
|
+
return False
|
|
515
|
+
if s[2] % 8 in (3, 5):
|
|
516
|
+
if s[4] not in (2, 4, 6):
|
|
517
|
+
return False
|
|
518
|
+
if (s[1] - s[4]) % 2:
|
|
519
|
+
return False
|
|
520
|
+
if s[3] == 0 and s[4] != 0:
|
|
521
|
+
return False
|
|
522
|
+
return True
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def canonical_2_adic_compartments(genus_symbol_quintuple_list):
|
|
526
|
+
r"""
|
|
527
|
+
Given a `2`-adic local symbol (as the underlying list of quintuples)
|
|
528
|
+
this returns a list of lists of indices of the
|
|
529
|
+
``genus_symbol_quintuple_list`` which are in the same compartment. A
|
|
530
|
+
compartment is defined to be a maximal interval of Jordan
|
|
531
|
+
components all (scaled) of type I (i.e. odd).
|
|
532
|
+
|
|
533
|
+
INPUT:
|
|
534
|
+
|
|
535
|
+
- ``genus_symbol_quintuple_list`` -- a quintuple of integers (with certain
|
|
536
|
+
restrictions)
|
|
537
|
+
|
|
538
|
+
OUTPUT: list of lists of integers
|
|
539
|
+
|
|
540
|
+
EXAMPLES::
|
|
541
|
+
|
|
542
|
+
sage: from sage.quadratic_forms.genera.genus import LocalGenusSymbol
|
|
543
|
+
sage: from sage.quadratic_forms.genera.genus import canonical_2_adic_compartments
|
|
544
|
+
|
|
545
|
+
sage: A = Matrix(ZZ, 2, 2, [1,1,1,2])
|
|
546
|
+
sage: G2 = LocalGenusSymbol(A, 2); G2.symbol_tuple_list()
|
|
547
|
+
[[0, 2, 1, 1, 2]]
|
|
548
|
+
sage: canonical_2_adic_compartments(G2.symbol_tuple_list())
|
|
549
|
+
[[0]]
|
|
550
|
+
|
|
551
|
+
sage: A = Matrix(ZZ, 2, 2, [1,0,0,2])
|
|
552
|
+
sage: G2 = LocalGenusSymbol(A, 2); G2.symbol_tuple_list()
|
|
553
|
+
[[0, 1, 1, 1, 1], [1, 1, 1, 1, 1]]
|
|
554
|
+
sage: canonical_2_adic_compartments(G2.symbol_tuple_list())
|
|
555
|
+
[[0, 1]]
|
|
556
|
+
|
|
557
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1,2,3,4]).Hessian_matrix()
|
|
558
|
+
sage: G2 = LocalGenusSymbol(A, 2); G2.symbol_tuple_list()
|
|
559
|
+
[[1, 2, 3, 1, 4], [2, 1, 1, 1, 1], [3, 1, 1, 1, 1]]
|
|
560
|
+
sage: canonical_2_adic_compartments(G2.symbol_tuple_list())
|
|
561
|
+
[[0, 1, 2]]
|
|
562
|
+
|
|
563
|
+
sage: A = Matrix(ZZ, 2, 2, [2,1,1,2])
|
|
564
|
+
sage: G2 = LocalGenusSymbol(A, 2); G2.symbol_tuple_list()
|
|
565
|
+
[[0, 2, 3, 0, 0]]
|
|
566
|
+
sage: canonical_2_adic_compartments(G2.symbol_tuple_list()) # No compartments here!
|
|
567
|
+
[]
|
|
568
|
+
|
|
569
|
+
.. NOTE::
|
|
570
|
+
|
|
571
|
+
See [CS1999]_ Conway-Sloane 3rd edition, pp. 381-382 for definitions
|
|
572
|
+
and examples.
|
|
573
|
+
"""
|
|
574
|
+
symbol = genus_symbol_quintuple_list
|
|
575
|
+
compartments = []
|
|
576
|
+
i = 0
|
|
577
|
+
r = len(symbol)
|
|
578
|
+
while i < r:
|
|
579
|
+
s = symbol[i]
|
|
580
|
+
if s[3] == 1:
|
|
581
|
+
v = s[0]
|
|
582
|
+
c = []
|
|
583
|
+
while i < r and symbol[i][3] == 1 and symbol[i][0] == v:
|
|
584
|
+
c.append(i)
|
|
585
|
+
i += 1
|
|
586
|
+
v += 1
|
|
587
|
+
compartments.append(c)
|
|
588
|
+
else:
|
|
589
|
+
i += 1
|
|
590
|
+
return compartments
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def canonical_2_adic_trains(genus_symbol_quintuple_list, compartments=None):
|
|
594
|
+
r"""
|
|
595
|
+
Given a `2`-adic local symbol (as the underlying list of quintuples)
|
|
596
|
+
this returns a list of lists of indices of the
|
|
597
|
+
``genus_symbol_quintuple_list`` which are in the same train. A train
|
|
598
|
+
is defined to be a maximal interval of Jordan components so that
|
|
599
|
+
at least one of each adjacent pair (allowing zero-dimensional
|
|
600
|
+
Jordan components) is (scaled) of type I (i.e. odd).
|
|
601
|
+
Note that an interval of length one respects this condition as
|
|
602
|
+
there is no pair in this interval.
|
|
603
|
+
In particular, every Jordan component is part of a train.
|
|
604
|
+
|
|
605
|
+
INPUT:
|
|
606
|
+
|
|
607
|
+
- ``genus_symbol_quintuple_list`` -- a quintuple of integers (with certain
|
|
608
|
+
restrictions).
|
|
609
|
+
- ``compartments`` -- this argument is deprecated
|
|
610
|
+
|
|
611
|
+
OUTPUT: list of lists of distinct integers
|
|
612
|
+
|
|
613
|
+
EXAMPLES::
|
|
614
|
+
|
|
615
|
+
sage: from sage.quadratic_forms.genera.genus import LocalGenusSymbol
|
|
616
|
+
sage: from sage.quadratic_forms.genera.genus import canonical_2_adic_compartments
|
|
617
|
+
sage: from sage.quadratic_forms.genera.genus import canonical_2_adic_trains
|
|
618
|
+
|
|
619
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 1, 1, 2])
|
|
620
|
+
sage: G2 = LocalGenusSymbol(A, 2); G2.symbol_tuple_list()
|
|
621
|
+
[[0, 2, 1, 1, 2]]
|
|
622
|
+
sage: canonical_2_adic_trains(G2.symbol_tuple_list())
|
|
623
|
+
[[0]]
|
|
624
|
+
|
|
625
|
+
sage: A = Matrix(ZZ, 2, 2, [1,0,0,2])
|
|
626
|
+
sage: G2 = LocalGenusSymbol(A, 2); G2.symbol_tuple_list()
|
|
627
|
+
[[0, 1, 1, 1, 1], [1, 1, 1, 1, 1]]
|
|
628
|
+
sage: canonical_2_adic_compartments(G2.symbol_tuple_list())
|
|
629
|
+
[[0, 1]]
|
|
630
|
+
|
|
631
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1,2,3,4]).Hessian_matrix()
|
|
632
|
+
sage: G2 = LocalGenusSymbol(A, 2); G2.symbol_tuple_list()
|
|
633
|
+
[[1, 2, 3, 1, 4], [2, 1, 1, 1, 1], [3, 1, 1, 1, 1]]
|
|
634
|
+
sage: canonical_2_adic_trains(G2.symbol_tuple_list())
|
|
635
|
+
[[0, 1, 2]]
|
|
636
|
+
|
|
637
|
+
sage: A = Matrix(ZZ, 2, 2, [2, 1, 1, 2])
|
|
638
|
+
sage: G2 = LocalGenusSymbol(A, 2); G2.symbol_tuple_list()
|
|
639
|
+
[[0, 2, 3, 0, 0]]
|
|
640
|
+
sage: canonical_2_adic_trains(G2.symbol_tuple_list())
|
|
641
|
+
[[0]]
|
|
642
|
+
sage: symbol = [[0, 1, 1, 1, 1], [1, 2, -1, 0, 0], [2, 1, 1, 1, 1],
|
|
643
|
+
....: [3, 1, 1, 1, 1], [4, 1, 1, 1, 1], [5, 2, -1, 0, 0],
|
|
644
|
+
....: [7, 1, 1, 1, 1], [10, 1, 1, 1, 1], [11, 1, 1, 1, 1], [12, 1, 1, 1, 1]]
|
|
645
|
+
sage: canonical_2_adic_trains(symbol)
|
|
646
|
+
[[0, 1, 2, 3, 4, 5], [6], [7, 8, 9]]
|
|
647
|
+
|
|
648
|
+
Check that :issue:`24818` is fixed::
|
|
649
|
+
|
|
650
|
+
sage: symbol = [[0, 1, 1, 1, 1], [1, 3, 1, 1, 1]]
|
|
651
|
+
sage: canonical_2_adic_trains(symbol)
|
|
652
|
+
[[0, 1]]
|
|
653
|
+
|
|
654
|
+
.. NOTE::
|
|
655
|
+
|
|
656
|
+
See [CS1999]_, pp. 381-382 for definitions and examples.
|
|
657
|
+
"""
|
|
658
|
+
if compartments is not None:
|
|
659
|
+
from sage.misc.superseded import deprecation
|
|
660
|
+
deprecation(23955, "the compartments keyword has been deprecated")
|
|
661
|
+
|
|
662
|
+
# avoid a special case for the end of symbol
|
|
663
|
+
# if a jordan component has rank zero it is considered even.
|
|
664
|
+
symbol = genus_symbol_quintuple_list
|
|
665
|
+
symbol.append([symbol[-1][0]+1, 0, 1, 0, 0]) # We have just modified the input globally!
|
|
666
|
+
# Hence, we have to remove the last entry of symbol at the end.
|
|
667
|
+
try:
|
|
668
|
+
|
|
669
|
+
trains = []
|
|
670
|
+
new_train = [0]
|
|
671
|
+
for i in range(1, len(symbol) - 1):
|
|
672
|
+
# start a new train if there are two adjacent even symbols
|
|
673
|
+
prev, cur = symbol[i-1:i+1]
|
|
674
|
+
if cur[0] - prev[0] > 2:
|
|
675
|
+
trains.append(new_train)
|
|
676
|
+
new_train = [i] # create a new train starting at
|
|
677
|
+
elif (cur[0] - prev[0] == 2) and cur[3]*prev[3] == 0:
|
|
678
|
+
trains.append(new_train)
|
|
679
|
+
new_train = [i]
|
|
680
|
+
elif prev[3] == 0 and cur[3] == 0:
|
|
681
|
+
trains.append(new_train)
|
|
682
|
+
new_train = [i]
|
|
683
|
+
else:
|
|
684
|
+
# there is an odd jordan block adjacent to this jordan block
|
|
685
|
+
# the train continues
|
|
686
|
+
new_train.append(i)
|
|
687
|
+
# the last train was never added.
|
|
688
|
+
trains.append(new_train)
|
|
689
|
+
return trains
|
|
690
|
+
finally:
|
|
691
|
+
# revert the input list to its original state
|
|
692
|
+
symbol.pop()
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def canonical_2_adic_reduction(genus_symbol_quintuple_list):
|
|
696
|
+
r"""
|
|
697
|
+
Given a `2`-adic local symbol (as the underlying list of quintuples)
|
|
698
|
+
this returns a canonical `2`-adic symbol (again as a raw list of
|
|
699
|
+
quintuples of integers) which has at most one minus sign per train
|
|
700
|
+
and this sign appears on the smallest dimensional Jordan component
|
|
701
|
+
in each train. This results from applying the "sign-walking" and
|
|
702
|
+
"oddity fusion" equivalences.
|
|
703
|
+
|
|
704
|
+
INPUT:
|
|
705
|
+
|
|
706
|
+
- ``genus_symbol_quintuple_list`` -- a quintuple of integers (with certain
|
|
707
|
+
restrictions)
|
|
708
|
+
|
|
709
|
+
- ``compartments`` -- list of lists of distinct integers (optional)
|
|
710
|
+
|
|
711
|
+
OUTPUT: list of lists of distinct integers
|
|
712
|
+
|
|
713
|
+
EXAMPLES::
|
|
714
|
+
|
|
715
|
+
sage: from sage.quadratic_forms.genera.genus import LocalGenusSymbol
|
|
716
|
+
sage: from sage.quadratic_forms.genera.genus import canonical_2_adic_reduction
|
|
717
|
+
|
|
718
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 1, 1, 2])
|
|
719
|
+
sage: G2 = LocalGenusSymbol(A, 2); G2.symbol_tuple_list()
|
|
720
|
+
[[0, 2, 1, 1, 2]]
|
|
721
|
+
sage: canonical_2_adic_reduction(G2.symbol_tuple_list())
|
|
722
|
+
[[0, 2, 1, 1, 2]]
|
|
723
|
+
|
|
724
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 0, 0, 2])
|
|
725
|
+
sage: G2 = LocalGenusSymbol(A, 2); G2.symbol_tuple_list()
|
|
726
|
+
[[0, 1, 1, 1, 1], [1, 1, 1, 1, 1]]
|
|
727
|
+
sage: canonical_2_adic_reduction(G2.symbol_tuple_list()) # Oddity fusion occurred here!
|
|
728
|
+
[[0, 1, 1, 1, 2], [1, 1, 1, 1, 0]]
|
|
729
|
+
|
|
730
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
731
|
+
sage: G2 = LocalGenusSymbol(A, 2); G2.symbol_tuple_list()
|
|
732
|
+
[[1, 2, 3, 1, 4], [2, 1, 1, 1, 1], [3, 1, 1, 1, 1]]
|
|
733
|
+
sage: canonical_2_adic_reduction(G2.symbol_tuple_list()) # Oddity fusion occurred here!
|
|
734
|
+
[[1, 2, -1, 1, 6], [2, 1, 1, 1, 0], [3, 1, 1, 1, 0]]
|
|
735
|
+
|
|
736
|
+
sage: A = Matrix(ZZ, 2, 2, [2, 1, 1, 2])
|
|
737
|
+
sage: G2 = LocalGenusSymbol(A, 2); G2.symbol_tuple_list()
|
|
738
|
+
[[0, 2, 3, 0, 0]]
|
|
739
|
+
sage: canonical_2_adic_reduction(G2.symbol_tuple_list())
|
|
740
|
+
[[0, 2, -1, 0, 0]]
|
|
741
|
+
|
|
742
|
+
.. NOTE::
|
|
743
|
+
|
|
744
|
+
See [CS1999]_ Conway-Sloane 3rd edition, pp. 381-382 for definitions
|
|
745
|
+
and examples.
|
|
746
|
+
|
|
747
|
+
.. TODO::
|
|
748
|
+
|
|
749
|
+
Add an example where sign walking occurs!
|
|
750
|
+
"""
|
|
751
|
+
# Protect the input from unwanted modification
|
|
752
|
+
genus_symbol_quintuple_list = deepcopy(genus_symbol_quintuple_list)
|
|
753
|
+
canonical_symbol = genus_symbol_quintuple_list
|
|
754
|
+
# Canonical determinants:
|
|
755
|
+
for i in range(len(genus_symbol_quintuple_list)):
|
|
756
|
+
d = genus_symbol_quintuple_list[i][2]
|
|
757
|
+
if d in (1, 7):
|
|
758
|
+
canonical_symbol[i][2] = 1
|
|
759
|
+
else:
|
|
760
|
+
canonical_symbol[i][2] = -1
|
|
761
|
+
# Oddity fusion:
|
|
762
|
+
compartments = canonical_2_adic_compartments(genus_symbol_quintuple_list)
|
|
763
|
+
for compart in compartments:
|
|
764
|
+
oddity = sum([genus_symbol_quintuple_list[i][4] for i in compart]) % 8
|
|
765
|
+
for i in compart:
|
|
766
|
+
genus_symbol_quintuple_list[i][4] = 0
|
|
767
|
+
genus_symbol_quintuple_list[compart[0]][4] = oddity
|
|
768
|
+
verbose(mesg="End oddity fusion: %s" % canonical_symbol, level=2)
|
|
769
|
+
# Sign walking:
|
|
770
|
+
trains = canonical_2_adic_trains(genus_symbol_quintuple_list)
|
|
771
|
+
for train in trains:
|
|
772
|
+
t = len(train)
|
|
773
|
+
for i in range(t-1):
|
|
774
|
+
t1 = train[t-i-1]
|
|
775
|
+
if canonical_symbol[t1][2] == -1:
|
|
776
|
+
canonical_symbol[t1][2] = 1
|
|
777
|
+
canonical_symbol[t1-1][2] *= -1
|
|
778
|
+
for compart in compartments:
|
|
779
|
+
if t1-1 in compart or t1 in compart:
|
|
780
|
+
o = canonical_symbol[compart[0]][4]
|
|
781
|
+
canonical_symbol[compart[0]][4] = (o+4) % 8
|
|
782
|
+
verbose(mesg="End sign walking: %s" % canonical_symbol, level=2)
|
|
783
|
+
return canonical_symbol
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def basis_complement(B):
|
|
787
|
+
r"""
|
|
788
|
+
Given an echelonized basis matrix `B` (over a field), calculate a
|
|
789
|
+
matrix whose rows form a basis complement (to the rows of `B`).
|
|
790
|
+
|
|
791
|
+
INPUT:
|
|
792
|
+
|
|
793
|
+
- ``B`` -- matrix over a field in row echelon form
|
|
794
|
+
|
|
795
|
+
OUTPUT: a rectangular matrix over a field
|
|
796
|
+
|
|
797
|
+
EXAMPLES::
|
|
798
|
+
|
|
799
|
+
sage: from sage.quadratic_forms.genera.genus import basis_complement
|
|
800
|
+
|
|
801
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 1, 1, 1])
|
|
802
|
+
sage: B = A.kernel().echelonized_basis_matrix(); B
|
|
803
|
+
[ 1 -1]
|
|
804
|
+
sage: basis_complement(B)
|
|
805
|
+
[0 1]
|
|
806
|
+
"""
|
|
807
|
+
F = B.parent().base_ring()
|
|
808
|
+
m = B.nrows()
|
|
809
|
+
n = B.ncols()
|
|
810
|
+
C = MatrixSpace(F, n - m, n, sparse=True)(0)
|
|
811
|
+
k = 0
|
|
812
|
+
l = 0
|
|
813
|
+
for i in range(m):
|
|
814
|
+
for j in range(k, n):
|
|
815
|
+
if B[i, j] == 0:
|
|
816
|
+
C[l, j] = 1
|
|
817
|
+
l += 1
|
|
818
|
+
else:
|
|
819
|
+
k = j + 1
|
|
820
|
+
break
|
|
821
|
+
for j in range(k, n):
|
|
822
|
+
C[l + j - k, j] = 1
|
|
823
|
+
return C
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
def signature_pair_of_matrix(A):
|
|
827
|
+
r"""
|
|
828
|
+
Compute the signature pair `(p, n)` of a non-degenerate symmetric
|
|
829
|
+
matrix, where
|
|
830
|
+
|
|
831
|
+
- `p` is the number of positive eigenvalues of `A`
|
|
832
|
+
- `n` is the number of negative eigenvalues of `A`
|
|
833
|
+
|
|
834
|
+
INPUT:
|
|
835
|
+
|
|
836
|
+
- ``A`` -- symmetric matrix (assumed to be non-degenerate)
|
|
837
|
+
|
|
838
|
+
OUTPUT: `(p, n)` -- a pair (tuple) of integers.
|
|
839
|
+
|
|
840
|
+
EXAMPLES::
|
|
841
|
+
|
|
842
|
+
sage: from sage.quadratic_forms.genera.genus import signature_pair_of_matrix
|
|
843
|
+
|
|
844
|
+
sage: A = Matrix(ZZ, 2, 2, [-1, 0, 0, 3])
|
|
845
|
+
sage: signature_pair_of_matrix(A)
|
|
846
|
+
(1, 1)
|
|
847
|
+
|
|
848
|
+
sage: A = Matrix(ZZ, 2, 2, [-1, 1, 1, 7])
|
|
849
|
+
sage: signature_pair_of_matrix(A)
|
|
850
|
+
(1, 1)
|
|
851
|
+
|
|
852
|
+
sage: A = Matrix(ZZ, 2, 2, [3, 1, 1, 7])
|
|
853
|
+
sage: signature_pair_of_matrix(A)
|
|
854
|
+
(2, 0)
|
|
855
|
+
|
|
856
|
+
sage: A = Matrix(ZZ, 2, 2, [-3, 1, 1, -11])
|
|
857
|
+
sage: signature_pair_of_matrix(A)
|
|
858
|
+
(0, 2)
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 1, 1, 1])
|
|
862
|
+
sage: signature_pair_of_matrix(A)
|
|
863
|
+
Traceback (most recent call last):
|
|
864
|
+
...
|
|
865
|
+
ArithmeticError: given matrix is not invertible
|
|
866
|
+
"""
|
|
867
|
+
from sage.quadratic_forms.quadratic_form import QuadraticForm
|
|
868
|
+
s_vec = QuadraticForm(A.base_extend(A.base_ring().fraction_field())).signature_vector()
|
|
869
|
+
|
|
870
|
+
# Check that the matrix is non-degenerate (i.e. no zero eigenvalues)
|
|
871
|
+
if s_vec[2]:
|
|
872
|
+
raise ArithmeticError("given matrix is not invertible")
|
|
873
|
+
|
|
874
|
+
# Return the pair (p,n)
|
|
875
|
+
return s_vec[:2]
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def p_adic_symbol(A, p, val):
|
|
879
|
+
r"""
|
|
880
|
+
Given a symmetric matrix `A` and prime `p`, return the genus symbol at `p`.
|
|
881
|
+
|
|
882
|
+
.. TODO::
|
|
883
|
+
|
|
884
|
+
Some description of the definition of the genus symbol.
|
|
885
|
+
|
|
886
|
+
INPUT:
|
|
887
|
+
|
|
888
|
+
- ``A`` -- symmetric matrix with integer coefficients
|
|
889
|
+
- ``p`` -- prime number
|
|
890
|
+
- ``val`` -- nonnegative integer; valuation of the maximal elementary
|
|
891
|
+
divisor of `A` needed to obtain enough precision.
|
|
892
|
+
Calculation is modulo `p` to the ``val+3``.
|
|
893
|
+
|
|
894
|
+
OUTPUT: list of lists of integers
|
|
895
|
+
|
|
896
|
+
EXAMPLES::
|
|
897
|
+
|
|
898
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
899
|
+
|
|
900
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
901
|
+
sage: p_adic_symbol(A, 2, 2)
|
|
902
|
+
[[1, 2, 3, 1, 4], [2, 1, 1, 1, 1], [3, 1, 1, 1, 1]]
|
|
903
|
+
|
|
904
|
+
sage: p_adic_symbol(A, 3, 1)
|
|
905
|
+
[[0, 3, 1], [1, 1, -1]]
|
|
906
|
+
"""
|
|
907
|
+
if p % 2 == 0:
|
|
908
|
+
return two_adic_symbol(A, val)
|
|
909
|
+
|
|
910
|
+
from sage.rings.finite_rings.finite_field_constructor import FiniteField
|
|
911
|
+
|
|
912
|
+
m0 = min(c.valuation(p) for c in A.list())
|
|
913
|
+
q = p**m0
|
|
914
|
+
n = A.nrows()
|
|
915
|
+
A = MatrixSpace(ZZ, n, n)([c // q for c in A.list()])
|
|
916
|
+
A_p = MatrixSpace(FiniteField(p), n, n)(A)
|
|
917
|
+
B_p = A_p.kernel().echelonized_basis_matrix()
|
|
918
|
+
if B_p.nrows() == 0:
|
|
919
|
+
e0 = Integer(A_p.det()).kronecker(p)
|
|
920
|
+
n0 = A.nrows()
|
|
921
|
+
return [[m0, n0, e0]]
|
|
922
|
+
else:
|
|
923
|
+
C_p = basis_complement(B_p)
|
|
924
|
+
e0 = Integer((C_p * A_p * C_p.transpose()).det()).kronecker(p)
|
|
925
|
+
n0 = C_p.nrows()
|
|
926
|
+
sym = [[0, n0, e0]]
|
|
927
|
+
r = B_p.nrows()
|
|
928
|
+
B = MatrixSpace(ZZ, r, n)(B_p)
|
|
929
|
+
C = MatrixSpace(ZZ, n - r, n)(C_p)
|
|
930
|
+
# Construct the blocks for the Jordan decomposition [F,X;X,A_new]
|
|
931
|
+
F = MatrixSpace(QQ, n - r, n - r)(C * A * C.transpose())
|
|
932
|
+
U = F**-1
|
|
933
|
+
d = LCM([c.denominator() for c in U.list()])
|
|
934
|
+
R = ZZ.quotient_ring(Integer(p)**(val + 3))
|
|
935
|
+
u = R(d)**-1
|
|
936
|
+
MatR = MatrixSpace(R, n - r, n - r)
|
|
937
|
+
MatZ = MatrixSpace(ZZ, n - r, n - r)
|
|
938
|
+
U = MatZ(MatR(MatZ(U * d)) * u)
|
|
939
|
+
# X = C*A*B.transpose()
|
|
940
|
+
# A = B*A*B.transpose() - X.transpose()*U*X
|
|
941
|
+
X = C * A
|
|
942
|
+
A = B * (A - X.transpose() * U * X) * B.transpose()
|
|
943
|
+
return [[s[0]+m0] + s[1:] for s in sym + p_adic_symbol(A, p, val)]
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def is_even_matrix(A) -> tuple[bool, int]:
|
|
947
|
+
r"""
|
|
948
|
+
Determine if the integral symmetric matrix `A` is even
|
|
949
|
+
(i.e. represents only even numbers). If not, then it returns the
|
|
950
|
+
index of an odd diagonal entry. If it is even, then we return the
|
|
951
|
+
index `-1`.
|
|
952
|
+
|
|
953
|
+
INPUT:
|
|
954
|
+
|
|
955
|
+
- ``A`` -- symmetric integer matrix
|
|
956
|
+
|
|
957
|
+
OUTPUT: a pair of the form (boolean, integer)
|
|
958
|
+
|
|
959
|
+
EXAMPLES::
|
|
960
|
+
|
|
961
|
+
sage: from sage.quadratic_forms.genera.genus import is_even_matrix
|
|
962
|
+
|
|
963
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 1, 1, 1])
|
|
964
|
+
sage: is_even_matrix(A)
|
|
965
|
+
(False, 0)
|
|
966
|
+
|
|
967
|
+
sage: A = Matrix(ZZ, 2, 2, [2, 1, 1, 2])
|
|
968
|
+
sage: is_even_matrix(A)
|
|
969
|
+
(True, -1)
|
|
970
|
+
"""
|
|
971
|
+
for i in range(A.nrows()):
|
|
972
|
+
if A[i, i] % 2:
|
|
973
|
+
return False, i
|
|
974
|
+
return True, -1
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
def split_odd(A):
|
|
978
|
+
r"""
|
|
979
|
+
Given a non-degenerate Gram matrix `A (\mod 8)`, return a splitting
|
|
980
|
+
``[u] + B`` such that u is odd and `B` is not even.
|
|
981
|
+
|
|
982
|
+
INPUT:
|
|
983
|
+
|
|
984
|
+
- ``A`` -- an odd symmetric matrix with integer coefficients (which admits a
|
|
985
|
+
splitting as above)
|
|
986
|
+
|
|
987
|
+
OUTPUT:
|
|
988
|
+
|
|
989
|
+
a pair ``(u, B)`` consisting of an odd integer `u` and an odd
|
|
990
|
+
integral symmetric matrix `B`.
|
|
991
|
+
|
|
992
|
+
EXAMPLES::
|
|
993
|
+
|
|
994
|
+
sage: from sage.quadratic_forms.genera.genus import is_even_matrix
|
|
995
|
+
sage: from sage.quadratic_forms.genera.genus import split_odd
|
|
996
|
+
|
|
997
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 2, 2, 3])
|
|
998
|
+
sage: is_even_matrix(A)
|
|
999
|
+
(False, 0)
|
|
1000
|
+
sage: split_odd(A)
|
|
1001
|
+
(1, [-1])
|
|
1002
|
+
|
|
1003
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 2, 2, 5])
|
|
1004
|
+
sage: split_odd(A)
|
|
1005
|
+
(1, [1])
|
|
1006
|
+
|
|
1007
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 1, 1, 1])
|
|
1008
|
+
sage: is_even_matrix(A)
|
|
1009
|
+
(False, 0)
|
|
1010
|
+
sage: split_odd(A) # This fails because no such splitting exists. =(
|
|
1011
|
+
Traceback (most recent call last):
|
|
1012
|
+
...
|
|
1013
|
+
RuntimeError: The matrix A does not admit a non-even splitting.
|
|
1014
|
+
|
|
1015
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 2, 2, 6])
|
|
1016
|
+
sage: split_odd(A) # This fails because no such splitting exists. =(
|
|
1017
|
+
Traceback (most recent call last):
|
|
1018
|
+
...
|
|
1019
|
+
RuntimeError: The matrix A does not admit a non-even splitting.
|
|
1020
|
+
"""
|
|
1021
|
+
n0 = A.nrows()
|
|
1022
|
+
if n0 == 1:
|
|
1023
|
+
return A[0, 0], MatrixSpace(ZZ, 0, A.ncols())([])
|
|
1024
|
+
even, i = is_even_matrix(A)
|
|
1025
|
+
R = A.parent().base_ring()
|
|
1026
|
+
C = MatrixSpace(R, n0 - 1, n0)(0)
|
|
1027
|
+
u = A[i, i]
|
|
1028
|
+
for j in range(n0-1):
|
|
1029
|
+
if j < i:
|
|
1030
|
+
C[j, j] = 1
|
|
1031
|
+
C[j, i] = -A[j, i] * u
|
|
1032
|
+
else:
|
|
1033
|
+
C[j, j+1] = 1
|
|
1034
|
+
C[j, i] = -A[j+1, i] * u
|
|
1035
|
+
B = C*A*C.transpose()
|
|
1036
|
+
even, j = is_even_matrix(B)
|
|
1037
|
+
if even:
|
|
1038
|
+
I = A.parent()(1)
|
|
1039
|
+
# TODO: we could manually (re)construct the kernel here...
|
|
1040
|
+
if i == 0:
|
|
1041
|
+
I[1, 0] = 1 - A[1, 0]*u
|
|
1042
|
+
i = 1
|
|
1043
|
+
else:
|
|
1044
|
+
I[0, i] = 1 - A[0, i]*u
|
|
1045
|
+
i = 0
|
|
1046
|
+
A = I*A*I.transpose()
|
|
1047
|
+
u = A[i, i]
|
|
1048
|
+
C = MatrixSpace(R, n0-1, n0)(0)
|
|
1049
|
+
for j in range(n0-1):
|
|
1050
|
+
if j < i:
|
|
1051
|
+
C[j, j] = 1
|
|
1052
|
+
C[j, i] = -A[j, i] * u
|
|
1053
|
+
else:
|
|
1054
|
+
C[j, j+1] = 1
|
|
1055
|
+
C[j, i] = -A[j+1, i] * u
|
|
1056
|
+
B = C * A * C.transpose()
|
|
1057
|
+
even, j = is_even_matrix(B)
|
|
1058
|
+
if even:
|
|
1059
|
+
print("B:")
|
|
1060
|
+
print(B)
|
|
1061
|
+
raise RuntimeError("The matrix A does not admit a non-even splitting.")
|
|
1062
|
+
return u, B
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
def trace_diag_mod_8(A):
|
|
1066
|
+
r"""
|
|
1067
|
+
Return the trace of the diagonalised form of `A` of an integral
|
|
1068
|
+
symmetric matrix which is diagonalizable mod `8`. (Note that since
|
|
1069
|
+
the Jordan decomposition into blocks of size `\leq 2` is not unique
|
|
1070
|
+
here, this is not the same as saying that `A` is always diagonal in
|
|
1071
|
+
any `2`-adic Jordan decomposition!)
|
|
1072
|
+
|
|
1073
|
+
INPUT:
|
|
1074
|
+
|
|
1075
|
+
- ``A`` -- symmetric matrix with coefficients in `\ZZ` which is odd in
|
|
1076
|
+
`\ZZ/2\ZZ` and has determinant not divisible by `8`
|
|
1077
|
+
|
|
1078
|
+
OUTPUT: integer
|
|
1079
|
+
|
|
1080
|
+
EXAMPLES::
|
|
1081
|
+
|
|
1082
|
+
sage: from sage.quadratic_forms.genera.genus import is_even_matrix
|
|
1083
|
+
sage: from sage.quadratic_forms.genera.genus import split_odd
|
|
1084
|
+
sage: from sage.quadratic_forms.genera.genus import trace_diag_mod_8
|
|
1085
|
+
|
|
1086
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 2, 2, 3])
|
|
1087
|
+
sage: is_even_matrix(A)
|
|
1088
|
+
(False, 0)
|
|
1089
|
+
sage: split_odd(A)
|
|
1090
|
+
(1, [-1])
|
|
1091
|
+
sage: trace_diag_mod_8(A)
|
|
1092
|
+
0
|
|
1093
|
+
|
|
1094
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 2, 2, 5])
|
|
1095
|
+
sage: split_odd(A)
|
|
1096
|
+
(1, [1])
|
|
1097
|
+
sage: trace_diag_mod_8(A)
|
|
1098
|
+
2
|
|
1099
|
+
"""
|
|
1100
|
+
tr = 0
|
|
1101
|
+
while A.nrows():
|
|
1102
|
+
u, A = split_odd(A)
|
|
1103
|
+
tr += u
|
|
1104
|
+
return ZZ(tr)
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
def two_adic_symbol(A, val):
|
|
1108
|
+
r"""
|
|
1109
|
+
Given a symmetric matrix `A` and prime `p`, return the genus symbol at `p`.
|
|
1110
|
+
|
|
1111
|
+
The genus symbol of a component `2^m f` is of the form ``(m,n,s,d[,o])``,
|
|
1112
|
+
where
|
|
1113
|
+
|
|
1114
|
+
- ``m`` = valuation of the component
|
|
1115
|
+
- ``n`` = dimension of `f`
|
|
1116
|
+
- ``d`` = det(`f`) in {1,3,5,7}
|
|
1117
|
+
- ``s`` = 0 (or 1) if even (or odd)
|
|
1118
|
+
- ``o`` = oddity of `f` (= 0 if s = 0) in `\ZZ/8\ZZ`
|
|
1119
|
+
|
|
1120
|
+
INPUT:
|
|
1121
|
+
|
|
1122
|
+
- ``A`` -- symmetric matrix with integer coefficients, non-degenerate
|
|
1123
|
+
- ``val`` -- nonnegative integer; valuation of maximal `2`-elementary divisor
|
|
1124
|
+
|
|
1125
|
+
OUTPUT:
|
|
1126
|
+
|
|
1127
|
+
a list of lists of integers (representing a Conway-Sloane `2`-adic symbol)
|
|
1128
|
+
|
|
1129
|
+
EXAMPLES::
|
|
1130
|
+
|
|
1131
|
+
sage: from sage.quadratic_forms.genera.genus import two_adic_symbol
|
|
1132
|
+
|
|
1133
|
+
sage: A = diagonal_matrix(ZZ, [1, 2, 3, 4])
|
|
1134
|
+
sage: two_adic_symbol(A, 2)
|
|
1135
|
+
[[0, 2, 3, 1, 4], [1, 1, 1, 1, 1], [2, 1, 1, 1, 1]]
|
|
1136
|
+
"""
|
|
1137
|
+
from sage.rings.finite_rings.finite_field_constructor import FiniteField
|
|
1138
|
+
|
|
1139
|
+
n = A.nrows()
|
|
1140
|
+
# deal with the empty matrix
|
|
1141
|
+
if n == 0:
|
|
1142
|
+
return [[0, 0, 1, 0, 0]]
|
|
1143
|
+
m0 = min([c.valuation(2) for c in A.list()])
|
|
1144
|
+
q = 2**m0
|
|
1145
|
+
A = A.parent()([c // q for c in A.list()])
|
|
1146
|
+
A_2 = MatrixSpace(FiniteField(2), n, n)(A)
|
|
1147
|
+
K_2 = A_2.kernel()
|
|
1148
|
+
R_8 = ZZ.quotient_ring(Integer(8))
|
|
1149
|
+
|
|
1150
|
+
# Deal with the matrix being non-degenerate mod 2.
|
|
1151
|
+
if K_2.dimension() == 0:
|
|
1152
|
+
A_8 = MatrixSpace(R_8, n)(A)
|
|
1153
|
+
n0 = A.nrows()
|
|
1154
|
+
# d0 = ZZ(A_8.determinant()) # no determinant over Z/8Z
|
|
1155
|
+
d0 = ZZ(R_8(MatrixSpace(ZZ, n)(A_8).determinant()))
|
|
1156
|
+
if d0 == 0: # SANITY CHECK: The mod 8 determinant shouldn't be zero.
|
|
1157
|
+
print("A:")
|
|
1158
|
+
print(A)
|
|
1159
|
+
assert False
|
|
1160
|
+
even, _ = is_even_matrix(A_2) # Determine whether the matrix is even or odd.
|
|
1161
|
+
if even:
|
|
1162
|
+
return [[m0, n0, d0, 0, 0]]
|
|
1163
|
+
else:
|
|
1164
|
+
tr8 = trace_diag_mod_8(A_8) # Here we already know that A_8 is odd and diagonalizable mod 8.
|
|
1165
|
+
return [[m0, n0, d0, 1, tr8]]
|
|
1166
|
+
|
|
1167
|
+
# Deal with the matrix being degenerate mod 2.
|
|
1168
|
+
else:
|
|
1169
|
+
B_2 = K_2.echelonized_basis_matrix()
|
|
1170
|
+
C_2 = basis_complement(B_2)
|
|
1171
|
+
n0 = C_2.nrows()
|
|
1172
|
+
C = MatrixSpace(ZZ, n0, n)(C_2)
|
|
1173
|
+
A_new = C * A * C.transpose()
|
|
1174
|
+
# compute oddity modulo 8:
|
|
1175
|
+
A_8 = MatrixSpace(R_8, n0, n0)(A_new)
|
|
1176
|
+
# d0 = A_8.det() # no determinant over Z/8Z
|
|
1177
|
+
d0 = ZZ(R_8(MatrixSpace(ZZ, n0, n0)(A_8).determinant()))
|
|
1178
|
+
if d0 == 0:
|
|
1179
|
+
print("A:")
|
|
1180
|
+
print(A_new)
|
|
1181
|
+
assert False
|
|
1182
|
+
even, _ = is_even_matrix(A_new)
|
|
1183
|
+
if even:
|
|
1184
|
+
sym = [[0, n0, d0, 0, 0]]
|
|
1185
|
+
else:
|
|
1186
|
+
tr8 = trace_diag_mod_8(A_8)
|
|
1187
|
+
sym = [[0, n0, d0, 1, tr8]]
|
|
1188
|
+
r = B_2.nrows()
|
|
1189
|
+
B = MatrixSpace(ZZ, r, n)(B_2)
|
|
1190
|
+
C = MatrixSpace(ZZ, n - r, n)(C_2)
|
|
1191
|
+
F = MatrixSpace(QQ, n - r, n - r)(C * A * C.transpose())
|
|
1192
|
+
U = F**-1
|
|
1193
|
+
d = LCM([c.denominator() for c in U.list()])
|
|
1194
|
+
R = ZZ.quotient_ring(Integer(2)**(val + 3))
|
|
1195
|
+
u = R(d)**-1
|
|
1196
|
+
MatR = MatrixSpace(R, n - r, n - r)
|
|
1197
|
+
MatZ = MatrixSpace(ZZ, n - r, n - r)
|
|
1198
|
+
U = MatZ(MatR(MatZ(U * d)) * u)
|
|
1199
|
+
X = C * A
|
|
1200
|
+
A = B * (A - X.transpose()*U*X) * B.transpose()
|
|
1201
|
+
return [[s[0]+m0] + s[1:] for s in sym + two_adic_symbol(A, val)]
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
class Genus_Symbol_p_adic_ring:
|
|
1205
|
+
r"""
|
|
1206
|
+
Local genus symbol over a `p`-adic ring.
|
|
1207
|
+
|
|
1208
|
+
The genus symbol of a component `p^m A` for odd prime `= p` is of the
|
|
1209
|
+
form `(m,n,d)`, where
|
|
1210
|
+
|
|
1211
|
+
- `m` = valuation of the component
|
|
1212
|
+
- `n` = rank of `A`
|
|
1213
|
+
- `d = det(A) \in \{1,u\}` for a normalized quadratic non-residue `u`.
|
|
1214
|
+
|
|
1215
|
+
The genus symbol of a component `2^m A` is of the form `(m, n, s, d, o)`,
|
|
1216
|
+
where
|
|
1217
|
+
|
|
1218
|
+
- `m` = valuation of the component
|
|
1219
|
+
- `n` = rank of `A`
|
|
1220
|
+
- `d` = det(A) in `\{1,3,5,7\}`
|
|
1221
|
+
- `s` = 0 (or 1) if even (or odd)
|
|
1222
|
+
- `o` = oddity of `A` (= 0 if s = 0) in `Z/8Z` = the trace of the diagonalization of `A`
|
|
1223
|
+
|
|
1224
|
+
The genus symbol is a list of such symbols (ordered by `m`) for each
|
|
1225
|
+
of the Jordan blocks `A_1,...,A_t`.
|
|
1226
|
+
|
|
1227
|
+
REFERENCE:
|
|
1228
|
+
|
|
1229
|
+
[CS1999]_ Conway and Sloane 3rd edition, Chapter 15, Section 7.
|
|
1230
|
+
|
|
1231
|
+
|
|
1232
|
+
.. WARNING::
|
|
1233
|
+
|
|
1234
|
+
This normalization seems non-standard, and we
|
|
1235
|
+
should review this entire class to make sure that we have our
|
|
1236
|
+
doubling conventions straight throughout! This is especially
|
|
1237
|
+
noticeable in the determinant and excess methods!!
|
|
1238
|
+
|
|
1239
|
+
INPUT:
|
|
1240
|
+
|
|
1241
|
+
- ``prime`` -- a prime number
|
|
1242
|
+
- ``symbol`` -- the list of invariants for Jordan blocks `A_t,...,A_t` given
|
|
1243
|
+
as a list of lists of integers
|
|
1244
|
+
|
|
1245
|
+
EXAMPLES::
|
|
1246
|
+
|
|
1247
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
1248
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
1249
|
+
|
|
1250
|
+
sage: A = diagonal_matrix(ZZ, [1, 2, 3, 4])
|
|
1251
|
+
sage: p = 2
|
|
1252
|
+
sage: s2 = p_adic_symbol(A, p, 2); s2
|
|
1253
|
+
[[0, 2, 3, 1, 4], [1, 1, 1, 1, 1], [2, 1, 1, 1, 1]]
|
|
1254
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p,s2); G2
|
|
1255
|
+
Genus symbol at 2: [1^-2 2^1 4^1]_6
|
|
1256
|
+
|
|
1257
|
+
sage: A = diagonal_matrix(ZZ, [1, 2, 3, 4])
|
|
1258
|
+
sage: p = 3
|
|
1259
|
+
sage: s3 = p_adic_symbol(A, p, 1); s3
|
|
1260
|
+
[[0, 3, -1], [1, 1, 1]]
|
|
1261
|
+
sage: G3 = Genus_Symbol_p_adic_ring(p,s3); G3
|
|
1262
|
+
Genus symbol at 3: 1^-3 3^1
|
|
1263
|
+
"""
|
|
1264
|
+
def __init__(self, prime, symbol, check=True):
|
|
1265
|
+
r"""
|
|
1266
|
+
Create the local genus symbol of given prime and local invariants.
|
|
1267
|
+
|
|
1268
|
+
EXAMPLES::
|
|
1269
|
+
|
|
1270
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
1271
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
1272
|
+
sage: A = diagonal_matrix(ZZ, [1,2,3,4])
|
|
1273
|
+
sage: p = 2
|
|
1274
|
+
sage: s2 = p_adic_symbol(A, p, 2); s2
|
|
1275
|
+
[[0, 2, 3, 1, 4], [1, 1, 1, 1, 1], [2, 1, 1, 1, 1]]
|
|
1276
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p,s2);G2
|
|
1277
|
+
Genus symbol at 2: [1^-2 2^1 4^1]_6
|
|
1278
|
+
|
|
1279
|
+
sage: A = diagonal_matrix(ZZ, [1,2,3,4])
|
|
1280
|
+
sage: p = 3
|
|
1281
|
+
sage: s3 = p_adic_symbol(A, p, 1); s3
|
|
1282
|
+
[[0, 3, -1], [1, 1, 1]]
|
|
1283
|
+
sage: G3 = Genus_Symbol_p_adic_ring(p,s3);G3
|
|
1284
|
+
Genus symbol at 3: 1^-3 3^1
|
|
1285
|
+
sage: G2 == loads(dumps(G2))
|
|
1286
|
+
True
|
|
1287
|
+
sage: G3 == loads(dumps(G3))
|
|
1288
|
+
True
|
|
1289
|
+
"""
|
|
1290
|
+
if check:
|
|
1291
|
+
pass
|
|
1292
|
+
self._prime = ZZ(prime)
|
|
1293
|
+
self._symbol = symbol
|
|
1294
|
+
self._canonical_symbol = None
|
|
1295
|
+
|
|
1296
|
+
def __repr__(self):
|
|
1297
|
+
r"""
|
|
1298
|
+
String representation for the `p`-adic genus symbol.
|
|
1299
|
+
|
|
1300
|
+
OUTPUT: string
|
|
1301
|
+
|
|
1302
|
+
EXAMPLES::
|
|
1303
|
+
|
|
1304
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
1305
|
+
sage: symbol = [[0, 4, -1, 0, 0], [1, 2, 1, 1, 2], [2, 1, 1, 1, 1], [4, 4, 1, 0, 0], [5, 1, 1, 1, 1]]
|
|
1306
|
+
sage: g = Genus_Symbol_p_adic_ring(2,symbol)
|
|
1307
|
+
sage: g
|
|
1308
|
+
Genus symbol at 2: 1^-4 [2^2 4^1]_3:16^4 [32^1]_1
|
|
1309
|
+
|
|
1310
|
+
TESTS:
|
|
1311
|
+
|
|
1312
|
+
Check that :issue:`25776` is fixed::
|
|
1313
|
+
|
|
1314
|
+
sage: G = Genus(matrix.diagonal([2,2,64]))
|
|
1315
|
+
sage: G
|
|
1316
|
+
Genus of
|
|
1317
|
+
[ 2 0 0]
|
|
1318
|
+
[ 0 2 0]
|
|
1319
|
+
[ 0 0 64]
|
|
1320
|
+
Signature: (3, 0)
|
|
1321
|
+
Genus symbol at 2: [2^2]_2:[64^1]_1
|
|
1322
|
+
|
|
1323
|
+
sage: a = matrix.diagonal([1,3])
|
|
1324
|
+
sage: b = 2 * matrix(ZZ,2,[2,1,1,2])
|
|
1325
|
+
sage: c = matrix.block_diagonal([a, b])
|
|
1326
|
+
sage: Genus(c)
|
|
1327
|
+
Genus of
|
|
1328
|
+
[1 0|0 0]
|
|
1329
|
+
[0 3|0 0]
|
|
1330
|
+
[---+---]
|
|
1331
|
+
[0 0|4 2]
|
|
1332
|
+
[0 0|2 4]
|
|
1333
|
+
Signature: (4, 0)
|
|
1334
|
+
Genus symbol at 2: [1^2]_0 2^2
|
|
1335
|
+
Genus symbol at 3: 1^2 3^2
|
|
1336
|
+
"""
|
|
1337
|
+
p = self._prime
|
|
1338
|
+
CS_string = ""
|
|
1339
|
+
if p == 2:
|
|
1340
|
+
CS = self.canonical_symbol()
|
|
1341
|
+
for train in self.trains():
|
|
1342
|
+
# mark the beginning of a train with a colon
|
|
1343
|
+
CS_string += " :"
|
|
1344
|
+
# collect the indices where compartments begin and end
|
|
1345
|
+
compartment_begins = []
|
|
1346
|
+
compartment_ends = []
|
|
1347
|
+
for comp in self.compartments():
|
|
1348
|
+
compartment_begins.append(comp[0])
|
|
1349
|
+
compartment_ends.append(comp[-1])
|
|
1350
|
+
|
|
1351
|
+
for block_index in train:
|
|
1352
|
+
if block_index in compartment_begins:
|
|
1353
|
+
# mark the beginning of this compartment with [
|
|
1354
|
+
CS_string += "["
|
|
1355
|
+
block = CS[block_index]
|
|
1356
|
+
block_string = f"{p**block[0]}^{block[2] * block[1]} "
|
|
1357
|
+
CS_string += block_string
|
|
1358
|
+
if block_index in compartment_ends:
|
|
1359
|
+
# close this compartment with ] and remove a space
|
|
1360
|
+
CS_string = CS_string[:-1] + "]"
|
|
1361
|
+
# the oddity belongs to the compartment
|
|
1362
|
+
# and is saved in its first block
|
|
1363
|
+
i = compartment_ends.index(block_index)
|
|
1364
|
+
compartment_start = compartment_begins[i]
|
|
1365
|
+
oddity = CS[compartment_start][4]
|
|
1366
|
+
CS_string += "_%s " % oddity
|
|
1367
|
+
# remove the first colon
|
|
1368
|
+
CS_string = CS_string[2:]
|
|
1369
|
+
# remove some unnecessary whitespace
|
|
1370
|
+
CS_string = CS_string.replace(" :", ":")
|
|
1371
|
+
|
|
1372
|
+
else:
|
|
1373
|
+
for s in self._symbol:
|
|
1374
|
+
CS_string += f" {p**s[0]}^{s[2] * s[1]}"
|
|
1375
|
+
rep = f"Genus symbol at {p}: {CS_string}"
|
|
1376
|
+
return rep.rstrip()
|
|
1377
|
+
|
|
1378
|
+
def _latex_(self):
|
|
1379
|
+
r"""
|
|
1380
|
+
The LaTeX representation of this local genus symbol.
|
|
1381
|
+
|
|
1382
|
+
EXAMPLES::
|
|
1383
|
+
|
|
1384
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
1385
|
+
sage: symbol = [[0, 4, -1, 0, 0], [1, 2, 1, 1, 2], [2, 1, 1, 1, 1], [4, 4, 1, 0, 0], [5, 1, 1, 1, 1]]
|
|
1386
|
+
sage: g = Genus_Symbol_p_adic_ring(2,symbol)
|
|
1387
|
+
sage: g._canonical_symbol = [[0, 4, 1, 0, 0], [1, 2, 1, 1, 3], [2, 1, 1, 1, 0], [4, 4, 1, 0, 0], [5, 1, 1, 1, 1]]
|
|
1388
|
+
sage: latex(g)
|
|
1389
|
+
\mbox{Genus symbol at } 2\mbox{: }1^{4} [2^{2} 4^{1}]_{3} :16^{4} [32^{1}]_{1}
|
|
1390
|
+
"""
|
|
1391
|
+
p = self._prime
|
|
1392
|
+
CS_string = ""
|
|
1393
|
+
if p == 2:
|
|
1394
|
+
CS = self.canonical_symbol()
|
|
1395
|
+
for train in self.trains():
|
|
1396
|
+
# mark the beginning of a train with a colon
|
|
1397
|
+
CS_string += " :"
|
|
1398
|
+
# collect the indices where compartments begin and end
|
|
1399
|
+
compartment_begins = []
|
|
1400
|
+
compartment_ends = []
|
|
1401
|
+
for comp in self.compartments():
|
|
1402
|
+
compartment_begins.append(comp[0])
|
|
1403
|
+
compartment_ends.append(comp[-1])
|
|
1404
|
+
|
|
1405
|
+
for block_index in train:
|
|
1406
|
+
if block_index in compartment_begins:
|
|
1407
|
+
# mark the beginning of this compartment with [
|
|
1408
|
+
CS_string += "["
|
|
1409
|
+
block = CS[block_index]
|
|
1410
|
+
block_string = f"{p**block[0]}^{{{block[2] * block[1]}}} "
|
|
1411
|
+
CS_string += block_string
|
|
1412
|
+
if block_index in compartment_ends:
|
|
1413
|
+
# close this compartment with ] and remove a space
|
|
1414
|
+
CS_string = CS_string[:-1] + "]"
|
|
1415
|
+
# the oddity belongs to the compartment
|
|
1416
|
+
# and is saved in its first block
|
|
1417
|
+
i = compartment_ends.index(block_index)
|
|
1418
|
+
compartment_start = compartment_begins[i]
|
|
1419
|
+
oddity = CS[compartment_start][4]
|
|
1420
|
+
CS_string += "_{%s}" % oddity
|
|
1421
|
+
# remove the first colon
|
|
1422
|
+
CS_string = CS_string[2:]
|
|
1423
|
+
|
|
1424
|
+
else:
|
|
1425
|
+
for s in self._symbol:
|
|
1426
|
+
CS_string += f" {{{p**s[0]}}}^{{{s[2]*s[1]}}}"
|
|
1427
|
+
return fr"\mbox{{Genus symbol at }} {p}\mbox{{: }}{CS_string}"
|
|
1428
|
+
|
|
1429
|
+
def __eq__(self, other):
|
|
1430
|
+
r"""
|
|
1431
|
+
Determines if two genus symbols are equal (not just equivalent!).
|
|
1432
|
+
|
|
1433
|
+
INPUT:
|
|
1434
|
+
|
|
1435
|
+
- ``other`` -- a :class:`Genus_Symbol_p_adic_ring` object
|
|
1436
|
+
|
|
1437
|
+
OUTPUT: boolean
|
|
1438
|
+
|
|
1439
|
+
EXAMPLES::
|
|
1440
|
+
|
|
1441
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
1442
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
1443
|
+
|
|
1444
|
+
sage: A = diagonal_matrix(ZZ, [1, 2, 3, 4])
|
|
1445
|
+
sage: p = 2
|
|
1446
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2))
|
|
1447
|
+
sage: p = 3
|
|
1448
|
+
sage: G3 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 1))
|
|
1449
|
+
|
|
1450
|
+
sage: G2 == G3
|
|
1451
|
+
False
|
|
1452
|
+
sage: G3 == G2
|
|
1453
|
+
False
|
|
1454
|
+
sage: G2 == G2
|
|
1455
|
+
True
|
|
1456
|
+
sage: G3 == G3
|
|
1457
|
+
True
|
|
1458
|
+
"""
|
|
1459
|
+
p = self._prime
|
|
1460
|
+
if p != other._prime:
|
|
1461
|
+
return False
|
|
1462
|
+
return self.canonical_symbol() == other.canonical_symbol()
|
|
1463
|
+
|
|
1464
|
+
def __ne__(self, other):
|
|
1465
|
+
r"""
|
|
1466
|
+
Determines if two genus symbols are unequal (not just inequivalent!).
|
|
1467
|
+
|
|
1468
|
+
INPUT:
|
|
1469
|
+
|
|
1470
|
+
- ``other`` -- a :class:`Genus_Symbol_p_adic_ring` object
|
|
1471
|
+
|
|
1472
|
+
OUTPUT: boolean
|
|
1473
|
+
|
|
1474
|
+
EXAMPLES::
|
|
1475
|
+
|
|
1476
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
1477
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
1478
|
+
|
|
1479
|
+
sage: A = diagonal_matrix(ZZ, [1, 2, 3, 4])
|
|
1480
|
+
sage: p = 2
|
|
1481
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2))
|
|
1482
|
+
sage: p = 3
|
|
1483
|
+
sage: G3 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 1))
|
|
1484
|
+
|
|
1485
|
+
sage: G2 != G3
|
|
1486
|
+
True
|
|
1487
|
+
sage: G3 != G2
|
|
1488
|
+
True
|
|
1489
|
+
sage: G2 != G2
|
|
1490
|
+
False
|
|
1491
|
+
sage: G3 != G3
|
|
1492
|
+
False
|
|
1493
|
+
"""
|
|
1494
|
+
return not self == other
|
|
1495
|
+
|
|
1496
|
+
# Added these two methods to make this class iterable...
|
|
1497
|
+
# def __getitem__(self, i):
|
|
1498
|
+
# return self._symbol[i]
|
|
1499
|
+
#
|
|
1500
|
+
# def len(self):
|
|
1501
|
+
# return len(self._symbol)
|
|
1502
|
+
# ------------------------------------------------------
|
|
1503
|
+
|
|
1504
|
+
def automorphous_numbers(self):
|
|
1505
|
+
r"""
|
|
1506
|
+
Return generators of the automorphous square classes at this prime.
|
|
1507
|
+
|
|
1508
|
+
A `p`-adic square class `r` is called automorphous if it is
|
|
1509
|
+
the spinor norm of a proper `p`-adic integral automorphism of this form.
|
|
1510
|
+
These classes form a group. See [CS1999]_ Chapter 15, 9.6 for details.
|
|
1511
|
+
|
|
1512
|
+
OUTPUT:
|
|
1513
|
+
|
|
1514
|
+
a list of integers representing the square classes of generators of
|
|
1515
|
+
the automorphous numbers
|
|
1516
|
+
|
|
1517
|
+
EXAMPLES:
|
|
1518
|
+
|
|
1519
|
+
The following examples are given in
|
|
1520
|
+
[CS1999]_ 3rd edition, Chapter 15, 9.6 pp. 392::
|
|
1521
|
+
|
|
1522
|
+
sage: A = matrix.diagonal([3, 16])
|
|
1523
|
+
sage: G = Genus(A)
|
|
1524
|
+
sage: sym2 = G.local_symbols()[0]; sym2
|
|
1525
|
+
Genus symbol at 2: [1^-1]_3:[16^1]_1
|
|
1526
|
+
sage: sym2.automorphous_numbers()
|
|
1527
|
+
[3, 5]
|
|
1528
|
+
|
|
1529
|
+
sage: A = matrix(ZZ, 3, [2,1,0, 1,2,0, 0,0,18])
|
|
1530
|
+
sage: G = Genus(A)
|
|
1531
|
+
sage: sym = G.local_symbols()
|
|
1532
|
+
sage: sym[0]
|
|
1533
|
+
Genus symbol at 2: 1^-2 [2^1]_1
|
|
1534
|
+
sage: sym[0].automorphous_numbers()
|
|
1535
|
+
[1, 3, 5, 7]
|
|
1536
|
+
sage: sym[1]
|
|
1537
|
+
Genus symbol at 3: 1^-1 3^-1 9^-1
|
|
1538
|
+
sage: sym[1].automorphous_numbers()
|
|
1539
|
+
[1, 3]
|
|
1540
|
+
|
|
1541
|
+
Note that the generating set given is not minimal.
|
|
1542
|
+
The first supplementation rule is used here::
|
|
1543
|
+
|
|
1544
|
+
sage: A = matrix.diagonal([2, 2, 4])
|
|
1545
|
+
sage: G = Genus(A)
|
|
1546
|
+
sage: sym = G.local_symbols()
|
|
1547
|
+
sage: sym[0]
|
|
1548
|
+
Genus symbol at 2: [2^2 4^1]_3
|
|
1549
|
+
sage: sym[0].automorphous_numbers()
|
|
1550
|
+
[1, 2, 3, 5, 7]
|
|
1551
|
+
|
|
1552
|
+
but not there::
|
|
1553
|
+
|
|
1554
|
+
sage: A = matrix.diagonal([2, 2, 32])
|
|
1555
|
+
sage: G = Genus(A)
|
|
1556
|
+
sage: sym = G.local_symbols()
|
|
1557
|
+
sage: sym[0]
|
|
1558
|
+
Genus symbol at 2: [2^2]_2:[32^1]_1
|
|
1559
|
+
sage: sym[0].automorphous_numbers()
|
|
1560
|
+
[1, 2, 5]
|
|
1561
|
+
|
|
1562
|
+
Here the second supplementation rule is used::
|
|
1563
|
+
|
|
1564
|
+
sage: A = matrix.diagonal([2, 2, 64])
|
|
1565
|
+
sage: G = Genus(A)
|
|
1566
|
+
sage: sym = G.local_symbols()
|
|
1567
|
+
sage: sym[0]
|
|
1568
|
+
Genus symbol at 2: [2^2]_2:[64^1]_1
|
|
1569
|
+
sage: sym[0].automorphous_numbers()
|
|
1570
|
+
[1, 2, 5]
|
|
1571
|
+
"""
|
|
1572
|
+
from .normal_form import collect_small_blocks
|
|
1573
|
+
automorphs = []
|
|
1574
|
+
sym = self.symbol_tuple_list()
|
|
1575
|
+
G = self.gram_matrix().change_ring(ZZ)
|
|
1576
|
+
p = self.prime()
|
|
1577
|
+
if p != 2:
|
|
1578
|
+
up = ZZ(_min_nonsquare(p))
|
|
1579
|
+
I = G.diagonal()
|
|
1580
|
+
for r in I:
|
|
1581
|
+
# We need to consider all pairs in I
|
|
1582
|
+
# since at most 2 elements are part of a pair
|
|
1583
|
+
# we need at most 2 of each type
|
|
1584
|
+
if I.count(r) > 2:
|
|
1585
|
+
I.remove(r)
|
|
1586
|
+
# products of all pairs
|
|
1587
|
+
automorphs.extend(r1*r2 for r1 in I for r2 in I)
|
|
1588
|
+
|
|
1589
|
+
# supplement (i)
|
|
1590
|
+
for block in sym:
|
|
1591
|
+
if block[1] >= 2:
|
|
1592
|
+
automorphs.append(up)
|
|
1593
|
+
break
|
|
1594
|
+
# normalize the square classes and remove duplicates
|
|
1595
|
+
automorphs1 = set()
|
|
1596
|
+
for s in automorphs:
|
|
1597
|
+
u = 1
|
|
1598
|
+
if s.prime_to_m_part(p).kronecker(p) == -1:
|
|
1599
|
+
u = up
|
|
1600
|
+
v = (s.valuation(p) % 2)
|
|
1601
|
+
sq = u * p**v
|
|
1602
|
+
automorphs1.add(sq)
|
|
1603
|
+
return list(automorphs1)
|
|
1604
|
+
|
|
1605
|
+
# p = 2
|
|
1606
|
+
I = []
|
|
1607
|
+
II = []
|
|
1608
|
+
for block in collect_small_blocks(G):
|
|
1609
|
+
if block.ncols() == 1:
|
|
1610
|
+
u = block[0, 0]
|
|
1611
|
+
if I.count(u) < 2:
|
|
1612
|
+
I.append(block[0, 0])
|
|
1613
|
+
else: # rank2
|
|
1614
|
+
q = block[0, 1]
|
|
1615
|
+
II += [2*q, 3*2*q, 5*2*q, 7*2*q]
|
|
1616
|
+
|
|
1617
|
+
L = I + II
|
|
1618
|
+
# We need to consider all pairs in L
|
|
1619
|
+
# since at most 2 elements are part of a pair
|
|
1620
|
+
# we need at most 2 of each type
|
|
1621
|
+
for r in L: # remove triplicates
|
|
1622
|
+
if L.count(r) > 2:
|
|
1623
|
+
L.remove(r)
|
|
1624
|
+
n = len(L)
|
|
1625
|
+
for i in range(n):
|
|
1626
|
+
for j in range(i):
|
|
1627
|
+
r = L[i] * L[j]
|
|
1628
|
+
automorphs.append(r)
|
|
1629
|
+
|
|
1630
|
+
# supplement (i)
|
|
1631
|
+
for k in range(len(sym)):
|
|
1632
|
+
s = sym[k:k+3]
|
|
1633
|
+
if sum([b[1] for b in s if b[0] - s[0][0] < 4]) >= 3:
|
|
1634
|
+
automorphs += [ZZ.one(), ZZ(3), ZZ(5), ZZ(7)]
|
|
1635
|
+
break
|
|
1636
|
+
|
|
1637
|
+
# supplement (ii)
|
|
1638
|
+
I.sort(key=lambda x: x.valuation(2))
|
|
1639
|
+
n = len(I)
|
|
1640
|
+
for i in range(n):
|
|
1641
|
+
for j in range(i):
|
|
1642
|
+
r = I[i] / I[j]
|
|
1643
|
+
v, u = r.val_unit(ZZ(2))
|
|
1644
|
+
u = u % 8
|
|
1645
|
+
assert v >= 0
|
|
1646
|
+
if v == 0 and u == 1:
|
|
1647
|
+
automorphs.append(ZZ(2))
|
|
1648
|
+
if v == 0 and u == 5:
|
|
1649
|
+
automorphs.append(ZZ(6))
|
|
1650
|
+
if v in [0, 2, 4]: # this overlaps with the first two cases!
|
|
1651
|
+
automorphs.append(ZZ(5))
|
|
1652
|
+
if v in [1, 3] and u in [1, 5]:
|
|
1653
|
+
automorphs.append(ZZ(3))
|
|
1654
|
+
if v in [1, 3] and u in [3, 7]:
|
|
1655
|
+
automorphs.append(ZZ(7))
|
|
1656
|
+
|
|
1657
|
+
# normalize the square classes and remove duplicates
|
|
1658
|
+
automorphs1 = set()
|
|
1659
|
+
for s in automorphs:
|
|
1660
|
+
v, u = s.val_unit(ZZ(2))
|
|
1661
|
+
v = v % 2
|
|
1662
|
+
u = u % 8
|
|
1663
|
+
sq = u * 2**v
|
|
1664
|
+
automorphs1.add(sq)
|
|
1665
|
+
return list(automorphs1)
|
|
1666
|
+
|
|
1667
|
+
def canonical_symbol(self):
|
|
1668
|
+
r"""
|
|
1669
|
+
Return (and cache) the canonical `p`-adic genus symbol. This is
|
|
1670
|
+
only really affects the `2`-adic symbol, since when `p > 2` the
|
|
1671
|
+
symbol is already canonical.
|
|
1672
|
+
|
|
1673
|
+
OUTPUT: list of lists of integers
|
|
1674
|
+
|
|
1675
|
+
EXAMPLES::
|
|
1676
|
+
|
|
1677
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
1678
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
1679
|
+
|
|
1680
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 1, 1, 2])
|
|
1681
|
+
sage: p = 2
|
|
1682
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G2.symbol_tuple_list()
|
|
1683
|
+
[[0, 2, 1, 1, 2]]
|
|
1684
|
+
sage: G2.canonical_symbol()
|
|
1685
|
+
[[0, 2, 1, 1, 2]]
|
|
1686
|
+
|
|
1687
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 0, 0, 2])
|
|
1688
|
+
sage: p = 2
|
|
1689
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G2.symbol_tuple_list()
|
|
1690
|
+
[[0, 1, 1, 1, 1], [1, 1, 1, 1, 1]]
|
|
1691
|
+
sage: G2.canonical_symbol() # Oddity fusion occurred here!
|
|
1692
|
+
[[0, 1, 1, 1, 2], [1, 1, 1, 1, 0]]
|
|
1693
|
+
|
|
1694
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1,2,3,4]).Hessian_matrix()
|
|
1695
|
+
sage: p = 2
|
|
1696
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G2.symbol_tuple_list()
|
|
1697
|
+
[[1, 2, 3, 1, 4], [2, 1, 1, 1, 1], [3, 1, 1, 1, 1]]
|
|
1698
|
+
sage: G2.canonical_symbol() # Oddity fusion occurred here!
|
|
1699
|
+
[[1, 2, -1, 1, 6], [2, 1, 1, 1, 0], [3, 1, 1, 1, 0]]
|
|
1700
|
+
|
|
1701
|
+
sage: A = Matrix(ZZ, 2, 2, [2, 1, 1, 2])
|
|
1702
|
+
sage: p = 2
|
|
1703
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G2.symbol_tuple_list()
|
|
1704
|
+
[[0, 2, 3, 0, 0]]
|
|
1705
|
+
sage: G2.canonical_symbol()
|
|
1706
|
+
[[0, 2, -1, 0, 0]]
|
|
1707
|
+
|
|
1708
|
+
|
|
1709
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
1710
|
+
sage: p = 3
|
|
1711
|
+
sage: G3 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G3.symbol_tuple_list()
|
|
1712
|
+
[[0, 3, 1], [1, 1, -1]]
|
|
1713
|
+
sage: G3.canonical_symbol()
|
|
1714
|
+
[[0, 3, 1], [1, 1, -1]]
|
|
1715
|
+
|
|
1716
|
+
.. NOTE::
|
|
1717
|
+
|
|
1718
|
+
See [CS1999]_ Conway-Sloane 3rd edition, pp. 381-382 for definitions
|
|
1719
|
+
and examples.
|
|
1720
|
+
|
|
1721
|
+
.. TODO::
|
|
1722
|
+
|
|
1723
|
+
Add an example where sign walking occurs!
|
|
1724
|
+
"""
|
|
1725
|
+
symbol = self._symbol
|
|
1726
|
+
if self._prime == 2:
|
|
1727
|
+
if self._canonical_symbol is None:
|
|
1728
|
+
self._canonical_symbol = canonical_2_adic_reduction(symbol)
|
|
1729
|
+
return self._canonical_symbol
|
|
1730
|
+
else:
|
|
1731
|
+
return self._symbol
|
|
1732
|
+
|
|
1733
|
+
def gram_matrix(self, check=True):
|
|
1734
|
+
r"""
|
|
1735
|
+
Return a Gram matrix of a representative of this local genus.
|
|
1736
|
+
|
|
1737
|
+
INPUT:
|
|
1738
|
+
|
|
1739
|
+
- ``check`` -- boolean (default: ``True``); double check the result
|
|
1740
|
+
|
|
1741
|
+
EXAMPLES::
|
|
1742
|
+
|
|
1743
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
1744
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
1745
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
1746
|
+
sage: p = 2
|
|
1747
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2))
|
|
1748
|
+
sage: G2.gram_matrix()
|
|
1749
|
+
[2 0|0|0]
|
|
1750
|
+
[0 6|0|0]
|
|
1751
|
+
[---+-+-]
|
|
1752
|
+
[0 0|4|0]
|
|
1753
|
+
[---+-+-]
|
|
1754
|
+
[0 0|0|8]
|
|
1755
|
+
"""
|
|
1756
|
+
p = self._prime
|
|
1757
|
+
symbol = self.symbol_tuple_list()
|
|
1758
|
+
G = [_gram_from_jordan_block(p, block) for block in symbol]
|
|
1759
|
+
G = matrix.block_diagonal(G)
|
|
1760
|
+
# check calculation
|
|
1761
|
+
if check:
|
|
1762
|
+
symG = p_adic_symbol(G, p, symbol[-1][0])
|
|
1763
|
+
assert Genus_Symbol_p_adic_ring(p, symG) == self
|
|
1764
|
+
return G
|
|
1765
|
+
|
|
1766
|
+
def mass(self):
|
|
1767
|
+
r"""
|
|
1768
|
+
Return the local mass `m_p` of this genus as defined by Conway.
|
|
1769
|
+
|
|
1770
|
+
See Equation (3) in [CS1988]_.
|
|
1771
|
+
|
|
1772
|
+
EXAMPLES::
|
|
1773
|
+
|
|
1774
|
+
sage: G = Genus(matrix.diagonal([1, 3, 9]))
|
|
1775
|
+
sage: G.local_symbol(3).mass()
|
|
1776
|
+
9/8
|
|
1777
|
+
|
|
1778
|
+
TESTS::
|
|
1779
|
+
|
|
1780
|
+
sage: G = Genus(matrix([1]))
|
|
1781
|
+
sage: G.local_symbol(2).mass()
|
|
1782
|
+
Traceback (most recent call last):
|
|
1783
|
+
....
|
|
1784
|
+
ValueError: the dimension must be at least 2
|
|
1785
|
+
"""
|
|
1786
|
+
if self.dimension() <= 1:
|
|
1787
|
+
raise ValueError("the dimension must be at least 2")
|
|
1788
|
+
p = self.prime()
|
|
1789
|
+
sym = self._symbol
|
|
1790
|
+
##############
|
|
1791
|
+
# diagonal product
|
|
1792
|
+
##############
|
|
1793
|
+
|
|
1794
|
+
# diagonal factors
|
|
1795
|
+
m_p = ZZ.prod(M_p(species, p) for species in self._species_list())
|
|
1796
|
+
# cross terms
|
|
1797
|
+
r = len(sym)
|
|
1798
|
+
ct = 0
|
|
1799
|
+
for j in range(r):
|
|
1800
|
+
for i in range(j):
|
|
1801
|
+
ct += (sym[j][0] - sym[i][0]) * sym[i][1] * sym[j][1]
|
|
1802
|
+
ct = ct / QQ(2)
|
|
1803
|
+
m_p *= p**ct
|
|
1804
|
+
|
|
1805
|
+
if p != 2:
|
|
1806
|
+
return m_p
|
|
1807
|
+
|
|
1808
|
+
# type factors
|
|
1809
|
+
nII = ZZ.sum(fq[1] for fq in sym if fq[3] == 0)
|
|
1810
|
+
|
|
1811
|
+
nI_I = ZZ.zero() # the total number of pairs of adjacent constituents f_q,
|
|
1812
|
+
# f_2q that are both of type I (odd)
|
|
1813
|
+
for k in range(r-1):
|
|
1814
|
+
if sym[k][3] == sym[k+1][3] == 1 and sym[k][0] + 1 == sym[k+1][0]:
|
|
1815
|
+
nI_I += ZZ.one()
|
|
1816
|
+
return m_p * ZZ(2)**(nI_I - nII)
|
|
1817
|
+
|
|
1818
|
+
def _standard_mass(self):
|
|
1819
|
+
r"""
|
|
1820
|
+
Return the standard p-mass of this local genus.
|
|
1821
|
+
|
|
1822
|
+
See Equation (6) of [CS1988]_.
|
|
1823
|
+
|
|
1824
|
+
EXAMPLES::
|
|
1825
|
+
|
|
1826
|
+
sage: G = Genus(matrix.diagonal([1,3,9]))
|
|
1827
|
+
sage: g3 = G.local_symbol(3)
|
|
1828
|
+
sage: g3._standard_mass()
|
|
1829
|
+
9/16
|
|
1830
|
+
"""
|
|
1831
|
+
n = self.dimension()
|
|
1832
|
+
p = self.prime()
|
|
1833
|
+
s = (n + 1) // 2
|
|
1834
|
+
std = 2 * QQ.prod(1 - p**(-2 * k) for k in range(1, s))
|
|
1835
|
+
if not n % 2:
|
|
1836
|
+
D = ZZ(-1)**s * self.determinant()
|
|
1837
|
+
epsilon = (4 * D).kronecker(p)
|
|
1838
|
+
std *= (1 - epsilon * p**(-s))
|
|
1839
|
+
return QQ.one() / std
|
|
1840
|
+
|
|
1841
|
+
def _species_list(self) -> list:
|
|
1842
|
+
r"""
|
|
1843
|
+
Return the species list.
|
|
1844
|
+
|
|
1845
|
+
See Table 1 in [CS1988]_.
|
|
1846
|
+
|
|
1847
|
+
EXAMPLES::
|
|
1848
|
+
|
|
1849
|
+
sage: G = Genus(matrix.diagonal([1,3,27]))
|
|
1850
|
+
sage: g3 = G.local_symbol(3)
|
|
1851
|
+
sage: g3._species_list()
|
|
1852
|
+
[1, 1, 1]
|
|
1853
|
+
"""
|
|
1854
|
+
p = self.prime()
|
|
1855
|
+
species_list = []
|
|
1856
|
+
sym = self._symbol
|
|
1857
|
+
if self.prime() != 2:
|
|
1858
|
+
for k in range(len(sym)):
|
|
1859
|
+
n = ZZ(sym[k][1])
|
|
1860
|
+
d = sym[k][2]
|
|
1861
|
+
if n % 2 == 0 and d != ZZ(-1).kronecker(p)**(n // ZZ(2)):
|
|
1862
|
+
species = -n
|
|
1863
|
+
else:
|
|
1864
|
+
species = n
|
|
1865
|
+
species_list.append(species)
|
|
1866
|
+
return species_list
|
|
1867
|
+
|
|
1868
|
+
# p == 2
|
|
1869
|
+
# create a dense list of symbols
|
|
1870
|
+
symbols = []
|
|
1871
|
+
s = 0
|
|
1872
|
+
for k in range(sym[-1][0] + 1):
|
|
1873
|
+
if sym[s][0] == k:
|
|
1874
|
+
symbols.append(sym[s])
|
|
1875
|
+
s += 1
|
|
1876
|
+
else:
|
|
1877
|
+
symbols.append([k, 0, 1, 0, 0])
|
|
1878
|
+
# avoid a case distinction
|
|
1879
|
+
sym = [[-2, 0, 1, 0, 0], [-1, 0, 1, 0, 0]] + symbols + [[sym[-1][0]+1, 0, 1, 0, 0], [sym[-1][0] + 2, 0, 1, 0, 0]]
|
|
1880
|
+
for k in range(1, len(sym)-1):
|
|
1881
|
+
free = True
|
|
1882
|
+
if sym[k-1][3] == 1 or sym[k+1][3] == 1:
|
|
1883
|
+
free = False
|
|
1884
|
+
n = sym[k][1]
|
|
1885
|
+
o = sym[k][4]
|
|
1886
|
+
if ZZ(sym[k][2]).kronecker(2) == -1:
|
|
1887
|
+
o = (o + ZZ(4)) % 8
|
|
1888
|
+
if sym[k][3] == 0 or n % 2 == 1:
|
|
1889
|
+
t = n // ZZ(2)
|
|
1890
|
+
else:
|
|
1891
|
+
t = (n // ZZ(2)) - ZZ.one()
|
|
1892
|
+
if free and (o == 0 or o == 1 or o == 7):
|
|
1893
|
+
species = 2*t
|
|
1894
|
+
elif free and (o == 3 or o == 5 or o == 4):
|
|
1895
|
+
species = -2*t
|
|
1896
|
+
else:
|
|
1897
|
+
species = 2*t + 1
|
|
1898
|
+
species_list.append(species)
|
|
1899
|
+
return species_list
|
|
1900
|
+
|
|
1901
|
+
def prime(self):
|
|
1902
|
+
r"""
|
|
1903
|
+
Return the prime number `p` of this `p`-adic local symbol.
|
|
1904
|
+
|
|
1905
|
+
OUTPUT: integer
|
|
1906
|
+
|
|
1907
|
+
EXAMPLES::
|
|
1908
|
+
|
|
1909
|
+
sage: from sage.quadratic_forms.genera.genus import LocalGenusSymbol
|
|
1910
|
+
sage: M1 = matrix(ZZ, [2])
|
|
1911
|
+
sage: p = 2
|
|
1912
|
+
sage: G0 = LocalGenusSymbol(M1, 2)
|
|
1913
|
+
sage: G0.prime()
|
|
1914
|
+
2
|
|
1915
|
+
"""
|
|
1916
|
+
return self._prime
|
|
1917
|
+
|
|
1918
|
+
def is_even(self) -> bool:
|
|
1919
|
+
r"""
|
|
1920
|
+
Return if the underlying `p`-adic lattice is even.
|
|
1921
|
+
|
|
1922
|
+
If `p` is odd, every lattice is even.
|
|
1923
|
+
|
|
1924
|
+
EXAMPLES::
|
|
1925
|
+
|
|
1926
|
+
sage: from sage.quadratic_forms.genera.genus import LocalGenusSymbol
|
|
1927
|
+
sage: M0 = matrix(ZZ, [1])
|
|
1928
|
+
sage: G0 = LocalGenusSymbol(M0, 2)
|
|
1929
|
+
sage: G0.is_even()
|
|
1930
|
+
False
|
|
1931
|
+
sage: G1 = LocalGenusSymbol(M0, 3)
|
|
1932
|
+
sage: G1.is_even()
|
|
1933
|
+
True
|
|
1934
|
+
sage: M2 = matrix(ZZ, [2])
|
|
1935
|
+
sage: G2 = LocalGenusSymbol(M2, 2)
|
|
1936
|
+
sage: G2.is_even()
|
|
1937
|
+
True
|
|
1938
|
+
"""
|
|
1939
|
+
if self.prime() != 2:
|
|
1940
|
+
return True
|
|
1941
|
+
sym = self.symbol_tuple_list()[0]
|
|
1942
|
+
return sym[0] > 0 or sym[3] == 0
|
|
1943
|
+
|
|
1944
|
+
def symbol_tuple_list(self):
|
|
1945
|
+
r"""
|
|
1946
|
+
Return a copy of the underlying list of lists of integers
|
|
1947
|
+
defining the genus symbol.
|
|
1948
|
+
|
|
1949
|
+
OUTPUT: list of lists of integers
|
|
1950
|
+
|
|
1951
|
+
EXAMPLES::
|
|
1952
|
+
|
|
1953
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
1954
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
1955
|
+
|
|
1956
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
1957
|
+
sage: p = 3
|
|
1958
|
+
sage: G3 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G3
|
|
1959
|
+
Genus symbol at 3: 1^3 3^-1
|
|
1960
|
+
sage: G3.symbol_tuple_list()
|
|
1961
|
+
[[0, 3, 1], [1, 1, -1]]
|
|
1962
|
+
sage: type(G3.symbol_tuple_list())
|
|
1963
|
+
<... 'list'>
|
|
1964
|
+
|
|
1965
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
1966
|
+
sage: p = 2
|
|
1967
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G2
|
|
1968
|
+
Genus symbol at 2: [2^-2 4^1 8^1]_6
|
|
1969
|
+
sage: G2.symbol_tuple_list()
|
|
1970
|
+
[[1, 2, 3, 1, 4], [2, 1, 1, 1, 1], [3, 1, 1, 1, 1]]
|
|
1971
|
+
sage: type(G2.symbol_tuple_list())
|
|
1972
|
+
<... 'list'>
|
|
1973
|
+
"""
|
|
1974
|
+
return deepcopy(self._symbol)
|
|
1975
|
+
|
|
1976
|
+
def number_of_blocks(self):
|
|
1977
|
+
r"""
|
|
1978
|
+
Return the number of positive dimensional symbols/Jordan blocks.
|
|
1979
|
+
|
|
1980
|
+
OUTPUT: nonnegative integer
|
|
1981
|
+
|
|
1982
|
+
EXAMPLES::
|
|
1983
|
+
|
|
1984
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
1985
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
1986
|
+
|
|
1987
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
1988
|
+
sage: p = 2
|
|
1989
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2))
|
|
1990
|
+
sage: G2.symbol_tuple_list()
|
|
1991
|
+
[[1, 2, 3, 1, 4], [2, 1, 1, 1, 1], [3, 1, 1, 1, 1]]
|
|
1992
|
+
sage: G2.number_of_blocks()
|
|
1993
|
+
3
|
|
1994
|
+
|
|
1995
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1,2,3,4]).Hessian_matrix()
|
|
1996
|
+
sage: p = 3
|
|
1997
|
+
sage: G3 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2))
|
|
1998
|
+
sage: G3.symbol_tuple_list()
|
|
1999
|
+
[[0, 3, 1], [1, 1, -1]]
|
|
2000
|
+
sage: G3.number_of_blocks()
|
|
2001
|
+
2
|
|
2002
|
+
"""
|
|
2003
|
+
return len(self._symbol)
|
|
2004
|
+
|
|
2005
|
+
def determinant(self):
|
|
2006
|
+
r"""
|
|
2007
|
+
Return the (`p`-part of the) determinant (square-class) of the
|
|
2008
|
+
Hessian matrix of the quadratic form (given by regarding the
|
|
2009
|
+
integral symmetric matrix which generated this genus symbol as
|
|
2010
|
+
the Gram matrix of `Q`) associated to this local genus symbol.
|
|
2011
|
+
|
|
2012
|
+
OUTPUT: integer
|
|
2013
|
+
|
|
2014
|
+
EXAMPLES::
|
|
2015
|
+
|
|
2016
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
2017
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
2018
|
+
|
|
2019
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
2020
|
+
sage: p = 2
|
|
2021
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G2
|
|
2022
|
+
Genus symbol at 2: [2^-2 4^1 8^1]_6
|
|
2023
|
+
sage: G2.determinant()
|
|
2024
|
+
128
|
|
2025
|
+
|
|
2026
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
2027
|
+
sage: p = 3
|
|
2028
|
+
sage: G3 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G3
|
|
2029
|
+
Genus symbol at 3: 1^3 3^-1
|
|
2030
|
+
sage: G3.determinant()
|
|
2031
|
+
3
|
|
2032
|
+
"""
|
|
2033
|
+
p = self._prime
|
|
2034
|
+
return prod([p**(s[0] * s[1]) for s in self._symbol])
|
|
2035
|
+
|
|
2036
|
+
det = determinant
|
|
2037
|
+
|
|
2038
|
+
def dimension(self):
|
|
2039
|
+
r"""
|
|
2040
|
+
Return the dimension of a quadratic form associated to this genus symbol.
|
|
2041
|
+
|
|
2042
|
+
OUTPUT: nonnegative integer
|
|
2043
|
+
|
|
2044
|
+
EXAMPLES::
|
|
2045
|
+
|
|
2046
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
2047
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
2048
|
+
|
|
2049
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
2050
|
+
sage: p = 2
|
|
2051
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G2
|
|
2052
|
+
Genus symbol at 2: [2^-2 4^1 8^1]_6
|
|
2053
|
+
sage: G2.dimension()
|
|
2054
|
+
4
|
|
2055
|
+
|
|
2056
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
2057
|
+
sage: p = 3
|
|
2058
|
+
sage: G3 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G3
|
|
2059
|
+
Genus symbol at 3: 1^3 3^-1
|
|
2060
|
+
sage: G3.dimension()
|
|
2061
|
+
4
|
|
2062
|
+
"""
|
|
2063
|
+
return sum([s[1] for s in self._symbol])
|
|
2064
|
+
|
|
2065
|
+
dim = dimension
|
|
2066
|
+
rank = dimension
|
|
2067
|
+
|
|
2068
|
+
def direct_sum(self, other):
|
|
2069
|
+
r"""
|
|
2070
|
+
Return the local genus of the direct sum of two representatives.
|
|
2071
|
+
|
|
2072
|
+
EXAMPLES::
|
|
2073
|
+
|
|
2074
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
2075
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
2076
|
+
sage: A = matrix.diagonal([1, 2, 3, 4])
|
|
2077
|
+
sage: p = 2
|
|
2078
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G2
|
|
2079
|
+
Genus symbol at 2: [1^-2 2^1 4^1]_6
|
|
2080
|
+
sage: G2.direct_sum(G2)
|
|
2081
|
+
Genus symbol at 2: [1^4 2^2 4^2]_4
|
|
2082
|
+
|
|
2083
|
+
TESTS::
|
|
2084
|
+
|
|
2085
|
+
sage: G = Genus(matrix([6]))
|
|
2086
|
+
sage: G2 = G.local_symbol(2)
|
|
2087
|
+
sage: G3 = G.local_symbol(3)
|
|
2088
|
+
sage: G2.direct_sum(G3)
|
|
2089
|
+
Traceback (most recent call last):
|
|
2090
|
+
...
|
|
2091
|
+
ValueError: the local genus symbols must be over the same prime
|
|
2092
|
+
"""
|
|
2093
|
+
if self.prime() != other.prime():
|
|
2094
|
+
raise ValueError("the local genus symbols must be over the same prime")
|
|
2095
|
+
sym1 = self.symbol_tuple_list()
|
|
2096
|
+
sym2 = other.symbol_tuple_list()
|
|
2097
|
+
m = max(sym1[-1][0], sym2[-1][0])
|
|
2098
|
+
sym1 = dict([[s[0], s] for s in sym1])
|
|
2099
|
+
sym2 = dict([[s[0], s] for s in sym2])
|
|
2100
|
+
|
|
2101
|
+
symbol = []
|
|
2102
|
+
for k in range(m + 1):
|
|
2103
|
+
if self.prime() == 2:
|
|
2104
|
+
b = [k, 0, 1, 0, 0]
|
|
2105
|
+
else:
|
|
2106
|
+
b = [k, 0, 1]
|
|
2107
|
+
for sym in [sym1, sym2]:
|
|
2108
|
+
try:
|
|
2109
|
+
s = sym[k]
|
|
2110
|
+
b[1] += s[1]
|
|
2111
|
+
b[2] *= s[2]
|
|
2112
|
+
if self.prime() == 2:
|
|
2113
|
+
b[2] = b[2] % 8
|
|
2114
|
+
if s[3] == 1:
|
|
2115
|
+
b[3] = s[3]
|
|
2116
|
+
b[4] = (b[4] + s[4]) % 8
|
|
2117
|
+
except KeyError:
|
|
2118
|
+
pass
|
|
2119
|
+
if b[1] != 0:
|
|
2120
|
+
symbol.append(b)
|
|
2121
|
+
if self.rank() == other.rank() == 0:
|
|
2122
|
+
symbol = self.symbol_tuple_list()
|
|
2123
|
+
return Genus_Symbol_p_adic_ring(self.prime(), symbol)
|
|
2124
|
+
|
|
2125
|
+
def excess(self):
|
|
2126
|
+
r"""
|
|
2127
|
+
Return the `p`-excess of the quadratic form whose Hessian
|
|
2128
|
+
matrix is the symmetric matrix `A`. When `p = 2`, the `p`-excess is
|
|
2129
|
+
called the oddity.
|
|
2130
|
+
|
|
2131
|
+
.. WARNING::
|
|
2132
|
+
|
|
2133
|
+
This normalization seems non-standard, and we
|
|
2134
|
+
should review this entire class to make sure that we have our
|
|
2135
|
+
doubling conventions straight throughout!
|
|
2136
|
+
|
|
2137
|
+
REFERENCE:
|
|
2138
|
+
|
|
2139
|
+
[CS1999]_ Conway and Sloane Book, 3rd edition, pp 370-371.
|
|
2140
|
+
|
|
2141
|
+
OUTPUT: integer
|
|
2142
|
+
|
|
2143
|
+
EXAMPLES::
|
|
2144
|
+
|
|
2145
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
2146
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
2147
|
+
|
|
2148
|
+
sage: AC = diagonal_matrix(ZZ, [1, 3, -3])
|
|
2149
|
+
sage: p=2; Genus_Symbol_p_adic_ring(p, p_adic_symbol(AC, p, 2)).excess()
|
|
2150
|
+
1
|
|
2151
|
+
sage: p=3; Genus_Symbol_p_adic_ring(p, p_adic_symbol(AC, p, 2)).excess()
|
|
2152
|
+
0
|
|
2153
|
+
sage: p=5; Genus_Symbol_p_adic_ring(p, p_adic_symbol(AC, p, 2)).excess()
|
|
2154
|
+
0
|
|
2155
|
+
sage: p=7; Genus_Symbol_p_adic_ring(p, p_adic_symbol(AC, p, 2)).excess()
|
|
2156
|
+
0
|
|
2157
|
+
sage: p=11; Genus_Symbol_p_adic_ring(p, p_adic_symbol(AC, p, 2)).excess()
|
|
2158
|
+
0
|
|
2159
|
+
|
|
2160
|
+
sage: AC = 2 * diagonal_matrix(ZZ, [1, 3, -3])
|
|
2161
|
+
sage: p=2; Genus_Symbol_p_adic_ring(p, p_adic_symbol(AC, p, 2)).excess()
|
|
2162
|
+
1
|
|
2163
|
+
sage: p=3; Genus_Symbol_p_adic_ring(p, p_adic_symbol(AC, p, 2)).excess()
|
|
2164
|
+
0
|
|
2165
|
+
sage: p=5; Genus_Symbol_p_adic_ring(p, p_adic_symbol(AC, p, 2)).excess()
|
|
2166
|
+
0
|
|
2167
|
+
sage: p=7; Genus_Symbol_p_adic_ring(p, p_adic_symbol(AC, p, 2)).excess()
|
|
2168
|
+
0
|
|
2169
|
+
sage: p=11; Genus_Symbol_p_adic_ring(p, p_adic_symbol(AC, p, 2)).excess()
|
|
2170
|
+
0
|
|
2171
|
+
|
|
2172
|
+
sage: A = 2*diagonal_matrix(ZZ, [1, 2, 3, 4])
|
|
2173
|
+
sage: p = 2; Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)).excess()
|
|
2174
|
+
2
|
|
2175
|
+
sage: p = 3; Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)).excess()
|
|
2176
|
+
6
|
|
2177
|
+
sage: p = 5; Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)).excess()
|
|
2178
|
+
0
|
|
2179
|
+
sage: p = 7; Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)).excess()
|
|
2180
|
+
0
|
|
2181
|
+
sage: p = 11; Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)).excess()
|
|
2182
|
+
0
|
|
2183
|
+
"""
|
|
2184
|
+
p = self._prime
|
|
2185
|
+
if self._prime == 2:
|
|
2186
|
+
k = 0
|
|
2187
|
+
for s in self._symbol:
|
|
2188
|
+
if s[0] % 2 == 1 and s[2] in (3, 5):
|
|
2189
|
+
k += 1
|
|
2190
|
+
return Integer(sum([s[4] for s in self._symbol]) + 4*k).mod(8)
|
|
2191
|
+
else:
|
|
2192
|
+
k = 0
|
|
2193
|
+
for s in self._symbol:
|
|
2194
|
+
if s[0] % 2 == 1 and s[2] == -1:
|
|
2195
|
+
k += 1
|
|
2196
|
+
return Integer(sum([s[1] * (p**s[0]-1) for s in self._symbol]) + 4*k).mod(8)
|
|
2197
|
+
|
|
2198
|
+
def scale(self):
|
|
2199
|
+
r"""
|
|
2200
|
+
Return the scale of this local genus.
|
|
2201
|
+
|
|
2202
|
+
Let `L` be a lattice with bilinear form `b`.
|
|
2203
|
+
The scale of `(L,b)` is defined as the ideal
|
|
2204
|
+
`b(L,L)`.
|
|
2205
|
+
|
|
2206
|
+
OUTPUT: integer
|
|
2207
|
+
|
|
2208
|
+
EXAMPLES::
|
|
2209
|
+
|
|
2210
|
+
sage: G = Genus(matrix.diagonal([2, 4, 18]))
|
|
2211
|
+
sage: G.local_symbol(2).scale()
|
|
2212
|
+
2
|
|
2213
|
+
sage: G.local_symbol(3).scale()
|
|
2214
|
+
1
|
|
2215
|
+
"""
|
|
2216
|
+
if self.rank() == 0:
|
|
2217
|
+
return ZZ.zero()
|
|
2218
|
+
return self.prime()**self._symbol[0][0]
|
|
2219
|
+
|
|
2220
|
+
def norm(self):
|
|
2221
|
+
r"""
|
|
2222
|
+
Return the norm of this local genus.
|
|
2223
|
+
|
|
2224
|
+
Let `L` be a lattice with bilinear form `b`.
|
|
2225
|
+
The norm of `(L,b)` is defined as the ideal
|
|
2226
|
+
generated by `\{b(x,x) | x \in L\}`.
|
|
2227
|
+
|
|
2228
|
+
EXAMPLES::
|
|
2229
|
+
|
|
2230
|
+
sage: G = Genus(matrix.diagonal([2, 4, 18]))
|
|
2231
|
+
sage: G.local_symbol(2).norm()
|
|
2232
|
+
2
|
|
2233
|
+
sage: G = Genus(matrix(ZZ,2,[0, 1, 1, 0]))
|
|
2234
|
+
sage: G.local_symbol(2).norm()
|
|
2235
|
+
2
|
|
2236
|
+
"""
|
|
2237
|
+
if self.rank() == 0:
|
|
2238
|
+
return ZZ.zero()
|
|
2239
|
+
p = self.prime()
|
|
2240
|
+
if p == 2:
|
|
2241
|
+
fq = self._symbol[0]
|
|
2242
|
+
return self.prime()**(fq[0] + 1 - fq[3])
|
|
2243
|
+
else:
|
|
2244
|
+
return self.scale()
|
|
2245
|
+
|
|
2246
|
+
def level(self):
|
|
2247
|
+
r"""
|
|
2248
|
+
Return the maximal scale of a Jordan component.
|
|
2249
|
+
|
|
2250
|
+
EXAMPLES::
|
|
2251
|
+
|
|
2252
|
+
sage: G = Genus(matrix.diagonal([2, 4, 18]))
|
|
2253
|
+
sage: G.local_symbol(2).level()
|
|
2254
|
+
4
|
|
2255
|
+
"""
|
|
2256
|
+
if self.rank() == 0:
|
|
2257
|
+
return ZZ.one()
|
|
2258
|
+
return self.prime()**self._symbol[-1][0]
|
|
2259
|
+
|
|
2260
|
+
def trains(self) -> list:
|
|
2261
|
+
r"""
|
|
2262
|
+
Compute the indices for each of the trains in this local genus
|
|
2263
|
+
symbol if it is associated to the prime `p=2` (and raise an
|
|
2264
|
+
error for all other primes).
|
|
2265
|
+
|
|
2266
|
+
OUTPUT: list of nonnegative integers
|
|
2267
|
+
|
|
2268
|
+
EXAMPLES::
|
|
2269
|
+
|
|
2270
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
2271
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
2272
|
+
|
|
2273
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
2274
|
+
sage: p = 2
|
|
2275
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G2
|
|
2276
|
+
Genus symbol at 2: [2^-2 4^1 8^1]_6
|
|
2277
|
+
sage: G2.trains()
|
|
2278
|
+
[[0, 1, 2]]
|
|
2279
|
+
"""
|
|
2280
|
+
# Check that p = 2
|
|
2281
|
+
if self._prime != 2:
|
|
2282
|
+
raise TypeError("trains() only makes sense when the prime of the p_adic_Genus_Symbol is p=2")
|
|
2283
|
+
symbol = self._symbol
|
|
2284
|
+
return canonical_2_adic_trains(symbol)
|
|
2285
|
+
|
|
2286
|
+
def compartments(self) -> list:
|
|
2287
|
+
r"""
|
|
2288
|
+
Compute the indices for each of the compartments in this local genus
|
|
2289
|
+
symbol if it is associated to the prime `p=2` (and raise an
|
|
2290
|
+
error for all other primes).
|
|
2291
|
+
|
|
2292
|
+
OUTPUT: list of nonnegative integers
|
|
2293
|
+
|
|
2294
|
+
EXAMPLES::
|
|
2295
|
+
|
|
2296
|
+
sage: from sage.quadratic_forms.genera.genus import p_adic_symbol
|
|
2297
|
+
sage: from sage.quadratic_forms.genera.genus import Genus_Symbol_p_adic_ring
|
|
2298
|
+
|
|
2299
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
2300
|
+
sage: p = 2
|
|
2301
|
+
sage: G2 = Genus_Symbol_p_adic_ring(p, p_adic_symbol(A, p, 2)); G2
|
|
2302
|
+
Genus symbol at 2: [2^-2 4^1 8^1]_6
|
|
2303
|
+
sage: G2.compartments()
|
|
2304
|
+
[[0, 1, 2]]
|
|
2305
|
+
"""
|
|
2306
|
+
# Check that p = 2
|
|
2307
|
+
if self._prime != 2:
|
|
2308
|
+
raise TypeError("compartments() only makes sense when the prime of the p_adic_Genus_Symbol is p=2")
|
|
2309
|
+
symbol = self._symbol
|
|
2310
|
+
return canonical_2_adic_compartments(symbol)
|
|
2311
|
+
|
|
2312
|
+
|
|
2313
|
+
class GenusSymbol_global_ring:
|
|
2314
|
+
r"""
|
|
2315
|
+
This represents a collection of local genus symbols (at primes)
|
|
2316
|
+
and signature information which represent the genus of a
|
|
2317
|
+
non-degenerate integral lattice.
|
|
2318
|
+
|
|
2319
|
+
INPUT:
|
|
2320
|
+
|
|
2321
|
+
- ``signature_pair`` -- tuple of two nonnegative integers
|
|
2322
|
+
|
|
2323
|
+
- ``local_symbols`` -- list of :class:`Genus_Symbol_p_adic_ring` instances
|
|
2324
|
+
sorted by their primes
|
|
2325
|
+
|
|
2326
|
+
- ``representative`` -- (default: ``None``) integer symmetric matrix;
|
|
2327
|
+
the Gram matrix of a representative of this genus
|
|
2328
|
+
|
|
2329
|
+
- ``check`` -- boolean (default: ``True``); checks the input
|
|
2330
|
+
|
|
2331
|
+
EXAMPLES::
|
|
2332
|
+
|
|
2333
|
+
sage: from sage.quadratic_forms.genera.genus import GenusSymbol_global_ring, LocalGenusSymbol
|
|
2334
|
+
sage: A = matrix.diagonal(ZZ, [2, 4, 6, 8])
|
|
2335
|
+
sage: local_symbols = [LocalGenusSymbol(A, p) for p in (2*A.det()).prime_divisors()]
|
|
2336
|
+
sage: G = GenusSymbol_global_ring((4, 0),local_symbols, representative=A);G
|
|
2337
|
+
Genus of
|
|
2338
|
+
[2 0 0 0]
|
|
2339
|
+
[0 4 0 0]
|
|
2340
|
+
[0 0 6 0]
|
|
2341
|
+
[0 0 0 8]
|
|
2342
|
+
Signature: (4, 0)
|
|
2343
|
+
Genus symbol at 2: [2^-2 4^1 8^1]_6
|
|
2344
|
+
Genus symbol at 3: 1^3 3^-1
|
|
2345
|
+
|
|
2346
|
+
.. SEEALSO::
|
|
2347
|
+
|
|
2348
|
+
:func:`Genus` to create a :class:`GenusSymbol_global_ring` from the Gram matrix directly.
|
|
2349
|
+
"""
|
|
2350
|
+
|
|
2351
|
+
def __init__(self, signature_pair, local_symbols, representative=None, check=True):
|
|
2352
|
+
r"""
|
|
2353
|
+
Initialize a global genus symbol.
|
|
2354
|
+
|
|
2355
|
+
EXAMPLES::
|
|
2356
|
+
|
|
2357
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
2358
|
+
sage: G = Genus(A)
|
|
2359
|
+
sage: G == loads(dumps(G))
|
|
2360
|
+
True
|
|
2361
|
+
"""
|
|
2362
|
+
if check:
|
|
2363
|
+
if not all(isinstance(sym, Genus_Symbol_p_adic_ring) for sym in local_symbols):
|
|
2364
|
+
raise TypeError("local symbols must be a list of local genus symbols")
|
|
2365
|
+
n = signature_pair[0] + signature_pair[1]
|
|
2366
|
+
if not all(sym.dimension() == n for sym in local_symbols):
|
|
2367
|
+
raise TypeError("all local symbols must be of the same dimension")
|
|
2368
|
+
if representative is not None:
|
|
2369
|
+
if not representative.is_symmetric():
|
|
2370
|
+
raise ValueError("the representative must be a symmetric matrix")
|
|
2371
|
+
# check the symbols are sorted increasing by their prime
|
|
2372
|
+
if any(local_symbols[i].prime() >= local_symbols[i+1].prime()
|
|
2373
|
+
for i in range(len(local_symbols)-1)):
|
|
2374
|
+
raise ValueError("the local symbols must be sorted by their primes")
|
|
2375
|
+
if local_symbols[0].prime() != 2:
|
|
2376
|
+
raise ValueError("the first symbol must be 2-adic")
|
|
2377
|
+
if representative is not None:
|
|
2378
|
+
if representative.base_ring() != ZZ:
|
|
2379
|
+
representative = matrix(ZZ, representative)
|
|
2380
|
+
representative.set_immutable()
|
|
2381
|
+
self._representative = representative
|
|
2382
|
+
self._signature = signature_pair
|
|
2383
|
+
self._local_symbols = local_symbols
|
|
2384
|
+
|
|
2385
|
+
def __repr__(self) -> str:
|
|
2386
|
+
r"""
|
|
2387
|
+
Return a string representing the global genus symbol.
|
|
2388
|
+
|
|
2389
|
+
OUTPUT: string
|
|
2390
|
+
|
|
2391
|
+
EXAMPLES::
|
|
2392
|
+
|
|
2393
|
+
sage: A = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
2394
|
+
sage: GS = Genus(A)
|
|
2395
|
+
sage: GS
|
|
2396
|
+
Genus of
|
|
2397
|
+
[2 0 0 0]
|
|
2398
|
+
[0 4 0 0]
|
|
2399
|
+
[0 0 6 0]
|
|
2400
|
+
[0 0 0 8]
|
|
2401
|
+
Signature: (4, 0)
|
|
2402
|
+
Genus symbol at 2: [2^-2 4^1 8^1]_6
|
|
2403
|
+
Genus symbol at 3: 1^3 3^-1
|
|
2404
|
+
|
|
2405
|
+
sage: A2 = Matrix(ZZ, 2, 2, [2, -1, -1, 2])
|
|
2406
|
+
sage: Genus(A2)
|
|
2407
|
+
Genus of
|
|
2408
|
+
[ 2 -1]
|
|
2409
|
+
[-1 2]
|
|
2410
|
+
Signature: (2, 0)
|
|
2411
|
+
Genus symbol at 2: 1^-2
|
|
2412
|
+
Genus symbol at 3: 1^-1 3^-1
|
|
2413
|
+
"""
|
|
2414
|
+
rep = "Genus"
|
|
2415
|
+
if self.dimension() <= 20:
|
|
2416
|
+
rep += " of\n%s" % self._representative
|
|
2417
|
+
rep += f"\nSignature: {self._signature}"
|
|
2418
|
+
for s in self._local_symbols:
|
|
2419
|
+
rep += "\n" + s.__repr__()
|
|
2420
|
+
return rep
|
|
2421
|
+
|
|
2422
|
+
def _latex_(self) -> str:
|
|
2423
|
+
r"""
|
|
2424
|
+
The Latex representation of this lattice.
|
|
2425
|
+
|
|
2426
|
+
EXAMPLES::
|
|
2427
|
+
|
|
2428
|
+
sage: D4 = QuadraticForm(Matrix(ZZ, 4, 4, [2,0,0,-1, 0,2,0,-1, 0,0,2,-1, -1,-1,-1,2]))
|
|
2429
|
+
sage: G = D4.global_genus_symbol()
|
|
2430
|
+
sage: latex(G)
|
|
2431
|
+
\mbox{Genus of}\\ \left(\begin{array}{rrrr}
|
|
2432
|
+
2 & 0 & 0 & -1 \\
|
|
2433
|
+
0 & 2 & 0 & -1 \\
|
|
2434
|
+
0 & 0 & 2 & -1 \\
|
|
2435
|
+
-1 & -1 & -1 & 2
|
|
2436
|
+
\end{array}\right)\\ \mbox{Signature: } (4, 0)\\ \mbox{Genus symbol at } 2\mbox{: }1^{-2} :2^{-2}
|
|
2437
|
+
"""
|
|
2438
|
+
rep = r"\mbox{Genus"
|
|
2439
|
+
if self.dimension() <= 20:
|
|
2440
|
+
rep += r" of}\\ %s" % self._representative._latex_()
|
|
2441
|
+
else:
|
|
2442
|
+
rep += r"}"
|
|
2443
|
+
rep += fr"\\ \mbox{{Signature: }} {self._signature}"
|
|
2444
|
+
for s in self._local_symbols:
|
|
2445
|
+
rep += r"\\ " + s._latex_()
|
|
2446
|
+
return rep
|
|
2447
|
+
|
|
2448
|
+
def __eq__(self, other) -> bool:
|
|
2449
|
+
r"""
|
|
2450
|
+
Determines if two global genus symbols are equal (not just equivalent!).
|
|
2451
|
+
|
|
2452
|
+
INPUT:
|
|
2453
|
+
|
|
2454
|
+
- ``other`` -- a :class:`GenusSymbol_global_ring` object
|
|
2455
|
+
|
|
2456
|
+
OUTPUT: boolean
|
|
2457
|
+
|
|
2458
|
+
EXAMPLES::
|
|
2459
|
+
|
|
2460
|
+
sage: A1 = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
2461
|
+
sage: GS1 = Genus(A1)
|
|
2462
|
+
sage: A2 = DiagonalQuadraticForm(ZZ, [1, 2, 3, 5]).Hessian_matrix()
|
|
2463
|
+
sage: GS2 = Genus(A2)
|
|
2464
|
+
|
|
2465
|
+
sage: GS1 == GS2
|
|
2466
|
+
False
|
|
2467
|
+
|
|
2468
|
+
sage: GS2 == GS1
|
|
2469
|
+
False
|
|
2470
|
+
|
|
2471
|
+
sage: GS1 == GS1
|
|
2472
|
+
True
|
|
2473
|
+
|
|
2474
|
+
sage: GS2 == GS2
|
|
2475
|
+
True
|
|
2476
|
+
|
|
2477
|
+
TESTS::
|
|
2478
|
+
|
|
2479
|
+
sage: D4 = QuadraticForm(Matrix(ZZ, 4, 4, [2,0,0,-1, 0,2,0,-1, 0,0,2,-1, -1,-1,-1,2]))
|
|
2480
|
+
sage: G = D4.global_genus_symbol()
|
|
2481
|
+
sage: sage.quadratic_forms.genera.genus.is_GlobalGenus(G)
|
|
2482
|
+
True
|
|
2483
|
+
sage: G == deepcopy(G)
|
|
2484
|
+
True
|
|
2485
|
+
sage: sage.quadratic_forms.genera.genus.is_GlobalGenus(G)
|
|
2486
|
+
True
|
|
2487
|
+
"""
|
|
2488
|
+
if self is other:
|
|
2489
|
+
return True
|
|
2490
|
+
t = len(self._local_symbols)
|
|
2491
|
+
if t != len(other._local_symbols):
|
|
2492
|
+
return False
|
|
2493
|
+
for i in range(t):
|
|
2494
|
+
if self._local_symbols[i] != other._local_symbols[i]:
|
|
2495
|
+
return False
|
|
2496
|
+
return True
|
|
2497
|
+
|
|
2498
|
+
def __ne__(self, other) -> bool:
|
|
2499
|
+
r"""
|
|
2500
|
+
Determine if two global genus symbols are unequal (not just inequivalent!).
|
|
2501
|
+
|
|
2502
|
+
INPUT:
|
|
2503
|
+
|
|
2504
|
+
- ``other`` -- a ``GenusSymbol_global_ring`` object
|
|
2505
|
+
|
|
2506
|
+
OUTPUT: boolean
|
|
2507
|
+
|
|
2508
|
+
EXAMPLES::
|
|
2509
|
+
|
|
2510
|
+
sage: A1 = DiagonalQuadraticForm(ZZ, [1, 2, 3, 4]).Hessian_matrix()
|
|
2511
|
+
sage: GS1 = Genus(A1)
|
|
2512
|
+
sage: A2 = DiagonalQuadraticForm(ZZ, [1, 2, 3, 5]).Hessian_matrix()
|
|
2513
|
+
sage: GS2 = Genus(A2)
|
|
2514
|
+
|
|
2515
|
+
sage: GS1 != GS2
|
|
2516
|
+
True
|
|
2517
|
+
|
|
2518
|
+
sage: GS2 != GS1
|
|
2519
|
+
True
|
|
2520
|
+
|
|
2521
|
+
sage: GS1 != GS1
|
|
2522
|
+
False
|
|
2523
|
+
|
|
2524
|
+
sage: GS2 != GS2
|
|
2525
|
+
False
|
|
2526
|
+
"""
|
|
2527
|
+
return not self == other
|
|
2528
|
+
|
|
2529
|
+
def is_even(self) -> bool:
|
|
2530
|
+
r"""
|
|
2531
|
+
Return if this genus is even.
|
|
2532
|
+
|
|
2533
|
+
EXAMPLES::
|
|
2534
|
+
|
|
2535
|
+
sage: G = Genus(Matrix(ZZ,2,[2,1,1,2]))
|
|
2536
|
+
sage: G.is_even()
|
|
2537
|
+
True
|
|
2538
|
+
"""
|
|
2539
|
+
if self.rank() == 0:
|
|
2540
|
+
return True
|
|
2541
|
+
return self._local_symbols[0].is_even()
|
|
2542
|
+
|
|
2543
|
+
def signature_pair(self):
|
|
2544
|
+
r"""
|
|
2545
|
+
Return the signature pair `(p, n)` of the (non-degenerate)
|
|
2546
|
+
global genus symbol, where `p` is the number of positive
|
|
2547
|
+
eigenvalues and `n` is the number of negative eigenvalues.
|
|
2548
|
+
|
|
2549
|
+
OUTPUT: a pair of integers `(p, n)`, each `\geq 0`
|
|
2550
|
+
|
|
2551
|
+
EXAMPLES::
|
|
2552
|
+
|
|
2553
|
+
sage: A = matrix.diagonal(ZZ, [1, -2, 3, 4, 8, -11])
|
|
2554
|
+
sage: GS = Genus(A)
|
|
2555
|
+
sage: GS.signature_pair()
|
|
2556
|
+
(4, 2)
|
|
2557
|
+
"""
|
|
2558
|
+
return self._signature
|
|
2559
|
+
|
|
2560
|
+
signature_pair_of_matrix = signature_pair
|
|
2561
|
+
|
|
2562
|
+
def _proper_spinor_kernel(self):
|
|
2563
|
+
r"""
|
|
2564
|
+
Return the proper spinor kernel.
|
|
2565
|
+
|
|
2566
|
+
OUTPUT: a pair `(A, K)` where
|
|
2567
|
+
|
|
2568
|
+
.. MATH::
|
|
2569
|
+
|
|
2570
|
+
A = \prod_{p \mid 2d} ZZ_p^\times / ZZ_p^{\times2},
|
|
2571
|
+
|
|
2572
|
+
`d` is the determinant of this genus and `K` is a subgroup of `A`.
|
|
2573
|
+
|
|
2574
|
+
EXAMPLES::
|
|
2575
|
+
|
|
2576
|
+
sage: # needs sage.libs.gap
|
|
2577
|
+
sage: gram = matrix(ZZ, 4, [2,0,1,0, 0,2,1,0, 1,1,5,0, 0,0,0,16])
|
|
2578
|
+
sage: genus = Genus(gram)
|
|
2579
|
+
sage: genus._proper_spinor_kernel()
|
|
2580
|
+
(Group of SpinorOperators at primes (2,),
|
|
2581
|
+
Subgroup of Group of SpinorOperators at primes (2,) generated by (1, 1, f2))
|
|
2582
|
+
sage: gram = matrix(ZZ, 4, [3,0,1,-1, 0,3,-1,-1, 1,-1,6,0, -1,-1,0,6])
|
|
2583
|
+
sage: genus = Genus(gram)
|
|
2584
|
+
sage: genus._proper_spinor_kernel()
|
|
2585
|
+
(Group of SpinorOperators at primes (2,),
|
|
2586
|
+
Subgroup of Group of SpinorOperators at primes (2,) generated by (1, 1, f2))
|
|
2587
|
+
"""
|
|
2588
|
+
from sage.quadratic_forms.genera.spinor_genus import SpinorOperators
|
|
2589
|
+
syms = self.local_symbols()
|
|
2590
|
+
primes = tuple([sym.prime() for sym in syms])
|
|
2591
|
+
A = SpinorOperators(primes)
|
|
2592
|
+
kernel_gens = []
|
|
2593
|
+
# -1 adic contribution
|
|
2594
|
+
sig = self.signature_pair_of_matrix()
|
|
2595
|
+
if sig[0] * sig[1] > 1:
|
|
2596
|
+
kernel_gens.append(A.delta(-1, prime=-1))
|
|
2597
|
+
kernel_gens.extend(A.delta(r, prime=sym.prime())
|
|
2598
|
+
for sym in syms
|
|
2599
|
+
for r in sym.automorphous_numbers())
|
|
2600
|
+
return A, A.subgroup(kernel_gens)
|
|
2601
|
+
|
|
2602
|
+
def _improper_spinor_kernel(self):
|
|
2603
|
+
r"""
|
|
2604
|
+
Return the improper spinor kernel.
|
|
2605
|
+
|
|
2606
|
+
OUTPUT: a pair ``(A, K)`` where
|
|
2607
|
+
|
|
2608
|
+
.. MATH::
|
|
2609
|
+
|
|
2610
|
+
A = \prod_{p \mid 2d} ZZ_p^\times / ZZ_p^{\times2},
|
|
2611
|
+
|
|
2612
|
+
`d` is the determinant of this genus and `K` is a subgroup of `A`.
|
|
2613
|
+
|
|
2614
|
+
EXAMPLES::
|
|
2615
|
+
|
|
2616
|
+
sage: # needs sage.libs.gap
|
|
2617
|
+
sage: gram = matrix(ZZ, 4, [2,0,1,0, 0,2,1,0, 1,1,5,0, 0,0,0,16])
|
|
2618
|
+
sage: genus = Genus(gram)
|
|
2619
|
+
sage: genus._proper_spinor_kernel()
|
|
2620
|
+
(Group of SpinorOperators at primes (2,),
|
|
2621
|
+
Subgroup of Group of SpinorOperators at primes (2,) generated by (1, 1, f2))
|
|
2622
|
+
sage: gram = matrix(ZZ, 4, [3,0,1,-1, 0,3,-1,-1, 1,-1,6,0, -1,-1,0,6])
|
|
2623
|
+
sage: genus = Genus(gram)
|
|
2624
|
+
sage: A, K = genus._improper_spinor_kernel()
|
|
2625
|
+
sage: A
|
|
2626
|
+
Group of SpinorOperators at primes (2,)
|
|
2627
|
+
sage: K
|
|
2628
|
+
Subgroup of Group of SpinorOperators at primes (2,) generated by (1, 1, f2, ...)
|
|
2629
|
+
sage: sorted([K.generators()[3], K.generators()[2] * K.generators()[3]])
|
|
2630
|
+
[f1, f1*f2]
|
|
2631
|
+
"""
|
|
2632
|
+
A, K = self._proper_spinor_kernel()
|
|
2633
|
+
if A.order() == K.order():
|
|
2634
|
+
return A, K
|
|
2635
|
+
b, j = self._proper_is_improper()
|
|
2636
|
+
if b:
|
|
2637
|
+
return A, K
|
|
2638
|
+
else:
|
|
2639
|
+
K = A.subgroup(K.gens() + (j,))
|
|
2640
|
+
return A, K
|
|
2641
|
+
|
|
2642
|
+
def spinor_generators(self, proper) -> list:
|
|
2643
|
+
r"""
|
|
2644
|
+
Return the spinor generators.
|
|
2645
|
+
|
|
2646
|
+
INPUT:
|
|
2647
|
+
|
|
2648
|
+
- ``proper`` -- boolean
|
|
2649
|
+
|
|
2650
|
+
OUTPUT: list of primes not dividing the determinant
|
|
2651
|
+
|
|
2652
|
+
EXAMPLES::
|
|
2653
|
+
|
|
2654
|
+
sage: # needs sage.libs.gap
|
|
2655
|
+
sage: g = matrix(ZZ, 3, [2,1,0, 1,2,0, 0,0,18])
|
|
2656
|
+
sage: gen = Genus(g)
|
|
2657
|
+
sage: gen.spinor_generators(False)
|
|
2658
|
+
[5]
|
|
2659
|
+
"""
|
|
2660
|
+
from sage.sets.primes import Primes
|
|
2661
|
+
if proper:
|
|
2662
|
+
A, K = self._proper_spinor_kernel()
|
|
2663
|
+
else:
|
|
2664
|
+
A, K = self._improper_spinor_kernel()
|
|
2665
|
+
Q = A.quotient(K)
|
|
2666
|
+
q = Q.order()
|
|
2667
|
+
U = Q.subgroup([])
|
|
2668
|
+
|
|
2669
|
+
spinor_gens = []
|
|
2670
|
+
P = Primes()
|
|
2671
|
+
p = ZZ(2)
|
|
2672
|
+
while not U.order() == q:
|
|
2673
|
+
p = P.next(p)
|
|
2674
|
+
if p.divides(self.determinant()):
|
|
2675
|
+
continue
|
|
2676
|
+
g = Q(A.delta(p))
|
|
2677
|
+
if g.gap() in U.gap(): # containment in sage is broken
|
|
2678
|
+
continue
|
|
2679
|
+
else:
|
|
2680
|
+
spinor_gens.append(p)
|
|
2681
|
+
U = Q.subgroup((g,) + Q.gens())
|
|
2682
|
+
return spinor_gens
|
|
2683
|
+
|
|
2684
|
+
def _proper_is_improper(self):
|
|
2685
|
+
r"""
|
|
2686
|
+
Return if proper and improper spinor genus coincide.
|
|
2687
|
+
|
|
2688
|
+
EXAMPLES::
|
|
2689
|
+
|
|
2690
|
+
sage: # needs sage.libs.gap
|
|
2691
|
+
sage: gram = matrix(ZZ, 4, [2,0,1,0, 0,2,1,0, 1,1,5,0, 0,0,0,16])
|
|
2692
|
+
sage: genus = Genus(gram)
|
|
2693
|
+
sage: b, j = genus._proper_is_improper(); b
|
|
2694
|
+
True
|
|
2695
|
+
sage: j.exponents() in ((0, 0), (0, 1))
|
|
2696
|
+
True
|
|
2697
|
+
|
|
2698
|
+
This genus consists of only on (improper) class, hence spinor genus and
|
|
2699
|
+
improper spinor genus differ::
|
|
2700
|
+
|
|
2701
|
+
sage: # needs sage.libs.gap
|
|
2702
|
+
sage: gram = matrix(ZZ, 4, [3,0,1,-1, 0,3,-1,-1, 1,-1,6,0, -1,-1,0,6])
|
|
2703
|
+
sage: genus = Genus(gram)
|
|
2704
|
+
sage: b, j = genus._proper_is_improper(); b
|
|
2705
|
+
False
|
|
2706
|
+
sage: j.exponents() in ((1, 0), (1, 1))
|
|
2707
|
+
True
|
|
2708
|
+
"""
|
|
2709
|
+
G = self.representative()
|
|
2710
|
+
d = self.dimension()
|
|
2711
|
+
V = ZZ**d
|
|
2712
|
+
# TODO:
|
|
2713
|
+
# this is a potential bottleneck
|
|
2714
|
+
# find a more clever way
|
|
2715
|
+
# with just the condition q != 0
|
|
2716
|
+
# even better would be a
|
|
2717
|
+
# version which does not require a representative
|
|
2718
|
+
norm = self.norm()
|
|
2719
|
+
P = [s.prime() for s in self._local_symbols]
|
|
2720
|
+
while True:
|
|
2721
|
+
x = V.random_element()
|
|
2722
|
+
q = x * G * x
|
|
2723
|
+
if q != 0 and all(q.valuation(p) == norm.valuation(p) for p in P):
|
|
2724
|
+
break
|
|
2725
|
+
Q = [p for p in q.prime_factors() if (norm.valuation(p) + q.valuation(p)) % 2]
|
|
2726
|
+
r = ZZ.prod(Q)
|
|
2727
|
+
# M = \tau_x(L)
|
|
2728
|
+
# q = [L: L & M]
|
|
2729
|
+
A, K = self._proper_spinor_kernel()
|
|
2730
|
+
j = A.delta(r) # diagonal embedding of r
|
|
2731
|
+
return j in K, j
|
|
2732
|
+
|
|
2733
|
+
def signature(self):
|
|
2734
|
+
r"""
|
|
2735
|
+
Return the signature of this genus.
|
|
2736
|
+
|
|
2737
|
+
The signature is `p - n` where `p` is the number of positive eigenvalues
|
|
2738
|
+
and `n` the number of negative eigenvalues.
|
|
2739
|
+
|
|
2740
|
+
EXAMPLES::
|
|
2741
|
+
|
|
2742
|
+
sage: A = matrix.diagonal(ZZ, [1, -2, 3, 4, 8, -11])
|
|
2743
|
+
sage: GS = Genus(A)
|
|
2744
|
+
sage: GS.signature()
|
|
2745
|
+
2
|
|
2746
|
+
"""
|
|
2747
|
+
p, n = self.signature_pair()
|
|
2748
|
+
return p - n
|
|
2749
|
+
|
|
2750
|
+
def determinant(self):
|
|
2751
|
+
r"""
|
|
2752
|
+
Return the determinant of this genus.
|
|
2753
|
+
|
|
2754
|
+
The determinant is the Hessian determinant of the quadratic
|
|
2755
|
+
form whose Gram matrix is the Gram matrix giving rise to this
|
|
2756
|
+
global genus symbol.
|
|
2757
|
+
|
|
2758
|
+
OUTPUT: integer
|
|
2759
|
+
|
|
2760
|
+
EXAMPLES::
|
|
2761
|
+
|
|
2762
|
+
sage: A = matrix.diagonal(ZZ, [1, -2, 3, 4])
|
|
2763
|
+
sage: GS = Genus(A)
|
|
2764
|
+
sage: GS.determinant()
|
|
2765
|
+
-24
|
|
2766
|
+
"""
|
|
2767
|
+
_, n = self.signature_pair()
|
|
2768
|
+
return (-1)**n * ZZ.prod(G.determinant() for G in self._local_symbols)
|
|
2769
|
+
|
|
2770
|
+
det = determinant
|
|
2771
|
+
|
|
2772
|
+
def dimension(self):
|
|
2773
|
+
r"""
|
|
2774
|
+
Return the dimension of this genus.
|
|
2775
|
+
|
|
2776
|
+
EXAMPLES::
|
|
2777
|
+
|
|
2778
|
+
sage: A = Matrix(ZZ, 2, 2, [1, 1, 1, 2])
|
|
2779
|
+
sage: G = Genus(A)
|
|
2780
|
+
sage: G.dimension()
|
|
2781
|
+
2
|
|
2782
|
+
"""
|
|
2783
|
+
p, n = self.signature_pair()
|
|
2784
|
+
return p + n
|
|
2785
|
+
|
|
2786
|
+
dim = dimension
|
|
2787
|
+
rank = dimension
|
|
2788
|
+
|
|
2789
|
+
def direct_sum(self, other):
|
|
2790
|
+
r"""
|
|
2791
|
+
Return the genus of the direct sum of ``self`` and ``other``.
|
|
2792
|
+
|
|
2793
|
+
The direct sum is defined as the direct sum of representatives.
|
|
2794
|
+
|
|
2795
|
+
EXAMPLES::
|
|
2796
|
+
|
|
2797
|
+
sage: # needs sage.graphs
|
|
2798
|
+
sage: G = IntegralLattice("A4").twist(3).genus()
|
|
2799
|
+
sage: G.direct_sum(G)
|
|
2800
|
+
Genus of
|
|
2801
|
+
None
|
|
2802
|
+
Signature: (8, 0)
|
|
2803
|
+
Genus symbol at 2: 1^8
|
|
2804
|
+
Genus symbol at 3: 3^8
|
|
2805
|
+
Genus symbol at 5: 1^6 5^2
|
|
2806
|
+
"""
|
|
2807
|
+
p1, n1 = self.signature_pair()
|
|
2808
|
+
p2, n2 = other.signature_pair()
|
|
2809
|
+
signature_pair = (p1 + p2, n1 + n2)
|
|
2810
|
+
|
|
2811
|
+
primes = [s.prime() for s in self.local_symbols()]
|
|
2812
|
+
primes.extend(s.prime() for s in other.local_symbols()
|
|
2813
|
+
if s.prime() not in primes)
|
|
2814
|
+
primes.sort()
|
|
2815
|
+
local_symbols = []
|
|
2816
|
+
for p in primes:
|
|
2817
|
+
sym_p = self.local_symbol(p=p).direct_sum(other.local_symbol(p=p))
|
|
2818
|
+
local_symbols.append(sym_p)
|
|
2819
|
+
return GenusSymbol_global_ring(signature_pair, local_symbols)
|
|
2820
|
+
|
|
2821
|
+
def discriminant_form(self):
|
|
2822
|
+
r"""
|
|
2823
|
+
Return the discriminant form associated to this genus.
|
|
2824
|
+
|
|
2825
|
+
EXAMPLES::
|
|
2826
|
+
|
|
2827
|
+
sage: A = matrix.diagonal(ZZ, [2, -4, 6, 8])
|
|
2828
|
+
sage: GS = Genus(A)
|
|
2829
|
+
sage: GS.discriminant_form()
|
|
2830
|
+
Finite quadratic module over Integer Ring with invariants (2, 2, 4, 24)
|
|
2831
|
+
Gram matrix of the quadratic form with values in Q/2Z:
|
|
2832
|
+
[ 1/2 0 1/2 0]
|
|
2833
|
+
[ 0 3/2 0 0]
|
|
2834
|
+
[ 1/2 0 3/4 0]
|
|
2835
|
+
[ 0 0 0 25/24]
|
|
2836
|
+
sage: A = matrix.diagonal(ZZ, [1, -4, 6, 8])
|
|
2837
|
+
sage: GS = Genus(A)
|
|
2838
|
+
sage: GS.discriminant_form()
|
|
2839
|
+
Finite quadratic module over Integer Ring with invariants (2, 4, 24)
|
|
2840
|
+
Gram matrix of the quadratic form with values in Q/Z:
|
|
2841
|
+
[ 1/2 1/2 0]
|
|
2842
|
+
[ 1/2 3/4 0]
|
|
2843
|
+
[ 0 0 1/24]
|
|
2844
|
+
"""
|
|
2845
|
+
from sage.modules.torsion_quadratic_module import TorsionQuadraticForm
|
|
2846
|
+
qL = []
|
|
2847
|
+
for gs in self._local_symbols:
|
|
2848
|
+
p = gs._prime
|
|
2849
|
+
qL.extend(_gram_from_jordan_block(p, block, True)
|
|
2850
|
+
for block in gs.symbol_tuple_list())
|
|
2851
|
+
|
|
2852
|
+
q = matrix.block_diagonal(qL)
|
|
2853
|
+
return TorsionQuadraticForm(q)
|
|
2854
|
+
|
|
2855
|
+
def rational_representative(self):
|
|
2856
|
+
r"""
|
|
2857
|
+
Return a representative of the rational
|
|
2858
|
+
bilinear form defined by this genus.
|
|
2859
|
+
|
|
2860
|
+
OUTPUT: a diagonal_matrix
|
|
2861
|
+
|
|
2862
|
+
EXAMPLES::
|
|
2863
|
+
|
|
2864
|
+
sage: from sage.quadratic_forms.genera.genus import genera
|
|
2865
|
+
sage: G = genera((8,0), 1)[0]
|
|
2866
|
+
sage: G
|
|
2867
|
+
Genus of
|
|
2868
|
+
None
|
|
2869
|
+
Signature: (8, 0)
|
|
2870
|
+
Genus symbol at 2: 1^8
|
|
2871
|
+
sage: G.rational_representative()
|
|
2872
|
+
[1 0 0 0 0 0 0 0]
|
|
2873
|
+
[0 1 0 0 0 0 0 0]
|
|
2874
|
+
[0 0 1 0 0 0 0 0]
|
|
2875
|
+
[0 0 0 1 0 0 0 0]
|
|
2876
|
+
[0 0 0 0 1 0 0 0]
|
|
2877
|
+
[0 0 0 0 0 2 0 0]
|
|
2878
|
+
[0 0 0 0 0 0 1 0]
|
|
2879
|
+
[0 0 0 0 0 0 0 2]
|
|
2880
|
+
"""
|
|
2881
|
+
from sage.quadratic_forms.quadratic_form import QuadraticForm
|
|
2882
|
+
from sage.quadratic_forms.quadratic_form import quadratic_form_from_invariants
|
|
2883
|
+
sminus = self.signature_pair_of_matrix()[1]
|
|
2884
|
+
det = self.determinant()
|
|
2885
|
+
m = self.rank()
|
|
2886
|
+
P = []
|
|
2887
|
+
for sym in self._local_symbols:
|
|
2888
|
+
p = sym._prime
|
|
2889
|
+
# it is important to use the definition of Cassels here!
|
|
2890
|
+
if QuadraticForm(QQ, 2*sym.gram_matrix()).hasse_invariant(p) == -1:
|
|
2891
|
+
P.append(p)
|
|
2892
|
+
q = quadratic_form_from_invariants(F=QQ, rk=m, det=det,
|
|
2893
|
+
P=P, sminus=sminus)
|
|
2894
|
+
return q.Hessian_matrix()/2
|
|
2895
|
+
|
|
2896
|
+
def _compute_representative(self, LLL=True):
|
|
2897
|
+
r"""
|
|
2898
|
+
Compute a representative of this genus and cache it.
|
|
2899
|
+
|
|
2900
|
+
INPUT:
|
|
2901
|
+
|
|
2902
|
+
- ``LLL`` -- boolean (default: ``True``); whether or not to LLL reduce the result
|
|
2903
|
+
|
|
2904
|
+
TESTS::
|
|
2905
|
+
|
|
2906
|
+
sage: from sage.quadratic_forms.genera.genus import genera
|
|
2907
|
+
sage: for det in range(1, 5):
|
|
2908
|
+
....: G = genera((4,0), det, even=False)
|
|
2909
|
+
....: assert all(g==Genus(g.representative()) for g in G)
|
|
2910
|
+
sage: for det in range(1, 5):
|
|
2911
|
+
....: G = genera((1,2), det, even=False)
|
|
2912
|
+
....: assert all(g==Genus(g.representative()) for g in G)
|
|
2913
|
+
sage: for det in range(1, 9): # long time (8s, 2020)
|
|
2914
|
+
....: G = genera((2,2), det, even=False)
|
|
2915
|
+
....: assert all(g==Genus(g.representative()) for g in G)
|
|
2916
|
+
"""
|
|
2917
|
+
from sage.modules.free_quadratic_module_integer_symmetric import IntegralLattice, local_modification
|
|
2918
|
+
q = self.rational_representative()
|
|
2919
|
+
# the associated quadratic form xGx.T/2 should be integral
|
|
2920
|
+
L = IntegralLattice(4 * q).maximal_overlattice()
|
|
2921
|
+
p = 2
|
|
2922
|
+
sym2 = self.local_symbols()[0]
|
|
2923
|
+
if not self.is_even():
|
|
2924
|
+
# the quadratic form of xGx.T/2 must be integral
|
|
2925
|
+
# for things to work
|
|
2926
|
+
# solve this by multiplying the basis by 2
|
|
2927
|
+
L = local_modification(L, 4 * sym2.gram_matrix(), p)
|
|
2928
|
+
L = L.overlattice(L.basis_matrix() / 2)
|
|
2929
|
+
else:
|
|
2930
|
+
L = local_modification(L, sym2.gram_matrix(), p)
|
|
2931
|
+
for sym in self._local_symbols[1:]:
|
|
2932
|
+
p = sym.prime()
|
|
2933
|
+
L = local_modification(L, sym.gram_matrix(), p)
|
|
2934
|
+
L = L.gram_matrix().change_ring(ZZ)
|
|
2935
|
+
if LLL:
|
|
2936
|
+
from sage.libs.pari import pari
|
|
2937
|
+
|
|
2938
|
+
sig = self.signature_pair_of_matrix()
|
|
2939
|
+
if sig[0] * sig[1] != 0:
|
|
2940
|
+
from sage.env import SAGE_EXTCODE
|
|
2941
|
+
m = pari(L)
|
|
2942
|
+
pari.read(Path(SAGE_EXTCODE) / "pari" / "simon" / "qfsolve.gp")
|
|
2943
|
+
m = pari('qflllgram_indefgoon')(m)
|
|
2944
|
+
# convert the output string to sage
|
|
2945
|
+
L = m.sage()[0]
|
|
2946
|
+
elif sig[1] != 0:
|
|
2947
|
+
U = -(-L).LLL_gram()
|
|
2948
|
+
L = U.T * L * U
|
|
2949
|
+
else:
|
|
2950
|
+
U = L.LLL_gram()
|
|
2951
|
+
L = U.T * L * U
|
|
2952
|
+
# confirm the computation
|
|
2953
|
+
assert Genus(L) == self
|
|
2954
|
+
L.set_immutable()
|
|
2955
|
+
self._representative = L
|
|
2956
|
+
|
|
2957
|
+
def representative(self):
|
|
2958
|
+
r"""
|
|
2959
|
+
Return a representative in this genus.
|
|
2960
|
+
|
|
2961
|
+
EXAMPLES::
|
|
2962
|
+
|
|
2963
|
+
sage: from sage.quadratic_forms.genera.genus import genera
|
|
2964
|
+
sage: g = genera([1,3], 24)[0]
|
|
2965
|
+
sage: g
|
|
2966
|
+
Genus of
|
|
2967
|
+
None
|
|
2968
|
+
Signature: (1, 3)
|
|
2969
|
+
Genus symbol at 2: [1^-1 2^3]_0
|
|
2970
|
+
Genus symbol at 3: 1^3 3^1
|
|
2971
|
+
|
|
2972
|
+
A representative of ``g`` is not known yet.
|
|
2973
|
+
Let us trigger its computation::
|
|
2974
|
+
|
|
2975
|
+
sage: g.representative()
|
|
2976
|
+
[ 0 0 0 2]
|
|
2977
|
+
[ 0 -1 0 0]
|
|
2978
|
+
[ 0 0 -6 0]
|
|
2979
|
+
[ 2 0 0 0]
|
|
2980
|
+
sage: g == Genus(g.representative())
|
|
2981
|
+
True
|
|
2982
|
+
"""
|
|
2983
|
+
if self._representative is None:
|
|
2984
|
+
self._compute_representative()
|
|
2985
|
+
return self._representative
|
|
2986
|
+
|
|
2987
|
+
def representatives(self, backend=None, algorithm=None):
|
|
2988
|
+
r"""
|
|
2989
|
+
Return a list of representatives for the classes in this genus.
|
|
2990
|
+
|
|
2991
|
+
INPUT:
|
|
2992
|
+
|
|
2993
|
+
- ``backend`` -- (default: ``None``)
|
|
2994
|
+
- ``algorithm`` -- (default: ``None``)
|
|
2995
|
+
|
|
2996
|
+
OUTPUT: list of Gram matrices
|
|
2997
|
+
|
|
2998
|
+
EXAMPLES::
|
|
2999
|
+
|
|
3000
|
+
sage: # needs sage.libs.gap
|
|
3001
|
+
sage: from sage.quadratic_forms.genera.genus import genera
|
|
3002
|
+
sage: G = Genus(matrix.diagonal([1, 1, 7]))
|
|
3003
|
+
sage: G.representatives()
|
|
3004
|
+
(
|
|
3005
|
+
[1 0 0] [1 0 0]
|
|
3006
|
+
[0 2 1] [0 1 0]
|
|
3007
|
+
[0 1 4], [0 0 7]
|
|
3008
|
+
)
|
|
3009
|
+
|
|
3010
|
+
Indefinite genera work as well::
|
|
3011
|
+
|
|
3012
|
+
sage: # needs sage.libs.gap
|
|
3013
|
+
sage: G = Genus(matrix(ZZ, 3, [6,3,0, 3,6,0, 0,0,2]))
|
|
3014
|
+
sage: G.representatives()
|
|
3015
|
+
(
|
|
3016
|
+
[2 0 0] [ 2 1 0]
|
|
3017
|
+
[0 6 3] [ 1 2 0]
|
|
3018
|
+
[0 3 6], [ 0 0 18]
|
|
3019
|
+
)
|
|
3020
|
+
|
|
3021
|
+
For positive definite forms the magma backend is available::
|
|
3022
|
+
|
|
3023
|
+
sage: G = Genus(matrix.diagonal([1, 1, 7]))
|
|
3024
|
+
sage: G.representatives(backend='magma') # optional - magma
|
|
3025
|
+
(
|
|
3026
|
+
[1 0 0] [ 1 0 0]
|
|
3027
|
+
[0 1 0] [ 0 2 -1]
|
|
3028
|
+
[0 0 7], [ 0 -1 4]
|
|
3029
|
+
)
|
|
3030
|
+
"""
|
|
3031
|
+
try:
|
|
3032
|
+
return self._representatives
|
|
3033
|
+
except AttributeError:
|
|
3034
|
+
pass
|
|
3035
|
+
n = self.dimension()
|
|
3036
|
+
representatives = []
|
|
3037
|
+
if n == 0:
|
|
3038
|
+
return (self.representative(), )
|
|
3039
|
+
if backend is None:
|
|
3040
|
+
if n > 6 and prod(self.signature_pair_of_matrix()) == 0:
|
|
3041
|
+
backend = 'magma'
|
|
3042
|
+
else:
|
|
3043
|
+
backend = 'sage'
|
|
3044
|
+
if backend == 'magma':
|
|
3045
|
+
if prod(self.signature_pair_of_matrix()) != 0:
|
|
3046
|
+
if n <= 2:
|
|
3047
|
+
raise NotImplementedError()
|
|
3048
|
+
K = magma.RationalsAsNumberField()
|
|
3049
|
+
gram = magma.Matrix(K, n, self.representative().list())
|
|
3050
|
+
L = gram.NumberFieldLatticeWithGram()
|
|
3051
|
+
representatives = L.GenusRepresentatives()
|
|
3052
|
+
representatives = [r.GramMatrix().ChangeRing(magma.Rationals()).sage() for r in representatives]
|
|
3053
|
+
else:
|
|
3054
|
+
e = 1
|
|
3055
|
+
if self.signature_pair_of_matrix()[1] != 0:
|
|
3056
|
+
e = -1
|
|
3057
|
+
K = magma.Rationals()
|
|
3058
|
+
gram = magma.Matrix(K, n, (e*self.representative()).list())
|
|
3059
|
+
L = gram.LatticeWithGram()
|
|
3060
|
+
representatives = L.GenusRepresentatives()
|
|
3061
|
+
representatives = [e*r.GramMatrix().sage() for r in representatives]
|
|
3062
|
+
elif backend == "sage":
|
|
3063
|
+
if n == 1:
|
|
3064
|
+
return [self.representative()]
|
|
3065
|
+
if n == 2:
|
|
3066
|
+
# Binary forms are considered positive definite take care of that.
|
|
3067
|
+
e = ZZ.one()
|
|
3068
|
+
if self.signature_pair()[0] == 0:
|
|
3069
|
+
e = ZZ(-1)
|
|
3070
|
+
d = - 4 * self.determinant()
|
|
3071
|
+
from sage.quadratic_forms.binary_qf import BinaryQF_reduced_representatives
|
|
3072
|
+
for q in BinaryQF_reduced_representatives(d, proper=False):
|
|
3073
|
+
if q[1] % 2 == 0: # we want integrality of the gram matrix
|
|
3074
|
+
m = e*matrix(ZZ, 2, [q[0], q[1] // 2, q[1] // 2, q[2]])
|
|
3075
|
+
if Genus(m) == self:
|
|
3076
|
+
representatives.append(m)
|
|
3077
|
+
if n > 2:
|
|
3078
|
+
from sage.quadratic_forms.quadratic_form import QuadraticForm
|
|
3079
|
+
from sage.quadratic_forms.quadratic_form__neighbors import neighbor_iteration
|
|
3080
|
+
e = ZZ.one()
|
|
3081
|
+
if not self.is_even():
|
|
3082
|
+
e = ZZ(2)
|
|
3083
|
+
if self.signature_pair()[0] == 0:
|
|
3084
|
+
e *= ZZ(-1)
|
|
3085
|
+
Q = QuadraticForm(ZZ, e * self.representative())
|
|
3086
|
+
seeds = [Q]
|
|
3087
|
+
for p in self.spinor_generators(proper=False):
|
|
3088
|
+
v = Q.find_primitive_p_divisible_vector__next(p)
|
|
3089
|
+
seeds.append(Q.find_p_neighbor_from_vec(p, v))
|
|
3090
|
+
if ZZ.prod(self.signature_pair()) != 0:
|
|
3091
|
+
# indefinite genus and improper spinor genus agree
|
|
3092
|
+
representatives = seeds
|
|
3093
|
+
else:
|
|
3094
|
+
# we do a neighbor iteration
|
|
3095
|
+
from sage.sets.primes import Primes
|
|
3096
|
+
P = Primes()
|
|
3097
|
+
# we need a prime with L_p isotropic
|
|
3098
|
+
# this is certainly the case if the lattice is even
|
|
3099
|
+
# and p does not divide the determinant
|
|
3100
|
+
if self.is_even():
|
|
3101
|
+
p = ZZ(2)
|
|
3102
|
+
else:
|
|
3103
|
+
p = ZZ(3)
|
|
3104
|
+
det = self.determinant()
|
|
3105
|
+
while p.divides(det):
|
|
3106
|
+
p = P.next(p)
|
|
3107
|
+
representatives = neighbor_iteration(seeds, p, mass=Q.conway_mass(), algorithm=algorithm)
|
|
3108
|
+
representatives = [g.Hessian_matrix() for g in representatives]
|
|
3109
|
+
representatives = [(g/e).change_ring(ZZ) for g in representatives]
|
|
3110
|
+
else:
|
|
3111
|
+
raise ValueError("unknown algorithm")
|
|
3112
|
+
for g in representatives:
|
|
3113
|
+
g.set_immutable()
|
|
3114
|
+
self._representatives = tuple(representatives)
|
|
3115
|
+
assert len(representatives) > 0, self
|
|
3116
|
+
return self._representatives
|
|
3117
|
+
|
|
3118
|
+
def local_symbols(self):
|
|
3119
|
+
r"""
|
|
3120
|
+
Return a copy of the list of local symbols of this symbol.
|
|
3121
|
+
|
|
3122
|
+
EXAMPLES::
|
|
3123
|
+
|
|
3124
|
+
sage: A = matrix.diagonal(ZZ, [2, -4, 6, 8])
|
|
3125
|
+
sage: GS = Genus(A)
|
|
3126
|
+
sage: GS.local_symbols()
|
|
3127
|
+
[Genus symbol at 2: [2^-2 4^1 8^1]_4,
|
|
3128
|
+
Genus symbol at 3: 1^-3 3^-1]
|
|
3129
|
+
"""
|
|
3130
|
+
return deepcopy(self._local_symbols)
|
|
3131
|
+
|
|
3132
|
+
def local_symbol(self, p):
|
|
3133
|
+
r"""
|
|
3134
|
+
Return a copy of the local symbol at the prime `p`.
|
|
3135
|
+
|
|
3136
|
+
EXAMPLES::
|
|
3137
|
+
|
|
3138
|
+
sage: A = matrix.diagonal(ZZ, [2, -4, 6, 8])
|
|
3139
|
+
sage: GS = Genus(A)
|
|
3140
|
+
sage: GS.local_symbol(3)
|
|
3141
|
+
Genus symbol at 3: 1^-3 3^-1
|
|
3142
|
+
"""
|
|
3143
|
+
p = ZZ(p)
|
|
3144
|
+
for sym in self._local_symbols:
|
|
3145
|
+
if p == sym.prime():
|
|
3146
|
+
return deepcopy(sym)
|
|
3147
|
+
assert p != 2
|
|
3148
|
+
sym_p = [[0, self.rank(), self.det().kronecker(p)]]
|
|
3149
|
+
return Genus_Symbol_p_adic_ring(p, sym_p)
|
|
3150
|
+
|
|
3151
|
+
def _standard_mass(self):
|
|
3152
|
+
r"""
|
|
3153
|
+
Return the standard mass of this genus.
|
|
3154
|
+
|
|
3155
|
+
It depends only on the dimension and determinant.
|
|
3156
|
+
|
|
3157
|
+
EXAMPLES::
|
|
3158
|
+
|
|
3159
|
+
sage: A = matrix.diagonal(ZZ, [1, 1, 1, 1])
|
|
3160
|
+
sage: GS = Genus(A)
|
|
3161
|
+
sage: GS._standard_mass() # needs sage.symbolic
|
|
3162
|
+
1/48
|
|
3163
|
+
"""
|
|
3164
|
+
from sage.symbolic.constants import pi
|
|
3165
|
+
from sage.symbolic.ring import SR
|
|
3166
|
+
from sage.functions.transcendental import zeta
|
|
3167
|
+
from sage.functions.gamma import gamma
|
|
3168
|
+
n = self.dimension()
|
|
3169
|
+
if n % 2 == 0:
|
|
3170
|
+
s = n // 2
|
|
3171
|
+
else:
|
|
3172
|
+
s = (n // 2) + 1
|
|
3173
|
+
std = QQ(2) * pi**(-n * (n + 1) / QQ(4))
|
|
3174
|
+
std *= SR.prod(gamma(QQ(j) / QQ(2)) for j in range(1, n+1))
|
|
3175
|
+
std *= SR.prod(zeta(ZZ(2) * ZZ(k)) for k in range(1, s))
|
|
3176
|
+
if n % 2 == 0:
|
|
3177
|
+
D = ZZ(-1)**(s) * self.determinant()
|
|
3178
|
+
std *= quadratic_L_function__exact(ZZ(s), D)
|
|
3179
|
+
d = fundamental_discriminant(D)
|
|
3180
|
+
# since quadratic_L_function__exact is different
|
|
3181
|
+
# from \zeta_D as defined by Conway and Sloane
|
|
3182
|
+
# we have to compensate
|
|
3183
|
+
# the missing Euler factors
|
|
3184
|
+
for sym in self.local_symbols():
|
|
3185
|
+
p = sym.prime()
|
|
3186
|
+
std *= (1 - d.kronecker(p)*p**(-s))
|
|
3187
|
+
return std
|
|
3188
|
+
|
|
3189
|
+
@cached_method
|
|
3190
|
+
def mass(self, backend='sage'):
|
|
3191
|
+
r"""
|
|
3192
|
+
Return the mass of this genus.
|
|
3193
|
+
|
|
3194
|
+
The genus must be definite.
|
|
3195
|
+
Let `L_1, ... L_n` be a complete list of representatives
|
|
3196
|
+
of the isometry classes in this genus.
|
|
3197
|
+
Its mass is defined as
|
|
3198
|
+
|
|
3199
|
+
.. MATH::
|
|
3200
|
+
|
|
3201
|
+
\sum_{i=1}^n \frac{1}{|O(L_i)|}.
|
|
3202
|
+
|
|
3203
|
+
INPUT:
|
|
3204
|
+
|
|
3205
|
+
- ``backend`` -- ``'sage'`` (default) or ``'magma'``
|
|
3206
|
+
|
|
3207
|
+
OUTPUT: a rational number
|
|
3208
|
+
|
|
3209
|
+
EXAMPLES::
|
|
3210
|
+
|
|
3211
|
+
sage: from sage.quadratic_forms.genera.genus import genera
|
|
3212
|
+
sage: G = genera((8,0), 1, even=True)[0]
|
|
3213
|
+
sage: G.mass() # needs sage.symbolic
|
|
3214
|
+
1/696729600
|
|
3215
|
+
sage: G.mass(backend='magma') # optional - magma
|
|
3216
|
+
1/696729600
|
|
3217
|
+
|
|
3218
|
+
The `E_8` lattice is unique in its genus::
|
|
3219
|
+
|
|
3220
|
+
sage: E8 = QuadraticForm(G.representative())
|
|
3221
|
+
sage: E8.number_of_automorphisms()
|
|
3222
|
+
696729600
|
|
3223
|
+
|
|
3224
|
+
TESTS:
|
|
3225
|
+
|
|
3226
|
+
Check a random genus with magma::
|
|
3227
|
+
|
|
3228
|
+
sage: d = ZZ.random_element(1, 1000)
|
|
3229
|
+
sage: n = ZZ.random_element(2, 10)
|
|
3230
|
+
sage: L = genera((n,0), d, d, even=False)
|
|
3231
|
+
sage: k = ZZ.random_element(0, len(L))
|
|
3232
|
+
sage: G = L[k]
|
|
3233
|
+
sage: G.mass()==G.mass(backend='magma') # optional - magma
|
|
3234
|
+
True
|
|
3235
|
+
|
|
3236
|
+
Error messages::
|
|
3237
|
+
|
|
3238
|
+
sage: G.mass(backend='foo')
|
|
3239
|
+
Traceback (most recent call last):
|
|
3240
|
+
...
|
|
3241
|
+
ValueError: unknown backend: foo
|
|
3242
|
+
sage: G = Genus(matrix(ZZ, 2, [0, 1, 1, 0]))
|
|
3243
|
+
sage: G.mass()
|
|
3244
|
+
Traceback (most recent call last):
|
|
3245
|
+
...
|
|
3246
|
+
ValueError: the genus must be definite.
|
|
3247
|
+
"""
|
|
3248
|
+
pos, neg = self.signature_pair()
|
|
3249
|
+
if pos * neg != 0:
|
|
3250
|
+
raise ValueError("the genus must be definite.")
|
|
3251
|
+
if pos + neg == 1:
|
|
3252
|
+
return QQ((1, 2))
|
|
3253
|
+
if backend == 'sage':
|
|
3254
|
+
mass = self._standard_mass()
|
|
3255
|
+
for sym in self._local_symbols:
|
|
3256
|
+
mass *= sym.mass() / sym._standard_mass()
|
|
3257
|
+
return QQ(mass.canonicalize_radical())
|
|
3258
|
+
elif backend == 'magma':
|
|
3259
|
+
e = 1 # lattices in magma are positive definite
|
|
3260
|
+
if neg != 0:
|
|
3261
|
+
e = -1
|
|
3262
|
+
# for some reason LatticeWithGram wants a dense matrix
|
|
3263
|
+
L = magma(e * self.representative().dense_matrix())
|
|
3264
|
+
L = L.LatticeWithGram()
|
|
3265
|
+
return QQ(L.Mass())
|
|
3266
|
+
else:
|
|
3267
|
+
raise ValueError("unknown backend: %s" % backend)
|
|
3268
|
+
|
|
3269
|
+
def level(self):
|
|
3270
|
+
r"""
|
|
3271
|
+
Return the level of this genus.
|
|
3272
|
+
|
|
3273
|
+
This is the denominator of the inverse Gram matrix
|
|
3274
|
+
of a representative.
|
|
3275
|
+
|
|
3276
|
+
EXAMPLES::
|
|
3277
|
+
|
|
3278
|
+
sage: G = Genus(matrix.diagonal([2, 4, 18]))
|
|
3279
|
+
sage: G.level()
|
|
3280
|
+
36
|
|
3281
|
+
"""
|
|
3282
|
+
return prod(sym.level() for sym in self.local_symbols())
|
|
3283
|
+
|
|
3284
|
+
def scale(self):
|
|
3285
|
+
r"""
|
|
3286
|
+
Return the scale of this genus.
|
|
3287
|
+
|
|
3288
|
+
Let `L` be a lattice with bilinear form `b`.
|
|
3289
|
+
The scale of `(L,b)` is defined as the ideal
|
|
3290
|
+
`b(L,L)`.
|
|
3291
|
+
|
|
3292
|
+
OUTPUT: integer
|
|
3293
|
+
|
|
3294
|
+
EXAMPLES::
|
|
3295
|
+
|
|
3296
|
+
sage: G = Genus(matrix.diagonal([2, 4, 18]))
|
|
3297
|
+
sage: G.scale()
|
|
3298
|
+
2
|
|
3299
|
+
"""
|
|
3300
|
+
return prod([s.scale() for s in self.local_symbols()])
|
|
3301
|
+
|
|
3302
|
+
def norm(self):
|
|
3303
|
+
r"""
|
|
3304
|
+
Return the norm of this genus.
|
|
3305
|
+
|
|
3306
|
+
Let `L` be a lattice with bilinear form `b`.
|
|
3307
|
+
The scale of `(L,b)` is defined as the ideal
|
|
3308
|
+
generated by `\{b(x,x) | x \in L\}`.
|
|
3309
|
+
|
|
3310
|
+
EXAMPLES::
|
|
3311
|
+
|
|
3312
|
+
sage: G = Genus(matrix.diagonal([6, 4, 18]))
|
|
3313
|
+
sage: G.norm()
|
|
3314
|
+
2
|
|
3315
|
+
sage: G = Genus(matrix(ZZ, 2, [0, 1, 1, 0]))
|
|
3316
|
+
sage: G.norm()
|
|
3317
|
+
2
|
|
3318
|
+
"""
|
|
3319
|
+
return prod([s.norm() for s in self.local_symbols()])
|
|
3320
|
+
|
|
3321
|
+
|
|
3322
|
+
def _gram_from_jordan_block(p, block, discr_form=False):
|
|
3323
|
+
r"""
|
|
3324
|
+
Return the Gram matrix of this jordan block.
|
|
3325
|
+
|
|
3326
|
+
This is a helper for :meth:`discriminant_form` and :meth:`gram_matrix`.
|
|
3327
|
+
No input checks.
|
|
3328
|
+
|
|
3329
|
+
INPUT:
|
|
3330
|
+
|
|
3331
|
+
- ``p`` -- a prime number
|
|
3332
|
+
|
|
3333
|
+
- ``block`` -- list of 3 integers or 5 integers if `p` is `2`
|
|
3334
|
+
|
|
3335
|
+
- ``discr_form`` -- boolean (default: ``False``); if ``True`` invert the scales
|
|
3336
|
+
to obtain a Gram matrix for the discriminant form instead
|
|
3337
|
+
|
|
3338
|
+
EXAMPLES::
|
|
3339
|
+
|
|
3340
|
+
sage: from sage.quadratic_forms.genera.genus import _gram_from_jordan_block
|
|
3341
|
+
sage: block = [1, 3, 1]
|
|
3342
|
+
sage: _gram_from_jordan_block(5, block)
|
|
3343
|
+
[5 0 0]
|
|
3344
|
+
[0 5 0]
|
|
3345
|
+
[0 0 5]
|
|
3346
|
+
sage: block = [1, 4, 7, 1, 2]
|
|
3347
|
+
sage: _gram_from_jordan_block(2, block)
|
|
3348
|
+
[0 2 0 0]
|
|
3349
|
+
[2 0 0 0]
|
|
3350
|
+
[0 0 2 0]
|
|
3351
|
+
[0 0 0 2]
|
|
3352
|
+
|
|
3353
|
+
For the discriminant form we obtain::
|
|
3354
|
+
|
|
3355
|
+
sage: block = [1, 3, 1]
|
|
3356
|
+
sage: _gram_from_jordan_block(5, block, True)
|
|
3357
|
+
[4/5 0 0]
|
|
3358
|
+
[ 0 2/5 0]
|
|
3359
|
+
[ 0 0 2/5]
|
|
3360
|
+
sage: block = [1, 4, 7, 1, 2]
|
|
3361
|
+
sage: _gram_from_jordan_block(2, block, True)
|
|
3362
|
+
[ 0 1/2 0 0]
|
|
3363
|
+
[1/2 0 0 0]
|
|
3364
|
+
[ 0 0 1/2 0]
|
|
3365
|
+
[ 0 0 0 1/2]
|
|
3366
|
+
"""
|
|
3367
|
+
level = block[0]
|
|
3368
|
+
rk = block[1]
|
|
3369
|
+
det = block[2]
|
|
3370
|
+
if p == 2:
|
|
3371
|
+
o = ZZ(block[3])
|
|
3372
|
+
t = ZZ(block[4])
|
|
3373
|
+
U = matrix(QQ, 2, [0, 1, 1, 0])
|
|
3374
|
+
V = matrix(QQ, 2, [2, 1, 1, 2])
|
|
3375
|
+
W = matrix(QQ, 1, [1])
|
|
3376
|
+
if o == 0:
|
|
3377
|
+
if det in [1, 7]:
|
|
3378
|
+
qL = (rk // 2) * [U]
|
|
3379
|
+
else:
|
|
3380
|
+
qL = (rk // 2 - 1) * [U] + [V]
|
|
3381
|
+
if o == 1:
|
|
3382
|
+
if rk % 2 == 1:
|
|
3383
|
+
qL = max(0, (rk - 3) // 2) * [U]
|
|
3384
|
+
if t * det % 8 in [3, 5]:
|
|
3385
|
+
qL += [V]
|
|
3386
|
+
elif rk >= 3:
|
|
3387
|
+
qL += [U]
|
|
3388
|
+
qL += [t * W]
|
|
3389
|
+
else:
|
|
3390
|
+
if det in [3, 5]:
|
|
3391
|
+
det = -1
|
|
3392
|
+
else:
|
|
3393
|
+
det = 1
|
|
3394
|
+
qL = max(0, (rk - 4) // 2) * [U]
|
|
3395
|
+
if (det, t) == (1, 0):
|
|
3396
|
+
qL += [U, 1 * W, 7 * W]
|
|
3397
|
+
if (det, t) == (1, 2):
|
|
3398
|
+
qL += [U, 1 * W, 1 * W]
|
|
3399
|
+
if (det, t) == (1, 4):
|
|
3400
|
+
qL += [V, 1 * W, 3 * W]
|
|
3401
|
+
if (det, t) == (1, 6):
|
|
3402
|
+
qL += [U, 7 * W, 7 * W]
|
|
3403
|
+
if (det, t) == (-1, 0):
|
|
3404
|
+
qL += [V, 1 * W, 7 * W]
|
|
3405
|
+
if (det, t) == (-1, 2):
|
|
3406
|
+
qL += [U, 3 * W, 7 * W]
|
|
3407
|
+
if (det, t) == (-1, 4):
|
|
3408
|
+
qL += [U, 1 * W, 3 * W]
|
|
3409
|
+
if (det, t) == (-1, 6):
|
|
3410
|
+
qL += [U, 1 * W, 5 * W]
|
|
3411
|
+
# if the rank is 2 there is a U too much
|
|
3412
|
+
if rk == 2:
|
|
3413
|
+
qL = qL[-2:]
|
|
3414
|
+
q = matrix.block_diagonal(qL)
|
|
3415
|
+
if discr_form:
|
|
3416
|
+
q = q / 2**level
|
|
3417
|
+
else:
|
|
3418
|
+
q = q * 2**level
|
|
3419
|
+
if p != 2 and discr_form:
|
|
3420
|
+
q = matrix.identity(QQ, rk)
|
|
3421
|
+
d = 2**(rk % 2)
|
|
3422
|
+
if Integer(d).kronecker(p) != det:
|
|
3423
|
+
u = ZZ(_min_nonsquare(p))
|
|
3424
|
+
q[0, 0] = u
|
|
3425
|
+
q = q * (2 / p**level)
|
|
3426
|
+
if p != 2 and not discr_form:
|
|
3427
|
+
q = matrix.identity(QQ, rk)
|
|
3428
|
+
if det != 1:
|
|
3429
|
+
u = ZZ(_min_nonsquare(p))
|
|
3430
|
+
q[0, 0] = u
|
|
3431
|
+
q = q * p**level
|
|
3432
|
+
return q
|
|
3433
|
+
|
|
3434
|
+
|
|
3435
|
+
# Helper functions for mass computations
|
|
3436
|
+
|
|
3437
|
+
def M_p(species, p):
|
|
3438
|
+
r"""
|
|
3439
|
+
Return the diagonal factor `M_p` as a function of the species.
|
|
3440
|
+
|
|
3441
|
+
EXAMPLES:
|
|
3442
|
+
|
|
3443
|
+
These examples are taken from Table 2 of [CS1988]_::
|
|
3444
|
+
|
|
3445
|
+
sage: from sage.quadratic_forms.genera.genus import M_p
|
|
3446
|
+
sage: M_p(0, 2)
|
|
3447
|
+
1
|
|
3448
|
+
sage: M_p(1, 2)
|
|
3449
|
+
1/2
|
|
3450
|
+
sage: M_p(-2, 2)
|
|
3451
|
+
1/3
|
|
3452
|
+
sage: M_p(2, 2)
|
|
3453
|
+
1
|
|
3454
|
+
sage: M_p(3, 2)
|
|
3455
|
+
2/3
|
|
3456
|
+
sage: M_p(-4, 2)
|
|
3457
|
+
8/15
|
|
3458
|
+
sage: M_p(4, 2)
|
|
3459
|
+
8/9
|
|
3460
|
+
sage: M_p(5, 2)
|
|
3461
|
+
32/45
|
|
3462
|
+
|
|
3463
|
+
TESTS:
|
|
3464
|
+
|
|
3465
|
+
More values of the table for testing::
|
|
3466
|
+
|
|
3467
|
+
sage: M_p(0, 3)
|
|
3468
|
+
1
|
|
3469
|
+
sage: M_p(1, 3)
|
|
3470
|
+
1/2
|
|
3471
|
+
sage: M_p(-2, 3)
|
|
3472
|
+
3/8
|
|
3473
|
+
sage: M_p(2, 3)
|
|
3474
|
+
3/4
|
|
3475
|
+
sage: M_p(3, 3)
|
|
3476
|
+
9/16
|
|
3477
|
+
sage: M_p(-4, 3)
|
|
3478
|
+
81/160
|
|
3479
|
+
sage: M_p(4, 3)
|
|
3480
|
+
81/128
|
|
3481
|
+
sage: M_p(5, 3)
|
|
3482
|
+
729/1280
|
|
3483
|
+
|
|
3484
|
+
sage: M_p(0, 5)
|
|
3485
|
+
1
|
|
3486
|
+
sage: M_p(1, 5)
|
|
3487
|
+
1/2
|
|
3488
|
+
sage: M_p(-2, 5)
|
|
3489
|
+
5/12
|
|
3490
|
+
sage: M_p(2, 5)
|
|
3491
|
+
5/8
|
|
3492
|
+
sage: M_p(3, 5)
|
|
3493
|
+
25/48
|
|
3494
|
+
sage: M_p(-4, 5)
|
|
3495
|
+
625/1248
|
|
3496
|
+
sage: M_p(4, 5)
|
|
3497
|
+
625/1152
|
|
3498
|
+
"""
|
|
3499
|
+
if species == 0:
|
|
3500
|
+
return QQ.one()
|
|
3501
|
+
n = species.abs()
|
|
3502
|
+
s = (n + 1) // ZZ(2)
|
|
3503
|
+
mp = ZZ(2) * ZZ.prod(ZZ.one() - p**(-2 * k) for k in range(1, s))
|
|
3504
|
+
if n % 2 == 0:
|
|
3505
|
+
mp *= ZZ.one() - species.sign() * p**(-s)
|
|
3506
|
+
return QQ.one() / mp
|