passagemath-singular 10.6.31rc3__cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.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.
- PySingular.cpython-314-aarch64-linux-gnu.so +0 -0
- passagemath_singular-10.6.31rc3.dist-info/METADATA +183 -0
- passagemath_singular-10.6.31rc3.dist-info/RECORD +490 -0
- passagemath_singular-10.6.31rc3.dist-info/WHEEL +6 -0
- passagemath_singular-10.6.31rc3.dist-info/top_level.txt +3 -0
- passagemath_singular.libs/libSingular-4-6a2a8666.4.1.so +0 -0
- passagemath_singular.libs/libcddgmp-ac579979.so.0.1.3 +0 -0
- passagemath_singular.libs/libfactory-4-66e33516.4.1.so +0 -0
- passagemath_singular.libs/libflint-81de1160.so.21.0.0 +0 -0
- passagemath_singular.libs/libgf2x-fbd36f80.so.3.0.0 +0 -0
- passagemath_singular.libs/libgfortran-e1b7dfc8.so.5.0.0 +0 -0
- passagemath_singular.libs/libgmp-93ebf16a.so.10.5.0 +0 -0
- passagemath_singular.libs/libgsl-e3525837.so.28.0.0 +0 -0
- passagemath_singular.libs/libmpfr-e0f11cf3.so.6.2.1 +0 -0
- passagemath_singular.libs/libntl-0043a3a2.so.44.0.1 +0 -0
- passagemath_singular.libs/libomalloc-0-06512335.9.6.so +0 -0
- passagemath_singular.libs/libopenblasp-r0-4c5b64b1.3.29.so +0 -0
- passagemath_singular.libs/libpolys-4-cb7246b5.4.1.so +0 -0
- passagemath_singular.libs/libreadline-28330744.so.8.2 +0 -0
- passagemath_singular.libs/libsingular_resources-4-8c425241.4.1.so +0 -0
- passagemath_singular.libs/libtinfo-f81c2d16.so.6.3 +0 -0
- sage/algebras/all__sagemath_singular.py +3 -0
- sage/algebras/fusion_rings/all.py +19 -0
- sage/algebras/fusion_rings/f_matrix.py +2448 -0
- sage/algebras/fusion_rings/fast_parallel_fmats_methods.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/algebras/fusion_rings/fast_parallel_fmats_methods.pxd +5 -0
- sage/algebras/fusion_rings/fast_parallel_fmats_methods.pyx +538 -0
- sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pxd +3 -0
- sage/algebras/fusion_rings/fast_parallel_fusion_ring_braid_repn.pyx +331 -0
- sage/algebras/fusion_rings/fusion_double.py +899 -0
- sage/algebras/fusion_rings/fusion_ring.py +1580 -0
- sage/algebras/fusion_rings/poly_tup_engine.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/algebras/fusion_rings/poly_tup_engine.pxd +24 -0
- sage/algebras/fusion_rings/poly_tup_engine.pyx +579 -0
- sage/algebras/fusion_rings/shm_managers.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/algebras/fusion_rings/shm_managers.pxd +24 -0
- sage/algebras/fusion_rings/shm_managers.pyx +780 -0
- sage/algebras/letterplace/all.py +1 -0
- sage/algebras/letterplace/free_algebra_element_letterplace.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/algebras/letterplace/free_algebra_element_letterplace.pxd +18 -0
- sage/algebras/letterplace/free_algebra_element_letterplace.pyx +755 -0
- sage/algebras/letterplace/free_algebra_letterplace.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/algebras/letterplace/free_algebra_letterplace.pxd +35 -0
- sage/algebras/letterplace/free_algebra_letterplace.pyx +914 -0
- sage/algebras/letterplace/letterplace_ideal.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/algebras/letterplace/letterplace_ideal.pyx +408 -0
- sage/algebras/quatalg/all.py +2 -0
- sage/algebras/quatalg/quaternion_algebra.py +4778 -0
- sage/algebras/quatalg/quaternion_algebra_cython.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/algebras/quatalg/quaternion_algebra_cython.pyx +261 -0
- sage/algebras/quatalg/quaternion_algebra_element.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/algebras/quatalg/quaternion_algebra_element.pxd +29 -0
- sage/algebras/quatalg/quaternion_algebra_element.pyx +2176 -0
- sage/all__sagemath_singular.py +11 -0
- sage/ext_data/all__sagemath_singular.py +1 -0
- sage/ext_data/singular/function_field/core.lib +98 -0
- sage/interfaces/all__sagemath_singular.py +1 -0
- sage/interfaces/singular.py +2835 -0
- sage/libs/all__sagemath_singular.py +1 -0
- sage/libs/singular/__init__.py +1 -0
- sage/libs/singular/decl.pxd +1168 -0
- sage/libs/singular/function.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/libs/singular/function.pxd +87 -0
- sage/libs/singular/function.pyx +1901 -0
- sage/libs/singular/function_factory.py +61 -0
- sage/libs/singular/groebner_strategy.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/libs/singular/groebner_strategy.pxd +22 -0
- sage/libs/singular/groebner_strategy.pyx +582 -0
- sage/libs/singular/option.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/libs/singular/option.pyx +671 -0
- sage/libs/singular/polynomial.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/libs/singular/polynomial.pxd +39 -0
- sage/libs/singular/polynomial.pyx +661 -0
- sage/libs/singular/ring.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/libs/singular/ring.pxd +58 -0
- sage/libs/singular/ring.pyx +893 -0
- sage/libs/singular/singular.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/libs/singular/singular.pxd +72 -0
- sage/libs/singular/singular.pyx +1944 -0
- sage/libs/singular/standard_options.py +145 -0
- sage/matrix/all__sagemath_singular.py +1 -0
- sage/matrix/matrix_mpolynomial_dense.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/matrix/matrix_mpolynomial_dense.pxd +7 -0
- sage/matrix/matrix_mpolynomial_dense.pyx +615 -0
- sage/rings/all__sagemath_singular.py +1 -0
- sage/rings/function_field/all__sagemath_singular.py +1 -0
- sage/rings/function_field/derivations_polymod.py +911 -0
- sage/rings/function_field/element_polymod.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/rings/function_field/element_polymod.pyx +406 -0
- sage/rings/function_field/function_field_polymod.py +2611 -0
- sage/rings/function_field/ideal_polymod.py +1775 -0
- sage/rings/function_field/order_polymod.py +1475 -0
- sage/rings/function_field/place_polymod.py +681 -0
- sage/rings/polynomial/all__sagemath_singular.py +1 -0
- sage/rings/polynomial/multi_polynomial_ideal_libsingular.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/rings/polynomial/multi_polynomial_ideal_libsingular.pxd +5 -0
- sage/rings/polynomial/multi_polynomial_ideal_libsingular.pyx +339 -0
- sage/rings/polynomial/multi_polynomial_libsingular.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/rings/polynomial/multi_polynomial_libsingular.pxd +30 -0
- sage/rings/polynomial/multi_polynomial_libsingular.pyx +6277 -0
- sage/rings/polynomial/plural.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/rings/polynomial/plural.pxd +48 -0
- sage/rings/polynomial/plural.pyx +3171 -0
- sage/symbolic/all__sagemath_singular.py +1 -0
- sage/symbolic/comparison_impl.pxi +428 -0
- sage/symbolic/constants_c_impl.pxi +178 -0
- sage/symbolic/expression.cpython-314-aarch64-linux-gnu.so +0 -0
- sage/symbolic/expression.pxd +7 -0
- sage/symbolic/expression.pyx +14200 -0
- sage/symbolic/getitem_impl.pxi +202 -0
- sage/symbolic/pynac.pxi +572 -0
- sage/symbolic/pynac_constant_impl.pxi +133 -0
- sage/symbolic/pynac_function_impl.pxi +206 -0
- sage/symbolic/pynac_impl.pxi +2576 -0
- sage/symbolic/pynac_wrap.h +124 -0
- sage/symbolic/series_impl.pxi +272 -0
- sage/symbolic/substitution_map_impl.pxi +94 -0
- sage_wheels/bin/ESingular +0 -0
- sage_wheels/bin/Singular +0 -0
- sage_wheels/bin/TSingular +0 -0
- sage_wheels/lib/singular/MOD/cohomo.la +41 -0
- sage_wheels/lib/singular/MOD/cohomo.so +0 -0
- sage_wheels/lib/singular/MOD/customstd.la +41 -0
- sage_wheels/lib/singular/MOD/customstd.so +0 -0
- sage_wheels/lib/singular/MOD/freealgebra.la +41 -0
- sage_wheels/lib/singular/MOD/freealgebra.so +0 -0
- sage_wheels/lib/singular/MOD/gfanlib.la +41 -0
- sage_wheels/lib/singular/MOD/gfanlib.so +0 -0
- sage_wheels/lib/singular/MOD/gitfan.la +41 -0
- sage_wheels/lib/singular/MOD/gitfan.so +0 -0
- sage_wheels/lib/singular/MOD/interval.la +41 -0
- sage_wheels/lib/singular/MOD/interval.so +0 -0
- sage_wheels/lib/singular/MOD/loctriv.la +41 -0
- sage_wheels/lib/singular/MOD/loctriv.so +0 -0
- sage_wheels/lib/singular/MOD/machinelearning.la +41 -0
- sage_wheels/lib/singular/MOD/machinelearning.so +0 -0
- sage_wheels/lib/singular/MOD/p_Procs_FieldGeneral.la +41 -0
- sage_wheels/lib/singular/MOD/p_Procs_FieldGeneral.so +0 -0
- sage_wheels/lib/singular/MOD/p_Procs_FieldIndep.la +41 -0
- sage_wheels/lib/singular/MOD/p_Procs_FieldIndep.so +0 -0
- sage_wheels/lib/singular/MOD/p_Procs_FieldQ.la +41 -0
- sage_wheels/lib/singular/MOD/p_Procs_FieldQ.so +0 -0
- sage_wheels/lib/singular/MOD/p_Procs_FieldZp.la +41 -0
- sage_wheels/lib/singular/MOD/p_Procs_FieldZp.so +0 -0
- sage_wheels/lib/singular/MOD/partialgb.la +41 -0
- sage_wheels/lib/singular/MOD/partialgb.so +0 -0
- sage_wheels/lib/singular/MOD/pyobject.la +41 -0
- sage_wheels/lib/singular/MOD/pyobject.so +0 -0
- sage_wheels/lib/singular/MOD/singmathic.la +41 -0
- sage_wheels/lib/singular/MOD/singmathic.so +0 -0
- sage_wheels/lib/singular/MOD/sispasm.la +41 -0
- sage_wheels/lib/singular/MOD/sispasm.so +0 -0
- sage_wheels/lib/singular/MOD/subsets.la +41 -0
- sage_wheels/lib/singular/MOD/subsets.so +0 -0
- sage_wheels/lib/singular/MOD/systhreads.la +41 -0
- sage_wheels/lib/singular/MOD/systhreads.so +0 -0
- sage_wheels/lib/singular/MOD/syzextra.la +41 -0
- sage_wheels/lib/singular/MOD/syzextra.so +0 -0
- sage_wheels/libexec/singular/MOD/change_cost +0 -0
- sage_wheels/libexec/singular/MOD/singularsurf +11 -0
- sage_wheels/libexec/singular/MOD/singularsurf_jupyter +9 -0
- sage_wheels/libexec/singular/MOD/singularsurf_win +10 -0
- sage_wheels/libexec/singular/MOD/solve_IP +0 -0
- sage_wheels/libexec/singular/MOD/surfex +16 -0
- sage_wheels/libexec/singular/MOD/toric_ideal +0 -0
- sage_wheels/share/factory/gftables/10201 +342 -0
- sage_wheels/share/factory/gftables/1024 +37 -0
- sage_wheels/share/factory/gftables/10609 +356 -0
- sage_wheels/share/factory/gftables/11449 +384 -0
- sage_wheels/share/factory/gftables/11881 +398 -0
- sage_wheels/share/factory/gftables/121 +6 -0
- sage_wheels/share/factory/gftables/12167 +408 -0
- sage_wheels/share/factory/gftables/125 +7 -0
- sage_wheels/share/factory/gftables/12769 +428 -0
- sage_wheels/share/factory/gftables/128 +7 -0
- sage_wheels/share/factory/gftables/1331 +47 -0
- sage_wheels/share/factory/gftables/1369 +48 -0
- sage_wheels/share/factory/gftables/14641 +490 -0
- sage_wheels/share/factory/gftables/15625 +523 -0
- sage_wheels/share/factory/gftables/16 +3 -0
- sage_wheels/share/factory/gftables/16129 +540 -0
- sage_wheels/share/factory/gftables/16384 +549 -0
- sage_wheels/share/factory/gftables/16807 +563 -0
- sage_wheels/share/factory/gftables/1681 +58 -0
- sage_wheels/share/factory/gftables/169 +8 -0
- sage_wheels/share/factory/gftables/17161 +574 -0
- sage_wheels/share/factory/gftables/1849 +64 -0
- sage_wheels/share/factory/gftables/18769 +628 -0
- sage_wheels/share/factory/gftables/19321 +646 -0
- sage_wheels/share/factory/gftables/19683 +659 -0
- sage_wheels/share/factory/gftables/2048 +71 -0
- sage_wheels/share/factory/gftables/2187 +75 -0
- sage_wheels/share/factory/gftables/2197 +76 -0
- sage_wheels/share/factory/gftables/2209 +76 -0
- sage_wheels/share/factory/gftables/22201 +742 -0
- sage_wheels/share/factory/gftables/22801 +762 -0
- sage_wheels/share/factory/gftables/2401 +82 -0
- sage_wheels/share/factory/gftables/243 +11 -0
- sage_wheels/share/factory/gftables/24389 +815 -0
- sage_wheels/share/factory/gftables/24649 +824 -0
- sage_wheels/share/factory/gftables/25 +3 -0
- sage_wheels/share/factory/gftables/256 +11 -0
- sage_wheels/share/factory/gftables/26569 +888 -0
- sage_wheels/share/factory/gftables/27 +3 -0
- sage_wheels/share/factory/gftables/27889 +932 -0
- sage_wheels/share/factory/gftables/2809 +96 -0
- sage_wheels/share/factory/gftables/28561 +954 -0
- sage_wheels/share/factory/gftables/289 +12 -0
- sage_wheels/share/factory/gftables/29791 +995 -0
- sage_wheels/share/factory/gftables/29929 +1000 -0
- sage_wheels/share/factory/gftables/3125 +107 -0
- sage_wheels/share/factory/gftables/32 +4 -0
- sage_wheels/share/factory/gftables/32041 +1070 -0
- sage_wheels/share/factory/gftables/32761 +1094 -0
- sage_wheels/share/factory/gftables/32768 +1095 -0
- sage_wheels/share/factory/gftables/343 +14 -0
- sage_wheels/share/factory/gftables/3481 +118 -0
- sage_wheels/share/factory/gftables/361 +14 -0
- sage_wheels/share/factory/gftables/36481 +1218 -0
- sage_wheels/share/factory/gftables/3721 +126 -0
- sage_wheels/share/factory/gftables/37249 +1244 -0
- sage_wheels/share/factory/gftables/38809 +1296 -0
- sage_wheels/share/factory/gftables/39601 +1322 -0
- sage_wheels/share/factory/gftables/4 +3 -0
- sage_wheels/share/factory/gftables/4096 +139 -0
- sage_wheels/share/factory/gftables/44521 +1486 -0
- sage_wheels/share/factory/gftables/4489 +152 -0
- sage_wheels/share/factory/gftables/49 +4 -0
- sage_wheels/share/factory/gftables/4913 +166 -0
- sage_wheels/share/factory/gftables/49729 +1660 -0
- sage_wheels/share/factory/gftables/5041 +170 -0
- sage_wheels/share/factory/gftables/50653 +1691 -0
- sage_wheels/share/factory/gftables/512 +20 -0
- sage_wheels/share/factory/gftables/51529 +1720 -0
- sage_wheels/share/factory/gftables/52441 +1750 -0
- sage_wheels/share/factory/gftables/529 +20 -0
- sage_wheels/share/factory/gftables/5329 +180 -0
- sage_wheels/share/factory/gftables/54289 +1812 -0
- sage_wheels/share/factory/gftables/57121 +1906 -0
- sage_wheels/share/factory/gftables/58081 +1938 -0
- sage_wheels/share/factory/gftables/59049 +1971 -0
- sage_wheels/share/factory/gftables/6241 +210 -0
- sage_wheels/share/factory/gftables/625 +23 -0
- sage_wheels/share/factory/gftables/63001 +2102 -0
- sage_wheels/share/factory/gftables/64 +5 -0
- sage_wheels/share/factory/gftables/6561 +221 -0
- sage_wheels/share/factory/gftables/6859 +231 -0
- sage_wheels/share/factory/gftables/6889 +232 -0
- sage_wheels/share/factory/gftables/729 +27 -0
- sage_wheels/share/factory/gftables/7921 +266 -0
- sage_wheels/share/factory/gftables/8 +3 -0
- sage_wheels/share/factory/gftables/81 +5 -0
- sage_wheels/share/factory/gftables/8192 +276 -0
- sage_wheels/share/factory/gftables/841 +30 -0
- sage_wheels/share/factory/gftables/9 +3 -0
- sage_wheels/share/factory/gftables/9409 +316 -0
- sage_wheels/share/factory/gftables/961 +34 -0
- sage_wheels/share/info/singular.info +191898 -0
- sage_wheels/share/singular/LIB/GND.lib +1359 -0
- sage_wheels/share/singular/LIB/JMBTest.lib +976 -0
- sage_wheels/share/singular/LIB/JMSConst.lib +1363 -0
- sage_wheels/share/singular/LIB/KVequiv.lib +699 -0
- sage_wheels/share/singular/LIB/SingularityDBM.lib +491 -0
- sage_wheels/share/singular/LIB/VecField.lib +1542 -0
- sage_wheels/share/singular/LIB/absfact.lib +959 -0
- sage_wheels/share/singular/LIB/ainvar.lib +730 -0
- sage_wheels/share/singular/LIB/aksaka.lib +419 -0
- sage_wheels/share/singular/LIB/alexpoly.lib +2542 -0
- sage_wheels/share/singular/LIB/algebra.lib +1193 -0
- sage_wheels/share/singular/LIB/all.lib +136 -0
- sage_wheels/share/singular/LIB/arcpoint.lib +514 -0
- sage_wheels/share/singular/LIB/arnold.lib +4553 -0
- sage_wheels/share/singular/LIB/arnoldclassify.lib +2058 -0
- sage_wheels/share/singular/LIB/arr.lib +3486 -0
- sage_wheels/share/singular/LIB/assprimeszerodim.lib +755 -0
- sage_wheels/share/singular/LIB/autgradalg.lib +3361 -0
- sage_wheels/share/singular/LIB/bfun.lib +1964 -0
- sage_wheels/share/singular/LIB/bimodules.lib +774 -0
- sage_wheels/share/singular/LIB/brillnoether.lib +226 -0
- sage_wheels/share/singular/LIB/brnoeth.lib +5017 -0
- sage_wheels/share/singular/LIB/central.lib +2169 -0
- sage_wheels/share/singular/LIB/chern.lib +4162 -0
- sage_wheels/share/singular/LIB/cimonom.lib +571 -0
- sage_wheels/share/singular/LIB/cisimplicial.lib +1835 -0
- sage_wheels/share/singular/LIB/classify.lib +3239 -0
- sage_wheels/share/singular/LIB/classify2.lib +1462 -0
- sage_wheels/share/singular/LIB/classifyMapGerms.lib +1515 -0
- sage_wheels/share/singular/LIB/classify_aeq.lib +3253 -0
- sage_wheels/share/singular/LIB/classifyceq.lib +2092 -0
- sage_wheels/share/singular/LIB/classifyci.lib +1133 -0
- sage_wheels/share/singular/LIB/combinat.lib +91 -0
- sage_wheels/share/singular/LIB/compregb.lib +276 -0
- sage_wheels/share/singular/LIB/control.lib +1636 -0
- sage_wheels/share/singular/LIB/crypto.lib +3795 -0
- sage_wheels/share/singular/LIB/curveInv.lib +667 -0
- sage_wheels/share/singular/LIB/curvepar.lib +1817 -0
- sage_wheels/share/singular/LIB/customstd.lib +100 -0
- sage_wheels/share/singular/LIB/deRham.lib +5979 -0
- sage_wheels/share/singular/LIB/decodegb.lib +2134 -0
- sage_wheels/share/singular/LIB/decomp.lib +1655 -0
- sage_wheels/share/singular/LIB/deflation.lib +872 -0
- sage_wheels/share/singular/LIB/deform.lib +925 -0
- sage_wheels/share/singular/LIB/difform.lib +3055 -0
- sage_wheels/share/singular/LIB/divisors.lib +750 -0
- sage_wheels/share/singular/LIB/dmod.lib +5817 -0
- sage_wheels/share/singular/LIB/dmodapp.lib +3269 -0
- sage_wheels/share/singular/LIB/dmodideal.lib +1211 -0
- sage_wheels/share/singular/LIB/dmodloc.lib +2645 -0
- sage_wheels/share/singular/LIB/dmodvar.lib +818 -0
- sage_wheels/share/singular/LIB/dummy.lib +17 -0
- sage_wheels/share/singular/LIB/elim.lib +1009 -0
- sage_wheels/share/singular/LIB/ellipticcovers.lib +548 -0
- sage_wheels/share/singular/LIB/enumpoints.lib +146 -0
- sage_wheels/share/singular/LIB/equising.lib +2127 -0
- sage_wheels/share/singular/LIB/ffmodstd.lib +2384 -0
- sage_wheels/share/singular/LIB/ffsolve.lib +1289 -0
- sage_wheels/share/singular/LIB/findifs.lib +778 -0
- sage_wheels/share/singular/LIB/finitediff.lib +1768 -0
- sage_wheels/share/singular/LIB/finvar.lib +7989 -0
- sage_wheels/share/singular/LIB/fpadim.lib +2429 -0
- sage_wheels/share/singular/LIB/fpalgebras.lib +1666 -0
- sage_wheels/share/singular/LIB/fpaprops.lib +1462 -0
- sage_wheels/share/singular/LIB/freegb.lib +3853 -0
- sage_wheels/share/singular/LIB/general.lib +1350 -0
- sage_wheels/share/singular/LIB/gfan.lib +1768 -0
- sage_wheels/share/singular/LIB/gitfan.lib +3130 -0
- sage_wheels/share/singular/LIB/gkdim.lib +99 -0
- sage_wheels/share/singular/LIB/gmspoly.lib +589 -0
- sage_wheels/share/singular/LIB/gmssing.lib +1739 -0
- sage_wheels/share/singular/LIB/goettsche.lib +909 -0
- sage_wheels/share/singular/LIB/graal.lib +1366 -0
- sage_wheels/share/singular/LIB/gradedModules.lib +2541 -0
- sage_wheels/share/singular/LIB/graphics.lib +360 -0
- sage_wheels/share/singular/LIB/grobcov.lib +7706 -0
- sage_wheels/share/singular/LIB/groups.lib +1123 -0
- sage_wheels/share/singular/LIB/grwalk.lib +507 -0
- sage_wheels/share/singular/LIB/hdepth.lib +194 -0
- sage_wheels/share/singular/LIB/help.cnf +57 -0
- sage_wheels/share/singular/LIB/hess.lib +1946 -0
- sage_wheels/share/singular/LIB/hnoether.lib +4292 -0
- sage_wheels/share/singular/LIB/hodge.lib +400 -0
- sage_wheels/share/singular/LIB/homolog.lib +1965 -0
- sage_wheels/share/singular/LIB/hyperel.lib +975 -0
- sage_wheels/share/singular/LIB/inout.lib +679 -0
- sage_wheels/share/singular/LIB/integralbasis.lib +6224 -0
- sage_wheels/share/singular/LIB/interval.lib +1418 -0
- sage_wheels/share/singular/LIB/intprog.lib +778 -0
- sage_wheels/share/singular/LIB/invar.lib +443 -0
- sage_wheels/share/singular/LIB/involut.lib +980 -0
- sage_wheels/share/singular/LIB/jacobson.lib +1215 -0
- sage_wheels/share/singular/LIB/kskernel.lib +534 -0
- sage_wheels/share/singular/LIB/latex.lib +3146 -0
- sage_wheels/share/singular/LIB/lejeune.lib +651 -0
- sage_wheels/share/singular/LIB/linalg.lib +2040 -0
- sage_wheels/share/singular/LIB/locnormal.lib +212 -0
- sage_wheels/share/singular/LIB/lrcalc.lib +526 -0
- sage_wheels/share/singular/LIB/makedbm.lib +294 -0
- sage_wheels/share/singular/LIB/mathml.lib +813 -0
- sage_wheels/share/singular/LIB/matrix.lib +1372 -0
- sage_wheels/share/singular/LIB/maxlike.lib +1132 -0
- sage_wheels/share/singular/LIB/methods.lib +212 -0
- sage_wheels/share/singular/LIB/moddiq.lib +322 -0
- sage_wheels/share/singular/LIB/modfinduni.lib +181 -0
- sage_wheels/share/singular/LIB/modnormal.lib +218 -0
- sage_wheels/share/singular/LIB/modprimdec.lib +1278 -0
- sage_wheels/share/singular/LIB/modquotient.lib +269 -0
- sage_wheels/share/singular/LIB/modstd.lib +1024 -0
- sage_wheels/share/singular/LIB/modular.lib +545 -0
- sage_wheels/share/singular/LIB/modules.lib +2561 -0
- sage_wheels/share/singular/LIB/modwalk.lib +609 -0
- sage_wheels/share/singular/LIB/mondromy.lib +1016 -0
- sage_wheels/share/singular/LIB/monomialideal.lib +3851 -0
- sage_wheels/share/singular/LIB/mprimdec.lib +2353 -0
- sage_wheels/share/singular/LIB/mregular.lib +1863 -0
- sage_wheels/share/singular/LIB/multigrading.lib +5629 -0
- sage_wheels/share/singular/LIB/ncHilb.lib +777 -0
- sage_wheels/share/singular/LIB/ncModslimgb.lib +791 -0
- sage_wheels/share/singular/LIB/ncalg.lib +16311 -0
- sage_wheels/share/singular/LIB/ncall.lib +31 -0
- sage_wheels/share/singular/LIB/ncdecomp.lib +468 -0
- sage_wheels/share/singular/LIB/ncfactor.lib +13371 -0
- sage_wheels/share/singular/LIB/ncfrac.lib +1023 -0
- sage_wheels/share/singular/LIB/nchilbert.lib +448 -0
- sage_wheels/share/singular/LIB/nchomolog.lib +759 -0
- sage_wheels/share/singular/LIB/ncloc.lib +361 -0
- sage_wheels/share/singular/LIB/ncpreim.lib +795 -0
- sage_wheels/share/singular/LIB/ncrat.lib +2849 -0
- sage_wheels/share/singular/LIB/nctools.lib +1887 -0
- sage_wheels/share/singular/LIB/nets.lib +1456 -0
- sage_wheels/share/singular/LIB/nfmodstd.lib +1000 -0
- sage_wheels/share/singular/LIB/nfmodsyz.lib +732 -0
- sage_wheels/share/singular/LIB/noether.lib +1106 -0
- sage_wheels/share/singular/LIB/normal.lib +8700 -0
- sage_wheels/share/singular/LIB/normaliz.lib +2226 -0
- sage_wheels/share/singular/LIB/ntsolve.lib +362 -0
- sage_wheels/share/singular/LIB/numerAlg.lib +560 -0
- sage_wheels/share/singular/LIB/numerDecom.lib +2261 -0
- sage_wheels/share/singular/LIB/olga.lib +1933 -0
- sage_wheels/share/singular/LIB/orbitparam.lib +351 -0
- sage_wheels/share/singular/LIB/parallel.lib +319 -0
- sage_wheels/share/singular/LIB/paraplanecurves.lib +3110 -0
- sage_wheels/share/singular/LIB/perron.lib +202 -0
- sage_wheels/share/singular/LIB/pfd.lib +2223 -0
- sage_wheels/share/singular/LIB/phindex.lib +642 -0
- sage_wheels/share/singular/LIB/pointid.lib +673 -0
- sage_wheels/share/singular/LIB/polybori.lib +1430 -0
- sage_wheels/share/singular/LIB/polyclass.lib +525 -0
- sage_wheels/share/singular/LIB/polylib.lib +1174 -0
- sage_wheels/share/singular/LIB/polymake.lib +1902 -0
- sage_wheels/share/singular/LIB/presolve.lib +1533 -0
- sage_wheels/share/singular/LIB/primdec.lib +9576 -0
- sage_wheels/share/singular/LIB/primdecint.lib +1782 -0
- sage_wheels/share/singular/LIB/primitiv.lib +401 -0
- sage_wheels/share/singular/LIB/puiseuxexpansions.lib +1631 -0
- sage_wheels/share/singular/LIB/purityfiltration.lib +960 -0
- sage_wheels/share/singular/LIB/qhmoduli.lib +1561 -0
- sage_wheels/share/singular/LIB/qmatrix.lib +293 -0
- sage_wheels/share/singular/LIB/random.lib +455 -0
- sage_wheels/share/singular/LIB/ratgb.lib +489 -0
- sage_wheels/share/singular/LIB/realclassify.lib +5759 -0
- sage_wheels/share/singular/LIB/realizationMatroids.lib +772 -0
- sage_wheels/share/singular/LIB/realrad.lib +1197 -0
- sage_wheels/share/singular/LIB/recover.lib +2628 -0
- sage_wheels/share/singular/LIB/redcgs.lib +3984 -0
- sage_wheels/share/singular/LIB/reesclos.lib +465 -0
- sage_wheels/share/singular/LIB/resbinomial.lib +2802 -0
- sage_wheels/share/singular/LIB/resgraph.lib +789 -0
- sage_wheels/share/singular/LIB/resjung.lib +820 -0
- sage_wheels/share/singular/LIB/resolve.lib +5110 -0
- sage_wheels/share/singular/LIB/resources.lib +170 -0
- sage_wheels/share/singular/LIB/reszeta.lib +5473 -0
- sage_wheels/share/singular/LIB/ring.lib +1328 -0
- sage_wheels/share/singular/LIB/ringgb.lib +343 -0
- sage_wheels/share/singular/LIB/rinvar.lib +1153 -0
- sage_wheels/share/singular/LIB/rootisolation.lib +1481 -0
- sage_wheels/share/singular/LIB/rootsmr.lib +709 -0
- sage_wheels/share/singular/LIB/rootsur.lib +886 -0
- sage_wheels/share/singular/LIB/rstandard.lib +607 -0
- sage_wheels/share/singular/LIB/rwalk.lib +336 -0
- sage_wheels/share/singular/LIB/sagbi.lib +1353 -0
- sage_wheels/share/singular/LIB/sagbiNormaliz.lib +1622 -0
- sage_wheels/share/singular/LIB/sagbiNormaliz0.lib +1498 -0
- sage_wheels/share/singular/LIB/sagbigrob.lib +449 -0
- sage_wheels/share/singular/LIB/schreyer.lib +321 -0
- sage_wheels/share/singular/LIB/schubert.lib +2551 -0
- sage_wheels/share/singular/LIB/sets.lib +524 -0
- sage_wheels/share/singular/LIB/sheafcoh.lib +1663 -0
- sage_wheels/share/singular/LIB/signcond.lib +437 -0
- sage_wheels/share/singular/LIB/sing.lib +1094 -0
- sage_wheels/share/singular/LIB/sing4ti2.lib +419 -0
- sage_wheels/share/singular/LIB/solve.lib +2243 -0
- sage_wheels/share/singular/LIB/spcurve.lib +1077 -0
- sage_wheels/share/singular/LIB/spectrum.lib +62 -0
- sage_wheels/share/singular/LIB/sresext.lib +757 -0
- sage_wheels/share/singular/LIB/ssi.lib +143 -0
- sage_wheels/share/singular/LIB/standard.lib +2769 -0
- sage_wheels/share/singular/LIB/stanleyreisner.lib +473 -0
- sage_wheels/share/singular/LIB/stdmodule.lib +547 -0
- sage_wheels/share/singular/LIB/stratify.lib +1070 -0
- sage_wheels/share/singular/LIB/surf.lib +506 -0
- sage_wheels/share/singular/LIB/surf_jupyter.lib +223 -0
- sage_wheels/share/singular/LIB/surfacesignature.lib +522 -0
- sage_wheels/share/singular/LIB/surfex.lib +1462 -0
- sage_wheels/share/singular/LIB/swalk.lib +877 -0
- sage_wheels/share/singular/LIB/symodstd.lib +1570 -0
- sage_wheels/share/singular/LIB/systhreads.lib +74 -0
- sage_wheels/share/singular/LIB/tasks.lib +1324 -0
- sage_wheels/share/singular/LIB/tateProdCplxNegGrad.lib +2412 -0
- sage_wheels/share/singular/LIB/teachstd.lib +858 -0
- sage_wheels/share/singular/LIB/template.lib +116 -0
- sage_wheels/share/singular/LIB/toric.lib +1119 -0
- sage_wheels/share/singular/LIB/transformation.lib +116 -0
- sage_wheels/share/singular/LIB/triang.lib +1197 -0
- sage_wheels/share/singular/LIB/tropical.lib +8741 -0
- sage_wheels/share/singular/LIB/tropicalEllipticCovers.lib +2922 -0
- sage_wheels/share/singular/LIB/tropicalNewton.lib +1128 -0
- sage_wheels/share/singular/LIB/tst.lib +1108 -0
- sage_wheels/share/singular/LIB/weierstr.lib +241 -0
- sage_wheels/share/singular/LIB/zeroset.lib +1478 -0
- sage_wheels/share/singular/emacs/.emacs-general +184 -0
- sage_wheels/share/singular/emacs/.emacs-singular +234 -0
- sage_wheels/share/singular/emacs/COPYING +44 -0
- sage_wheels/share/singular/emacs/cmd-cmpl.el +241 -0
- sage_wheels/share/singular/emacs/ex-cmpl.el +1681 -0
- sage_wheels/share/singular/emacs/hlp-cmpl.el +4318 -0
- sage_wheels/share/singular/emacs/lib-cmpl.el +179 -0
- sage_wheels/share/singular/emacs/singular.el +4273 -0
- sage_wheels/share/singular/emacs/singular.xpm +39 -0
- 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))]
|