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.

Files changed (331) hide show
  1. PARIKernel/__init__.py +2 -0
  2. PARIKernel/__main__.py +5 -0
  3. PARIKernel/io.cpython-314-x86_64-linux-musl.so +0 -0
  4. PARIKernel/io.pxd +7 -0
  5. PARIKernel/io.pyx +84 -0
  6. PARIKernel/kernel.cpython-314-x86_64-linux-musl.so +0 -0
  7. PARIKernel/kernel.pyx +260 -0
  8. PARIKernel/paridecl.pxd +95 -0
  9. PARIKernel/svg.cpython-314-x86_64-linux-musl.so +0 -0
  10. PARIKernel/svg.pyx +52 -0
  11. cypari2/__init__.py +8 -0
  12. cypari2/auto_paridecl.pxd +1070 -0
  13. cypari2/closure.cpython-314-x86_64-linux-musl.so +0 -0
  14. cypari2/closure.pxd +5 -0
  15. cypari2/closure.pyx +246 -0
  16. cypari2/convert.cpython-314-x86_64-linux-musl.so +0 -0
  17. cypari2/convert.pxd +80 -0
  18. cypari2/convert.pyx +613 -0
  19. cypari2/custom_block.cpython-314-x86_64-linux-musl.so +0 -0
  20. cypari2/custom_block.pyx +30 -0
  21. cypari2/cypari.h +13 -0
  22. cypari2/gen.cpython-314-x86_64-linux-musl.so +0 -0
  23. cypari2/gen.pxd +69 -0
  24. cypari2/gen.pyx +4819 -0
  25. cypari2/handle_error.cpython-314-x86_64-linux-musl.so +0 -0
  26. cypari2/handle_error.pxd +7 -0
  27. cypari2/handle_error.pyx +232 -0
  28. cypari2/pari_instance.cpython-314-x86_64-linux-musl.so +0 -0
  29. cypari2/pari_instance.pxd +27 -0
  30. cypari2/pari_instance.pyx +1438 -0
  31. cypari2/paridecl.pxd +5353 -0
  32. cypari2/paripriv.pxd +34 -0
  33. cypari2/pycore_long.h +98 -0
  34. cypari2/pycore_long.pxd +9 -0
  35. cypari2/stack.cpython-314-x86_64-linux-musl.so +0 -0
  36. cypari2/stack.pxd +27 -0
  37. cypari2/stack.pyx +278 -0
  38. cypari2/string_utils.cpython-314-x86_64-linux-musl.so +0 -0
  39. cypari2/string_utils.pxd +29 -0
  40. cypari2/string_utils.pyx +65 -0
  41. cypari2/types.pxd +147 -0
  42. passagemath_pari-10.6.32.data/data/etc/jupyter/nbconfig/notebook.d/gp-mode.json +5 -0
  43. passagemath_pari-10.6.32.data/data/share/jupyter/kernels/pari_jupyter/kernel.js +28 -0
  44. passagemath_pari-10.6.32.data/data/share/jupyter/kernels/pari_jupyter/kernel.json +6 -0
  45. passagemath_pari-10.6.32.data/data/share/jupyter/kernels/pari_jupyter/logo-64x64.png +0 -0
  46. passagemath_pari-10.6.32.data/data/share/jupyter/kernels/xeus-gp/kernel.json +13 -0
  47. passagemath_pari-10.6.32.data/data/share/jupyter/kernels/xeus-gp/logo-32x32.png +0 -0
  48. passagemath_pari-10.6.32.data/data/share/jupyter/kernels/xeus-gp/logo-64x64.png +0 -0
  49. passagemath_pari-10.6.32.data/data/share/jupyter/kernels/xeus-gp/logo-svg.svg +75 -0
  50. passagemath_pari-10.6.32.data/data/share/jupyter/nbextensions/gp-mode/gp.js +284 -0
  51. passagemath_pari-10.6.32.data/data/share/jupyter/nbextensions/gp-mode/main.js +15 -0
  52. passagemath_pari-10.6.32.dist-info/METADATA +209 -0
  53. passagemath_pari-10.6.32.dist-info/RECORD +331 -0
  54. passagemath_pari-10.6.32.dist-info/WHEEL +5 -0
  55. passagemath_pari-10.6.32.dist-info/top_level.txt +4 -0
  56. passagemath_pari.libs/libcrypto-f04afe95.so.3 +0 -0
  57. passagemath_pari.libs/libflint-fd6f12fc.so.21.0.0 +0 -0
  58. passagemath_pari.libs/libgcc_s-0cd532bd.so.1 +0 -0
  59. passagemath_pari.libs/libgf2x-9e30c3e3.so.3.0.0 +0 -0
  60. passagemath_pari.libs/libgfortran-2c33b284.so.5.0.0 +0 -0
  61. passagemath_pari.libs/libgivaro-9a94c711.so.9.2.1 +0 -0
  62. passagemath_pari.libs/libgmp-0e7fc84e.so.10.5.0 +0 -0
  63. passagemath_pari.libs/libgmpxx-9e08595c.so.4.7.0 +0 -0
  64. passagemath_pari.libs/libgsl-42cda06f.so.28.0.0 +0 -0
  65. passagemath_pari.libs/libmpfr-aaecbfc0.so.6.2.1 +0 -0
  66. passagemath_pari.libs/libncursesw-9c9e32c3.so.6.5 +0 -0
  67. passagemath_pari.libs/libntl-26885ca2.so.44.0.1 +0 -0
  68. passagemath_pari.libs/libopenblasp-r0-905cb27d.3.29.so +0 -0
  69. passagemath_pari.libs/libpari-gmp-tls-f31f908f.so.2.17.2 +0 -0
  70. passagemath_pari.libs/libquadmath-bb76a5fc.so.0.0.0 +0 -0
  71. passagemath_pari.libs/libreadline-06542304.so.8.2 +0 -0
  72. passagemath_pari.libs/libstdc++-5d72f927.so.6.0.33 +0 -0
  73. passagemath_pari.libs/libuuid-f3770415.so.1.3.0 +0 -0
  74. passagemath_pari.libs/libxeus-735780ff.so.13.1.0 +0 -0
  75. passagemath_pari.libs/libxeus-zmq-c68577b4.so.6.0.1 +0 -0
  76. passagemath_pari.libs/libzmq-1ba9a3da.so.5.2.5 +0 -0
  77. sage/all__sagemath_pari.py +26 -0
  78. sage/databases/all__sagemath_pari.py +7 -0
  79. sage/databases/conway.py +274 -0
  80. sage/ext/all__sagemath_pari.py +1 -0
  81. sage/ext/memory.cpython-314-x86_64-linux-musl.so +0 -0
  82. sage/ext/memory.pyx +98 -0
  83. sage/ext_data/pari/buzzard/DimensionSk.g +286 -0
  84. sage/ext_data/pari/buzzard/Tpprog.g +179 -0
  85. sage/ext_data/pari/buzzard/genusn.g +129 -0
  86. sage/ext_data/pari/dokchitser/computel.gp +740 -0
  87. sage/ext_data/pari/dokchitser/computel.gp.template +740 -0
  88. sage/ext_data/pari/dokchitser/ex-bsw +43 -0
  89. sage/ext_data/pari/dokchitser/ex-chgen +48 -0
  90. sage/ext_data/pari/dokchitser/ex-chqua +37 -0
  91. sage/ext_data/pari/dokchitser/ex-delta +35 -0
  92. sage/ext_data/pari/dokchitser/ex-eisen +30 -0
  93. sage/ext_data/pari/dokchitser/ex-gen2 +38 -0
  94. sage/ext_data/pari/dokchitser/ex-gen3 +49 -0
  95. sage/ext_data/pari/dokchitser/ex-gen4 +54 -0
  96. sage/ext_data/pari/dokchitser/ex-nf +48 -0
  97. sage/ext_data/pari/dokchitser/ex-shin +50 -0
  98. sage/ext_data/pari/dokchitser/ex-tau2 +30 -0
  99. sage/ext_data/pari/dokchitser/ex-zeta +27 -0
  100. sage/ext_data/pari/dokchitser/ex-zeta2 +47 -0
  101. sage/ext_data/pari/dokchitser/testall +13 -0
  102. sage/ext_data/pari/simon/ell.gp +2129 -0
  103. sage/ext_data/pari/simon/ellQ.gp +2151 -0
  104. sage/ext_data/pari/simon/ellcommon.gp +126 -0
  105. sage/ext_data/pari/simon/qfsolve.gp +722 -0
  106. sage/ext_data/pari/simon/resultant3.gp +306 -0
  107. sage/groups/all__sagemath_pari.py +3 -0
  108. sage/groups/pari_group.py +175 -0
  109. sage/interfaces/all__sagemath_pari.py +1 -0
  110. sage/interfaces/genus2reduction.py +464 -0
  111. sage/interfaces/gp.py +1114 -0
  112. sage/libs/all__sagemath_pari.py +2 -0
  113. sage/libs/linkages/__init__.py +1 -0
  114. sage/libs/linkages/padics/API.pxi +617 -0
  115. sage/libs/linkages/padics/Polynomial_ram.pxi +388 -0
  116. sage/libs/linkages/padics/Polynomial_shared.pxi +554 -0
  117. sage/libs/linkages/padics/__init__.py +1 -0
  118. sage/libs/linkages/padics/fmpz_poly_unram.pxi +869 -0
  119. sage/libs/linkages/padics/mpz.pxi +691 -0
  120. sage/libs/linkages/padics/relaxed/API.pxi +518 -0
  121. sage/libs/linkages/padics/relaxed/__init__.py +1 -0
  122. sage/libs/linkages/padics/relaxed/flint.pxi +543 -0
  123. sage/libs/linkages/padics/unram_shared.pxi +247 -0
  124. sage/libs/pari/__init__.py +210 -0
  125. sage/libs/pari/all.py +5 -0
  126. sage/libs/pari/convert_flint.cpython-314-x86_64-linux-musl.so +0 -0
  127. sage/libs/pari/convert_flint.pxd +14 -0
  128. sage/libs/pari/convert_flint.pyx +159 -0
  129. sage/libs/pari/convert_gmp.cpython-314-x86_64-linux-musl.so +0 -0
  130. sage/libs/pari/convert_gmp.pxd +14 -0
  131. sage/libs/pari/convert_gmp.pyx +210 -0
  132. sage/libs/pari/convert_sage.cpython-314-x86_64-linux-musl.so +0 -0
  133. sage/libs/pari/convert_sage.pxd +16 -0
  134. sage/libs/pari/convert_sage.pyx +588 -0
  135. sage/libs/pari/convert_sage_complex_double.cpython-314-x86_64-linux-musl.so +0 -0
  136. sage/libs/pari/convert_sage_complex_double.pxd +14 -0
  137. sage/libs/pari/convert_sage_complex_double.pyx +132 -0
  138. sage/libs/pari/convert_sage_matrix.cpython-314-x86_64-linux-musl.so +0 -0
  139. sage/libs/pari/convert_sage_matrix.pyx +106 -0
  140. sage/libs/pari/convert_sage_real_double.cpython-314-x86_64-linux-musl.so +0 -0
  141. sage/libs/pari/convert_sage_real_double.pxd +5 -0
  142. sage/libs/pari/convert_sage_real_double.pyx +14 -0
  143. sage/libs/pari/convert_sage_real_mpfr.cpython-314-x86_64-linux-musl.so +0 -0
  144. sage/libs/pari/convert_sage_real_mpfr.pxd +7 -0
  145. sage/libs/pari/convert_sage_real_mpfr.pyx +108 -0
  146. sage/libs/pari/misc.cpython-314-x86_64-linux-musl.so +0 -0
  147. sage/libs/pari/misc.pxd +4 -0
  148. sage/libs/pari/misc.pyx +26 -0
  149. sage/libs/pari/tests.py +1848 -0
  150. sage/matrix/all__sagemath_pari.py +1 -0
  151. sage/matrix/matrix_integer_pari.cpython-314-x86_64-linux-musl.so +0 -0
  152. sage/matrix/matrix_integer_pari.pyx +187 -0
  153. sage/matrix/matrix_rational_pari.cpython-314-x86_64-linux-musl.so +0 -0
  154. sage/matrix/matrix_rational_pari.pyx +160 -0
  155. sage/quadratic_forms/all__sagemath_pari.py +10 -0
  156. sage/quadratic_forms/genera/all.py +9 -0
  157. sage/quadratic_forms/genera/genus.py +3506 -0
  158. sage/quadratic_forms/genera/normal_form.py +1519 -0
  159. sage/quadratic_forms/genera/spinor_genus.py +243 -0
  160. sage/quadratic_forms/qfsolve.py +255 -0
  161. sage/quadratic_forms/quadratic_form__automorphisms.py +427 -0
  162. sage/quadratic_forms/quadratic_form__genus.py +141 -0
  163. sage/quadratic_forms/quadratic_form__local_density_interfaces.py +140 -0
  164. sage/quadratic_forms/quadratic_form__local_normal_form.py +421 -0
  165. sage/quadratic_forms/quadratic_form__local_representation_conditions.py +889 -0
  166. sage/quadratic_forms/quadratic_form__mass.py +69 -0
  167. sage/quadratic_forms/quadratic_form__mass__Conway_Sloane_masses.py +663 -0
  168. sage/quadratic_forms/quadratic_form__mass__Siegel_densities.py +373 -0
  169. sage/quadratic_forms/quadratic_form__siegel_product.py +198 -0
  170. sage/quadratic_forms/special_values.py +323 -0
  171. sage/rings/all__sagemath_pari.py +15 -0
  172. sage/rings/factorint_pari.cpython-314-x86_64-linux-musl.so +0 -0
  173. sage/rings/factorint_pari.pyx +80 -0
  174. sage/rings/finite_rings/all__sagemath_pari.py +1 -0
  175. sage/rings/finite_rings/element_givaro.cpython-314-x86_64-linux-musl.so +0 -0
  176. sage/rings/finite_rings/element_givaro.pxd +91 -0
  177. sage/rings/finite_rings/element_givaro.pyx +1769 -0
  178. sage/rings/finite_rings/element_ntl_gf2e.cpython-314-x86_64-linux-musl.so +0 -0
  179. sage/rings/finite_rings/element_ntl_gf2e.pxd +22 -0
  180. sage/rings/finite_rings/element_ntl_gf2e.pyx +1333 -0
  181. sage/rings/finite_rings/element_pari_ffelt.cpython-314-x86_64-linux-musl.so +0 -0
  182. sage/rings/finite_rings/element_pari_ffelt.pxd +13 -0
  183. sage/rings/finite_rings/element_pari_ffelt.pyx +1441 -0
  184. sage/rings/finite_rings/finite_field_givaro.py +612 -0
  185. sage/rings/finite_rings/finite_field_pari_ffelt.py +238 -0
  186. sage/rings/finite_rings/hom_finite_field_givaro.cpython-314-x86_64-linux-musl.so +0 -0
  187. sage/rings/finite_rings/hom_finite_field_givaro.pxd +28 -0
  188. sage/rings/finite_rings/hom_finite_field_givaro.pyx +280 -0
  189. sage/rings/finite_rings/residue_field_givaro.cpython-314-x86_64-linux-musl.so +0 -0
  190. sage/rings/finite_rings/residue_field_givaro.pyx +133 -0
  191. sage/rings/finite_rings/residue_field_pari_ffelt.cpython-314-x86_64-linux-musl.so +0 -0
  192. sage/rings/finite_rings/residue_field_pari_ffelt.pyx +128 -0
  193. sage/rings/function_field/all__sagemath_pari.py +1 -0
  194. sage/rings/function_field/valuation.py +1450 -0
  195. sage/rings/function_field/valuation_ring.py +212 -0
  196. sage/rings/number_field/all__sagemath_pari.py +14 -0
  197. sage/rings/number_field/totallyreal.cpython-314-x86_64-linux-musl.so +0 -0
  198. sage/rings/number_field/totallyreal.pyx +509 -0
  199. sage/rings/number_field/totallyreal_data.cpython-314-x86_64-linux-musl.so +0 -0
  200. sage/rings/number_field/totallyreal_data.pxd +26 -0
  201. sage/rings/number_field/totallyreal_data.pyx +928 -0
  202. sage/rings/number_field/totallyreal_phc.py +144 -0
  203. sage/rings/number_field/totallyreal_rel.py +1018 -0
  204. sage/rings/padics/CA_template.pxi +1847 -0
  205. sage/rings/padics/CA_template_header.pxi +50 -0
  206. sage/rings/padics/CR_template.pxi +2563 -0
  207. sage/rings/padics/CR_template_header.pxi +57 -0
  208. sage/rings/padics/FM_template.pxi +1575 -0
  209. sage/rings/padics/FM_template_header.pxi +50 -0
  210. sage/rings/padics/FP_template.pxi +2176 -0
  211. sage/rings/padics/FP_template_header.pxi +57 -0
  212. sage/rings/padics/all.py +3 -0
  213. sage/rings/padics/all__sagemath_pari.py +11 -0
  214. sage/rings/padics/common_conversion.cpython-314-x86_64-linux-musl.so +0 -0
  215. sage/rings/padics/common_conversion.pxd +15 -0
  216. sage/rings/padics/common_conversion.pyx +508 -0
  217. sage/rings/padics/eisenstein_extension_generic.py +232 -0
  218. sage/rings/padics/factory.py +3623 -0
  219. sage/rings/padics/generic_nodes.py +1615 -0
  220. sage/rings/padics/lattice_precision.py +2889 -0
  221. sage/rings/padics/morphism.cpython-314-x86_64-linux-musl.so +0 -0
  222. sage/rings/padics/morphism.pxd +11 -0
  223. sage/rings/padics/morphism.pyx +366 -0
  224. sage/rings/padics/padic_base_generic.py +467 -0
  225. sage/rings/padics/padic_base_leaves.py +1235 -0
  226. sage/rings/padics/padic_capped_absolute_element.cpython-314-x86_64-linux-musl.so +0 -0
  227. sage/rings/padics/padic_capped_absolute_element.pxd +15 -0
  228. sage/rings/padics/padic_capped_absolute_element.pyx +520 -0
  229. sage/rings/padics/padic_capped_relative_element.cpython-314-x86_64-linux-musl.so +0 -0
  230. sage/rings/padics/padic_capped_relative_element.pxd +14 -0
  231. sage/rings/padics/padic_capped_relative_element.pyx +614 -0
  232. sage/rings/padics/padic_extension_generic.py +990 -0
  233. sage/rings/padics/padic_extension_leaves.py +738 -0
  234. sage/rings/padics/padic_fixed_mod_element.cpython-314-x86_64-linux-musl.so +0 -0
  235. sage/rings/padics/padic_fixed_mod_element.pxd +15 -0
  236. sage/rings/padics/padic_fixed_mod_element.pyx +584 -0
  237. sage/rings/padics/padic_floating_point_element.cpython-314-x86_64-linux-musl.so +0 -0
  238. sage/rings/padics/padic_floating_point_element.pxd +14 -0
  239. sage/rings/padics/padic_floating_point_element.pyx +447 -0
  240. sage/rings/padics/padic_generic_element.cpython-314-x86_64-linux-musl.so +0 -0
  241. sage/rings/padics/padic_generic_element.pxd +48 -0
  242. sage/rings/padics/padic_generic_element.pyx +4642 -0
  243. sage/rings/padics/padic_lattice_element.py +1342 -0
  244. sage/rings/padics/padic_printing.cpython-314-x86_64-linux-musl.so +0 -0
  245. sage/rings/padics/padic_printing.pxd +38 -0
  246. sage/rings/padics/padic_printing.pyx +1505 -0
  247. sage/rings/padics/padic_relaxed_element.cpython-314-x86_64-linux-musl.so +0 -0
  248. sage/rings/padics/padic_relaxed_element.pxd +56 -0
  249. sage/rings/padics/padic_relaxed_element.pyx +18 -0
  250. sage/rings/padics/padic_relaxed_errors.cpython-314-x86_64-linux-musl.so +0 -0
  251. sage/rings/padics/padic_relaxed_errors.pxd +11 -0
  252. sage/rings/padics/padic_relaxed_errors.pyx +71 -0
  253. sage/rings/padics/padic_template_element.pxi +1212 -0
  254. sage/rings/padics/padic_template_element_header.pxi +50 -0
  255. sage/rings/padics/padic_valuation.py +1423 -0
  256. sage/rings/padics/pow_computer_flint.cpython-314-x86_64-linux-musl.so +0 -0
  257. sage/rings/padics/pow_computer_flint.pxd +38 -0
  258. sage/rings/padics/pow_computer_flint.pyx +641 -0
  259. sage/rings/padics/pow_computer_relative.cpython-314-x86_64-linux-musl.so +0 -0
  260. sage/rings/padics/pow_computer_relative.pxd +29 -0
  261. sage/rings/padics/pow_computer_relative.pyx +415 -0
  262. sage/rings/padics/qadic_flint_CA.cpython-314-x86_64-linux-musl.so +0 -0
  263. sage/rings/padics/qadic_flint_CA.pxd +21 -0
  264. sage/rings/padics/qadic_flint_CA.pyx +130 -0
  265. sage/rings/padics/qadic_flint_CR.cpython-314-x86_64-linux-musl.so +0 -0
  266. sage/rings/padics/qadic_flint_CR.pxd +13 -0
  267. sage/rings/padics/qadic_flint_CR.pyx +172 -0
  268. sage/rings/padics/qadic_flint_FM.cpython-314-x86_64-linux-musl.so +0 -0
  269. sage/rings/padics/qadic_flint_FM.pxd +14 -0
  270. sage/rings/padics/qadic_flint_FM.pyx +111 -0
  271. sage/rings/padics/qadic_flint_FP.cpython-314-x86_64-linux-musl.so +0 -0
  272. sage/rings/padics/qadic_flint_FP.pxd +12 -0
  273. sage/rings/padics/qadic_flint_FP.pyx +165 -0
  274. sage/rings/padics/relative_extension_leaves.py +429 -0
  275. sage/rings/padics/relative_ramified_CA.cpython-314-x86_64-linux-musl.so +0 -0
  276. sage/rings/padics/relative_ramified_CA.pxd +9 -0
  277. sage/rings/padics/relative_ramified_CA.pyx +33 -0
  278. sage/rings/padics/relative_ramified_CR.cpython-314-x86_64-linux-musl.so +0 -0
  279. sage/rings/padics/relative_ramified_CR.pxd +8 -0
  280. sage/rings/padics/relative_ramified_CR.pyx +33 -0
  281. sage/rings/padics/relative_ramified_FM.cpython-314-x86_64-linux-musl.so +0 -0
  282. sage/rings/padics/relative_ramified_FM.pxd +9 -0
  283. sage/rings/padics/relative_ramified_FM.pyx +33 -0
  284. sage/rings/padics/relative_ramified_FP.cpython-314-x86_64-linux-musl.so +0 -0
  285. sage/rings/padics/relative_ramified_FP.pxd +8 -0
  286. sage/rings/padics/relative_ramified_FP.pyx +33 -0
  287. sage/rings/padics/relaxed_template.pxi +4229 -0
  288. sage/rings/padics/relaxed_template_header.pxi +160 -0
  289. sage/rings/padics/tests.py +35 -0
  290. sage/rings/padics/tutorial.py +341 -0
  291. sage/rings/padics/unramified_extension_generic.py +335 -0
  292. sage/rings/padics/witt_vector.py +917 -0
  293. sage/rings/padics/witt_vector_ring.py +934 -0
  294. sage/rings/pari_ring.py +235 -0
  295. sage/rings/polynomial/all__sagemath_pari.py +1 -0
  296. sage/rings/polynomial/padics/all.py +1 -0
  297. sage/rings/polynomial/padics/polynomial_padic.py +360 -0
  298. sage/rings/polynomial/padics/polynomial_padic_capped_relative_dense.py +1324 -0
  299. sage/rings/polynomial/padics/polynomial_padic_flat.py +72 -0
  300. sage/rings/power_series_pari.cpython-314-x86_64-linux-musl.so +0 -0
  301. sage/rings/power_series_pari.pxd +6 -0
  302. sage/rings/power_series_pari.pyx +934 -0
  303. sage/rings/tate_algebra.py +1282 -0
  304. sage/rings/tate_algebra_element.cpython-314-x86_64-linux-musl.so +0 -0
  305. sage/rings/tate_algebra_element.pxd +49 -0
  306. sage/rings/tate_algebra_element.pyx +3464 -0
  307. sage/rings/tate_algebra_ideal.cpython-314-x86_64-linux-musl.so +0 -0
  308. sage/rings/tate_algebra_ideal.pxd +7 -0
  309. sage/rings/tate_algebra_ideal.pyx +1307 -0
  310. sage/rings/valuation/all.py +7 -0
  311. sage/rings/valuation/augmented_valuation.py +2118 -0
  312. sage/rings/valuation/developing_valuation.py +362 -0
  313. sage/rings/valuation/gauss_valuation.py +812 -0
  314. sage/rings/valuation/inductive_valuation.py +1686 -0
  315. sage/rings/valuation/limit_valuation.py +946 -0
  316. sage/rings/valuation/mapped_valuation.py +656 -0
  317. sage/rings/valuation/scaled_valuation.py +322 -0
  318. sage/rings/valuation/trivial_valuation.py +382 -0
  319. sage/rings/valuation/valuation.py +1119 -0
  320. sage/rings/valuation/valuation_space.py +1615 -0
  321. sage/rings/valuation/valuations_catalog.py +10 -0
  322. sage/rings/valuation/value_group.py +697 -0
  323. sage/schemes/all__sagemath_pari.py +1 -0
  324. sage/schemes/elliptic_curves/all__sagemath_pari.py +1 -0
  325. sage/schemes/elliptic_curves/descent_two_isogeny_pari.cpython-314-x86_64-linux-musl.so +0 -0
  326. sage/schemes/elliptic_curves/descent_two_isogeny_pari.pyx +46 -0
  327. sage_wheels/bin/gp +0 -0
  328. sage_wheels/bin/gp2c +0 -0
  329. sage_wheels/bin/gp2c-run +57 -0
  330. sage_wheels/bin/xeus-gp +0 -0
  331. 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