passagemath-schemes 10.6.47__cp312-cp312-macosx_13_0_arm64.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.
Files changed (311) hide show
  1. passagemath_schemes/.dylibs/libflint.22.0.dylib +0 -0
  2. passagemath_schemes/.dylibs/libgmp.10.dylib +0 -0
  3. passagemath_schemes/.dylibs/libgmpxx.4.dylib +0 -0
  4. passagemath_schemes/.dylibs/libmpfr.6.dylib +0 -0
  5. passagemath_schemes/__init__.py +3 -0
  6. passagemath_schemes-10.6.47.dist-info/METADATA +204 -0
  7. passagemath_schemes-10.6.47.dist-info/METADATA.bak +205 -0
  8. passagemath_schemes-10.6.47.dist-info/RECORD +311 -0
  9. passagemath_schemes-10.6.47.dist-info/WHEEL +6 -0
  10. passagemath_schemes-10.6.47.dist-info/top_level.txt +3 -0
  11. sage/all__sagemath_schemes.py +23 -0
  12. sage/databases/all__sagemath_schemes.py +7 -0
  13. sage/databases/cremona.py +1723 -0
  14. sage/dynamics/all__sagemath_schemes.py +2 -0
  15. sage/dynamics/arithmetic_dynamics/affine_ds.py +1083 -0
  16. sage/dynamics/arithmetic_dynamics/all.py +14 -0
  17. sage/dynamics/arithmetic_dynamics/berkovich_ds.py +1101 -0
  18. sage/dynamics/arithmetic_dynamics/dynamical_semigroup.py +1543 -0
  19. sage/dynamics/arithmetic_dynamics/endPN_automorphism_group.py +2426 -0
  20. sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py +1169 -0
  21. sage/dynamics/arithmetic_dynamics/generic_ds.py +663 -0
  22. sage/dynamics/arithmetic_dynamics/product_projective_ds.py +339 -0
  23. sage/dynamics/arithmetic_dynamics/projective_ds.py +9558 -0
  24. sage/dynamics/arithmetic_dynamics/projective_ds_helper.cpython-312-darwin.so +0 -0
  25. sage/dynamics/arithmetic_dynamics/projective_ds_helper.pyx +301 -0
  26. sage/dynamics/arithmetic_dynamics/wehlerK3.py +2576 -0
  27. sage/lfunctions/all.py +18 -0
  28. sage/lfunctions/dokchitser.py +745 -0
  29. sage/lfunctions/pari.py +818 -0
  30. sage/lfunctions/zero_sums.cpython-312-darwin.so +0 -0
  31. sage/lfunctions/zero_sums.pyx +1847 -0
  32. sage/modular/abvar/abvar.py +5135 -0
  33. sage/modular/abvar/abvar_ambient_jacobian.py +413 -0
  34. sage/modular/abvar/abvar_newform.py +244 -0
  35. sage/modular/abvar/all.py +8 -0
  36. sage/modular/abvar/constructor.py +186 -0
  37. sage/modular/abvar/cuspidal_subgroup.py +371 -0
  38. sage/modular/abvar/finite_subgroup.py +896 -0
  39. sage/modular/abvar/homology.py +720 -0
  40. sage/modular/abvar/homspace.py +998 -0
  41. sage/modular/abvar/lseries.py +415 -0
  42. sage/modular/abvar/morphism.py +935 -0
  43. sage/modular/abvar/torsion_point.py +274 -0
  44. sage/modular/abvar/torsion_subgroup.py +740 -0
  45. sage/modular/all.py +43 -0
  46. sage/modular/arithgroup/all.py +20 -0
  47. sage/modular/arithgroup/arithgroup_element.cpython-312-darwin.so +0 -0
  48. sage/modular/arithgroup/arithgroup_element.pyx +474 -0
  49. sage/modular/arithgroup/arithgroup_generic.py +1402 -0
  50. sage/modular/arithgroup/arithgroup_perm.py +2692 -0
  51. sage/modular/arithgroup/congroup.cpython-312-darwin.so +0 -0
  52. sage/modular/arithgroup/congroup.pyx +334 -0
  53. sage/modular/arithgroup/congroup_gamma.py +363 -0
  54. sage/modular/arithgroup/congroup_gamma0.py +692 -0
  55. sage/modular/arithgroup/congroup_gamma1.py +653 -0
  56. sage/modular/arithgroup/congroup_gammaH.py +1469 -0
  57. sage/modular/arithgroup/congroup_generic.py +628 -0
  58. sage/modular/arithgroup/congroup_sl2z.py +267 -0
  59. sage/modular/arithgroup/farey_symbol.cpython-312-darwin.so +0 -0
  60. sage/modular/arithgroup/farey_symbol.pyx +1066 -0
  61. sage/modular/arithgroup/tests.py +418 -0
  62. sage/modular/btquotients/all.py +4 -0
  63. sage/modular/btquotients/btquotient.py +3753 -0
  64. sage/modular/btquotients/pautomorphicform.py +2570 -0
  65. sage/modular/buzzard.py +100 -0
  66. sage/modular/congroup.py +29 -0
  67. sage/modular/congroup_element.py +13 -0
  68. sage/modular/cusps.py +1109 -0
  69. sage/modular/cusps_nf.py +1270 -0
  70. sage/modular/dims.py +569 -0
  71. sage/modular/dirichlet.py +3310 -0
  72. sage/modular/drinfeld_modform/all.py +2 -0
  73. sage/modular/drinfeld_modform/element.py +446 -0
  74. sage/modular/drinfeld_modform/ring.py +773 -0
  75. sage/modular/drinfeld_modform/tutorial.py +236 -0
  76. sage/modular/etaproducts.py +1065 -0
  77. sage/modular/hecke/algebra.py +746 -0
  78. sage/modular/hecke/all.py +20 -0
  79. sage/modular/hecke/ambient_module.py +1019 -0
  80. sage/modular/hecke/degenmap.py +119 -0
  81. sage/modular/hecke/element.py +325 -0
  82. sage/modular/hecke/hecke_operator.py +780 -0
  83. sage/modular/hecke/homspace.py +206 -0
  84. sage/modular/hecke/module.py +1767 -0
  85. sage/modular/hecke/morphism.py +174 -0
  86. sage/modular/hecke/submodule.py +989 -0
  87. sage/modular/hypergeometric_misc.cpython-312-darwin.so +0 -0
  88. sage/modular/hypergeometric_misc.pxd +4 -0
  89. sage/modular/hypergeometric_misc.pyx +166 -0
  90. sage/modular/hypergeometric_motive.py +2017 -0
  91. sage/modular/local_comp/all.py +2 -0
  92. sage/modular/local_comp/liftings.py +292 -0
  93. sage/modular/local_comp/local_comp.py +1071 -0
  94. sage/modular/local_comp/smoothchar.py +1825 -0
  95. sage/modular/local_comp/type_space.py +748 -0
  96. sage/modular/modform/all.py +30 -0
  97. sage/modular/modform/ambient.py +815 -0
  98. sage/modular/modform/ambient_R.py +177 -0
  99. sage/modular/modform/ambient_eps.py +306 -0
  100. sage/modular/modform/ambient_g0.py +124 -0
  101. sage/modular/modform/ambient_g1.py +204 -0
  102. sage/modular/modform/constructor.py +545 -0
  103. sage/modular/modform/cuspidal_submodule.py +708 -0
  104. sage/modular/modform/defaults.py +14 -0
  105. sage/modular/modform/eis_series.py +505 -0
  106. sage/modular/modform/eisenstein_submodule.py +663 -0
  107. sage/modular/modform/element.py +4131 -0
  108. sage/modular/modform/find_generators.py +59 -0
  109. sage/modular/modform/half_integral.py +154 -0
  110. sage/modular/modform/hecke_operator_on_qexp.py +247 -0
  111. sage/modular/modform/j_invariant.py +47 -0
  112. sage/modular/modform/l_series_gross_zagier.py +133 -0
  113. sage/modular/modform/l_series_gross_zagier_coeffs.cpython-312-darwin.so +0 -0
  114. sage/modular/modform/l_series_gross_zagier_coeffs.pyx +177 -0
  115. sage/modular/modform/notes.py +45 -0
  116. sage/modular/modform/numerical.py +514 -0
  117. sage/modular/modform/periods.py +14 -0
  118. sage/modular/modform/ring.py +1257 -0
  119. sage/modular/modform/space.py +1860 -0
  120. sage/modular/modform/submodule.py +118 -0
  121. sage/modular/modform/tests.py +64 -0
  122. sage/modular/modform/theta.py +110 -0
  123. sage/modular/modform/vm_basis.py +381 -0
  124. sage/modular/modform/weight1.py +220 -0
  125. sage/modular/modform_hecketriangle/abstract_ring.py +1932 -0
  126. sage/modular/modform_hecketriangle/abstract_space.py +2528 -0
  127. sage/modular/modform_hecketriangle/all.py +30 -0
  128. sage/modular/modform_hecketriangle/analytic_type.py +590 -0
  129. sage/modular/modform_hecketriangle/constructor.py +416 -0
  130. sage/modular/modform_hecketriangle/element.py +351 -0
  131. sage/modular/modform_hecketriangle/functors.py +752 -0
  132. sage/modular/modform_hecketriangle/graded_ring.py +541 -0
  133. sage/modular/modform_hecketriangle/graded_ring_element.py +2225 -0
  134. sage/modular/modform_hecketriangle/hecke_triangle_group_element.py +3352 -0
  135. sage/modular/modform_hecketriangle/hecke_triangle_groups.py +1432 -0
  136. sage/modular/modform_hecketriangle/readme.py +1214 -0
  137. sage/modular/modform_hecketriangle/series_constructor.py +580 -0
  138. sage/modular/modform_hecketriangle/space.py +1037 -0
  139. sage/modular/modform_hecketriangle/subspace.py +423 -0
  140. sage/modular/modsym/all.py +17 -0
  141. sage/modular/modsym/ambient.py +3846 -0
  142. sage/modular/modsym/boundary.py +1420 -0
  143. sage/modular/modsym/element.py +336 -0
  144. sage/modular/modsym/g1list.py +178 -0
  145. sage/modular/modsym/ghlist.py +182 -0
  146. sage/modular/modsym/hecke_operator.py +73 -0
  147. sage/modular/modsym/manin_symbol.cpython-312-darwin.so +0 -0
  148. sage/modular/modsym/manin_symbol.pxd +5 -0
  149. sage/modular/modsym/manin_symbol.pyx +497 -0
  150. sage/modular/modsym/manin_symbol_list.py +1295 -0
  151. sage/modular/modsym/modsym.py +400 -0
  152. sage/modular/modsym/modular_symbols.py +384 -0
  153. sage/modular/modsym/p1list_nf.py +1241 -0
  154. sage/modular/modsym/relation_matrix.py +591 -0
  155. sage/modular/modsym/relation_matrix_pyx.cpython-312-darwin.so +0 -0
  156. sage/modular/modsym/relation_matrix_pyx.pyx +108 -0
  157. sage/modular/modsym/space.py +2468 -0
  158. sage/modular/modsym/subspace.py +455 -0
  159. sage/modular/modsym/tests.py +375 -0
  160. sage/modular/multiple_zeta.py +2632 -0
  161. sage/modular/multiple_zeta_F_algebra.py +786 -0
  162. sage/modular/overconvergent/all.py +6 -0
  163. sage/modular/overconvergent/genus0.py +1878 -0
  164. sage/modular/overconvergent/hecke_series.py +1187 -0
  165. sage/modular/overconvergent/weightspace.py +778 -0
  166. sage/modular/pollack_stevens/all.py +4 -0
  167. sage/modular/pollack_stevens/distributions.py +874 -0
  168. sage/modular/pollack_stevens/fund_domain.py +1572 -0
  169. sage/modular/pollack_stevens/manin_map.py +859 -0
  170. sage/modular/pollack_stevens/modsym.py +1593 -0
  171. sage/modular/pollack_stevens/padic_lseries.py +417 -0
  172. sage/modular/pollack_stevens/sigma0.py +534 -0
  173. sage/modular/pollack_stevens/space.py +1076 -0
  174. sage/modular/quasimodform/all.py +3 -0
  175. sage/modular/quasimodform/element.py +845 -0
  176. sage/modular/quasimodform/ring.py +828 -0
  177. sage/modular/quatalg/all.py +3 -0
  178. sage/modular/quatalg/brandt.py +1642 -0
  179. sage/modular/ssmod/all.py +8 -0
  180. sage/modular/ssmod/ssmod.py +827 -0
  181. sage/rings/all__sagemath_schemes.py +1 -0
  182. sage/rings/polynomial/all__sagemath_schemes.py +1 -0
  183. sage/rings/polynomial/binary_form_reduce.py +585 -0
  184. sage/schemes/all.py +41 -0
  185. sage/schemes/berkovich/all.py +6 -0
  186. sage/schemes/berkovich/berkovich_cp_element.py +2582 -0
  187. sage/schemes/berkovich/berkovich_space.py +748 -0
  188. sage/schemes/curves/affine_curve.py +2928 -0
  189. sage/schemes/curves/all.py +33 -0
  190. sage/schemes/curves/closed_point.py +434 -0
  191. sage/schemes/curves/constructor.py +381 -0
  192. sage/schemes/curves/curve.py +542 -0
  193. sage/schemes/curves/plane_curve_arrangement.py +1283 -0
  194. sage/schemes/curves/point.py +463 -0
  195. sage/schemes/curves/projective_curve.py +3026 -0
  196. sage/schemes/curves/zariski_vankampen.py +1932 -0
  197. sage/schemes/cyclic_covers/all.py +2 -0
  198. sage/schemes/cyclic_covers/charpoly_frobenius.py +320 -0
  199. sage/schemes/cyclic_covers/constructor.py +137 -0
  200. sage/schemes/cyclic_covers/cycliccover_finite_field.py +1309 -0
  201. sage/schemes/cyclic_covers/cycliccover_generic.py +310 -0
  202. sage/schemes/elliptic_curves/BSD.py +1036 -0
  203. sage/schemes/elliptic_curves/Qcurves.py +592 -0
  204. sage/schemes/elliptic_curves/addition_formulas_ring.py +94 -0
  205. sage/schemes/elliptic_curves/all.py +49 -0
  206. sage/schemes/elliptic_curves/cardinality.py +609 -0
  207. sage/schemes/elliptic_curves/cm.py +1102 -0
  208. sage/schemes/elliptic_curves/constructor.py +1552 -0
  209. sage/schemes/elliptic_curves/ec_database.py +175 -0
  210. sage/schemes/elliptic_curves/ell_curve_isogeny.py +3972 -0
  211. sage/schemes/elliptic_curves/ell_egros.py +459 -0
  212. sage/schemes/elliptic_curves/ell_field.py +2836 -0
  213. sage/schemes/elliptic_curves/ell_finite_field.py +3359 -0
  214. sage/schemes/elliptic_curves/ell_generic.py +3760 -0
  215. sage/schemes/elliptic_curves/ell_local_data.py +1207 -0
  216. sage/schemes/elliptic_curves/ell_modular_symbols.py +775 -0
  217. sage/schemes/elliptic_curves/ell_number_field.py +4220 -0
  218. sage/schemes/elliptic_curves/ell_padic_field.py +107 -0
  219. sage/schemes/elliptic_curves/ell_point.py +4787 -0
  220. sage/schemes/elliptic_curves/ell_rational_field.py +7368 -0
  221. sage/schemes/elliptic_curves/ell_tate_curve.py +671 -0
  222. sage/schemes/elliptic_curves/ell_torsion.py +436 -0
  223. sage/schemes/elliptic_curves/ell_wp.py +352 -0
  224. sage/schemes/elliptic_curves/formal_group.py +760 -0
  225. sage/schemes/elliptic_curves/gal_reps.py +1459 -0
  226. sage/schemes/elliptic_curves/gal_reps_number_field.py +1669 -0
  227. sage/schemes/elliptic_curves/gp_simon.py +152 -0
  228. sage/schemes/elliptic_curves/heegner.py +7335 -0
  229. sage/schemes/elliptic_curves/height.py +2109 -0
  230. sage/schemes/elliptic_curves/hom.py +1406 -0
  231. sage/schemes/elliptic_curves/hom_composite.py +934 -0
  232. sage/schemes/elliptic_curves/hom_frobenius.py +522 -0
  233. sage/schemes/elliptic_curves/hom_scalar.py +531 -0
  234. sage/schemes/elliptic_curves/hom_sum.py +682 -0
  235. sage/schemes/elliptic_curves/hom_velusqrt.py +1290 -0
  236. sage/schemes/elliptic_curves/homset.py +271 -0
  237. sage/schemes/elliptic_curves/isogeny_class.py +1521 -0
  238. sage/schemes/elliptic_curves/isogeny_small_degree.py +2797 -0
  239. sage/schemes/elliptic_curves/jacobian.py +237 -0
  240. sage/schemes/elliptic_curves/kodaira_symbol.py +344 -0
  241. sage/schemes/elliptic_curves/kraus.py +1014 -0
  242. sage/schemes/elliptic_curves/lseries_ell.py +943 -0
  243. sage/schemes/elliptic_curves/mod5family.py +105 -0
  244. sage/schemes/elliptic_curves/mod_poly.py +197 -0
  245. sage/schemes/elliptic_curves/mod_sym_num.cpython-312-darwin.so +0 -0
  246. sage/schemes/elliptic_curves/mod_sym_num.pyx +3796 -0
  247. sage/schemes/elliptic_curves/modular_parametrization.py +305 -0
  248. sage/schemes/elliptic_curves/padic_lseries.py +1793 -0
  249. sage/schemes/elliptic_curves/padics.py +1816 -0
  250. sage/schemes/elliptic_curves/period_lattice.py +2234 -0
  251. sage/schemes/elliptic_curves/period_lattice_region.cpython-312-darwin.so +0 -0
  252. sage/schemes/elliptic_curves/period_lattice_region.pyx +722 -0
  253. sage/schemes/elliptic_curves/saturation.py +715 -0
  254. sage/schemes/elliptic_curves/sha_tate.py +1158 -0
  255. sage/schemes/elliptic_curves/weierstrass_morphism.py +1117 -0
  256. sage/schemes/elliptic_curves/weierstrass_transform.py +200 -0
  257. sage/schemes/hyperelliptic_curves/all.py +6 -0
  258. sage/schemes/hyperelliptic_curves/constructor.py +291 -0
  259. sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py +1914 -0
  260. sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py +192 -0
  261. sage/schemes/hyperelliptic_curves/hyperelliptic_generic.py +954 -0
  262. sage/schemes/hyperelliptic_curves/hyperelliptic_padic_field.py +1332 -0
  263. sage/schemes/hyperelliptic_curves/hyperelliptic_rational_field.py +84 -0
  264. sage/schemes/hyperelliptic_curves/invariants.py +410 -0
  265. sage/schemes/hyperelliptic_curves/jacobian_endomorphism_utils.py +315 -0
  266. sage/schemes/hyperelliptic_curves/jacobian_g2.py +32 -0
  267. sage/schemes/hyperelliptic_curves/jacobian_generic.py +419 -0
  268. sage/schemes/hyperelliptic_curves/jacobian_homset.py +186 -0
  269. sage/schemes/hyperelliptic_curves/jacobian_morphism.py +875 -0
  270. sage/schemes/hyperelliptic_curves/kummer_surface.py +99 -0
  271. sage/schemes/hyperelliptic_curves/mestre.py +302 -0
  272. sage/schemes/hyperelliptic_curves/monsky_washnitzer.py +3871 -0
  273. sage/schemes/jacobians/abstract_jacobian.py +277 -0
  274. sage/schemes/jacobians/all.py +2 -0
  275. sage/schemes/overview.py +161 -0
  276. sage/schemes/plane_conics/all.py +22 -0
  277. sage/schemes/plane_conics/con_field.py +1296 -0
  278. sage/schemes/plane_conics/con_finite_field.py +158 -0
  279. sage/schemes/plane_conics/con_number_field.py +456 -0
  280. sage/schemes/plane_conics/con_rational_field.py +406 -0
  281. sage/schemes/plane_conics/con_rational_function_field.py +580 -0
  282. sage/schemes/plane_conics/constructor.py +249 -0
  283. sage/schemes/plane_quartics/all.py +2 -0
  284. sage/schemes/plane_quartics/quartic_constructor.py +71 -0
  285. sage/schemes/plane_quartics/quartic_generic.py +73 -0
  286. sage/schemes/riemann_surfaces/all.py +1 -0
  287. sage/schemes/riemann_surfaces/riemann_surface.py +4117 -0
  288. sage_wheels/share/cremona/cremona_mini.db +0 -0
  289. sage_wheels/share/ellcurves/rank0 +30427 -0
  290. sage_wheels/share/ellcurves/rank1 +31871 -0
  291. sage_wheels/share/ellcurves/rank10 +6 -0
  292. sage_wheels/share/ellcurves/rank11 +6 -0
  293. sage_wheels/share/ellcurves/rank12 +1 -0
  294. sage_wheels/share/ellcurves/rank14 +1 -0
  295. sage_wheels/share/ellcurves/rank15 +1 -0
  296. sage_wheels/share/ellcurves/rank17 +1 -0
  297. sage_wheels/share/ellcurves/rank19 +1 -0
  298. sage_wheels/share/ellcurves/rank2 +2388 -0
  299. sage_wheels/share/ellcurves/rank20 +1 -0
  300. sage_wheels/share/ellcurves/rank21 +1 -0
  301. sage_wheels/share/ellcurves/rank22 +1 -0
  302. sage_wheels/share/ellcurves/rank23 +1 -0
  303. sage_wheels/share/ellcurves/rank24 +1 -0
  304. sage_wheels/share/ellcurves/rank28 +1 -0
  305. sage_wheels/share/ellcurves/rank3 +836 -0
  306. sage_wheels/share/ellcurves/rank4 +10 -0
  307. sage_wheels/share/ellcurves/rank5 +5 -0
  308. sage_wheels/share/ellcurves/rank6 +5 -0
  309. sage_wheels/share/ellcurves/rank7 +5 -0
  310. sage_wheels/share/ellcurves/rank8 +6 -0
  311. sage_wheels/share/ellcurves/rank9 +7 -0
@@ -0,0 +1,2426 @@
1
+ # sage_setup: distribution = sagemath-schemes
2
+ r"""
3
+ Automorphism groups of dynamical systems of the projective line
4
+
5
+ AUTHORS:
6
+
7
+ - Xander Faber, Michelle Manes, Bianca Viray: algorithm and original code
8
+ "Computing Conjugating Sets and Automorphism Groups of Rational Functions" by
9
+ Xander Faber, Michelle Manes, and Bianca Viray [FMV]_.
10
+
11
+ - Joao de Faria, Ben Hutz, Bianca Thompson (11-2013): adaptation for inclusion in Sage
12
+
13
+ - Alexander Galarraga (7-2021): Added helper functions for conjugating set
14
+ """
15
+ # ****************************************************************************
16
+ # Copyright (C) 2012
17
+ #
18
+ # Distributed under the terms of the GNU General Public License (GPL)
19
+ # as published by the Free Software Foundation; either version 2 of
20
+ # the License, or (at your option) any later version.
21
+ # https://www.gnu.org/licenses/
22
+ # ****************************************************************************
23
+ from copy import copy, deepcopy
24
+ from itertools import permutations, combinations, product
25
+
26
+ from sage.arith.functions import lcm
27
+ from sage.arith.misc import CRT, divisors, gcd, is_square
28
+ from sage.combinat.permutation import Arrangements
29
+ from sage.combinat.subset import Subsets
30
+ from sage.matrix.constructor import matrix
31
+ from sage.misc.functional import sqrt
32
+ from sage.misc.lazy_import import lazy_import
33
+ from sage.misc.misc_c import prod
34
+ from sage.parallel.use_fork import p_iter_fork
35
+ from sage.rings.finite_rings.finite_field_constructor import GF
36
+ from sage.rings.finite_rings.integer_mod_ring import Integers
37
+ from sage.rings.integer_ring import ZZ
38
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
39
+ from sage.rings.rational_field import QQ
40
+ from sage.sets.primes import Primes
41
+ from sage.sets.set import Set
42
+ from sage.structure.element import Matrix
43
+
44
+ lazy_import('sage.rings.number_field.number_field', 'NumberField')
45
+
46
+
47
+ def automorphism_group_QQ_fixedpoints(rational_function, return_functions=False, iso_type=False):
48
+ r"""
49
+ Compute the automorphism group for ``rational_function`` via the method of
50
+ fixed points.
51
+
52
+ ALGORITHM:
53
+
54
+ See Algorithm 3 in Faber-Manes-Viray [FMV]_.
55
+
56
+ INPUT:
57
+
58
+ - ``rational_function`` -- Rational Function defined over `\ZZ` or `\QQ`
59
+
60
+ - ``return_functions`` -- boolean value; ``True`` will return elements in
61
+ the automorphism group as linear fractional transformations. ``False``
62
+ will return elements as `PGL_2` matrices.
63
+
64
+ - ``iso_type`` -- boolean; ``True`` will cause the classification of the
65
+ finite automorphism group to also be returned
66
+
67
+ OUTPUT: list of automorphisms that make up the automorphism group
68
+ of ``rational_function``
69
+
70
+ EXAMPLES::
71
+
72
+ sage: F.<z> = PolynomialRing(QQ)
73
+ sage: rational_function = (z^2 - 2*z - 2)/(-2*z^2 - 2*z + 1)
74
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import automorphism_group_QQ_fixedpoints
75
+ sage: automorphism_group_QQ_fixedpoints(rational_function, True)
76
+ [z, 1/z, -z - 1, -z/(z + 1), (-z - 1)/z, -1/(z + 1)]
77
+
78
+ ::
79
+
80
+ sage: F.<z> = PolynomialRing(QQ)
81
+ sage: rational_function = (z^2 + 2*z)/(-2*z - 1)
82
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import automorphism_group_QQ_fixedpoints
83
+ sage: automorphism_group_QQ_fixedpoints(rational_function)
84
+ [
85
+ [1 0] [-1 -1] [-2 0] [0 2] [-1 -1] [ 0 -1]
86
+ [0 1], [ 0 1], [ 2 2], [2 0], [ 1 0], [ 1 1]
87
+ ]
88
+
89
+ ::
90
+
91
+ sage: F.<z> = PolynomialRing(QQ)
92
+ sage: rational_function = (z^2 - 4*z - 3)/(-3*z^2 - 2*z + 2)
93
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import automorphism_group_QQ_fixedpoints
94
+ sage: automorphism_group_QQ_fixedpoints(rational_function, True, True)
95
+ ([z, (-z - 1)/z, -1/(z + 1)], 'Cyclic of order 3')
96
+ """
97
+
98
+ if rational_function.parent().is_field():
99
+ K = rational_function.parent()
100
+ R = K.ring()
101
+ else:
102
+ R = rational_function.parent()
103
+ K = R.fraction_field()
104
+
105
+ F = R.base_ring()
106
+
107
+ if F != QQ and F != ZZ:
108
+ raise TypeError("coefficient ring is not the rational numbers or the integers")
109
+
110
+ z = R.gen(0)
111
+ phi = R.fraction_field()(rational_function)
112
+
113
+ f = phi.numerator()
114
+ g = phi.denominator()
115
+
116
+ #scale f,g so both have integer coefficients
117
+ N = lcm(f.denominator(),g.denominator())
118
+ f = f*N
119
+ g = g*N
120
+ N = gcd(gcd(f.coefficients()), gcd(g.coefficients()))
121
+ f = f/N
122
+ g = g/N
123
+
124
+ d = max(f.degree(), g.degree())
125
+
126
+ h = f - g*z
127
+
128
+ if return_functions:
129
+ elements = [z]
130
+ else:
131
+ elements = [matrix(F, 2, [1,0,0,1])]
132
+
133
+ rational_roots = h.roots(multiplicities=False)
134
+
135
+ min_poly = 1
136
+
137
+ #check if infinity is a fixed point
138
+ if g.degree() < d: #then infinity is a fixed point
139
+ #find elements in W of the form (infinity, y)
140
+ #where W is the set of F-rational points (x,y) such that
141
+ #x is fixed by phi and phi(y)=x
142
+ for T in g.roots(multiplicities=False):
143
+ alpha = T
144
+ zeta = -1
145
+ s = (zeta*z + alpha*(1 - zeta))
146
+ if s(phi(z)) == phi(s(z)):
147
+ if return_functions:
148
+ elements.append(s)
149
+ else:
150
+ elements.append(matrix(F, 2, [zeta, alpha*(1-zeta), 0, 1]))
151
+
152
+ for S in h.roots():
153
+ min_poly = min_poly*(z - S[0])**(S[1])
154
+
155
+ if g.degree() < d: #then infinity is a fixed point so (infinity, S[0])
156
+ alpha = S[0] # is in Z_(1,1)**2
157
+ zeta = -1
158
+ s = (zeta*z + alpha*(1 - zeta))
159
+ if s(phi(z)) == phi(s(z)):
160
+ if return_functions:
161
+ elements.append(s)
162
+ else:
163
+ elements.append(matrix(F, 2, [zeta, alpha*(1-zeta), 0, 1]))
164
+
165
+ #now compute points in W
166
+ preimage = f - g*S[0]
167
+ if preimage.degree() < d: #infinity is in W
168
+ zeta = -1
169
+ alpha = S[0]
170
+ s = (zeta*z + alpha*(1 - zeta))
171
+ if s(phi(z)) == phi(s(z)):
172
+ if return_functions:
173
+ elements.append(s)
174
+ else:
175
+ elements.append(matrix(F, 2, [zeta, alpha*(1-zeta), 0, 1]))
176
+ for T in preimage.roots(multiplicities=False):
177
+ if T != S[0]:
178
+ zeta = -1
179
+ alpha = S[0]
180
+ beta = T
181
+ s = ( (alpha - zeta*beta)*z - (alpha*beta)*(1 - zeta))/((1 - zeta)*z + (alpha*zeta - beta))
182
+ if s(phi(z)) == phi(s(z)):
183
+ if return_functions:
184
+ elements.append(s)
185
+ else:
186
+ elements.append(matrix(F, 2,
187
+ [(alpha - zeta*beta), - (alpha*beta)*(1 - zeta),
188
+ (1 - zeta), (alpha*zeta - beta)]))
189
+
190
+ #first look at rational fixed points
191
+ #Subsets is ok since we just needed unordered pairs
192
+ for S in Subsets(rational_roots, 2):
193
+ zeta = -1
194
+ alpha = S[0]
195
+ beta = S[1]
196
+ s = ( (alpha - zeta*beta)*z - (alpha*beta)*(1 - zeta))/((1 - zeta)*z + (alpha*zeta - beta))
197
+ if s(phi(z)) == phi(s(z)):
198
+ if return_functions:
199
+ elements.append(s)
200
+ else:
201
+ elements.append(matrix(F, 2,
202
+ [(alpha - zeta*beta), - (alpha*beta)*(1 - zeta),
203
+ (1 - zeta), (alpha*zeta - beta)]))
204
+
205
+ # now consider 2-periodic points
206
+ psi = phi(phi(z))
207
+ f2 = psi.numerator()
208
+ g2 = psi.denominator()
209
+ period2_points = [x for x in (f2 - z*g2).roots(multiplicities=False)
210
+ if x not in rational_roots]
211
+ for S in Subsets(period2_points, 2):
212
+ zeta = -1
213
+ alpha = S[0]
214
+ beta = S[1]
215
+ s = ( (alpha - zeta*beta)*z - (alpha*beta)*(1 - zeta))/((1 - zeta)*z + (alpha*zeta - beta))
216
+ if s(phi(z)) == phi(s(z)):
217
+ if return_functions:
218
+ elements.append(s)
219
+ else:
220
+ elements.append(matrix(F, 2,
221
+ [(alpha - zeta*beta), - (alpha*beta)*(1 - zeta),
222
+ (1 - zeta), (alpha*zeta - beta)]))
223
+ if g2.degree() < f2.degree() and g.degree() == d: #infinity has period 2
224
+ for alpha in period2_points:
225
+ zeta = -1
226
+ s = (zeta*z + alpha*(1 - zeta))
227
+ if s(phi(z)) == phi(s(z)):
228
+ if return_functions:
229
+ elements.append(s)
230
+ else:
231
+ elements.append(matrix(F, 2, [zeta, alpha*(1-zeta), 0, 1]))
232
+ factors = (f2 - z*g2).factor()
233
+ L1 = NumberField(z**2 + 1,'i')
234
+ i = L1.gen(0)
235
+ L2 = NumberField(z**2 + 3,'isqrt3')
236
+ isqrt3 = L2.gen(0)
237
+ for psi in factors:
238
+ if psi[0].degree() == 2:
239
+ a = psi[0][2]
240
+ b = psi[0][1]
241
+ c = psi[0][0]
242
+ disc = b**2 - 4*a*c
243
+ s = (-b*z - 2*c)/(2*a*z + b)
244
+ if s(phi(z)) == phi(s(z)):
245
+ if return_functions:
246
+ elements.append(K(s))
247
+ else:
248
+ elements.append(matrix(F, 2, [-b,-2*c, 2*a, b]))
249
+ if is_square(-disc): #psi[0] generates Q(i)
250
+ alpha = psi[0].change_ring(L1).roots()[0][0]
251
+ beta = alpha.trace() - alpha
252
+ for zeta in [i, -i]:
253
+ a = (alpha - zeta*beta)/(1 - zeta)
254
+ d = (alpha*zeta - beta)/(1 - zeta)
255
+ if a in F and d in F:
256
+ a = F(a)
257
+ d = F(d)
258
+ b = F(-alpha*beta)
259
+ s = (a * z + b) / (z + d)
260
+ if s(phi(z)) == phi(s(z)):
261
+ if return_functions:
262
+ elements.append(K(s))
263
+ else:
264
+ elements.append(matrix(F, 2, [a,b, 1, d]))
265
+ elif is_square(-3*disc): #psi[0] generates Q(zeta_3)
266
+ alpha = psi[0].change_ring(L2).roots()[0][0]
267
+ beta = alpha.trace() - alpha
268
+ for zeta in [F(1)/F(2)*(1 + isqrt3), F(1)/F(2)*(1 - isqrt3),F(1)/F(2)*(-1 + isqrt3), F(1)/F(2)*(-1 - isqrt3)]:
269
+ a = (alpha - zeta*beta)/(1 - zeta)
270
+ d = (alpha*zeta - beta)/(1 - zeta)
271
+ if a in F and d in F:
272
+ a = F(a)
273
+ d = F(d)
274
+ b = F(-alpha*beta)
275
+ s = (a * z + b) / (z + d)
276
+ if s(phi(z)) == phi(s(z)):
277
+ if return_functions:
278
+ elements.append(K(s))
279
+ else:
280
+ elements.append(matrix(F, 2, [a,b, 1, d]))
281
+
282
+ if iso_type:
283
+ return elements, which_group(elements)
284
+ return elements
285
+
286
+
287
+ def height_bound(polynomial):
288
+ r"""
289
+ Compute the maximum height of the coefficients of an automorphism.
290
+
291
+ This bounds sets the termination criteria for the Chinese Remainder Theorem step.
292
+
293
+ Let `f` be a square-free polynomial with coefficients in `K`
294
+ Let `F` be an automorphism of `\mathbb{P}^1_{Frac(R)}` that permutes the roots of `f`
295
+ This function returns a bound on the height of `F`,
296
+ when viewed as an element of `\mathbb{P}^3`
297
+
298
+ In [FMV]_ it is proven that `ht(F) <= 6^{[K:Q]}*M`, where `M` is the Mahler measure of `f`
299
+ M is bounded above by `H(f)`, so we return the floor of `6*H(f)`
300
+ (since `ht(F)` is an integer)
301
+
302
+ INPUT:
303
+
304
+ - ``polynomial`` -- a univariate polynomial
305
+
306
+ OUTPUT: a positive integer
307
+
308
+ EXAMPLES::
309
+
310
+ sage: R.<z> = PolynomialRing(QQ)
311
+ sage: f = z^3 + 2*z + 6
312
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import height_bound
313
+ sage: height_bound(f)
314
+ 413526
315
+ """
316
+ # first check that polynomial is over QQ or ZZ
317
+ K = polynomial.parent()
318
+
319
+ if K.is_field():
320
+ R = K.ring()
321
+ else:
322
+ R = K
323
+ F = R.base_ring()
324
+
325
+ if F != QQ and F != ZZ:
326
+ raise TypeError("coefficient ring is not the rational numbers or the integers")
327
+
328
+ # scale polynomial so that it has integer coefficients with gcd 1
329
+ # this ensures that H(f) = H_infinity(f)
330
+ f = R(polynomial)
331
+ f = f*f.denominator()
332
+ f = f/(gcd(f.coefficients()))
333
+
334
+ # compute the infinite height
335
+ L2norm_sq = sum([a**2 for a in f.coefficients()])
336
+
337
+ return (6*(L2norm_sq)**3)
338
+
339
+
340
+ def PGL_repn(rational_function):
341
+ r"""
342
+ Take a linear fraction transformation and represent it as a 2x2 matrix.
343
+
344
+ INPUT:
345
+
346
+ - ``rational_function`` -- a linear fraction transformation
347
+
348
+ OUTPUT: a 2x2 matrix representing ``rational_function``
349
+
350
+ EXAMPLES::
351
+
352
+ sage: R.<z> = PolynomialRing(QQ)
353
+ sage: f = (2*z-1)/(3-z)
354
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import PGL_repn
355
+ sage: PGL_repn(f)
356
+ [-2 1]
357
+ [ 1 -3]
358
+ """
359
+ if isinstance(rational_function, Matrix):
360
+ return rational_function
361
+ K = rational_function.parent()
362
+ F = K.base_ring()
363
+ if not K.is_field():
364
+ return matrix(F, 2, [rational_function[1], rational_function[0], 0, 1])
365
+ else:
366
+ f = rational_function.numerator()
367
+ g = rational_function.denominator()
368
+ return matrix(F, 2, [f[1], f[0], g[1], g[0]])
369
+
370
+
371
+ def PGL_order(A):
372
+ r"""
373
+ Find the multiplicative order of a linear fractional transformation that
374
+ has a finite order as an element of `PGL_2(R)`.
375
+
376
+ ``A`` can be represented either as a rational function or a 2x2 matrix
377
+
378
+ INPUT:
379
+
380
+ - ``A`` -- a linear fractional transformation
381
+
382
+ OUTPUT: a positive integer
383
+
384
+ EXAMPLES::
385
+
386
+ sage: M = matrix([[0,2], [2,0]])
387
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import PGL_order
388
+ sage: PGL_order(M)
389
+ 2
390
+
391
+ ::
392
+
393
+ sage: R.<x> = PolynomialRing(QQ)
394
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import PGL_order
395
+ sage: PGL_order(-1/x)
396
+ 2
397
+ """
398
+
399
+ n = 1
400
+ AA = PGL_repn(A)
401
+ B = copy(AA)
402
+ while B[0][0] != B[1][1] or B[0][1] != 0 or B[1][0] != 0:
403
+ n = n + 1
404
+ B = AA*B
405
+
406
+ return n
407
+
408
+
409
+ def CRT_helper(automorphisms, moduli):
410
+ r"""
411
+ Lift the given list of automorphisms to `Zmod(M)`.
412
+
413
+ Given a list of automorphisms over various `Zmod(p^k)` find a list
414
+ of automorphisms over `Zmod(M)` where `M=\prod p^k` that surjects
415
+ onto every tuple of automorphisms from the various `Zmod(p^k)`.
416
+
417
+ INPUT:
418
+
419
+ - ``automorphisms`` -- list of lists of automorphisms over various `Zmod(p^k)`
420
+
421
+ - ``moduli`` -- list of the various `p^k`
422
+
423
+ OUTPUT: list of automorphisms over `Zmod(M)`
424
+
425
+ EXAMPLES::
426
+
427
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import CRT_helper
428
+ sage: CRT_helper([[matrix([[4,0], [0,1]]), matrix([[0,1], [1,0]])]], [5])
429
+ ([
430
+ [4 0] [0 1]
431
+ [0 1], [1 0]
432
+ ], 5)
433
+ """
434
+ if len(automorphisms) > 2:
435
+ temp, modulus = CRT_helper(
436
+ [automorphisms[i] for i in range(len(automorphisms)) if i != 0],
437
+ [moduli[i] for i in range(len(moduli)) if i != 0])
438
+ elif len(automorphisms) == 2:
439
+ temp = automorphisms[1]
440
+ modulus = moduli[1]
441
+ else:
442
+ return automorphisms[0], moduli[0]
443
+
444
+ autos = []
445
+ for B in temp:
446
+ for C in automorphisms[0]:
447
+ A = matrix(Integers(modulus*moduli[0]), 2,
448
+ [CRT(B[0][0].lift(), C[0][0].lift(), modulus, moduli[0]),
449
+ CRT(B[0][1].lift(), C[0][1].lift(), modulus, moduli[0]),
450
+ CRT(B[1][0].lift(), C[1][0].lift(), modulus, moduli[0]),
451
+ CRT(B[1][1].lift(), C[1][1].lift(), modulus, moduli[0])])
452
+ autos.append(A)
453
+
454
+ return autos, modulus*moduli[0]
455
+
456
+
457
+ def CRT_automorphisms(automorphisms, order_elts, degree, moduli):
458
+ r"""
459
+ Compute a maximal list of automorphisms over `Zmod(M)`.
460
+
461
+ Given a list of automorphisms over various `Zmod(p^k)`, a list of the
462
+ elements orders, an integer degree, and a list of the `p^k` values compute
463
+ a maximal list of automorphisms over `Zmod(M)`, such that for every `j` in ``len(moduli)``,
464
+ each element reduces mod ``moduli[j]`` to one of the elements in ``automorphisms[j]`` that
465
+ has order = ``degree``
466
+
467
+ INPUT:
468
+
469
+ - ``automorphisms`` -- list of lists of automorphisms over various `Zmod(p^k)`
470
+
471
+ - ``order_elts`` -- list of lists of the orders of the elements of ``automorphisms``
472
+
473
+ - ``degree`` -- positive integer
474
+
475
+ - ``moduli`` -- list of prime powers, i.e., `p^k`
476
+
477
+ OUTPUT: list containing a list of automorphisms over `Zmod(M)` and the
478
+ product of the moduli
479
+
480
+ EXAMPLES::
481
+
482
+ sage: aut = [[matrix([[1,0], [0,1]]), matrix([[0,1], [1,0]])]]
483
+ sage: ords = [[1,2]]
484
+ sage: degree = 2
485
+ sage: mods = [5]
486
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import CRT_automorphisms
487
+ sage: CRT_automorphisms(aut,ords,degree,mods)
488
+ ([
489
+ [0 1]
490
+ [1 0]
491
+ ], 5)
492
+ """
493
+ # restrict to automorphisms of degree `degree`
494
+ degree_d_autos = []
495
+ for j in range(len(automorphisms)):
496
+ L = automorphisms[j]
497
+ degree_d_autos.append(
498
+ [L[i] for i in range(len(L)) if order_elts[j][i] == degree])
499
+
500
+ # get list of CRT'ed automorphisms
501
+ return CRT_helper(degree_d_autos, moduli)
502
+
503
+
504
+ def valid_automorphisms(automorphisms_CRT, rational_function, ht_bound, M,
505
+ return_functions=False):
506
+ r"""
507
+ Check if automorphism mod `p^k` lifts to automorphism over `\ZZ`.
508
+
509
+ Checks whether an element that is an automorphism of ``rational_function`` modulo `p^k` for various
510
+ `p` s and `k` s can be lifted to an automorphism over `\ZZ`. It uses the fact that every
511
+ automorphism has height at most ``ht_bound``
512
+
513
+ INPUT:
514
+
515
+ - ``automorphisms`` -- list of lists of automorphisms over various `Zmod(p^k)`
516
+
517
+ - ``rational_function`` -- a one variable rational function
518
+
519
+ - ``ht_bound`` -- positive integer
520
+
521
+ - ``M`` -- positive integer, a product of prime powers
522
+
523
+ - ``return_functions`` -- boolean (default: ``False``)
524
+
525
+ OUTPUT: list of automorphisms over `\ZZ`
526
+
527
+ EXAMPLES::
528
+
529
+ sage: R.<z> = PolynomialRing(QQ)
530
+ sage: F = z^2
531
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import valid_automorphisms
532
+ sage: valid_automorphisms([matrix(GF(5), [[0,1],[1,0]])], F, 48, 5, True)
533
+ [1/z]
534
+ """
535
+ z = rational_function.parent().gen(0)
536
+ valid_auto = []
537
+
538
+ for A in automorphisms_CRT:
539
+ init_lift = [x.lift() for x in A.list()] # lift coefficients of A
540
+ # multiply lift by appropriate scalar matrices and adjust (mod M)
541
+ # to find an element of minimal height. These will have
542
+ # coefficients in [-M/2, M/2)
543
+ for scalar in M.coprime_integers(M):
544
+ new_lift = [scalar*x - (scalar*x/M).round()*M
545
+ for x in init_lift]
546
+ g = gcd(new_lift)
547
+ new_lift = [x // g for x in new_lift]
548
+ if all(abs(x) <= ht_bound for x in new_lift):
549
+ a, b, c, d = new_lift
550
+ f = (a*z + b) / (c*z + d)
551
+ if rational_function(f(z)) == f(rational_function(z)):
552
+ if return_functions:
553
+ valid_auto.append(f)
554
+ else:
555
+ valid_auto.append(matrix(ZZ,2,2,new_lift))
556
+ break
557
+
558
+ return valid_auto
559
+
560
+
561
+ def remove_redundant_automorphisms(automorphisms, order_elts, moduli, integral_autos):
562
+ r"""
563
+ If an element of `Aut_{F_p}` has been lifted to `\QQ`
564
+ remove that element from `Aut_{F_p}`.
565
+
566
+ We don't want to attempt to lift that element again unnecessarily.
567
+
568
+ INPUT:
569
+
570
+ - ``automorphisms`` -- list of lists of automorphisms
571
+
572
+ - ``order_elts`` -- list of lists of the orders of the elements of ``automorphisms``
573
+
574
+ - ``moduli`` -- list of prime powers
575
+
576
+ - ``integral_autos`` -- list of known automorphisms
577
+
578
+ OUTPUT: list of automorphisms
579
+
580
+ EXAMPLES::
581
+
582
+ sage: auts = [[matrix([[1,0],[0,1]]), matrix([[6,0],[0,1]]), matrix([[0,1],[1,0]]),
583
+ ....: matrix([[6,1],[1,1]]), matrix([[1,1],[1,6]]), matrix([[0,6],[1,0]]),
584
+ ....: matrix([[1,6],[1,1]]), matrix([[6,6],[1,6]])]]
585
+ sage: ord_elts = [[1, 2, 2, 2, 2, 2, 4, 4]]
586
+ sage: mods = [7]
587
+ sage: R.<x> = PolynomialRing(QQ)
588
+ sage: int_auts = [-1/x]
589
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import remove_redundant_automorphisms
590
+ sage: remove_redundant_automorphisms(auts, ord_elts, mods, int_auts)
591
+ [[
592
+ [1 0] [6 0] [0 1] [6 1] [1 1] [1 6] [6 6]
593
+ [0 1], [0 1], [1 0], [1 1], [1 6], [1 1], [1 6]
594
+ ]]
595
+ """
596
+ to_del = []
597
+
598
+ for i in range(len(automorphisms)):
599
+ p = moduli[i]
600
+ to_del_temp = []
601
+ for psi in integral_autos:
602
+ #The return_functions boolean determines if the automorphisms
603
+ #are matrices or linear fractional transformations
604
+ if isinstance(psi, Matrix):
605
+ ppsi = psi.change_ring(GF(p))
606
+ B = [ppsi[0,0], ppsi[0,1], ppsi[1,0], psi[1,1]]
607
+ else:
608
+ ff = psi.numerator().change_ring(GF(p))
609
+ gg = psi.denominator().change_ring(GF(p))
610
+ B = [ff[1],ff[0],gg[1],gg[0]]
611
+ for j in range(len(automorphisms[i])):
612
+ A = automorphisms[i][j]
613
+ M = matrix(GF(p), [B, [A[0][0], A[0][1], A[1][0], A[1][1]]])
614
+ if M.rank() == 1:
615
+ to_del_temp.append(j)
616
+ break
617
+ to_del.append(to_del_temp)
618
+
619
+ for i in range(len(to_del)):
620
+ to_del[i].sort()
621
+ to_del[i].reverse()
622
+ for j in to_del[i]:
623
+ del automorphisms[i][j]
624
+ del order_elts[i][j]
625
+
626
+ return automorphisms
627
+
628
+
629
+ def automorphism_group_QQ_CRT(rational_function, prime_lower_bound=4, return_functions=True, iso_type=False):
630
+ r"""
631
+ Determines the complete group of rational automorphisms (under the conjugation action
632
+ of `PGL(2,\QQ)`) for a rational function of one variable.
633
+
634
+ See [FMV]_ for details.
635
+
636
+ INPUT:
637
+
638
+ - ``rational_function`` -- a rational function of a univariate polynomial ring over `\QQ`
639
+
640
+ - ``prime_lower_bound`` -- (default: 4) a positive integer; a lower bound for the primes to use for
641
+ the Chinese Remainder Theorem step
642
+
643
+ - ``return_functions`` -- boolean (default: ``True``); ``True`` returns
644
+ linear fractional transformations ``False`` returns elements of `PGL(2,\QQ)`
645
+
646
+ - ``iso_type`` -- boolean (default: ``False``); ``True`` returns the
647
+ isomorphism type of the automorphism group
648
+
649
+ OUTPUT: a complete list of automorphisms of ``rational_function``
650
+
651
+ EXAMPLES::
652
+
653
+ sage: R.<z> = PolynomialRing(QQ)
654
+ sage: f = (3*z^2 - 1)/(z^3 - 3*z)
655
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import automorphism_group_QQ_CRT
656
+ sage: sorted(automorphism_group_QQ_CRT(f, 4, True))
657
+ [-1/z,
658
+ 1/z,
659
+ (-z - 1)/(z - 1),
660
+ (-z + 1)/(z + 1),
661
+ (z - 1)/(z + 1),
662
+ (z + 1)/(z - 1),
663
+ -z,
664
+ z]
665
+
666
+ ::
667
+
668
+ sage: R.<z> = PolynomialRing(QQ)
669
+ sage: f = (3*z^2 - 1)/(z^3 - 3*z)
670
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import automorphism_group_QQ_CRT
671
+ sage: sorted(automorphism_group_QQ_CRT(f, 4, False))
672
+ [
673
+ [-1 -1] [-1 0] [-1 1] [ 0 -1] [0 1] [ 1 -1] [1 0] [ 1 1]
674
+ [ 1 -1], [ 0 1], [ 1 1], [ 1 0], [1 0], [ 1 1], [0 1], [ 1 -1]
675
+ ]
676
+ """
677
+ if rational_function.parent().is_field():
678
+ K = rational_function.parent()
679
+ R = K.ring()
680
+ else:
681
+ R = rational_function.parent()
682
+ K = R.fraction_field()
683
+
684
+ F = R.base_ring()
685
+
686
+ if F != QQ and F != ZZ:
687
+ raise TypeError("coefficient ring is not the rational numbers or the integers")
688
+
689
+ z = R.gen(0)
690
+ phi = K(rational_function)
691
+
692
+ f = phi.numerator()
693
+ g = phi.denominator()
694
+
695
+ #scale f,g so both have integer coefficients
696
+ N = lcm(f.denominator(),g.denominator())
697
+ f = f*N
698
+ g = g*N
699
+ N = gcd(gcd(f.coefficients()), gcd(g.coefficients()))
700
+ f = f/N
701
+ g = g/N
702
+
703
+ d = max(f.degree(), g.degree())
704
+
705
+ if d == 1:
706
+ raise ValueError("rational function has degree 1")
707
+
708
+ #badprimes is an integer divisible by every prime p such that either
709
+ # 1) phi has bad reduction at p or
710
+ # 2) the reduction map fails to be injective
711
+ badprimes = (gcd(f[d],g[d])*f.resultant(g)*6)
712
+ #6 is because over Q, Aut(phi) has order dividing 12
713
+ #when generalizing to a number field K, 6 should be replaced with
714
+ # 2*gcd(2*[K:Q] + 1, d^3 - d)
715
+
716
+ #Determining the set that is used to obtain the height bound
717
+ h = R(prod(x[0] for x in (R(f - g*z)).factor()))# take minimal polynomial of fixed points
718
+ if h.degree() == 2: #if there are only 2 finite fixed points, take preimage of fixed points
719
+ h = h[2]*f**2 + h[1]*f*g + h[0]*g**2
720
+ elif h.degree() == 1: #if there is just 1 finite fixed point, take preimages under phi^2
721
+ psi = phi(phi(z))
722
+ f2 = psi.numerator()
723
+ g2 = psi.denominator()
724
+ N = lcm(f2.denominator(),g2.denominator())
725
+ f2 = f2*N
726
+ g2 = g2*N
727
+ N = gcd(gcd(f2.coefficients()), gcd(g2.coefficients()))
728
+ f2 = f2/N
729
+ g2 = g2/N
730
+ h = h[1]*f2 + h[0]*g2
731
+
732
+ MaxH = height_bound(h)
733
+ congruence = 1
734
+ primes = Primes()
735
+ p = primes.next(ZZ(prime_lower_bound))
736
+ primepowers = []
737
+ automorphisms = []
738
+ orderaut = []
739
+ orderelts = []
740
+
741
+ if return_functions:
742
+ elements = [z]
743
+ else:
744
+ elements = [matrix(ZZ, 2, [1,0,0,1])]
745
+
746
+ badorders = [1, 12]# order 12 not possible over Q, even though 4 and 6 are
747
+
748
+ #over QQ, elts of PGL_2 of finite order can only have order dividing 6 or 4,
749
+ # and the finite subgroups can only be cyclic or dihedral (Beauville) so
750
+ # the only possible groups are C_n, D_2n for n|6 or n|4
751
+ # all of these groups have order dividing 24
752
+ while (congruence < (2*MaxH**2)) and len(elements) < gcd(orderaut + [24]):
753
+ if badprimes % p != 0: #prime of good reduction
754
+ # compute automorphisms mod p
755
+ phi_p = f.change_ring(GF(p))/g.change_ring(GF(p))
756
+ sorted_automorphisms = automorphism_group_FF(phi_p)
757
+ sorted_automorphisms.sort(key=PGL_order)
758
+ orders = [PGL_order(A) for A in sorted_automorphisms]
759
+
760
+ automorphisms.append(sorted_automorphisms)
761
+ orderaut.append(len(automorphisms[-1]))
762
+ orderelts.append(orders)
763
+ primepowers.append(p)
764
+
765
+ # check if we already found 8 or 12 automorphisms
766
+ # and the gcd of orders over Fp and 24 is 24
767
+ # or if the gcd is equal to the number of automorphisms we have
768
+ if (len(elements) == gcd(orderaut + [24])) or \
769
+ (gcd(orderaut + [24]) == 24 and
770
+ (len(elements) == 12 or len(elements) == 8)):
771
+ if iso_type:
772
+ return elements, which_group(elements)
773
+ return elements
774
+ else:
775
+ N = gcd(orderaut + [12]) # all orders of elements divide N
776
+ for order in divisors(N):
777
+ if order in badorders:
778
+ continue
779
+ # range over all orders
780
+ # that are possible over QQ such that we haven't already
781
+ # found all elements of that order
782
+
783
+ # First count number of elements of particular order
784
+ numeltsoffixedorder = []
785
+ for L in orderelts:
786
+ numeltsoffixedorder.append(L.count(order))
787
+ numelts = min(numeltsoffixedorder)
788
+ # Have some elts of fixed order mod p for each p
789
+ if numelts != 0:
790
+ # CRT order d elements together and check if
791
+ # they are an automorphism
792
+ autos, M = CRT_automorphisms(automorphisms,
793
+ orderelts, order, primepowers)
794
+ temp = valid_automorphisms(autos, phi, MaxH, M,
795
+ return_functions)
796
+ elements.extend(temp)
797
+
798
+ if (len(elements) == gcd(orderaut + [24])):
799
+ #found enough automorphisms
800
+ if iso_type:
801
+ return elements, which_group(elements)
802
+ return elements
803
+ elif numelts <= (len(temp)):
804
+ badorders.append(order)
805
+ # found all elements of order 'order;
806
+ elif len(temp) != 0:
807
+ # found some elements of order 'order'
808
+ # if an element of Aut_{F_p} has been lifted to QQ
809
+ # remove that element from Aut_{F_p} so we don't
810
+ # attempt to lift that element again unnecessarily
811
+ automorphisms = remove_redundant_automorphisms(automorphisms,
812
+ orderelts, primepowers, temp)
813
+ if order == 4: #have some elements of order 4
814
+ # so possible aut group is Z/4 or D_4
815
+ badorders.extend([3, 6])
816
+ elif order == 3 or order == 6:#have some elements of
817
+ # order 3 or 6 so possible aut groups are Z/3,
818
+ # D_3, Z/6, or D_6
819
+ badorders.append(4)
820
+ else: #no elements of order d in some F_v
821
+ for m in divisors(N):
822
+ if m % order == 0:
823
+ badorders.append(m)
824
+ #no elements of that order or any order that
825
+ # is a multiple of it
826
+ if all(order in badorders for order in divisors(N)):
827
+ #found all elements of every possible order
828
+ if iso_type:
829
+ return (elements, which_group(elements))
830
+ return elements
831
+ congruence = congruence * p
832
+
833
+ p = primes.next(p)
834
+
835
+ if iso_type:
836
+ return elements, which_group(elements)
837
+ return elements
838
+
839
+
840
+ def automorphism_group_FF(rational_function, absolute=False, iso_type=False, return_functions=False):
841
+ r"""
842
+ This function computes automorphism groups over finite fields.
843
+
844
+ ALGORITHM:
845
+
846
+ See Algorithm 4 in Faber-Manes-Viray [FMV]_.
847
+
848
+ INPUT:
849
+
850
+ - ``rational_function`` -- a rational function defined over the fraction field
851
+ of a polynomial ring in one variable with finite field coefficients
852
+
853
+ - ``absolute`` -- boolean (default: ``False``); ``True`` returns the
854
+ absolute automorphism group and a field of definition
855
+
856
+ - ``iso_type`` -- boolean (default: ``False``); ``True`` returns the
857
+ isomorphism type of the automorphism group
858
+
859
+ - ``return_functions`` -- boolean (default: ``False``); ``True`` returns
860
+ linear fractional transformations ``False`` returns elements of `PGL(2)`
861
+
862
+ OUTPUT: list of automorphisms of ``rational_function``
863
+
864
+ EXAMPLES::
865
+
866
+ sage: R.<x> = PolynomialRing(GF(5^2, 't'))
867
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import automorphism_group_FF
868
+ sage: automorphism_group_FF((x^2+x+1)/(x+1))
869
+ [
870
+ [1 0] [4 3]
871
+ [0 1], [0 1]
872
+ ]
873
+
874
+ ::
875
+
876
+ sage: R.<x> = PolynomialRing(GF(2^5, 't'))
877
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import automorphism_group_FF
878
+ sage: automorphism_group_FF(x^(5), True, False, True)
879
+ [Univariate Polynomial Ring in w over Finite Field in b of size 2^5, [w, 1/w]]
880
+
881
+ ::
882
+
883
+ sage: R.<x> = PolynomialRing(GF(2^5, 't'))
884
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import automorphism_group_FF
885
+ sage: automorphism_group_FF(x^(5), False, False, True)
886
+ [x, 1/x]
887
+ """
888
+
889
+ if not absolute:
890
+ G = automorphism_group_FF_alg3(rational_function)
891
+ else:
892
+ G = automorphism_group_FF_alg2(rational_function)
893
+
894
+ if not return_functions:
895
+ if absolute:
896
+ R = G[1][0].parent()
897
+ if R.is_field():
898
+ R = R.ring()
899
+ G[1] = [matrix(R.base_ring(),[[R(g.numerator())[1],R(g.numerator())[0]],[R(g.denominator())[1],R(g.denominator())[0]]]) for g in G[1]]
900
+ else:
901
+ R = G[0].parent()
902
+ if R.is_field():
903
+ R = R.ring()
904
+ G = [matrix(R.base_ring(),[[R(g.numerator())[1],R(g.numerator())[0]],[R(g.denominator())[1],R(g.denominator())[0]]]) for g in G]
905
+
906
+ if not iso_type:
907
+ return G
908
+ elif not absolute:
909
+ return G, which_group(G)
910
+ else:
911
+ return G, which_group(G[1])
912
+
913
+
914
+ def field_descent(sigma, y):
915
+ r"""
916
+ Function for descending an element in a field `E` to a subfield `F`.
917
+
918
+ Here `F`, `E` must be finite fields or number fields. This function determines
919
+ the unique image of subfield which is ``y`` by the embedding ``sigma`` if it exists.
920
+ Otherwise returns ``None``.
921
+ This functionality is necessary because Sage does not keep track of subfields.
922
+
923
+ INPUT:
924
+
925
+ - ``sigma`` -- an embedding sigma: `F` -> `E` of fields
926
+
927
+ - ``y`` --an element of the field `E`
928
+
929
+ OUTPUT: the unique element of the subfield if it exists, otherwise ``None``
930
+
931
+ EXAMPLES::
932
+
933
+ sage: R = GF(11^2,'b')
934
+ sage: RR = GF(11)
935
+ sage: s = RR.Hom(R)[0]
936
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import field_descent
937
+ sage: field_descent(s, R(1))
938
+ 1
939
+ """
940
+ F = sigma.domain()
941
+ a = F.gen()
942
+
943
+ p = F.characteristic()
944
+ r = F.degree()
945
+ if p != 0 and y**(p**r) != y:
946
+ return
947
+
948
+ K = F.prime_subfield()
949
+ R = PolynomialRing(K,'X')
950
+ f = R(sigma(a).polynomial().coefficients(sparse=False))
951
+ g = R(y.polynomial().coefficients(sparse=False))
952
+
953
+ x = F(0)
954
+ quotient, remainder = g.quo_rem(f)
955
+ if not remainder.is_constant():
956
+ return
957
+ else:
958
+ x = x + F(remainder)
959
+
960
+ steps = 1
961
+ while not quotient.is_constant():
962
+ quotient, remainder = quotient.quo_rem(f)
963
+ if not remainder.is_constant():
964
+ return
965
+ else:
966
+ x = x + F(remainder)*a**(steps)
967
+ steps += 1
968
+
969
+ return x + F(quotient)*a**(steps)
970
+
971
+
972
+ def rational_function_coefficient_descent(rational_function, sigma, poly_ring):
973
+ r"""
974
+ Function for descending the coefficients of a rational function from field `E`
975
+ to a subfield `F`.
976
+
977
+ Here `F`, `E` must be finite fields or number fields.
978
+ It determines the unique rational function in fraction field of
979
+ ``poly_ring`` which is the image of ``rational_function`` by ``sigma``,
980
+ if it exists, and otherwise returns ``None``.
981
+
982
+ INPUT:
983
+
984
+ - ``rational_function``--a rational function with coefficients in a field `E`
985
+
986
+ - ``sigma`` -- a field embedding sigma: `F` -> `E`
987
+
988
+ - ``poly_ring`` -- a polynomial ring `R` with coefficients in `F`
989
+
990
+ OUTPUT: a rational function with coefficients in the fraction field of ``poly_ring``
991
+ if it exists, and otherwise ``None``
992
+
993
+ EXAMPLES::
994
+
995
+ sage: T.<z> = PolynomialRing(GF(11^2,'b'))
996
+ sage: S.<y> = PolynomialRing(GF(11))
997
+ sage: s = S.base_ring().hom(T.base_ring())
998
+ sage: f = (3*z^3 - z^2)/(z-1)
999
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import rational_function_coefficient_descent
1000
+ sage: rational_function_coefficient_descent(f,s,S)
1001
+ (3*y^3 + 10*y^2)/(y + 10)
1002
+ """
1003
+
1004
+ if rational_function.parent().is_field():
1005
+ S = rational_function.parent().ring()
1006
+ else:
1007
+ S = rational_function.parent()
1008
+
1009
+ if rational_function == S(0):
1010
+ return poly_ring(0)
1011
+
1012
+ num = S(rational_function.numerator())
1013
+ denom = S(rational_function.denominator())
1014
+ f = num.coefficients()
1015
+ fe = num.exponents()
1016
+ g = denom.coefficients()
1017
+ ge = denom.exponents()
1018
+ #force the cancellation of common coefficient factors by scaling by f[-1]
1019
+ ff = [ field_descent(sigma, x/f[-1]) for x in f]
1020
+ gg = [ field_descent(sigma, x/f[-1]) for x in g]
1021
+ if None in ff or None in gg:
1022
+ return
1023
+
1024
+ z = poly_ring.gen(0)
1025
+ numer = sum(poly_ring(ff[i]) * z**fe[i] for i in range(len(ff)))
1026
+ denom = sum(poly_ring(gg[i]) * z**ge[i] for i in range(len(gg)))
1027
+ return numer / denom
1028
+
1029
+
1030
+ def rational_function_coerce(rational_function, sigma, S_polys):
1031
+ r"""
1032
+ Function for coercing a rational function defined over a ring `R` to have
1033
+ coefficients in a second ring ``S_polys``.
1034
+
1035
+ The fraction field of polynomial ring ``S_polys`` will contain the new rational function.
1036
+
1037
+ INPUT:
1038
+
1039
+ - ``rational_function`` -- rational function with coefficients in `R`
1040
+
1041
+ - ``sigma`` -- a ring homomorphism sigma: `R` -> ``S_polys``
1042
+
1043
+ - ``S_polys`` -- a polynomial ring
1044
+
1045
+ OUTPUT: a rational function with coefficients in ``S_polys``
1046
+
1047
+ EXAMPLES::
1048
+
1049
+ sage: R.<y> = PolynomialRing(QQ)
1050
+ sage: S.<z> = PolynomialRing(ZZ)
1051
+ sage: s = S.hom([z],R)
1052
+ sage: f = (3*z^2 + 1)/(z^3-1)
1053
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import rational_function_coerce
1054
+ sage: rational_function_coerce(f,s,R)
1055
+ (3*y^2 + 1)/(y^3 - 1)
1056
+ """
1057
+ if rational_function.parent().is_field():
1058
+ R = rational_function.parent().ring()
1059
+ else:
1060
+ R = rational_function.parent()
1061
+
1062
+ f = R(rational_function.numerator()).coefficients(sparse=False)
1063
+ g = R(rational_function.denominator()).coefficients(sparse=False)
1064
+
1065
+ if g == [R(1)]:
1066
+ return S_polys([sigma(a) for a in f]) # allows for coercion of polynomials
1067
+ else:
1068
+ return S_polys([sigma(a) for a in f]) / S_polys([sigma(b) for b in g])
1069
+
1070
+
1071
+ def rational_function_reduce(rational_function):
1072
+ r"""
1073
+ Force Sage to divide out common factors in numerator and denominator
1074
+ of rational function.
1075
+
1076
+ INPUT:
1077
+
1078
+ - ``rational_function`` -- rational function `= F/G` in univariate polynomial ring
1079
+
1080
+ OUTPUT: rational function -- `(F/\gcd(F,G)) / (G/\gcd(F,G))`
1081
+
1082
+ EXAMPLES::
1083
+
1084
+ sage: R.<z> = PolynomialRing(GF(7))
1085
+ sage: f = ((z-1)*(z^2+z+1))/((z-1)*(z^3+1))
1086
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import rational_function_reduce
1087
+ sage: rational_function_reduce(f)
1088
+ (z^2 + z + 1)/(z^3 + 1)
1089
+ """
1090
+ phi = rational_function
1091
+ F = phi.numerator()
1092
+ G = phi.denominator()
1093
+ comm_factor = gcd(F,G)
1094
+ return (F.quo_rem(comm_factor)[0]) / (G.quo_rem(comm_factor)[0])
1095
+
1096
+
1097
+ def three_stable_points(rational_function, invariant_list):
1098
+ r"""
1099
+ Implementation of Algorithm 1 for automorphism groups from
1100
+ Faber-Manes-Viray [FMV]_.
1101
+
1102
+ INPUT:
1103
+
1104
+ - ``rational_function`` -- rational function `\phi` defined over finite
1105
+ field `E`
1106
+
1107
+ - ``invariant_list`` -- list of at least `3` points of `\mathbb{P}^1(E)` that
1108
+ is stable under `Aut_{\phi}(E)`
1109
+
1110
+ OUTPUT: list of automorphisms
1111
+
1112
+ EXAMPLES::
1113
+
1114
+ sage: R.<z> = PolynomialRing(GF(5^2,'t'))
1115
+ sage: f = z^3
1116
+ sage: L = [[0,1],[4,1],[1,1],[1,0]]
1117
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import three_stable_points
1118
+ sage: three_stable_points(f,L)
1119
+ [z, 4*z, 1/z, 4/z]
1120
+ """
1121
+ # define ground field and ambient function field
1122
+ if rational_function.parent().is_field():
1123
+ K = rational_function.parent()
1124
+ R = K.ring()
1125
+ else:
1126
+ R = rational_function.parent()
1127
+ K = R.fraction_field()
1128
+
1129
+ z = R.gen(0)
1130
+ phi = K(rational_function)
1131
+
1132
+ T = invariant_list
1133
+
1134
+ automorphisms = []
1135
+ for t in permutations(range(len(T)),3):
1136
+ a = (T[0][0]*T[1][1]*T[2][1]*T[t[0]][0]*T[t[1]][0]*T[t[2]][1] -
1137
+ T[0][0]*T[1][1]*T[2][1]*T[t[0]][0]*T[t[1]][1]*T[t[2]][0] -
1138
+ T[0][1]*T[1][0]*T[2][1]*T[t[0]][0]*T[t[1]][0]*T[t[2]][1] +
1139
+ T[0][1]*T[1][0]*T[2][1]*T[t[0]][1]*T[t[1]][0]*T[t[2]][0] +
1140
+ T[0][1]*T[1][1]*T[2][0]*T[t[0]][0]*T[t[1]][1]*T[t[2]][0] -
1141
+ T[0][1]*T[1][1]*T[2][0]*T[t[0]][1]*T[t[1]][0]*T[t[2]][0])
1142
+
1143
+ b = (T[0][0]*T[1][0]*T[2][1]*T[t[0]][0]*T[t[1]][1]*T[t[2]][0] -
1144
+ T[0][0]*T[1][0]*T[2][1]*T[t[0]][1]*T[t[1]][0]*T[t[2]][0] -
1145
+ T[0][0]*T[1][1]*T[2][0]*T[t[0]][0]*T[t[1]][0] * T[t[2]][1] +
1146
+ T[0][0]*T[1][1]*T[2][0]*T[t[0]][1]*T[t[1]][0]*T[t[2]][0] +
1147
+ T[0][1]*T[1][0]*T[2][0]*T[t[0]][0]*T[t[1]][0]*T[t[2]][1] -
1148
+ T[0][1]*T[1][0]*T[2][0]*T[t[0]][0]*T[t[1]][1]*T[t[2]][0])
1149
+
1150
+ c = (T[0][0]*T[1][1]*T[2][1]*T[t[0]][1]*T[t[1]][0] * T[t[2]][1] -
1151
+ T[0][0]*T[1][1]*T[2][1]*T[t[0]][1]*T[t[1]][1]*T[t[2]][0] -
1152
+ T[0][1]*T[1][0]*T[2][1]*T[t[0]][0]*T[t[1]][1]*T[t[2]][1] +
1153
+ T[0][1]*T[1][0]*T[2][1]*T[t[0]][1]*T[t[1]][1]*T[t[2]][0] +
1154
+ T[0][1]*T[1][1]*T[2][0]*T[t[0]][0]*T[t[1]][1]*T[t[2]][1] -
1155
+ T[0][1]*T[1][1]*T[2][0]*T[t[0]][1]*T[t[1]][0]*T[t[2]][1])
1156
+
1157
+ d = (T[0][0]*T[1][0]*T[2][1]*T[t[0]][0]*T[t[1]][1]*T[t[2]][1] -
1158
+ T[0][0]*T[1][0]*T[2][1]*T[t[0]][1]*T[t[1]][0] * T[t[2]][1] -
1159
+ T[0][0]*T[1][1]*T[2][0]*T[t[0]][0]*T[t[1]][1]*T[t[2]][1] +
1160
+ T[0][0]*T[1][1]*T[2][0]*T[t[0]][1]*T[t[1]][1]*T[t[2]][0] +
1161
+ T[0][1]*T[1][0]*T[2][0]*T[t[0]][1]*T[t[1]][0] * T[t[2]][1] -
1162
+ T[0][1]*T[1][0]*T[2][0]*T[t[0]][1]*T[t[1]][1]*T[t[2]][0])
1163
+
1164
+ if a*d - b*c != 0:
1165
+ s = K(a*z + b) / K(c*z + d)
1166
+ if s(phi(z)) == phi(s(z)) and s not in automorphisms:
1167
+ automorphisms.append(s)
1168
+ return automorphisms
1169
+
1170
+
1171
+ def automorphism_group_FF_alg2(rational_function):
1172
+ r"""
1173
+ Implementation of algorithm for determining the absolute automorphism
1174
+ group over a finite field, given an invariant set, see [FMV]_.
1175
+
1176
+ INPUT:
1177
+
1178
+ - ``rational_function`` -- a rational function defined over a finite field
1179
+
1180
+ OUTPUT: absolute automorphism group of ``rational_function`` and a ring of definition
1181
+
1182
+ EXAMPLES::
1183
+
1184
+ sage: R.<z> = PolynomialRing(GF(7^2,'t'))
1185
+ sage: f = (3*z^3 - z^2)/(z-1)
1186
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import automorphism_group_FF_alg2
1187
+ sage: automorphism_group_FF_alg2(f)
1188
+ [Univariate Polynomial Ring in w over Finite Field in b of size 7^2, [w, 5/w]]
1189
+
1190
+ ::
1191
+
1192
+ sage: R.<z> = PolynomialRing(GF(5^3,'t'))
1193
+ sage: f = (3456*z^(4))
1194
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import automorphism_group_FF_alg2
1195
+ sage: automorphism_group_FF_alg2(f)
1196
+ [Univariate Polynomial Ring in w over Finite Field in b of size 5^6,
1197
+ [w,
1198
+ (3*b^5 + 4*b^4 + 3*b^2 + 2*b + 1)*w,
1199
+ (2*b^5 + b^4 + 2*b^2 + 3*b + 3)*w,
1200
+ 1/w,
1201
+ (3*b^5 + 4*b^4 + 3*b^2 + 2*b + 1)/w,
1202
+ (2*b^5 + b^4 + 2*b^2 + 3*b + 3)/w]]
1203
+ """
1204
+ # define ground field and ambient function field
1205
+ if rational_function.parent().is_field():
1206
+ K = rational_function.parent()
1207
+ R = K.ring()
1208
+ else:
1209
+ R = rational_function.parent()
1210
+ K = R.fraction_field()
1211
+
1212
+ F = R.base_ring()
1213
+ if not F.is_finite() or not F.is_field():
1214
+ raise TypeError("coefficient ring is not a finite field")
1215
+ p = F.characteristic()
1216
+ z = R.gen(0)
1217
+ phi = K(rational_function)
1218
+ f = phi.numerator()
1219
+ g = phi.denominator()
1220
+ D = max(f.degree(), g.degree())
1221
+
1222
+ # Build an invariant set for phi
1223
+ fix = f(z) - z*g(z)
1224
+ factor_list = fix.factor()
1225
+ minimal_fix_poly = R(prod(x[0] for x in factor_list))
1226
+ n = sum(x[0].degree() for x in factor_list) + bool(fix.degree() < D+1)
1227
+
1228
+ if n >= 3:
1229
+ T_poly = minimal_fix_poly
1230
+ infinity_check = bool(fix.degree() < D+1)
1231
+ elif n == 2:
1232
+ # Infinity is a fixed point
1233
+ if bool(fix.degree() < D+1):
1234
+ y = fix.roots(multiplicities=False)[0]
1235
+ preimage = g*(f(z) - y*g(z))
1236
+ infinity_check = 1
1237
+ # Infinity is not a fixed point
1238
+ else:
1239
+ C = minimal_fix_poly.coefficients(sparse=False)
1240
+ preimage = C[2]*f(z)**2 + C[1]*f(z)*g(z) + C[0]*g(z)**2
1241
+ infinity_check = bool(preimage.degree() < 2*D)
1242
+
1243
+ T_poly = R(prod(x[0] for x in preimage.factor()))
1244
+
1245
+ else: #case n=1
1246
+ # Infinity is the fixed point
1247
+ if bool(fix.degree() < D+1):
1248
+ minimal_preimage = R(prod(x[0] for x in g.factor()))
1249
+ if minimal_preimage.degree() + 1 >= 3:
1250
+ T_poly = minimal_preimage
1251
+ infinity_check = 1
1252
+ else:
1253
+ T_poly = R(prod(x[0] for x in phi(phi(z)).denominator().factor() ) )
1254
+ infinity_check = 1
1255
+
1256
+ # Infinity is not a fixed point
1257
+ else:
1258
+ y = fix.roots(multiplicities=False)[0]
1259
+ preimage = R(f(z) - y*g(z))
1260
+ minimal_preimage = R(prod(x[0] for x in preimage.factor()))
1261
+ if minimal_preimage.degree() + bool(preimage.degree() < D) >= 3:
1262
+ T_poly = minimal_preimage
1263
+ infinity_check = bool(preimage.degree() < D)
1264
+ else:
1265
+ preimage2 = R(phi(phi(z)).numerator() - y*phi(phi(z)).denominator())
1266
+ T_poly = R(prod(x[0] for x in preimage2.factor() ) )
1267
+ infinity_check = bool(preimage2.degree() < D**2)
1268
+
1269
+ # Define a field of definition for the absolute automorphism group
1270
+ r = lcm([x[0].degree() for x in T_poly.factor()])*F.degree()
1271
+ E = GF(p**r,'b')
1272
+ sigma = F.Hom(E)[0]
1273
+ S = PolynomialRing(E, 'w')
1274
+ E_poly = rational_function_coerce(T_poly, sigma, S)
1275
+
1276
+ T = [ [alpha, E(1)] for alpha in E_poly.roots(ring=E, multiplicities=False)]
1277
+ if infinity_check == 1:
1278
+ T.append([E(1),E(0)])
1279
+
1280
+ # Coerce phi into the larger ring and call Algorithm 1
1281
+ Phi = rational_function_coerce(phi, sigma, S)
1282
+ return [S, three_stable_points(Phi, T)]
1283
+
1284
+
1285
+ def order_p_automorphisms(rational_function, pre_image):
1286
+ r"""
1287
+ Determine the order-p automorphisms given the input data.
1288
+
1289
+ This is algorithm 4 in Faber-Manes-Viray [FMV]_.
1290
+
1291
+ INPUT:
1292
+
1293
+ - ``rational_function`` -- rational function defined over finite field `F`
1294
+
1295
+ - ``pre_image`` -- set of triples `[x, L, f]`, where `x` is an `F`-rational
1296
+ fixed point of ``rational_function``, `L` is the list of `F`-rational
1297
+ pre-images of `x` (excluding `x`), and `f` is the polynomial defining
1298
+ the full set of pre-images of `x` (again excluding `x` itself)
1299
+
1300
+ OUTPUT: set of automorphisms of order `p` defined over `F`
1301
+
1302
+ EXAMPLES::
1303
+
1304
+ sage: R.<x> = PolynomialRing(GF(11))
1305
+ sage: f = x^11
1306
+ sage: L = [[[0, 1], [], 1], [[10, 1], [], 1], [[9, 1], [], 1],
1307
+ ....: [[8, 1], [],1], [[7, 1], [], 1], [[6, 1], [], 1], [[5, 1], [], 1],
1308
+ ....: [[4, 1], [], 1],[[3, 1], [], 1], [[2, 1], [], 1], [[1, 1], [], 1],
1309
+ ....: [[1, 0], [], 1]]
1310
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import order_p_automorphisms
1311
+ sage: order_p_automorphisms(f,L)
1312
+ [x/(x + 1), 6*x/(x + 6), 3*x/(x + 3), 7*x/(x + 7), 9*x/(x + 9), 10*x/(x
1313
+ + 10), 5*x/(x + 5), 8*x/(x + 8), 4*x/(x + 4), 2*x/(x + 2), 10/(x + 2),
1314
+ (5*x + 10)/(x + 7), (2*x + 10)/(x + 4), (6*x + 10)/(x + 8), (8*x +
1315
+ 10)/(x + 10), (9*x + 10)/x, (4*x + 10)/(x + 6), (7*x + 10)/(x + 9), (3*x
1316
+ + 10)/(x + 5), (x + 10)/(x + 3), (10*x + 7)/(x + 3), (4*x + 7)/(x + 8),
1317
+ (x + 7)/(x + 5), (5*x + 7)/(x + 9), (7*x + 7)/x, (8*x + 7)/(x + 1), (3*x
1318
+ + 7)/(x + 7), (6*x + 7)/(x + 10), (2*x + 7)/(x + 6), 7/(x + 4), (9*x +
1319
+ 2)/(x + 4), (3*x + 2)/(x + 9), 2/(x + 6), (4*x + 2)/(x + 10), (6*x +
1320
+ 2)/(x + 1), (7*x + 2)/(x + 2), (2*x + 2)/(x + 8), (5*x + 2)/x, (x +
1321
+ 2)/(x + 7), (10*x + 2)/(x + 5), (8*x + 6)/(x + 5), (2*x + 6)/(x + 10),
1322
+ (10*x + 6)/(x + 7), (3*x + 6)/x, (5*x + 6)/(x + 2), (6*x + 6)/(x + 3),
1323
+ (x + 6)/(x + 9), (4*x + 6)/(x + 1), 6/(x + 8), (9*x + 6)/(x + 6), (7*x +
1324
+ 8)/(x + 6), (x + 8)/x, (9*x + 8)/(x + 8), (2*x + 8)/(x + 1), (4*x +
1325
+ 8)/(x + 3), (5*x + 8)/(x + 4), 8/(x + 10), (3*x + 8)/(x + 2), (10*x +
1326
+ 8)/(x + 9), (8*x + 8)/(x + 7), (6*x + 8)/(x + 7), 8/(x + 1), (8*x +
1327
+ 8)/(x + 9), (x + 8)/(x + 2), (3*x + 8)/(x + 4), (4*x + 8)/(x + 5), (10*x
1328
+ + 8)/x, (2*x + 8)/(x + 3), (9*x + 8)/(x + 10), (7*x + 8)/(x + 8), (5*x +
1329
+ 6)/(x + 8), (10*x + 6)/(x + 2), (7*x + 6)/(x + 10), 6/(x + 3), (2*x +
1330
+ 6)/(x + 5), (3*x + 6)/(x + 6), (9*x + 6)/(x + 1), (x + 6)/(x + 4), (8*x
1331
+ + 6)/x, (6*x + 6)/(x + 9), (4*x + 2)/(x + 9), (9*x + 2)/(x + 3), (6*x +
1332
+ 2)/x, (10*x + 2)/(x + 4), (x + 2)/(x + 6), (2*x + 2)/(x + 7), (8*x +
1333
+ 2)/(x + 2), 2/(x + 5), (7*x + 2)/(x + 1), (5*x + 2)/(x + 10), (3*x +
1334
+ 7)/(x + 10), (8*x + 7)/(x + 4), (5*x + 7)/(x + 1), (9*x + 7)/(x + 5),
1335
+ 7/(x + 7), (x + 7)/(x + 8), (7*x + 7)/(x + 3), (10*x + 7)/(x + 6), (6*x
1336
+ + 7)/(x + 2), (4*x + 7)/x, (2*x + 10)/x, (7*x + 10)/(x + 5), (4*x +
1337
+ 10)/(x + 2), (8*x + 10)/(x + 6), (10*x + 10)/(x + 8), 10/(x + 9), (6*x +
1338
+ 10)/(x + 4), (9*x + 10)/(x + 7), (5*x + 10)/(x + 3), (3*x + 10)/(x + 1),
1339
+ x + 1, x + 2, x + 4, x + 8, x + 5, x + 10, x + 9, x + 7, x + 3, x + 6]
1340
+ """
1341
+ # define ground field and ambient function field
1342
+ if rational_function.parent().is_field():
1343
+ K = rational_function.parent()
1344
+ R = K.ring()
1345
+ else:
1346
+ R = rational_function.parent()
1347
+ K = R.fraction_field()
1348
+
1349
+ z = R.gen(0)
1350
+ phi = K(rational_function)
1351
+ F = R.base_ring()
1352
+ q = F.cardinality()
1353
+ p = F.characteristic()
1354
+ r = (q-1) / (p-1) # index of F_p^\times inside F^\times
1355
+
1356
+ # Compute the threshold r2 for determining which algorithm to use
1357
+ if len(pre_image) > 1:
1358
+ r2 = len(pre_image)
1359
+ case = 'fix'
1360
+ elif len(pre_image[0][1]) > 0:
1361
+ r2 = len(pre_image[0][1])
1362
+ case = 'F-pre_images'
1363
+ else:
1364
+ factor_list = pre_image[0][2].factor()
1365
+ r2 = sum(x[0].degree() for x in factor_list)
1366
+ # Note that infinity is F-rational, so covered by preceding case
1367
+ case = 'all pre_images'
1368
+
1369
+ automorphisms_p = []
1370
+
1371
+ if r2 >= r or r2 == 0:
1372
+ # Note that r2 == 0 corresponds to having a unique F-rational fixed point
1373
+ # that is totally ramified
1374
+
1375
+ for guy in pre_image:
1376
+ pt = guy[0]
1377
+ zeta = F.multiplicative_generator()
1378
+ alpha = zeta**r
1379
+
1380
+ if pt == [F(1),F(0)]:
1381
+ for j in range(r):
1382
+ s = z + zeta**j
1383
+ if s(phi(z)) == phi(s(z)):
1384
+ for i in range(p-1):
1385
+ automorphisms_p.append(z+alpha**i*zeta**j)
1386
+
1387
+ else:
1388
+ u = F(1) / (z - pt[0])
1389
+ u_inv = pt[0] + F(1)/z
1390
+ for j in range(r):
1391
+ s = u_inv( u(z) + zeta**j )
1392
+ if s(phi(z)) == phi(s(z)):
1393
+ for i in range(p-1):
1394
+ automorphisms_p.append(u_inv( u(z) + alpha**i*zeta**j) )
1395
+
1396
+ elif r2 < r:
1397
+
1398
+ if case == 'fix':
1399
+ T = [x[0] for x in pre_image]
1400
+ elif case == 'F-pre_images':
1401
+ T = list(pre_image[0][1])
1402
+ else:
1403
+ T = []
1404
+
1405
+ # loop over all F-rational pre-images
1406
+ for guy in pre_image:
1407
+ pt = guy[0]
1408
+ # treat case of multiple F-rational fixed points or
1409
+ # 1 F-rational fixed point with F-rational pre-images
1410
+ if T:
1411
+ M = [t for t in T if t != pt]
1412
+ m = len(M)
1413
+ if pt == [F(1), F(0)]:
1414
+ for i in range(1, m):
1415
+ s = z + M[i][0] - M[0][0]
1416
+ if s(phi(z)) == phi(s(z)):
1417
+ automorphisms_p.append(s)
1418
+ else:
1419
+ u = F(1) / (z - pt[0])
1420
+ u_inv = pt[0] + F(1)/z
1421
+ for i in range(1, m):
1422
+ if M[0] == [F(1), F(0)]:
1423
+ uy1 = 0
1424
+ else:
1425
+ uy1 = u(M[0][0])
1426
+ if M[i] == [F(1),F(0)]:
1427
+ uy2 = 0
1428
+ else:
1429
+ uy2 = u(M[i][0])
1430
+ s = u_inv( u(z) + uy2 - uy1 )
1431
+ if s(phi(z)) == phi(s(z)):
1432
+ automorphisms_p.append(s)
1433
+ elif not T:
1434
+ # create the extension field generated by pre-images of the unique fixed point
1435
+ T_poly = pre_image[0][2]
1436
+ e = lcm([x[0].degree() for x in T_poly.factor()])*F.degree()
1437
+ E = GF(p**e, 'b')
1438
+ sigma = F.Hom(E)[0]
1439
+ S = PolynomialRing(E, 'w')
1440
+ w = S.gen(0)
1441
+ E_poly = rational_function_coerce(T_poly, sigma, S)
1442
+ # List of roots permuted by elements of order p
1443
+ # Since infinity is F-rational, it won't appear in this list
1444
+ T = [ [alpha, E(1)] for alpha in E_poly.roots(ring=E, multiplicities=False)]
1445
+
1446
+ # coerce the rational function and fixed point into E
1447
+ Phi = rational_function_coerce(phi, sigma, S)
1448
+ Pt = [sigma(pt[0]), sigma(pt[1])]
1449
+
1450
+ m = len(T)
1451
+ if Pt == [E(1),E(0)]:
1452
+ for i in range(1, m):
1453
+ s = w + T[i][0] - T[0][0]
1454
+ if s(Phi(w)) == Phi(s(w)):
1455
+ automorphisms_p.append(rational_function_coefficient_descent(s, sigma, R))
1456
+ else:
1457
+ u = E(1) / (w - Pt[0])
1458
+ u_inv = Pt[0] + E(1)/w
1459
+ for i in range(1,m):
1460
+ uy1 = u(T[0][0])
1461
+ uy2 = u(T[i][0])
1462
+ s = u_inv( u(w) + uy2 - uy1 )
1463
+ if s(Phi(w)) == Phi(s(w)):
1464
+ s = rational_function_reduce(s)
1465
+ automorphisms_p.append(rational_function_coefficient_descent(s,sigma,R))
1466
+
1467
+ return automorphisms_p
1468
+
1469
+
1470
+ def automorphisms_fixing_pair(rational_function, pair, quad):
1471
+ r"""
1472
+ Compute the set of automorphisms with order prime to the characteristic
1473
+ that fix the pair, excluding the identity.
1474
+
1475
+ INPUT:
1476
+
1477
+ - ``rational_function`` -- rational function defined over finite field `E`
1478
+
1479
+ - ``pair`` -- a pair of points of `\mathbb{P}^1(E)`
1480
+
1481
+ - ``quad`` -- boolean; an indicator if this is a quadratic pair of points
1482
+
1483
+ OUTPUT: set of automorphisms with order prime to characteristic defined over `E` that fix
1484
+ the pair, excluding the identity
1485
+
1486
+ EXAMPLES::
1487
+
1488
+ sage: R.<z> = PolynomialRing(GF(7^2, 't'))
1489
+ sage: f = (z^2 + 5*z + 5)/(5*z^2 + 5*z + 1)
1490
+ sage: L = [[4, 1], [2, 1]]
1491
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import automorphisms_fixing_pair
1492
+ sage: sorted(automorphisms_fixing_pair(f, L, False))
1493
+ [6/(z + 1), (6*z + 6)/z]
1494
+ """
1495
+ # define ground field and ambient function field
1496
+ if rational_function.parent().is_field():
1497
+ K = rational_function.parent()
1498
+ R = K.ring()
1499
+ else:
1500
+ R = rational_function.parent()
1501
+ K = R.fraction_field()
1502
+
1503
+ z = R.gen(0)
1504
+ phi = K(rational_function)
1505
+ E = R.base_ring()
1506
+ f = phi.numerator()
1507
+ g = phi.denominator()
1508
+ D = max(f.degree(), g.degree())
1509
+
1510
+ #assumes the second coordinate of the point is 1
1511
+ if pair[0] == [1,0]:
1512
+ u = K(z - pair[1][0])
1513
+ u_inv = K(z + pair[1][0])
1514
+ elif pair[1] == [1,0]:
1515
+ u = K(E(1) / (z - pair[0][0]))
1516
+ u_inv = K( (pair[0][0]*z + 1) / z )
1517
+ else:
1518
+ u = K( (z - pair[1][0]) / (z - pair[0][0]) )
1519
+ u_inv = K( (pair[0][0]*z - pair[1][0] ) / (z - 1) )
1520
+
1521
+ automorphisms_prime_to_p = []
1522
+ # Quadratic automorphisms have order dividing q+1 and D, D-1, or D+1
1523
+ if quad:
1524
+ #need sqrt to get the cardinality of the base field and not the
1525
+ #degree 2 extension
1526
+ q = sqrt(E.cardinality())
1527
+ zeta = (E.multiplicative_generator())**(q-1)
1528
+ for j in [-1,0,1]:
1529
+ g = gcd(q+1, D + j)
1530
+ xi = zeta**( (q+1) / g )
1531
+ for i in range(1,g):
1532
+ s = u_inv(xi**i*u(z))
1533
+ if s(phi(z)) == phi(s(z)):
1534
+ automorphisms_prime_to_p.append(rational_function_reduce(s))
1535
+
1536
+ # rational automorphisms have order dividing q-1 and D, D-1, or D+1
1537
+ else:
1538
+ q = E.cardinality()
1539
+ zeta = E.multiplicative_generator()
1540
+ for j in [-1,0,1]:
1541
+ g = gcd(q-1, D + j)
1542
+ xi = zeta**( (q-1) / g )
1543
+ for i in range(1,g):
1544
+ s = u_inv(xi**i*u(z))
1545
+ if s(phi(z)) == phi(s(z)):
1546
+ automorphisms_prime_to_p.append(rational_function_reduce(s))
1547
+
1548
+ return list(set(automorphisms_prime_to_p))
1549
+
1550
+
1551
+ def automorphism_group_FF_alg3(rational_function):
1552
+ r"""
1553
+ Implementation of Algorithm 3 in the paper by Faber/Manes/Viray [FMV]_
1554
+ for computing the automorphism group over a finite field.
1555
+
1556
+ INPUT:
1557
+
1558
+ - ``rational_function`` -- a rational function defined over a finite field `F`
1559
+
1560
+ OUTPUT: list of `F`-rational automorphisms of ``rational_function``
1561
+
1562
+ EXAMPLES::
1563
+
1564
+ sage: R.<z> = PolynomialRing(GF(5^3,'t'))
1565
+ sage: f = 3456*z^4
1566
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import automorphism_group_FF_alg3
1567
+ sage: automorphism_group_FF_alg3(f)
1568
+ [z, 1/z]
1569
+ """
1570
+ # define ground field and ambient function field
1571
+ if rational_function.parent().is_field():
1572
+ K = rational_function.parent()
1573
+ R = K.ring()
1574
+ else:
1575
+ R = rational_function.parent()
1576
+ K = R.fraction_field()
1577
+
1578
+ F = R.base_ring()
1579
+ if not F.is_finite() or not F.is_field():
1580
+ raise TypeError("coefficient ring is not a finite field")
1581
+ p = F.characteristic()
1582
+ q = F.cardinality()
1583
+ z = R.gen(0)
1584
+ phi = K(rational_function)
1585
+ f = phi.numerator()
1586
+ g = phi.denominator()
1587
+ D = max(f.degree(), g.degree())
1588
+
1589
+ # For use in the quadratic extension parts of the algorithm
1590
+ E = GF(p**(2 * F.degree()), 'b')
1591
+ sigma = F.Hom(E)[0]
1592
+ S = PolynomialRing(E, 'w')
1593
+ Phi = rational_function_coerce(phi, sigma, S)
1594
+
1595
+ # Compute the set of distinct F-rational and F-quadratic
1596
+ # factors of the fixed point polynomial
1597
+ fix = R(f(z) - z*g(z))
1598
+ linear_fix = gcd(fix, z**q - z)
1599
+ quad_temp = fix.quo_rem(linear_fix)[0]
1600
+ residual = gcd(quad_temp, z**q - z)
1601
+ while residual.degree() > 0:
1602
+ quad_temp = quad_temp.quo_rem(residual)[0]
1603
+ residual = gcd(quad_temp, z**q - z)
1604
+ quadratic_fix = gcd(quad_temp, z**(q**2) - z).factor()
1605
+
1606
+ # Compute the set of distinct F-rational fixed points
1607
+ linear_fix_pts = [[ x, F(1)] for x in linear_fix.roots(multiplicities=False)]
1608
+ if bool(fix.degree() < D+1):
1609
+ linear_fix_pts.append( [F(1),F(0)] )
1610
+ n1 = len(linear_fix_pts)
1611
+
1612
+ # Coerce quadratic factors into a quadratic extension
1613
+ quad_fix_factors = [ rational_function_coerce(poly[0], sigma, S) for poly in quadratic_fix]
1614
+ n2 = 2*len(quad_fix_factors)
1615
+
1616
+ # Collect pre-image data as a list L with entries in the form
1617
+ # [fixed point y, F-rational pre-images z != y, polynomial defining the pre-images]
1618
+ # Note that we remove the fixed point from its pre-image set and its polynomial
1619
+ pre_images = []
1620
+ for y in linear_fix_pts:
1621
+ if y == [F(1),F(0)]:
1622
+ Fpre = [ [x,F(1)] for x in g.roots(multiplicities=False) ]
1623
+ pre_images.append([y, Fpre, g])
1624
+ else:
1625
+ Fpre = [ [x,F(1)] for x in (f - y[0]*g).roots(multiplicities=False) if x != y[0]]
1626
+ if y[0] == 0 and f.degree() < g.degree():
1627
+ Fpre.append([F(1), F(0)]) # infinity is a pre-image of 0
1628
+ elif f.degree() == g.degree() and f.leading_coefficient() == y[0]*g.leading_coefficient():
1629
+ Fpre.append([F(1), F(0)]) # infinity is a pre-image of y[0]
1630
+ # remove y[0] as a root of pre-image polynomial
1631
+ h = (f - y[0]*g).quo_rem(z-y[0])[0]
1632
+ h_common = gcd(h, z-y[0])
1633
+ while h_common.degree() > 0:
1634
+ h = h.quo_rem(z-y[0])[0]
1635
+ h_common = gcd(h,z-y[0])
1636
+ pre_images.append([y, Fpre, h])
1637
+
1638
+ # Initialize the set of automorphisms to contain the identity
1639
+ automorphisms = [R(z)]
1640
+ automorphisms_quad = []
1641
+
1642
+ # order p elements
1643
+ # An F-rational fixed point has orbit length 1 or p under the action of an element of
1644
+ # order p. An F-quadratic fixed point has orbit length p. The set of F-rational
1645
+ # pre-images of fixed points decomposes as a union of orbits of length p.
1646
+ if n1 % p == 1 and n2 % p == 0 and sum(len(x[1]) for x in pre_images) % p == 0:
1647
+ # Compute total number of distinct fixed points as a final check for order p auts
1648
+ factor_list = fix.factor()
1649
+ n = sum(x[0].degree() for x in factor_list) + bool(fix.degree() < D+1)
1650
+ if n % p == 1:
1651
+ automorphisms = automorphisms + order_p_automorphisms(phi, pre_images)
1652
+
1653
+ # nontrivial elements with order prime to p #
1654
+ # case of 2 F-rational fixed points
1655
+ for pt_pair in combinations(linear_fix_pts, 2):
1656
+ x = pt_pair[0]
1657
+ y = pt_pair[1]
1658
+ automorphisms = automorphisms + automorphisms_fixing_pair(phi, [x,y], False)
1659
+
1660
+ # case of 1 F-rational fixed point and an F-rational pre-image
1661
+ for y in pre_images:
1662
+ for x in y[1]:
1663
+ automorphisms = automorphisms + automorphisms_fixing_pair(phi, [x,y[0]], False)
1664
+
1665
+ # case of a pair of quadratic fixed points
1666
+ for h in quad_fix_factors:
1667
+ quad_fix_pts = [ [x,E(1)] for x in h.roots(multiplicities=False)]
1668
+ automorphisms_quad = automorphisms_quad + automorphisms_fixing_pair(Phi, quad_fix_pts, True)
1669
+
1670
+ phi_2 = phi(phi(z))
1671
+ f_2 = phi_2.numerator()
1672
+ g_2 = phi_2.denominator()
1673
+
1674
+ period_2 = (f_2(z) - z*g_2(z)).quo_rem(fix)[0]
1675
+ factor_list_2 = period_2.factor()
1676
+ linear_period_2_pts = [[ x, F(1)] for x in period_2.roots(multiplicities=False)]
1677
+ if bool(period_2.degree() < D**2-D):
1678
+ linear_period_2_pts.append( [F(1),F(0)] )
1679
+ quad_period_2_factors = [rational_function_coerce(poly[0], sigma, S) for poly in factor_list_2 if poly[0].degree() == 2]
1680
+ # n2 = n1 + 2*len(quad_fix_factors)
1681
+
1682
+ # case of a pair of F-rational period 2 points
1683
+ linear_period_2_pairs = []
1684
+ while linear_period_2_pts:
1685
+ x = linear_period_2_pts.pop(-1)
1686
+ if x[1] == 1 and g(x[0]) != 0:
1687
+ y = [phi(x[0]), F(1)]
1688
+ elif x[1] == 1 or f.degree() > g.degree():
1689
+ y = [F(1), F(0)]
1690
+ elif f.degree() == g.degree():
1691
+ y = [f.leading_coefficient() / g.leading_coefficient(), F(1)]
1692
+ else:
1693
+ y = [F(0), F(1)]
1694
+
1695
+ if x != y:
1696
+ linear_period_2_pts.remove(y)
1697
+ linear_period_2_pairs.append([x,y])
1698
+
1699
+ for pt_pair in linear_period_2_pairs:
1700
+ automorphisms = automorphisms + automorphisms_fixing_pair(phi, pt_pair, False)
1701
+
1702
+ # case of a pair of quadratic period 2 points
1703
+ for h in quad_period_2_factors:
1704
+ pt_pair = [ [x,E(1)] for x in h.roots(multiplicities=False)]
1705
+ if Phi(pt_pair[0][0]) == pt_pair[1][0]:
1706
+ automorphisms_quad = automorphisms_quad + automorphisms_fixing_pair(Phi, pt_pair, True)
1707
+
1708
+ # Descend coefficients of the quadratic guys back to the base field
1709
+ for s in automorphisms_quad:
1710
+ automorphisms.append(rational_function_coefficient_descent(s, sigma, R))
1711
+
1712
+ return automorphisms
1713
+
1714
+
1715
+ def which_group(list_of_elements):
1716
+ r"""
1717
+ Given a finite subgroup of `PGL_2` determine its isomorphism class.
1718
+
1719
+ This function makes heavy use of the classification of finite subgroups of `PGL(2,K)`.
1720
+
1721
+ INPUT:
1722
+
1723
+ - ``list_of_elements`` -- a finite list of elements of `PGL(2,K)`
1724
+ that we know a priori form a group
1725
+
1726
+ OUTPUT: string; the isomorphism type of the group
1727
+
1728
+ EXAMPLES::
1729
+
1730
+ sage: R.<x> = PolynomialRing(GF(7,'t'))
1731
+ sage: G = [x, 6*x/(x + 1), 6*x + 6, 1/x, (6*x + 6)/x, 6/(x + 1)]
1732
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import which_group
1733
+ sage: which_group(G)
1734
+ 'Dihedral of order 6'
1735
+ """
1736
+ if isinstance(list_of_elements[-1], Matrix):
1737
+ R = PolynomialRing(list_of_elements[-1].base_ring(),'z')
1738
+ z = R.gen(0)
1739
+ G = [(t[0,0]*z+t[0,1])/(t[1,0]*z+t[1,1]) for t in list_of_elements]
1740
+ else:
1741
+ G = list_of_elements
1742
+
1743
+ n = ZZ(len(G))
1744
+
1745
+ # invalid input
1746
+ if n == 0:
1747
+ raise ValueError("group must have at least one element")
1748
+
1749
+ # define ground field and ambient function field
1750
+ rational_function = G[-1]
1751
+
1752
+ if rational_function.parent().is_field():
1753
+ K = rational_function.parent()
1754
+ R = K.ring()
1755
+ else:
1756
+ R = rational_function.parent()
1757
+ K = R.fraction_field()
1758
+
1759
+ z = R.gen(0)
1760
+ p = K.characteristic()
1761
+
1762
+ # factor n = mp^e; set e = 0 and m = n if p = 0 (Sage sets 0^0 = 1)
1763
+ if p > 0:
1764
+ m = n.prime_to_m_part(p)
1765
+ e = ZZ(n/m).exact_log(p)
1766
+ else:
1767
+ m = n
1768
+ e = 0
1769
+
1770
+ # Determine if G is cyclic or dihedral.
1771
+ # This determines the maximal cyclic subgroup and the maximal cyclic
1772
+ # p-regular subgroup. Algorithm terminates if the order of this subgroup agrees with
1773
+ # the order of the group.
1774
+ max_reg_cyclic = [1, z, [z]] # initialize order of cyclic p-regular subgroup and generator
1775
+ discard = [] # list of elements already considered
1776
+
1777
+ for g in G:
1778
+ if g not in discard:
1779
+ H = [g]
1780
+ for i in range(n-1):
1781
+ h = g(H[-1])
1782
+ H.append(h)
1783
+ H = list(set(H))
1784
+ if len(H) == n:
1785
+ return 'Cyclic of order {0}'.format(n)
1786
+ if len(H) > max_reg_cyclic[0] and gcd(len(H), p) != p:
1787
+ max_reg_cyclic = [len(H), g, H]
1788
+ discard = list(set(discard + H)) # adjoin all new elements to discard
1789
+
1790
+ n_reg = max_reg_cyclic[0]
1791
+ # Test for dihedral subgroup. A subgroup of index 2 is always normal, so the
1792
+ # presence of a cyclic subgroup H of index 2 indicates the group is either
1793
+ # H x Z/2Z or dihedral. The former occurs only if H has order 1 or 2, both of
1794
+ # which are dihedral.
1795
+ if 2*n_reg == n:
1796
+ for g in G:
1797
+ if g not in max_reg_cyclic[2]:
1798
+ return 'Dihedral of order {0}'.format(n)
1799
+ # Check the p-irregular cases. There is overlap in these cases when p^e = 2,
1800
+ # which is dihedral and so already dealt with above. By the classification theorem,
1801
+ # these are either p-semi-elementary, PGL(2,q), PSL(2,q), or A_5 when p=3. The latter
1802
+ # case is already covered by the remaining sporadic cases below.
1803
+ if e > 0:
1804
+ if n_reg == m: # p-semi-elementary
1805
+ return '{0}-semi-elementary of order {1}'.format(p, n)
1806
+ if n_reg == m / (p**e - 1) and m == p**(2*e) - 1: # PGL(2)
1807
+ return 'PGL(2,{0})'.format(p**e)
1808
+ if n_reg == m / (p**e - 1) and m == (1/2)*(p**(2*e) - 1): # PSL(2)
1809
+ return 'PSL(2,{0})'.format(p**e)
1810
+
1811
+ # Treat sporadic cases
1812
+ if n == 12:
1813
+ return ['A_4']
1814
+ elif n == 24:
1815
+ return ['S_4']
1816
+ else:
1817
+ return ['A_5']
1818
+
1819
+
1820
+ def conjugating_set_initializer(f, g):
1821
+ r"""
1822
+ Return a conjugation invariant set together with information
1823
+ to reduce the combinatorics of checking all possible conjugations.
1824
+
1825
+ This function constructs the invariant pair (``source``, ``possible_targets``)
1826
+ necessary for the conjugating set algorithm described in [FMV2014]_.
1827
+ Let `f` and `g` be dynamical systems on `\mathbb{P}^n`.
1828
+ An invariant pair is a pair of two sets `U`, `V` such that
1829
+ `|U| = |V|` and for all `\phi \in PGL` such that `f^\phi = g`,
1830
+ `\phi(u) \in V` for all `u \in U`. Invariant pairs can be used
1831
+ to determine all conjugations from `f` to `g`. For details
1832
+ in the `\mathbb{P}^1` case, see [FMV2014]_.
1833
+
1834
+ Additionally, this function keeps track of multipliers to reduce the combinatorics.
1835
+ This information is then passed to ``conjugating_set_helper`` or
1836
+ ``is_conjugate_helper``, which check all possible conjugations determined
1837
+ by the invariant pair.
1838
+
1839
+ Do not call this function directly, instead use ``f.conjugating_set(g)``.
1840
+
1841
+ INPUT:
1842
+
1843
+ - ``f`` -- a rational function of degree at least 2, and the same
1844
+ degree as ``g``
1845
+
1846
+ - ``g`` -- a nonconstant rational function of the same
1847
+ degree as ``f``
1848
+
1849
+ OUTPUT:
1850
+
1851
+ A tuple of the form (``source``, ``possible_targets``).
1852
+
1853
+ - ``source`` -- a conjugation invariant set of `n+2` points of the domain of `f`,
1854
+ of which no `n+1` are linearly dependent. Used to specify a possible conjugation
1855
+ from `f` to `g`.
1856
+
1857
+ - ``possible_targets`` -- list of tuples of the form (``points``, ``repeated``). ``points``
1858
+ is a list of ``points`` which are possible targets for point(s) in ``source``. ``repeated``
1859
+ specifies how many points in ``source`` have points in ``points`` as their possible target.
1860
+
1861
+ EXAMPLES:
1862
+
1863
+ We check that ``source`` has no `n+1` linearly dependent points, and that
1864
+ ``possible_targets`` tracks multiplier information::
1865
+
1866
+ sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
1867
+ sage: f = DynamicalSystem([
1868
+ ....: 8*x^7 - 35*x^4*y^3 - 35*x^4*z^3 - 7*x*y^6 - 140*x*y^3*z^3 - 7*x*z^6,
1869
+ ....: -7*x^6*y - 35*x^3*y^4 - 140*x^3*y*z^3 + 8*y^7 - 35*y^4*z^3 - 7*y*z^6,
1870
+ ....: -7*x^6*z - 140*x^3*y^3*z - 35*x^3*z^4 - 7*y^6*z - 35*y^3*z^4 + 8*z^7])
1871
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import conjugating_set_initializer
1872
+ sage: source, possible_targets = conjugating_set_initializer(f, f)
1873
+ sage: P.is_linearly_independent(source, 3)
1874
+ True
1875
+ sage: f.multiplier(possible_targets[0][0][0], 1) == f.multiplier(source[0], 1)
1876
+ True
1877
+ """
1878
+ n = f.domain().dimension_relative()
1879
+
1880
+ L = Set(f.periodic_points(1))
1881
+ K = Set(g.periodic_points(1))
1882
+ P = f.codomain().ambient_space()
1883
+ if len(L) != len(K): # checks maps have the same number of fixed points
1884
+ return []
1885
+
1886
+ # we store fixed points an multipliers in dictionaries
1887
+ # to avoid recalculating them
1888
+ mult_to_point_L = {}
1889
+ mult_to_point_K = {}
1890
+ point_to_mult_L = {}
1891
+ point_to_mult_K = {}
1892
+
1893
+ # as we will calculate preimages, we differentiate points by their 'level'
1894
+ # which is how many preimages of a fixed point they are, i.e. a fixed point
1895
+ # has level 0, a preimage of a fixed point level 1, etc.
1896
+ level = 0
1897
+
1898
+ # initializing the dictionaries
1899
+ for i in range(len(L)):
1900
+ mult_L = f.multiplier(L[i], 1).charpoly()
1901
+ mult_K = g.multiplier(K[i], 1).charpoly()
1902
+ tup_L = (mult_L, level)
1903
+ tup_K = (mult_K, level)
1904
+ if tup_L not in mult_to_point_L:
1905
+ mult_to_point_L[tup_L] = [L[i]]
1906
+ else:
1907
+ mult_to_point_L[tup_L] += [L[i]]
1908
+ if tup_K not in mult_to_point_K:
1909
+ mult_to_point_K[tup_K] = [K[i]]
1910
+ else:
1911
+ mult_to_point_K[tup_K] += [K[i]]
1912
+ point_to_mult_L[L[i]] = (mult_L, level)
1913
+ point_to_mult_K[K[i]] = (mult_K, level)
1914
+
1915
+ # we keep a dictionary which tracks how often a (multiplier, level) pair
1916
+ # is repeated. As points can only be sent to points with the same (multiplier, level)
1917
+ # pair, the less times a (multiplier, level) pair are repeated the better the
1918
+ # combinatorics
1919
+ repeated_mult_L = {}
1920
+ for mult_L in mult_to_point_L:
1921
+ repeated = len(mult_to_point_L[mult_L])
1922
+ if mult_L not in mult_to_point_K:
1923
+ return []
1924
+ elif len(mult_to_point_K[mult_L]) != repeated:
1925
+ return []
1926
+ if repeated not in repeated_mult_L:
1927
+ repeated_mult_L[repeated] = [mult_to_point_L[mult_L]]
1928
+ else:
1929
+ repeated_mult_L[repeated] += [mult_to_point_L[mult_L]]
1930
+ more = True
1931
+
1932
+ # the n+2 points to be used to specify PGL conjugations
1933
+ source = []
1934
+
1935
+ # a list of tuples of the form ((multiplier, level), repeat) where the
1936
+ # (multiplier, level) pair specifies the possible targets of a point in source and repeat
1937
+ # specifies how many points in source have that (multiplier, level) pair
1938
+ corresponding = []
1939
+
1940
+ # we now greedily look for a set of n+2 points, of which no n+1 are linearly dependent,
1941
+ # and we make sure to add the points with the best combinatorics first.
1942
+ # this check sometimes fails, i.e. sometimes there is a subset with the
1943
+ # desired property which is not found. however, this check is very fast and if it
1944
+ # does find a subset, then the subset will most likely minimize the combinatorics
1945
+ # of checking conjugations
1946
+ tup = greedy_independence_check(P, repeated_mult_L, point_to_mult_L)
1947
+ if tup is not None:
1948
+ more = False
1949
+ source, corresponding = tup
1950
+
1951
+ else:
1952
+ # loop_repeated_mult stores the points to find preimages of
1953
+ loop_repeated_mult = deepcopy(repeated_mult_L)
1954
+ # next_repeated_mult stores the points to find preimages of on the next loop
1955
+ next_repeated_mult = {}
1956
+ found_no_more = True
1957
+
1958
+ # if we don't find enough points, we go to preimages
1959
+ while more:
1960
+ level += 1
1961
+ # we calculate preimages, starting with preimages with the best
1962
+ # expected combinatorics
1963
+ for r in sorted(loop_repeated_mult.keys()):
1964
+ for point_lst_L in loop_repeated_mult[r]:
1965
+ old_tup_L = point_to_mult_L[point_lst_L[0]]
1966
+ point_lst_K = mult_to_point_K[old_tup_L]
1967
+ mult_L = old_tup_L[0]
1968
+ Tl = []
1969
+ Tk = []
1970
+ # first we calculate preimages
1971
+ for pnt in point_lst_L:
1972
+ for preimage in f.rational_preimages(pnt):
1973
+ if preimage != pnt:
1974
+ Tl.append(preimage)
1975
+ for pnt in point_lst_K:
1976
+ for preimage in g.rational_preimages(pnt):
1977
+ if preimage != pnt:
1978
+ Tk.append(preimage)
1979
+ if len(Tl) != len(Tk):
1980
+ return []
1981
+ if Tl:
1982
+ found_no_more = False
1983
+ new_tup_L = (mult_L, level)
1984
+ new_tup_K = (mult_L, level)
1985
+ # we update dictionaries with the new preimages
1986
+ mult_to_point_L[new_tup_L] = Tl
1987
+ mult_to_point_K[new_tup_K] = Tk
1988
+ for i in range(len(Tl)):
1989
+ point_to_mult_L[Tl[i]] = new_tup_L
1990
+ point_to_mult_K[Tk[i]] = new_tup_K
1991
+ repeated = len(Tl)
1992
+ if repeated not in repeated_mult_L:
1993
+ repeated_mult_L[repeated] = [Tl]
1994
+ else:
1995
+ repeated_mult_L[repeated] += [Tl]
1996
+ if repeated not in next_repeated_mult:
1997
+ next_repeated_mult[repeated] = [Tl]
1998
+ else:
1999
+ next_repeated_mult[repeated] += [Tl]
2000
+ # we again do a greedy check for a subset of n+2 points, of which no n+1
2001
+ # are linearly dependent
2002
+ tup = greedy_independence_check(P, repeated_mult_L, point_to_mult_L)
2003
+ if tup is not None:
2004
+ more = False
2005
+ source, corresponding = tup
2006
+ if not more:
2007
+ break
2008
+ if not more:
2009
+ break
2010
+
2011
+ # if no more preimages can be found, we must check all subsets
2012
+ # of size n+2 to see if there is a subset in which no n+1 points
2013
+ # are linearly dependent
2014
+ if found_no_more:
2015
+ # we construct a list of all the possible sources points
2016
+ all_points = []
2017
+ # we order the list by how many repeated multipliers each point has
2018
+ # in an attempt to reduce the combinatorics of checking conjugations
2019
+ for r in sorted(repeated_mult_L.keys()):
2020
+ for point_lst in repeated_mult_L[r]:
2021
+ all_points += point_lst
2022
+ # this loop is quite long, so we break after finding the
2023
+ # first subset with the desired property. There is,
2024
+ # however, no guarantee that the subset we found minimizes
2025
+ # the combinatorics when checking conjugations
2026
+ for subset in Subsets(range(len(all_points)), n+2):
2027
+ source = []
2028
+ for i in subset:
2029
+ source.append(all_points[i])
2030
+ if P.is_linearly_independent(source, n+1):
2031
+ more = False
2032
+ corresponding = []
2033
+ mult_only = []
2034
+ for i in subset:
2035
+ mult = point_to_mult_L[all_points[i]]
2036
+ if mult in mult_only:
2037
+ corresponding[mult_only.index(mult)][1] += 1
2038
+ else:
2039
+ corresponding.append([mult, 1])
2040
+ mult_only.append(mult)
2041
+ break
2042
+ # if we iterated over all subsets of size n+2, and did not find one
2043
+ # in which all subsets of size n+1 are linearly independent,
2044
+ # then we fail as we cannot specify conjugations
2045
+ if more:
2046
+ raise ValueError('no more rational preimages; try extending the base field and trying again')
2047
+
2048
+ # if we need to add more preimages, we update loop dictionaries
2049
+ if more:
2050
+ loop_repeated_mult = deepcopy(next_repeated_mult)
2051
+ next_repeated_mult = {}
2052
+ found_no_more = True
2053
+
2054
+ # we build a list of iterators in order to loop over the product of those iterators
2055
+ possible_targets = []
2056
+ for tup in corresponding:
2057
+ possible_targets.append([mult_to_point_K[tup[0]], tup[1]])
2058
+ return source, possible_targets
2059
+
2060
+
2061
+ def greedy_independence_check(P, repeated_mult, point_to_mult):
2062
+ r"""
2063
+ Return an invariant pair together with information
2064
+ to reduce the combinatorics of checking all possible conjugations.
2065
+
2066
+ Let `f` and `g` be dynamical systems on `\mathbb{P}^n`.
2067
+ An invariant pair is a pair of two sets `U`, `V` such that
2068
+ `|U| = |V|` and for all `\phi \in PGL` such that `f^\phi = g`,
2069
+ `\phi(u) \in V` for all `u \in U`. Invariant pairs can be used
2070
+ to determine all conjugations from `f` to `g`. For details
2071
+ in the `\mathbb{P}^1` case, see [FMV2014]_.
2072
+
2073
+ This function may sometimes fail to find the invariant pair
2074
+ set even though one exists. It is useful, however, as it is fast
2075
+ and returns a set which usually minimizes the combinatorics of
2076
+ checking all conjugations.
2077
+
2078
+ INPUT:
2079
+
2080
+ - ``P`` -- a projective space
2081
+
2082
+ - ``repeated_mult`` -- dictionary of integers to lists of points of
2083
+ the projective space ``P``. The list of points should be conjugation
2084
+ invariant. The keys are considered as weights, and this function attempts
2085
+ to minimize the total weight
2086
+
2087
+ - ``point_to_mult`` -- dictionary of points of ``P`` to tuples of the form
2088
+ (multiplier, level), where multiplier is the characteristic polynomial
2089
+ of the multiplier of the point, and level is the number of preimages
2090
+ taken to find the point
2091
+
2092
+ OUTPUT:
2093
+
2094
+ If no set of `n+2` points of which all subsets of size `n+1` are linearly
2095
+ independent can be found, then ``None`` is returned.
2096
+
2097
+ Otherwise, a tuple of the form (``source``, ``corresponding``) is returned.
2098
+
2099
+ - ``source`` -- the set `U` of the conjugation invariant pair. A set of `n+2` points
2100
+ of the domain of `f`, of which no `n+1` are linearly dependent
2101
+
2102
+ - ``corresponding`` -- list of tuples of the form ((multiplier, level), repeat) where the
2103
+ (multiplier, level) pair is the multiplier of a point in ``source`` and repeat
2104
+ specifies how many points in source have that (multiplier, level) pair. This
2105
+ information specifies the set `V` of the invariant pair.
2106
+
2107
+ EXAMPLES::
2108
+
2109
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import greedy_independence_check
2110
+ sage: P.<x,y> = ProjectiveSpace(QQ, 1)
2111
+ sage: repeated_mult = {2: [[P((0, 1)), P((1, 0))]], 1: [[P((1, 1))]]}
2112
+ sage: point_to_mult = {P((0, 1)): (x, 0), P((1, 0)): (x, 0), P((1, 1)): (x - 2, 0)}
2113
+ sage: greedy_independence_check(P, repeated_mult, point_to_mult)
2114
+ ([(1 : 1), (0 : 1), (1 : 0)], [[(x - 2, 0), 1], [(x, 0), 2]])
2115
+ """
2116
+ n = P.dimension_relative()
2117
+ source = []
2118
+ corresponding = []
2119
+ for r in sorted(repeated_mult.keys()):
2120
+ for point_lst in repeated_mult[r]:
2121
+ for point in point_lst:
2122
+ if len(source) == n+1:
2123
+ independent = P.is_linearly_independent(source + [point], n+1)
2124
+ else:
2125
+ independent = P.is_linearly_independent(source + [point])
2126
+ if independent:
2127
+ source.append(point)
2128
+ mult = point_to_mult[point]
2129
+ # if another point with this multiplier and level pair is in S
2130
+ # then the multiplier level pair will be the last element of corresponding
2131
+ if len(corresponding) != 0:
2132
+ if corresponding[-1][0] == mult:
2133
+ corresponding[-1][1] += 1
2134
+ else:
2135
+ corresponding.append([mult, 1])
2136
+ else:
2137
+ corresponding.append([mult, 1])
2138
+ if len(source) == n+2:
2139
+ return source, corresponding
2140
+
2141
+
2142
+ def conjugating_set_helper(f, g, num_cpus, source, possible_targets):
2143
+ r"""
2144
+ Return the set of elements in PGL over the base ring
2145
+ that conjugates ``f`` to ``g``.
2146
+
2147
+ This function takes as input the invariant pair
2148
+ and multiplier data from ``conjugating_set_initializer``.
2149
+
2150
+ Do not call this function directly, instead use ``f.conjugate_set(g)``.
2151
+
2152
+ INPUT:
2153
+
2154
+ - ``f`` -- a rational function of degree at least 2, and the same
2155
+ degree as ``g``
2156
+
2157
+ - ``g`` -- a rational function of the same degree as ``f``
2158
+
2159
+ - ``num_cpus`` -- the number of threads to run in parallel
2160
+
2161
+ - ``source`` -- list of `n+2` conjugation invariant points, of which
2162
+ no `n+1` are linearly dependent
2163
+
2164
+ - ``possible_targets`` -- list of tuples of the form (``points``, ``repeated``). ``points``
2165
+ is a list of ``points`` which are possible targets for point(s) in ``source``. ``repeated``
2166
+ specifies how many points in ``source`` have points in ``points`` as their possible target.
2167
+
2168
+ OUTPUT: list of elements of PGL which conjugate ``f`` to ``g``
2169
+
2170
+ EXAMPLES::
2171
+
2172
+ sage: P.<x,y> = ProjectiveSpace(QQ, 1)
2173
+ sage: f = DynamicalSystem([x^2, y^2])
2174
+ sage: source = [P((1, 1)), P((0, 1)), P((1, 0))]
2175
+ sage: possible_targets = [[[P((1, 1))], 1], [[P((0, 1)), P((1, 0))], 2]]
2176
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import conjugating_set_helper
2177
+ sage: sorted(conjugating_set_helper(f, f, 2, source, possible_targets))
2178
+ [
2179
+ [0 1] [1 0]
2180
+ [1 0], [0 1]
2181
+ ]
2182
+ """
2183
+ Conj = []
2184
+ P = f.domain().ambient_space()
2185
+ n = f.domain().dimension_relative()
2186
+
2187
+ subset_iterators = []
2188
+
2189
+ for lst in possible_targets:
2190
+ subset_iterators.append(Subsets(range(len(lst[0])), lst[1]))
2191
+
2192
+ # helper function for parallelization
2193
+ # given a list of tuples which specify indices of possible target
2194
+ # points in possible_targets, check all arrangements of those
2195
+ # possible target points and if any of them define a conjugation
2196
+ # which sends f to g, return those conjugations as a list
2197
+ def find_conjugations_subset(tuples):
2198
+ conj = []
2199
+ for tup in tuples:
2200
+ target_set = []
2201
+ for i in range(len(tup)):
2202
+ for j in tup[i]:
2203
+ target_set.append(possible_targets[i][0][j])
2204
+
2205
+ # if there is a subset of n+1 points which is linearly dependent,
2206
+ # we don't need to check any of these arrangements
2207
+ if P.is_linearly_independent(target_set, n+1):
2208
+ subset_arrangements = []
2209
+ for subset in tup:
2210
+ subset_arrangements.append(Arrangements(subset, len(subset)))
2211
+ for tup in product(*subset_arrangements):
2212
+ current_target = []
2213
+ for i in range(len(tup)):
2214
+ for j in tup[i]:
2215
+ current_target.append(possible_targets[i][0][j])
2216
+ phi = P.point_transformation_matrix(current_target, source)
2217
+ if f.conjugate(phi) == g:
2218
+ conj.append(phi)
2219
+ return conj
2220
+
2221
+ # helper function for parallelization
2222
+ # given a list of tuples which specify indices of possible target
2223
+ # points in possible_targets, check all possible target points and
2224
+ # if any of them define a conjugation which sends f to g, return
2225
+ # those conjugations as a list
2226
+ def find_conjugations_arrangement(tuples):
2227
+ conj = []
2228
+ for tup in tuples:
2229
+ current_target = []
2230
+ for i in range(len(tup)):
2231
+ for j in tup[i]:
2232
+ current_target.append(possible_targets[i][0][j])
2233
+ phi = P.point_transformation_matrix(current_target, source)
2234
+ if f.conjugate(phi) == g:
2235
+ conj.append(phi)
2236
+ return conj
2237
+
2238
+ if num_cpus > 1:
2239
+ all_subsets = list(product(*subset_iterators))
2240
+ parallel_data = []
2241
+
2242
+ # if there are enough subsets, we can divide up the work based on subsets
2243
+ # and check linear independence in parallel
2244
+ if len(all_subsets) > num_cpus:
2245
+ for i in range(num_cpus):
2246
+ start = (len(all_subsets) * i) // num_cpus
2247
+ end = (len(all_subsets) * (i+1)) // num_cpus
2248
+ tuples = all_subsets[start:end]
2249
+ parallel_data.append(([tuples], {}))
2250
+
2251
+ X = p_iter_fork(num_cpus)
2252
+ for ret in X(find_conjugations_subset, parallel_data):
2253
+ if ret[1]:
2254
+ Conj += ret[1]
2255
+ # otherwise, we need to first check linear independence of the subsets
2256
+ # and then build a big list of all the arrangements to split among
2257
+ # the threads
2258
+ else:
2259
+ good_targets = []
2260
+ for tup in product(*subset_iterators):
2261
+ target_set = []
2262
+ for i in range(len(tup)):
2263
+ for j in tup[i]:
2264
+ target_set.append(possible_targets[i][0][j])
2265
+ if P.is_linearly_independent(target_set, n+1):
2266
+ good_targets.append(tup)
2267
+ all_arrangements = []
2268
+ for tup in good_targets:
2269
+ subset_arrangements = []
2270
+ for subset in tup:
2271
+ subset_arrangements.append(Arrangements(subset, len(subset)))
2272
+ all_arrangements += list(product(*subset_arrangements))
2273
+ parallel_data = []
2274
+ for i in range(num_cpus):
2275
+ start = (len(all_arrangements) * i) // num_cpus
2276
+ end = (len(all_arrangements) * (i+1)) // num_cpus
2277
+ tuples = all_arrangements[start:end]
2278
+ parallel_data.append(([tuples], {}))
2279
+ X = p_iter_fork(num_cpus)
2280
+ for ret in X(find_conjugations_arrangement, parallel_data):
2281
+ if ret[1]:
2282
+ Conj += ret[1]
2283
+ else:
2284
+ Conj = find_conjugations_subset(product(*subset_iterators))
2285
+ return Conj
2286
+
2287
+
2288
+ def is_conjugate_helper(f, g, num_cpus, source, possible_targets):
2289
+ r"""
2290
+ Return if ``f`` is conjugate to ``g``.
2291
+
2292
+ This function takes as input the invariant pair
2293
+ and multiplier data from ``conjugating_set_initializer``.
2294
+
2295
+ Do not call this function directly, instead use ``f.is_conjugate(g)``.
2296
+
2297
+ INPUT:
2298
+
2299
+ - ``f`` -- a rational function of degree at least 2, and the same
2300
+ degree as ``g``
2301
+
2302
+ - ``g`` -- a rational function of the same degree as ``f``
2303
+
2304
+ - ``num_cpus`` -- the number of threads to run in parallel
2305
+
2306
+ - ``source`` -- list of `n+2` conjugation invariant points, of which
2307
+ no `n+1` are linearly dependent
2308
+
2309
+ - ``possible_targets`` -- list of tuples of the form (``points``, ``repeated``). ``points``
2310
+ is a list of ``points`` which are possible targets for point(s) in ``source``. ``repeated``
2311
+ specifies how many points in ``source`` have points in ``points`` as their possible target.
2312
+
2313
+ OUTPUT: ``True`` if ``f`` is conjugate to ``g``, ``False`` otherwise
2314
+
2315
+ EXAMPLES::
2316
+
2317
+ sage: P.<x,y> = ProjectiveSpace(QQ, 1)
2318
+ sage: f = DynamicalSystem([x^2, y^2])
2319
+ sage: source = [P((1, 1)), P((0, 1)), P((1, 0))]
2320
+ sage: possible_targets = [[[P((1, 1))], 1], [[P((0, 1)), P((1, 0))], 2]]
2321
+ sage: from sage.dynamics.arithmetic_dynamics.endPN_automorphism_group import is_conjugate_helper
2322
+ sage: is_conjugate_helper(f, f, 2, source, possible_targets)
2323
+ True
2324
+ """
2325
+ is_conj = False
2326
+ P = f.domain().ambient_space()
2327
+ n = f.domain().dimension_relative()
2328
+
2329
+ subset_iterators = []
2330
+
2331
+ for lst in possible_targets:
2332
+ subset_iterators.append(Subsets(range(len(lst[0])), lst[1]))
2333
+
2334
+ # helper function for parallelization
2335
+ # given a list of tuples which specify indices of possible target
2336
+ # points in possible_targets, check all arrangements of those
2337
+ # possible target points and if any of them define a conjugation
2338
+ # which sends f to g, return True
2339
+ def find_conjugations_subset(tuples):
2340
+ for tup in tuples:
2341
+ target_set = []
2342
+ for i in range(len(tup)):
2343
+ for j in tup[i]:
2344
+ target_set.append(possible_targets[i][0][j])
2345
+
2346
+ # if there is a subset of n+1 points which is linearly dependent,
2347
+ # we don't need to check any of these arrangements
2348
+ if P.is_linearly_independent(target_set, n+1):
2349
+ subset_arrangements = []
2350
+ for subset in tup:
2351
+ subset_arrangements.append(Arrangements(subset, len(subset)))
2352
+ for tup in product(*subset_arrangements):
2353
+ current_target = []
2354
+ for i in range(len(tup)):
2355
+ for j in tup[i]:
2356
+ current_target.append(possible_targets[i][0][j])
2357
+ phi = P.point_transformation_matrix(current_target, source)
2358
+ if f.conjugate(phi) == g:
2359
+ return True
2360
+ return False
2361
+
2362
+ # helper function for parallelization
2363
+ # given a list of tuples which specify indices of possible target points
2364
+ # in possible_targets, check all possible target points
2365
+ # and if any of them define a conjugation which sends f to g, return True
2366
+ def find_conjugations_arrangement(tuples):
2367
+ for tup in tuples:
2368
+ current_target = []
2369
+ for i in range(len(tup)):
2370
+ for j in tup[i]:
2371
+ current_target.append(possible_targets[i][0][j])
2372
+ phi = P.point_transformation_matrix(current_target, source)
2373
+ if f.conjugate(phi) == g:
2374
+ return True
2375
+ return False
2376
+
2377
+ if num_cpus > 1:
2378
+ all_subsets = list(product(*subset_iterators))
2379
+ parallel_data = []
2380
+
2381
+ # if there are enough subsets, we can divide up the work based on subsets
2382
+ # and check linear independence in parallel
2383
+ if len(all_subsets) > num_cpus:
2384
+ for i in range(num_cpus):
2385
+ start = (len(all_subsets) * i) // num_cpus
2386
+ end = (len(all_subsets) * (i+1)) // num_cpus
2387
+ tuples = all_subsets[start:end]
2388
+ parallel_data.append(([tuples], {}))
2389
+
2390
+ X = p_iter_fork(num_cpus)
2391
+ for ret in X(find_conjugations_subset, parallel_data):
2392
+ if ret[1]:
2393
+ is_conj = True
2394
+ break
2395
+ # otherwise, we need to first check linear independence of the subsets
2396
+ # and then build a big list of all the arrangements to split among
2397
+ # the threads
2398
+ else:
2399
+ good_targets = []
2400
+ for tup in product(*subset_iterators):
2401
+ target_set = []
2402
+ for i in range(len(tup)):
2403
+ for j in tup[i]:
2404
+ target_set.append(possible_targets[i][0][j])
2405
+ if P.is_linearly_independent(target_set, n+1):
2406
+ good_targets.append(tup)
2407
+ all_arrangements = []
2408
+ for tup in good_targets:
2409
+ subset_arrangements = []
2410
+ for subset in tup:
2411
+ subset_arrangements.append(Arrangements(subset, len(subset)))
2412
+ all_arrangements += list(product(*subset_arrangements))
2413
+ parallel_data = []
2414
+ for i in range(num_cpus):
2415
+ start = (len(all_arrangements) * i) // num_cpus
2416
+ end = (len(all_arrangements) * (i+1)) // num_cpus
2417
+ tuples = all_arrangements[start:end]
2418
+ parallel_data.append(([tuples], {}))
2419
+ X = p_iter_fork(num_cpus)
2420
+ for ret in X(find_conjugations_arrangement, parallel_data):
2421
+ if ret[1]:
2422
+ is_conj = True
2423
+ break
2424
+ else:
2425
+ is_conj = find_conjugations_subset(product(*subset_iterators))
2426
+ return is_conj