Repository: sagemath/sage

Issue:
adding support for subschemes in _is_preperiodic
<div id="comment:0"></div>

Currently _is_preperiodic does not support dynamical systems defined on subschemes. This ticket aims to add support for morphisms of projective space defined over a subscheme.

CC:  @bhutz

Component: **dynamics**

Keywords: **gsoc2021**

Author: **Alexander Galarraga**

Branch/Commit: **[u/gh-EnderWannabe/is_preperiodic](https://github.com/sagemath/sagetrac-mirror/tree/u/gh-EnderWannabe/is_preperiodic) @ [`faf2b23`](https://github.com/sagemath/sagetrac-mirror/commit/faf2b23e9fa2d925b30e61ce8cf8717335eaf972)**

Reviewer: **Ben Hutz**

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




Discussion:

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
@@ -6226,10 +6226,6 @@ def _is_preperiodic(self, P, err=0.1, return_period=False):
         Determine if the point is preperiodic with respect to this
         dynamical system.
 
-        .. NOTE::
-
-            This is only implemented for projective space (not subschemes).
-
         ALGORITHM:
 
         We know that a point is preperiodic if and only if it has
@@ -6284,15 +6280,56 @@ def _is_preperiodic(self, P, err=0.1, return_period=False):
             sage: Q = P.point([a,1])
             sage: Q.is_preperiodic(f)
             True
+
+        ::
+
+            sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
+            sage: X = P.subscheme(z)
+            sage: f = DynamicalSystem([x^2 - y^2, y^2, z^2], domain=X)
+            sage: p = X((-1, 1, 0))
+            sage: f._is_preperiodic(p, return_period=True)
+            (0, 2)
+
+        ::
+
+            sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
+            sage: X = P.subscheme(x)
+            sage: f = DynamicalSystem([x^2 - y^2, y^2, z^2], domain=X)
+            sage: p = X((0, 1, 0))
+            sage: f._is_preperiodic(p, return_period=True)
+            Traceback (most recent call last):
+            ...
+            ValueError: orbit of point leaves domain
+
+        ::
+
+            sage: R.<t> = QQ[]
+            sage: K.<a> = NumberField(t^2 - t - 1)
+            sage: P.<x,y,z> = ProjectiveSpace(K, 2)
+            sage: X = P.subscheme(z)
+            sage: f = DynamicalSystem([x^2 - y^2, y^2, z^2], domain=X)
+            sage: p = X((-a + 1, 1, 0))
+            sage: f._is_preperiodic(p)
+            True
         """
-        if not is_ProjectiveSpace(self.codomain()):
-            raise NotImplementedError("must be over projective space")
-        if not self.is_morphism():
-            raise TypeError("must be a morphism")
+        codomain = self.codomain()
+        if not is_ProjectiveSpace(codomain):
+            # in order to calculate the canonical height, we need
+            # this map to be a morphism of projective space
+            ambient_space = codomain.ambient_space()
+            f = DynamicalSystem(self.defining_polynomials(), domain=ambient_space)
+            if not f.is_morphism():
+                raise ValueError('must be a morphism of projective space')
+        else:
+            f = self
+            if not f.is_morphism():
+                raise TypeError("must be a morphism")
         if not P.codomain() == self.domain():
             raise TypeError("point must be in domain of map")
 
-        h = self.canonical_height(P, error_bound = err)
+        # we calculate the canonical height without considering
+        # if the domain is a subscheme
+        h = f.canonical_height(P, error_bound = err)
         # we know canonical height 0 if and only if preperiodic
         # however precision issues can occur so we can only tell *not* preperiodic
         # if the value is larger than the error
@@ -6302,14 +6339,20 @@ def _is_preperiodic(self, P, err=0.1, return_period=False):
             # either we can find the cycle or the height is
             # larger than the difference between the canonical height
             # and the height, so the canonical height cannot be 0
-            B = self.height_difference_bound()
+            B = f.height_difference_bound()
             orbit = [P]
             n = 1 # to compute period
-            Q = self(P)
+            try:
+                Q = self(P)
+            except TypeError:
+                raise ValueError('orbit of point leaves domain')
             H = Q.global_height()
             while Q not in orbit and H <= B:
                 orbit.append(Q)
-                Q = self(Q)
+                try:
+                    Q = self(Q)
+                except TypeError:
+                    raise ValueError('orbit of point leaves domain')
                 H = Q.global_height()
                 n += 1
             if H <= B: #it must have been in the cycle
diff --git a/src/sage/schemes/projective/projective_point.py b/src/sage/schemes/projective/projective_point.py
--- a/src/sage/schemes/projective/projective_point.py
+++ b/src/sage/schemes/projective/projective_point.py
@@ -878,7 +878,7 @@ def is_preperiodic(self, f, err=0.1, return_period=False):
         r"""
         Determine if the point is preperiodic with respect to the map ``f``.
 
-        This is only implemented for projective space (not subschemes).
+        This is implemented for both projective space and subschemes.
         There are two optional keyword arguments:
         ``error_bound`` sets the error_bound used in the canonical height computation
         and ``return_period`` a boolean which controls if the period is returned if the
@@ -926,7 +926,16 @@ def is_preperiodic(self, f, err=0.1, return_period=False):
 
         ::
 
-            sage: P.<x,y> = ProjectiveSpace(QQ, 1)
+            sage: P.<x,y,z> = ProjectiveSpace(QQ, 2)
+            sage: X = P.subscheme(z)
+            sage: f = DynamicalSystem([x^2 - y^2, y^2, z^2], domain=X)
+            sage: p = X((-1, 1, 0))
+            sage: p.is_preperiodic(f, return_period=True)
+            (0, 2)
+
+        ::
+
+            sage: P.<x,y> = ProjectiveSpace(QQ,1)
             sage: f = DynamicalSystem_projective([x^2 - 29/16*y^2, y^2], domain=P)
             sage: Q = P(1, 4)
             sage: Q.is_preperiodic(f, return_period=True)


Test patch: