Repository: sagemath/sage

Issue:
Implemented is_chebyshev and is_lattes for one dimensional projective dynamical systems
<div id="comment:0"></div>

Added functions to projective dynamical systems which test whether the dynamical system is conjugate (over an algebraic closure of the base field) to a Chebyshev polynomial or to a Lattes map.

Component: **dynamics**

Keywords: **SI2019, sd104**

Author: **Trevor Hyde, Fiona Lu, Alex Shearer**

Branch/Commit: **[u/gh-EnderWannabe/implemented_is_chebyshev_and_is_lattes_for_one_dimensional_projective_dynamical_systems](https://github.com/sagemath/sagetrac-mirror/tree/u/gh-EnderWannabe/implemented_is_chebyshev_and_is_lattes_for_one_dimensional_projective_dynamical_systems) @ [`0d6c684`](https://github.com/sagemath/sagetrac-mirror/commit/0d6c684580c93527c3598f681fafa5737000cf23)**

Reviewer: **Andrew Odesky, Benjamin Krakoff, Krystal Maughan, Mingming Zhang**

_Issue created by migration from https://trac.sagemath.org/ticket/28292_




Discussion:
Branch: **[u/gh-sheareralexj/implemented_is_chebyshev_and_is_lattes_for_one_dimensional_projective_dynamical_systems](https://github.com/sagemath/sagetrac-mirror/tree/u/gh-sheareralexj/implemented_is_chebyshev_and_is_lattes_for_one_dimensional_projective_dynamical_systems)**
Commit: **[`827ae2f`](https://github.com/sagemath/sagetrac-mirror/commit/827ae2f9bafe40743a973e4906d10693944e5e2f)**
<div id="comment:2"></div>

New commits:
<table><tr><td><a href="https://github.com/sagemath/sagetrac-mirror/commit/827ae2f9bafe40743a973e4906d10693944e5e2f"><code>827ae2f</code></a></td><td><code>28292: Added is_lattes() and is_chebyshev()</code></td></tr></table>

Changed branch from **[u/gh-sheareralexj/implemented_is_chebyshev_and_is_lattes_for_one_dimensional_projective_dynamical_systems](https://github.com/sagemath/sagetrac-mirror/tree/u/gh-sheareralexj/implemented_is_chebyshev_and_is_lattes_for_one_dimensional_projective_dynamical_systems)** to **[u/gh-aodesky/implemented_is_chebyshev_and_is_lattes_for_one_dimensional_projective_dynamical_systems](https://github.com/sagemath/sagetrac-mirror/tree/u/gh-aodesky/implemented_is_chebyshev_and_is_lattes_for_one_dimensional_projective_dynamical_systems)**
Changed keywords from **SI2019** to **SI2019, Sagedays 104**
<div id="comment:4" align="right">comment:4</div>

All tests pass.

---
New commits:
<table><tr><td><a href="https://github.com/sagemath/sagetrac-mirror/commit/39c7482a6dc80dd998bc11519502d371a15471d1"><code>39c7482</code></a></td><td><code>Added examples over number fields, fixed an issue with changing the base ring not specifying the embedding.</code></td></tr></table>

Changed commit from **[`827ae2f`](https://github.com/sagemath/sagetrac-mirror/commit/827ae2f9bafe40743a973e4906d10693944e5e2f)** to **[`39c7482`](https://github.com/sagemath/sagetrac-mirror/commit/39c7482a6dc80dd998bc11519502d371a15471d1)**
Reviewer: **Andrew Odesky, Krystal Maughan, Mingming Zhang**
Changed reviewer from **Andrew Odesky, Krystal Maughan, Mingming Zhang** to **Andrew Odesky, Benjamin Krakoff, Krystal Maughan, Mingming Zhang**
Changed keywords from **SI2019, Sagedays 104** to **SI2019, sd104**
<div id="comment:8" align="right">comment:8</div>

Merge conflict
Changed commit from **[`39c7482`](https://github.com/sagemath/sagetrac-mirror/commit/39c7482a6dc80dd998bc11519502d371a15471d1)** to **[`0d6c684`](https://github.com/sagemath/sagetrac-mirror/commit/0d6c684580c93527c3598f681fafa5737000cf23)**
<div id="comment:9"></div>

Branch pushed to git repo; I updated commit sha1. Last 10 new commits:
<table><tr><td><a href="https://github.com/sagemath/sagetrac-mirror/commit/ed31de85ee5bd19183cc24efe8f3fdccce8b529c"><code>ed31de8</code></a></td><td><code>Changed factor to be called on the function and a typo in the doctests.</code></td></tr><tr><td><a href="https://github.com/sagemath/sagetrac-mirror/commit/b9b20293df80d84c5f6846abb56d3ff33b3e8094"><code>b9b2029</code></a></td><td><code>Updated doc to include an example with the embedding specified for splitting the Galois orbits of critical points.</code></td></tr><tr><td><a href="https://github.com/sagemath/sagetrac-mirror/commit/f7ec2022ebe5a1978f7c185440fd5cbcda6a4de0"><code>f7ec202</code></a></td><td><code>Made the ramification type compute over a sufficiently large field by definition.</code></td></tr><tr><td><a href="https://github.com/sagemath/sagetrac-mirror/commit/5dde8f0e7e42a59546d6115ad62178df59bafefc"><code>5dde8f0</code></a></td><td><code>Merge branch 'u/gh-aodesky/ramification_type_for_rational_maps' of git://trac.sagemath.org/sage into 28753</code></td></tr><tr><td><a href="https://github.com/sagemath/sagetrac-mirror/commit/0160c01d0e7d09e500e1a487e815eb359f2bd243"><code>0160c01</code></a></td><td><code>Merge commit '296b749a019a0365169fe82b5e0859502af909b3' of git://trac.sagemath.org/sage into 28753</code></td></tr><tr><td><a href="https://github.com/sagemath/sagetrac-mirror/commit/fa92c61aad503150c91df6bb0eb110d6f662bb17"><code>fa92c61</code></a></td><td><code>Merge commit '485ed39a9482fb8336f58c778f2f6a0f847b7caa' of git://trac.sagemath.org/sage into 28753</code></td></tr><tr><td><a href="https://github.com/sagemath/sagetrac-mirror/commit/ddb7200ea8fc32b6d8ba24cba7fba2d2f06093cf"><code>ddb7200</code></a></td><td><code>Merge commit 'e5b1ca33870046f4ed9bc5a3a4561fec54b54b3b' of git://trac.sagemath.org/sage into 28753</code></td></tr><tr><td><a href="https://github.com/sagemath/sagetrac-mirror/commit/0a97a35590c15beaf23c355c70c3fbce13ceef9a"><code>0a97a35</code></a></td><td><code>Merge branch 'develop' of git://trac.sagemath.org/sage into 28753</code></td></tr><tr><td><a href="https://github.com/sagemath/sagetrac-mirror/commit/7c17261b301d7f7546bb25c74935d9ec6dab0771"><code>7c17261</code></a></td><td><code>Merge branch 'u/gh-aodesky/ramification_type_for_rational_maps' of git://trac.sagemath.org/sage into 28292</code></td></tr><tr><td><a href="https://github.com/sagemath/sagetrac-mirror/commit/0d6c684580c93527c3598f681fafa5737000cf23"><code>0d6c684</code></a></td><td><code>Merge branch 'develop' of git://trac.sagemath.org/sage into 28292</code></td></tr></table>

<div id="comment:11" align="right">comment:11</div>

Ticket retargeted after milestone closed
<div id="comment:12" align="right">comment:12</div>

Batch modifying tickets that will likely not be ready for 9.1, based on a review of the ticket title, branch/review status, and last modification date.
<div id="comment:14" align="right">comment:14</div>

Setting new milestone based on a cursory review of ticket status, priority, and last modification date.
<div id="comment:15" align="right">comment:15</div>

Setting a new milestone for this ticket based on a cursory review.
Changed branch from **[u/gh-aodesky/implemented_is_chebyshev_and_is_lattes_for_one_dimensional_projective_dynamical_systems](https://github.com/sagemath/sagetrac-mirror/tree/u/gh-aodesky/implemented_is_chebyshev_and_is_lattes_for_one_dimensional_projective_dynamical_systems)** to **[u/gh-EnderWannabe/implemented_is_chebyshev_and_is_lattes_for_one_dimensional_projective_dynamical_systems](https://github.com/sagemath/sagetrac-mirror/tree/u/gh-EnderWannabe/implemented_is_chebyshev_and_is_lattes_for_one_dimensional_projective_dynamical_systems)**
<div id="comment:17" align="right">comment:17</div>

Stalled in `needs_review` or `needs_info`; likely won't make it into Sage 9.5.
Branch has merge conflicts

Resolving patch:
diff --git a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py
--- a/src/sage/dynamics/arithmetic_dynamics/projective_ds.py
+++ b/src/sage/dynamics/arithmetic_dynamics/projective_ds.py
@@ -6482,6 +6482,309 @@ def postcritical_set(self, check=True):
                 next_point = f(next_point)
         return post_critical_list
 
+    def is_chebyshev(self):
+        r"""
+        Check if ``self`` is a Chebyshev polynomial.
+
+        OUTPUT: True if ``self`` is Chebyshev, False otherwise.
+
+        EXAMPLES::
+
+            sage: P.<x,y> = ProjectiveSpace(QQ, 1)
+            sage: F = DynamicalSystem_projective([x^4, y^4])
+            sage: F.is_chebyshev()
+            False
+
+        ::
+
+            sage: P.<x,y> = ProjectiveSpace(QQ, 1)
+            sage: F = DynamicalSystem_projective([x^2 + y^2, y^2])
+            sage: F.is_chebyshev()
+            False
+
+        ::
+
+            sage: P.<x,y> = ProjectiveSpace(QQ, 1)
+            sage: F = DynamicalSystem_projective([2*x^2 - y^2, y^2])
+            sage: F.is_chebyshev()
+            True
+
+        ::
+
+            sage: P.<x,y> = ProjectiveSpace(QQ, 1)
+            sage: F = DynamicalSystem_projective([x^3, 4*y^3 - 3*x^2*y])
+            sage: F.is_chebyshev()
+            True
+
+        ::
+
+            sage: P.<x,y> = ProjectiveSpace(QQ, 1)
+            sage: F = DynamicalSystem_projective([2*x^2 - y^2, y^2])
+            sage: L.<i> = CyclotomicField(4)
+            sage: M = Matrix([[0,i],[-i,0]])
+            sage: F.conjugate(M)
+            Dynamical System of Projective Space of dimension 1 over Cyclotomic Field of order 4 and degree 2
+              Defn: Defined on coordinates by sending (x : y) to
+                    ((-i)*x^2 : (-i)*x^2 + (2*i)*y^2)
+            sage: F.is_chebyshev()
+            True
+
+        REFERENCES:
+
+        - [Mil2006]_
+        """
+        # We need `F` to be defined over a number field for
+        # the function `is_postcrtically_finite` to work
+        if self.base_ring() not in NumberFields():
+            raise NotImplementedError("Base ring must be a number field")
+
+        if self.domain().dimension() != 1:
+            return False
+
+        # All Chebyshev polynomials are postcritically finite
+        if not self.is_postcritically_finite():
+            return False
+
+        # Get field of definition for critical points and change base field
+        critical_field, phi = self.field_of_definition_critical(return_embedding=True)
+        F_crit = self.change_ring(phi)
+
+        # Get the critical points and post-critical set
+        crit_set = set(F_crit.critical_points())
+        post_crit_set = set()
+        images_needed = copy(crit_set)
+        while len(images_needed) != 0:
+            Q = images_needed.pop()
+            Q2 = F_crit(Q)
+            if Q2 not in post_crit_set:
+                post_crit_set.add(Q2)
+                images_needed.add(Q2)
+
+        crit_fixed_pts = set()
+        for crit in crit_set:
+            if F_crit.nth_iterate(crit, 1) == crit:
+                crit_fixed_pts.add(crit)
+
+        # All Chebyshev maps have 3 post-critical values
+        if (len(post_crit_set) != 3) or (len(crit_fixed_pts) != 1):
+            return False
+
+        f = F_crit.dehomogenize(1)[0]
+        x = f.parent().gen()
+        ram_points = {}
+        for crit in crit_set:
+            g = f
+            new_crit = crit
+
+            # Check if critical point is infinity
+            if crit[1] == 0:
+                g = g.subs(x=1/x)
+                new_crit = F_crit.domain()([0, 1])
+
+            # Check if output is infinity
+            if F_crit.nth_iterate(crit, 1)[1] == 0:
+                g = 1/g
+
+            new_crit = new_crit.dehomogenize(1)[0]
+            e = 1
+            g = g.derivative(x)
+
+            while g(new_crit) == 0:
+                e += 1
+                g = g.derivative(x)
+
+            ram_points[crit] = e
+
+        r = {}
+
+        # Set r value to 0 to represent infinity
+        r[crit_fixed_pts.pop()] = 0
+
+        # Get non-fixed tail points in the post-critical portrait
+        crit_tails = crit_set.difference(post_crit_set)
+        for crit in crit_tails:
+            # Each critical tail point has r value 1
+            r[crit] = 1
+            point = crit
+
+            # Assign r values to every point in the orbit of crit.
+            # If we find a point in the orbit which already has an r value assigned,
+            # check that we get a consistent r value.
+            while F_crit.nth_iterate(point, 1) not in r.keys():
+                if point not in ram_points.keys():
+                    r[F_crit.nth_iterate(point,1)] = r[point]
+                else:
+                    r[F_crit.nth_iterate(point,1)] = r[point] * ram_points[point]
+
+                point = F_crit.nth_iterate(point,1)
+
+            # Once we get here, the image of point has an assigned r value
+            # We check that this value is consistent
+            if point not in ram_points.keys():
+                if r[F_crit.nth_iterate(point,1)] != r[point]:
+                    return False
+            elif r[F_crit.nth_iterate(point,1)] != r[point] * ram_points[point]:
+                return False
+
+        # The non-one r values must be one of the following in order for F to be Chebyshev
+        r_vals = sorted([val for val in r.values() if val != 1])
+        return r_vals == [0, 2, 2]
+
+    def is_Lattes(self):
+        r"""
+        Check if ``self`` is a Lattes map
+
+        OUTPUT: True if ``self`` is Lattes, False otherwise
+
+        EXAMPLES::
+
+            sage: P.<x,y> = ProjectiveSpace(QQ, 1)
+            sage: F = DynamicalSystem_projective([x^3, y^3])
+            sage: F.is_Lattes()
+            False
+
+        ::
+
+            sage: P.<x,y> = ProjectiveSpace(QQ, 1)
+            sage: F = DynamicalSystem_projective([x^2 - 2*y^2, y^2])
+            sage: F.is_Lattes()
+            False
+
+        ::
+
+            sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
+            sage: F = DynamicalSystem_projective([x^2 + y^2 + z^2, y^2, z^2])
+            sage: F.is_Lattes()
+            False
+
+        ::
+
+            sage: P.<x,y> = ProjectiveSpace(QQ, 1)
+            sage: F = DynamicalSystem_projective([(x + y)*(x - y)^3, y*(2*x + y)^3])
+            sage: F.is_Lattes()
+            True
+
+        ::
+
+            sage: P.<x,y> = ProjectiveSpace(QQ, 1)
+            sage: F = DynamicalSystem_projective([(x + y)^4, 16*x*y*(x - y)^2])
+            sage: F.is_Lattes()
+            True
+
+        ::
+
+            sage: f = P.Lattes_map(EllipticCurve([0, 0, 0, 0, 2]),2)
+            sage: f.is_Lattes()
+            True
+
+        ::
+
+            sage: f = P.Lattes_map(EllipticCurve([0, 0, 0, 0, 2]), 2)
+            sage: L.<i> = CyclotomicField(4)
+            sage: M = Matrix([[i, 0], [0, -i]])
+            sage: f.conjugate(M)
+            Dynamical System of Projective Space of dimension 1 over Cyclotomic Field of order 4 and degree 2
+              Defn: Defined on coordinates by sending (x : y) to
+                    ((-1/4*i)*x^4 + (-4*i)*x*y^3 : (-i)*x^3*y + (2*i)*y^4)
+            sage: f.is_Lattes()
+            True
+
+        REFERENCES:
+
+        - [Mil2006]_
+        """
+        # We need `f` to be defined over a number field for
+        # the function `is_postcrtically_finite` to work
+        if self.base_ring() not in NumberFields():
+            raise NotImplementedError("Base ring must be a number field")
+
+        if self.domain().dimension() != 1:
+            return False
+
+        # All Lattes maps are postcritically finite
+        if not self.is_postcritically_finite():
+            return False
+
+        # Get field of definition for critical points and change basefield
+        critical_field, phi = self.field_of_definition_critical(return_embedding=True)
+        F_crit = self.change_ring(phi)
+
+        # Get the critical points and post-critical set
+        crit = F_crit.critical_points()
+        post_crit = set()
+        images_needed = copy(crit)
+        while len(images_needed) != 0:
+            Q = images_needed.pop()
+            Q2 = F_crit(Q)
+            if Q2 not in post_crit:
+                post_crit.add(Q2)
+                images_needed.append(Q2)
+
+        (crit_set, post_crit_set) = crit, list(post_crit)
+
+        # All Lattes maps have 3 or 4 post critical values
+        if not len(post_crit_set) in [3, 4]:
+            return False
+
+        f = F_crit.dehomogenize(1)[0]
+        x = f.parent().gen()
+        ram_points = {}
+        for crit in crit_set:
+            g = f
+            new_crit = crit
+
+            # Check if critical point is infinity
+            if crit[1] == 0:
+                g = g.subs(x=1/x)
+                new_crit = F_crit.domain()([0, 1])
+
+            # Check if output is infinity
+            if F_crit.nth_iterate(crit, 1)[1] == 0:
+                g = 1/g
+
+            new_crit = new_crit.dehomogenize(1)[0]
+
+            e = 1
+            g = g.derivative(x)
+            while g(new_crit) == 0:
+                e += 1
+                g = g.derivative(x)
+            ram_points[crit] = e
+
+        r = {}
+
+        # Get tail points in the post-critical portrait
+        crit_tails = set(crit_set).difference(set(post_crit_set))
+        for crit in crit_tails:
+            # Each critical tail point has r value 1
+            r[crit] = 1
+            point = crit
+
+            # Assign r values to every point in the orbit of crit
+            # If we find a point in the orbit which already has an r value assigned,
+            # check that we get a consistent r value
+            while F_crit.nth_iterate(point, 1) not in r.keys():
+                if point not in ram_points.keys():
+                    r[F_crit.nth_iterate(point, 1)] = r[point]
+                else:
+                    r[F_crit.nth_iterate(point, 1)] = r[point] * ram_points[point]
+
+                point = F_crit.nth_iterate(point,1)
+
+            # Once we get here the image of point has an assigned r value
+            # We check that this value is consistent.
+            if point not in ram_points.keys():
+                if r[F_crit.nth_iterate(point, 1)] != r[point]:
+                    return False
+            else:
+                if r[F_crit.nth_iterate(point, 1)] != r[point] * ram_points[point]:
+                    return False
+
+        # The non-one r values must be one of the following in order for F to be Lattes
+        r_lattes_cases = [[2, 2, 2, 2], [3, 3, 3], [2, 4, 4], [2, 3, 6]]
+        r_vals = sorted([val for val in r.values() if val != 1])
+        return r_vals in r_lattes_cases
+
 
 class DynamicalSystem_projective_field(DynamicalSystem_projective,
                                        SchemeMorphism_polynomial_projective_space_field):


Test patch: