passagemath-singular 10.6.31rc3__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-singular might be problematic. Click here for more details.

Files changed (493) hide show
  1. PySingular.cpython-314-x86_64-linux-musl.so +0 -0
  2. passagemath_singular-10.6.31rc3.dist-info/METADATA +183 -0
  3. passagemath_singular-10.6.31rc3.dist-info/RECORD +493 -0
  4. passagemath_singular-10.6.31rc3.dist-info/WHEEL +5 -0
  5. passagemath_singular-10.6.31rc3.dist-info/top_level.txt +3 -0
  6. passagemath_singular.libs/libSingular-4-67059f19.4.1.so +0 -0
  7. passagemath_singular.libs/libcddgmp-30166d29.so.0.1.3 +0 -0
  8. passagemath_singular.libs/libfactory-4-9d37bcf4.4.1.so +0 -0
  9. passagemath_singular.libs/libflint-fd6f12fc.so.21.0.0 +0 -0
  10. passagemath_singular.libs/libgcc_s-0cd532bd.so.1 +0 -0
  11. passagemath_singular.libs/libgf2x-9e30c3e3.so.3.0.0 +0 -0
  12. passagemath_singular.libs/libgfortran-2c33b284.so.5.0.0 +0 -0
  13. passagemath_singular.libs/libgmp-0e7fc84e.so.10.5.0 +0 -0
  14. passagemath_singular.libs/libgsl-42cda06f.so.28.0.0 +0 -0
  15. passagemath_singular.libs/libmpfr-aaecbfc0.so.6.2.1 +0 -0
  16. passagemath_singular.libs/libncursesw-9c9e32c3.so.6.5 +0 -0
  17. passagemath_singular.libs/libntl-26885ca2.so.44.0.1 +0 -0
  18. passagemath_singular.libs/libomalloc-0-e9ff96db.9.6.so +0 -0
  19. passagemath_singular.libs/libopenblasp-r0-905cb27d.3.29.so +0 -0
  20. passagemath_singular.libs/libpolys-4-8bcf8e7d.4.1.so +0 -0
  21. passagemath_singular.libs/libquadmath-bb76a5fc.so.0.0.0 +0 -0
  22. passagemath_singular.libs/libreadline-06542304.so.8.2 +0 -0
  23. passagemath_singular.libs/libsingular_resources-4-73bf7623.4.1.so +0 -0
  24. passagemath_singular.libs/libstdc++-5d72f927.so.6.0.33 +0 -0
  25. sage/algebras/all__sagemath_singular.py +3 -0
  26. sage/algebras/fusion_rings/all.py +19 -0
  27. sage/algebras/fusion_rings/f_matrix.py +2448 -0
  28. sage/algebras/fusion_rings/fast_parallel_fmats_methods.cpython-314-x86_64-linux-musl.so +0 -0
  29. sage/algebras/fusion_rings/fast_parallel_fmats_methods.pxd +5 -0
  30. sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx +538 -0
  31. sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.cpython-314-x86_64-linux-musl.so +0 -0
  32. sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pxd +3 -0
  33. sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx +331 -0
  34. sage/algebras/fusion_rings/fusion_double.py +899 -0
  35. sage/algebras/fusion_rings/fusion_ring.py +1580 -0
  36. sage/algebras/fusion_rings/poly_tup_engine.cpython-314-x86_64-linux-musl.so +0 -0
  37. sage/algebras/fusion_rings/poly_tup_engine.pxd +24 -0
  38. sage/algebras/fusion_rings/poly_tup_engine.pyx +579 -0
  39. sage/algebras/fusion_rings/shm_managers.cpython-314-x86_64-linux-musl.so +0 -0
  40. sage/algebras/fusion_rings/shm_managers.pxd +24 -0
  41. sage/algebras/fusion_rings/shm_managers.pyx +780 -0
  42. sage/algebras/letterplace/all.py +1 -0
  43. sage/algebras/letterplace/free_algebra_element_letterplace.cpython-314-x86_64-linux-musl.so +0 -0
  44. sage/algebras/letterplace/free_algebra_element_letterplace.pxd +18 -0
  45. sage/algebras/letterplace/free_algebra_element_letterplace.pyx +755 -0
  46. sage/algebras/letterplace/free_algebra_letterplace.cpython-314-x86_64-linux-musl.so +0 -0
  47. sage/algebras/letterplace/free_algebra_letterplace.pxd +35 -0
  48. sage/algebras/letterplace/free_algebra_letterplace.pyx +914 -0
  49. sage/algebras/letterplace/letterplace_ideal.cpython-314-x86_64-linux-musl.so +0 -0
  50. sage/algebras/letterplace/letterplace_ideal.pyx +408 -0
  51. sage/algebras/quatalg/all.py +2 -0
  52. sage/algebras/quatalg/quaternion_algebra.py +4778 -0
  53. sage/algebras/quatalg/quaternion_algebra_cython.cpython-314-x86_64-linux-musl.so +0 -0
  54. sage/algebras/quatalg/quaternion_algebra_cython.pyx +261 -0
  55. sage/algebras/quatalg/quaternion_algebra_element.cpython-314-x86_64-linux-musl.so +0 -0
  56. sage/algebras/quatalg/quaternion_algebra_element.pxd +29 -0
  57. sage/algebras/quatalg/quaternion_algebra_element.pyx +2176 -0
  58. sage/all__sagemath_singular.py +11 -0
  59. sage/ext_data/all__sagemath_singular.py +1 -0
  60. sage/ext_data/singular/function_field/core.lib +98 -0
  61. sage/interfaces/all__sagemath_singular.py +1 -0
  62. sage/interfaces/singular.py +2835 -0
  63. sage/libs/all__sagemath_singular.py +1 -0
  64. sage/libs/singular/__init__.py +1 -0
  65. sage/libs/singular/decl.pxd +1168 -0
  66. sage/libs/singular/function.cpython-314-x86_64-linux-musl.so +0 -0
  67. sage/libs/singular/function.pxd +87 -0
  68. sage/libs/singular/function.pyx +1901 -0
  69. sage/libs/singular/function_factory.py +61 -0
  70. sage/libs/singular/groebner_strategy.cpython-314-x86_64-linux-musl.so +0 -0
  71. sage/libs/singular/groebner_strategy.pxd +22 -0
  72. sage/libs/singular/groebner_strategy.pyx +582 -0
  73. sage/libs/singular/option.cpython-314-x86_64-linux-musl.so +0 -0
  74. sage/libs/singular/option.pyx +671 -0
  75. sage/libs/singular/polynomial.cpython-314-x86_64-linux-musl.so +0 -0
  76. sage/libs/singular/polynomial.pxd +39 -0
  77. sage/libs/singular/polynomial.pyx +661 -0
  78. sage/libs/singular/ring.cpython-314-x86_64-linux-musl.so +0 -0
  79. sage/libs/singular/ring.pxd +58 -0
  80. sage/libs/singular/ring.pyx +893 -0
  81. sage/libs/singular/singular.cpython-314-x86_64-linux-musl.so +0 -0
  82. sage/libs/singular/singular.pxd +72 -0
  83. sage/libs/singular/singular.pyx +1944 -0
  84. sage/libs/singular/standard_options.py +145 -0
  85. sage/matrix/all__sagemath_singular.py +1 -0
  86. sage/matrix/matrix_mpolynomial_dense.cpython-314-x86_64-linux-musl.so +0 -0
  87. sage/matrix/matrix_mpolynomial_dense.pxd +7 -0
  88. sage/matrix/matrix_mpolynomial_dense.pyx +615 -0
  89. sage/rings/all__sagemath_singular.py +1 -0
  90. sage/rings/function_field/all__sagemath_singular.py +1 -0
  91. sage/rings/function_field/derivations_polymod.py +911 -0
  92. sage/rings/function_field/element_polymod.cpython-314-x86_64-linux-musl.so +0 -0
  93. sage/rings/function_field/element_polymod.pyx +406 -0
  94. sage/rings/function_field/function_field_polymod.py +2611 -0
  95. sage/rings/function_field/ideal_polymod.py +1775 -0
  96. sage/rings/function_field/order_polymod.py +1475 -0
  97. sage/rings/function_field/place_polymod.py +681 -0
  98. sage/rings/polynomial/all__sagemath_singular.py +1 -0
  99. sage/rings/polynomial/multi_polynomial_ideal_libsingular.cpython-314-x86_64-linux-musl.so +0 -0
  100. sage/rings/polynomial/multi_polynomial_ideal_libsingular.pxd +5 -0
  101. sage/rings/polynomial/multi_polynomial_ideal_libsingular.pyx +339 -0
  102. sage/rings/polynomial/multi_polynomial_libsingular.cpython-314-x86_64-linux-musl.so +0 -0
  103. sage/rings/polynomial/multi_polynomial_libsingular.pxd +30 -0
  104. sage/rings/polynomial/multi_polynomial_libsingular.pyx +6277 -0
  105. sage/rings/polynomial/plural.cpython-314-x86_64-linux-musl.so +0 -0
  106. sage/rings/polynomial/plural.pxd +48 -0
  107. sage/rings/polynomial/plural.pyx +3171 -0
  108. sage/symbolic/all__sagemath_singular.py +1 -0
  109. sage/symbolic/comparison_impl.pxi +428 -0
  110. sage/symbolic/constants_c_impl.pxi +178 -0
  111. sage/symbolic/expression.cpython-314-x86_64-linux-musl.so +0 -0
  112. sage/symbolic/expression.pxd +7 -0
  113. sage/symbolic/expression.pyx +14200 -0
  114. sage/symbolic/getitem_impl.pxi +202 -0
  115. sage/symbolic/pynac.pxi +572 -0
  116. sage/symbolic/pynac_constant_impl.pxi +133 -0
  117. sage/symbolic/pynac_function_impl.pxi +206 -0
  118. sage/symbolic/pynac_impl.pxi +2576 -0
  119. sage/symbolic/pynac_wrap.h +124 -0
  120. sage/symbolic/series_impl.pxi +272 -0
  121. sage/symbolic/substitution_map_impl.pxi +94 -0
  122. sage_wheels/bin/ESingular +0 -0
  123. sage_wheels/bin/Singular +0 -0
  124. sage_wheels/bin/TSingular +0 -0
  125. sage_wheels/lib/singular/MOD/cohomo.la +41 -0
  126. sage_wheels/lib/singular/MOD/cohomo.so +0 -0
  127. sage_wheels/lib/singular/MOD/customstd.la +41 -0
  128. sage_wheels/lib/singular/MOD/customstd.so +0 -0
  129. sage_wheels/lib/singular/MOD/freealgebra.la +41 -0
  130. sage_wheels/lib/singular/MOD/freealgebra.so +0 -0
  131. sage_wheels/lib/singular/MOD/gfanlib.la +41 -0
  132. sage_wheels/lib/singular/MOD/gfanlib.so +0 -0
  133. sage_wheels/lib/singular/MOD/gitfan.la +41 -0
  134. sage_wheels/lib/singular/MOD/gitfan.so +0 -0
  135. sage_wheels/lib/singular/MOD/interval.la +41 -0
  136. sage_wheels/lib/singular/MOD/interval.so +0 -0
  137. sage_wheels/lib/singular/MOD/loctriv.la +41 -0
  138. sage_wheels/lib/singular/MOD/loctriv.so +0 -0
  139. sage_wheels/lib/singular/MOD/machinelearning.la +41 -0
  140. sage_wheels/lib/singular/MOD/machinelearning.so +0 -0
  141. sage_wheels/lib/singular/MOD/p_Procs_FieldGeneral.la +41 -0
  142. sage_wheels/lib/singular/MOD/p_Procs_FieldGeneral.so +0 -0
  143. sage_wheels/lib/singular/MOD/p_Procs_FieldIndep.la +41 -0
  144. sage_wheels/lib/singular/MOD/p_Procs_FieldIndep.so +0 -0
  145. sage_wheels/lib/singular/MOD/p_Procs_FieldQ.la +41 -0
  146. sage_wheels/lib/singular/MOD/p_Procs_FieldQ.so +0 -0
  147. sage_wheels/lib/singular/MOD/p_Procs_FieldZp.la +41 -0
  148. sage_wheels/lib/singular/MOD/p_Procs_FieldZp.so +0 -0
  149. sage_wheels/lib/singular/MOD/partialgb.la +41 -0
  150. sage_wheels/lib/singular/MOD/partialgb.so +0 -0
  151. sage_wheels/lib/singular/MOD/pyobject.la +41 -0
  152. sage_wheels/lib/singular/MOD/pyobject.so +0 -0
  153. sage_wheels/lib/singular/MOD/singmathic.la +41 -0
  154. sage_wheels/lib/singular/MOD/singmathic.so +0 -0
  155. sage_wheels/lib/singular/MOD/sispasm.la +41 -0
  156. sage_wheels/lib/singular/MOD/sispasm.so +0 -0
  157. sage_wheels/lib/singular/MOD/subsets.la +41 -0
  158. sage_wheels/lib/singular/MOD/subsets.so +0 -0
  159. sage_wheels/lib/singular/MOD/systhreads.la +41 -0
  160. sage_wheels/lib/singular/MOD/systhreads.so +0 -0
  161. sage_wheels/lib/singular/MOD/syzextra.la +41 -0
  162. sage_wheels/lib/singular/MOD/syzextra.so +0 -0
  163. sage_wheels/libexec/singular/MOD/change_cost +0 -0
  164. sage_wheels/libexec/singular/MOD/singularsurf +11 -0
  165. sage_wheels/libexec/singular/MOD/singularsurf_jupyter +9 -0
  166. sage_wheels/libexec/singular/MOD/singularsurf_win +10 -0
  167. sage_wheels/libexec/singular/MOD/solve_IP +0 -0
  168. sage_wheels/libexec/singular/MOD/surfex +16 -0
  169. sage_wheels/libexec/singular/MOD/toric_ideal +0 -0
  170. sage_wheels/share/factory/gftables/10201 +342 -0
  171. sage_wheels/share/factory/gftables/1024 +37 -0
  172. sage_wheels/share/factory/gftables/10609 +356 -0
  173. sage_wheels/share/factory/gftables/11449 +384 -0
  174. sage_wheels/share/factory/gftables/11881 +398 -0
  175. sage_wheels/share/factory/gftables/121 +6 -0
  176. sage_wheels/share/factory/gftables/12167 +408 -0
  177. sage_wheels/share/factory/gftables/125 +7 -0
  178. sage_wheels/share/factory/gftables/12769 +428 -0
  179. sage_wheels/share/factory/gftables/128 +7 -0
  180. sage_wheels/share/factory/gftables/1331 +47 -0
  181. sage_wheels/share/factory/gftables/1369 +48 -0
  182. sage_wheels/share/factory/gftables/14641 +490 -0
  183. sage_wheels/share/factory/gftables/15625 +523 -0
  184. sage_wheels/share/factory/gftables/16 +3 -0
  185. sage_wheels/share/factory/gftables/16129 +540 -0
  186. sage_wheels/share/factory/gftables/16384 +549 -0
  187. sage_wheels/share/factory/gftables/16807 +563 -0
  188. sage_wheels/share/factory/gftables/1681 +58 -0
  189. sage_wheels/share/factory/gftables/169 +8 -0
  190. sage_wheels/share/factory/gftables/17161 +574 -0
  191. sage_wheels/share/factory/gftables/1849 +64 -0
  192. sage_wheels/share/factory/gftables/18769 +628 -0
  193. sage_wheels/share/factory/gftables/19321 +646 -0
  194. sage_wheels/share/factory/gftables/19683 +659 -0
  195. sage_wheels/share/factory/gftables/2048 +71 -0
  196. sage_wheels/share/factory/gftables/2187 +75 -0
  197. sage_wheels/share/factory/gftables/2197 +76 -0
  198. sage_wheels/share/factory/gftables/2209 +76 -0
  199. sage_wheels/share/factory/gftables/22201 +742 -0
  200. sage_wheels/share/factory/gftables/22801 +762 -0
  201. sage_wheels/share/factory/gftables/2401 +82 -0
  202. sage_wheels/share/factory/gftables/243 +11 -0
  203. sage_wheels/share/factory/gftables/24389 +815 -0
  204. sage_wheels/share/factory/gftables/24649 +824 -0
  205. sage_wheels/share/factory/gftables/25 +3 -0
  206. sage_wheels/share/factory/gftables/256 +11 -0
  207. sage_wheels/share/factory/gftables/26569 +888 -0
  208. sage_wheels/share/factory/gftables/27 +3 -0
  209. sage_wheels/share/factory/gftables/27889 +932 -0
  210. sage_wheels/share/factory/gftables/2809 +96 -0
  211. sage_wheels/share/factory/gftables/28561 +954 -0
  212. sage_wheels/share/factory/gftables/289 +12 -0
  213. sage_wheels/share/factory/gftables/29791 +995 -0
  214. sage_wheels/share/factory/gftables/29929 +1000 -0
  215. sage_wheels/share/factory/gftables/3125 +107 -0
  216. sage_wheels/share/factory/gftables/32 +4 -0
  217. sage_wheels/share/factory/gftables/32041 +1070 -0
  218. sage_wheels/share/factory/gftables/32761 +1094 -0
  219. sage_wheels/share/factory/gftables/32768 +1095 -0
  220. sage_wheels/share/factory/gftables/343 +14 -0
  221. sage_wheels/share/factory/gftables/3481 +118 -0
  222. sage_wheels/share/factory/gftables/361 +14 -0
  223. sage_wheels/share/factory/gftables/36481 +1218 -0
  224. sage_wheels/share/factory/gftables/3721 +126 -0
  225. sage_wheels/share/factory/gftables/37249 +1244 -0
  226. sage_wheels/share/factory/gftables/38809 +1296 -0
  227. sage_wheels/share/factory/gftables/39601 +1322 -0
  228. sage_wheels/share/factory/gftables/4 +3 -0
  229. sage_wheels/share/factory/gftables/4096 +139 -0
  230. sage_wheels/share/factory/gftables/44521 +1486 -0
  231. sage_wheels/share/factory/gftables/4489 +152 -0
  232. sage_wheels/share/factory/gftables/49 +4 -0
  233. sage_wheels/share/factory/gftables/4913 +166 -0
  234. sage_wheels/share/factory/gftables/49729 +1660 -0
  235. sage_wheels/share/factory/gftables/5041 +170 -0
  236. sage_wheels/share/factory/gftables/50653 +1691 -0
  237. sage_wheels/share/factory/gftables/512 +20 -0
  238. sage_wheels/share/factory/gftables/51529 +1720 -0
  239. sage_wheels/share/factory/gftables/52441 +1750 -0
  240. sage_wheels/share/factory/gftables/529 +20 -0
  241. sage_wheels/share/factory/gftables/5329 +180 -0
  242. sage_wheels/share/factory/gftables/54289 +1812 -0
  243. sage_wheels/share/factory/gftables/57121 +1906 -0
  244. sage_wheels/share/factory/gftables/58081 +1938 -0
  245. sage_wheels/share/factory/gftables/59049 +1971 -0
  246. sage_wheels/share/factory/gftables/6241 +210 -0
  247. sage_wheels/share/factory/gftables/625 +23 -0
  248. sage_wheels/share/factory/gftables/63001 +2102 -0
  249. sage_wheels/share/factory/gftables/64 +5 -0
  250. sage_wheels/share/factory/gftables/6561 +221 -0
  251. sage_wheels/share/factory/gftables/6859 +231 -0
  252. sage_wheels/share/factory/gftables/6889 +232 -0
  253. sage_wheels/share/factory/gftables/729 +27 -0
  254. sage_wheels/share/factory/gftables/7921 +266 -0
  255. sage_wheels/share/factory/gftables/8 +3 -0
  256. sage_wheels/share/factory/gftables/81 +5 -0
  257. sage_wheels/share/factory/gftables/8192 +276 -0
  258. sage_wheels/share/factory/gftables/841 +30 -0
  259. sage_wheels/share/factory/gftables/9 +3 -0
  260. sage_wheels/share/factory/gftables/9409 +316 -0
  261. sage_wheels/share/factory/gftables/961 +34 -0
  262. sage_wheels/share/info/singular.info +191898 -0
  263. sage_wheels/share/singular/LIB/GND.lib +1359 -0
  264. sage_wheels/share/singular/LIB/JMBTest.lib +976 -0
  265. sage_wheels/share/singular/LIB/JMSConst.lib +1363 -0
  266. sage_wheels/share/singular/LIB/KVequiv.lib +699 -0
  267. sage_wheels/share/singular/LIB/SingularityDBM.lib +491 -0
  268. sage_wheels/share/singular/LIB/VecField.lib +1542 -0
  269. sage_wheels/share/singular/LIB/absfact.lib +959 -0
  270. sage_wheels/share/singular/LIB/ainvar.lib +730 -0
  271. sage_wheels/share/singular/LIB/aksaka.lib +419 -0
  272. sage_wheels/share/singular/LIB/alexpoly.lib +2542 -0
  273. sage_wheels/share/singular/LIB/algebra.lib +1193 -0
  274. sage_wheels/share/singular/LIB/all.lib +136 -0
  275. sage_wheels/share/singular/LIB/arcpoint.lib +514 -0
  276. sage_wheels/share/singular/LIB/arnold.lib +4553 -0
  277. sage_wheels/share/singular/LIB/arnoldclassify.lib +2058 -0
  278. sage_wheels/share/singular/LIB/arr.lib +3486 -0
  279. sage_wheels/share/singular/LIB/assprimeszerodim.lib +755 -0
  280. sage_wheels/share/singular/LIB/autgradalg.lib +3361 -0
  281. sage_wheels/share/singular/LIB/bfun.lib +1964 -0
  282. sage_wheels/share/singular/LIB/bimodules.lib +774 -0
  283. sage_wheels/share/singular/LIB/brillnoether.lib +226 -0
  284. sage_wheels/share/singular/LIB/brnoeth.lib +5017 -0
  285. sage_wheels/share/singular/LIB/central.lib +2169 -0
  286. sage_wheels/share/singular/LIB/chern.lib +4162 -0
  287. sage_wheels/share/singular/LIB/cimonom.lib +571 -0
  288. sage_wheels/share/singular/LIB/cisimplicial.lib +1835 -0
  289. sage_wheels/share/singular/LIB/classify.lib +3239 -0
  290. sage_wheels/share/singular/LIB/classify2.lib +1462 -0
  291. sage_wheels/share/singular/LIB/classifyMapGerms.lib +1515 -0
  292. sage_wheels/share/singular/LIB/classify_aeq.lib +3253 -0
  293. sage_wheels/share/singular/LIB/classifyceq.lib +2092 -0
  294. sage_wheels/share/singular/LIB/classifyci.lib +1133 -0
  295. sage_wheels/share/singular/LIB/combinat.lib +91 -0
  296. sage_wheels/share/singular/LIB/compregb.lib +276 -0
  297. sage_wheels/share/singular/LIB/control.lib +1636 -0
  298. sage_wheels/share/singular/LIB/crypto.lib +3795 -0
  299. sage_wheels/share/singular/LIB/curveInv.lib +667 -0
  300. sage_wheels/share/singular/LIB/curvepar.lib +1817 -0
  301. sage_wheels/share/singular/LIB/customstd.lib +100 -0
  302. sage_wheels/share/singular/LIB/deRham.lib +5979 -0
  303. sage_wheels/share/singular/LIB/decodegb.lib +2134 -0
  304. sage_wheels/share/singular/LIB/decomp.lib +1655 -0
  305. sage_wheels/share/singular/LIB/deflation.lib +872 -0
  306. sage_wheels/share/singular/LIB/deform.lib +925 -0
  307. sage_wheels/share/singular/LIB/difform.lib +3055 -0
  308. sage_wheels/share/singular/LIB/divisors.lib +750 -0
  309. sage_wheels/share/singular/LIB/dmod.lib +5817 -0
  310. sage_wheels/share/singular/LIB/dmodapp.lib +3269 -0
  311. sage_wheels/share/singular/LIB/dmodideal.lib +1211 -0
  312. sage_wheels/share/singular/LIB/dmodloc.lib +2645 -0
  313. sage_wheels/share/singular/LIB/dmodvar.lib +818 -0
  314. sage_wheels/share/singular/LIB/dummy.lib +17 -0
  315. sage_wheels/share/singular/LIB/elim.lib +1009 -0
  316. sage_wheels/share/singular/LIB/ellipticcovers.lib +548 -0
  317. sage_wheels/share/singular/LIB/enumpoints.lib +146 -0
  318. sage_wheels/share/singular/LIB/equising.lib +2127 -0
  319. sage_wheels/share/singular/LIB/ffmodstd.lib +2384 -0
  320. sage_wheels/share/singular/LIB/ffsolve.lib +1289 -0
  321. sage_wheels/share/singular/LIB/findifs.lib +778 -0
  322. sage_wheels/share/singular/LIB/finitediff.lib +1768 -0
  323. sage_wheels/share/singular/LIB/finvar.lib +7989 -0
  324. sage_wheels/share/singular/LIB/fpadim.lib +2429 -0
  325. sage_wheels/share/singular/LIB/fpalgebras.lib +1666 -0
  326. sage_wheels/share/singular/LIB/fpaprops.lib +1462 -0
  327. sage_wheels/share/singular/LIB/freegb.lib +3853 -0
  328. sage_wheels/share/singular/LIB/general.lib +1350 -0
  329. sage_wheels/share/singular/LIB/gfan.lib +1768 -0
  330. sage_wheels/share/singular/LIB/gitfan.lib +3130 -0
  331. sage_wheels/share/singular/LIB/gkdim.lib +99 -0
  332. sage_wheels/share/singular/LIB/gmspoly.lib +589 -0
  333. sage_wheels/share/singular/LIB/gmssing.lib +1739 -0
  334. sage_wheels/share/singular/LIB/goettsche.lib +909 -0
  335. sage_wheels/share/singular/LIB/graal.lib +1366 -0
  336. sage_wheels/share/singular/LIB/gradedModules.lib +2541 -0
  337. sage_wheels/share/singular/LIB/graphics.lib +360 -0
  338. sage_wheels/share/singular/LIB/grobcov.lib +7706 -0
  339. sage_wheels/share/singular/LIB/groups.lib +1123 -0
  340. sage_wheels/share/singular/LIB/grwalk.lib +507 -0
  341. sage_wheels/share/singular/LIB/hdepth.lib +194 -0
  342. sage_wheels/share/singular/LIB/help.cnf +57 -0
  343. sage_wheels/share/singular/LIB/hess.lib +1946 -0
  344. sage_wheels/share/singular/LIB/hnoether.lib +4292 -0
  345. sage_wheels/share/singular/LIB/hodge.lib +400 -0
  346. sage_wheels/share/singular/LIB/homolog.lib +1965 -0
  347. sage_wheels/share/singular/LIB/hyperel.lib +975 -0
  348. sage_wheels/share/singular/LIB/inout.lib +679 -0
  349. sage_wheels/share/singular/LIB/integralbasis.lib +6224 -0
  350. sage_wheels/share/singular/LIB/interval.lib +1418 -0
  351. sage_wheels/share/singular/LIB/intprog.lib +778 -0
  352. sage_wheels/share/singular/LIB/invar.lib +443 -0
  353. sage_wheels/share/singular/LIB/involut.lib +980 -0
  354. sage_wheels/share/singular/LIB/jacobson.lib +1215 -0
  355. sage_wheels/share/singular/LIB/kskernel.lib +534 -0
  356. sage_wheels/share/singular/LIB/latex.lib +3146 -0
  357. sage_wheels/share/singular/LIB/lejeune.lib +651 -0
  358. sage_wheels/share/singular/LIB/linalg.lib +2040 -0
  359. sage_wheels/share/singular/LIB/locnormal.lib +212 -0
  360. sage_wheels/share/singular/LIB/lrcalc.lib +526 -0
  361. sage_wheels/share/singular/LIB/makedbm.lib +294 -0
  362. sage_wheels/share/singular/LIB/mathml.lib +813 -0
  363. sage_wheels/share/singular/LIB/matrix.lib +1372 -0
  364. sage_wheels/share/singular/LIB/maxlike.lib +1132 -0
  365. sage_wheels/share/singular/LIB/methods.lib +212 -0
  366. sage_wheels/share/singular/LIB/moddiq.lib +322 -0
  367. sage_wheels/share/singular/LIB/modfinduni.lib +181 -0
  368. sage_wheels/share/singular/LIB/modnormal.lib +218 -0
  369. sage_wheels/share/singular/LIB/modprimdec.lib +1278 -0
  370. sage_wheels/share/singular/LIB/modquotient.lib +269 -0
  371. sage_wheels/share/singular/LIB/modstd.lib +1024 -0
  372. sage_wheels/share/singular/LIB/modular.lib +545 -0
  373. sage_wheels/share/singular/LIB/modules.lib +2561 -0
  374. sage_wheels/share/singular/LIB/modwalk.lib +609 -0
  375. sage_wheels/share/singular/LIB/mondromy.lib +1016 -0
  376. sage_wheels/share/singular/LIB/monomialideal.lib +3851 -0
  377. sage_wheels/share/singular/LIB/mprimdec.lib +2353 -0
  378. sage_wheels/share/singular/LIB/mregular.lib +1863 -0
  379. sage_wheels/share/singular/LIB/multigrading.lib +5629 -0
  380. sage_wheels/share/singular/LIB/ncHilb.lib +777 -0
  381. sage_wheels/share/singular/LIB/ncModslimgb.lib +791 -0
  382. sage_wheels/share/singular/LIB/ncalg.lib +16311 -0
  383. sage_wheels/share/singular/LIB/ncall.lib +31 -0
  384. sage_wheels/share/singular/LIB/ncdecomp.lib +468 -0
  385. sage_wheels/share/singular/LIB/ncfactor.lib +13371 -0
  386. sage_wheels/share/singular/LIB/ncfrac.lib +1023 -0
  387. sage_wheels/share/singular/LIB/nchilbert.lib +448 -0
  388. sage_wheels/share/singular/LIB/nchomolog.lib +759 -0
  389. sage_wheels/share/singular/LIB/ncloc.lib +361 -0
  390. sage_wheels/share/singular/LIB/ncpreim.lib +795 -0
  391. sage_wheels/share/singular/LIB/ncrat.lib +2849 -0
  392. sage_wheels/share/singular/LIB/nctools.lib +1887 -0
  393. sage_wheels/share/singular/LIB/nets.lib +1456 -0
  394. sage_wheels/share/singular/LIB/nfmodstd.lib +1000 -0
  395. sage_wheels/share/singular/LIB/nfmodsyz.lib +732 -0
  396. sage_wheels/share/singular/LIB/noether.lib +1106 -0
  397. sage_wheels/share/singular/LIB/normal.lib +8700 -0
  398. sage_wheels/share/singular/LIB/normaliz.lib +2226 -0
  399. sage_wheels/share/singular/LIB/ntsolve.lib +362 -0
  400. sage_wheels/share/singular/LIB/numerAlg.lib +560 -0
  401. sage_wheels/share/singular/LIB/numerDecom.lib +2261 -0
  402. sage_wheels/share/singular/LIB/olga.lib +1933 -0
  403. sage_wheels/share/singular/LIB/orbitparam.lib +351 -0
  404. sage_wheels/share/singular/LIB/parallel.lib +319 -0
  405. sage_wheels/share/singular/LIB/paraplanecurves.lib +3110 -0
  406. sage_wheels/share/singular/LIB/perron.lib +202 -0
  407. sage_wheels/share/singular/LIB/pfd.lib +2223 -0
  408. sage_wheels/share/singular/LIB/phindex.lib +642 -0
  409. sage_wheels/share/singular/LIB/pointid.lib +673 -0
  410. sage_wheels/share/singular/LIB/polybori.lib +1430 -0
  411. sage_wheels/share/singular/LIB/polyclass.lib +525 -0
  412. sage_wheels/share/singular/LIB/polylib.lib +1174 -0
  413. sage_wheels/share/singular/LIB/polymake.lib +1902 -0
  414. sage_wheels/share/singular/LIB/presolve.lib +1533 -0
  415. sage_wheels/share/singular/LIB/primdec.lib +9576 -0
  416. sage_wheels/share/singular/LIB/primdecint.lib +1782 -0
  417. sage_wheels/share/singular/LIB/primitiv.lib +401 -0
  418. sage_wheels/share/singular/LIB/puiseuxexpansions.lib +1631 -0
  419. sage_wheels/share/singular/LIB/purityfiltration.lib +960 -0
  420. sage_wheels/share/singular/LIB/qhmoduli.lib +1561 -0
  421. sage_wheels/share/singular/LIB/qmatrix.lib +293 -0
  422. sage_wheels/share/singular/LIB/random.lib +455 -0
  423. sage_wheels/share/singular/LIB/ratgb.lib +489 -0
  424. sage_wheels/share/singular/LIB/realclassify.lib +5759 -0
  425. sage_wheels/share/singular/LIB/realizationMatroids.lib +772 -0
  426. sage_wheels/share/singular/LIB/realrad.lib +1197 -0
  427. sage_wheels/share/singular/LIB/recover.lib +2628 -0
  428. sage_wheels/share/singular/LIB/redcgs.lib +3984 -0
  429. sage_wheels/share/singular/LIB/reesclos.lib +465 -0
  430. sage_wheels/share/singular/LIB/resbinomial.lib +2802 -0
  431. sage_wheels/share/singular/LIB/resgraph.lib +789 -0
  432. sage_wheels/share/singular/LIB/resjung.lib +820 -0
  433. sage_wheels/share/singular/LIB/resolve.lib +5110 -0
  434. sage_wheels/share/singular/LIB/resources.lib +170 -0
  435. sage_wheels/share/singular/LIB/reszeta.lib +5473 -0
  436. sage_wheels/share/singular/LIB/ring.lib +1328 -0
  437. sage_wheels/share/singular/LIB/ringgb.lib +343 -0
  438. sage_wheels/share/singular/LIB/rinvar.lib +1153 -0
  439. sage_wheels/share/singular/LIB/rootisolation.lib +1481 -0
  440. sage_wheels/share/singular/LIB/rootsmr.lib +709 -0
  441. sage_wheels/share/singular/LIB/rootsur.lib +886 -0
  442. sage_wheels/share/singular/LIB/rstandard.lib +607 -0
  443. sage_wheels/share/singular/LIB/rwalk.lib +336 -0
  444. sage_wheels/share/singular/LIB/sagbi.lib +1353 -0
  445. sage_wheels/share/singular/LIB/sagbiNormaliz.lib +1622 -0
  446. sage_wheels/share/singular/LIB/sagbiNormaliz0.lib +1498 -0
  447. sage_wheels/share/singular/LIB/sagbigrob.lib +449 -0
  448. sage_wheels/share/singular/LIB/schreyer.lib +321 -0
  449. sage_wheels/share/singular/LIB/schubert.lib +2551 -0
  450. sage_wheels/share/singular/LIB/sets.lib +524 -0
  451. sage_wheels/share/singular/LIB/sheafcoh.lib +1663 -0
  452. sage_wheels/share/singular/LIB/signcond.lib +437 -0
  453. sage_wheels/share/singular/LIB/sing.lib +1094 -0
  454. sage_wheels/share/singular/LIB/sing4ti2.lib +419 -0
  455. sage_wheels/share/singular/LIB/solve.lib +2243 -0
  456. sage_wheels/share/singular/LIB/spcurve.lib +1077 -0
  457. sage_wheels/share/singular/LIB/spectrum.lib +62 -0
  458. sage_wheels/share/singular/LIB/sresext.lib +757 -0
  459. sage_wheels/share/singular/LIB/ssi.lib +143 -0
  460. sage_wheels/share/singular/LIB/standard.lib +2769 -0
  461. sage_wheels/share/singular/LIB/stanleyreisner.lib +473 -0
  462. sage_wheels/share/singular/LIB/stdmodule.lib +547 -0
  463. sage_wheels/share/singular/LIB/stratify.lib +1070 -0
  464. sage_wheels/share/singular/LIB/surf.lib +506 -0
  465. sage_wheels/share/singular/LIB/surf_jupyter.lib +223 -0
  466. sage_wheels/share/singular/LIB/surfacesignature.lib +522 -0
  467. sage_wheels/share/singular/LIB/surfex.lib +1462 -0
  468. sage_wheels/share/singular/LIB/swalk.lib +877 -0
  469. sage_wheels/share/singular/LIB/symodstd.lib +1570 -0
  470. sage_wheels/share/singular/LIB/systhreads.lib +74 -0
  471. sage_wheels/share/singular/LIB/tasks.lib +1324 -0
  472. sage_wheels/share/singular/LIB/tateProdCplxNegGrad.lib +2412 -0
  473. sage_wheels/share/singular/LIB/teachstd.lib +858 -0
  474. sage_wheels/share/singular/LIB/template.lib +116 -0
  475. sage_wheels/share/singular/LIB/toric.lib +1119 -0
  476. sage_wheels/share/singular/LIB/transformation.lib +116 -0
  477. sage_wheels/share/singular/LIB/triang.lib +1197 -0
  478. sage_wheels/share/singular/LIB/tropical.lib +8741 -0
  479. sage_wheels/share/singular/LIB/tropicalEllipticCovers.lib +2922 -0
  480. sage_wheels/share/singular/LIB/tropicalNewton.lib +1128 -0
  481. sage_wheels/share/singular/LIB/tst.lib +1108 -0
  482. sage_wheels/share/singular/LIB/weierstr.lib +241 -0
  483. sage_wheels/share/singular/LIB/zeroset.lib +1478 -0
  484. sage_wheels/share/singular/emacs/.emacs-general +184 -0
  485. sage_wheels/share/singular/emacs/.emacs-singular +234 -0
  486. sage_wheels/share/singular/emacs/COPYING +44 -0
  487. sage_wheels/share/singular/emacs/cmd-cmpl.el +241 -0
  488. sage_wheels/share/singular/emacs/ex-cmpl.el +1681 -0
  489. sage_wheels/share/singular/emacs/hlp-cmpl.el +4318 -0
  490. sage_wheels/share/singular/emacs/lib-cmpl.el +179 -0
  491. sage_wheels/share/singular/emacs/singular.el +4273 -0
  492. sage_wheels/share/singular/emacs/singular.xpm +39 -0
  493. sage_wheels/share/singular/singular.idx +5002 -0
@@ -0,0 +1,4778 @@
1
+ # sage_setup: distribution = sagemath-singular
2
+ """
3
+ Quaternion Algebras
4
+
5
+ AUTHORS:
6
+
7
+ - Jon Bobber (2009): rewrite
8
+
9
+ - William Stein (2009): rewrite
10
+
11
+ - Julian Rueth (2014-03-02): use UniqueFactory for caching
12
+
13
+ - Peter Bruin (2021): do not require the base ring to be a field
14
+
15
+ - Lorenz Panny (2022): :meth:`QuaternionOrder.isomorphism_to`,
16
+ :meth:`QuaternionFractionalIdeal_rational.minimal_element`
17
+
18
+ - Sebastian A. Spindler (2024): extend ramification functionality to number fields,
19
+ adapt :meth:`QuaternionAlgebra_ab.maximal_order` to allow for extension of an order
20
+
21
+ - Eloi Torrents (2024): construct quaternion algebras over number fields from ramification
22
+
23
+ This code is partly based on Sage code by David Kohel from 2005.
24
+
25
+ TESTS:
26
+
27
+ Pickling test::
28
+
29
+ sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)
30
+ sage: Q == loads(dumps(Q))
31
+ True
32
+ """
33
+
34
+ # ****************************************************************************
35
+ # Copyright (C) 2009 William Stein <wstein@gmail.com>
36
+ # Copyright (C) 2009 Jonathan Bober <jwbober@gmail.com>
37
+ # Copyright (C) 2014 Julian Rueth <julian.rueth@fsfe.org>
38
+ # Copyright (C) 2021 Peter Bruin <P.J.Bruin@math.leidenuniv.nl>
39
+ #
40
+ # This program is free software: you can redistribute it and/or modify
41
+ # it under the terms of the GNU General Public License as published by
42
+ # the Free Software Foundation, either version 2 of the License, or
43
+ # (at your option) any later version.
44
+ # https://www.gnu.org/licenses/
45
+ # ****************************************************************************
46
+ from operator import itemgetter
47
+
48
+ from sage.arith.misc import (hilbert_conductor_inverse,
49
+ hilbert_symbol,
50
+ gcd,
51
+ kronecker as kronecker_symbol,
52
+ prime_divisors,
53
+ valuation)
54
+ from sage.misc.lazy_import import lazy_import
55
+ from sage.rings.real_mpfr import RR
56
+ from sage.rings.integer import Integer
57
+ from sage.rings.integer_ring import ZZ
58
+ from sage.rings.finite_rings.finite_field_constructor import GF
59
+ from sage.rings.ideal import Ideal_fractional
60
+ from sage.rings.rational_field import RationalField, QQ
61
+ from sage.rings.infinity import infinity
62
+ from sage.rings.number_field.number_field_base import NumberField
63
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
64
+ from sage.rings.polynomial.polynomial_ring import polygen
65
+ from sage.rings.power_series_ring import PowerSeriesRing
66
+ from sage.structure.category_object import normalize_names
67
+ from sage.structure.parent import Parent
68
+ from sage.matrix.matrix_space import MatrixSpace
69
+ from sage.matrix.constructor import diagonal_matrix, matrix
70
+ from sage.structure.sequence import Sequence
71
+ from sage.structure.element import RingElement
72
+ from sage.structure.factory import UniqueFactory
73
+ from sage.modules.free_module import FreeModule
74
+ from sage.modules.free_module_element import vector
75
+ from sage.quadratic_forms.quadratic_form import QuadraticForm
76
+
77
+
78
+ from .quaternion_algebra_element import (
79
+ QuaternionAlgebraElement_abstract,
80
+ QuaternionAlgebraElement_generic,
81
+ QuaternionAlgebraElement_rational_field,
82
+ QuaternionAlgebraElement_number_field)
83
+ from . import quaternion_algebra_cython
84
+
85
+
86
+ lazy_import('sage.modular.modsym.p1list', 'P1List')
87
+
88
+ from sage.misc.cachefunc import cached_method
89
+ from sage.misc.functional import is_odd
90
+
91
+ from sage.categories.algebras import Algebras
92
+ from sage.categories.number_fields import NumberFields
93
+
94
+ from sage.structure.richcmp import richcmp_method
95
+
96
+ ########################################################
97
+ # Constructor
98
+ ########################################################
99
+
100
+
101
+ class QuaternionAlgebraFactory(UniqueFactory):
102
+ r"""
103
+ Construct a quaternion algebra.
104
+
105
+ INPUT:
106
+
107
+ There are four input formats:
108
+
109
+ - ``QuaternionAlgebra(a, b)``, where `a` and `b` can be coerced to
110
+ units in a common field `K` of characteristic different from 2.
111
+
112
+ - ``QuaternionAlgebra(K, a, b)``, where `K` is a ring in which 2
113
+ is a unit and `a` and `b` are units of `K`.
114
+
115
+ - ``QuaternionAlgebra(D)``, where `D \ge 1` is a squarefree
116
+ integer. This constructs a quaternion algebra of discriminant
117
+ `D` over `K = \QQ`. Suitable nonzero rational numbers `a`, `b`
118
+ as above are deduced from `D`.
119
+
120
+ - ``QuaternionAlgebra(K, primes, inv_archimedean)``, where `K` is a
121
+ number field or `\QQ`, ``primes`` is a list of prime ideals of `K`
122
+ and ``inv_archimedean`` is a list of local invariants (`0` or
123
+ `\frac{1}{2}`) specifying the ramification at the (infinite) real
124
+ places of `K`. This constructs a quaternion algebra ramified exactly
125
+ at the places given by ``primes`` and those (algebraic) real
126
+ embeddings of `K` indexed in ``K.embeddings(AA)`` by ``l`` with
127
+ ``inv_archimedean[l] = 1/2``.
128
+
129
+ OUTPUT:
130
+
131
+ The quaternion algebra `(a, b)_K` over `K` generated by `i`, `j`
132
+ subject to `i^2 = a`, `j^2 = b`, and `ji = -ij`.
133
+
134
+ EXAMPLES:
135
+
136
+ ``QuaternionAlgebra(a, b)`` -- return the quaternion algebra
137
+ `(a, b)_K`, where the base ring `K` is a suitably chosen field
138
+ containing `a` and `b`::
139
+
140
+ sage: QuaternionAlgebra(-2,-3)
141
+ Quaternion Algebra (-2, -3) with base ring Rational Field
142
+ sage: QuaternionAlgebra(GF(5)(2), GF(5)(3))
143
+ Quaternion Algebra (2, 3) with base ring Finite Field of size 5
144
+ sage: QuaternionAlgebra(2, GF(5)(3))
145
+ Quaternion Algebra (2, 3) with base ring Finite Field of size 5
146
+ sage: QuaternionAlgebra(QQ[sqrt(2)](-1), -5) # needs sage.symbolic
147
+ Quaternion Algebra (-1, -5) with base ring Number Field in sqrt2
148
+ with defining polynomial x^2 - 2 with sqrt2 = 1.414213562373095?
149
+ sage: QuaternionAlgebra(sqrt(-1), sqrt(-3)) # needs sage.symbolic
150
+ Quaternion Algebra (I, sqrt(-3)) with base ring Symbolic Ring
151
+ sage: QuaternionAlgebra(1r,1)
152
+ Quaternion Algebra (1, 1) with base ring Rational Field
153
+ sage: A.<t> = ZZ[]
154
+ sage: QuaternionAlgebra(-1, t)
155
+ Quaternion Algebra (-1, t) with base ring
156
+ Fraction Field of Univariate Polynomial Ring in t over Integer Ring
157
+
158
+ Python ints and floats may be passed to the
159
+ ``QuaternionAlgebra(a, b)`` constructor, as may all pairs of
160
+ nonzero elements of a domain not of characteristic 2.
161
+
162
+ The following tests address the issues raised in :issue:`10601`::
163
+
164
+ sage: QuaternionAlgebra(1r,1)
165
+ Quaternion Algebra (1, 1) with base ring Rational Field
166
+ sage: QuaternionAlgebra(1,1.0r)
167
+ Quaternion Algebra (1.00000000000000, 1.00000000000000) with base ring
168
+ Real Field with 53 bits of precision
169
+ sage: QuaternionAlgebra(0,0)
170
+ Traceback (most recent call last):
171
+ ...
172
+ ValueError: defining elements of quaternion algebra (0, 0)
173
+ are not invertible in Rational Field
174
+ sage: QuaternionAlgebra(GF(2)(1),1)
175
+ Traceback (most recent call last):
176
+ ...
177
+ ValueError: 2 is not invertible in Finite Field of size 2
178
+ sage: a = PermutationGroupElement([1,2,3]) # needs sage.groups
179
+ sage: QuaternionAlgebra(a, a) # needs sage.groups
180
+ Traceback (most recent call last):
181
+ ...
182
+ ValueError: a and b must be elements of a ring with characteristic not 2
183
+
184
+ ``QuaternionAlgebra(K, a, b)`` -- return the quaternion algebra
185
+ defined by `(a, b)` over the ring `K`::
186
+
187
+ sage: QuaternionAlgebra(QQ, -7, -21)
188
+ Quaternion Algebra (-7, -21) with base ring Rational Field
189
+ sage: QuaternionAlgebra(QQ[sqrt(2)], -2,-3) # needs sage.rings.number_field sage.symbolic
190
+ Quaternion Algebra (-2, -3) with base ring Number Field in sqrt2
191
+ with defining polynomial x^2 - 2 with sqrt2 = 1.414213562373095?
192
+
193
+ ``QuaternionAlgebra(D)`` -- `D` is a squarefree integer; return a
194
+ rational quaternion algebra of discriminant `D`::
195
+
196
+ sage: QuaternionAlgebra(1)
197
+ Quaternion Algebra (-1, 1) with base ring Rational Field
198
+ sage: QuaternionAlgebra(2)
199
+ Quaternion Algebra (-1, -1) with base ring Rational Field
200
+ sage: QuaternionAlgebra(7)
201
+ Quaternion Algebra (-1, -7) with base ring Rational Field
202
+ sage: QuaternionAlgebra(2*3*5*7)
203
+ Quaternion Algebra (-22, 210) with base ring Rational Field
204
+
205
+ ``QuaternionAlgebra(K, primes, inv_archimedean)`` -- return the
206
+ quaternion algebra over `K` with the ramification specified by
207
+ ``primes`` and ``inv_archimedean``::
208
+
209
+ sage: QuaternionAlgebra(QQ, [(2), (3)], [0])
210
+ Quaternion Algebra (-1, 3) with base ring Rational Field
211
+ sage: QuaternionAlgebra(QQ, [(2), (3)], [1/2])
212
+ Traceback (most recent call last):
213
+ ...
214
+ ValueError: quaternion algebra over the rationals must have an even number of ramified places
215
+
216
+ sage: x = polygen(ZZ, 'x')
217
+ sage: K.<w> = NumberField(x^2-x-1)
218
+ sage: P = K.prime_above(2)
219
+ sage: Q = K.prime_above(3)
220
+ sage: A = QuaternionAlgebra(K, [P,Q], [0,0])
221
+ sage: A.discriminant()
222
+ Fractional ideal (6)
223
+ sage: A = QuaternionAlgebra(K, [P,Q], [1/2,0])
224
+ Traceback (most recent call last):
225
+ ...
226
+ ValueError: quaternion algebra over a number field must have an even number of ramified places
227
+
228
+ The construction via ramification fails if the base field is not
229
+ a number field::
230
+
231
+ sage: QuaternionAlgebra(RR, [], [])
232
+ Traceback (most recent call last):
233
+ ...
234
+ ValueError: quaternion algebra construction via ramification only works over a number field
235
+
236
+ The list of local invariants must specify the ramification data
237
+ at all real places of the number field `K`::
238
+
239
+ sage: QuaternionAlgebra(QuadraticField(5), [], [0])
240
+ Traceback (most recent call last):
241
+ ...
242
+ ValueError: must specify ramification at all real places of the number field
243
+
244
+ The list of local invariants specifying the ramification at the
245
+ real places may only contain `0` and `\frac{1}{2}`::
246
+
247
+ sage: QuaternionAlgebra(QuadraticField(5), [], [0,1])
248
+ Traceback (most recent call last):
249
+ ...
250
+ ValueError: list of local invariants specifying ramification should contain only 0 and 1/2
251
+
252
+ Similarly, the list of finite ramified places must consist
253
+ of primes or prime ideals of the number field `K`::
254
+
255
+ sage: QuaternionAlgebra(QuadraticField(5), ['water',2], [])
256
+ Traceback (most recent call last):
257
+ ...
258
+ ValueError: quaternion algebra constructor requires a list of primes specifying the ramification
259
+
260
+ If the coefficients `a` and `b` in the definition of the quaternion
261
+ algebra are not integral, then a slower generic type is used for
262
+ arithmetic::
263
+
264
+ sage: type(QuaternionAlgebra(-1,-3).0)
265
+ <... 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>
266
+ sage: type(QuaternionAlgebra(-1,-3/2).0)
267
+ <... 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_generic'>
268
+
269
+ Make sure caching is sane::
270
+
271
+ sage: A = QuaternionAlgebra(2,3); A
272
+ Quaternion Algebra (2, 3) with base ring Rational Field
273
+ sage: B = QuaternionAlgebra(GF(5)(2),GF(5)(3)); B
274
+ Quaternion Algebra (2, 3) with base ring Finite Field of size 5
275
+ sage: A is QuaternionAlgebra(2,3)
276
+ True
277
+ sage: B is QuaternionAlgebra(GF(5)(2),GF(5)(3))
278
+ True
279
+ sage: Q = QuaternionAlgebra(2); Q
280
+ Quaternion Algebra (-1, -1) with base ring Rational Field
281
+ sage: Q is QuaternionAlgebra(QQ,-1,-1)
282
+ True
283
+ sage: Q is QuaternionAlgebra(-1,-1)
284
+ True
285
+ sage: Q.<ii,jj,kk> = QuaternionAlgebra(15); Q.variable_names()
286
+ ('ii', 'jj', 'kk')
287
+ sage: QuaternionAlgebra(15).variable_names()
288
+ ('i', 'j', 'k')
289
+
290
+ TESTS:
291
+
292
+ Verify that bug found when working on :issue:`12006` involving coercing
293
+ invariants into the base field is fixed::
294
+
295
+ sage: Q = QuaternionAlgebra(-1,-1); Q
296
+ Quaternion Algebra (-1, -1) with base ring Rational Field
297
+ sage: parent(Q._a)
298
+ Rational Field
299
+ sage: parent(Q._b)
300
+ Rational Field
301
+
302
+ Check that construction via ramification yields the correct algebra,
303
+ i.e. that the differences between Sage and PARI are accounted for::
304
+
305
+ sage: x = polygen(ZZ, 'x')
306
+ sage: K.<v> = NumberField(-3*x^5 - 11*x^4 - 4*x^3 + 1)
307
+ sage: inv_arch = [1/2, 0, 1/2]
308
+ sage: emb_arch = K.embeddings(AA)[0:3:2]
309
+ sage: ram = QuaternionAlgebra(K, [], inv_arch).ramified_places()
310
+ sage: ram[0] == [] and ram[1] == emb_arch
311
+ True
312
+
313
+ Also check that the required Sage-PARI permutation is the correct way
314
+ around, i.e. that it does not need to be replaced by its inverse::
315
+
316
+ sage: x = polygen(ZZ, 'x')
317
+ sage: K.<j> = NumberField(5*x^4 - 50*x^2 + 5)
318
+ sage: P = K.prime_above(2)
319
+ sage: Q = K.prime_above(5)
320
+ sage: inv_arch = [1/2, 1/2, 0, 0]
321
+ sage: emb_arch = K.embeddings(AA)[0:2]
322
+ sage: ram = QuaternionAlgebra(K, [P,Q], inv_arch).ramified_places()
323
+ sage: set(ram[0]) == set([P,Q]) and ram[1] == emb_arch
324
+ True
325
+
326
+ """
327
+ def create_key(self, arg0, arg1=None, arg2=None, names='i,j,k'):
328
+ """
329
+ Create a key that uniquely determines a quaternion algebra.
330
+
331
+ TESTS::
332
+
333
+ sage: QuaternionAlgebra.create_key(-1,-1)
334
+ (Rational Field, -1, -1, ('i', 'j', 'k'))
335
+ """
336
+ # QuaternionAlgebra(D)
337
+ if arg1 is None and arg2 is None:
338
+ K = QQ
339
+ D = Integer(arg0)
340
+ a, b = hilbert_conductor_inverse(D)
341
+ a = QQ(a)
342
+ b = QQ(b)
343
+
344
+ elif arg2 is None:
345
+ # If arg0 or arg1 are Python data types, coerce them
346
+ # to the relevant Sage types. This is a bit inelegant.
347
+ L = []
348
+ for a in [arg0, arg1]:
349
+ if isinstance(a, RingElement):
350
+ L.append(a)
351
+ elif isinstance(a, int):
352
+ L.append(Integer(a))
353
+ elif isinstance(a, float):
354
+ L.append(RR(a))
355
+ else:
356
+ raise ValueError("a and b must be elements of a ring with characteristic not 2")
357
+
358
+ # QuaternionAlgebra(a, b)
359
+ v = Sequence(L)
360
+ K = v.universe().fraction_field()
361
+ a = K(v[0])
362
+ b = K(v[1])
363
+
364
+ elif isinstance(arg1, list) and isinstance(arg2, list):
365
+ # QuaternionAlgebra(K, primes, inv_archimedean)
366
+ K = arg0
367
+ if K not in NumberFields():
368
+ raise ValueError("quaternion algebra construction via ramification only works over a number field")
369
+ if not set(arg2).issubset(set([0, QQ((1,2))])):
370
+ raise ValueError("list of local invariants specifying ramification should contain only 0 and 1/2")
371
+
372
+ # Check that the finite ramification is given by prime ideals
373
+ try:
374
+ if isinstance(K, RationalField):
375
+ primes = set(ZZ.ideal(p) for p in arg1)
376
+ else:
377
+ primes = set(K.ideal(p) for p in arg1)
378
+ assert all(p.is_prime() for p in primes)
379
+ except (AssertionError, TypeError, NameError):
380
+ raise ValueError("quaternion algebra constructor requires a list of primes specifying the ramification")
381
+
382
+ if isinstance(K, RationalField):
383
+ # Construct the quaternion algebra via ramification over the rationals
384
+ if len(arg2) > 1 or (len(arg2) == 1 and is_odd(len(primes) + 2*arg2[0])):
385
+ raise ValueError("quaternion algebra over the rationals must have an even number of ramified places")
386
+ D = ZZ.ideal_monoid().prod(primes).gen()
387
+ a, b = hilbert_conductor_inverse(D)
388
+ a = QQ(a)
389
+ b = QQ(b)
390
+
391
+ else:
392
+ # Construct the quaternion algebra via ramification over a number field
393
+ if len(arg2) != len(K.real_places()):
394
+ raise ValueError("must specify ramification at all real places of the number field")
395
+ if is_odd(len(primes) + 2 * sum(arg2)):
396
+ raise ValueError("quaternion algebra over a number field must have an even number of ramified places")
397
+
398
+ from sage.combinat.words.word import Word
399
+ from sage.rings.qqbar import AA
400
+
401
+ # We want to compute the correct quaternion algebra over K with PARI
402
+ # As PARI computes an alternative representation of K given by an integral
403
+ # and monic defining polynomial, we precompute this representation and then
404
+ # permute the local invariants (using an isomorphism between the representations)
405
+ x = polygen(QQ, 'x')
406
+ g = K.pari_polynomial().sage({'x': x})
407
+ alpha = g.roots(ring=K, multiplicities=False)[0]
408
+
409
+ # Compute the number field PARI actually uses, together with isomorphisms to and from K
410
+ L, to_K, from_K = K.change_generator(alpha)
411
+
412
+ # This computation of the permutation relies on the fact that both
413
+ # Sage and PARI sort real roots of polynomials in increasing order
414
+ v = K.gen()
415
+ vals_embed = [sigma(from_K(v)) for sigma in L.embeddings(AA)]
416
+ perm = Word(vals_embed).standard_permutation()
417
+
418
+ # Transfer the primes to PARI and permute the local invariants
419
+ fin_places_pari = [I.pari_prime() for I in primes]
420
+ inv_arch_pari = [arg2[i-1] for i in perm]
421
+
422
+ # Compute the correct quaternion algebra over L in PARI
423
+ A = L.__pari__().alginit([2, [fin_places_pari, [QQ((1,2))] * len(fin_places_pari)],
424
+ inv_arch_pari], flag=0)
425
+
426
+ # Obtain representation of A in terms of invariants in L
427
+ a_L = L(A.algsplittingfield().disc()[1])
428
+ b_L = L(A.algb())
429
+
430
+ # Finally, transfer the result to K
431
+ a = to_K(a_L)
432
+ b = to_K(b_L)
433
+ else:
434
+ # QuaternionAlgebra(K, a, b)
435
+ K = arg0
436
+ a = K(arg1)
437
+ b = K(arg2)
438
+
439
+ if not K(2).is_unit():
440
+ raise ValueError("2 is not invertible in %s" % K)
441
+ if not (a.is_unit() and b.is_unit()):
442
+ raise ValueError("defining elements of quaternion algebra (%s, %s) are not invertible in %s"
443
+ % (a, b, K))
444
+
445
+ names = normalize_names(3, names)
446
+ return (K, a, b, names)
447
+
448
+ def create_object(self, version, key, **extra_args):
449
+ """
450
+ Create the object from the key (extra arguments are ignored). This is
451
+ only called if the object was not found in the cache.
452
+
453
+ TESTS::
454
+
455
+ sage: QuaternionAlgebra.create_object("6.0", (QQ, -1, -1, ('i', 'j', 'k')))
456
+ Quaternion Algebra (-1, -1) with base ring Rational Field
457
+ """
458
+ K, a, b, names = key
459
+ return QuaternionAlgebra_ab(K, a, b, names=names)
460
+
461
+
462
+ QuaternionAlgebra = QuaternionAlgebraFactory("QuaternionAlgebra")
463
+
464
+ ########################################################
465
+ # Classes
466
+ ########################################################
467
+
468
+
469
+ def is_QuaternionAlgebra(A):
470
+ """
471
+ Return ``True`` if ``A`` is of the QuaternionAlgebra data type.
472
+
473
+ EXAMPLES::
474
+
475
+ sage: sage.algebras.quatalg.quaternion_algebra.is_QuaternionAlgebra(QuaternionAlgebra(QQ,-1,-1))
476
+ doctest:warning...
477
+ DeprecationWarning: the function is_QuaternionAlgebra is deprecated;
478
+ use 'isinstance(..., QuaternionAlgebra_abstract)' instead
479
+ See https://github.com/sagemath/sage/issues/37896 for details.
480
+ True
481
+ sage: sage.algebras.quatalg.quaternion_algebra.is_QuaternionAlgebra(ZZ)
482
+ False
483
+ """
484
+ from sage.misc.superseded import deprecation
485
+ deprecation(37896, "the function is_QuaternionAlgebra is deprecated; use 'isinstance(..., QuaternionAlgebra_abstract)' instead")
486
+ return isinstance(A, QuaternionAlgebra_abstract)
487
+
488
+
489
+ class QuaternionAlgebra_abstract(Parent):
490
+ def _repr_(self):
491
+ """
492
+ EXAMPLES::
493
+
494
+ sage: sage.algebras.quatalg.quaternion_algebra.QuaternionAlgebra_abstract(QQ)._repr_()
495
+ 'Quaternion Algebra with base ring Rational Field'
496
+ """
497
+ return "Quaternion Algebra with base ring %s" % self.base_ring()
498
+
499
+ def ngens(self):
500
+ """
501
+ Return the number of generators of the quaternion algebra as a K-vector
502
+ space, not including 1.
503
+
504
+ This value is always 3: the algebra is spanned
505
+ by the standard basis `1`, `i`, `j`, `k`.
506
+
507
+ EXAMPLES::
508
+
509
+ sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)
510
+ sage: Q.ngens()
511
+ 3
512
+ sage: Q.gens()
513
+ (i, j, k)
514
+ """
515
+ return 3
516
+
517
+ @cached_method
518
+ def basis(self):
519
+ """
520
+ Return the fixed basis of ``self``, which is `1`, `i`, `j`, `k`, where
521
+ `i`, `j`, `k` are the generators of ``self``.
522
+
523
+ EXAMPLES::
524
+
525
+ sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)
526
+ sage: Q.basis()
527
+ (1, i, j, k)
528
+
529
+ sage: Q.<xyz,abc,theta> = QuaternionAlgebra(GF(9,'a'),-5,-2)
530
+ sage: Q.basis()
531
+ (1, xyz, abc, theta)
532
+
533
+ The basis is cached::
534
+
535
+ sage: Q.basis() is Q.basis()
536
+ True
537
+ """
538
+ i, j, k = self.gens()
539
+ return (self.one(), i, j, k)
540
+
541
+ @cached_method
542
+ def inner_product_matrix(self):
543
+ """
544
+ Return the inner product matrix associated to ``self``.
545
+
546
+ This is the
547
+ Gram matrix of the reduced norm as a quadratic form on ``self``.
548
+ The standard basis `1`, `i`, `j`, `k` is orthogonal, so this matrix
549
+ is just the diagonal matrix with diagonal entries `2`, `-2a`, `-2b`,
550
+ `2ab`.
551
+
552
+ EXAMPLES::
553
+
554
+ sage: Q.<i,j,k> = QuaternionAlgebra(-5,-19)
555
+ sage: Q.inner_product_matrix()
556
+ [ 2 0 0 0]
557
+ [ 0 10 0 0]
558
+ [ 0 0 38 0]
559
+ [ 0 0 0 190]
560
+ """
561
+ a, b = self._a, self._b
562
+ M = diagonal_matrix(self.base_ring(), [2, -2 * a, -2 * b, 2 * a * b])
563
+ M.set_immutable()
564
+ return M
565
+
566
+ def is_commutative(self) -> bool:
567
+ """
568
+ Return ``False`` always, since all quaternion algebras are
569
+ noncommutative.
570
+
571
+ EXAMPLES::
572
+
573
+ sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3,-7)
574
+ sage: Q.is_commutative()
575
+ False
576
+ """
577
+ return False
578
+
579
+ def is_division_algebra(self) -> bool:
580
+ """
581
+ Check whether this quaternion algebra is a division algebra,
582
+ i.e. whether every nonzero element in it is invertible.
583
+
584
+ Currently only implemented for quaternion algebras
585
+ defined over a number field.
586
+
587
+ EXAMPLES::
588
+
589
+ sage: QuaternionAlgebra(QQ,-5,-2).is_division_algebra()
590
+ True
591
+ sage: QuaternionAlgebra(2,9).is_division_algebra()
592
+ False
593
+ sage: K.<z> = QuadraticField(3)
594
+ sage: QuaternionAlgebra(K, 1+z, 3-z).is_division_algebra()
595
+ False
596
+
597
+ By checking ramification, the method correctly recognizes division
598
+ quaternion algebras over a number field even if they have trivial
599
+ discriminant::
600
+
601
+ sage: L = QuadraticField(5)
602
+ sage: A = QuaternionAlgebra(L, -1, -1)
603
+ sage: A.discriminant()
604
+ Fractional ideal (1)
605
+ sage: A.is_division_algebra()
606
+ True
607
+
608
+ The method is not implemented over arbitrary base rings yet::
609
+
610
+ sage: QuaternionAlgebra(RR(2.),1).is_division_algebra()
611
+ Traceback (most recent call last):
612
+ ...
613
+ NotImplementedError: base ring must be rational numbers or a number field
614
+ """
615
+ try:
616
+ return self.ramified_places(inf=True) != ([], [])
617
+ except ValueError:
618
+ raise NotImplementedError("base ring must be rational numbers or a number field")
619
+
620
+ def is_matrix_ring(self) -> bool:
621
+ """
622
+ Check whether this quaternion algebra is isomorphic to the
623
+ 2x2 matrix ring over the base ring.
624
+
625
+ Currently only implemented for quaternion algebras
626
+ defined over a number field.
627
+
628
+ EXAMPLES::
629
+
630
+ sage: QuaternionAlgebra(QQ,-5,-2).is_matrix_ring()
631
+ False
632
+ sage: QuaternionAlgebra(2,9).is_matrix_ring()
633
+ True
634
+ sage: K.<z> = QuadraticField(3)
635
+ sage: QuaternionAlgebra(K, 1+z, 3-z).is_matrix_ring()
636
+ True
637
+
638
+ By checking ramification, the method is able to recognize that
639
+ quaternion algebras (defined over a number field) with trivial
640
+ discriminant need not be matrix rings::
641
+
642
+ sage: L = QuadraticField(5)
643
+ sage: A = QuaternionAlgebra(L, -1, -1)
644
+ sage: A.discriminant()
645
+ Fractional ideal (1)
646
+ sage: A.is_matrix_ring()
647
+ False
648
+
649
+ The method is not implemented over arbitrary base rings yet::
650
+
651
+ sage: QuaternionAlgebra(RR(2.),1).is_matrix_ring()
652
+ Traceback (most recent call last):
653
+ ...
654
+ NotImplementedError: base ring must be rational numbers or a number field
655
+ """
656
+ try:
657
+ return self.ramified_places(inf=True) == ([], [])
658
+ except ValueError:
659
+ raise NotImplementedError("base ring must be rational numbers or a number field")
660
+
661
+ def is_exact(self) -> bool:
662
+ """
663
+ Return ``True`` if elements of this quaternion algebra are represented
664
+ exactly, i.e. there is no precision loss when doing arithmetic. A
665
+ quaternion algebra is exact if and only if its base field is
666
+ exact.
667
+
668
+ EXAMPLES::
669
+
670
+ sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)
671
+ sage: Q.is_exact()
672
+ True
673
+ sage: Q.<i,j,k> = QuaternionAlgebra(Qp(7), -3, -7)
674
+ sage: Q.is_exact()
675
+ False
676
+ """
677
+ return self.base_ring().is_exact()
678
+
679
+ def is_field(self, proof=True) -> bool:
680
+ """
681
+ Return ``False`` always, since all quaternion algebras are
682
+ noncommutative and all fields are commutative.
683
+
684
+ EXAMPLES::
685
+
686
+ sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)
687
+ sage: Q.is_field()
688
+ False
689
+ """
690
+ return False
691
+
692
+ def is_finite(self) -> bool:
693
+ """
694
+ Return ``True`` if the quaternion algebra is finite as a set.
695
+
696
+ Algorithm: A quaternion algebra is finite if and only if the
697
+ base field is finite.
698
+
699
+ EXAMPLES::
700
+
701
+ sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)
702
+ sage: Q.is_finite()
703
+ False
704
+ sage: Q.<i,j,k> = QuaternionAlgebra(GF(5), -3, -7)
705
+ sage: Q.is_finite()
706
+ True
707
+ """
708
+ return self.base_ring().is_finite()
709
+
710
+ def is_integral_domain(self, proof=True) -> bool:
711
+ """
712
+ Return ``False`` always, since all quaternion algebras are
713
+ noncommutative and integral domains are commutative (in Sage).
714
+
715
+ EXAMPLES::
716
+
717
+ sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)
718
+ sage: Q.is_integral_domain()
719
+ False
720
+ """
721
+ return False
722
+
723
+ def is_noetherian(self) -> bool:
724
+ """
725
+ Return ``True`` always, since any quaternion algebra is a Noetherian
726
+ ring (because it is a finitely generated module over a field).
727
+
728
+ EXAMPLES::
729
+
730
+ sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)
731
+ sage: Q.is_noetherian()
732
+ True
733
+ """
734
+ return True
735
+
736
+ def order(self):
737
+ """
738
+ Return the number of elements of the quaternion algebra, or
739
+ ``+Infinity`` if the algebra is not finite.
740
+
741
+ EXAMPLES::
742
+
743
+ sage: Q.<i,j,k> = QuaternionAlgebra(QQ, -3, -7)
744
+ sage: Q.order()
745
+ +Infinity
746
+ sage: Q.<i,j,k> = QuaternionAlgebra(GF(5), -3, -7)
747
+ sage: Q.order()
748
+ 625
749
+ """
750
+ return (self.base_ring().order())**4
751
+
752
+ def random_element(self, *args, **kwds):
753
+ """
754
+ Return a random element of this quaternion algebra.
755
+
756
+ The ``args`` and ``kwds`` are passed to the ``random_element`` method
757
+ of the base ring.
758
+
759
+ EXAMPLES::
760
+
761
+ sage: g = QuaternionAlgebra(QQ[sqrt(2)], -3, 7).random_element() # needs sage.symbolic
762
+ sage: g.parent() is QuaternionAlgebra(QQ[sqrt(2)], -3, 7) # needs sage.symbolic
763
+ True
764
+ sage: g = QuaternionAlgebra(-3, 19).random_element()
765
+ sage: g.parent() is QuaternionAlgebra(-3, 19)
766
+ True
767
+ sage: g = QuaternionAlgebra(GF(17)(2), 3).random_element()
768
+ sage: g.parent() is QuaternionAlgebra(GF(17)(2), 3)
769
+ True
770
+
771
+ Specify the numerator and denominator bounds::
772
+
773
+ sage: g = QuaternionAlgebra(-3,19).random_element(10^6, 10^6)
774
+ sage: for h in g:
775
+ ....: assert h.numerator() in range(-10^6, 10^6 + 1)
776
+ ....: assert h.denominator() in range(10^6 + 1)
777
+
778
+ sage: g = QuaternionAlgebra(-3,19).random_element(5, 4)
779
+ sage: for h in g:
780
+ ....: assert h.numerator() in range(-5, 5 + 1)
781
+ ....: assert h.denominator() in range(4 + 1)
782
+ """
783
+ K = self.base_ring()
784
+ return self([K.random_element(*args, **kwds) for _ in range(4)])
785
+
786
+ @cached_method
787
+ def free_module(self):
788
+ """
789
+ Return the free module associated to ``self`` with inner
790
+ product given by the reduced norm.
791
+
792
+ EXAMPLES::
793
+
794
+ sage: A.<t> = LaurentPolynomialRing(GF(3))
795
+ sage: B = QuaternionAlgebra(A, -1, t)
796
+ sage: B.free_module()
797
+ Ambient free quadratic module of rank 4 over the principal ideal domain
798
+ Univariate Laurent Polynomial Ring in t over Finite Field of size 3
799
+ Inner product matrix:
800
+ [2 0 0 0]
801
+ [0 2 0 0]
802
+ [0 0 t 0]
803
+ [0 0 0 t]
804
+ """
805
+ return FreeModule(self.base_ring(), 4, inner_product_matrix=self.inner_product_matrix())
806
+
807
+ def vector_space(self):
808
+ """
809
+ Alias for :meth:`free_module`.
810
+
811
+ EXAMPLES::
812
+
813
+ sage: QuaternionAlgebra(-3,19).vector_space()
814
+ Ambient quadratic space of dimension 4 over Rational Field
815
+ Inner product matrix:
816
+ [ 2 0 0 0]
817
+ [ 0 6 0 0]
818
+ [ 0 0 -38 0]
819
+ [ 0 0 0 -114]
820
+ """
821
+ return self.free_module()
822
+
823
+
824
+ class QuaternionAlgebra_ab(QuaternionAlgebra_abstract):
825
+ """
826
+ A quaternion algebra of the form `(a, b)_K`.
827
+
828
+ See ``QuaternionAlgebra`` for many more examples.
829
+
830
+ INPUT:
831
+
832
+ - ``base_ring`` -- a commutative ring `K` in which 2 is invertible
833
+ - ``a``, ``b`` -- units of `K`
834
+ - ``names`` -- string (default: ``'i,j,k'``); names of the generators
835
+
836
+ OUTPUT:
837
+
838
+ The quaternion algebra `(a, b)` over `K` generated by `i` and `j`
839
+ subject to `i^2 = a`, `j^2 = b`, and `ji = -ij`.
840
+
841
+ EXAMPLES::
842
+
843
+ sage: QuaternionAlgebra(QQ, -7, -21) # indirect doctest
844
+ Quaternion Algebra (-7, -21) with base ring Rational Field
845
+ """
846
+ def __init__(self, base_ring, a, b, names='i,j,k'):
847
+ """
848
+ Create the quaternion algebra with `i^2 = a`, `j^2 = b`, and
849
+ `ij = -ji = k`.
850
+
851
+ TESTS:
852
+
853
+ Test making quaternion elements (using the element constructor)::
854
+
855
+ sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-1,-2)
856
+ sage: a = Q(2/3); a
857
+ 2/3
858
+ sage: type(a)
859
+ <... 'sage.algebras.quatalg.quaternion_algebra_element.QuaternionAlgebraElement_rational_field'>
860
+ sage: Q(a)
861
+ 2/3
862
+ sage: Q([1,2,3,4])
863
+ 1 + 2*i + 3*j + 4*k
864
+ sage: Q((1,2,3,4))
865
+ 1 + 2*i + 3*j + 4*k
866
+ sage: Q(-3/5)
867
+ -3/5
868
+
869
+ sage: TestSuite(Q).run()
870
+
871
+ The element 2 must be a unit in the base ring::
872
+
873
+ sage: Q.<ii,jj,kk> = QuaternionAlgebra(ZZ,-5,-19)
874
+ Traceback (most recent call last):
875
+ ...
876
+ ValueError: 2 is not invertible in Integer Ring
877
+ """
878
+ cat = Algebras(base_ring).Division().FiniteDimensional()
879
+ Parent.__init__(self, base=base_ring, names=names, category=cat)
880
+ self._a = a
881
+ self._b = b
882
+ if isinstance(base_ring, RationalField) and a.denominator() == 1 == b.denominator():
883
+ self.Element = QuaternionAlgebraElement_rational_field
884
+ elif (isinstance(base_ring, NumberField) and base_ring.degree() > 2 and base_ring.is_absolute() and
885
+ a.denominator() == 1 == b.denominator() and base_ring.defining_polynomial().is_monic()):
886
+ # This QuaternionAlgebraElement_number_field class is not
887
+ # designed to work with elements of a quadratic field. To
888
+ # do that, the main thing would be to implement
889
+ # __getitem__, etc. This would maybe give a factor of 2
890
+ # (or more?) speedup. Much care must be taken because the
891
+ # underlying representation of quadratic fields is a bit
892
+ # tricky.
893
+ self.Element = QuaternionAlgebraElement_number_field
894
+ else:
895
+ self.Element = QuaternionAlgebraElement_generic
896
+ self._populate_coercion_lists_(coerce_list=[base_ring])
897
+ self._gens = (self([0, 1, 0, 0]), self([0, 0, 1, 0]), self([0, 0, 0, 1]))
898
+
899
+ @cached_method
900
+ def maximal_order(self, take_shortcuts=True, order_basis=None):
901
+ r"""
902
+ Return a maximal order in this quaternion algebra.
903
+
904
+ If ``order_basis`` is specified, the resulting maximal order
905
+ will contain the order of the quaternion algebra given by this
906
+ basis. The algorithm used is from [Voi2012]_.
907
+
908
+ INPUT:
909
+
910
+ - ``take_shortcuts`` -- boolean (default: ``True``); if the discriminant is
911
+ prime and the invariants of the algebra are of a nice form, use
912
+ Proposition 5.2 of [Piz1980]_.
913
+
914
+ - ``order_basis`` -- (default: ``None``) a basis of an
915
+ order of this quaternion algebra
916
+
917
+ OUTPUT: a maximal order in this quaternion algebra
918
+
919
+ EXAMPLES::
920
+
921
+ sage: QuaternionAlgebra(-1,-7).maximal_order()
922
+ Order of Quaternion Algebra (-1, -7) with base ring Rational Field
923
+ with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)
924
+
925
+ sage: QuaternionAlgebra(-1,-1).maximal_order().basis()
926
+ (1/2 + 1/2*i + 1/2*j + 1/2*k, i, j, k)
927
+
928
+ sage: QuaternionAlgebra(-1,-11).maximal_order().basis()
929
+ (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)
930
+
931
+ sage: QuaternionAlgebra(-1,-3).maximal_order().basis()
932
+ (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)
933
+
934
+ sage: QuaternionAlgebra(-3,-1).maximal_order().basis()
935
+ (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
936
+
937
+ sage: QuaternionAlgebra(-2,-5).maximal_order().basis()
938
+ (1/2 + 1/2*j + 1/2*k, 1/4*i + 1/2*j + 1/4*k, j, k)
939
+
940
+ sage: QuaternionAlgebra(-5,-2).maximal_order().basis()
941
+ (1/2 + 1/2*i - 1/2*k, 1/2*i + 1/4*j - 1/4*k, i, -k)
942
+
943
+ sage: QuaternionAlgebra(-17,-3).maximal_order().basis()
944
+ (1/2 + 1/2*j, 1/2*i + 1/2*k, -1/3*j - 1/3*k, k)
945
+
946
+ sage: QuaternionAlgebra(-3,-17).maximal_order().basis()
947
+ (1/2 + 1/2*i, 1/2*j - 1/2*k, -1/3*i + 1/3*k, -k)
948
+
949
+ sage: QuaternionAlgebra(-17*9,-3).maximal_order().basis()
950
+ (1, 1/3*i, 1/6*i + 1/2*j, 1/2 + 1/3*j + 1/18*k)
951
+
952
+ sage: QuaternionAlgebra(-2, -389).maximal_order().basis()
953
+ (1/2 + 1/2*j + 1/2*k, 1/4*i + 1/2*j + 1/4*k, j, k)
954
+
955
+ If you want bases containing 1, switch off ``take_shortcuts``::
956
+
957
+ sage: QuaternionAlgebra(-3,-89).maximal_order(take_shortcuts=False)
958
+ Order of Quaternion Algebra (-3, -89) with base ring Rational Field
959
+ with basis (1, 1/2 + 1/2*i, j, 1/2 + 1/6*i + 1/2*j + 1/6*k)
960
+
961
+ sage: QuaternionAlgebra(1,1).maximal_order(take_shortcuts=False) # Matrix ring
962
+ Order of Quaternion Algebra (1, 1) with base ring Rational Field
963
+ with basis (1, 1/2 + 1/2*i, j, 1/2*j + 1/2*k)
964
+
965
+ sage: QuaternionAlgebra(-22,210).maximal_order(take_shortcuts=False)
966
+ Order of Quaternion Algebra (-22, 210) with base ring Rational Field
967
+ with basis (1, i, 1/2*i + 1/2*j, 1/2 + 17/22*i + 1/44*k)
968
+
969
+ sage: for d in ( m for m in range(1, 750) if is_squarefree(m) ): # long time (3s)
970
+ ....: A = QuaternionAlgebra(d)
971
+ ....: assert A.maximal_order(take_shortcuts=False).is_maximal()
972
+
973
+ Specifying an order basis gives an extension of orders::
974
+
975
+ sage: A.<i,j,k> = QuaternionAlgebra(-292, -732)
976
+ sage: alpha = A.random_element()
977
+ sage: while alpha.is_zero():
978
+ ....: alpha = A.random_element()
979
+ sage: conj = [alpha * b * alpha.inverse() for b in [k,i,j]]
980
+ sage: order_basis = tuple(conj) + (A.one(),)
981
+ sage: O = A.quaternion_order(basis=order_basis)
982
+ sage: R = A.maximal_order(order_basis=order_basis)
983
+ sage: O <= R and R.is_maximal()
984
+ True
985
+
986
+ We do not support number fields other than the rationals yet::
987
+
988
+ sage: K = QuadraticField(5)
989
+ sage: QuaternionAlgebra(K,-1,-1).maximal_order()
990
+ Traceback (most recent call last):
991
+ ...
992
+ NotImplementedError: maximal order only implemented
993
+ for rational quaternion algebras
994
+
995
+ TESTS:
996
+
997
+ Check that :issue:`37417` and the first part of :issue:`37217` are fixed::
998
+
999
+ sage: invars = [(-4, -28), (-292, -732), (-48, -564), (-436, -768),
1000
+ ....: (-752, -708), (885, 545), (411, -710), (-411, 593),
1001
+ ....: (805, -591), (-921, 353), (409, 96), (394, 873),
1002
+ ....: (353, -722), (730, 830), (-466, -427), (-213, -630),
1003
+ ....: (-511, 608), (493, 880), (105, -709), (-213, 530),
1004
+ ....: (97, 745)]
1005
+ sage: all(QuaternionAlgebra(a, b).maximal_order().is_maximal()
1006
+ ....: for (a, b) in invars)
1007
+ True
1008
+ """
1009
+ if self.base_ring() != QQ:
1010
+ raise NotImplementedError("maximal order only implemented for "
1011
+ "rational quaternion algebras")
1012
+
1013
+ d_A = self.discriminant()
1014
+
1015
+ # The following only works over QQ if the discriminant is prime
1016
+ # and if the invariants are of the special form
1017
+ # (every quaternion algebra of prime discriminant has a representation
1018
+ # of such a form though)
1019
+ a, b = self.invariants()
1020
+ if (not order_basis and take_shortcuts and d_A.is_prime()
1021
+ and a in ZZ and b in ZZ):
1022
+ a = ZZ(a)
1023
+ b = ZZ(b)
1024
+ i, j, k = self.gens()
1025
+
1026
+ # if necessary, try to swap invariants to match Pizer's paper
1027
+ if (a != -1 and b == -1) or (b == -2) \
1028
+ or (a != -1 and a != -2 and (-a) % 8 != 1):
1029
+ a, b = b, a
1030
+ i, j = j, i
1031
+ k = i * j
1032
+
1033
+ basis = []
1034
+ if (a, b) == (-1, -1):
1035
+ basis = [(1+i+j+k)/2, i, j, k]
1036
+ elif a == -1 and (-b).is_prime() and ((-b) % 4 == 3):
1037
+ basis = [(1+j)/2, (i+k)/2, j, k]
1038
+ elif a == -2 and (-b).is_prime() and ((-b) % 8 == 5):
1039
+ basis = [(1+j+k)/2, (i+2*j+k)/4, j, k]
1040
+ elif (-a).is_prime() and (-b).is_prime():
1041
+ q = -b
1042
+ p = -a
1043
+
1044
+ if q % 4 == 3 and kronecker_symbol(p, q) == -1:
1045
+ a = 0
1046
+ while (a * a * p + 1) % q:
1047
+ a += 1
1048
+ basis = [(1+j)/2, (i+k)/2, -(j+a*k)/q, k]
1049
+
1050
+ if basis:
1051
+ return self.quaternion_order(basis)
1052
+
1053
+ # The following code should always work (over QQ)
1054
+ # If no order basis is given, start with <1,i,j,k>
1055
+ if not order_basis:
1056
+ order_basis = (self.one(),) + self.gens()
1057
+
1058
+ try:
1059
+ R = self.quaternion_order(order_basis)
1060
+ d_R = R.discriminant()
1061
+ except (TypeError, ValueError):
1062
+ raise ValueError('order_basis is not a basis of an order of the'
1063
+ ' given quaternion algebra')
1064
+
1065
+ # Since Voight's algorithm only works for a starting basis having 1 as
1066
+ # its first vector, we derive such a basis from the given order basis
1067
+ basis = basis_for_quaternion_lattice(order_basis)
1068
+
1069
+ e_new_gens = []
1070
+
1071
+ # For each prime at which R is not yet maximal, make it bigger
1072
+ for p, _ in d_R.factor():
1073
+ e = basis
1074
+ disc = d_R
1075
+ while disc.valuation(p) > d_A.valuation(p):
1076
+ # Compute a normalized basis at p
1077
+ f = normalize_basis_at_p(list(e), p)
1078
+
1079
+ # Ensure the basis lies in R by clearing denominators
1080
+ # (this may make the order smaller at q != p)
1081
+ # Also saturate the basis (divide out p as far as possible)
1082
+ V = self.base_ring()**4
1083
+ A = matrix(self.base_ring(), 4, 4, [list(g) for g in e])
1084
+
1085
+ e_n = []
1086
+ x_rows = A.solve_left(matrix([V(vec.coefficient_tuple())
1087
+ for (vec, val) in f]),
1088
+ check=False).rows()
1089
+ denoms = [x.denominator() for x in x_rows]
1090
+ for i in range(4):
1091
+ vec = f[i][0]
1092
+ val = f[i][1]
1093
+
1094
+ v = (val/2).floor()
1095
+ e_n.append(denoms[i] / p**(v) * vec)
1096
+
1097
+ # for e_n to become p-saturated we still need to sort by
1098
+ # ascending valuation of the quadratic form
1099
+ lst = sorted(zip(e_n, [f[m][1].mod(2) for m in range(4)]),
1100
+ key=itemgetter(1))
1101
+ e_n = list(next(zip(*lst)))
1102
+
1103
+ # Final step: Enlarge the basis at p
1104
+ if p != 2:
1105
+ # ensure that v_p(e_n[1]**2) = 0 by swapping basis elements
1106
+ if ZZ(e_n[1]**2).valuation(p) != 0:
1107
+ if ZZ(e_n[2]**2).valuation(p) == 0:
1108
+ e_n[1], e_n[2] = e_n[2], e_n[1]
1109
+ else:
1110
+ e_n[1], e_n[3] = e_n[3], e_n[1]
1111
+
1112
+ a = ZZ(e_n[1]**2)
1113
+ b = ZZ(e_n[2]**2)
1114
+
1115
+ if b.valuation(p) > 0: # if v_p(b) = 0, then already p-maximal
1116
+ F = ZZ.quo(p)
1117
+ if F(a).is_square():
1118
+ x = F(a).sqrt().lift()
1119
+ if (x**2 - a).mod(p**2) == 0: # make sure v_p(x**2 - a) = 1
1120
+ x = x + p
1121
+ g = 1/p*(x - e_n[1])*e_n[2]
1122
+ e_n[2] = g
1123
+ e_n[3] = e_n[1]*g
1124
+
1125
+ else: # p == 2
1126
+ t = e_n[1].reduced_trace()
1127
+ a = -e_n[1].reduced_norm()
1128
+ b = ZZ(e_n[2]**2)
1129
+
1130
+ if t.valuation(p) == 0:
1131
+ if b.valuation(p) > 0:
1132
+ x = a
1133
+ if (x**2 - t*x + a).mod(p**2) == 0: # make sure v_p(...) = 1
1134
+ x = x + p
1135
+ g = 1/p*(x - e_n[1])*e_n[2]
1136
+ e_n[2] = g
1137
+ e_n[3] = e_n[1]*g
1138
+
1139
+ else: # t.valuation(p) > 0
1140
+ (y, z, w) = maxord_solve_aux_eq(a, b, p)
1141
+ g = 1/p*(1 + y*e_n[1] + z*e_n[2] + w*e_n[1]*e_n[2])
1142
+ h = (z*b)*e_n[1] - (y*a)*e_n[2]
1143
+ e_n[1:4] = [g, h, g * h]
1144
+ if (1 - a*y**2 - b*z**2 + a*b*w**2).valuation(2) > 2:
1145
+ e_n = basis_for_quaternion_lattice(list(e) + e_n[1:])
1146
+
1147
+ # e_n now contains elements that locally at p give a bigger order,
1148
+ # but the basis may be messed up at other primes (it might not
1149
+ # even be an order). We will join them all together at the end
1150
+ e = e_n
1151
+
1152
+ # Since e might not define an order at this point, we need to
1153
+ # manually calculate the updated discriminant
1154
+ L = [[x.pair(y) for y in e] for x in e]
1155
+ disc = matrix(QQ, 4, 4, L).determinant().sqrt()
1156
+
1157
+ e_new_gens.extend(e[1:])
1158
+
1159
+ e_new = basis_for_quaternion_lattice(list(basis) + e_new_gens)
1160
+ return self.quaternion_order(e_new)
1161
+
1162
+ def order_with_level(self, level):
1163
+ """
1164
+ Return an order in this quaternion algebra with given level.
1165
+
1166
+ INPUT:
1167
+
1168
+ - ``level`` -- positive integer
1169
+
1170
+ Currently this is only implemented when the base field is the
1171
+ rational numbers and the level is divisible by at most one
1172
+ power of a prime that ramifies in this quaternion algebra.
1173
+
1174
+ EXAMPLES::
1175
+
1176
+ sage: A.<i,j,k> = QuaternionAlgebra(5)
1177
+ sage: level = 2 * 5 * 17
1178
+ sage: O = A.order_with_level(level); O
1179
+ Order of Quaternion Algebra (-2, -5) with base ring Rational Field with basis (1/2 + 1/2*j + 7/2*k, 1/2*i + 19/2*k, j + 7*k, 17*k)
1180
+
1181
+ Check that the order has the right index in the maximal order::
1182
+
1183
+ sage: L = O.free_module()
1184
+ sage: N = A.maximal_order().free_module()
1185
+ sage: L.index_in(N) == level / 5
1186
+ True
1187
+ """
1188
+ if self.base_ring() is not QQ:
1189
+ raise NotImplementedError("base field must be rational numbers")
1190
+
1191
+ if len(self.ramified_primes()) > 1:
1192
+ raise NotImplementedError("currently this algorithm only works when the quaternion algebra is only ramified at one finite prime")
1193
+
1194
+ # The algorithm we use is similar to that in Magma (by David Kohel).
1195
+ level = abs(level)
1196
+ N = self.discriminant()
1197
+ N1 = gcd(level, N)
1198
+ M1 = level // N1
1199
+
1200
+ O = self.maximal_order()
1201
+ # if N1 != 1:
1202
+ # # we do not know why magma does the following, so we do not do it.
1203
+ # for p in self.ramified_primes():
1204
+ # if not (level % p**2):
1205
+ # raise NotImplementedError("currently sage can only compute orders whose level is divisible by at most one power of any prime that ramifies in the quaternion algebra")
1206
+
1207
+ # P = O._left_ideal_basis([N1] + [x * y - y * x
1208
+ # for x in self.basis()
1209
+ # for y in self.basis()])
1210
+ # O = self.quaternion_order(P)
1211
+
1212
+ fact = M1.factor()
1213
+ B = O.basis()
1214
+
1215
+ for (p, r) in fact:
1216
+ a = int(-p) // 2
1217
+ for v in GF(p)**4:
1218
+ x = sum([int(v[i] + a) * B[i] for i in range(4)])
1219
+ D = x.reduced_trace()**2 - 4 * x.reduced_norm()
1220
+ # x = O.random_element((-p/2).floor(), (p/2).ceil())
1221
+ if kronecker_symbol(D, p) == 1:
1222
+ break
1223
+ X = PolynomialRing(GF(p), 'x').gen()
1224
+ a = ZZ((X**2 - ZZ(x.reduced_trace()) * X + ZZ(x.reduced_norm())).roots()[0][0])
1225
+ I = O._left_ideal_basis([p**r, (x - a)**r])
1226
+ O = O._right_order_from_ideal_basis(I)
1227
+ # right_order returns the RightOrder of I inside O, so we
1228
+ # do not need to do another intersection
1229
+
1230
+ return O
1231
+
1232
+ def invariants(self):
1233
+ """
1234
+ Return the structural invariants `a`, `b` of this quaternion
1235
+ algebra: ``self`` is generated by `i`, `j` subject to
1236
+ `i^2 = a`, `j^2 = b` and `ji = -ij`.
1237
+
1238
+ EXAMPLES::
1239
+
1240
+ sage: Q.<i,j,k> = QuaternionAlgebra(15)
1241
+ sage: Q.invariants()
1242
+ (-3, 5)
1243
+ sage: i^2
1244
+ -3
1245
+ sage: j^2
1246
+ 5
1247
+ """
1248
+ return self._a, self._b
1249
+
1250
+ def __eq__(self, other):
1251
+ """
1252
+ Compare ``self`` and ``other``.
1253
+
1254
+ EXAMPLES::
1255
+
1256
+ sage: QuaternionAlgebra(-1,-7) == QuaternionAlgebra(-1,-7)
1257
+ True
1258
+ sage: QuaternionAlgebra(-1,-7) == QuaternionAlgebra(-1,-5)
1259
+ False
1260
+ """
1261
+ if not isinstance(other, QuaternionAlgebra_abstract):
1262
+ return False
1263
+ return (self.base_ring() == other.base_ring() and
1264
+ (self._a, self._b) == (other._a, other._b))
1265
+
1266
+ def __ne__(self, other):
1267
+ """
1268
+ Compare ``self`` and ``other``.
1269
+
1270
+ EXAMPLES::
1271
+
1272
+ sage: QuaternionAlgebra(-1,-7) != QuaternionAlgebra(-1,-7)
1273
+ False
1274
+ sage: QuaternionAlgebra(-1,-7) != QuaternionAlgebra(-1,-5)
1275
+ True
1276
+ """
1277
+ return not self.__eq__(other)
1278
+
1279
+ def __hash__(self):
1280
+ """
1281
+ Compute the hash of ``self``.
1282
+
1283
+ EXAMPLES::
1284
+
1285
+ sage: h1 = hash(QuaternionAlgebra(-1,-7))
1286
+ sage: h2 = hash(QuaternionAlgebra(-1,-7))
1287
+ sage: h3 = hash(QuaternionAlgebra(-1,-5))
1288
+ sage: h1 == h2 and h1 != h3
1289
+ True
1290
+ """
1291
+ return hash((self.base_ring(), self._a, self._b))
1292
+
1293
+ def gen(self, i=0):
1294
+ """
1295
+ Return the `i`-th generator of ``self``.
1296
+
1297
+ INPUT:
1298
+
1299
+ - ``i`` -- integer (default: 0)
1300
+
1301
+ EXAMPLES::
1302
+
1303
+ sage: Q.<ii,jj,kk> = QuaternionAlgebra(QQ,-1,-2); Q
1304
+ Quaternion Algebra (-1, -2) with base ring Rational Field
1305
+ sage: Q.gen(0)
1306
+ ii
1307
+ sage: Q.gen(1)
1308
+ jj
1309
+ sage: Q.gen(2)
1310
+ kk
1311
+ sage: Q.gens()
1312
+ (ii, jj, kk)
1313
+ """
1314
+ return self._gens[i]
1315
+
1316
+ def gens(self) -> tuple:
1317
+ """
1318
+ Return the generators of ``self``.
1319
+
1320
+ EXAMPLES::
1321
+
1322
+ sage: Q.<ii,jj,kk> = QuaternionAlgebra(QQ,-1,-2); Q
1323
+ Quaternion Algebra (-1, -2) with base ring Rational Field
1324
+ sage: Q.gens()
1325
+ (ii, jj, kk)
1326
+ """
1327
+ return self._gens
1328
+
1329
+ def _repr_(self):
1330
+ """
1331
+ Print representation.
1332
+
1333
+ TESTS::
1334
+
1335
+ sage: Q.<i,j,k> = QuaternionAlgebra(QQ,-5,-2)
1336
+ sage: type(Q)
1337
+ <class 'sage.algebras.quatalg.quaternion_algebra.QuaternionAlgebra_ab_with_category'>
1338
+ sage: Q._repr_()
1339
+ 'Quaternion Algebra (-5, -2) with base ring Rational Field'
1340
+ sage: Q
1341
+ Quaternion Algebra (-5, -2) with base ring Rational Field
1342
+ sage: print(Q)
1343
+ Quaternion Algebra (-5, -2) with base ring Rational Field
1344
+ sage: str(Q)
1345
+ 'Quaternion Algebra (-5, -2) with base ring Rational Field'
1346
+ """
1347
+ return "Quaternion Algebra (%r, %r) with base ring %s" % (self._a, self._b, self.base_ring())
1348
+
1349
+ def inner_product_matrix(self):
1350
+ """
1351
+ Return the inner product matrix associated to ``self``, i.e. the
1352
+ Gram matrix of the reduced norm as a quadratic form on ``self``.
1353
+ The standard basis `1`, `i`, `j`, `k` is orthogonal, so this matrix
1354
+ is just the diagonal matrix with diagonal entries `1`, `a`, `b`, `ab`.
1355
+
1356
+ EXAMPLES::
1357
+
1358
+ sage: Q.<i,j,k> = QuaternionAlgebra(-5,-19)
1359
+ sage: Q.inner_product_matrix()
1360
+ [ 2 0 0 0]
1361
+ [ 0 10 0 0]
1362
+ [ 0 0 38 0]
1363
+ [ 0 0 0 190]
1364
+
1365
+ sage: R.<a,b> = QQ[]; Q.<i,j,k> = QuaternionAlgebra(Frac(R),a,b)
1366
+ sage: Q.inner_product_matrix()
1367
+ [ 2 0 0 0]
1368
+ [ 0 -2*a 0 0]
1369
+ [ 0 0 -2*b 0]
1370
+ [ 0 0 0 2*a*b]
1371
+ """
1372
+ a, b = self._a, self._b
1373
+ return diagonal_matrix(self.base_ring(), [2, -2*a, -2*b, 2*a*b])
1374
+
1375
+ def is_definite(self):
1376
+ r"""
1377
+ Check whether this quaternion algebra is definite.
1378
+
1379
+ A quaternion algebra over `\QQ` is definite if it ramifies at the
1380
+ unique real place of `\QQ`, which happens if and only if both of
1381
+ its invariants are negative (see Exercise 2.4(c) in [Voi2021]_).
1382
+
1383
+ EXAMPLES::
1384
+
1385
+ sage: QuaternionAlgebra(QQ,-5,-2).is_definite()
1386
+ True
1387
+ sage: QuaternionAlgebra(1).is_definite()
1388
+ False
1389
+
1390
+ The method does not make sense over an arbitrary base ring::
1391
+
1392
+ sage: QuaternionAlgebra(RR(2.), 1).is_definite()
1393
+ Traceback (most recent call last):
1394
+ ...
1395
+ ValueError: base field must be rational numbers
1396
+ """
1397
+ if not isinstance(self.base_ring(), RationalField):
1398
+ raise ValueError("base field must be rational numbers")
1399
+ a, b = self.invariants()
1400
+ return a < 0 and b < 0
1401
+
1402
+ def is_totally_definite(self):
1403
+ """
1404
+ Check whether this quaternion algebra is totally definite.
1405
+
1406
+ A quaternion algebra defined over a number field is
1407
+ totally definite if it ramifies at all Archimedean
1408
+ places of its base field. In particular, the base number
1409
+ field has to be totally real (see 14.5.8 in [Voi2021]_).
1410
+
1411
+ EXAMPLES::
1412
+
1413
+ sage: QuaternionAlgebra(QQ, -5, -2).is_totally_definite()
1414
+ True
1415
+
1416
+ sage: K = QuadraticField(3)
1417
+ sage: QuaternionAlgebra(K, -1, -1).is_totally_definite()
1418
+ True
1419
+
1420
+ We can also use number field elements as invariants::
1421
+
1422
+ sage: x = polygen(ZZ, 'x')
1423
+ sage: F.<a> = NumberField(x^2 - x - 1)
1424
+ sage: QuaternionAlgebra(F, 2*a, F(-1)).is_totally_definite()
1425
+ False
1426
+
1427
+ The method does not make sense over an arbitrary base ring::
1428
+
1429
+ sage: QuaternionAlgebra(RR(2.), 1).is_totally_definite()
1430
+ Traceback (most recent call last):
1431
+ ...
1432
+ ValueError: base field must be rational numbers or a number field
1433
+ """
1434
+ F = self.base_ring()
1435
+ if isinstance(F, RationalField):
1436
+ return self.is_definite()
1437
+
1438
+ if F not in NumberFields():
1439
+ raise ValueError("base field must be rational numbers or a number field")
1440
+
1441
+ from sage.rings.qqbar import AA
1442
+
1443
+ # Since we need the list of real embeddings of the number field (instead
1444
+ # of just the number of them), we avoid a call of the `is_totally_real()`-
1445
+ # method by directly comparing the embedding list's length to the degree
1446
+ E = F.embeddings(AA)
1447
+ return len(E) == F.degree() and all(F.hilbert_symbol(self._a, self._b, e) == -1
1448
+ for e in E)
1449
+
1450
+ @cached_method
1451
+ def ramified_places(self, inf=True):
1452
+ r"""
1453
+ Return the places of the base number field at which this
1454
+ quaternion algebra ramifies.
1455
+
1456
+ INPUT:
1457
+
1458
+ - ``inf`` -- bool (default: ``True``)
1459
+
1460
+ OUTPUT:
1461
+
1462
+ The non-Archimedean (AKA finite) places at which this quaternion
1463
+ algebra ramifies, given as
1464
+
1465
+ - elements of `\ZZ` (sorted small to large) if the base field is `\QQ`,
1466
+
1467
+ - integral fractional ideals of the base number field, otherwise.
1468
+
1469
+ Additionally, if ``inf`` is set to ``True``, then the Archimedean
1470
+ (AKA infinite) places at which the quaternion algebra ramifies are
1471
+ also returned, given as
1472
+
1473
+ - the embeddings of `\QQ` into `\RR` if the base field is `\QQ`, or
1474
+
1475
+ - the embeddings of the base number field into the Algebraic Real Field.
1476
+
1477
+ .. NOTE::
1478
+
1479
+ Any Archimedean place at which a quaternion algebra ramifies
1480
+ has to be real (see 14.5.8 in [Voi2021]_).
1481
+
1482
+ EXAMPLES::
1483
+
1484
+ sage: QuaternionAlgebra(210,-22).ramified_places()
1485
+ ([2, 3, 5, 7], [])
1486
+
1487
+ For a definite quaternion algebra we get ramification at the
1488
+ unique infinite place of `\QQ`::
1489
+
1490
+ sage: QuaternionAlgebra(-1, -1).ramified_places()
1491
+ ([2],
1492
+ [Ring morphism:
1493
+ From: Rational Field
1494
+ To: Real Field with 53 bits of precision
1495
+ Defn: 1 |--> 1.00000000000000])
1496
+
1497
+ Extending the base field can resolve all ramification::
1498
+
1499
+ sage: F = QuadraticField(-1)
1500
+ sage: QuaternionAlgebra(F, -1, -1).ramified_places()
1501
+ ([], [])
1502
+
1503
+ Extending the base field can also resolve all ramification at finite
1504
+ places while still leaving some ramification at infinite places::
1505
+
1506
+ sage: K = QuadraticField(3)
1507
+ sage: QuaternionAlgebra(K, -1, -1).ramified_places()
1508
+ ([],
1509
+ [Ring morphism:
1510
+ From: Number Field in a with defining polynomial x^2 - 3 with a = 1.732050807568878?
1511
+ To: Algebraic Real Field
1512
+ Defn: a |--> -1.732050807568878?,
1513
+ Ring morphism:
1514
+ From: Number Field in a with defining polynomial x^2 - 3 with a = 1.732050807568878?
1515
+ To: Algebraic Real Field
1516
+ Defn: a |--> 1.732050807568878?])
1517
+
1518
+ Extending the base field can also get rid of ramification at infinite
1519
+ places while still leaving some ramification at finite places::
1520
+
1521
+ sage: L = QuadraticField(-15)
1522
+ sage: QuaternionAlgebra(L, -1, -1).ramified_places()
1523
+ ([Fractional ideal (2, 1/2*a + 1/2), Fractional ideal (2, 1/2*a - 1/2)], [])
1524
+
1525
+ We can use number field elements as invariants as well::
1526
+
1527
+ sage: x = polygen(ZZ, 'x')
1528
+ sage: F.<a> = NumberField(x^2 - x - 1)
1529
+ sage: QuaternionAlgebra(F, 2*a, F(-1)).ramified_places()
1530
+ ([Fractional ideal (2)],
1531
+ [Ring morphism:
1532
+ From: Number Field in a with defining polynomial x^2 - x - 1
1533
+ To: Algebraic Real Field
1534
+ Defn: a |--> -0.618033988749895?])
1535
+
1536
+ The method does not make sense over an arbitrary base ring::
1537
+
1538
+ sage: QuaternionAlgebra(RR(2.),1).ramified_places()
1539
+ Traceback (most recent call last):
1540
+ ...
1541
+ ValueError: base field must be rational numbers or a number field
1542
+ """
1543
+ if not isinstance(inf, bool):
1544
+ raise TypeError("inf must be a truth value")
1545
+
1546
+ F = self.base_ring()
1547
+ a = self._a
1548
+ b = self._b
1549
+
1550
+ # The initial choice of primes (for the base field QQ) respectively
1551
+ # of prime ideals (in the number field case) to check ramification
1552
+ # for is based on 12.4.12(a) in [Voi2021]_.
1553
+
1554
+ # For efficiency (and to not convert QQ into a number field manually),
1555
+ # we handle the case F = QQ first
1556
+ if isinstance(F, RationalField):
1557
+ ram_fin = sorted([p for p in set([2]).union(
1558
+ prime_divisors(a.numerator()), prime_divisors(a.denominator()),
1559
+ prime_divisors(b.numerator()), prime_divisors(b.denominator()))
1560
+ if hilbert_symbol(a, b, p) == -1])
1561
+
1562
+ if not inf:
1563
+ return ram_fin
1564
+
1565
+ # The given quaternion algebra ramifies at the unique infinite place
1566
+ # of QQ, by definition, if and only if it is definite
1567
+ if self.is_definite():
1568
+ return ram_fin, QQ.places()
1569
+
1570
+ return ram_fin, []
1571
+
1572
+ # At this point F needs to be a number field
1573
+ # Note: Support for global function fields will be added in a future update
1574
+ if F not in NumberFields():
1575
+ raise ValueError("base field must be rational numbers or a number field")
1576
+
1577
+ # Over the number field F, first compute the finite ramified places
1578
+ ram_fin = [p for p in set(F.primes_above(2)).union(F.primes_above(a),
1579
+ F.primes_above(b)) if F.hilbert_symbol(a, b, p) == -1]
1580
+
1581
+ if not inf:
1582
+ return ram_fin
1583
+
1584
+ from sage.rings.qqbar import AA
1585
+
1586
+ # At this point the infinite ramified places also need to be computed
1587
+ return ram_fin, [e for e in F.embeddings(AA) if F.hilbert_symbol(a, b, e) == -1]
1588
+
1589
+ @cached_method
1590
+ def ramified_primes(self):
1591
+ r"""
1592
+ Return the (finite) primes of the base number field at
1593
+ which this quaternion algebra ramifies.
1594
+
1595
+ OUTPUT:
1596
+
1597
+ The list of finite primes at which this quaternion algebra ramifies,
1598
+ given as
1599
+
1600
+ - elements of `\ZZ` (sorted small to large) if the base field is `\QQ`,
1601
+
1602
+ - integral fractional ideals of the base number field, otherwise.
1603
+
1604
+ EXAMPLES::
1605
+
1606
+ sage: QuaternionAlgebra(-58, -69).ramified_primes()
1607
+ [3, 23, 29]
1608
+
1609
+ Under field extensions, the number of ramified primes can increase
1610
+ or decrease::
1611
+
1612
+ sage: K = QuadraticField(3)
1613
+ sage: L = QuadraticField(-15)
1614
+ sage: QuaternionAlgebra(-1, -1).ramified_primes()
1615
+ [2]
1616
+ sage: QuaternionAlgebra(K, -1, -1).ramified_primes()
1617
+ []
1618
+ sage: QuaternionAlgebra(L, -1, -1).ramified_primes()
1619
+ [Fractional ideal (2, 1/2*a + 1/2), Fractional ideal (2, 1/2*a - 1/2)]
1620
+
1621
+ We can also use number field elements as invariants::
1622
+
1623
+ sage: x = polygen(ZZ, 'x')
1624
+ sage: F.<a> = NumberField(x^2 - x - 1)
1625
+ sage: QuaternionAlgebra(F, 2*a, F(-1)).ramified_primes()
1626
+ [Fractional ideal (2)]
1627
+
1628
+ The method does not make sense over an arbitrary base ring::
1629
+
1630
+ sage: QuaternionAlgebra(RR(2.),1).ramified_primes()
1631
+ Traceback (most recent call last):
1632
+ ...
1633
+ ValueError: base field must be rational numbers or a number field
1634
+ """
1635
+ return self.ramified_places(inf=False)
1636
+
1637
+ @cached_method
1638
+ def discriminant(self):
1639
+ r"""
1640
+ Return the discriminant of this quaternion algebra.
1641
+
1642
+ The discriminant of a quaternion algebra over a number field is the
1643
+ product of the finite places at which the algebra ramifies.
1644
+
1645
+ OUTPUT:
1646
+
1647
+ The discriminant of this quaternion algebra, given as
1648
+
1649
+ - an element of `\ZZ` if the algebra is defined over `\QQ`,
1650
+
1651
+ - an integral fractional ideal of the base number field, otherwise.
1652
+
1653
+ EXAMPLES::
1654
+
1655
+ sage: QuaternionAlgebra(210, -22).discriminant()
1656
+ 210
1657
+ sage: QuaternionAlgebra(19).discriminant()
1658
+ 19
1659
+ sage: QuaternionAlgebra(-1, -1).discriminant()
1660
+ 2
1661
+
1662
+ Some examples over number fields::
1663
+
1664
+ sage: K = QuadraticField(3)
1665
+ sage: L = QuadraticField(-15)
1666
+ sage: QuaternionAlgebra(K, -1, -1).discriminant()
1667
+ Fractional ideal (1)
1668
+ sage: QuaternionAlgebra(L, -1, -1).discriminant()
1669
+ Fractional ideal (2)
1670
+
1671
+ We can also use number field elements as invariants::
1672
+
1673
+ sage: x = polygen(ZZ, 'x')
1674
+ sage: F.<a> = NumberField(x^2 - x - 1)
1675
+ sage: QuaternionAlgebra(F, 2*a, F(-1)).discriminant()
1676
+ Fractional ideal (2)
1677
+
1678
+ The method does not make sense over an arbitrary base ring::
1679
+
1680
+ sage: QuaternionAlgebra(RR(2.),1).discriminant()
1681
+ Traceback (most recent call last):
1682
+ ...
1683
+ ValueError: base field must be rational numbers or a number field
1684
+ """
1685
+ F = self.base_ring()
1686
+ if isinstance(F, RationalField):
1687
+ return ZZ.prod(self.ramified_places(inf=False))
1688
+
1689
+ return F.ideal(F.prod(self.ramified_places(inf=False)))
1690
+
1691
+ def is_isomorphic(self, A) -> bool:
1692
+ r"""
1693
+ Check whether this quaternion algebra is isomorphic to ``A``.
1694
+
1695
+ Currently only implemented for quaternion algebras defined over
1696
+ a number field; based on Main Theorem 14.6.1 in [Voi2021]_,
1697
+ noting that `\QQ` has a unique infinite place.
1698
+
1699
+ INPUT:
1700
+
1701
+ - ``A`` -- a quaternion algebra defined over a number field
1702
+
1703
+ EXAMPLES::
1704
+
1705
+ sage: B = QuaternionAlgebra(-46, -87)
1706
+ sage: A = QuaternionAlgebra(-58, -69)
1707
+ sage: A == B
1708
+ False
1709
+ sage: B.is_isomorphic(A)
1710
+ True
1711
+
1712
+ Checking ramification at both finite and infinite places, the method
1713
+ correctly distinguishes isomorphism classes of quaternion algebras
1714
+ that the discriminant can not distinguish::
1715
+
1716
+ sage: K = QuadraticField(3)
1717
+ sage: A = QuaternionAlgebra(K, -1, -1)
1718
+ sage: B = QuaternionAlgebra(K, 1, -1)
1719
+ sage: A.discriminant() == B.discriminant()
1720
+ True
1721
+ sage: B.is_isomorphic(A)
1722
+ False
1723
+ """
1724
+ if not isinstance(A, QuaternionAlgebra_ab):
1725
+ raise TypeError("A must be a quaternion algebra of the form (a,b)_K")
1726
+
1727
+ F = self.base_ring()
1728
+ if F is not A.base_ring():
1729
+ raise ValueError("both quaternion algebras must be defined over the same ring")
1730
+
1731
+ if isinstance(F, RationalField):
1732
+ return self.ramified_places(inf=False) == A.ramified_places(inf=False)
1733
+
1734
+ try:
1735
+ ram_self = self.ramified_places(inf=True)
1736
+ ram_A = A.ramified_places(inf=True)
1737
+ return set(ram_self[0]) == set(ram_A[0]) and ram_self[1] == ram_A[1]
1738
+ except ValueError:
1739
+ raise NotImplementedError("base field must be rational numbers or a number field")
1740
+
1741
+ def _magma_init_(self, magma):
1742
+ """
1743
+ Return Magma version of this quaternion algebra.
1744
+
1745
+ EXAMPLES::
1746
+
1747
+ sage: Q = QuaternionAlgebra(-1,-1); Q
1748
+ Quaternion Algebra (-1, -1) with base ring Rational Field
1749
+ sage: Q._magma_init_(magma) # optional - magma
1750
+ 'QuaternionAlgebra(_sage_[...],-1/1,-1/1)'
1751
+ sage: A = magma(Q); A # optional - magma
1752
+ Quaternion Algebra with base ring Rational Field, defined by i^2 = -1, j^2 = -1
1753
+ sage: A.RamifiedPlaces() # optional - magma
1754
+ [
1755
+ Ideal of Integer Ring generated by 2
1756
+ ]
1757
+
1758
+ A more complicated example involving a quaternion algebra over a number field::
1759
+
1760
+ sage: K.<a> = QQ[sqrt(2)]; Q = QuaternionAlgebra(K,-1,a); Q # needs sage.symbolic
1761
+ Quaternion Algebra (-1, sqrt2) with base ring Number Field in sqrt2
1762
+ with defining polynomial x^2 - 2 with sqrt2 = 1.414213562373095?
1763
+ sage: magma(Q) # optional - magma, needs sage.symbolic
1764
+ Quaternion Algebra with base ring Number Field with defining polynomial
1765
+ x^2 - 2 over the Rational Field, defined by i^2 = -1, j^2 = sqrt2
1766
+ sage: Q._magma_init_(magma) # optional - magma, needs sage.symbolic
1767
+ 'QuaternionAlgebra(_sage_[...],(_sage_[...]![-1, 0]),(_sage_[...]![0, 1]))'
1768
+ """
1769
+ R = magma(self.base_ring())
1770
+ return 'QuaternionAlgebra(%s,%s,%s)' % (R.name(),
1771
+ self._a._magma_init_(magma),
1772
+ self._b._magma_init_(magma))
1773
+
1774
+ def quaternion_order(self, basis, check=True):
1775
+ """
1776
+ Return the order of this quaternion order with given basis.
1777
+
1778
+ INPUT:
1779
+
1780
+ - ``basis`` -- list of 4 elements of ``self``
1781
+ - ``check`` -- boolean (default: ``True``)
1782
+
1783
+ EXAMPLES::
1784
+
1785
+ sage: Q.<i,j,k> = QuaternionAlgebra(-11,-1)
1786
+ sage: Q.quaternion_order([1,i,j,k])
1787
+ Order of Quaternion Algebra (-11, -1) with base ring Rational Field
1788
+ with basis (1, i, j, k)
1789
+
1790
+ We test out ``check=False``::
1791
+
1792
+ sage: Q.quaternion_order([1,i,j,k], check=False)
1793
+ Order of Quaternion Algebra (-11, -1) with base ring Rational Field
1794
+ with basis (1, i, j, k)
1795
+ sage: Q.quaternion_order([i,j,k], check=False)
1796
+ Order of Quaternion Algebra (-11, -1) with base ring Rational Field
1797
+ with basis (i, j, k)
1798
+ """
1799
+ return QuaternionOrder(self, basis, check=check)
1800
+
1801
+ def ideal(self, gens, left_order=None, right_order=None, check=True, **kwds):
1802
+ r"""
1803
+ Return the quaternion ideal with given gens over `\ZZ`.
1804
+
1805
+ Neither a left or right order structure need be specified.
1806
+
1807
+ INPUT:
1808
+
1809
+ - ``gens`` -- list of elements of this quaternion order
1810
+
1811
+ - ``check`` -- boolean (default: ``True``)
1812
+
1813
+ - ``left_order`` -- a quaternion order or ``None``
1814
+
1815
+ - ``right_order`` -- a quaternion order or ``None``
1816
+
1817
+ EXAMPLES::
1818
+
1819
+ sage: R = QuaternionAlgebra(-11,-1)
1820
+ sage: R.ideal([2*a for a in R.basis()])
1821
+ Fractional ideal (2, 2*i, 2*j, 2*k)
1822
+ """
1823
+ gens = [self(g) for g in gens] # coerce integers etc. into quaternions
1824
+ if self.base_ring() == QQ:
1825
+ return QuaternionFractionalIdeal_rational(self, gens, left_order=left_order, right_order=right_order, check=check)
1826
+ else:
1827
+ raise NotImplementedError("ideal only implemented for quaternion algebras over QQ")
1828
+
1829
+ @cached_method
1830
+ def modp_splitting_data(self, p):
1831
+ r"""
1832
+ Return mod `p` splitting data for this quaternion algebra at
1833
+ the unramified prime `p`.
1834
+
1835
+ This is `2\times 2`
1836
+ matrices `I`, `J`, `K` over the finite field `\GF{p}` such that if
1837
+ the quaternion algebra has generators `i, j, k`, then `I^2 =
1838
+ i^2`, `J^2 = j^2`, `IJ=K` and `IJ=-JI`.
1839
+
1840
+ .. NOTE::
1841
+
1842
+ Currently only implemented when `p` is odd and the base
1843
+ ring is `\QQ`.
1844
+
1845
+ INPUT:
1846
+
1847
+ - ``p`` -- unramified odd prime
1848
+
1849
+ OUTPUT: 2-tuple of matrices over finite field
1850
+
1851
+ EXAMPLES::
1852
+
1853
+ sage: Q = QuaternionAlgebra(-15, -19)
1854
+ sage: Q.modp_splitting_data(7)
1855
+ (
1856
+ [0 6] [6 1] [6 6]
1857
+ [1 0], [1 1], [6 1]
1858
+ )
1859
+ sage: Q.modp_splitting_data(next_prime(10^5))
1860
+ (
1861
+ [ 0 99988] [97311 4] [99999 59623]
1862
+ [ 1 0], [13334 2692], [97311 4]
1863
+ )
1864
+ sage: I,J,K = Q.modp_splitting_data(23)
1865
+ sage: I
1866
+ [0 8]
1867
+ [1 0]
1868
+ sage: I^2
1869
+ [8 0]
1870
+ [0 8]
1871
+ sage: J
1872
+ [19 2]
1873
+ [17 4]
1874
+ sage: J^2
1875
+ [4 0]
1876
+ [0 4]
1877
+ sage: I*J == -J*I
1878
+ True
1879
+ sage: I*J == K
1880
+ True
1881
+
1882
+ The following is a good test because of the asserts in the code::
1883
+
1884
+ sage: v = [Q.modp_splitting_data(p) for p in primes(20,1000)]
1885
+
1886
+ Proper error handling::
1887
+
1888
+ sage: Q.modp_splitting_data(5)
1889
+ Traceback (most recent call last):
1890
+ ...
1891
+ NotImplementedError: algorithm for computing local splittings
1892
+ not implemented in general (currently require the first invariant
1893
+ to be coprime to p)
1894
+
1895
+ sage: Q.modp_splitting_data(2)
1896
+ Traceback (most recent call last):
1897
+ ...
1898
+ NotImplementedError: p must be odd
1899
+ """
1900
+ if self.base_ring() != QQ:
1901
+ raise NotImplementedError("must be rational quaternion algebra")
1902
+ p = ZZ(p)
1903
+ if not p.is_prime():
1904
+ raise ValueError("p (=%s) must be prime" % p)
1905
+ if p == 2:
1906
+ raise NotImplementedError("p must be odd")
1907
+ if self.discriminant() % p == 0:
1908
+ raise ValueError("p (=%s) must be an unramified prime" % p)
1909
+
1910
+ i, j, _ = self.gens()
1911
+ F = GF(p)
1912
+ i2 = F(i * i)
1913
+ j2 = F(j * j)
1914
+
1915
+ M = MatrixSpace(F, 2)
1916
+ I = M([0, i2, 1, 0])
1917
+ if i2 == 0:
1918
+ raise NotImplementedError("algorithm for computing local splittings not implemented in general (currently require the first invariant to be coprime to p)")
1919
+ i2inv = ~i2
1920
+ a = None
1921
+ for b in F:
1922
+ if not b:
1923
+ continue
1924
+ c = j2 + i2inv * b*b
1925
+ if c.is_square():
1926
+ a = -c.sqrt()
1927
+ break
1928
+
1929
+ if a is None:
1930
+ # do a fallback search, maybe needed in char 3 sometimes.
1931
+ for J in M:
1932
+ K = I*J
1933
+ if J*J == j2 and K == -J*I:
1934
+ return I, J, K
1935
+
1936
+ J = M([a, b, (j2 - a * a) / b, -a])
1937
+ K = I * J
1938
+ assert K == -J * I, "bug in that I,J don't skew commute"
1939
+ return I, J, K
1940
+
1941
+ def modp_splitting_map(self, p):
1942
+ r"""
1943
+ Return Python map from the (`p`-integral) quaternion algebra to
1944
+ the set of `2\times 2` matrices over `\GF{p}`.
1945
+
1946
+ INPUT:
1947
+
1948
+ - ``p`` -- prime number
1949
+
1950
+ EXAMPLES::
1951
+
1952
+ sage: Q.<i,j,k> = QuaternionAlgebra(-1, -7)
1953
+ sage: f = Q.modp_splitting_map(13)
1954
+ sage: a = 2+i-j+3*k; b = 7+2*i-4*j+k
1955
+ sage: f(a*b)
1956
+ [12 3]
1957
+ [10 5]
1958
+ sage: f(a)*f(b)
1959
+ [12 3]
1960
+ [10 5]
1961
+ """
1962
+ I, J, K = self.modp_splitting_data(p)
1963
+ F = I.base_ring()
1964
+
1965
+ def phi(q):
1966
+ v = [F(a) for a in q.coefficient_tuple()]
1967
+ return v[0] + I*v[1] + J*v[2] + K*v[3]
1968
+ return phi
1969
+
1970
+
1971
+ ############################################################
1972
+ # Unpickling
1973
+ ############################################################
1974
+ def unpickle_QuaternionAlgebra_v0(*key):
1975
+ """
1976
+ The `0`-th version of pickling for quaternion algebras.
1977
+
1978
+ EXAMPLES::
1979
+
1980
+ sage: Q = QuaternionAlgebra(-5,-19)
1981
+ sage: t = (QQ, -5, -19, ('i', 'j', 'k'))
1982
+ sage: sage.algebras.quatalg.quaternion_algebra.unpickle_QuaternionAlgebra_v0(*t)
1983
+ Quaternion Algebra (-5, -19) with base ring Rational Field
1984
+ sage: loads(dumps(Q)) == Q
1985
+ True
1986
+ sage: loads(dumps(Q)) is Q
1987
+ True
1988
+ """
1989
+ return QuaternionAlgebra(*key)
1990
+
1991
+
1992
+ @richcmp_method
1993
+ class QuaternionOrder(Parent):
1994
+ """
1995
+ An order in a quaternion algebra.
1996
+
1997
+ EXAMPLES::
1998
+
1999
+ sage: QuaternionAlgebra(-1,-7).maximal_order()
2000
+ Order of Quaternion Algebra (-1, -7) with base ring Rational Field with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)
2001
+ sage: type(QuaternionAlgebra(-1,-7).maximal_order())
2002
+ <class 'sage.algebras.quatalg.quaternion_algebra.QuaternionOrder_with_category'>
2003
+ """
2004
+ def __init__(self, A, basis, check=True):
2005
+ """
2006
+ INPUT:
2007
+
2008
+ - ``A`` -- a quaternion algebra
2009
+ - ``basis`` -- list of 4 integral quaternions in ``A``
2010
+ - ``check`` -- whether to do type and other consistency checks
2011
+
2012
+ .. WARNING::
2013
+
2014
+ Currently most methods silently assume that the ``A.base_ring()``
2015
+ is ``QQ``.
2016
+
2017
+ EXAMPLES::
2018
+
2019
+ sage: A.<i,j,k> = QuaternionAlgebra(-3,-5)
2020
+ sage: sage.algebras.quatalg.quaternion_algebra.QuaternionOrder(A, [1,i,j,k])
2021
+ Order of Quaternion Algebra (-3, -5) with base ring Rational Field with basis (1, i, j, k)
2022
+ sage: R = sage.algebras.quatalg.quaternion_algebra.QuaternionOrder(A, [1,2*i,2*j,2*k]); R
2023
+ Order of Quaternion Algebra (-3, -5) with base ring Rational Field with basis (1, 2*i, 2*j, 2*k)
2024
+ sage: type(R)
2025
+ <class 'sage.algebras.quatalg.quaternion_algebra.QuaternionOrder_with_category'>
2026
+
2027
+ Over QQ and number fields it is checked whether the given
2028
+ basis actually gives an order (as a module over the maximal order)::
2029
+
2030
+ sage: A.<i,j,k> = QuaternionAlgebra(-1,-1)
2031
+ sage: A.quaternion_order([1,i,j,i-j])
2032
+ Traceback (most recent call last):
2033
+ ...
2034
+ ValueError: basis must have rank 4
2035
+ sage: A.quaternion_order([2,i,j,k])
2036
+ Traceback (most recent call last):
2037
+ ...
2038
+ ValueError: lattice must contain 1
2039
+ sage: A.quaternion_order([1,i/2,j/2,k/2])
2040
+ Traceback (most recent call last):
2041
+ ...
2042
+ ValueError: given lattice must be a ring
2043
+
2044
+ sage: K = QuadraticField(10)
2045
+ sage: A.<i,j,k> = QuaternionAlgebra(K,-1,-1)
2046
+ sage: A.quaternion_order([1,i,j,k])
2047
+ Order of Quaternion Algebra (-1, -1) with base ring Number Field in a
2048
+ with defining polynomial x^2 - 10 with a = 3.162277660168380? with basis (1, i, j, k)
2049
+ sage: A.quaternion_order([1,i/2,j,k])
2050
+ Traceback (most recent call last):
2051
+ ...
2052
+ ValueError: given lattice must be a ring
2053
+
2054
+ TESTS::
2055
+
2056
+ sage: TestSuite(R).run()
2057
+ """
2058
+ if check:
2059
+ # right data type
2060
+ if not isinstance(basis, (list, tuple)):
2061
+ raise TypeError("basis must be a list or tuple")
2062
+ # right length
2063
+ if len(basis) != 4:
2064
+ raise ValueError("basis must have length 4")
2065
+ # coerce to common parent
2066
+ basis = tuple([A(x) for x in basis])
2067
+
2068
+ # has rank 4
2069
+ V = A.base_ring()**4
2070
+ if V.span([V(x.coefficient_tuple()) for x in basis]).dimension() != 4:
2071
+ raise ValueError("basis must have rank 4")
2072
+
2073
+ # The additional checks will work over QQ and over number fields,
2074
+ # but we can't actually do much with an order defined over a number
2075
+ # field
2076
+
2077
+ if A.base_ring() == QQ: # fast code over QQ
2078
+ M = matrix(QQ, 4, 4, [x.coefficient_tuple() for x in basis])
2079
+ v = M.solve_left(V([1, 0, 0, 0]))
2080
+
2081
+ if v.denominator() != 1:
2082
+ raise ValueError("lattice must contain 1")
2083
+
2084
+ # check if multiplicatively closed
2085
+ M1 = basis_for_quaternion_lattice(basis)
2086
+ M2 = basis_for_quaternion_lattice(list(basis) + [x * y for x in basis for y in basis])
2087
+ if M1 != M2:
2088
+ raise ValueError("given lattice must be a ring")
2089
+
2090
+ if A.base_ring() != QQ: # slow code over number fields (should eventually use PARI's nfhnf)
2091
+ O = None
2092
+ try:
2093
+ O = A.base_ring().maximal_order()
2094
+ except AttributeError:
2095
+ pass
2096
+
2097
+ if O:
2098
+ M = matrix(A.base_ring(), 4, 4, [x.coefficient_tuple()
2099
+ for x in basis])
2100
+ v = M.solve_left(V([1, 0, 0, 0]))
2101
+
2102
+ if any(a not in O for a in v):
2103
+ raise ValueError("lattice must contain 1")
2104
+
2105
+ # check if multiplicatively closed
2106
+ Y = matrix(QQ, 16, 4, [(x*y).coefficient_tuple()
2107
+ for x in basis for y in basis])
2108
+ X = M.solve_left(Y)
2109
+ if any(a not in O for x in X for a in x):
2110
+ raise ValueError("given lattice must be a ring")
2111
+
2112
+ self.__basis = tuple(basis)
2113
+ self.__quaternion_algebra = A
2114
+ Parent.__init__(self, base=ZZ, facade=(A,),
2115
+ category=Algebras(ZZ).Facade().FiniteDimensional())
2116
+
2117
+ def _element_constructor_(self, x):
2118
+ """
2119
+ Construct an element of this quaternion order from ``x``,
2120
+ or throw an error if ``x`` is not contained in the order.
2121
+
2122
+ EXAMPLES::
2123
+
2124
+ sage: Q.<i,j,k> = QuaternionAlgebra(-1,-19)
2125
+ sage: O = Q.quaternion_order([1,i,j,k])
2126
+ sage: O(1+i)
2127
+ 1 + i
2128
+ sage: O(1/2)
2129
+ Traceback (most recent call last):
2130
+ ...
2131
+ TypeError: 1/2 does not lie in Order of Quaternion Algebra (-1, -19)
2132
+ with base ring Rational Field with basis (1, i, j, k)
2133
+
2134
+ TESTS:
2135
+
2136
+ Test for :issue:`32364`::
2137
+
2138
+ sage: 1/5 in O
2139
+ False
2140
+ sage: j/2 in O
2141
+ False
2142
+ """
2143
+ y = self.quaternion_algebra()(x)
2144
+ if y not in self.unit_ideal():
2145
+ raise TypeError(f'{x!r} does not lie in {self!r}')
2146
+ return y
2147
+
2148
+ def one(self):
2149
+ """
2150
+ Return the multiplicative unit of this quaternion order.
2151
+
2152
+ EXAMPLES::
2153
+
2154
+ sage: QuaternionAlgebra(-1,-7).maximal_order().one()
2155
+ 1
2156
+ """
2157
+ return self.quaternion_algebra().one()
2158
+
2159
+ def gens(self) -> tuple:
2160
+ """
2161
+ Return generators for ``self``.
2162
+
2163
+ EXAMPLES::
2164
+
2165
+ sage: QuaternionAlgebra(-1,-7).maximal_order().gens()
2166
+ (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)
2167
+ """
2168
+ return self.__basis
2169
+
2170
+ def ngens(self):
2171
+ """
2172
+ Return the number of generators (which is 4).
2173
+
2174
+ EXAMPLES::
2175
+
2176
+ sage: QuaternionAlgebra(-1,-7).maximal_order().ngens()
2177
+ 4
2178
+ """
2179
+ return 4
2180
+
2181
+ def gen(self, n):
2182
+ """
2183
+ Return the `n`-th generator.
2184
+
2185
+ INPUT:
2186
+
2187
+ - ``n`` -- integer between 0 and 3, inclusive
2188
+
2189
+ EXAMPLES::
2190
+
2191
+ sage: R = QuaternionAlgebra(-11,-1).maximal_order(); R
2192
+ Order of Quaternion Algebra (-11, -1) with base ring Rational Field
2193
+ with basis (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
2194
+ sage: R.gen(0)
2195
+ 1/2 + 1/2*i
2196
+ sage: R.gen(1)
2197
+ 1/2*j - 1/2*k
2198
+ sage: R.gen(2)
2199
+ i
2200
+ sage: R.gen(3)
2201
+ -k
2202
+ """
2203
+ return self.__basis[n]
2204
+
2205
+ def __richcmp__(self, other, op):
2206
+ """
2207
+ Compare this quaternion order to ``other``.
2208
+
2209
+ EXAMPLES::
2210
+
2211
+ sage: R = QuaternionAlgebra(-1, -11).maximal_order()
2212
+ sage: R == R # indirect doctest
2213
+ True
2214
+ sage: R == 5
2215
+ False
2216
+ sage: R == QuaternionAlgebra(-1, -7).maximal_order()
2217
+ False
2218
+
2219
+ Orders can be equal even if they are defined by different
2220
+ bases (see :issue:`32245`)::
2221
+
2222
+ sage: Q.<i,j,k> = QuaternionAlgebra(-1, -19)
2223
+ sage: Q.quaternion_order([1,-i,k,j+i*7]) == Q.quaternion_order([1,i,j,k])
2224
+ True
2225
+
2226
+ TESTS::
2227
+
2228
+ sage: B = QuaternionAlgebra(-1, -11)
2229
+ sage: i,j,k = B.gens()
2230
+ sage: O = B.quaternion_order([1,i,j,k])
2231
+ sage: O == O
2232
+ True
2233
+ sage: R = B.quaternion_order([1,i,(i+j)/2,(1+k)/2])
2234
+ sage: O <= R # indirect doctest
2235
+ True
2236
+ sage: O >= R
2237
+ False
2238
+ sage: O != R
2239
+ True
2240
+ sage: O == R
2241
+ False
2242
+ sage: O != O
2243
+ False
2244
+ sage: O < O
2245
+ False
2246
+ sage: O < R
2247
+ True
2248
+ sage: O <= O
2249
+ True
2250
+ sage: R >= R
2251
+ True
2252
+ """
2253
+ from sage.structure.richcmp import richcmp, op_NE
2254
+ if not isinstance(other, QuaternionOrder):
2255
+ return op == op_NE
2256
+ return richcmp(self.unit_ideal(), other.unit_ideal(), op)
2257
+
2258
+ def __hash__(self):
2259
+ """
2260
+ Compute the hash of ``self``.
2261
+
2262
+ EXAMPLES::
2263
+
2264
+ sage: h1 = hash(QuaternionAlgebra(-1,-7).maximal_order())
2265
+ sage: h2 = hash(QuaternionAlgebra(-1,-7).maximal_order())
2266
+ sage: h3 = hash(QuaternionAlgebra(-1,-5).maximal_order())
2267
+ sage: h1 == h2 and h1 != h3
2268
+ True
2269
+ """
2270
+ return hash((self.__quaternion_algebra, self.__basis))
2271
+
2272
+ def basis(self):
2273
+ """
2274
+ Return fix choice of basis for this quaternion order.
2275
+
2276
+ EXAMPLES::
2277
+
2278
+ sage: QuaternionAlgebra(-11,-1).maximal_order().basis()
2279
+ (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
2280
+ """
2281
+ return self.__basis
2282
+
2283
+ def quaternion_algebra(self):
2284
+ """
2285
+ Return ambient quaternion algebra that contains this quaternion order.
2286
+
2287
+ EXAMPLES::
2288
+
2289
+ sage: QuaternionAlgebra(-11,-1).maximal_order().quaternion_algebra()
2290
+ Quaternion Algebra (-11, -1) with base ring Rational Field
2291
+ """
2292
+ return self.__quaternion_algebra
2293
+
2294
+ def _repr_(self):
2295
+ """
2296
+ Return string representation of this order.
2297
+
2298
+ EXAMPLES::
2299
+
2300
+ sage: QuaternionAlgebra(-11,-1).maximal_order()._repr_()
2301
+ 'Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)'
2302
+ sage: QuaternionAlgebra(-11,-1).maximal_order()
2303
+ Order of Quaternion Algebra (-11, -1) with base ring Rational Field with basis (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
2304
+ """
2305
+ return 'Order of %s with basis %s' % (self.quaternion_algebra(), self.basis())
2306
+
2307
+ def random_element(self, *args, **kwds):
2308
+ """
2309
+ Return a random element of this order.
2310
+
2311
+ The args and kwds are passed to the random_element method of
2312
+ the integer ring, and we return an element of the form
2313
+
2314
+ .. MATH::
2315
+
2316
+ ae_1 + be_2 + ce_3 + de_4
2317
+
2318
+ where `e_1`, ..., `e_4` are the basis of this order and `a`,
2319
+ `b`, `c`, `d` are random integers.
2320
+
2321
+ EXAMPLES::
2322
+
2323
+ sage: QuaternionAlgebra(-11,-1).maximal_order().random_element() # random
2324
+ -4 - 4*i + j - k
2325
+ sage: QuaternionAlgebra(-11,-1).maximal_order().random_element(-10,10) # random
2326
+ -9/2 - 7/2*i - 7/2*j - 3/2*k
2327
+ """
2328
+ return sum(ZZ.random_element(*args, **kwds) * b for b in self.basis())
2329
+
2330
+ def intersection(self, other):
2331
+ """
2332
+ Return the intersection of this order with other.
2333
+
2334
+ INPUT:
2335
+
2336
+ - ``other`` -- a quaternion order in the same ambient quaternion algebra
2337
+
2338
+ OUTPUT: a quaternion order
2339
+
2340
+ EXAMPLES::
2341
+
2342
+ sage: R = QuaternionAlgebra(-11,-1).maximal_order()
2343
+ sage: R.intersection(R)
2344
+ Order of Quaternion Algebra (-11, -1) with base ring Rational Field
2345
+ with basis (1/2 + 1/2*i, i, 1/2*j + 1/2*k, k)
2346
+
2347
+ We intersect various orders in the quaternion algebra ramified at 11::
2348
+
2349
+ sage: # needs sage.schemes
2350
+ sage: B = BrandtModule(11,3)
2351
+ sage: R = B.maximal_order(); S = B.order_of_level_N()
2352
+ sage: R.intersection(S)
2353
+ Order of Quaternion Algebra (-1, -11) with base ring Rational Field
2354
+ with basis (1/2 + 1/2*j, 1/2*i + 5/2*k, j, 3*k)
2355
+ sage: R.intersection(S) == S
2356
+ True
2357
+ sage: B = BrandtModule(11,5)
2358
+ sage: T = B.order_of_level_N()
2359
+ sage: S.intersection(T)
2360
+ Order of Quaternion Algebra (-1, -11) with base ring Rational Field
2361
+ with basis (1/2 + 1/2*j, 1/2*i + 23/2*k, j, 15*k)
2362
+ """
2363
+ if not isinstance(other, QuaternionOrder):
2364
+ raise TypeError("other must be a QuaternionOrder")
2365
+
2366
+ A = self.quaternion_algebra()
2367
+ if other.quaternion_algebra() != A:
2368
+ raise ValueError("self and other must be in the same ambient quaternion algebra")
2369
+
2370
+ V = A.base_ring()**4
2371
+
2372
+ B = V.span([V(list(g)) for g in self.basis()], ZZ)
2373
+ C = V.span([V(list(g)) for g in other.basis()], ZZ)
2374
+
2375
+ # todo -- A(list(e)) could be A(e)
2376
+ return QuaternionOrder(A, [A(list(e)) for e in B.intersection(C).basis()])
2377
+
2378
+ @cached_method
2379
+ def free_module(self):
2380
+ r"""
2381
+ Return the free `\ZZ`-module that corresponds to this order
2382
+ inside the vector space corresponding to the ambient
2383
+ quaternion algebra.
2384
+
2385
+ OUTPUT: a free `\ZZ`-module of rank 4
2386
+
2387
+ EXAMPLES::
2388
+
2389
+ sage: R = QuaternionAlgebra(-11,-1).maximal_order()
2390
+ sage: R.basis()
2391
+ (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
2392
+ sage: R.free_module()
2393
+ Free module of degree 4 and rank 4 over Integer Ring
2394
+ Echelon basis matrix:
2395
+ [1/2 1/2 0 0]
2396
+ [ 0 1 0 0]
2397
+ [ 0 0 1/2 1/2]
2398
+ [ 0 0 0 1]
2399
+ """
2400
+ V = self.quaternion_algebra().base_ring()**4
2401
+ return V.span([V(list(g)) for g in self.basis()], ZZ)
2402
+
2403
+ def discriminant(self):
2404
+ r"""
2405
+ Return the discriminant of this order.
2406
+
2407
+ This is defined as
2408
+ `\sqrt{ det ( Tr(e_i \bar{e}_j ) ) }`, where `\{e_i\}` is the
2409
+ basis of the order.
2410
+
2411
+ OUTPUT: rational number
2412
+
2413
+ EXAMPLES::
2414
+
2415
+ sage: QuaternionAlgebra(-11,-1).maximal_order().discriminant()
2416
+ 11
2417
+
2418
+ sage: # needs sage.schemes
2419
+ sage: S = BrandtModule(11,5).order_of_level_N()
2420
+ sage: S.discriminant()
2421
+ 55
2422
+ sage: type(S.discriminant())
2423
+ <... 'sage.rings.rational.Rational'>
2424
+ """
2425
+ e = self.basis()
2426
+ L = [[x.pair(y) for y in e] for x in e]
2427
+ return matrix(QQ, 4, 4, L).determinant().sqrt()
2428
+
2429
+ def is_maximal(self) -> bool:
2430
+ r"""
2431
+ Check whether the order of ``self`` is maximal in the ambient quaternion algebra.
2432
+
2433
+ Only implemented for quaternion algebras over number fields; for reference,
2434
+ see Theorem 15.5.5 in [Voi2021]_.
2435
+
2436
+ EXAMPLES::
2437
+
2438
+ sage: p = 11
2439
+ sage: B = QuaternionAlgebra(QQ, -1, -p)
2440
+ sage: i, j, k = B.gens()
2441
+ sage: O0_basis = (1, i, (i+j)/2, (1+i*j)/2)
2442
+ sage: O0 = B.quaternion_order(O0_basis)
2443
+ sage: O0.is_maximal()
2444
+ True
2445
+ sage: O1 = B.quaternion_order([1, i, j, i*j])
2446
+ sage: O1.is_maximal()
2447
+ False
2448
+
2449
+ TESTS::
2450
+
2451
+ sage: B = QuaternionAlgebra(GF(13), -1, -11)
2452
+ sage: i, j, k = B.gens()
2453
+ sage: O0_basis = (1, i, j, k)
2454
+ sage: O0 = B.quaternion_order(O0_basis)
2455
+ sage: O0.is_maximal()
2456
+ Traceback (most recent call last):
2457
+ ...
2458
+ NotImplementedError: check for maximality is only implemented for quaternion algebras over number fields
2459
+ """
2460
+ if self.quaternion_algebra().base_ring() not in NumberFields():
2461
+ raise NotImplementedError("check for maximality is only implemented for quaternion algebras over number fields")
2462
+ return self.discriminant() == self.quaternion_algebra().discriminant()
2463
+
2464
+ def _left_ideal_basis(self, gens):
2465
+ """
2466
+ Return a basis for the left ideal of ``self`` with given generators.
2467
+
2468
+ INPUT:
2469
+
2470
+ - ``gens`` -- list of elements of ``self``
2471
+
2472
+ OUTPUT: list of four elements of ``self``
2473
+
2474
+ EXAMPLES::
2475
+
2476
+ sage: A.<i,j,k> = QuaternionAlgebra(-17, -3)
2477
+ sage: A.maximal_order()._left_ideal_basis([i + j, i - j, 2*k, A(3)])
2478
+ [1, i, 1/2 + 1/2*j, 1/2 + 1/2*i + 1/6*j + 1/6*k]
2479
+ sage: A.maximal_order()._left_ideal_basis([3*(i + j), 3*(i - j), 6*k, A(3)])
2480
+ [3, 3*i, 3/2 + 3/2*j, 3/2 + 3/2*i + 1/2*j + 1/2*k]
2481
+ """
2482
+ return basis_for_quaternion_lattice([b * g for b in self.basis() for g in gens])
2483
+
2484
+ def _right_order_from_ideal_basis(self, basis):
2485
+ """
2486
+ Given a basis for a left ideal `I`, return the right order in
2487
+ ``self`` of elements `x` such that `I x` is contained in `I`.
2488
+
2489
+ INPUT:
2490
+
2491
+ - ``basis`` -- basis for an ideal `I`
2492
+
2493
+ EXAMPLES:
2494
+
2495
+ sage: A.<i,j,k> = QuaternionAlgebra(17)
2496
+ sage: O = A.maximal_order()
2497
+ sage: basis = O._left_ideal_basis([1]); basis
2498
+ [1, 1/2 + 1/2*i, j, 1/3*i + 1/2*j + 1/6*k]
2499
+ sage: O._right_order_from_ideal_basis(basis)
2500
+ Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k)
2501
+
2502
+ sage: basis = O._left_ideal_basis([i*j - j]); basis
2503
+ [34, 17 + 17*i, 2*j, 17 + 17/3*i + j + 1/3*k]
2504
+ sage: O._right_order_from_ideal_basis(basis)
2505
+ Order of Quaternion Algebra (-3, -17) with base ring Rational Field with basis (1/2 + 1/6*i + 1/3*k, 1/3*i + 2/3*k, 1/2*j + 1/2*k, k)
2506
+ """
2507
+ # Compute matrix of multiplication by each element of the basis.
2508
+ B = self.basis()
2509
+ Z = self.quaternion_algebra()
2510
+ M = MatrixSpace(QQ, 4)
2511
+
2512
+ # I = matrix with rows the given basis for I
2513
+ I = M([list(f) for f in basis])
2514
+
2515
+ # psi = matrix of right multiplication on each basis element
2516
+ psi = [M([list(f * x) for x in Z.basis()]) for f in basis]
2517
+
2518
+ # invert them
2519
+ psi_inv = [x**(-1) for x in psi]
2520
+
2521
+ # apply the four inverses to I
2522
+ W = [I * x for x in psi_inv]
2523
+
2524
+ # The right order is the intersection of the row span of the W with the row span of B.
2525
+ X = M([list(b) for b in B]).row_module(ZZ)
2526
+ for A in W:
2527
+ X = X.intersection(A.row_module(ZZ))
2528
+ C = [Z(list(b)) for b in X.basis()]
2529
+ return Z.quaternion_order(C)
2530
+
2531
+ def left_ideal(self, gens, check=True, *, is_basis=False):
2532
+ r"""
2533
+ Return the left ideal of this order generated by the given generators.
2534
+
2535
+ INPUT:
2536
+
2537
+ - ``gens`` -- list of elements of this quaternion order
2538
+
2539
+ - ``check`` -- boolean (default: ``True``)
2540
+
2541
+ - ``is_basis`` -- boolean (default: ``False``); if ``True`` then
2542
+ ``gens`` must be a `\ZZ`-basis of the ideal
2543
+
2544
+ EXAMPLES::
2545
+
2546
+ sage: Q.<i,j,k> = QuaternionAlgebra(-11,-1)
2547
+ sage: R = Q.maximal_order()
2548
+ sage: R.left_ideal([a*2 for a in R.basis()], is_basis=True)
2549
+ Fractional ideal (1 + i, 2*i, j + k, 2*k)
2550
+ sage: R.left_ideal([a*(i+j) for a in R.basis()], is_basis=True)
2551
+ Fractional ideal (1/2 + 1/2*i + 1/2*j + 13/2*k, i + j, 6*j + 6*k, 12*k)
2552
+
2553
+ It is also possible to pass a generating set (rather than a basis),
2554
+ or a single generator::
2555
+
2556
+ sage: R.left_ideal([i+j])
2557
+ Fractional ideal (12, 6 + 6*i, i + j, 13/2 + 1/2*i + 1/2*j + 1/2*k)
2558
+ sage: R.left_ideal(i+j)
2559
+ Fractional ideal (12, 6 + 6*i, i + j, 13/2 + 1/2*i + 1/2*j + 1/2*k)
2560
+ sage: R.left_ideal([2, 1+j]) == R*2 + R*(1+j)
2561
+ True
2562
+ """
2563
+ if self.base_ring() is not ZZ:
2564
+ raise NotImplementedError("ideal only implemented for quaternion algebras over QQ")
2565
+ if is_basis:
2566
+ basis = gens
2567
+ else:
2568
+ if gens in self.quaternion_algebra():
2569
+ gens = [gens]
2570
+ basis = tuple(basis_for_quaternion_lattice([b * g for b in self.basis() for g in gens]))
2571
+ check = False
2572
+ return QuaternionFractionalIdeal_rational(self.quaternion_algebra(), basis, left_order=self, check=check)
2573
+
2574
+ def right_ideal(self, gens, check=True, *, is_basis=False):
2575
+ r"""
2576
+ Return the right ideal of this order generated by the given generators.
2577
+
2578
+ INPUT:
2579
+
2580
+ - ``gens`` -- list of elements of this quaternion order
2581
+
2582
+ - ``check`` -- boolean (default: ``True``)
2583
+
2584
+ - ``is_basis`` -- boolean (default: ``False``); if ``True`` then
2585
+ ``gens`` must be a `\ZZ`-basis of the ideal
2586
+
2587
+ EXAMPLES::
2588
+
2589
+ sage: Q.<i,j,k> = QuaternionAlgebra(-11,-1)
2590
+ sage: R = Q.maximal_order()
2591
+ sage: R.right_ideal([2*a for a in R.basis()], is_basis=True)
2592
+ Fractional ideal (1 + i, 2*i, j + k, 2*k)
2593
+ sage: R.right_ideal([(i+j)*a for a in R.basis()], is_basis=True)
2594
+ Fractional ideal (1/2 + 1/2*i + 1/2*j + 11/2*k, i + j, 6*j + 6*k, 12*k)
2595
+
2596
+ It is also possible to pass a generating set (rather than a basis),
2597
+ or a single generator::
2598
+
2599
+ sage: R.right_ideal([i+j])
2600
+ Fractional ideal (12, 6 + 6*i, i + j, 11/2 + 1/2*i + 1/2*j + 1/2*k)
2601
+ sage: R.right_ideal(i+j)
2602
+ Fractional ideal (12, 6 + 6*i, i + j, 11/2 + 1/2*i + 1/2*j + 1/2*k)
2603
+ sage: R.right_ideal([2, 1+j]) == 2*R + (1+j)*R
2604
+ True
2605
+ """
2606
+ if self.base_ring() is not ZZ:
2607
+ raise NotImplementedError("ideal only implemented for quaternion algebras over QQ")
2608
+ if is_basis:
2609
+ basis = gens
2610
+ else:
2611
+ if gens in self.quaternion_algebra():
2612
+ gens = [gens]
2613
+ basis = tuple(basis_for_quaternion_lattice([g * b for b in self.basis() for g in gens]))
2614
+ check = False
2615
+ return QuaternionFractionalIdeal_rational(self.quaternion_algebra(), basis, right_order=self, check=check)
2616
+
2617
+ @cached_method
2618
+ def unit_ideal(self):
2619
+ """
2620
+ Return the unit ideal in this quaternion order.
2621
+
2622
+ EXAMPLES::
2623
+
2624
+ sage: R = QuaternionAlgebra(-11,-1).maximal_order()
2625
+ sage: I = R.unit_ideal(); I
2626
+ Fractional ideal (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
2627
+ """
2628
+ if self.base_ring() is not ZZ:
2629
+ raise NotImplementedError("ideal only implemented for quaternion algebras over QQ")
2630
+ return QuaternionFractionalIdeal_rational(self.quaternion_algebra(), self.basis(), left_order=self, right_order=self, check=False)
2631
+
2632
+ def basis_matrix(self):
2633
+ r"""
2634
+ Return the basis matrix of this quaternion order, for the
2635
+ specific basis returned by :meth:`basis()`.
2636
+
2637
+ OUTPUT: matrix over `\QQ`
2638
+
2639
+ EXAMPLES::
2640
+
2641
+ sage: O = QuaternionAlgebra(-11,-1).maximal_order()
2642
+ sage: O.basis()
2643
+ (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
2644
+ sage: O.basis_matrix()
2645
+ [ 1/2 1/2 0 0]
2646
+ [ 0 0 1/2 -1/2]
2647
+ [ 0 1 0 0]
2648
+ [ 0 0 0 -1]
2649
+
2650
+ Note that the returned matrix is *not* necessarily the same as
2651
+ the basis matrix of the :meth:`unit_ideal()`::
2652
+
2653
+ sage: Q.<i,j,k> = QuaternionAlgebra(-1,-11)
2654
+ sage: O = Q.quaternion_order([j,i,-1,k])
2655
+ sage: O.basis_matrix()
2656
+ [ 0 0 1 0]
2657
+ [ 0 1 0 0]
2658
+ [-1 0 0 0]
2659
+ [ 0 0 0 1]
2660
+ sage: O.unit_ideal().basis_matrix()
2661
+ [1 0 0 0]
2662
+ [0 1 0 0]
2663
+ [0 0 1 0]
2664
+ [0 0 0 1]
2665
+ """
2666
+ return matrix(QQ, map(list, self.__basis))
2667
+
2668
+ def __mul__(self, other):
2669
+ """
2670
+ Every order equals its own unit ideal. Overload ideal multiplication
2671
+ and scaling to orders.
2672
+
2673
+ EXAMPLES::
2674
+
2675
+ sage: Q.<i,j,k> = QuaternionAlgebra(-1,-11)
2676
+ sage: O = Q.maximal_order()
2677
+ sage: I = O*j; I
2678
+ Fractional ideal (11, 11*i, 11/2 + 1/2*j, 11/2*i + 1/2*k)
2679
+ """
2680
+ return self.unit_ideal() * other
2681
+
2682
+ def __rmul__(self, other):
2683
+ return other * self.unit_ideal()
2684
+
2685
+ def __add__(self, other):
2686
+ """
2687
+ Every order equals its own unit ideal. Overload ideal addition
2688
+ to orders.
2689
+
2690
+ EXAMPLES::
2691
+
2692
+ sage: Q.<i,j,k> = QuaternionAlgebra(-1,-11)
2693
+ sage: O = Q.maximal_order()
2694
+ sage: I = O + O*((j-3)/5); I
2695
+ Fractional ideal (1/10 + 3/10*j, 1/10*i + 3/10*k, j, k)
2696
+ """
2697
+ return self.unit_ideal() + other
2698
+
2699
+ def quadratic_form(self):
2700
+ """
2701
+ Return the normalized quadratic form associated to this quaternion order.
2702
+
2703
+ OUTPUT: quadratic form
2704
+
2705
+ EXAMPLES::
2706
+
2707
+ sage: # needs sage.schemes
2708
+ sage: R = BrandtModule(11,13).order_of_level_N()
2709
+ sage: Q = R.quadratic_form(); Q
2710
+ Quadratic form in 4 variables over Rational Field with coefficients:
2711
+ [ 14 253 55 286 ]
2712
+ [ * 1455 506 3289 ]
2713
+ [ * * 55 572 ]
2714
+ [ * * * 1859 ]
2715
+ sage: Q.theta_series(10)
2716
+ 1 + 2*q + 2*q^4 + 4*q^6 + 4*q^8 + 2*q^9 + O(q^10)
2717
+ """
2718
+ return self.unit_ideal().quadratic_form()
2719
+
2720
+ def ternary_quadratic_form(self, include_basis=False):
2721
+ r"""
2722
+ Return the ternary quadratic form associated to this order.
2723
+
2724
+ INPUT:
2725
+
2726
+ - ``include_basis`` -- boolean (default: ``False``); if ``True`` also
2727
+ return a basis for the dimension 3 subspace `G`
2728
+
2729
+ OUTPUT: QuadraticForm
2730
+
2731
+ - optional basis for dimension 3 subspace
2732
+
2733
+ This function computes the positive definition quadratic form
2734
+ obtained by letting G be the trace zero subspace of `\ZZ` +
2735
+ 2* ``self``, which has rank 3, and restricting the pairing
2736
+ :meth:`QuaternionAlgebraElement_abstract.pair`::
2737
+
2738
+ (x,y) = (x.conjugate()*y).reduced_trace()
2739
+
2740
+ to `G`.
2741
+
2742
+ APPLICATIONS: Ternary quadratic forms associated to an order
2743
+ in a rational quaternion algebra are useful in computing with
2744
+ Gross points, in decided whether quaternion orders have
2745
+ embeddings from orders in quadratic imaginary fields, and in
2746
+ computing elements of the Kohnen plus subspace of modular
2747
+ forms of weight 3/2.
2748
+
2749
+ EXAMPLES::
2750
+
2751
+ sage: # needs sage.schemes
2752
+ sage: R = BrandtModule(11,13).order_of_level_N()
2753
+ sage: Q = R.ternary_quadratic_form(); Q
2754
+ Quadratic form in 3 variables over Rational Field with coefficients:
2755
+ [ 5820 1012 13156 ]
2756
+ [ * 55 1144 ]
2757
+ [ * * 7436 ]
2758
+ sage: factor(Q.disc())
2759
+ 2^4 * 11^2 * 13^2
2760
+
2761
+ The following theta series is a modular form of weight 3/2 and level 4*11*13::
2762
+
2763
+ sage: Q.theta_series(100) # needs sage.schemes
2764
+ 1 + 2*q^23 + 2*q^55 + 2*q^56 + 2*q^75 + 4*q^92 + O(q^100)
2765
+ """
2766
+ if self.base_ring() != ZZ:
2767
+ raise NotImplementedError("ternary quadratic form of order only implemented for quaternion algebras over QQ")
2768
+
2769
+ Q = self.quaternion_algebra()
2770
+ # 2*R + ZZ
2771
+ twoR = self.free_module().scale(2)
2772
+ Z = twoR.span([Q(1).coefficient_tuple()], ZZ)
2773
+ S = twoR + Z
2774
+ # Now we intersect with the trace 0 submodule
2775
+ v = [b.reduced_trace() for b in Q.basis()]
2776
+ M = matrix(QQ, 4, 1, v)
2777
+ tr0 = M.kernel()
2778
+ G = tr0.intersection(S)
2779
+ B = [Q(a) for a in G.basis()]
2780
+ m = matrix(QQ, [[x.pair(y) for x in B] for y in B])
2781
+ Q = QuadraticForm(m)
2782
+ if include_basis:
2783
+ return Q, B
2784
+ else:
2785
+ return Q
2786
+
2787
+ def isomorphism_to(self, other, *, conjugator=False, B=10):
2788
+ r"""
2789
+ Compute an isomorphism from this quaternion order `O`
2790
+ to another order `O'` in the same quaternion algebra.
2791
+
2792
+ INPUT:
2793
+
2794
+ - ``conjugator`` -- boolean (default: ``False``); if ``True`` this
2795
+ method returns a single quaternion `\gamma \in O \cap O'`
2796
+ of minimal norm such that `O' = \gamma^{-1} O \gamma`,
2797
+ rather than the ring isomorphism it defines
2798
+
2799
+ - ``B`` -- positive integer; bound on theta series
2800
+ coefficients to rule out non isomorphic orders
2801
+
2802
+ .. NOTE::
2803
+
2804
+ This method is currently only implemented for maximal orders in
2805
+ definite quaternion orders over `\QQ`. For a general algorithm,
2806
+ see [KV2010]_ (Problem ``IsConjugate``).
2807
+
2808
+ EXAMPLES::
2809
+
2810
+ sage: Quat.<i,j,k> = QuaternionAlgebra(-1, -19)
2811
+ sage: O0 = Quat.quaternion_order([1, i, (i+j)/2, (1+k)/2])
2812
+ sage: O1 = Quat.quaternion_order([1, 667*i, 1/2+j/2+9*i, (222075/2*i+333*j+k/2)/667])
2813
+ sage: iso = O0.isomorphism_to(O1)
2814
+ sage: iso
2815
+ Ring morphism:
2816
+ From: Order of Quaternion Algebra (-1, -19) with base ring Rational Field
2817
+ with basis (1, i, 1/2*i + 1/2*j, 1/2 + 1/2*k)
2818
+ To: Order of Quaternion Algebra (-1, -19) with base ring Rational Field
2819
+ with basis (1, 667*i, 1/2 + 9*i + 1/2*j, 222075/1334*i + 333/667*j + 1/1334*k)
2820
+ Defn: i |--> 629/667*i + 36/667*j - 36/667*k
2821
+ j |--> -684/667*i + 648/667*j + 19/667*k
2822
+ k |--> 684/667*i + 19/667*j + 648/667*k
2823
+ sage: iso(1)
2824
+ 1
2825
+ sage: iso(i)
2826
+ 629/667*i + 36/667*j - 36/667*k
2827
+ sage: iso(i/3)
2828
+ Traceback (most recent call last):
2829
+ ...
2830
+ TypeError: 1/3*i fails to convert into the map's domain ...
2831
+
2832
+ ::
2833
+
2834
+ sage: gamma = O0.isomorphism_to(O1, conjugator=True); gamma
2835
+ -36 + j + k
2836
+ sage: gamma in O0
2837
+ True
2838
+ sage: gamma in O1
2839
+ True
2840
+ sage: O1.unit_ideal() == ~gamma * O0 * gamma
2841
+ True
2842
+
2843
+ TESTS:
2844
+
2845
+ Some random testing::
2846
+
2847
+ sage: q = randrange(1,1000)
2848
+ sage: p = randrange(1,1000)
2849
+ sage: Quat.<i,j,k> = QuaternionAlgebra(-q, -p)
2850
+ sage: O0 = Quat.maximal_order()
2851
+ sage: while True:
2852
+ ....: b = Quat.random_element()
2853
+ ....: if gcd(b.reduced_norm(), Quat.discriminant()) == 1:
2854
+ ....: break
2855
+ sage: O1 = (b * O0).left_order()
2856
+ sage: iso = O0.isomorphism_to(O1); iso
2857
+ Ring ...
2858
+ sage: iso.domain() == O0
2859
+ True
2860
+ sage: iso.codomain() == O1
2861
+ True
2862
+ sage: iso(O0.random_element()) in O1
2863
+ True
2864
+ sage: iso(1) == 1
2865
+ True
2866
+ sage: els = list(O0.basis())
2867
+ sage: els += [O0.random_element() for _ in range(5)]
2868
+ sage: {iso(g).reduced_norm() == g.reduced_norm() for g in els}
2869
+ {True}
2870
+ sage: {iso(g).reduced_trace() == g.reduced_trace() for g in els}
2871
+ {True}
2872
+ sage: {iso(g * h) == iso(g) * iso(h) for g in els for h in els}
2873
+ {True}
2874
+
2875
+ Test edge case::
2876
+
2877
+ sage: Quat.<i,j,k> = QuaternionAlgebra(419)
2878
+ sage: O = Quat.quaternion_order([1/2 + 3/2*j + k, 1/18*i + 25/9*j + 5/6*k, 3*j + 2*k, 3*k])
2879
+ sage: Oconj = j.inverse() * O * j
2880
+ sage: Oconj = Quat.quaternion_order(Oconj.basis())
2881
+ sage: O.isomorphism_to(Oconj, conjugator=True)
2882
+ j
2883
+
2884
+ Test error cases::
2885
+
2886
+ sage: Quat.<i,j,k> = QuaternionAlgebra(-1,-11)
2887
+ sage: O = Quat.maximal_order()
2888
+ sage: O.isomorphism_to('potato')
2889
+ Traceback (most recent call last):
2890
+ ...
2891
+ TypeError: not a quaternion order
2892
+
2893
+ ::
2894
+
2895
+ sage: Quat1.<i1,j1,k1> = QuaternionAlgebra(-1,-11)
2896
+ sage: Quat2.<i2,j2,k2> = QuaternionAlgebra(-3,-11)
2897
+ sage: Quat1.discriminant() == Quat2.discriminant() # isomorphic
2898
+ True
2899
+ sage: O1 = Quat1.maximal_order()
2900
+ sage: O2 = Quat2.maximal_order()
2901
+ sage: O1.isomorphism_to(O2)
2902
+ Traceback (most recent call last):
2903
+ ...
2904
+ TypeError: not an order in the same quaternion algebra
2905
+
2906
+ ::
2907
+
2908
+ sage: Quat.<i,j,k> = QuaternionAlgebra(7,11)
2909
+ sage: O1 = Quat.maximal_order()
2910
+ sage: O2 = (O1 * (i+j)).right_order()
2911
+ sage: O1.isomorphism_to(O2)
2912
+ Traceback (most recent call last):
2913
+ ...
2914
+ NotImplementedError: only implemented for definite quaternion orders
2915
+
2916
+ ::
2917
+
2918
+ sage: Quat.<i,j,k> = QuaternionAlgebra(-1,-11)
2919
+ sage: O1 = Quat.quaternion_order([1, i, j, k])
2920
+ sage: O2 = Quat.quaternion_order([1,-i, j,-k])
2921
+ sage: O1.isomorphism_to(O2)
2922
+ Traceback (most recent call last):
2923
+ ...
2924
+ NotImplementedError: only implemented for maximal orders
2925
+
2926
+ ::
2927
+
2928
+ sage: Quat.<i,j,k> = QuaternionAlgebra(-1,-11)
2929
+ sage: O1 = Quat.quaternion_order([1, i, (i+j)/2, (1+k)/2])
2930
+ sage: O2 = Quat.quaternion_order([1, (2+i+k)/4, (-11*i+2*j+k)/4, (-5*i+k)/3])
2931
+ sage: O1.isomorphism_to(O2)
2932
+ Traceback (most recent call last):
2933
+ ...
2934
+ ValueError: quaternion orders not isomorphic
2935
+
2936
+ ::
2937
+
2938
+ sage: Quat.<i,j,k> = QuaternionAlgebra(-5, -17)
2939
+ sage: O1 = Quat.quaternion_order([1, i, j, 1/2 + 1/2*i + 1/2*j + 1/2*k])
2940
+ sage: O2 = Quat.quaternion_order([1/2 + 1/2*i + 1/6*j + 13/6*k, i, 1/3*j + 4/3*k, 3*k])
2941
+ sage: O1.isomorphism_to(O2)
2942
+ Traceback (most recent call last):
2943
+ ...
2944
+ NotImplementedError: isomorphism_to was not able to recognize the given orders as isomorphic
2945
+
2946
+ ALGORITHM:
2947
+
2948
+ Find a generator of the principal lattice `N\cdot O\cdot O'`
2949
+ where `N = [O : O cap O']` using
2950
+ :meth:`QuaternionFractionalIdeal_rational.minimal_element()`.
2951
+ An isomorphism is given by conjugation by such an element.
2952
+ Works providing reduced norm of conjugation element is not
2953
+ a ramified prime times a square. To cover cases where it is
2954
+ we repeat the check for orders conjugated by i, j, and k.
2955
+ """
2956
+
2957
+ # Method to find isomorphism, which might not work when O2 is
2958
+ # O1 conjugated by an alpha such that nrd(alpha) is a
2959
+ # ramified prime times a square
2960
+ def attempt_isomorphism(self, other):
2961
+ N = self.intersection(other).free_module().index_in(self.free_module())
2962
+ I = N * self * other
2963
+ gamma = I.minimal_element()
2964
+ if self*gamma != I:
2965
+ return False, None
2966
+ if gamma*other != I:
2967
+ return False, None
2968
+ return True, gamma
2969
+
2970
+ if not isinstance(other, QuaternionOrder):
2971
+ raise TypeError('not a quaternion order')
2972
+ Q = self.quaternion_algebra()
2973
+ if other.quaternion_algebra() != Q:
2974
+ raise TypeError('not an order in the same quaternion algebra')
2975
+
2976
+ if not isinstance(Q.base_ring(), RationalField):
2977
+ raise NotImplementedError('only implemented for orders in a rational quaternion algebra')
2978
+ if not Q.is_definite():
2979
+ raise NotImplementedError('only implemented for definite quaternion orders')
2980
+ if not (self.discriminant() == Q.discriminant() == other.discriminant()):
2981
+ raise NotImplementedError('only implemented for maximal orders')
2982
+
2983
+ # First try a theta series check, up to bound B
2984
+ if self.unit_ideal().theta_series_vector(B) != other.unit_ideal().theta_series_vector(B):
2985
+ raise ValueError('quaternion orders not isomorphic')
2986
+
2987
+ # Want to iterate over elements alpha where the square-free part of nrd(alpha) divides prod(Q.ramified_primes()),
2988
+ # and each time try attempt_isomorphism with the order conjugated by alpha.
2989
+ # But in general finding all such elements alpha is hard,
2990
+ # so we just try 1, i, j, k first.
2991
+ for alpha in [1] + list(Q.gens()):
2992
+ other_conj = other
2993
+ if alpha != 1:
2994
+ other_conj = Q.quaternion_order((alpha * other * alpha.inverse()).basis())
2995
+ found, gamma = attempt_isomorphism(self, other_conj)
2996
+ if found:
2997
+ gamma = gamma * alpha
2998
+ if conjugator:
2999
+ return gamma
3000
+ else:
3001
+ ims = [~gamma * gen * gamma for gen in Q.gens()]
3002
+ return self.hom(ims, other, check=False)
3003
+
3004
+ # We can tell if 1, i, j, k cover all the alpha we need to test,
3005
+ # by checking if we have additional ramified primes which are not the square-free parts of nrd(i), nrd(j) or nrd(k)
3006
+ a, b = -Q.invariants()[0], -Q.invariants()[1]
3007
+ square_free_invariants = [a.squarefree_part(), b.squarefree_part(), (a*b).squarefree_part()]
3008
+ is_result_guaranteed = len([a for a in Q.ramified_primes() if a not in square_free_invariants]) == 0
3009
+
3010
+ if is_result_guaranteed:
3011
+ raise ValueError('quaternion orders not isomorphic')
3012
+
3013
+ # Otherwise, there might be other unknown alpha's giving isomorphism. If so we can't find them.
3014
+ raise NotImplementedError("isomorphism_to was not able to recognize the given orders as isomorphic")
3015
+
3016
+
3017
+ class QuaternionFractionalIdeal(Ideal_fractional):
3018
+ pass
3019
+
3020
+
3021
+ class QuaternionFractionalIdeal_rational(QuaternionFractionalIdeal):
3022
+ """
3023
+ A fractional ideal in a rational quaternion algebra.
3024
+
3025
+ INPUT:
3026
+
3027
+ - ``left_order`` -- a quaternion order or ``None``
3028
+
3029
+ - ``right_order`` -- a quaternion order or ``None``
3030
+
3031
+ - ``basis`` -- tuple of length 4 of elements in of ambient
3032
+ quaternion algebra whose `\\ZZ`-span is an ideal
3033
+
3034
+ - ``check`` -- boolean (default: ``True``); if ``False``, do no type
3035
+ checking.
3036
+ """
3037
+ def __init__(self, Q, basis, left_order=None, right_order=None, check=True):
3038
+ """
3039
+ EXAMPLES::
3040
+
3041
+ sage: R = QuaternionAlgebra(-11,-1).maximal_order()
3042
+ sage: R.right_ideal(R.basis())
3043
+ Fractional ideal (1, 1/2 + 1/2*i, j, 1/2*j + 1/2*k)
3044
+ sage: R.right_ideal(tuple(R.basis()), check=False, is_basis=True)
3045
+ Fractional ideal (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
3046
+
3047
+ TESTS::
3048
+
3049
+ sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().gens()
3050
+ (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
3051
+
3052
+ Check that :issue:`31582` is fixed::
3053
+
3054
+ sage: BrandtModule(23).right_ideals()[0].parent() # needs sage.schemes
3055
+ Monoid of ideals of Quaternion Algebra (-1, -23) with base ring Rational Field
3056
+ """
3057
+ if check:
3058
+ if left_order is not None and not isinstance(left_order, QuaternionOrder):
3059
+ raise TypeError("left_order must be a quaternion order or None")
3060
+ if right_order is not None and not isinstance(right_order, QuaternionOrder):
3061
+ raise TypeError("right_order must be a quaternion order or None")
3062
+ if not isinstance(basis, (list, tuple)):
3063
+ raise TypeError("basis must be a list or tuple")
3064
+ basis = tuple([Q(v) for v in
3065
+ (QQ**4).span([Q(v).coefficient_tuple() for v in basis], ZZ).basis()])
3066
+ if len(basis) != 4:
3067
+ raise ValueError("fractional ideal must have rank 4")
3068
+ self.__left_order = left_order
3069
+ self.__right_order = right_order
3070
+ Ideal_fractional.__init__(self, Q, basis)
3071
+
3072
+ def scale(self, alpha, left=False):
3073
+ r"""
3074
+ Scale the fractional ideal ``self`` by multiplying the basis
3075
+ by ``alpha``.
3076
+
3077
+ INPUT:
3078
+
3079
+ - `\alpha` -- nonzero element of quaternion algebra
3080
+
3081
+ - ``left`` -- boolean (default: ``False``); if ``True`` multiply
3082
+ `\alpha` on the left, otherwise multiply `\alpha` on the right
3083
+
3084
+ OUTPUT: a new fractional ideal
3085
+
3086
+ EXAMPLES::
3087
+
3088
+ sage: # needs sage.schemes
3089
+ sage: B = BrandtModule(5,37); I = B.right_ideals()[0]
3090
+ sage: i,j,k = B.quaternion_algebra().gens(); I
3091
+ Fractional ideal (4, 148*i, 2 + 106*i + 2*j, 2 + 147*i + k)
3092
+ sage: I.scale(i)
3093
+ Fractional ideal (296, 4*i, 2 + 2*i + 2*j, 212 + 2*i + 2*k)
3094
+ sage: I.scale(i, left=True)
3095
+ Fractional ideal (296, 4*i, 294 + 2*i + 2*j, 84 + 2*i + 2*k)
3096
+ sage: I.scale(i, left=False)
3097
+ Fractional ideal (296, 4*i, 2 + 2*i + 2*j, 212 + 2*i + 2*k)
3098
+ sage: i * I.gens()[0]
3099
+ 4*i
3100
+ sage: I.gens()[0] * i
3101
+ 4*i
3102
+
3103
+ The scaling element must be nonzero::
3104
+
3105
+ sage: B.<i,j,k> = QuaternionAlgebra(419)
3106
+ sage: O = B.quaternion_order([1/2 + 3/2*j, 1/6*i + 2/3*j + 1/2*k, 3*j, k])
3107
+ sage: O * O.zero()
3108
+ Traceback (most recent call last):
3109
+ ...
3110
+ ValueError: the scaling factor must be nonzero
3111
+
3112
+ TESTS:
3113
+
3114
+ Scaling by `1` should not change anything (see :issue:`32245`)::
3115
+
3116
+ sage: I.scale(1) == I # needs sage.schemes
3117
+ True
3118
+
3119
+ Check that :issue:`32726` is fixed::
3120
+
3121
+ sage: Q.<i,j,k> = QuaternionAlgebra(-1,-19)
3122
+ sage: I = Q.ideal([1,i,j,k])
3123
+ sage: _ = I.left_order(), I.right_order() # cache
3124
+ sage: span = lambda L: L.basis_matrix().row_module(ZZ)
3125
+ sage: for left in (True,False):
3126
+ ....: J = I.scale(1+i+j+k, left=left)
3127
+ ....: Ol, Or = J.left_order(), J.right_order()
3128
+ ....: [
3129
+ ....: span(Ol.unit_ideal() * J) <= span(J),
3130
+ ....: span(J * Or.unit_ideal()) <= span(J),
3131
+ ....: ]
3132
+ [True, True]
3133
+ [True, True]
3134
+ """
3135
+ Q = self.quaternion_algebra()
3136
+ alpha = Q(alpha)
3137
+ if alpha.is_zero():
3138
+ raise ValueError("the scaling factor must be nonzero")
3139
+ if left:
3140
+ gens = basis_for_quaternion_lattice([alpha * b for b in self.basis()])
3141
+ else:
3142
+ gens = basis_for_quaternion_lattice([b * alpha for b in self.basis()])
3143
+ left_order = self.__left_order if alpha in QQ or not left else None
3144
+ right_order = self.__right_order if alpha in QQ or left else None
3145
+ return Q.ideal(gens, check=False,
3146
+ left_order=left_order, right_order=right_order)
3147
+
3148
+ def quaternion_algebra(self):
3149
+ """
3150
+ Return the ambient quaternion algebra that contains this fractional ideal.
3151
+
3152
+ This is an alias for `self.ring()`.
3153
+
3154
+ EXAMPLES::
3155
+
3156
+ sage: # needs sage.schemes
3157
+ sage: I = BrandtModule(3, 5).right_ideals()[1]; I
3158
+ Fractional ideal (8, 40*i, 6 + 28*i + 2*j, 4 + 18*i + 2*k)
3159
+ sage: I.quaternion_algebra()
3160
+ Quaternion Algebra (-1, -3) with base ring Rational Field
3161
+ """
3162
+ return self.ring()
3163
+
3164
+ def _compute_order(self, side='left'):
3165
+ r"""
3166
+ Used internally to compute either the left or right order
3167
+ associated to an ideal in a quaternion algebra.
3168
+
3169
+ INPUT:
3170
+
3171
+ - ``side`` -- ``'left'`` or ``'right'``
3172
+
3173
+ OUTPUT: the left order if ``side='left'``; the right order if ``side='right'``
3174
+
3175
+ EXAMPLES::
3176
+
3177
+ sage: R.<i,j,k> = QuaternionAlgebra(-1,-11)
3178
+ sage: I = R.ideal([2 + 2*j + 140*k, 2*i + 4*j + 150*k, 8*j + 104*k, 152*k])
3179
+ sage: Ol = I._compute_order('left'); Ol
3180
+ Order of Quaternion Algebra (-1, -11) with base ring Rational Field
3181
+ with basis (1/2 + 1/2*j + 35*k, 1/4*i + 1/2*j + 75/4*k, j + 32*k, 38*k)
3182
+ sage: Or = I._compute_order('right'); Or
3183
+ Order of Quaternion Algebra (-1, -11) with base ring Rational Field
3184
+ with basis (1/2 + 1/2*j + 16*k, 1/2*i + 11/2*k, j + 13*k, 19*k)
3185
+ sage: Ol.discriminant()
3186
+ 209
3187
+ sage: Or.discriminant()
3188
+ 209
3189
+ sage: I.left_order() == Ol
3190
+ True
3191
+ sage: I.right_order() == Or
3192
+ True
3193
+
3194
+ ALGORITHM: Let `b_1, b_2, b_3, b_3` be a basis for this
3195
+ fractional ideal `I`, and assume we want to compute the left
3196
+ order of `I` in the quaternion algebra `Q`. Then
3197
+ multiplication by `b_i` on the right defines a map `B_i:Q \to
3198
+ Q`. We have
3199
+
3200
+ .. MATH::
3201
+
3202
+ R = B_1^{-1}(I) \cap B_2^{-1}(I) \cap B_3^{-1}(I)\cap B_4^{-1}(I).
3203
+
3204
+ This is because
3205
+
3206
+ .. MATH::
3207
+
3208
+ B_n^{-1}(I) = \{\alpha \in Q : \alpha b_n \in I \},
3209
+
3210
+ and
3211
+
3212
+ .. MATH::
3213
+
3214
+ R = \{\alpha \in Q : \alpha b_n \in I, n=1,2,3,4\}.
3215
+ """
3216
+ if side == 'left':
3217
+ action = 'right'
3218
+ elif side == 'right':
3219
+ action = 'left'
3220
+ else:
3221
+ raise ValueError("side must be 'left' or 'right'")
3222
+ Q = self.quaternion_algebra()
3223
+ if Q.base_ring() != QQ:
3224
+ raise NotImplementedError("computation of left and right orders only implemented over QQ")
3225
+ M = [(~b).matrix(action=action) for b in self.basis()]
3226
+ B = self.basis_matrix()
3227
+ invs = [B * m for m in M]
3228
+ # Now intersect the row spans of each matrix in invs
3229
+ ISB = [Q(v) for v in intersection_of_row_modules_over_ZZ(invs).row_module(ZZ).basis()]
3230
+ return Q.quaternion_order(ISB)
3231
+
3232
+ def left_order(self):
3233
+ """
3234
+ Return the left order associated to this fractional ideal.
3235
+
3236
+ OUTPUT: an order in a quaternion algebra
3237
+
3238
+ EXAMPLES::
3239
+
3240
+ sage: # needs sage.schemes
3241
+ sage: B = BrandtModule(11)
3242
+ sage: R = B.maximal_order()
3243
+ sage: I = R.unit_ideal()
3244
+ sage: I.left_order()
3245
+ Order of Quaternion Algebra (-1, -11) with base ring Rational Field
3246
+ with basis (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)
3247
+
3248
+ We do a consistency check::
3249
+
3250
+ sage: # needs sage.schemes
3251
+ sage: B = BrandtModule(11,19); R = B.right_ideals()
3252
+ sage: [r.left_order().discriminant() for r in R]
3253
+ [209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209]
3254
+ """
3255
+ if self.__left_order is None:
3256
+ self.__left_order = self._compute_order(side='left')
3257
+ return self.__left_order
3258
+
3259
+ def right_order(self):
3260
+ """
3261
+ Return the right order associated to this fractional ideal.
3262
+
3263
+ OUTPUT: an order in a quaternion algebra
3264
+
3265
+ EXAMPLES::
3266
+
3267
+ sage: # needs sage.schemes
3268
+ sage: I = BrandtModule(389).right_ideals()[1]; I
3269
+ Fractional ideal (8, 8*i, 2 + 6*i + 2*j, 6 + 3*i + k)
3270
+ sage: I.right_order()
3271
+ Order of Quaternion Algebra (-2, -389) with base ring Rational Field
3272
+ with basis (1/2 + 1/2*j + 1/2*k, 1/4*i + 1/2*j + 1/4*k, j, k)
3273
+ sage: I.left_order()
3274
+ Order of Quaternion Algebra (-2, -389) with base ring Rational Field
3275
+ with basis (1/2 + 1/2*j + 3/2*k, 1/8*i + 1/4*j + 9/8*k, j + k, 2*k)
3276
+
3277
+ The following is a big consistency check. We take reps for
3278
+ all the right ideal classes of a certain order, take the
3279
+ corresponding left orders, then take ideals in the left orders
3280
+ and from those compute the right order again::
3281
+
3282
+ sage: # needs sage.schemes
3283
+ sage: B = BrandtModule(11,19); R = B.right_ideals()
3284
+ sage: O = [r.left_order() for r in R]
3285
+ sage: J = [O[i].left_ideal(R[i].basis()) for i in range(len(R))]
3286
+ sage: len(set(J))
3287
+ 18
3288
+ sage: len(set([I.right_order() for I in J]))
3289
+ 1
3290
+ sage: J[0].right_order() == B.order_of_level_N()
3291
+ True
3292
+ """
3293
+ if self.__right_order is None:
3294
+ self.__right_order = self._compute_order(side='right')
3295
+ return self.__right_order
3296
+
3297
+ def __repr__(self):
3298
+ """
3299
+ Return string representation of this quaternion fractional ideal.
3300
+
3301
+ EXAMPLES::
3302
+
3303
+ sage: # needs sage.schemes
3304
+ sage: I = BrandtModule(11).right_ideals()[1]
3305
+ sage: type(I)
3306
+ <class 'sage.algebras.quatalg.quaternion_algebra.QuaternionFractionalIdeal_rational'>
3307
+ sage: I.__repr__()
3308
+ 'Fractional ideal (8, 8*i, 6 + 4*i + 2*j, 4 + 2*i + 2*k)'
3309
+ """
3310
+ return 'Fractional ideal %s' % (self.gens(),)
3311
+
3312
+ def random_element(self, *args, **kwds):
3313
+ """
3314
+ Return a random element in the rational fractional ideal ``self``.
3315
+
3316
+ EXAMPLES::
3317
+
3318
+ sage: B.<i,j,k> = QuaternionAlgebra(211)
3319
+ sage: I = B.ideal([1, 1/4*j, 20*(i+k), 2/3*i])
3320
+ sage: I.random_element() in I
3321
+ True
3322
+ """
3323
+ return sum(ZZ.random_element(*args, **kwds) * g for g in self.gens())
3324
+
3325
+ def basis(self):
3326
+ """
3327
+ Return a basis for this fractional ideal.
3328
+
3329
+ OUTPUT: tuple
3330
+
3331
+ EXAMPLES::
3332
+
3333
+ sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().basis()
3334
+ (1/2 + 1/2*i, 1/2*j - 1/2*k, i, -k)
3335
+ """
3336
+ return self.gens()
3337
+
3338
+ def _richcmp_(self, right, op):
3339
+ """
3340
+ Compare this fractional quaternion ideal to ``right``.
3341
+
3342
+ EXAMPLES::
3343
+
3344
+ sage: I = QuaternionAlgebra(-11, -1).maximal_order().unit_ideal()
3345
+ sage: I == I # indirect doctest
3346
+ True
3347
+ sage: I == 5
3348
+ False
3349
+
3350
+ sage: J = QuaternionAlgebra(-7, -1).maximal_order().unit_ideal()
3351
+ sage: J == I
3352
+ False
3353
+
3354
+ Ideals can be equal even if they are defined by different
3355
+ bases (see :issue:`32245`)::
3356
+
3357
+ sage: I == I.scale(-1)
3358
+ True
3359
+
3360
+ sage: I != I # indirect doctest
3361
+ False
3362
+
3363
+ TESTS::
3364
+
3365
+ sage: B = QuaternionAlgebra(QQ,-1,-11)
3366
+ sage: i,j,k = B.gens()
3367
+ sage: I = B.ideal([1,i,j,i*j])
3368
+ sage: I == I
3369
+ True
3370
+ sage: O = B.ideal([1,i,(i+j)/2,(1+i*j)/2])
3371
+ sage: I <= O
3372
+ True
3373
+ sage: I >= O
3374
+ False
3375
+ sage: I != O
3376
+ True
3377
+ sage: I == O
3378
+ False
3379
+ sage: I != I
3380
+ False
3381
+ sage: I < I
3382
+ False
3383
+ sage: I < O
3384
+ True
3385
+ sage: I <= I
3386
+ True
3387
+ sage: O >= O
3388
+ True
3389
+ """
3390
+ return self.free_module().__richcmp__(right.free_module(), op)
3391
+
3392
+ def __hash__(self):
3393
+ """
3394
+ Return the hash of ``self``.
3395
+
3396
+ EXAMPLES::
3397
+
3398
+ sage: I = QuaternionAlgebra(-11,-1).maximal_order().unit_ideal()
3399
+ sage: hash(I) == hash(I)
3400
+ True
3401
+
3402
+ TESTS::
3403
+
3404
+ sage: R = QuaternionAlgebra(-11,-1).maximal_order()
3405
+ sage: H = hash(R.right_ideal(R.basis()))
3406
+ """
3407
+ return hash(self.gens())
3408
+
3409
+ @cached_method
3410
+ def basis_matrix(self):
3411
+ r"""
3412
+ Return basis matrix `M` in Hermite normal form for ``self`` as a
3413
+ matrix with rational entries.
3414
+
3415
+ If `Q` is the ambient quaternion algebra, then the `\ZZ`-span of
3416
+ the rows of `M` viewed as linear combinations of Q.basis() =
3417
+ `[1,i,j,k]` is the fractional ideal ``self``. Also,
3418
+ ``M * M.denominator()`` is an integer matrix in Hermite normal form.
3419
+
3420
+ OUTPUT: matrix over `\QQ`
3421
+
3422
+ EXAMPLES::
3423
+
3424
+ sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().basis_matrix()
3425
+ [1/2 1/2 0 0]
3426
+ [ 0 1 0 0]
3427
+ [ 0 0 1/2 1/2]
3428
+ [ 0 0 0 1]
3429
+ """
3430
+ B = quaternion_algebra_cython.rational_matrix_from_rational_quaternions(self.gens())
3431
+ C, d = B._clear_denom()
3432
+ return C.hermite_form() / d
3433
+
3434
+ def reduced_basis(self):
3435
+ r"""
3436
+ Let `I` = ``self`` be a fractional ideal in a (rational) definite
3437
+ quaternion algebra. This function returns an LLL reduced basis of `I`.
3438
+
3439
+ OUTPUT: a tuple of four elements in `I` forming an LLL reduced basis of
3440
+ `I` as a lattice
3441
+
3442
+ EXAMPLES::
3443
+
3444
+ sage: # needs sage.schemes
3445
+ sage: B = BrandtModule(2,37); I = B.right_ideals()[0]
3446
+ sage: I
3447
+ Fractional ideal (4, 148*i, 108*i + 4*j, 2 + 2*i + 2*j + 2*k)
3448
+ sage: I.reduced_basis()
3449
+ (4, 2 + 2*i + 2*j + 2*k, 2 - 14*i + 14*j - 2*k, 2 - 2*i - 14*j + 14*k)
3450
+ sage: l = I.reduced_basis()
3451
+ sage: assert all(l[i].reduced_norm() <= l[i+1].reduced_norm() for i in range(len(l) - 1))
3452
+
3453
+ sage: B = QuaternionAlgebra(next_prime(2**50))
3454
+ sage: O = B.maximal_order()
3455
+ sage: i,j,k = B.gens()
3456
+ sage: alpha = 1/2 - 1/2*i + 3/2*j - 7/2*k
3457
+ sage: I = O*alpha + O*3089622859
3458
+ sage: I.reduced_basis()[0]
3459
+ 1/2*i + j + 5/2*k
3460
+ """
3461
+ if not self.quaternion_algebra().is_definite():
3462
+ raise TypeError("the quaternion algebra must be definite")
3463
+
3464
+ U = self.gram_matrix().LLL_gram().transpose()
3465
+ return tuple(sum(c * g for c, g in zip(row, self.basis())) for row in U)
3466
+
3467
+ def theta_series_vector(self, B):
3468
+ r"""
3469
+ Return theta series coefficients of ``self``, as a vector
3470
+ of `B` integers.
3471
+
3472
+ INPUT:
3473
+
3474
+ - ``B`` -- positive integer
3475
+
3476
+ OUTPUT: vector over `\ZZ` with `B` entries
3477
+
3478
+ EXAMPLES::
3479
+
3480
+ sage: # needs sage.schemes
3481
+ sage: I = BrandtModule(37).right_ideals()[1]; I
3482
+ Fractional ideal (8, 8*i, 2 + 6*i + 2*j, 6 + 3*i + k)
3483
+ sage: I.theta_series_vector(5)
3484
+ (1, 0, 2, 2, 6)
3485
+ sage: I.theta_series_vector(10)
3486
+ (1, 0, 2, 2, 6, 4, 8, 6, 10, 10)
3487
+ sage: I.theta_series_vector(5)
3488
+ (1, 0, 2, 2, 6)
3489
+ """
3490
+ B = Integer(B)
3491
+ try:
3492
+ if len(self.__theta_series_vector) >= B:
3493
+ return self.__theta_series_vector[:B]
3494
+ except AttributeError:
3495
+ pass
3496
+ V = FreeModule(ZZ, B)
3497
+ Q = self.quadratic_form()
3498
+ v = V(Q.representation_number_list(B))
3499
+ self.__theta_series_vector = v
3500
+ return v
3501
+
3502
+ @cached_method
3503
+ def quadratic_form(self):
3504
+ """
3505
+ Return the normalized quadratic form associated to this quaternion ideal.
3506
+
3507
+ OUTPUT: quadratic form
3508
+
3509
+ EXAMPLES::
3510
+
3511
+ sage: # needs sage.schemes
3512
+ sage: I = BrandtModule(11).right_ideals()[1]
3513
+ sage: Q = I.quadratic_form(); Q
3514
+ Quadratic form in 4 variables over Rational Field with coefficients:
3515
+ [ 2 0 3 2 ]
3516
+ [ * 2 2 1 ]
3517
+ [ * * 3 2 ]
3518
+ [ * * * 2 ]
3519
+ sage: Q.theta_series(10)
3520
+ 1 + 12*q^2 + 12*q^3 + 12*q^4 + 12*q^5 + 24*q^6 + 24*q^7 + 36*q^8 + 36*q^9 + O(q^10)
3521
+ sage: I.theta_series(10)
3522
+ 1 + 12*q^2 + 12*q^3 + 12*q^4 + 12*q^5 + 24*q^6 + 24*q^7 + 36*q^8 + 36*q^9 + O(q^10)
3523
+ """
3524
+ # first get the gram matrix
3525
+ gram_matrix = self.gram_matrix()
3526
+ # rescale so that there are no denominators
3527
+ gram_matrix, _ = gram_matrix._clear_denom()
3528
+ # Make sure gcd of all entries is 1.
3529
+ g = gram_matrix.gcd()
3530
+ if g != 1:
3531
+ gram_matrix = gram_matrix / g
3532
+ # now get the quadratic form
3533
+ return QuadraticForm(gram_matrix)
3534
+
3535
+ def minimal_element(self):
3536
+ r"""
3537
+ Return an element in this quaternion ideal of minimal norm.
3538
+
3539
+ If the ideal is a principal lattice, this method can be used
3540
+ to find a generator; see [Piz1980]_, Corollary 1.20.
3541
+
3542
+ EXAMPLES::
3543
+
3544
+ sage: Quat.<i,j,k> = QuaternionAlgebra(-3,-101)
3545
+ sage: O = Quat.maximal_order(); O
3546
+ Order of Quaternion Algebra (-3, -101) with base ring Rational Field with basis (1/2 + 1/2*i, 1/2*j - 1/2*k, -1/3*i + 1/3*k, -k)
3547
+ sage: (O * 5).minimal_element()
3548
+ 5
3549
+ sage: alpha = 1/2 + 1/6*i + j + 55/3*k
3550
+ sage: I = O*141 + O*alpha; I.norm()
3551
+ 141
3552
+ sage: el = I.minimal_element(); el
3553
+ 13/2 - 7/6*i + j + 2/3*k
3554
+ sage: el.reduced_norm()
3555
+ 282
3556
+ """
3557
+ if not self.quaternion_algebra().is_definite():
3558
+ raise ValueError('quaternion algebra must be definite')
3559
+ pariqf = self.quadratic_form().__pari__()
3560
+ _, v = pariqf.qfminim(None, None, 1)
3561
+ return sum(ZZ(c) * g for c, g in zip(v, self.basis()))
3562
+
3563
+ def theta_series(self, B, var='q'):
3564
+ r"""
3565
+ Return normalized theta series of ``self``, as a power series over
3566
+ `\ZZ` in the variable ``var``, which is 'q' by default.
3567
+
3568
+ The normalized theta series is by definition
3569
+
3570
+ .. MATH::
3571
+
3572
+ \theta_I(q) = \sum_{x \in I} q^{\frac{N(x)}{N(I)}}.
3573
+
3574
+ INPUT:
3575
+
3576
+ - ``B`` -- positive integer
3577
+ - ``var`` -- string (default: ``'q'``)
3578
+
3579
+ OUTPUT: power series
3580
+
3581
+ EXAMPLES::
3582
+
3583
+ sage: # needs sage.schemes
3584
+ sage: I = BrandtModule(11).right_ideals()[1]; I
3585
+ Fractional ideal (8, 8*i, 6 + 4*i + 2*j, 4 + 2*i + 2*k)
3586
+ sage: I.norm()
3587
+ 32
3588
+ sage: I.theta_series(5)
3589
+ 1 + 12*q^2 + 12*q^3 + 12*q^4 + O(q^5)
3590
+ sage: I.theta_series(5,'T')
3591
+ 1 + 12*T^2 + 12*T^3 + 12*T^4 + O(T^5)
3592
+ sage: I.theta_series(3)
3593
+ 1 + 12*q^2 + O(q^3)
3594
+ """
3595
+ try:
3596
+ if self.__theta_series.prec() >= B:
3597
+ if var == self.__theta_series.variable():
3598
+ return self.__theta_series.add_bigoh(B)
3599
+ else:
3600
+ p_ring = self._theta_series.parent().change_var(var)
3601
+ p_ring(self.__theta_series.list()[:B+1])
3602
+ except AttributeError:
3603
+ pass
3604
+ v = self.theta_series_vector(B)
3605
+ p_ring = PowerSeriesRing(ZZ, var)
3606
+ theta = p_ring(v.list()).add_bigoh(B)
3607
+ self.__theta_series = theta
3608
+ return theta
3609
+
3610
+ @cached_method
3611
+ def gram_matrix(self):
3612
+ r"""
3613
+ Return the Gram matrix of this fractional ideal.
3614
+
3615
+ OUTPUT: `4 \times 4` matrix over `\QQ`
3616
+
3617
+ EXAMPLES::
3618
+
3619
+ sage: # needs sage.schemes
3620
+ sage: I = BrandtModule(3,5).right_ideals()[1]; I
3621
+ Fractional ideal (8, 40*i, 6 + 28*i + 2*j, 4 + 18*i + 2*k)
3622
+ sage: I.gram_matrix()
3623
+ [ 256 0 192 128]
3624
+ [ 0 6400 4480 2880]
3625
+ [ 192 4480 3328 2112]
3626
+ [ 128 2880 2112 1408]
3627
+ """
3628
+ A = self.gens()
3629
+ two = QQ(2)
3630
+ m = [two * a.pair(b) for b in A for a in A]
3631
+ M44 = MatrixSpace(QQ, 4, 4)
3632
+ return M44(m, coerce=False)
3633
+
3634
+ def norm(self):
3635
+ """
3636
+ Return the reduced norm of this fractional ideal.
3637
+
3638
+ OUTPUT: rational number
3639
+
3640
+ EXAMPLES::
3641
+
3642
+ sage: # needs sage.schemes
3643
+ sage: M = BrandtModule(37)
3644
+ sage: C = M.right_ideals()
3645
+ sage: [I.norm() for I in C]
3646
+ [16, 32, 32]
3647
+
3648
+ sage: # optional - magma
3649
+ sage: (a,b) = M.quaternion_algebra().invariants()
3650
+ sage: magma.eval('A<i,j,k> := QuaternionAlgebra<Rationals() | %s, %s>' % (a,b))
3651
+ ''
3652
+ sage: magma.eval('O := QuaternionOrder(%s)' % str(list(C[0].right_order().basis())))
3653
+ ''
3654
+ sage: [ magma('rideal<O | %s>' % str(list(I.basis()))).Norm() for I in C]
3655
+ [16, 32, 32]
3656
+
3657
+ sage: # needs sage.schemes
3658
+ sage: A.<i,j,k> = QuaternionAlgebra(-1,-1)
3659
+ sage: R = A.ideal([i,j,k,1/2 + 1/2*i + 1/2*j + 1/2*k]) # this is actually an order, so has reduced norm 1
3660
+ sage: R.norm()
3661
+ 1
3662
+ sage: [ J.norm() for J in R.cyclic_right_subideals(3) ] # enumerate maximal right R-ideals of reduced norm 3, verify their norms
3663
+ [3, 3, 3, 3]
3664
+ """
3665
+ G = self.gram_matrix() / QQ(2)
3666
+ r = G.det().abs()
3667
+ assert r.is_square(), "first is bad!"
3668
+ r = r.sqrt()
3669
+ # If we know either the left- or the right order, use that one to compute the norm.
3670
+ R = self.__left_order or self.__right_order or self.left_order()
3671
+ r /= R.discriminant()
3672
+ assert r.is_square(), "second is bad!"
3673
+ return r.sqrt()
3674
+
3675
+ def conjugate(self):
3676
+ """
3677
+ Return the ideal with generators the conjugates of the generators for ``self``.
3678
+
3679
+ OUTPUT: a quaternionic fractional ideal
3680
+
3681
+ EXAMPLES::
3682
+
3683
+ sage: # needs sage.schemes
3684
+ sage: I = BrandtModule(3,5).right_ideals()[1]; I
3685
+ Fractional ideal (8, 40*i, 6 + 28*i + 2*j, 4 + 18*i + 2*k)
3686
+ sage: I.conjugate()
3687
+ Fractional ideal (2 + 2*j + 28*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)
3688
+ """
3689
+ return self.quaternion_algebra().ideal([b.conjugate() for b in self.basis()],
3690
+ left_order=self.__right_order,
3691
+ right_order=self.__left_order)
3692
+
3693
+ def __mul__(self, right):
3694
+ """
3695
+ Return the product of the fractional ideals ``self`` and ``right``.
3696
+
3697
+ .. NOTE::
3698
+
3699
+ We do not keep track of left or right order structure.
3700
+
3701
+ EXAMPLES::
3702
+
3703
+ sage: # needs sage.schemes
3704
+ sage: I = BrandtModule(3,5).right_ideals()[1]; I
3705
+ Fractional ideal (8, 40*i, 6 + 28*i + 2*j, 4 + 18*i + 2*k)
3706
+ sage: I*I
3707
+ Fractional ideal (32, 160*i, 24 + 112*i + 8*j, 16 + 72*i + 8*k)
3708
+ sage: I*I.conjugate()
3709
+ Fractional ideal (32, 320*i, 16 + 224*i + 16*j, 16 + 232*i + 8*k)
3710
+ sage: I.multiply_by_conjugate(I)
3711
+ Fractional ideal (32, 320*i, 16 + 224*i + 16*j, 16 + 232*i + 8*k)
3712
+ """
3713
+ if isinstance(right, QuaternionOrder):
3714
+ right = right.unit_ideal()
3715
+ if not isinstance(right, QuaternionFractionalIdeal_rational):
3716
+ return self.scale(right, left=False)
3717
+ gens = [a*b for a in self.basis() for b in right.basis()]
3718
+ # if self.__right_order == right.__left_order:
3719
+ # left_order = self.__left_order
3720
+ # right_order = right.__right_order
3721
+ basis = tuple(basis_for_quaternion_lattice(gens))
3722
+ A = self.quaternion_algebra()
3723
+ return A.ideal(basis, check=False)
3724
+
3725
+ def __add__(self, other):
3726
+ """
3727
+ Return the sum of the fractional ideals ``self`` and ``other``.
3728
+
3729
+ EXAMPLES::
3730
+
3731
+ sage: # needs sage.schemes
3732
+ sage: I = BrandtModule(11,5).right_ideals()[1]; I
3733
+ Fractional ideal (8, 40*i, 2 + 20*i + 2*j, 4 + 14*i + 2*k)
3734
+ sage: J = BrandtModule(11,5).right_ideals()[2]; J
3735
+ Fractional ideal (8, 40*i, 6 + 20*i + 2*j, 4 + 34*i + 2*k)
3736
+ sage: I + J
3737
+ Fractional ideal (2 + 2*j, 2*i + 6*k, 4*j, 20*k)
3738
+ """
3739
+ if isinstance(other, QuaternionOrder):
3740
+ other = other.unit_ideal()
3741
+ if not isinstance(other, QuaternionFractionalIdeal_rational):
3742
+ raise TypeError("can only add quaternion ideals")
3743
+ return self.quaternion_algebra().ideal(self.basis() + other.basis())
3744
+
3745
+ def _acted_upon_(self, other, on_left):
3746
+ """
3747
+ Scale a quaternion ideal.
3748
+
3749
+ EXAMPLES::
3750
+
3751
+ sage: Q.<i,j,k> = QuaternionAlgebra(-1,-419)
3752
+ sage: O = Q.maximal_order()
3753
+ sage: I = 7*O.unit_ideal() + O.unit_ideal()*(j-1); I
3754
+ Fractional ideal (1/2 + 13/2*j, 1/2*i + 13/2*k, 7*j, 7*k)
3755
+
3756
+ TESTS::
3757
+
3758
+ sage: (5+i-j)*I == I.scale(5+i-j, left=True)
3759
+ True
3760
+ sage: I*(5+i-j) == I.scale(5+i-j, left=False)
3761
+ True
3762
+ """
3763
+ return self.scale(other, left=(not on_left))
3764
+
3765
+ @cached_method
3766
+ def free_module(self):
3767
+ r"""
3768
+ Return the underlying free `\ZZ`-module corresponding to this ideal.
3769
+
3770
+ OUTPUT: free `\ZZ`-module of rank 4 embedded in an ambient `\QQ^4`
3771
+
3772
+ EXAMPLES::
3773
+
3774
+ sage: # needs sage.schemes
3775
+ sage: X = BrandtModule(3,5).right_ideals()
3776
+ sage: X[0]
3777
+ Fractional ideal (4, 20*i, 2 + 8*i + 2*j, 18*i + 2*k)
3778
+ sage: X[0].free_module()
3779
+ Free module of degree 4 and rank 4 over Integer Ring
3780
+ Echelon basis matrix:
3781
+ [ 2 0 2 8]
3782
+ [ 0 2 0 18]
3783
+ [ 0 0 4 16]
3784
+ [ 0 0 0 20]
3785
+ sage: X[0].scale(1/7).free_module()
3786
+ Free module of degree 4 and rank 4 over Integer Ring
3787
+ Echelon basis matrix:
3788
+ [ 2/7 0 2/7 8/7]
3789
+ [ 0 2/7 0 18/7]
3790
+ [ 0 0 4/7 16/7]
3791
+ [ 0 0 0 20/7]
3792
+
3793
+ sage: QuaternionAlgebra(-11,-1).maximal_order().unit_ideal().basis_matrix()
3794
+ [1/2 1/2 0 0]
3795
+ [ 0 1 0 0]
3796
+ [ 0 0 1/2 1/2]
3797
+ [ 0 0 0 1]
3798
+
3799
+ The free module method is also useful since it allows for checking if
3800
+ one ideal is contained in another, computing quotients `I/J`, etc.::
3801
+
3802
+ sage: # needs sage.schemes
3803
+ sage: X = BrandtModule(3,17).right_ideals()
3804
+ sage: I = X[0].intersection(X[2]); I
3805
+ Fractional ideal (2 + 2*j + 164*k, 2*i + 4*j + 46*k, 16*j + 224*k, 272*k)
3806
+ sage: I.free_module().is_submodule(X[3].free_module())
3807
+ False
3808
+ sage: I.free_module().is_submodule(X[1].free_module())
3809
+ True
3810
+ sage: X[0].free_module() / I.free_module()
3811
+ Finitely generated module V/W over Integer Ring with invariants (4, 4)
3812
+
3813
+ This shows that the issue at :issue:`6760` is fixed::
3814
+
3815
+ sage: R.<i,j,k> = QuaternionAlgebra(-1, -13)
3816
+ sage: I = R.ideal([2+i, 3*i, 5*j, j+k]); I
3817
+ Fractional ideal (2 + i, 3*i, j + k, 5*k)
3818
+ sage: I.free_module()
3819
+ Free module of degree 4 and rank 4 over Integer Ring
3820
+ Echelon basis matrix:
3821
+ [2 1 0 0]
3822
+ [0 3 0 0]
3823
+ [0 0 1 1]
3824
+ [0 0 0 5]
3825
+ """
3826
+ return self.basis_matrix().row_module(ZZ)
3827
+
3828
+ def intersection(self, J):
3829
+ """
3830
+ Return the intersection of the ideals ``self`` and `J`.
3831
+
3832
+ EXAMPLES::
3833
+
3834
+ sage: # needs sage.schemes
3835
+ sage: X = BrandtModule(3,5).right_ideals()
3836
+ sage: I = X[0].intersection(X[1]); I
3837
+ Fractional ideal (2 + 6*j + 4*k, 2*i + 4*j + 34*k, 8*j + 32*k, 40*k)
3838
+ """
3839
+ V = self.free_module().intersection(J.free_module())
3840
+ H, d = V.basis_matrix()._clear_denom()
3841
+ A = self.quaternion_algebra()
3842
+ gens = quaternion_algebra_cython.rational_quaternions_from_integral_matrix_and_denom(A, H, d)
3843
+ return A.ideal(gens)
3844
+
3845
+ def multiply_by_conjugate(self, J):
3846
+ """
3847
+ Return product of ``self`` and the conjugate Jbar of `J`.
3848
+
3849
+ INPUT:
3850
+
3851
+ - ``J`` -- a quaternion ideal
3852
+
3853
+ OUTPUT: a quaternionic fractional ideal
3854
+
3855
+ EXAMPLES::
3856
+
3857
+ sage: # needs sage.schemes
3858
+ sage: R = BrandtModule(3,5).right_ideals()
3859
+ sage: R[0].multiply_by_conjugate(R[1])
3860
+ Fractional ideal (32, 160*i, 8 + 112*i + 8*j, 16 + 72*i + 8*k)
3861
+ sage: R[0]*R[1].conjugate()
3862
+ Fractional ideal (32, 160*i, 8 + 112*i + 8*j, 16 + 72*i + 8*k)
3863
+ """
3864
+ Jbar = [b.conjugate() for b in J.basis()]
3865
+ gens = [a * b for a in self.basis() for b in Jbar]
3866
+ basis = tuple(basis_for_quaternion_lattice(gens))
3867
+ R = self.quaternion_algebra()
3868
+ return R.ideal(basis, check=False)
3869
+
3870
+ def pushforward(self, J, side=None):
3871
+ """
3872
+ Compute the ideal which is the pushforward of ``self`` through an ideal ``J``.
3873
+
3874
+ Uses Lemma 2.1.7 of [Ler2022]_. Only works for integral ideals.
3875
+
3876
+ INPUT:
3877
+
3878
+ - ``J`` -- a fractional quaternion ideal with norm coprime to ``self`` and either
3879
+ the same left order or right order as ``self``
3880
+
3881
+ - ``side`` -- string (default: ``None``); set to ``'left'`` or ``'right'`` to
3882
+ perform pushforward of left or right ideals respectively. If ``None`` the side
3883
+ is determined by the matching left or right orders
3884
+
3885
+ OUTPUT: a fractional quaternion ideal
3886
+
3887
+ EXAMPLES::
3888
+
3889
+ sage: B = QuaternionAlgebra(419)
3890
+ sage: i,j,k = B.gens()
3891
+ sage: I1 = B.ideal([1/2 + 3/2*j + 2*k, 1/2*i + j + 3/2*k, 3*j, 3*k])
3892
+ sage: I2 = B.ideal([1/2 + 9/2*j, 1/2*i + 9/2*k, 5*j, 5*k])
3893
+ sage: I1.left_order() == I2.left_order()
3894
+ True
3895
+ sage: I1.pushforward(I2, side='left')
3896
+ Fractional ideal (3, 15*i, 3/2 + 10*i + 1/2*j, 1 + 9/10*i + 1/10*k)
3897
+
3898
+ TESTS::
3899
+
3900
+ sage: B = QuaternionAlgebra(419)
3901
+ sage: i,j,k = B.gens()
3902
+ sage: O0 = B.maximal_order()
3903
+ sage: O0.unit_ideal().pushforward(O0.unit_ideal())
3904
+ Traceback (most recent call last):
3905
+ ...
3906
+ ValueError: self and J have same left and right orders, side of pushforward must be specified
3907
+ sage: O0.unit_ideal().pushforward(O0.unit_ideal(), "left")
3908
+ Fractional ideal (1, i, 1/2 + 1/2*j, 1/2*i + 1/2*k)
3909
+ sage: I1 = B.ideal([1/2 + 3/2*j + 2*k, 1/2*i + j + 3/2*k, 3*j, 3*k])
3910
+ sage: I2 = B.ideal([1/2 + 9/2*j, 1/2*i + 9/2*k, 5*j, 5*k])
3911
+ sage: I1.pushforward(I2)
3912
+ Fractional ideal (3, 15*i, 3/2 + 10*i + 1/2*j, 1 + 9/10*i + 1/10*k)
3913
+ sage: I1.pushforward(I2, side='right')
3914
+ Traceback (most recent call last):
3915
+ ...
3916
+ ValueError: self and J must have the same right orders
3917
+ sage: I1.conjugate().pushforward(I2.conjugate())
3918
+ Fractional ideal (1/2 + 3/2*j + 10*k, 1/10*i + 2*j + 39/10*k, 3*j, 15*k)
3919
+ sage: I1.conjugate().pushforward(I2.conjugate(), side='left')
3920
+ Traceback (most recent call last):
3921
+ ...
3922
+ ValueError: self and J must have the same left orders
3923
+ sage: I1.pushforward(I1, side='left')
3924
+ Traceback (most recent call last):
3925
+ ...
3926
+ ValueError: self and J must have coprime norms
3927
+ sage: I3 = B.ideal([1/2 + 13/2*j + 6*k, 1/2*i + 3*j + 13/2*k, 9*j, 9*k])
3928
+ sage: I3.pushforward(I3*(1/3), side='left')
3929
+ Traceback (most recent call last):
3930
+ ...
3931
+ NotImplementedError: quaternion ideal pushforward not implemented for non-integral ideals
3932
+ """
3933
+ if not isinstance(J, QuaternionFractionalIdeal_rational):
3934
+ raise TypeError("can only pushforward through a quaternion ideal")
3935
+
3936
+ if side == "left":
3937
+ if self.left_order() != J.left_order():
3938
+ raise ValueError("self and J must have the same left orders")
3939
+ if not self.is_integral() or not J.is_integral():
3940
+ raise NotImplementedError("quaternion ideal pushforward not implemented for non-integral ideals")
3941
+ Jnorm = J.norm()
3942
+ if gcd(self.norm(), Jnorm) != 1:
3943
+ raise ValueError("self and J must have coprime norms")
3944
+ return (1 / Jnorm) * (J.conjugate() * self.intersection(J))
3945
+
3946
+ if side == "right":
3947
+ if self.right_order() != J.right_order():
3948
+ raise ValueError("self and J must have the same right orders")
3949
+ return self.conjugate().pushforward(J.conjugate(), side='left').conjugate()
3950
+
3951
+ if side is None:
3952
+ same_left_order = bool(self.left_order() == J.left_order())
3953
+ same_right_order = bool(self.right_order() == J.right_order())
3954
+ if not same_left_order and not same_right_order:
3955
+ raise ValueError("self and J must share a left or right order")
3956
+ if same_left_order and same_right_order:
3957
+ raise ValueError("self and J have same left and right orders, side of pushforward must be specified")
3958
+ if same_left_order:
3959
+ return self.pushforward(J, side='left')
3960
+ return self.pushforward(J, side='right')
3961
+
3962
+ raise ValueError('side must be "left", "right" or None')
3963
+
3964
+ def pullback(self, J, side=None):
3965
+ """
3966
+ Compute the ideal which is the pullback of ``self`` through an ideal ``J``.
3967
+
3968
+ Uses Lemma 2.1.7 of [Ler2022]_. Only works for integral ideals.
3969
+
3970
+ INPUT:
3971
+
3972
+ - ``J`` -- a fractional quaternion ideal with norm coprime to ``self`` and either
3973
+ left order equal to the right order of ``self``, or vice versa
3974
+
3975
+ - ``side`` -- string (default: ``None``); set to ``'left'`` or ``'right'`` to
3976
+ perform pullback of left or right ideals respectively. If ``None`` the side
3977
+ is determined by the matching left and right orders
3978
+
3979
+ OUTPUT: a fractional quaternion ideal
3980
+
3981
+ EXAMPLES::
3982
+
3983
+ sage: B = QuaternionAlgebra(419)
3984
+ sage: i,j,k = B.gens()
3985
+ sage: I1 = B.ideal([1/2 + 3/2*j + 2*k, 1/2*i + j + 3/2*k, 3*j, 3*k])
3986
+ sage: I2 = B.ideal([1/2 + 9/2*j, 1/2*i + 9/2*k, 5*j, 5*k])
3987
+ sage: I3 = I1.pushforward(I2, side='left')
3988
+ sage: I3.left_order() == I2.right_order()
3989
+ True
3990
+ sage: I3.pullback(I2, side='left') == I1
3991
+ True
3992
+
3993
+ TESTS::
3994
+
3995
+ sage: B = QuaternionAlgebra(419)
3996
+ sage: i,j,k = B.gens()
3997
+ sage: O0 = B.maximal_order()
3998
+ sage: O0.unit_ideal().pullback(O0.unit_ideal())
3999
+ Traceback (most recent call last):
4000
+ ...
4001
+ ValueError: self and J have same left and right orders, side of pullback must be specified
4002
+ sage: O0.unit_ideal().pullback(O0.unit_ideal(), "left")
4003
+ Fractional ideal (1/2 + 1/2*j, 1/2*i + 1/2*k, j, k)
4004
+ sage: I1 = B.ideal([1/2 + 3/2*j + 2*k, 1/2*i + j + 3/2*k, 3*j, 3*k])
4005
+ sage: I2 = B.ideal([1/2 + 15/2*j + 2*k, 1/6*i + 43/3*j + 5/2*k, 15*j, 5*k])
4006
+ sage: I2.pullback(I1)
4007
+ Fractional ideal (1/2 + 5/2*j + 2*k, 1/2*i + 3*j + 5/2*k, 5*j, 5*k)
4008
+ sage: I2.pullback(I1, side='right')
4009
+ Traceback (most recent call last):
4010
+ ...
4011
+ ValueError: right order of self should be left order of J
4012
+ sage: I2.conjugate().pullback(I1.conjugate(), side='right')
4013
+ Fractional ideal (1/2 + 5/2*j + 3*k, 1/2*i + 3*j + 5/2*k, 5*j, 5*k)
4014
+ sage: I2.conjugate().pullback(I1.conjugate(), side='left')
4015
+ Traceback (most recent call last):
4016
+ ...
4017
+ ValueError: left order of self should be right order of J
4018
+ sage: I1.pullback(I1.conjugate(), side='left')
4019
+ Traceback (most recent call last):
4020
+ ...
4021
+ ValueError: self and J must have coprime norms
4022
+ sage: I3 = B.ideal([1/2 + 13/2*j + 6*k, 1/2*i + 3*j + 13/2*k, 9*j, 9*k])
4023
+ sage: I3.pullback(I3.conjugate()*(1/3), side='left')
4024
+ Traceback (most recent call last):
4025
+ ...
4026
+ NotImplementedError: quaternion ideal pullback not implemented for non-integral ideals
4027
+ """
4028
+ if not isinstance(J, QuaternionFractionalIdeal_rational):
4029
+ raise TypeError("can only pullback through a quaternion ideal")
4030
+
4031
+ if side == "left":
4032
+ if self.left_order() != J.right_order():
4033
+ raise ValueError("left order of self should be right order of J")
4034
+ if not self.is_integral() or not J.is_integral():
4035
+ raise NotImplementedError("quaternion ideal pullback not implemented for non-integral ideals")
4036
+ N = self.norm()
4037
+ if gcd(N, J.norm()) != 1:
4038
+ raise ValueError("self and J must have coprime norms")
4039
+ return J*self + N*J.left_order()
4040
+
4041
+ if side == "right":
4042
+ if self.right_order() != J.left_order():
4043
+ raise ValueError("right order of self should be left order of J")
4044
+ return self.conjugate().pullback(J.conjugate(), side='left').conjugate()
4045
+
4046
+ if side is None:
4047
+ is_side_left = bool(self.left_order() == J.right_order())
4048
+ is_side_right = bool(self.right_order() == J.left_order())
4049
+ if not is_side_left and not is_side_right:
4050
+ raise ValueError("left order of self must equal right order of J, or vice versa")
4051
+ if is_side_left and is_side_right:
4052
+ raise ValueError("self and J have same left and right orders, side of pullback must be specified")
4053
+ if is_side_left:
4054
+ return self.pullback(J, side='left')
4055
+ return self.pullback(J, side='right')
4056
+
4057
+ raise ValueError('side must be "left", "right" or None')
4058
+
4059
+ def is_equivalent(self, J, B=10, certificate=False, side=None):
4060
+ r"""
4061
+ Check whether ``self`` and ``J`` are equivalent as ideals.
4062
+ Tests equivalence as right ideals by default. Requires the underlying
4063
+ rational quaternion algebra to be definite.
4064
+
4065
+ INPUT:
4066
+
4067
+ - ``J`` -- a fractional quaternion ideal with same order as ``self``
4068
+
4069
+ - ``B`` -- a bound to compute and compare theta series before
4070
+ doing the full equivalence test
4071
+
4072
+ - ``certificate`` -- if ``True`` returns an element alpha such that
4073
+ alpha*J = I or J*alpha = I for right and left ideals respectively
4074
+
4075
+ - ``side`` -- if ``'left'`` performs left equivalence test. If ``'right'
4076
+ ``or ``None`` performs right ideal equivalence test
4077
+
4078
+ OUTPUT: boolean, or (boolean, alpha) if ``certificate`` is ``True``
4079
+
4080
+ EXAMPLES::
4081
+
4082
+ sage: # needs sage.schemes
4083
+ sage: R = BrandtModule(3,5).right_ideals(); len(R)
4084
+ 2
4085
+ sage: OO = R[0].left_order()
4086
+ sage: S = OO.right_ideal([3*a for a in R[0].basis()])
4087
+ sage: R[0].is_equivalent(S)
4088
+ doctest:...: DeprecationWarning: is_equivalent is deprecated,
4089
+ please use is_left_equivalent or is_right_equivalent
4090
+ accordingly instead
4091
+ See https://github.com/sagemath/sage/issues/37100 for details.
4092
+ True
4093
+ """
4094
+ from sage.misc.superseded import deprecation
4095
+ deprecation(37100, 'is_equivalent is deprecated, please use is_left_equivalent'
4096
+ ' or is_right_equivalent accordingly instead')
4097
+ if side == 'left':
4098
+ return self.is_left_equivalent(J, B, certificate)
4099
+ # If None, assume right ideals, for backwards compatibility
4100
+ return self.is_right_equivalent(J, B, certificate)
4101
+
4102
+ def is_left_equivalent(self, J, B=10, certificate=False):
4103
+ r"""
4104
+ Check whether ``self`` and ``J`` are equivalent as left ideals.
4105
+ Requires the underlying rational quaternion algebra to be definite.
4106
+
4107
+ INPUT:
4108
+
4109
+ - ``J`` -- a fractional quaternion left ideal with same order as ``self``
4110
+
4111
+ - ``B`` -- a bound to compute and compare theta series before
4112
+ doing the full equivalence test
4113
+
4114
+ - ``certificate`` -- if ``True`` returns an element alpha such that J*alpha=I
4115
+
4116
+ OUTPUT: boolean, or (boolean, alpha) if ``certificate`` is ``True``
4117
+ """
4118
+ if certificate:
4119
+ is_equiv, cert = self.conjugate().is_right_equivalent(J.conjugate(), B, True)
4120
+ if is_equiv:
4121
+ return True, cert.conjugate()
4122
+ return False, None
4123
+ return self.conjugate().is_right_equivalent(J.conjugate(), B, False)
4124
+
4125
+ def is_right_equivalent(self, J, B=10, certificate=False):
4126
+ r"""
4127
+ Check whether ``self`` and ``J`` are equivalent as right ideals.
4128
+ Requires the underlying rational quaternion algebra to be definite.
4129
+
4130
+ INPUT:
4131
+
4132
+ - ``J`` -- a fractional quaternion right ideal with same order as ``self``
4133
+
4134
+ - ``B`` -- a bound to compute and compare theta series before
4135
+ doing the full equivalence test
4136
+
4137
+ - ``certificate`` -- if ``True`` returns an element alpha such that alpha*J=I
4138
+
4139
+ OUTPUT: boolean, or (boolean, alpha) if ``certificate`` is ``True``
4140
+
4141
+ EXAMPLES::
4142
+
4143
+ sage: # needs sage.schemes
4144
+ sage: R = BrandtModule(3,5).right_ideals(); len(R)
4145
+ 2
4146
+ sage: R[0].is_right_equivalent(R[1])
4147
+ False
4148
+ sage: R[0].is_right_equivalent(R[0])
4149
+ True
4150
+ sage: OO = R[0].left_order()
4151
+ sage: S = OO.right_ideal([3*a for a in R[0].basis()])
4152
+ sage: R[0].is_right_equivalent(S, certificate=True)
4153
+ (True, 1/3)
4154
+ sage: -1/3*S == R[0]
4155
+ True
4156
+
4157
+ sage: B = QuaternionAlgebra(101)
4158
+ sage: i,j,k = B.gens()
4159
+ sage: I = B.maximal_order().unit_ideal()
4160
+ sage: beta = B.random_element()
4161
+ sage: while beta.is_zero():
4162
+ ....: beta = B.random_element()
4163
+ sage: J = beta*I
4164
+ sage: bool, alpha = I.is_right_equivalent(J, certificate=True)
4165
+ sage: bool
4166
+ True
4167
+ sage: alpha*J == I
4168
+ True
4169
+ """
4170
+ if not isinstance(J, QuaternionFractionalIdeal_rational):
4171
+ raise TypeError('J must be a fractional ideal'
4172
+ ' in a rational quaternion algebra')
4173
+
4174
+ if self.right_order() != J.right_order():
4175
+ raise ValueError('self and J must be right ideals over the same order')
4176
+
4177
+ if not self.quaternion_algebra().is_definite():
4178
+ raise NotImplementedError('equivalence test of ideals not implemented'
4179
+ ' for indefinite quaternion algebras')
4180
+
4181
+ # Just test theta series first; if the theta series are
4182
+ # different, the ideals are definitely not equivalent
4183
+ if B > 0 and self.theta_series_vector(B) != J.theta_series_vector(B):
4184
+ if certificate:
4185
+ return False, None
4186
+ return False
4187
+
4188
+ # The theta series are the same, so perhaps the ideals are equivalent
4189
+ # We adapt Prop 1.18 of [Piz1980]_ to right ideals to decide:
4190
+ # 1. Compute I * Jbar
4191
+ IJbar = self.multiply_by_conjugate(J)
4192
+
4193
+ # 2. Determine if there is alpha in I * Jbar with N(alpha) = N(I)*N(J)
4194
+ # Equivalently, we can simply call the principality test on IJbar,
4195
+ # but we rescale by 1/N(J) to make sure this test directly gives back
4196
+ # the correct alpha if a certificate is requested
4197
+ return (1/J.norm()*IJbar).is_principal(certificate)
4198
+
4199
+ def is_principal(self, certificate=False):
4200
+ r"""
4201
+ Check whether ``self`` is principal as a full rank quaternion ideal.
4202
+ Requires the underlying quaternion algebra to be definite.
4203
+ Independent of whether ``self`` is a left or a right ideal.
4204
+
4205
+ INPUT:
4206
+
4207
+ - ``certificate`` -- if ``True`` returns a generator alpha s.t.
4208
+ `I = \alpha O` where `O` is the right order of `I`
4209
+
4210
+ OUTPUT: boolean, or (boolean, alpha) if ``certificate`` is ``True``
4211
+
4212
+ EXAMPLES::
4213
+
4214
+ sage: B.<i,j,k> = QuaternionAlgebra(419)
4215
+ sage: O = B.quaternion_order([1/2 + 3/2*j, 1/6*i + 2/3*j + 1/2*k, 3*j, k])
4216
+ sage: beta = O.random_element()
4217
+ sage: while beta.is_zero():
4218
+ ....: beta = O.random_element()
4219
+ sage: I = O*beta
4220
+ sage: bool, alpha = I.is_principal(True)
4221
+ sage: bool
4222
+ True
4223
+ sage: I == O*alpha
4224
+ True
4225
+ """
4226
+ if not self.quaternion_algebra().is_definite():
4227
+ raise NotImplementedError('principality test not implemented in'
4228
+ ' indefinite quaternion algebras')
4229
+
4230
+ c = self.theta_series_vector(2)[1]
4231
+ if not certificate:
4232
+ return c != 0
4233
+ if certificate and c == 0:
4234
+ return False, None
4235
+
4236
+ # From this point on we know that self is principal, so it suffices to
4237
+ # find an element of minimal norm in self; see [Piz1980]_, Corollary 1.20.
4238
+ return True, self.minimal_element()
4239
+
4240
+ def __contains__(self, x):
4241
+ """
4242
+ Return whether ``x`` is in ``self``.
4243
+
4244
+ EXAMPLES::
4245
+
4246
+ sage: R.<i,j,k> = QuaternionAlgebra(-3, -13)
4247
+ sage: I = R.ideal([2+i, 3*i, 5*j, j+k])
4248
+ sage: 2+i in I
4249
+ True
4250
+ sage: 2+i+j+k in I
4251
+ True
4252
+ sage: 1+i in I
4253
+ False
4254
+ sage: 101*j + k in I
4255
+ True
4256
+ """
4257
+ try:
4258
+ x = self.quaternion_algebra()(x)
4259
+ return self.basis_matrix().transpose().solve_right(vector(x)) in ZZ**4
4260
+ except (ValueError, TypeError):
4261
+ return False
4262
+
4263
+ @cached_method
4264
+ def cyclic_right_subideals(self, p, alpha=None):
4265
+ r"""
4266
+ Let `I` = ``self``. This function returns the right subideals
4267
+ `J` of `I` such that `I/J` is an `\GF{p}`-vector space of
4268
+ dimension 2.
4269
+
4270
+ INPUT:
4271
+
4272
+ - ``p`` -- prime number (see below)
4273
+
4274
+ - ``alpha`` -- (default: ``None``) element of quaternion algebra,
4275
+ which can be used to parameterize the order of the
4276
+ ideals `J`. More precisely the `J`'s are the right annihilators
4277
+ of `(1,0) \alpha^i` for `i=0,1,2,...,p`
4278
+
4279
+ OUTPUT: list of right ideals
4280
+
4281
+ .. NOTE::
4282
+
4283
+ Currently, `p` must satisfy a bunch of conditions, or a
4284
+ :exc:`NotImplementedError` is raised. In particular, `p` must
4285
+ be odd and unramified in the quaternion algebra, must be
4286
+ coprime to the index of the right order in the maximal
4287
+ order, and also coprime to the normal of ``self``. (The Brandt
4288
+ modules code has a more general algorithm in some cases.)
4289
+
4290
+ EXAMPLES::
4291
+
4292
+ sage: # needs sage.schemes
4293
+ sage: B = BrandtModule(2,37); I = B.right_ideals()[0]
4294
+ sage: I.cyclic_right_subideals(3)
4295
+ [Fractional ideal (12, 444*i, 8 + 404*i + 4*j, 2 + 150*i + 2*j + 2*k),
4296
+ Fractional ideal (12, 444*i, 4 + 256*i + 4*j, 10 + 150*i + 2*j + 2*k),
4297
+ Fractional ideal (12, 444*i, 8 + 256*i + 4*j, 6 + 298*i + 2*j + 2*k),
4298
+ Fractional ideal (12, 444*i, 4 + 404*i + 4*j, 6 + 2*i + 2*j + 2*k)]
4299
+ sage: B = BrandtModule(5,389); I = B.right_ideals()[0]
4300
+ sage: C = I.cyclic_right_subideals(3); C
4301
+ [Fractional ideal (12, 4668*i, 10 + 3426*i + 2*j, 6 + 379*i + k),
4302
+ Fractional ideal (12, 4668*i, 2 + 3426*i + 2*j, 6 + 3491*i + k),
4303
+ Fractional ideal (12, 4 + 1556*i, 6 + 942*i + 6*j, 693*i + 2*j + k),
4304
+ Fractional ideal (12, 8 + 1556*i, 6 + 942*i + 6*j, 2 + 1007*i + 4*j + k)]
4305
+ sage: [(I.free_module()/J.free_module()).invariants() for J in C]
4306
+ [(3, 3), (3, 3), (3, 3), (3, 3)]
4307
+ sage: I.scale(3).cyclic_right_subideals(3)
4308
+ [Fractional ideal (36, 14004*i, 30 + 10278*i + 6*j, 18 + 1137*i + 3*k),
4309
+ Fractional ideal (36, 14004*i, 6 + 10278*i + 6*j, 18 + 10473*i + 3*k),
4310
+ Fractional ideal (36, 12 + 4668*i, 18 + 2826*i + 18*j, 2079*i + 6*j + 3*k),
4311
+ Fractional ideal (36, 24 + 4668*i, 18 + 2826*i + 18*j, 6 + 3021*i + 12*j + 3*k)]
4312
+ sage: C = I.scale(1/9).cyclic_right_subideals(3); C
4313
+ [Fractional ideal (4/3, 1556/3*i, 10/9 + 1142/3*i + 2/9*j, 2/3 + 379/9*i + 1/9*k),
4314
+ Fractional ideal (4/3, 1556/3*i, 2/9 + 1142/3*i + 2/9*j, 2/3 + 3491/9*i + 1/9*k),
4315
+ Fractional ideal (4/3, 4/9 + 1556/9*i, 2/3 + 314/3*i + 2/3*j, 77*i + 2/9*j + 1/9*k),
4316
+ Fractional ideal (4/3, 8/9 + 1556/9*i, 2/3 + 314/3*i + 2/3*j, 2/9 + 1007/9*i + 4/9*j + 1/9*k)]
4317
+ sage: [(I.scale(1/9).free_module()/J.free_module()).invariants() for J in C]
4318
+ [(3, 3), (3, 3), (3, 3), (3, 3)]
4319
+ sage: Q.<i,j,k> = QuaternionAlgebra(-2,-5)
4320
+ sage: I = Q.ideal([Q(1),i,j,k])
4321
+ sage: I.cyclic_right_subideals(3)
4322
+ [Fractional ideal (3, 3*i, 2 + j, i + k),
4323
+ Fractional ideal (3, 3*i, 1 + j, 2*i + k),
4324
+ Fractional ideal (3, 2 + i, 3*j, 2*j + k),
4325
+ Fractional ideal (3, 1 + i, 3*j, j + k)]
4326
+
4327
+ The general algorithm is not yet implemented here::
4328
+
4329
+ sage: I.cyclic_right_subideals(3)[0].cyclic_right_subideals(3) # needs sage.schemes
4330
+ Traceback (most recent call last):
4331
+ ...
4332
+ NotImplementedError: general algorithm not implemented
4333
+ (The given basis vectors must be linearly independent.)
4334
+ """
4335
+ R = self.right_order()
4336
+ Q = self.quaternion_algebra()
4337
+ basis = basis_for_quaternion_lattice(self.basis(), reverse=False)
4338
+ f = Q.modp_splitting_map(p)
4339
+ if alpha is not None:
4340
+ alpha = f(alpha)
4341
+ W = GF(p)**4
4342
+ try:
4343
+ A = W.span_of_basis([W(f(a).list()) for a in basis])
4344
+ scale = 1
4345
+ IB = matrix(map(list, basis))
4346
+ except (ValueError, ZeroDivisionError):
4347
+ # try rescaling the ideal.
4348
+ B, d = matrix(map(list, basis))._clear_denom()
4349
+ g = gcd(B.list())
4350
+ IB = B / g
4351
+ scale = g / d
4352
+ try:
4353
+ A = W.span_of_basis([W(f(Q(a.list())).list()) for a in IB.rows()])
4354
+ except (ValueError, ZeroDivisionError) as msg:
4355
+ # Here we could replace the ideal by an *equivalent*
4356
+ # ideal that works. This is always possible.
4357
+ # However, I haven't implemented that algorithm yet.
4358
+ raise NotImplementedError("general algorithm not implemented (%s)" % msg)
4359
+
4360
+ Ai = ~A.basis_matrix()
4361
+ AiB = Ai.change_ring(QQ) * IB
4362
+
4363
+ # Do not care about the denominator since we're really working in I/p*I.
4364
+ AiB, _ = AiB._clear_denom()
4365
+
4366
+ pB = p*IB
4367
+ pB, d = pB._clear_denom()
4368
+
4369
+ ans = []
4370
+ Z = matrix(ZZ, 2, 4)
4371
+
4372
+ P1 = P1List(p)
4373
+ if alpha is None:
4374
+ lines = P1
4375
+ else:
4376
+ x = alpha
4377
+ lines = []
4378
+ for _ in range(p + 1):
4379
+ lines.append(P1.normalize(x[0, 0], x[0, 1]))
4380
+ x *= alpha
4381
+
4382
+ for u, v in lines:
4383
+ # The following does:
4384
+ # z = matrix(QQ,2,4,[0,-v,0,u, -v,0,u,0],check=False) * AiB
4385
+ Z[0, 1] = -v
4386
+ Z[0, 3] = u
4387
+ Z[1, 0] = -v
4388
+ Z[1, 2] = u
4389
+ z = Z * AiB
4390
+ # Now construct submodule of the ideal I spanned by the
4391
+ # linear combinations given by z of the basis for J along
4392
+ # with p*I.
4393
+ G = (d*z).stack(pB) # have to multiply by d since we divide by it below in the "gens = " line.
4394
+ H = G._hnf_pari(0, include_zero_rows=False)
4395
+ gens = tuple(quaternion_algebra_cython.rational_quaternions_from_integral_matrix_and_denom(Q, H, d))
4396
+ if scale != 1:
4397
+ gens = tuple([scale * gg for gg in gens])
4398
+ J = R.right_ideal(gens, check=False)
4399
+ ans.append(J)
4400
+ return ans
4401
+
4402
+ def is_integral(self) -> bool:
4403
+ r"""
4404
+ Check whether the quaternion fractional ideal ``self`` is integral.
4405
+
4406
+ An ideal in a quaternion algebra is integral if and only if it is
4407
+ contained in its left order. If the left order is already defined
4408
+ this method just checks this definition, otherwise it uses one
4409
+ of the alternative definitions from Lemma 16.2.8 of [Voi2021]_.
4410
+
4411
+ EXAMPLES::
4412
+
4413
+ sage: R.<i,j,k> = QuaternionAlgebra(QQ, -1,-11)
4414
+ sage: I = R.ideal([2 + 2*j + 140*k, 2*i + 4*j + 150*k, 8*j + 104*k, 152*k])
4415
+ sage: I.is_integral()
4416
+ True
4417
+ sage: O = I.left_order()
4418
+ sage: I.is_integral()
4419
+ True
4420
+ sage: I = R.ideal([1/2 + 2*j + 140*k, 2*i + 4*j + 150*k, 8*j + 104*k, 152*k])
4421
+ sage: I.is_integral()
4422
+ False
4423
+ """
4424
+ if self.__left_order is not None:
4425
+ return self.free_module() <= self.left_order().free_module()
4426
+ elif self.__right_order is not None:
4427
+ return self.free_module() <= self.right_order().free_module()
4428
+ else:
4429
+ self_square = self**2
4430
+ return self_square.free_module() <= self.free_module()
4431
+
4432
+ def primitive_decomposition(self):
4433
+ r"""
4434
+ Let `I` = ``self``. If `I` is an integral left `\mathcal{O}`-ideal return its decomposition
4435
+ as an equivalent primitive ideal and an integer such that their product is the initial ideal.
4436
+
4437
+ OUTPUTS: A primitive ideal equivalent to `I`, i.e. an equivalent ideal not contained
4438
+ in `n\mathcal{O}` for any `n>0`, and the smallest integer `g` such that `I \subset g\mathcal{O}`.
4439
+
4440
+ EXAMPLES::
4441
+
4442
+ sage: A.<i,j,k> = QuaternionAlgebra(QQ, -1,-11)
4443
+ sage: I = A.ideal([1/2 + 1/2*i + 1/2*j + 3/2*k, i + k, j + k, 2*k])
4444
+ sage: I.primitive_decomposition()
4445
+ (Fractional ideal (1/2 + 1/2*i + 1/2*j + 3/2*k, i + k, j + k, 2*k), 1)
4446
+ sage: J = A.ideal([7/2 + 7/2*i + 49/2*j + 91/2*k, 7*i + 21*k, 35*j + 35*k, 70*k])
4447
+ sage: Jequiv, g = J.primitive_decomposition()
4448
+ sage: Jequiv*g == J
4449
+ True
4450
+ sage: Jequiv, g
4451
+ (Fractional ideal (10, 5 + 5*i, 3 + j, 13/2 + 7/2*i + 1/2*j + 1/2*k), 7)
4452
+
4453
+ TESTS:
4454
+
4455
+ Check that randomly generated ideals decompose as expected::
4456
+
4457
+ sage: for d in ( m for m in range(400, 750) if is_squarefree(m) ): # long time (7s)
4458
+ ....: A = QuaternionAlgebra(d)
4459
+ ....: O = A.maximal_order()
4460
+ ....: for _ in range(10):
4461
+ ....: a = O.random_element()
4462
+ ....: if not a.is_constant(): # avoids a = 0
4463
+ ....: I = a*O + a.reduced_norm()*O
4464
+ ....: if I.is_integral():
4465
+ ....: J,g = I.primitive_decomposition()
4466
+ ....: assert J*g == I
4467
+ ....: assert J.is_primitive()
4468
+ """
4469
+ if not self.is_integral():
4470
+ raise ValueError("primitive ideals are defined only for integral ideals")
4471
+
4472
+ I_basis = self.basis_matrix()
4473
+ O_basis = self.left_order().basis_matrix()
4474
+
4475
+ # Write I in the basis of its left order via rref
4476
+ M = O_basis.solve_left(I_basis)
4477
+ g = Integer(gcd(M.list()))
4478
+
4479
+ # If g is 1 then the ideal is primitive
4480
+ if g.is_one():
4481
+ return self, g
4482
+
4483
+ J = self.scale(1/g)
4484
+
4485
+ return J, g
4486
+
4487
+ def is_primitive(self) -> bool:
4488
+ r"""
4489
+ Check whether the quaternion fractional ideal ``self`` is primitive.
4490
+
4491
+ An integral left `\mathcal{O}`-ideal for some order `\mathcal{O}`
4492
+ is called primitive if for all integers `n > 1` it is not
4493
+ contained in `n\mathcal{O}`.
4494
+
4495
+ EXAMPLES::
4496
+
4497
+ sage: A.<i,j,k> = QuaternionAlgebra(QQ, -1,-11)
4498
+ sage: I = A.ideal([1/2 + 1/2*i + 1/2*j + 3/2*k, i + k, j + k, 2*k])
4499
+ sage: I.is_primitive()
4500
+ True
4501
+ sage: (2*I).is_primitive()
4502
+ False
4503
+ """
4504
+ _, g = self.primitive_decomposition()
4505
+ return g.is_one()
4506
+
4507
+ #######################################################################
4508
+ # Some utility functions that are needed here and are too
4509
+ # specialized to go elsewhere.
4510
+ #######################################################################
4511
+
4512
+
4513
+ def basis_for_quaternion_lattice(gens, reverse=True):
4514
+ r"""
4515
+ Return a basis for the `\ZZ`-lattice in a quaternion algebra
4516
+ spanned by the given gens.
4517
+
4518
+ INPUT:
4519
+
4520
+ - ``gens`` -- list of elements of a single quaternion algebra
4521
+
4522
+ - ``reverse`` -- when computing the HNF do it on the basis
4523
+ `(k,j,i,1)` instead of `(1,i,j,k)`; this ensures that if
4524
+ ``gens`` are the generators for a fractional ideal (in
4525
+ particular, an order), the first returned basis vector
4526
+ equals the norm of the ideal (in case of an order, `1`)
4527
+
4528
+ EXAMPLES::
4529
+
4530
+ sage: from sage.algebras.quatalg.quaternion_algebra import basis_for_quaternion_lattice
4531
+ sage: A.<i,j,k> = QuaternionAlgebra(-1,-7)
4532
+ sage: basis_for_quaternion_lattice([i+j, i-j, 2*k, A(1/3)])
4533
+ [1/3, 2*i, i + j, 2*k]
4534
+ sage: basis_for_quaternion_lattice([A(1),i,j,k])
4535
+ [1, i, j, k]
4536
+ """
4537
+ if not gens:
4538
+ return []
4539
+ Z, d = quaternion_algebra_cython.integral_matrix_and_denom_from_rational_quaternions(gens, reverse)
4540
+ H = Z._hnf_pari(0, include_zero_rows=False)
4541
+ A = gens[0].parent()
4542
+ return quaternion_algebra_cython.rational_quaternions_from_integral_matrix_and_denom(A, H, d, reverse)
4543
+
4544
+
4545
+ def intersection_of_row_modules_over_ZZ(v):
4546
+ r"""
4547
+ Intersect the `\ZZ`-modules with basis matrices the full rank `4 \times 4`
4548
+ `\QQ`-matrices in the list v.
4549
+
4550
+ The returned intersection is
4551
+ represented by a `4 \times 4` matrix over `\QQ`. This can also be done
4552
+ using modules and intersection, but that would take over twice as long
4553
+ because of overhead, hence this function.
4554
+
4555
+ EXAMPLES::
4556
+
4557
+ sage: a = matrix(QQ,4,[-2, 0, 0, 0, 0, -1, -1, 1, 2, -1/2, 0, 0, 1, 1, -1, 0])
4558
+ sage: b = matrix(QQ,4,[0, -1/2, 0, -1/2, 2, 1/2, -1, -1/2, 1, 2, 1, -2, 0, -1/2, -2, 0])
4559
+ sage: c = matrix(QQ,4,[0, 1, 0, -1/2, 0, 0, 2, 2, 0, -1/2, 1/2, -1, 1, -1, -1/2, 0])
4560
+ sage: v = [a,b,c]
4561
+ sage: from sage.algebras.quatalg.quaternion_algebra import intersection_of_row_modules_over_ZZ
4562
+ sage: M = intersection_of_row_modules_over_ZZ(v); M
4563
+ [ 2 0 -1 -1]
4564
+ [ -4 1 1 -3]
4565
+ [ -3 19/2 -1 -4]
4566
+ [ 2 -3 -8 4]
4567
+ sage: M2 = a.row_module(ZZ).intersection(b.row_module(ZZ)).intersection(c.row_module(ZZ))
4568
+ sage: M.row_module(ZZ) == M2
4569
+ True
4570
+ """
4571
+ if len(v) <= 0:
4572
+ raise ValueError("v must have positive length")
4573
+ if len(v) == 1:
4574
+ return v[0]
4575
+ elif len(v) == 2:
4576
+ # real work - the base case
4577
+ a, b = v
4578
+ s, _ = a.stack(b)._clear_denom()
4579
+ s = s.transpose()
4580
+ K = s.right_kernel_matrix(algorithm='pari', basis='computed')
4581
+ n = a.nrows()
4582
+ return K.matrix_from_columns(range(n)) * a
4583
+ else:
4584
+ # induct
4585
+ w = intersection_of_row_modules_over_ZZ(v[:2])
4586
+ return intersection_of_row_modules_over_ZZ([w] + v[2:])
4587
+
4588
+
4589
+ def normalize_basis_at_p(e, p, B=QuaternionAlgebraElement_abstract.pair):
4590
+ r"""
4591
+ Compute a (at ``p``) normalized basis from the given basis ``e``
4592
+ of a `\ZZ`-module.
4593
+
4594
+ The returned basis is (at ``p``) a `\ZZ_p` basis for the same
4595
+ module, and has the property that with respect to it the quadratic
4596
+ form induced by the bilinear form B is represented as a orthogonal
4597
+ sum of atomic forms multiplied by p-powers.
4598
+
4599
+ If `p \neq 2` this means that the form is diagonal with respect to
4600
+ this basis.
4601
+
4602
+ If `p = 2` there may be additional 2-dimensional subspaces on which
4603
+ the form is represented as `2^e (ax^2 + bxy + cx^2)` with
4604
+ `0 = v_2(b) = v_2(a) \leq v_2(c)`.
4605
+
4606
+ INPUT:
4607
+
4608
+ - ``e`` -- list; basis of a `\ZZ` module
4609
+ (WARNING: will be modified!)
4610
+
4611
+ - ``p`` -- prime for at which the basis should be normalized
4612
+
4613
+ - ``B`` --
4614
+ (default: :meth:`QuaternionAlgebraElement_abstract.pair`)
4615
+ a bilinear form with respect to which to normalize
4616
+
4617
+ OUTPUT:
4618
+
4619
+ - A list containing two-element tuples: The first element of
4620
+ each tuple is a basis element, the second the valuation of
4621
+ the orthogonal summand to which it belongs. The list is sorted
4622
+ by ascending valuation.
4623
+
4624
+ EXAMPLES::
4625
+
4626
+ sage: from sage.algebras.quatalg.quaternion_algebra import normalize_basis_at_p
4627
+ sage: A.<i,j,k> = QuaternionAlgebra(-1, -1)
4628
+ sage: e = [A(1), i, j, k]
4629
+ sage: normalize_basis_at_p(e, 2)
4630
+ [(1, 0), (i, 0), (j, 0), (k, 0)]
4631
+
4632
+ sage: A.<i,j,k> = QuaternionAlgebra(210)
4633
+ sage: e = [A(1), i, j, k]
4634
+ sage: normalize_basis_at_p(e, 2)
4635
+ [(1, 0), (i, 1), (j, 1), (k, 2)]
4636
+
4637
+ sage: A.<i,j,k> = QuaternionAlgebra(286)
4638
+ sage: e = [A(1), k, 1/2*j + 1/2*k, 1/2 + 1/2*i + 1/2*k]
4639
+ sage: normalize_basis_at_p(e, 5)
4640
+ [(1, 0), (1/2*j + 1/2*k, 0), (-5/6*j + 1/6*k, 1), (1/2*i, 1)]
4641
+
4642
+ sage: A.<i,j,k> = QuaternionAlgebra(-1,-7)
4643
+ sage: e = [A(1), k, j, 1/2 + 1/2*i + 1/2*j + 1/2*k]
4644
+ sage: normalize_basis_at_p(e, 2)
4645
+ [(1, 0), (1/2 + 1/2*i + 1/2*j + 1/2*k, 0), (-34/105*i - 463/735*j + 71/105*k, 1),
4646
+ (1/7*i - 8/49*j + 1/7*k, 1)]
4647
+
4648
+ TESTS:
4649
+
4650
+ We check that the second part of :issue:`37217` is fixed::
4651
+
4652
+ sage: A.<i,j,k> = QuaternionAlgebra(-1,-7)
4653
+ sage: e = [A(1), k, j, 1/2 + 1/2*i + 1/2*j + 1/2*k]
4654
+ sage: e_norm = normalize_basis_at_p(e, 2)
4655
+ sage: V = QQ**4
4656
+ sage: V.span([V(x.coefficient_tuple()) for (x,_) in e_norm]).dimension()
4657
+ 4
4658
+ """
4659
+
4660
+ N = len(e)
4661
+ if N == 0:
4662
+ return []
4663
+ else:
4664
+ min_m, min_n, min_v = 0, 0, infinity
4665
+
4666
+ # Find two basis vector on which the bilinear form has minimal
4667
+ # p-valuation. If there is more than one such pair, always
4668
+ # prefer diagonal entries over any other and (secondary) take
4669
+ # min_m and then min_n as small as possible
4670
+ for m in range(N):
4671
+ for n in range(m, N):
4672
+ v = B(e[m], e[n]).valuation(p)
4673
+ if v < min_v or (v == min_v and (min_m != min_n) and (m == n)):
4674
+ min_m, min_n, min_v = m, n, v
4675
+
4676
+ if (min_m == min_n) or p != 2: # In this case we can diagonalize
4677
+ if min_m == min_n: # Diagonal entry has minimal valuation
4678
+ f0 = e[min_m]
4679
+ else:
4680
+ f0 = e[min_m] + e[min_n] # Only off-diagonal entries have min. val., but p!=2
4681
+
4682
+ # Swap with first vector
4683
+ e[0], e[min_m] = e[min_m], e[0]
4684
+
4685
+ # Orthogonalize remaining vectors with respect to f
4686
+ c = B(f0, f0)
4687
+ for l in range(1, N):
4688
+ e[l] = e[l] - B(e[l], f0) / c * f0
4689
+
4690
+ # Recursively normalize remaining vectors
4691
+ f = normalize_basis_at_p(e[1:], p)
4692
+ f.insert(0, (f0, min_v - valuation(p, 2)))
4693
+ return f
4694
+
4695
+ else: # p = 2 and only off-diagonal entries have min. val., gives 2-dim. block
4696
+ # first diagonal entry should have smaller valuation
4697
+ if B(e[min_m], e[min_m]).valuation(p) > B(e[min_n], e[min_n]).valuation(p):
4698
+ e[min_m], e[min_n] = e[min_n], e[min_m]
4699
+
4700
+ f0 = p**min_v / B(e[min_m], e[min_n]) * e[min_m]
4701
+ f1 = e[min_n]
4702
+
4703
+ # Ensures that (B(f0,f0)/2).valuation(p) <= B(f0,f1).valuation(p)
4704
+ if B(f0, f1).valuation(p) + 1 < B(f0, f0).valuation(p):
4705
+ g = f0
4706
+ f0 += f1
4707
+ f1 = g
4708
+
4709
+ # Make remaining vectors orthogonal to span of f0, f1
4710
+ e[min_m] = e[0]
4711
+ e[min_n] = e[1]
4712
+
4713
+ B00 = B(f0, f0)
4714
+ B11 = B(f1, f1)
4715
+ B01 = B(f0, f1)
4716
+ d = B00 * B11 - B01**2
4717
+ tu = [(B01 * B(f1, e[l]) - B11 * B(f0, e[l]),
4718
+ B01 * B(f0, e[l]) - B00 * B(f1, e[l])) for l in range(2, N)]
4719
+
4720
+ e[2:N] = [e[l] + tu[l-2][0]/d * f0 + tu[l-2][1]/d * f1 for l in range(2, N)]
4721
+
4722
+ # Recursively normalize remaining vectors
4723
+ f = normalize_basis_at_p(e[2:N], p)
4724
+ return [(f0, min_v), (f1, min_v)] + f
4725
+
4726
+
4727
+ def maxord_solve_aux_eq(a, b, p):
4728
+ r"""
4729
+ Given ``a`` and ``b`` and an even prime ideal ``p`` find
4730
+ (y,z,w) with y a unit mod `p^{2e}` such that
4731
+
4732
+ .. MATH::
4733
+
4734
+ 1 - ay^2 - bz^2 + abw^2 \equiv 0 mod p^{2e},
4735
+
4736
+ where `e` is the ramification index of `p`.
4737
+
4738
+ Currently only `p=2` is implemented by hardcoding solutions.
4739
+
4740
+ INPUT:
4741
+
4742
+ - ``a`` -- integer with `v_p(a) = 0`
4743
+
4744
+ - ``b`` -- integer with `v_p(b) \in \{0,1\}`
4745
+
4746
+ - ``p`` -- even prime ideal (actually only ``p=ZZ(2)`` is implemented)
4747
+
4748
+ OUTPUT: a tuple `(y, z, w)`
4749
+
4750
+ EXAMPLES::
4751
+
4752
+ sage: from sage.algebras.quatalg.quaternion_algebra import maxord_solve_aux_eq
4753
+ sage: for a in [1,3]:
4754
+ ....: for b in [1,2,3]:
4755
+ ....: (y,z,w) = maxord_solve_aux_eq(a, b, 2)
4756
+ ....: assert mod(y, 4) == 1 or mod(y, 4) == 3
4757
+ ....: assert mod(1 - a*y^2 - b*z^2 + a*b*w^2, 4) == 0
4758
+ """
4759
+ if p != ZZ(2):
4760
+ raise NotImplementedError("algorithm only implemented over ZZ at the moment")
4761
+
4762
+ v_a = a.valuation(p)
4763
+ v_b = b.valuation(p)
4764
+
4765
+ if v_a != 0:
4766
+ raise RuntimeError("a must have v_p(a)=0")
4767
+ if v_b != 0 and v_b != 1:
4768
+ raise RuntimeError("b must have v_p(b) in {0,1}")
4769
+
4770
+ R = ZZ.quo(ZZ(4))
4771
+ lut = {(R(1), R(1)): (1, 1, 1),
4772
+ (R(1), R(2)): (1, 0, 0),
4773
+ (R(1), R(3)): (1, 0, 0),
4774
+ (R(3), R(1)): (1, 1, 1),
4775
+ (R(3), R(2)): (1, 0, 1),
4776
+ (R(3), R(3)): (1, 1, 1)}
4777
+
4778
+ return lut[(R(a), R(b))]