# sage_setup: distribution = sagemath-combinat
# sage.doctest: needs sage.combinat sage.modules
"""
Binary recurrence sequences

This class implements several methods relating to general linear binary
recurrence sequences, including a sieve to find perfect powers in integral
linear binary recurrence sequences.

EXAMPLES::

    sage: R = BinaryRecurrenceSequence(1,1)        #the Fibonacci sequence
    sage: R(137)        #the 137th term of the Fibonacci sequence
    19134702400093278081449423917
    sage: R(137) == fibonacci(137)                                                      # needs sage.libs.pari
    True
    sage: [R(i) % 4 for i in range(12)]
    [0, 1, 1, 2, 3, 1, 0, 1, 1, 2, 3, 1]
    sage: R.period(4)        #the period of the fibonacci sequence modulo 4
    6
    sage: R.pthpowers(2, 10**10)        # long time (7 seconds) -- in fact these are all squares, c.f. [BMS06]
    [0, 1, 2, 12]

    sage: S = BinaryRecurrenceSequence(8,1)  #a Lucas sequence
    sage: S.period(73)
    148
    sage: S(5) % 73 == S(5 +148) %73
    True
    sage: S.pthpowers(3, 10**10)    # long time (3 seconds) -- provably finds the indices of all 3rd powers less than 10^10
    [0, 1, 2]

    sage: T = BinaryRecurrenceSequence(2,0,1,2)
    sage: [T(i) for i in range(10)]
    [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
    sage: T.is_degenerate()
    True
    sage: T.is_geometric()
    True
    sage: T.pthpowers(7, 10**30)                                                        # needs sage.symbolic
    Traceback (most recent call last):
    ...
    ValueError: the degenerate binary recurrence sequence is geometric or quasigeometric
    and has many pth powers


AUTHORS:

- Isabel Vogt (2013): initial version

See [SV2013]_, [BMS2006]_, and [SS1983]_.
"""

# ****************************************************************************
#       Copyright (C) 2013 Isabel Vogt <ivogt161@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#                  https://www.gnu.org/licenses/
# ****************************************************************************


from sage.structure.sage_object import SageObject
from sage.rings.finite_rings.integer_mod_ring import Integers
from sage.rings.finite_rings.finite_field_constructor import GF
from sage.rings.integer import Integer
from sage.arith.functions import lcm
from sage.arith.misc import is_prime, next_prime, next_prime_power, legendre_symbol
from sage.misc.lazy_import import lazy_import
from sage.misc.functional import sqrt

lazy_import("sage.functions.log", "log")
lazy_import("sage.rings.number_field.number_field", "QuadraticField")
lazy_import("sage.matrix.constructor", "matrix")
lazy_import("sage.modules.free_module_element", "vector")


class BinaryRecurrenceSequence(SageObject):
    """
    Create a linear binary recurrence sequence defined by initial conditions
    `u_0` and `u_1` and recurrence relation `u_{n+2} = b*u_{n+1}+c*u_n`.

    INPUT:

    - ``b`` -- integer; (partially determining the recurrence relation)

    - ``c`` -- integer; (partially determining the recurrence relation)

    - ``u0`` -- integer; (the `0`-th term of the binary recurrence sequence)

    - ``u1`` -- integer; (the `1`-st term of the binary recurrence sequence)

    OUTPUT: an integral linear binary recurrence sequence defined by `u_0`,
    `u_1`, and `u_{n+2} = b u_{n+1}+c u_n`

    .. SEEALSO::

        :func:`fibonacci`, :func:`lucas_number1`, :func:`lucas_number2`

    EXAMPLES::

        sage: R = BinaryRecurrenceSequence(3,3,2,1)
        sage: R
        Binary recurrence sequence defined by: u_n = 3 * u_{n-1} + 3 * u_{n-2};
        With initial conditions: u_0 = 2, and u_1 = 1
    """

    def __init__(self, b, c, u0=0, u1=1):
        """
        See :class:`BinaryRecurrenceSequence` for full documentation.

        EXAMPLES::

            sage: R = BinaryRecurrenceSequence(3,3,2,1)
            sage: R
            Binary recurrence sequence defined by: u_n = 3 * u_{n-1} + 3 * u_{n-2};
            With initial conditions: u_0 = 2, and u_1 = 1

            sage: R = BinaryRecurrenceSequence(1,1)
            sage: loads(R.dumps()) == R
            True
        """
        self.b = b
        self.c = c
        self.u0 = u0
        self.u1 = u1
        self._period_dict = {}  # dictionary to cache the period of a sequence for future lookup
        self._PGoodness = {}  # dictionary to cache primes that are "good" by some prime power
        self._ell = 1  # variable that keeps track of the last prime power to be used as a goodness

    def __repr__(self) -> str:
        """
        Give string representation of the class.

        EXAMPLES::

            sage: R = BinaryRecurrenceSequence(3,3,2,1)
            sage: R
            Binary recurrence sequence defined by: u_n = 3 * u_{n-1} + 3 * u_{n-2};
            With initial conditions: u_0 = 2, and u_1 = 1
        """
        return 'Binary recurrence sequence defined by: u_n = ' + str(self.b) + ' * u_{n-1} + ' + str(self.c) + ' * u_{n-2};\nWith initial conditions: u_0 = ' + str(self.u0) + ', and u_1 = ' + str(self.u1)

    def __eq__(self, other) -> bool:
        """
        Compare two binary recurrence sequences.

        EXAMPLES::

            sage: R = BinaryRecurrenceSequence(3,3,2,1)
            sage: S = BinaryRecurrenceSequence(3,3,2,1)
            sage: R == S
            True

            sage: T = BinaryRecurrenceSequence(3,3,2,2)
            sage: R == T
            False
        """
        return (self.u0 == other.u0) and (self.u1 == other.u1) and (self.b == other.b) and (self.c == other.c)

    def __call__(self, n, modulus=0):
        """
        Give the `n`-th term of a binary recurrence sequence, possibly mod some modulus.

        INPUT:

        - ``n`` -- integer; the index of the term in the binary recurrence sequence

        - ``modulus`` -- a natural number (optional --  default value is 0)

        OUTPUT:

        - An integer (the `n`-th term of the binary recurrence sequence modulo ``modulus``)

        EXAMPLES::

            sage: R = BinaryRecurrenceSequence(3,3,2,1)
            sage: R(2)
            9
            sage: R(101)
            16158686318788579168659644539538474790082623100896663971001
            sage: R(101,12)
            9
            sage: R(101)%12
            9
        """
        R = Integers(modulus)
        F = matrix(R, [[0, 1], [self.c, self.b]])
        # F*[u_{n}, u_{n+1}]^T = [u_{n+1}, u_{n+2}]^T (T indicates transpose).
        v = vector(R, [self.u0, self.u1])
        return list(F**n * v)[0]

    def is_degenerate(self) -> bool:
        """
        Decide whether the binary recurrence sequence is degenerate.

        Let `\\alpha` and `\\beta` denote the roots of the characteristic polynomial
        `p(x) = x^2-bx -c`.  Let `a = u_1-u_0\\beta/(\\beta - \\alpha)` and
        `b = u_1-u_0\\alpha/(\\beta - \\alpha)`.  The sequence is, thus, given by
        `u_n = a \\alpha^n - b\\beta^n`.  Then we say that the sequence is nondegenerate
        if and only if `a*b*\\alpha*\\beta \\neq 0` and `\\alpha/\\beta` is not a
        root of unity.

        More concretely, there are 4 classes of degeneracy, that can all be formulated
        in terms of the matrix `F = [[0,1], [c, b]]`.

        - `F` is singular -- this corresponds to ``c`` = 0, and thus
          `\\alpha*\\beta = 0`. This sequence is geometric after term ``u0``
          and so we call it ``quasigeometric``

        - `v = [[u_0], [u_1]]` is an eigenvector of `F` -- this corresponds to
          a ``geometric`` sequence with `a*b = 0`

        - `F` is nondiagonalizable -- this corresponds to `\\alpha = \\beta`.
          This sequence will be the point-wise product of an arithmetic and
          geometric sequence.

        - `F^k` is scalar, for some `k>1` -- this corresponds to
          `\\alpha/\\beta` a `k` th root of unity. This sequence is a union of
          several geometric sequences, and so we again call it ``quasigeometric``.

        EXAMPLES::

            sage: S = BinaryRecurrenceSequence(0,1)
            sage: S.is_degenerate()
            True
            sage: S.is_geometric()
            False
            sage: S.is_quasigeometric()
            True

            sage: R = BinaryRecurrenceSequence(3,-2)
            sage: R.is_degenerate()
            False

            sage: T = BinaryRecurrenceSequence(2,-1)
            sage: T.is_degenerate()
            True
            sage: T.is_arithmetic()
            True
        """
        D = self.b**2 + 4 * self.c
        if D != 0:
            if D.is_square():
                A = sqrt(D)
            else:
                A = QuadraticField(D, 'x').gen()

            aa = (self.u1 - self.u0 * (self.b + A)/2)/(A)     # called `a` in Docstring
            bb = (self.u1 - self.u0 * (self.b - A)/2)/(A)     # called `b` in Docstring

            # (b+A)/2 is called alpha in Docstring, (b-A)/2 is called beta in Docstring

            if self.b != A:
                if ((self.b+A)/(self.b-A))**6 == 1:
                    return True
            else:
                return True

            return aa*bb*(self.b + A)*(self.b - A) == 0

        return True

    def is_geometric(self) -> bool:
        """
        Decide whether the binary recurrence sequence is geometric - ie a geometric sequence.

        This is a subcase of a degenerate binary recurrence sequence, for which `ab=0`, i.e.
        `u_{n}/u_{n-1}=r` for some value of `r`.

        See :meth:`is_degenerate` for a description of
        degeneracy and definitions of `a` and `b`.

        EXAMPLES::

            sage: S = BinaryRecurrenceSequence(2,0,1,2)
            sage: [S(i) for i in range(10)]
            [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
            sage: S.is_geometric()
            True
        """
        # If [u_0, u_1]^T is an eigenvector for the incrementation matrix F = [[0,1],[c,b]], then the sequence
        # is geometric, ie we can write u_n = a*r^n for some a and r.

        # We decide if u0, u1, u2 = b*u1+c*u0 are in geometric progression by whether u1^2 = (b*u1+c*u0)*u0

        return (self.u1)**2 == (self.b*self.u1 + self.c*self.u0)*self.u0

    def is_quasigeometric(self) -> bool:
        """
        Decide whether the binary recurrence sequence is degenerate and similar to a geometric sequence,
        i.e. the union of multiple geometric sequences, or geometric after term ``u0``.

        If `\\alpha/\\beta` is a `k` th root of unity, where `k>1`, then necessarily `k = 2, 3, 4, 6`.
        Then `F = [[0,1],[c,b]` is diagonalizable, and `F^k = [[\\alpha^k, 0], [0,\\beta^k]]` is a diagonal
        matrix.  Thus for all values of `j` mod `k`, the `j` mod `k` terms of `u_n` form a geometric
        series.

        If `\\alpha` or `\\beta` is zero, this implies that `c=0`.  This is the case when `F` is
        singular.  In this case, `u_1, u_2, u_3, ...` is geometric.

        EXAMPLES::

            sage: S = BinaryRecurrenceSequence(0,1)
            sage: [S(i) for i in range(10)]
            [0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
            sage: S.is_quasigeometric()
            True

            sage: R = BinaryRecurrenceSequence(3,0)
            sage: [R(i) for i in range(10)]
            [0, 1, 3, 9, 27, 81, 243, 729, 2187, 6561]
            sage: R.is_quasigeometric()
            True
        """
        # First test if F is singular... i.e. beta = 0
        if self.c == 0:
            return True

        # Otherwise test if alpha/beta is a root of unity that is not 1
        D = self.b**2 + 4 * self.c
        if D != 0:    # thus alpha/beta != 1
            if D.is_square():
                A = sqrt(D)
            else:
                A = QuadraticField(D, 'x').gen()
            if ((self.b+A)/(self.b-A))**6 == 1:
                return True

        return False

    def is_arithmetic(self) -> bool:
        """
        Decide whether the sequence is degenerate and an arithmetic sequence.

        The sequence is arithmetic if and only if `u_1 - u_0 = u_2 - u_1 = u_3 - u_2`.

        This corresponds to the matrix `F = [[0,1],[c,b]]` being nondiagonalizable
        and `\\alpha/\\beta = 1`.

        EXAMPLES::

            sage: S = BinaryRecurrenceSequence(2,-1)
            sage: [S(i) for i in range(10)]
            [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
            sage: S.is_arithmetic()
            True
        """
        return self(1) - self(0) == self(2) - self(1) == self(3) - self(2)

    def period(self, m, *, eventual=False):
        """
        Return the period of the binary recurrence sequence modulo
        an integer ``m``.

        If `n_1` is congruent to `n_2` modulo ``period(m)``, then `u_{n_1}` is
        is congruent to `u_{n_2}` modulo ``m``.

        INPUT:

        - ``m`` -- integer; modulo which the period of the recurrence relation is calculated

        - ``eventual`` -- boolean (default: ``False``); if ``True``, allow the
          sequence to be eventually periodic, rather than requiring it to be
          purely periodic. So `n_1` might not be congruent to `n_2` modulo
          ``period(m)`` unless `n_1` and `n_2` are large.

        OUTPUT: integer (the period of the sequence modulo m)

        EXAMPLES:

        If `p = \\pm 1 \\mod 5`, then the period of the Fibonacci sequence
        mod `p` is `p-1` (c.f. Lemma 3.3 of [BMS2006]_).

        ::

            sage: R = BinaryRecurrenceSequence(1,1)
            sage: R.period(31)
            30

            sage: [R(i) % 4 for i in range(12)]
            [0, 1, 1, 2, 3, 1, 0, 1, 1, 2, 3, 1]
            sage: R.period(4)
            6

        This function works for degenerate sequences as well.

        ::

            sage: S = BinaryRecurrenceSequence(2,0,1,2)
            sage: S.is_degenerate()
            True
            sage: S.is_geometric()
            True
            sage: [S(i) % 17 for i in range(16)]
            [1, 2, 4, 8, 16, 15, 13, 9, 1, 2, 4, 8, 16, 15, 13, 9]
            sage: S.period(17)
            8

        Letting ``eventual`` be ``True`` allows us to find the period of a
        sequence that is not purely periodic. ::

            sage: T = BinaryRecurrenceSequence(5,12,u0=0,u1=1)
            sage: [T(n) % 10 for n in range(20)]
            [0, 1, 5, 7, 5, 9, 5, 3, 5, 1, 5, 7, 5, 9, 5, 3, 5, 1, 5, 7]
            sage: T.period(10)
            Traceback (most recent call last):
            ...
            ValueError: Binary recurrence sequence modulo 10 is not a purely
            periodic sequence.
            sage: T.period(10,eventual=True)
            8

        .. NOTE:: The answer is cached.

        TESTS:

        Verify that :issue:`38112` is fixed::

            sage: T = BinaryRecurrenceSequence(3,2,u0=0,u1=1)
            sage: [T(n) % 4 for n in range(5)]
            [0, 1, 3, 3, 3]
            sage: T.period(4)
            Traceback (most recent call last):
            ...
            ValueError: Binary recurrence sequence modulo 4 is not a purely
            periodic sequence.
            sage: T.period(4,eventual=True)
            1
        """

        # If we have already computed the period mod m, then we return the stored value.

        if m in self._period_dict:
            return self._period_dict[m]

        else:
            R = Integers(m)
            A = matrix(R, [[0, 1], [self.c, self.b]])
            w = vector(R, [self.u0, self.u1])
            Fac = list(m.factor())
            Periods = {}

            if eventual is True:
                # There are only m^2 possible pairs modulo m. Since the numbering
                # of the sequence starts at 0, this implies that the term numbered
                # m^2 must be in periodic part of the sequence. Hence, the
                # sequence starting with the terms numbered m^2 and m^2 + 1 must
                # be purely periodic.
                an = (A**(m**2)) * w
                return BinaryRecurrenceSequence(self.b, self.c,
                    an[0], an[1]).period(m, eventual=False)

            # To compute the period mod m, we compute the least integer n such that A^n*w == w.  This necessarily
            # divides the order of A as a matrix in GL_2(Z/mZ).

            # We compute the period modulo all distinct prime powers dividing m, and combine via the lcm.
            # To compute the period mod p^e, we first compute the order mod p.  Then the period mod p^e
            # must divide p^{4e-4}*period(p), as the subgroup of matrices mod p^e, which reduce to
            # the identity mod p is of order (p^{e-1})^4.  So we compute the period mod p^e by successively
            # multiplying the period mod p by powers of p.

            for p, e in Fac:
                # first compute the period mod p
                if p in self._period_dict:
                    perp = self._period_dict[p]
                else:
                    F = A.change_ring(GF(p))
                    v = w.change_ring(GF(p))
                    FF = F**(p-1)
                    p1fac = list((p-1).factor())

                    # The order of any matrix in GL_2(F_p) either divides p(p-1) or (p-1)(p+1).
                    # The order divides p-1 if it is diagonalizable.  In any case, det(F^(p-1))=1,
                    # so if tr(F^(p-1)) = 2, then it must be triangular of the form [[1,a],[0,1]].
                    # The order of the subgroup of matrices of this form is p, so the order must divide
                    # p(p-1) -- in fact it must be a multiple of p.  If this is not the case, then the
                    # order divides (p-1)(p+1).  As the period divides the order of the matrix in GL_2(F_p),
                    # these conditions hold for the period as well.

                    # check if the order divides (p-1)
                    if FF*v == v:
                        M = p-1
                        Mfac = p1fac

                    # check if the trace is 2, then the order is a multiple of p dividing p*(p-1)
                    elif FF.trace() == 2:
                        M = p-1
                        Mfac = p1fac
                        F = F**p        # replace F by F^p as now we only need to determine the factor dividing (p-1)

                    # otherwise it will divide (p+1)(p-1)
                    else:
                        M = (p+1)*(p-1)
                        p2fac = list((p+1).factor())        # factor the (p+1) and (p-1) terms separately and then combine for speed
                        Mfac_dic = {}
                        for i0, i1 in list(p1fac + p2fac):
                            if i0 not in Mfac_dic:
                                Mfac_dic[i0] = i1
                            else:
                                Mfac_dic[i0] += i1
                        Mfac = list(Mfac_dic.items())

                    # Now use a fast order algorithm to compute the period.  We know that the period divides
                    # M = i_1*i_2*...*i_l where the i_j denote not necessarily distinct prime factors.  As
                    # F^M*v == v, for each i_j, if F^(M/i_j)*v == v, then the period divides (M/i_j).  After
                    # all factors have been iterated over, the result is the period mod p.

                    Mfac = list(Mfac)

                    # expand the list of prime factors so every factor is with multiplicity 1
                    C = [i0 for i0, i1 in Mfac for _ in range(i1)]

                    Mfac = C
                    n = M
                    for ii in Mfac:
                        b = n // ii
                        if F**b * v == v:
                            n = b
                    perp = n

                # Now compute the period mod p^e by stepping up by multiples of p
                F = A.change_ring(Integers(p**e))
                v = w.change_ring(Integers(p**e))
                FF = F**perp
                if FF*v == v:
                    perpe = perp
                else:
                    tries = 0
                    while True:
                        tries += 1
                        FF = FF**p
                        if FF*v == v:
                            perpe = perp*p**tries
                            break
                        if tries > e:
                            raise ValueError("Binary recurrence sequence " +
                                             f"modulo {m} is not a purely " +
                                             "periodic sequence.")
                Periods[p] = perpe

            # take the lcm of the periods mod all distinct primes dividing m
            period = lcm(Periods.values())

            self._period_dict[m] = period        # cache the period mod m
            return period

    def pthpowers(self, p, Bound):
        """
        Find the indices of proveably all `p`-th powers in the recurrence sequence
        bounded by Bound.

        Let `u_n` be a binary recurrence sequence.  A ``p`` th power in `u_n`
        is a solution to `u_n = y^p` for some integer `y`.  There are only
        finitely many ``p`` th powers in any recurrence sequence [SS1983]_.

        INPUT:

        - ``p`` -- a rational prime integer (the fixed p in `u_n = y^p`)

        - ``Bound`` -- a natural number (the maximum index `n` in `u_n = y^p` that is checked)

        OUTPUT:

        A list of the indices of all ``p`` th powers less bounded by
        ``Bound``. If the sequence is degenerate and there are many
        ``p`` th powers, raises :exc:`ValueError`.

        EXAMPLES::

            sage: R = BinaryRecurrenceSequence(1,1)        #the Fibonacci sequence
            sage: R.pthpowers(2, 10**10)        # long time (7 seconds) -- in fact these are all squares, c.f. [BMS2006]_
            [0, 1, 2, 12]

            sage: S = BinaryRecurrenceSequence(8,1) #a Lucas sequence
            sage: S.pthpowers(3,10**10)    # long time (3 seconds) -- provably finds the indices of all 3rd powers less than 10^10
            [0, 1, 2]

            sage: Q = BinaryRecurrenceSequence(3,3,2,1)
            sage: Q.pthpowers(11,10**10)          # long time (7.5 seconds)
            [1]

        If the sequence is degenerate, and there are no ``p`` th powers, returns `[]`.  Otherwise, if
        there are many ``p`` th powers, raises :exc:`ValueError`.

        ::

            sage: T = BinaryRecurrenceSequence(2,0,1,2)
            sage: T.is_degenerate()
            True
            sage: T.is_geometric()
            True
            sage: T.pthpowers(7, 10**30)                                                # needs sage.symbolic
            Traceback (most recent call last):
            ...
            ValueError: the degenerate binary recurrence sequence is geometric or
            quasigeometric and has many pth powers

            sage: L = BinaryRecurrenceSequence(4,0,2,2)
            sage: [L(i).factor() for i in range(10)]
            [2, 2, 2^3, 2^5, 2^7, 2^9, 2^11, 2^13, 2^15, 2^17]
            sage: L.is_quasigeometric()
            True
            sage: L.pthpowers(2, 10**30)                                                # needs sage.symbolic
            []

        .. NOTE::

            This function is primarily optimized in the range where
            ``Bound`` is much larger than ``p``.
        """
        # Thanks to Jesse Silliman for helpful conversations!

        # Reset the dictionary of good primes, as this depends on p
        self._PGoodness = {}
        # Starting lower bound on good primes
        self._ell = 1

        # If the sequence is geometric, then the `n`th term is `a*r^n`.  Thus the
        # property of being a ``p`` th power is periodic mod ``p``.  So there are either
        # no ``p`` th powers if there are none in the first ``p`` terms, or many if there
        # is at least one in the first ``p`` terms.

        if self.is_geometric() or self.is_quasigeometric():
            no_powers = True
            for i in range(1, 6*p+1):
                if _is_p_power(self(i), p):
                    no_powers = False
                    break
            if no_powers:
                if _is_p_power(self.u0, p):
                    return [0]
                return []
            else:
                raise ValueError("the degenerate binary recurrence sequence is geometric or quasigeometric and has many pth powers")

        # If the sequence is degenerate without being geometric or quasigeometric, there
        # may be many ``p`` th powers or no ``p`` th powers.

        elif (self.b**2+4*self.c) == 0:

            # This is the case if the matrix F is not diagonalizable, ie b^2 +4c = 0, and alpha/beta = 1.

            alpha = self.b/2

            # In this case, u_n = u_0*alpha^n + (u_1 - u_0*alpha)*n*alpha^(n-1) = alpha^(n-1)*(u_0 +n*(u_1 - u_0*alpha)),
            # that is, it is a geometric term (alpha^(n-1)) times an arithmetic term (u_0 + n*(u_1-u_0*alpha)).

            # Look at classes n = k mod p, for k = 1,...,p.

            for k in range(1, p + 1):

                # The linear equation alpha^(k-1)*u_0 + (k+pm)*(alpha^(k-1)*u1 - u0*alpha^k)
                # must thus be a pth power.  This is a linear equation in m, namely, A + B*m, where

                A = (alpha**(k-1)*self.u0 + k*(alpha**(k-1)*self.u1 - self.u0*alpha**k))
                B = p*(alpha**(k-1)*self.u1 - self.u0*alpha**k)

                # This linear equation represents a pth power iff A is a pth power mod B.

                if _is_p_power_mod(A, p, B):
                    raise ValueError("the degenerate binary recurrence sequence has many pth powers")
            return []

        # We find ``p`` th powers using an elementary sieve.  Term `u_n` is a ``p`` th
        # power if and only if it is a ``p`` th power modulo every prime `\\ell`.  This condition
        # gives nontrivial information if ``p`` divides the order of the multiplicative group of
        # `\\Bold(F)_{\\ell}`, i.e. if `\\ell` is ` 1 \mod{p}`, as then only `1/p` terms are ``p`` th
        # powers modulo `\\ell``.

        # Thus, given such an `\\ell`, we get a set of necessary congruences for the index modulo the
        # the period of the sequence mod `\\ell`.  Then we intersect these congruences for many primes
        # to get a tight list modulo a growing modulus.  In order to keep this step manageable, we
        # only use primes `\\ell` that have particularly smooth periods.

        # Some congruences in the list will remain as the modulus grows.  If a congruence remains through
        # 7 rounds of increasing the modulus, then we check if this corresponds to a perfect power (if
        # it does, we add it to our list of indices corresponding to ``p`` th powers).  The rest of the congruences
        # are transient and grow with the modulus.  Once the smallest of these is greater than the bound,
        # the list of known indices corresponding to ``p`` th powers is complete.

        else:

            if Bound < 3 * p:

                powers = []
                ell = p + 1

                while not is_prime(ell):
                    ell += p

                F = GF(ell)
                a0 = F(self.u0)
                a1 = F(self.u1)  # a0 and a1 are variables for terms in sequence
                bf, cf = F(self.b), F(self.c)

                for n in range(Bound):  # n is the index of the a0

                    # Check whether a0 is a perfect power mod ell
                    if _is_p_power_mod(a0, p, ell):
                        # if a0 is a perfect power mod ell, check if nth term is ppower
                        if _is_p_power(self(n), p):
                            powers.append(n)

                    a0, a1 = a1, bf*a1 + cf*a0      # step up the variables

            else:

                powers = []      # documents the indices of the sequence that provably correspond to pth powers
                cong = [0]       # list of necessary congruences on the index for it to correspond to pth powers
                Possible_count = {}   # keeps track of the number of rounds a congruence lasts in cong

                # These parameters are involved in how we choose primes to increase the modulus
                qqold = 1        # we believe that we know complete information coming from primes good by qqold
                M1 = 1            # we have congruences modulo M1, this may not be the tightest list
                M2 = p            # we want to move to have congruences mod M2
                qq = 1            # the largest prime power divisor of M1 is qq

                # This loop ups the modulus.
                while True:

                    # Try to get good data mod M2

                    # patience of how long we should search for a "good prime"
                    patience = 0.01 * _estimated_time(lcm(M2, p * next_prime_power(qq)),
                                                      M1, len(cong), p)
                    tries = 0

                    # This loop uses primes to get a small set of congruences mod M2.
                    while True:

                        # only proceed if took less than patience time to find the next good prime
                        ell = _next_good_prime(p, self, qq, patience, qqold)
                        if ell:

                            # gather congruence data for the sequence mod ell, which will be mod period(ell) = modu
                            cong1, modu = _find_cong1(p, self, ell)

                            # makes a new list from cong that is now mod M = lcm(M1, modu) instead of M1
                            M = lcm(M1, modu)
                            CongNew = [k * M1 + i for k in range(M // M1)
                                       for i in cong]
                            cong = set(CongNew)

                            M1 = M

                            killed_something = False        # keeps track of when cong1 can rule out a congruence in cong

                            # CRT by hand to gain speed
                            for i in list(cong):
                                if i % modu not in cong1:     # congruence in cong is inconsistent with any in cong1
                                    cong.remove(i)            # remove that congruence
                                    killed_something = True

                            if M1 == M2:
                                if not killed_something:
                                    tries += 1
                                    if tries == 2:            # try twice to rule out congruences
                                        cong = list(cong)
                                        qqold = qq
                                        qq = next_prime_power(qq)
                                        M2 = lcm(M2, p * qq)
                                        break

                        else:
                            qq = next_prime_power(qq)
                            M2 = lcm(M2, p * qq)
                            cong = list(cong)
                            break

                    # Document how long each element of cong has been there
                    for i in cong:
                        if i in Possible_count:
                            Possible_count[i] += 1
                        else:
                            Possible_count[i] = 1

                    # Check how long each element has persisted, if it is for at least 7 cycles,
                    # then we check to see if it is actually a perfect power
                    for i, pci in Possible_count.items():
                        if pci == 7:
                            n = Integer(i)
                            if n < Bound:
                                if _is_p_power(self(n), p):
                                    powers.append(n)

                    # check for a contradiction
                    if len(cong) > len(powers):
                        if cong[len(powers)] > Bound:
                            break
                    elif M1 > Bound:
                        break

            return powers


def _prime_powers(N):
    r"""
    Find the prime powers dividing `N`.

    In other words, if `N = q_1^{e_1} q_2^{e_2} \cdots q_n^{e_n}`, it returns
    `[q_1^{e_1}, q_2^{e_2}, \ldots, q_n^{e_n}]`.

    INPUT:

    - ``N`` -- integer

    OUTPUT: list of the prime powers dividing N

    EXAMPLES::

        sage: sage.combinat.binary_recurrence_sequences._prime_powers(124656)
        [3, 16, 49, 53]

        sage: sage.combinat.binary_recurrence_sequences._prime_powers(65537)
        [65537]
    """
    return sorted(i**j for i, j in N.factor())


def _largest_ppower_divisor(N):
    """
    Find the largest prime power divisor of `N`.

    INPUT:

    - ``N`` -- integer

    OUTPUT: the largest prime power dividing `N`

    EXAMPLES::

        sage: sage.combinat.binary_recurrence_sequences._largest_ppower_divisor(124656)
        53
        sage: sage.combinat.binary_recurrence_sequences._largest_ppower_divisor(65537)
        65537
    """
    return _prime_powers(N)[-1]


def _goodness(n, R, p):
    """
    Return the goodness of `n` for the sequence `R` and the prime `p` -- that is the largest
    non-`p` prime power dividing ``period(n)``.

    INPUT:

    - ``n`` -- an integer

    - ``R`` -- an object in the class ``BinaryRecurrenceSequence``

    - ``p`` -- a rational prime

    OUTPUT:

    - An integer which is the "goodness" of ``n``, i.e. the largest non-``p`` prime power dividing ``period(n)``.

    EXAMPLES::

        sage: R = BinaryRecurrenceSequence(11,2)
        sage: sage.combinat.binary_recurrence_sequences._goodness(89,R,7)
        11

        sage: R = BinaryRecurrenceSequence(1,1)
        sage: sage.combinat.binary_recurrence_sequences._goodness(13,R,7)
        4
        sage: R.period(13)        #the period of R mod 13 is divisible by 7
        28
    """
    # The period of R mod ell
    K = R.period(n)
    return _largest_ppower_divisor(K // K.gcd(p))


def _next_good_prime(p, R, qq, patience, qqold):
    r"""
    Find the next prime `\ell` which is good by ``qq`` but not by ``qqold``, 1 mod ``p``, and for which
    ``b^2+4*c`` is a square mod `\ell`, for the sequence ``R`` if it is possible in runtime patience.

    INPUT:

    - ``p`` -- a prime

    - ``R`` -- an object in the class ``BinaryRecurrenceSequence``

    - ``qq`` -- a perfect power

    - ``patience`` -- a real number

    - ``qqold`` -- a perfect power less than or equal to ``qq``

    OUTPUT: a prime `\ell` such that `\ell` is 1 mod `p`, `b^2+4 c` is a
    square mod `\ell` and the period of `\ell` has ``goodness`` by ``qq`` but
    not ``qqold``, if patience has not be surpased; otherwise ``False``

    EXAMPLES::

        sage: # needs sage.libs.pari
        sage: R = BinaryRecurrenceSequence(1,1)
        sage: sage.combinat.binary_recurrence_sequences._next_good_prime(7,R,1,100,1)        #ran out of patience to search for good primes
        False
        sage: sage.combinat.binary_recurrence_sequences._next_good_prime(7,R,2,100,1)
        29
        sage: sage.combinat.binary_recurrence_sequences._next_good_prime(7,R,2,100,2)        #ran out of patience, as qqold == qq, so no primes work
        False
    """
    # We are looking for pth powers in R.
    # Our primes must be good by qq, but not qqold.
    # We only allow patience number of iterations to find a good prime.

    # The variable _ell for R keeps track of the last "good" prime returned
    # that was not found from the dictionary _PGoodness

    # First, we check to see if we have already computed the goodness of a prime that fits
    # our requirement of being good by qq but not by qqold.  This is stored in the _PGoodness
    # dictionary.

    # Then if we have, we return the smallest such prime and delete it from the list.  If not, we
    # search through patience number of primes R._ell to find one good by qq but not qqold.  If it is
    # not good by either qqold or qq, then we add this prime to R._PGoodness under its goodness.

    # Possible_Primes keeps track of possible primes satisfying our goodness requirements we might return
    # check to see if anything in R._PGoodness fits our goodness requirements
    Possible_Primes = [item[0] for j, item in R._PGoodness.items()
                       if qqold < j <= qq and item]

    # If we found good primes, we take the smallest
    if Possible_Primes:
        q = min(Possible_Primes)
        n = _goodness(q, R, p)
        del R._PGoodness[n][0]   # if we are going to use it, then we delete it from R._PGoodness
        return q

    # If nothing is already stored in R._PGoodness, we start (from where we left off at R._ell) checking
    # for good primes.  We only tolerate patience number of tries before giving up.
    else:
        i = 0
        while i < patience:
            i += 1
            R._ell = next_prime(R._ell)

            # we require that R._ell is 1 mod p, so that p divides the order of the multiplicative
            # group mod R._ell, so that not all elements of GF(R._ell) are pth powers.
            if R._ell % p == 1:

                # requiring that b^2 + 4c is a square in GF(R._ell) ensures that the period mod R._ell
                # divides R._ell - 1
                if legendre_symbol(R.b**2 + 4*R.c, R._ell) == 1:

                    N = _goodness(R._ell, R, p)

                    # proceed only if R._ell satisfies the goodness requirements
                    if qqold < N <= qq:
                        return R._ell

                    # if we do not use the prime, we store it in R._PGoodness
                    else:
                        if N in R._PGoodness:
                            R._PGoodness[N].append(R._ell)
                        else:
                            R._PGoodness[N] = [R._ell]

        return False


def _is_p_power_mod(a, p, N):
    """
    Determine if ``a`` is a ``p`` th power modulo ``N``.

    By the CRT, this is equivalent to the condition that ``a`` be a ``p`` th power mod all
    distinct prime powers dividing ``N``.  For each of these, we use the strong statement of
    Hensel's lemma to lift ``p`` th powers mod `q` or `q^2` or `q^3` to ``p`` th powers mod `q^e`.

    INPUT:

    - ``a`` -- integer

    - ``p`` -- a rational prime number

    - ``N`` -- positive integer

    OUTPUT: ``True`` if `a` is a `p`-th power modulo `N`; ``False`` otherwise

    EXAMPLES::

        sage: sage.combinat.binary_recurrence_sequences._is_p_power_mod(2**3,7,29)
        False
        sage: sage.combinat.binary_recurrence_sequences._is_p_power_mod(2**3,3,29)
        True
    """
    # By the chinese remainder theorem, we can answer this question by examining whether
    # a is a pth power mod q^e, for all distinct prime powers q^e dividing N.

    for q, e in N.factor():

        # If a = q^v*x, with

        v = a.valuation(q)

        # then if v>=e, a is congruent to 0 mod q^e and is thus a pth power trivially.

        if v >= e:
            continue

        # otherwise, it can only be a pth power if v is a multiple of p.
        if v % p:
            return False

        # in this cse it is a pth power if x is a pth power mod q^(e-v), so let x = aa,
        # and (e-v) = ee:

        aa = a / q**v
        ee = e - v

        # The above steps are equivalent to the statement that we may assume a and qq are
        # relatively prime, if we replace a with aa and e with ee.  Now we must determine when
        # aa is a pth power mod q^ee for (aa,q)=1.

        # If q != p, then by Hensel's lemma, we may lift a pth power mod q, to a pth power
        # mod q^2, etc.

        if q != p:

            # aa is necessarily a pth power mod q if p does not divide the order of the multiplicative
            # group mod q, ie if q is not 1 mod p.

            if q % p == 1:

                # otherwise aa if a pth power mod q iff aa^(q-1)/p == 1

                if GF(q)(aa)**((q - 1) / p) != 1:
                    return False

        # If q = p and ee = 1, then everything is a pth power p by Fermat's little theorem.

        elif ee > 1:

            # We use the strong statement of Hensel's lemma, which implies that if p is odd
            # and aa is a pth power mod p^2, then aa is a pth power mod any higher power of p

            if p % 2:

                # ZZ/(p^2)ZZ^\times is abstractly isomorphic to ZZ/(p)ZZ cross ZZ/(p-1)ZZ. then
                # aa is a pth power mod p^2 if (aa)^(p*(p-1)/p) == 1, ie if aa^(p-1) == 1.

                if Integers(p**2)(aa)**(p - 1) != 1:
                    return False

            # Otherwise, p=2.  By the strong statement of Hensel's lemma, if aa is a pth power
            # mod p^3, then it is a pth power mod higher powers of p.  So we need only check if it
            # is a pth power mod p^2 and p^3.

            elif ee == 2:

                # all odd squares a 1 mod 4

                if aa % 4 != 1:
                    return False

            # all odd squares are 1 mod 8

            elif aa % 8 != 1:
                return False

    return True


def _estimated_time(M2, M1, length, p):
    """
    Find the estimated time to extend congruences mod M1 to consistent congruences mod M2.

    INPUT:

    - ``M2`` -- integer; (the new modulus)

    - ``M1`` -- integer; (the old modulus)

    - ``length`` -- list (the current length of the list of congruences mod ``M1``)

    - ``p`` -- a prime

    OUTPUT: the estimated run time of the "CRT" step to combine consistent congruences

    EXAMPLES::

        sage: from sage.combinat.binary_recurrence_sequences import _estimated_time
        sage: _estimated_time(2**4*3**2*5*7*11*13*17, 2**4*3**2*5*7*11*13, 20, 7)       # needs sage.symbolic
        106.211159309421
    """
    # The heuristic run time of the CRT step to go from modulus M1 to M2

    # length is the current length of cong

    Q = p * log(M2)  # Size of our primes.
    NPrimes = log(M2 / M1) / log(Q)  # The number of primes

    return (length * (Q / p)**NPrimes).n()


# Find the list of necessary congruences for the index n of binary recurrence
# sequence R using the fact that the reduction mod ell must be a pth power
def _find_cong1(p, R, ell):
    """
    Find the list of permissible indices `n` for which `u_n = y^p` mod ``ell``.

    INPUT:

    - ``p`` -- a prime number

    - ``R`` -- an object in class :class:`BinaryRecurrenceSequence`

    - ``ell`` -- a prime number

    OUTPUT:

    - A list of permissible values of `n` modulo ``period(ell)`` and the integer ``period(ell)``.

    EXAMPLES::

        sage: R = BinaryRecurrenceSequence(1,1)
        sage: sage.combinat.binary_recurrence_sequences._find_cong1(7, R, 29)           # needs sage.rings.finite_rings
        ([0, 1, 2, 12, 13], 14)
    """
    F = GF(ell)
    u0 = F(R.u0)
    u1 = F(R.u1)
    bf, cf = F(R.b), F(R.c)
    a0 = u0
    a1 = u1      # a0 and a1 are variables for terms in sequence

    # The set of pth powers mod ell
    PPowers = set(i**p for i in F)

    # The period of R mod ell
    modu = R.period(ell)

    # cong1 keeps track of congruences mod modu for the sequence mod ell
    cong1 = []

    for n in range(modu):  # n is the index of the a0

        # Check whether a0 is a perfect power mod ell
        if a0 in PPowers:
            # if a0 is a perfect power mod ell, add the index
            # to the list of necessary congruences
            cong1.append(n)

        a0, a1 = a1, bf * a1 + cf * a0      # step up the variables

    cong1.sort()

    return cong1, modu


def _is_p_power(a, p) -> bool:
    """
    Determine whether `a` is a perfect `p`-th power.

    INPUT:

    - ``a`` -- integer

    - ``p`` -- a prime number

    OUTPUT: boolean

    EXAMPLES::

        sage: from sage.combinat.binary_recurrence_sequences import _is_p_power
        sage: _is_p_power(2**7, 7)
        True
        sage: _is_p_power(2**7*3**2, 7)
        False
    """
    try:
        Integer(a).nth_root(p)
    except ValueError:
        return False
    return True
