text: r"""
  Dynamical systems for products of projective spaces
  
  This class builds on the prouct projective space class.
  The main constructor functions are given by ``DynamicalSystem`` and
  ``DynamicalSystem_projective``. The constructors function can take either
  polynomials or a morphism from which to construct a dynamical system.
  
  The must be specified.
  
  EXAMPLES::
  
      sage: P1xP1.<x,y,u,v> = ProductProjectiveSpaces(QQ, [1, 1])
      sage: DynamicalSystem_projective([x^2*u, y^2*v, x*v^2, y*u^2], domain=P1xP1)
      Dynamical System of Product of projective spaces P^1 x P^1 over Rational Field
        Defn: Defined by sending (x : y , u : v) to
              (x^2*u : y^2*v , x*v^2 : y*u^2).
  """
  #*****************************************************************************
  # Copyright (C) 2014 Ben Hutz <bn4941@gmail.com>
  #
  # Distributed under the terms of the GNU General Public License (GPL)
  # as published by the Free Software Foundation; either version 2 of
  # the License, or (at your option) any later version.
  # http://www.gnu.org/licenses/
  #*****************************************************************************
  from copy import copy
  from sage.dynamics.arithmetic_dynamics.generic_ds import DynamicalSystem
  from sage.dynamics.arithmetic_dynamics.projective_ds import DynamicalSystem_projective
  from sage.rings.all import ZZ
  from sage.rings.quotient_ring import QuotientRing_generic
  from sage.schemes.product_projective.morphism import ProductProjectiveSpaces_morphism_ring
  
  
  class DynamicalSystem_product_projective(DynamicalSystem,
                                           ProductProjectiveSpaces_morphism_ring):
      r"""
      The class of dynamical systems on products of projective spaces.
  
      .. WARNING::
  
          You should not create objects of this class directly because
          no type or consistency checking is performed. The preferred
          method to construct such dynamical systems is to use
          :func:`~sage.dynamics.arithmetic_dynamics.generic_ds.DynamicalSystem_projective`
          function.
  
      INPUT:
  
      - ``polys`` -- a list of ``n_1 + \cdots + n_r`` multi-homogeneous polynomials, all
        of which should have the same parent
  
      - ``domain`` -- a projective scheme embedded in
        ``P^{n_1-1} \times \cdots \times P^{n_r-1}``
  
      EXAMPLES::
  
          sage: T.<x,y,z,w,u> = ProductProjectiveSpaces([2, 1], QQ)
          sage: DynamicalSystem_projective([x^2, y^2, z^2, w^2, u^2], domain=T)
          Dynamical System of Product of projective spaces P^2 x P^1 over Rational Field
                Defn: Defined by sending (x : y : z , w : u) to
                      (x^2 : y^2 : z^2 , w^2 : u^2).
      """
  
      def __init__(self, polys, domain):
          r"""
          The Python constructor.
  
          See :class:`DynamicalSystem` for details.
  
          EXAMPLES::
  
              sage: T.<x,y,w,u> = ProductProjectiveSpaces([1, 1], QQ)
              sage: DynamicalSystem_projective([x^2, y^2, w^2, u^2], domain=T)
              Dynamical System of Product of projective spaces P^1 x P^1 over Rational Field
                Defn: Defined by sending (x : y , w : u) to
                      (x^2 : y^2 , w^2 : u^2).
          """
          DynamicalSystem.__init__(self, polys, domain)
  
      def _call_with_args(self, P, check=True):
          r"""
          Make dynamical systems of products of projective spaces callable.
  
          INPUT:
  
          - ``P`` -- a point in the domain
  
          - ``check`` -- Boolean - whether or not to perform the input checks
            on the image point (Default: ``True``)
  
          OUTPUT: The image point in the codomain
  
          EXAMPLES::
  
              sage: T.<x,y,z,w,u> = ProductProjectiveSpaces([2, 1], QQ)
              sage: F = DynamicalSystem_projective([x^2*u, y^2*w, z^2*u, w^2, u^2], domain=T)
              sage: F(T([2, 1, 3, 0, 1]))
              (4/9 : 0 : 1 , 0 : 1)
          """
          if check:
              from sage.schemes.product_projective.point import ProductProjectiveSpaces_point_ring
              if not isinstance(P, ProductProjectiveSpaces_point_ring):
                  try:
                      P = self.domain()(P)
                  except (TypeError, NotImplementedError):
                      raise TypeError("%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(P, self.domain()))
              elif self.domain()!= P.codomain():
                  raise TypeError("%s fails to convert into the map's domain %s, but a `pushforward` method is not properly implemented"%(P, self.domain()))
  
          A = self.domain()
          Q = list(P)
          newP = [f(Q) for f in self.defining_polynomials()]
          return(A.point(newP, check))
  
      def nth_iterate(self, P, n, normalize=False):
          r"""
          Return the ``n``-th iterate of ``P`` by this dynamical system.
  
          If ``normalize`` is ``True``, then the coordinates are
          automatically normalized.
  
          .. TODO:: Is there a more efficient way to do this?
  
          INPUT:
  
          - ``P`` -- a point in ``self.domain()``
  
          - ``n`` -- a positive integer
  
          - ``normalize`` -- (default: ``False``) boolean
  
          OUTPUT: A point in ``self.codomain()``
  
          EXAMPLES::
  
              sage: Z.<a,b,x,y,z> = ProductProjectiveSpaces([1, 2], QQ)
              sage: f = DynamicalSystem_projective([a^3, b^3 + a*b^2, x^2, y^2 - z^2, z*y], domain=Z)
              sage: P = Z([1, 1, 1, 1, 1])
              sage: f.nth_iterate(P, 3)
              (1/1872 : 1 , 1 : 1 : 0)
  
          ::
  
              sage: Z.<a,b,x,y> = ProductProjectiveSpaces([1, 1], ZZ)
              sage: f = DynamicalSystem_projective([a*b, b^2, x^3 - y^3, y^2*x], domain=Z)
              sage: P = Z([2, 6, 2, 4])
              sage: f.nth_iterate(P, 2, normalize = True)
              (1 : 3 , 407 : 112)
          """
          if P.codomain() != self.domain():
              raise TypeError("point is not defined over domain of function")
          n = ZZ(n)
          if n < 0:
              raise TypeError("must be a forward orbit")
          if n == 0:
              return(self)
          else:
              Q = self(P)
              if normalize == True:
                  Q.normalize_coordinates()
              for i in range(2,n+1):
                  Q = self(Q)
                  if normalize == True:
                      Q.normalize_coordinates()
              return(Q)
  
      def orbit(self, P, N, **kwds):
          r"""
          Return the orbit of `P` by this dynamical system.
  
          Let `F` be this dynamical system. If `N` is an integer return `[P,F(P),\ldots,F^N(P)]`.
  
          If `N` is a list or tuple `N = [m, k]` return `[F^m(P),\ldots,F^k(P)]`.
          Automatically normalize the points if ``normalize == True``. Perform the checks on point initialize if
          ``check==True``.
  
          INPUT:
  
          - ``P`` -- a point in ``self.domain()``
  
          - ``N`` -- a non-negative integer or list or tuple of two non-negative integers
  
          kwds:
  
          - ``check`` -- (default: ``True``) boolean
  
          - ``normalize`` -- (default: ``False``) boolean
  
  
          OUTPUT: a list of points in ``self.codomain()``
  
          EXAMPLES::
  
              sage: Z.<a,b,x,y,z> = ProductProjectiveSpaces([1, 2], QQ)
              sage: f = DynamicalSystem_projective([a^3, b^3 + a*b^2, x^2, y^2 - z^2, z*y], domain=Z)
              sage: P = Z([1, 1, 1, 1, 1])
              sage: f.orbit(P, 3)
              [(1 : 1 , 1 : 1 : 1), (1/2 : 1 , 1 : 0 : 1), (1/12 : 1 , -1 : 1 : 0), (1/1872 : 1 , 1 : 1 : 0)]
  
          ::
  
              sage: Z.<a,b,x,y> = ProductProjectiveSpaces([1, 1], ZZ)
              sage: f = DynamicalSystem_projective([a*b, b^2, x^3 - y^3, y^2*x], domain=Z)
              sage: P = Z([2, 6, 2, 4])
              sage: f.orbit(P, 3, normalize=True)
              [(1 : 3 , 1 : 2), (1 : 3 , -7 : 4), (1 : 3 , 407 : 112), (1 : 3 , 66014215 : 5105408)]
          """
          if P.codomain() != self.domain():
              raise TypeError("point is not defined over domain of function")
          if not isinstance(N, (list,tuple)):
              N = [0, N]
          try:
              N[0] = ZZ(N[0])
              N[1] = ZZ(N[1])
          except TypeError:
              raise TypeError("orbit bounds must be integers")
          if N[0] < 0 or N[1] < 0:
              raise TypeError("orbit bounds must be non-negative")
          if N[0] > N[1]:
              return([])
  
          Q = copy(P)
          check = kwds.pop("check", True)
          normalize = kwds.pop("normalize", False)
  
          if normalize == True:
              Q.normalize_coordinates()
          for i in range(1, N[0]+1):
              Q = self(Q, check)
              if normalize == True:
                  Q.normalize_coordinates()
          orb = [Q]
          for i in range(N[0]+1, N[1]+1):
              Q = self(Q, check)
              if normalize == True:
                  Q.normalize_coordinates()
              orb.append(Q)
          return(orb)
  
      def nth_iterate_map(self, n):
          r"""
          Return the nth iterate of this dynamical system.
  
          ALGORITHM:
  
          Uses a form of successive squaring to reduce computations.
  
  
          .. TODO:: This could be improved.
  
          INPUT:
  
          - ``n`` -- a positive integer
  
          OUTPUT: A dynamical system of products of projective spaces
  
          EXAMPLES::
  
              sage: Z.<a,b,x,y,z> = ProductProjectiveSpaces([1 , 2], QQ)
              sage: f = DynamicalSystem_projective([a^3, b^3, x^2, y^2, z^2], domain=Z)
              sage: f.nth_iterate_map(3)
              Dynamical System of Product of projective spaces P^1 x P^2 over
              Rational Field
                Defn: Defined by sending (a : b , x : y : z) to
                      (a^27 : b^27 , x^8 : y^8 : z^8).
          """
          E = self.domain()
          D = int(n)
          if D < 0:
              raise TypeError("iterate number must be a nonnegative integer")
          N = sum([E.ambient_space()[i].dimension_relative() + 1 for i in range(E.ambient_space().num_components())])
          F = list(self._polys)
          Coord_ring = E.coordinate_ring()
          if isinstance(Coord_ring, QuotientRing_generic):
              PHI = [gen.lift() for gen in Coord_ring.gens()]
          else:
              PHI = list(Coord_ring.gens())
  
          while D:
              if D&1:
                  PHI = [poly(*F) for poly in PHI]
              if D > 1: #avoid extra iterate
                  F = [poly(*F) for poly in F] #'square'
              D >>= 1
          return DynamicalSystem_projective(PHI, domain=self.domain())
  