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.
- passagemath_schemes/.dylibs/libflint.22.0.dylib +0 -0
- passagemath_schemes/.dylibs/libgmp.10.dylib +0 -0
- passagemath_schemes/.dylibs/libgmpxx.4.dylib +0 -0
- passagemath_schemes/.dylibs/libmpfr.6.dylib +0 -0
- passagemath_schemes/__init__.py +3 -0
- passagemath_schemes-10.6.47.dist-info/METADATA +204 -0
- passagemath_schemes-10.6.47.dist-info/METADATA.bak +205 -0
- passagemath_schemes-10.6.47.dist-info/RECORD +311 -0
- passagemath_schemes-10.6.47.dist-info/WHEEL +6 -0
- passagemath_schemes-10.6.47.dist-info/top_level.txt +3 -0
- sage/all__sagemath_schemes.py +23 -0
- sage/databases/all__sagemath_schemes.py +7 -0
- sage/databases/cremona.py +1723 -0
- sage/dynamics/all__sagemath_schemes.py +2 -0
- sage/dynamics/arithmetic_dynamics/affine_ds.py +1083 -0
- sage/dynamics/arithmetic_dynamics/all.py +14 -0
- sage/dynamics/arithmetic_dynamics/berkovich_ds.py +1101 -0
- sage/dynamics/arithmetic_dynamics/dynamical_semigroup.py +1543 -0
- sage/dynamics/arithmetic_dynamics/endPN_automorphism_group.py +2426 -0
- sage/dynamics/arithmetic_dynamics/endPN_minimal_model.py +1169 -0
- sage/dynamics/arithmetic_dynamics/generic_ds.py +663 -0
- sage/dynamics/arithmetic_dynamics/product_projective_ds.py +339 -0
- sage/dynamics/arithmetic_dynamics/projective_ds.py +9558 -0
- sage/dynamics/arithmetic_dynamics/projective_ds_helper.cpython-312-darwin.so +0 -0
- sage/dynamics/arithmetic_dynamics/projective_ds_helper.pyx +301 -0
- sage/dynamics/arithmetic_dynamics/wehlerK3.py +2576 -0
- sage/lfunctions/all.py +18 -0
- sage/lfunctions/dokchitser.py +745 -0
- sage/lfunctions/pari.py +818 -0
- sage/lfunctions/zero_sums.cpython-312-darwin.so +0 -0
- sage/lfunctions/zero_sums.pyx +1847 -0
- sage/modular/abvar/abvar.py +5135 -0
- sage/modular/abvar/abvar_ambient_jacobian.py +413 -0
- sage/modular/abvar/abvar_newform.py +244 -0
- sage/modular/abvar/all.py +8 -0
- sage/modular/abvar/constructor.py +186 -0
- sage/modular/abvar/cuspidal_subgroup.py +371 -0
- sage/modular/abvar/finite_subgroup.py +896 -0
- sage/modular/abvar/homology.py +720 -0
- sage/modular/abvar/homspace.py +998 -0
- sage/modular/abvar/lseries.py +415 -0
- sage/modular/abvar/morphism.py +935 -0
- sage/modular/abvar/torsion_point.py +274 -0
- sage/modular/abvar/torsion_subgroup.py +740 -0
- sage/modular/all.py +43 -0
- sage/modular/arithgroup/all.py +20 -0
- sage/modular/arithgroup/arithgroup_element.cpython-312-darwin.so +0 -0
- sage/modular/arithgroup/arithgroup_element.pyx +474 -0
- sage/modular/arithgroup/arithgroup_generic.py +1402 -0
- sage/modular/arithgroup/arithgroup_perm.py +2692 -0
- sage/modular/arithgroup/congroup.cpython-312-darwin.so +0 -0
- sage/modular/arithgroup/congroup.pyx +334 -0
- sage/modular/arithgroup/congroup_gamma.py +363 -0
- sage/modular/arithgroup/congroup_gamma0.py +692 -0
- sage/modular/arithgroup/congroup_gamma1.py +653 -0
- sage/modular/arithgroup/congroup_gammaH.py +1469 -0
- sage/modular/arithgroup/congroup_generic.py +628 -0
- sage/modular/arithgroup/congroup_sl2z.py +267 -0
- sage/modular/arithgroup/farey_symbol.cpython-312-darwin.so +0 -0
- sage/modular/arithgroup/farey_symbol.pyx +1066 -0
- sage/modular/arithgroup/tests.py +418 -0
- sage/modular/btquotients/all.py +4 -0
- sage/modular/btquotients/btquotient.py +3753 -0
- sage/modular/btquotients/pautomorphicform.py +2570 -0
- sage/modular/buzzard.py +100 -0
- sage/modular/congroup.py +29 -0
- sage/modular/congroup_element.py +13 -0
- sage/modular/cusps.py +1109 -0
- sage/modular/cusps_nf.py +1270 -0
- sage/modular/dims.py +569 -0
- sage/modular/dirichlet.py +3310 -0
- sage/modular/drinfeld_modform/all.py +2 -0
- sage/modular/drinfeld_modform/element.py +446 -0
- sage/modular/drinfeld_modform/ring.py +773 -0
- sage/modular/drinfeld_modform/tutorial.py +236 -0
- sage/modular/etaproducts.py +1065 -0
- sage/modular/hecke/algebra.py +746 -0
- sage/modular/hecke/all.py +20 -0
- sage/modular/hecke/ambient_module.py +1019 -0
- sage/modular/hecke/degenmap.py +119 -0
- sage/modular/hecke/element.py +325 -0
- sage/modular/hecke/hecke_operator.py +780 -0
- sage/modular/hecke/homspace.py +206 -0
- sage/modular/hecke/module.py +1767 -0
- sage/modular/hecke/morphism.py +174 -0
- sage/modular/hecke/submodule.py +989 -0
- sage/modular/hypergeometric_misc.cpython-312-darwin.so +0 -0
- sage/modular/hypergeometric_misc.pxd +4 -0
- sage/modular/hypergeometric_misc.pyx +166 -0
- sage/modular/hypergeometric_motive.py +2017 -0
- sage/modular/local_comp/all.py +2 -0
- sage/modular/local_comp/liftings.py +292 -0
- sage/modular/local_comp/local_comp.py +1071 -0
- sage/modular/local_comp/smoothchar.py +1825 -0
- sage/modular/local_comp/type_space.py +748 -0
- sage/modular/modform/all.py +30 -0
- sage/modular/modform/ambient.py +815 -0
- sage/modular/modform/ambient_R.py +177 -0
- sage/modular/modform/ambient_eps.py +306 -0
- sage/modular/modform/ambient_g0.py +124 -0
- sage/modular/modform/ambient_g1.py +204 -0
- sage/modular/modform/constructor.py +545 -0
- sage/modular/modform/cuspidal_submodule.py +708 -0
- sage/modular/modform/defaults.py +14 -0
- sage/modular/modform/eis_series.py +505 -0
- sage/modular/modform/eisenstein_submodule.py +663 -0
- sage/modular/modform/element.py +4131 -0
- sage/modular/modform/find_generators.py +59 -0
- sage/modular/modform/half_integral.py +154 -0
- sage/modular/modform/hecke_operator_on_qexp.py +247 -0
- sage/modular/modform/j_invariant.py +47 -0
- sage/modular/modform/l_series_gross_zagier.py +133 -0
- sage/modular/modform/l_series_gross_zagier_coeffs.cpython-312-darwin.so +0 -0
- sage/modular/modform/l_series_gross_zagier_coeffs.pyx +177 -0
- sage/modular/modform/notes.py +45 -0
- sage/modular/modform/numerical.py +514 -0
- sage/modular/modform/periods.py +14 -0
- sage/modular/modform/ring.py +1257 -0
- sage/modular/modform/space.py +1860 -0
- sage/modular/modform/submodule.py +118 -0
- sage/modular/modform/tests.py +64 -0
- sage/modular/modform/theta.py +110 -0
- sage/modular/modform/vm_basis.py +381 -0
- sage/modular/modform/weight1.py +220 -0
- sage/modular/modform_hecketriangle/abstract_ring.py +1932 -0
- sage/modular/modform_hecketriangle/abstract_space.py +2528 -0
- sage/modular/modform_hecketriangle/all.py +30 -0
- sage/modular/modform_hecketriangle/analytic_type.py +590 -0
- sage/modular/modform_hecketriangle/constructor.py +416 -0
- sage/modular/modform_hecketriangle/element.py +351 -0
- sage/modular/modform_hecketriangle/functors.py +752 -0
- sage/modular/modform_hecketriangle/graded_ring.py +541 -0
- sage/modular/modform_hecketriangle/graded_ring_element.py +2225 -0
- sage/modular/modform_hecketriangle/hecke_triangle_group_element.py +3352 -0
- sage/modular/modform_hecketriangle/hecke_triangle_groups.py +1432 -0
- sage/modular/modform_hecketriangle/readme.py +1214 -0
- sage/modular/modform_hecketriangle/series_constructor.py +580 -0
- sage/modular/modform_hecketriangle/space.py +1037 -0
- sage/modular/modform_hecketriangle/subspace.py +423 -0
- sage/modular/modsym/all.py +17 -0
- sage/modular/modsym/ambient.py +3846 -0
- sage/modular/modsym/boundary.py +1420 -0
- sage/modular/modsym/element.py +336 -0
- sage/modular/modsym/g1list.py +178 -0
- sage/modular/modsym/ghlist.py +182 -0
- sage/modular/modsym/hecke_operator.py +73 -0
- sage/modular/modsym/manin_symbol.cpython-312-darwin.so +0 -0
- sage/modular/modsym/manin_symbol.pxd +5 -0
- sage/modular/modsym/manin_symbol.pyx +497 -0
- sage/modular/modsym/manin_symbol_list.py +1295 -0
- sage/modular/modsym/modsym.py +400 -0
- sage/modular/modsym/modular_symbols.py +384 -0
- sage/modular/modsym/p1list_nf.py +1241 -0
- sage/modular/modsym/relation_matrix.py +591 -0
- sage/modular/modsym/relation_matrix_pyx.cpython-312-darwin.so +0 -0
- sage/modular/modsym/relation_matrix_pyx.pyx +108 -0
- sage/modular/modsym/space.py +2468 -0
- sage/modular/modsym/subspace.py +455 -0
- sage/modular/modsym/tests.py +375 -0
- sage/modular/multiple_zeta.py +2632 -0
- sage/modular/multiple_zeta_F_algebra.py +786 -0
- sage/modular/overconvergent/all.py +6 -0
- sage/modular/overconvergent/genus0.py +1878 -0
- sage/modular/overconvergent/hecke_series.py +1187 -0
- sage/modular/overconvergent/weightspace.py +778 -0
- sage/modular/pollack_stevens/all.py +4 -0
- sage/modular/pollack_stevens/distributions.py +874 -0
- sage/modular/pollack_stevens/fund_domain.py +1572 -0
- sage/modular/pollack_stevens/manin_map.py +859 -0
- sage/modular/pollack_stevens/modsym.py +1593 -0
- sage/modular/pollack_stevens/padic_lseries.py +417 -0
- sage/modular/pollack_stevens/sigma0.py +534 -0
- sage/modular/pollack_stevens/space.py +1076 -0
- sage/modular/quasimodform/all.py +3 -0
- sage/modular/quasimodform/element.py +845 -0
- sage/modular/quasimodform/ring.py +828 -0
- sage/modular/quatalg/all.py +3 -0
- sage/modular/quatalg/brandt.py +1642 -0
- sage/modular/ssmod/all.py +8 -0
- sage/modular/ssmod/ssmod.py +827 -0
- sage/rings/all__sagemath_schemes.py +1 -0
- sage/rings/polynomial/all__sagemath_schemes.py +1 -0
- sage/rings/polynomial/binary_form_reduce.py +585 -0
- sage/schemes/all.py +41 -0
- sage/schemes/berkovich/all.py +6 -0
- sage/schemes/berkovich/berkovich_cp_element.py +2582 -0
- sage/schemes/berkovich/berkovich_space.py +748 -0
- sage/schemes/curves/affine_curve.py +2928 -0
- sage/schemes/curves/all.py +33 -0
- sage/schemes/curves/closed_point.py +434 -0
- sage/schemes/curves/constructor.py +381 -0
- sage/schemes/curves/curve.py +542 -0
- sage/schemes/curves/plane_curve_arrangement.py +1283 -0
- sage/schemes/curves/point.py +463 -0
- sage/schemes/curves/projective_curve.py +3026 -0
- sage/schemes/curves/zariski_vankampen.py +1932 -0
- sage/schemes/cyclic_covers/all.py +2 -0
- sage/schemes/cyclic_covers/charpoly_frobenius.py +320 -0
- sage/schemes/cyclic_covers/constructor.py +137 -0
- sage/schemes/cyclic_covers/cycliccover_finite_field.py +1309 -0
- sage/schemes/cyclic_covers/cycliccover_generic.py +310 -0
- sage/schemes/elliptic_curves/BSD.py +1036 -0
- sage/schemes/elliptic_curves/Qcurves.py +592 -0
- sage/schemes/elliptic_curves/addition_formulas_ring.py +94 -0
- sage/schemes/elliptic_curves/all.py +49 -0
- sage/schemes/elliptic_curves/cardinality.py +609 -0
- sage/schemes/elliptic_curves/cm.py +1102 -0
- sage/schemes/elliptic_curves/constructor.py +1552 -0
- sage/schemes/elliptic_curves/ec_database.py +175 -0
- sage/schemes/elliptic_curves/ell_curve_isogeny.py +3972 -0
- sage/schemes/elliptic_curves/ell_egros.py +459 -0
- sage/schemes/elliptic_curves/ell_field.py +2836 -0
- sage/schemes/elliptic_curves/ell_finite_field.py +3359 -0
- sage/schemes/elliptic_curves/ell_generic.py +3760 -0
- sage/schemes/elliptic_curves/ell_local_data.py +1207 -0
- sage/schemes/elliptic_curves/ell_modular_symbols.py +775 -0
- sage/schemes/elliptic_curves/ell_number_field.py +4220 -0
- sage/schemes/elliptic_curves/ell_padic_field.py +107 -0
- sage/schemes/elliptic_curves/ell_point.py +4787 -0
- sage/schemes/elliptic_curves/ell_rational_field.py +7368 -0
- sage/schemes/elliptic_curves/ell_tate_curve.py +671 -0
- sage/schemes/elliptic_curves/ell_torsion.py +436 -0
- sage/schemes/elliptic_curves/ell_wp.py +352 -0
- sage/schemes/elliptic_curves/formal_group.py +760 -0
- sage/schemes/elliptic_curves/gal_reps.py +1459 -0
- sage/schemes/elliptic_curves/gal_reps_number_field.py +1669 -0
- sage/schemes/elliptic_curves/gp_simon.py +152 -0
- sage/schemes/elliptic_curves/heegner.py +7335 -0
- sage/schemes/elliptic_curves/height.py +2109 -0
- sage/schemes/elliptic_curves/hom.py +1406 -0
- sage/schemes/elliptic_curves/hom_composite.py +934 -0
- sage/schemes/elliptic_curves/hom_frobenius.py +522 -0
- sage/schemes/elliptic_curves/hom_scalar.py +531 -0
- sage/schemes/elliptic_curves/hom_sum.py +682 -0
- sage/schemes/elliptic_curves/hom_velusqrt.py +1290 -0
- sage/schemes/elliptic_curves/homset.py +271 -0
- sage/schemes/elliptic_curves/isogeny_class.py +1521 -0
- sage/schemes/elliptic_curves/isogeny_small_degree.py +2797 -0
- sage/schemes/elliptic_curves/jacobian.py +237 -0
- sage/schemes/elliptic_curves/kodaira_symbol.py +344 -0
- sage/schemes/elliptic_curves/kraus.py +1014 -0
- sage/schemes/elliptic_curves/lseries_ell.py +943 -0
- sage/schemes/elliptic_curves/mod5family.py +105 -0
- sage/schemes/elliptic_curves/mod_poly.py +197 -0
- sage/schemes/elliptic_curves/mod_sym_num.cpython-312-darwin.so +0 -0
- sage/schemes/elliptic_curves/mod_sym_num.pyx +3796 -0
- sage/schemes/elliptic_curves/modular_parametrization.py +305 -0
- sage/schemes/elliptic_curves/padic_lseries.py +1793 -0
- sage/schemes/elliptic_curves/padics.py +1816 -0
- sage/schemes/elliptic_curves/period_lattice.py +2234 -0
- sage/schemes/elliptic_curves/period_lattice_region.cpython-312-darwin.so +0 -0
- sage/schemes/elliptic_curves/period_lattice_region.pyx +722 -0
- sage/schemes/elliptic_curves/saturation.py +715 -0
- sage/schemes/elliptic_curves/sha_tate.py +1158 -0
- sage/schemes/elliptic_curves/weierstrass_morphism.py +1117 -0
- sage/schemes/elliptic_curves/weierstrass_transform.py +200 -0
- sage/schemes/hyperelliptic_curves/all.py +6 -0
- sage/schemes/hyperelliptic_curves/constructor.py +291 -0
- sage/schemes/hyperelliptic_curves/hyperelliptic_finite_field.py +1914 -0
- sage/schemes/hyperelliptic_curves/hyperelliptic_g2.py +192 -0
- sage/schemes/hyperelliptic_curves/hyperelliptic_generic.py +954 -0
- sage/schemes/hyperelliptic_curves/hyperelliptic_padic_field.py +1332 -0
- sage/schemes/hyperelliptic_curves/hyperelliptic_rational_field.py +84 -0
- sage/schemes/hyperelliptic_curves/invariants.py +410 -0
- sage/schemes/hyperelliptic_curves/jacobian_endomorphism_utils.py +315 -0
- sage/schemes/hyperelliptic_curves/jacobian_g2.py +32 -0
- sage/schemes/hyperelliptic_curves/jacobian_generic.py +419 -0
- sage/schemes/hyperelliptic_curves/jacobian_homset.py +186 -0
- sage/schemes/hyperelliptic_curves/jacobian_morphism.py +875 -0
- sage/schemes/hyperelliptic_curves/kummer_surface.py +99 -0
- sage/schemes/hyperelliptic_curves/mestre.py +302 -0
- sage/schemes/hyperelliptic_curves/monsky_washnitzer.py +3871 -0
- sage/schemes/jacobians/abstract_jacobian.py +277 -0
- sage/schemes/jacobians/all.py +2 -0
- sage/schemes/overview.py +161 -0
- sage/schemes/plane_conics/all.py +22 -0
- sage/schemes/plane_conics/con_field.py +1296 -0
- sage/schemes/plane_conics/con_finite_field.py +158 -0
- sage/schemes/plane_conics/con_number_field.py +456 -0
- sage/schemes/plane_conics/con_rational_field.py +406 -0
- sage/schemes/plane_conics/con_rational_function_field.py +580 -0
- sage/schemes/plane_conics/constructor.py +249 -0
- sage/schemes/plane_quartics/all.py +2 -0
- sage/schemes/plane_quartics/quartic_constructor.py +71 -0
- sage/schemes/plane_quartics/quartic_generic.py +73 -0
- sage/schemes/riemann_surfaces/all.py +1 -0
- sage/schemes/riemann_surfaces/riemann_surface.py +4117 -0
- sage_wheels/share/cremona/cremona_mini.db +0 -0
- sage_wheels/share/ellcurves/rank0 +30427 -0
- sage_wheels/share/ellcurves/rank1 +31871 -0
- sage_wheels/share/ellcurves/rank10 +6 -0
- sage_wheels/share/ellcurves/rank11 +6 -0
- sage_wheels/share/ellcurves/rank12 +1 -0
- sage_wheels/share/ellcurves/rank14 +1 -0
- sage_wheels/share/ellcurves/rank15 +1 -0
- sage_wheels/share/ellcurves/rank17 +1 -0
- sage_wheels/share/ellcurves/rank19 +1 -0
- sage_wheels/share/ellcurves/rank2 +2388 -0
- sage_wheels/share/ellcurves/rank20 +1 -0
- sage_wheels/share/ellcurves/rank21 +1 -0
- sage_wheels/share/ellcurves/rank22 +1 -0
- sage_wheels/share/ellcurves/rank23 +1 -0
- sage_wheels/share/ellcurves/rank24 +1 -0
- sage_wheels/share/ellcurves/rank28 +1 -0
- sage_wheels/share/ellcurves/rank3 +836 -0
- sage_wheels/share/ellcurves/rank4 +10 -0
- sage_wheels/share/ellcurves/rank5 +5 -0
- sage_wheels/share/ellcurves/rank6 +5 -0
- sage_wheels/share/ellcurves/rank7 +5 -0
- sage_wheels/share/ellcurves/rank8 +6 -0
- 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
|