Skip to content

Commit 72b41aa

Browse files
author
Release Manager
committed
gh-36000: New functions `is_chebyshev` and `is_Lattes` for one dimensional projective dynamical systems This PR carries the work and hopes to fix #28292. All the codes in this PR were/are implemented by the participants in said issue. <!-- ^^^^^ Please provide a concise, informative and self-explanatory title. Don't put issue numbers in there, do this in the PR body below. For example, instead of "Fixes #1234" use "Introduce new method to calculate 1+1" --> <!-- Describe your changes here in detail --> <!-- Why is this change required? What problem does it solve? --> <!-- If this PR resolves an open issue, please link to it here. For example "Fixes #12345". --> <!-- If your change requires a documentation PR, please link it appropriately. --> ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> <!-- If your change requires a documentation PR, please link it appropriately --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> <!-- Feel free to remove irrelevant items. --> - [x] The title is concise, informative, and self-explanatory. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation accordingly. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on - #12345: short description why this is a dependency - #34567: ... --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> URL: #36000 Reported by: Jing Guo Reviewer(s):
2 parents a102fbd + ae3913d commit 72b41aa

File tree

2 files changed

+306
-0
lines changed

2 files changed

+306
-0
lines changed

src/doc/en/reference/references/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4588,6 +4588,9 @@ REFERENCES:
45884588
.. [Mil1974] \J. W. Milnor and J. D. Stasheff, *Characteristic Classes*,
45894589
University Press, Princeton and Tokyo, 1974.
45904590
4591+
.. [Mil2006] \J. W. Milnor, *On Lattes maps*,
4592+
Dynamics on the Riemann sphere, Eur. Math. Soc., 9–43
4593+
45914594
.. [Mil1978] \S. Milne, *A q-analog of restricted growth functions,
45924595
Dobinsky’s equality and Charlier
45934596
polynomials*. Trans. Amer. Math. Soc., 245 (1978),

src/sage/dynamics/arithmetic_dynamics/projective_ds.py

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6482,6 +6482,309 @@ def postcritical_set(self, check=True):
64826482
next_point = f(next_point)
64836483
return post_critical_list
64846484

6485+
def is_chebyshev(self):
6486+
r"""
6487+
Check if ``self`` is a Chebyshev polynomial.
6488+
6489+
OUTPUT: True if ``self`` is Chebyshev, False otherwise.
6490+
6491+
EXAMPLES::
6492+
6493+
sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6494+
sage: F = DynamicalSystem_projective([x^4, y^4])
6495+
sage: F.is_chebyshev()
6496+
False
6497+
6498+
::
6499+
6500+
sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6501+
sage: F = DynamicalSystem_projective([x^2 + y^2, y^2])
6502+
sage: F.is_chebyshev()
6503+
False
6504+
6505+
::
6506+
6507+
sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6508+
sage: F = DynamicalSystem_projective([2*x^2 - y^2, y^2])
6509+
sage: F.is_chebyshev()
6510+
True
6511+
6512+
::
6513+
6514+
sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6515+
sage: F = DynamicalSystem_projective([x^3, 4*y^3 - 3*x^2*y])
6516+
sage: F.is_chebyshev()
6517+
True
6518+
6519+
::
6520+
6521+
sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6522+
sage: F = DynamicalSystem_projective([2*x^2 - y^2, y^2])
6523+
sage: L.<i> = CyclotomicField(4)
6524+
sage: M = Matrix([[0,i],[-i,0]])
6525+
sage: F.conjugate(M)
6526+
Dynamical System of Projective Space of dimension 1 over Cyclotomic Field of order 4 and degree 2
6527+
Defn: Defined on coordinates by sending (x : y) to
6528+
((-i)*x^2 : (-i)*x^2 + (2*i)*y^2)
6529+
sage: F.is_chebyshev()
6530+
True
6531+
6532+
REFERENCES:
6533+
6534+
- [Mil2006]_
6535+
"""
6536+
# We need `F` to be defined over a number field for
6537+
# the function `is_postcrtically_finite` to work
6538+
if self.base_ring() not in NumberFields():
6539+
raise NotImplementedError("Base ring must be a number field")
6540+
6541+
if self.domain().dimension() != 1:
6542+
return False
6543+
6544+
# All Chebyshev polynomials are postcritically finite
6545+
if not self.is_postcritically_finite():
6546+
return False
6547+
6548+
# Get field of definition for critical points and change base field
6549+
critical_field, phi = self.field_of_definition_critical(return_embedding=True)
6550+
F_crit = self.change_ring(phi)
6551+
6552+
# Get the critical points and post-critical set
6553+
crit_set = set(F_crit.critical_points())
6554+
post_crit_set = set()
6555+
images_needed = copy(crit_set)
6556+
while len(images_needed) != 0:
6557+
Q = images_needed.pop()
6558+
Q2 = F_crit(Q)
6559+
if Q2 not in post_crit_set:
6560+
post_crit_set.add(Q2)
6561+
images_needed.add(Q2)
6562+
6563+
crit_fixed_pts = set()
6564+
for crit in crit_set:
6565+
if F_crit.nth_iterate(crit, 1) == crit:
6566+
crit_fixed_pts.add(crit)
6567+
6568+
# All Chebyshev maps have 3 post-critical values
6569+
if (len(post_crit_set) != 3) or (len(crit_fixed_pts) != 1):
6570+
return False
6571+
6572+
f = F_crit.dehomogenize(1)[0]
6573+
x = f.parent().gen()
6574+
ram_points = {}
6575+
for crit in crit_set:
6576+
g = f
6577+
new_crit = crit
6578+
6579+
# Check if critical point is infinity
6580+
if crit[1] == 0:
6581+
g = g.subs(x=1/x)
6582+
new_crit = F_crit.domain()([0, 1])
6583+
6584+
# Check if output is infinity
6585+
if F_crit.nth_iterate(crit, 1)[1] == 0:
6586+
g = 1/g
6587+
6588+
new_crit = new_crit.dehomogenize(1)[0]
6589+
e = 1
6590+
g = g.derivative(x)
6591+
6592+
while g(new_crit) == 0:
6593+
e += 1
6594+
g = g.derivative(x)
6595+
6596+
ram_points[crit] = e
6597+
6598+
r = {}
6599+
6600+
# Set r value to 0 to represent infinity
6601+
r[crit_fixed_pts.pop()] = 0
6602+
6603+
# Get non-fixed tail points in the post-critical portrait
6604+
crit_tails = crit_set.difference(post_crit_set)
6605+
for crit in crit_tails:
6606+
# Each critical tail point has r value 1
6607+
r[crit] = 1
6608+
point = crit
6609+
6610+
# Assign r values to every point in the orbit of crit.
6611+
# If we find a point in the orbit which already has an r value assigned,
6612+
# check that we get a consistent r value.
6613+
while F_crit.nth_iterate(point, 1) not in r.keys():
6614+
if point not in ram_points.keys():
6615+
r[F_crit.nth_iterate(point,1)] = r[point]
6616+
else:
6617+
r[F_crit.nth_iterate(point,1)] = r[point] * ram_points[point]
6618+
6619+
point = F_crit.nth_iterate(point,1)
6620+
6621+
# Once we get here, the image of point has an assigned r value
6622+
# We check that this value is consistent
6623+
if point not in ram_points.keys():
6624+
if r[F_crit.nth_iterate(point,1)] != r[point]:
6625+
return False
6626+
elif r[F_crit.nth_iterate(point,1)] != r[point] * ram_points[point]:
6627+
return False
6628+
6629+
# The non-one r values must be one of the following in order for F to be Chebyshev
6630+
r_vals = sorted([val for val in r.values() if val != 1])
6631+
return r_vals == [0, 2, 2]
6632+
6633+
def is_Lattes(self):
6634+
r"""
6635+
Check if ``self`` is a Lattes map
6636+
6637+
OUTPUT: True if ``self`` is Lattes, False otherwise
6638+
6639+
EXAMPLES::
6640+
6641+
sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6642+
sage: F = DynamicalSystem_projective([x^3, y^3])
6643+
sage: F.is_Lattes()
6644+
False
6645+
6646+
::
6647+
6648+
sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6649+
sage: F = DynamicalSystem_projective([x^2 - 2*y^2, y^2])
6650+
sage: F.is_Lattes()
6651+
False
6652+
6653+
::
6654+
6655+
sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
6656+
sage: F = DynamicalSystem_projective([x^2 + y^2 + z^2, y^2, z^2])
6657+
sage: F.is_Lattes()
6658+
False
6659+
6660+
::
6661+
6662+
sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6663+
sage: F = DynamicalSystem_projective([(x + y)*(x - y)^3, y*(2*x + y)^3])
6664+
sage: F.is_Lattes()
6665+
True
6666+
6667+
::
6668+
6669+
sage: P.<x,y> = ProjectiveSpace(QQ, 1)
6670+
sage: F = DynamicalSystem_projective([(x + y)^4, 16*x*y*(x - y)^2])
6671+
sage: F.is_Lattes()
6672+
True
6673+
6674+
::
6675+
6676+
sage: f = P.Lattes_map(EllipticCurve([0, 0, 0, 0, 2]),2)
6677+
sage: f.is_Lattes()
6678+
True
6679+
6680+
::
6681+
6682+
sage: f = P.Lattes_map(EllipticCurve([0, 0, 0, 0, 2]), 2)
6683+
sage: L.<i> = CyclotomicField(4)
6684+
sage: M = Matrix([[i, 0], [0, -i]])
6685+
sage: f.conjugate(M)
6686+
Dynamical System of Projective Space of dimension 1 over Cyclotomic Field of order 4 and degree 2
6687+
Defn: Defined on coordinates by sending (x : y) to
6688+
((-1/4*i)*x^4 + (-4*i)*x*y^3 : (-i)*x^3*y + (2*i)*y^4)
6689+
sage: f.is_Lattes()
6690+
True
6691+
6692+
REFERENCES:
6693+
6694+
- [Mil2006]_
6695+
"""
6696+
# We need `f` to be defined over a number field for
6697+
# the function `is_postcrtically_finite` to work
6698+
if self.base_ring() not in NumberFields():
6699+
raise NotImplementedError("Base ring must be a number field")
6700+
6701+
if self.domain().dimension() != 1:
6702+
return False
6703+
6704+
# All Lattes maps are postcritically finite
6705+
if not self.is_postcritically_finite():
6706+
return False
6707+
6708+
# Get field of definition for critical points and change basefield
6709+
critical_field, phi = self.field_of_definition_critical(return_embedding=True)
6710+
F_crit = self.change_ring(phi)
6711+
6712+
# Get the critical points and post-critical set
6713+
crit = F_crit.critical_points()
6714+
post_crit = set()
6715+
images_needed = copy(crit)
6716+
while len(images_needed) != 0:
6717+
Q = images_needed.pop()
6718+
Q2 = F_crit(Q)
6719+
if Q2 not in post_crit:
6720+
post_crit.add(Q2)
6721+
images_needed.append(Q2)
6722+
6723+
(crit_set, post_crit_set) = crit, list(post_crit)
6724+
6725+
# All Lattes maps have 3 or 4 post critical values
6726+
if not len(post_crit_set) in [3, 4]:
6727+
return False
6728+
6729+
f = F_crit.dehomogenize(1)[0]
6730+
x = f.parent().gen()
6731+
ram_points = {}
6732+
for crit in crit_set:
6733+
g = f
6734+
new_crit = crit
6735+
6736+
# Check if critical point is infinity
6737+
if crit[1] == 0:
6738+
g = g.subs(x=1/x)
6739+
new_crit = F_crit.domain()([0, 1])
6740+
6741+
# Check if output is infinity
6742+
if F_crit.nth_iterate(crit, 1)[1] == 0:
6743+
g = 1/g
6744+
6745+
new_crit = new_crit.dehomogenize(1)[0]
6746+
6747+
e = 1
6748+
g = g.derivative(x)
6749+
while g(new_crit) == 0:
6750+
e += 1
6751+
g = g.derivative(x)
6752+
ram_points[crit] = e
6753+
6754+
r = {}
6755+
6756+
# Get tail points in the post-critical portrait
6757+
crit_tails = set(crit_set).difference(set(post_crit_set))
6758+
for crit in crit_tails:
6759+
# Each critical tail point has r value 1
6760+
r[crit] = 1
6761+
point = crit
6762+
6763+
# Assign r values to every point in the orbit of crit
6764+
# If we find a point in the orbit which already has an r value assigned,
6765+
# check that we get a consistent r value
6766+
while F_crit.nth_iterate(point, 1) not in r.keys():
6767+
if point not in ram_points.keys():
6768+
r[F_crit.nth_iterate(point, 1)] = r[point]
6769+
else:
6770+
r[F_crit.nth_iterate(point, 1)] = r[point] * ram_points[point]
6771+
6772+
point = F_crit.nth_iterate(point,1)
6773+
6774+
# Once we get here the image of point has an assigned r value
6775+
# We check that this value is consistent.
6776+
if point not in ram_points.keys():
6777+
if r[F_crit.nth_iterate(point, 1)] != r[point]:
6778+
return False
6779+
else:
6780+
if r[F_crit.nth_iterate(point, 1)] != r[point] * ram_points[point]:
6781+
return False
6782+
6783+
# The non-one r values must be one of the following in order for F to be Lattes
6784+
r_lattes_cases = [[2, 2, 2, 2], [3, 3, 3], [2, 4, 4], [2, 3, 6]]
6785+
r_vals = sorted([val for val in r.values() if val != 1])
6786+
return r_vals in r_lattes_cases
6787+
64856788

64866789
class DynamicalSystem_projective_field(DynamicalSystem_projective,
64876790
SchemeMorphism_polynomial_projective_space_field):

0 commit comments

Comments
 (0)