def clifford_invariant(self, p):
    """
    This is the Clifford invariant, i.e. the class in the Brauer group of the
    Clifford algebra for even dimension, of the even Clifford Algebra for odd dimension.

    See Lam (AMS GSM 67) p. 117 for the definition, and p. 119 for the formula
    relating it to the Hasse invariant.

    EXAMPLES:

    For hyperbolic spaces, the clifford invariant is +1::

        sage: H = QuadraticForm(ZZ, 2, [0, 1, 0])
        sage: H.clifford_invariant(2)
        1
        sage: (H + H).clifford_invariant(2)
        1
        sage: (H + H + H).clifford_invariant(2)
        1
        sage: (H + H + H + H).clifford_invariant(2)
        1

    """
    n = self.dim() % 8
    if n == 1 or n == 2:
        s = 1
    elif n == 3 or n == 4:
        s = hilbert_symbol(-1, -self.disc(), p)
    elif n == 5 or n == 6:
        s = hilbert_symbol(-1, -1, p)
    elif n == 7 or n == 0:
        s = hilbert_symbol(-1, self.disc(), p)
    return s * self.hasse_invariant(p)
def clifford_invariant(self, p):
    """
    This is the Clifford invariant, i.e. the class in the Brauer group of the
    Clifford algebra for even dimension, of the even Clifford Algebra for odd dimension.

    See Lam (AMS GSM 67) p. 117 for the definition, and p. 119 for the formula
    relating it to the Hasse invariant.

    EXAMPLES:

    For hyperbolic spaces, the clifford invariant is +1::

        sage: H = QuadraticForm(ZZ, 2, [0, 1, 0])
        sage: H.clifford_invariant(2)
        1
        sage: (H + H).clifford_invariant(2)
        1
        sage: (H + H + H).clifford_invariant(2)
        1
        sage: (H + H + H + H).clifford_invariant(2)
        1

    """
    n = self.dim() % 8
    if n == 1 or n == 2:
        s = 1
    elif n == 3 or n == 4:
        s = hilbert_symbol(-1, -self.disc(), p)
    elif n == 5 or n == 6:
        s = hilbert_symbol(-1, -1, p)
    elif n == 7 or n == 0:
        s = hilbert_symbol(-1, self.disc(), p)
    return s * self.hasse_invariant(p)
    def is_locally_solvable(self, p):
        r"""
        Returns True if and only if ``self`` has a solution over the
        `p`-adic numbers. Here `p` is a prime number or equals
        `-1`, infinity, or `\RR` to denote the infinite place.

        EXAMPLES::

            sage: C = Conic(QQ, [1,2,3])
            sage: C.is_locally_solvable(-1)
            False
            sage: C.is_locally_solvable(2)
            False
            sage: C.is_locally_solvable(3)
            True
            sage: C.is_locally_solvable(QQ.hom(RR))
            False
            sage: D = Conic(QQ, [1, 2, -3])
            sage: D.is_locally_solvable(infinity)
            True
            sage: D.is_locally_solvable(RR)
            True

        """
        D, T = self.diagonal_matrix()
        abc = [D[j, j] for j in range(3)]
        if abc[2] == 0:
            return True
        a = -abc[0]/abc[2]
        b = -abc[1]/abc[2]
        if is_RealField(p) or is_InfinityElement(p):
            p = -1
        elif is_RingHomomorphism(p):
            if p.domain() is QQ and is_RealField(p.codomain()):
                p = -1
            else:
                raise TypeError("p (=%s) needs to be a prime of base field " \
                                 "B ( =`QQ`) in is_locally_solvable" % p)
        if hilbert_symbol(a, b, p) == -1:
            if self._local_obstruction == None:
                self._local_obstruction = p
            return False
        return True
Exemplo n.º 4
0
def is_anisotropic(self, p):
    """
    Checks if the quadratic form is anisotropic over the p-adic numbers `Q_p`.

    INPUT:
        `p` -- a prime number > 0

    OUTPUT:
        boolean

    EXAMPLES::

        sage: Q = DiagonalQuadraticForm(ZZ, [1,1])
        sage: Q.is_anisotropic(2)
        True
        sage: Q.is_anisotropic(3)
        True
        sage: Q.is_anisotropic(5)
        False

    ::

        sage: Q = DiagonalQuadraticForm(ZZ, [1,-1])
        sage: Q.is_anisotropic(2)
        False
        sage: Q.is_anisotropic(3)
        False
        sage: Q.is_anisotropic(5)
        False

    ::

        sage: [DiagonalQuadraticForm(ZZ, [1, -least_quadratic_nonresidue(p)]).is_anisotropic(p)  for p in prime_range(3, 30)]
        [True, True, True, True, True, True, True, True, True]

    ::

        sage: [DiagonalQuadraticForm(ZZ, [1, -least_quadratic_nonresidue(p), p, -p*least_quadratic_nonresidue(p)]).is_anisotropic(p)  for p in prime_range(3, 30)]
        [True, True, True, True, True, True, True, True, True]

    """
    n = self.dim()
    D = self.det()

    ## TO DO: Should check that p is prime

    if (n >= 5):
        return False

    if (n == 4):
        return (QQ(D).is_padic_square(p)
                and (self.hasse_invariant(p) == -hilbert_symbol(-1, -1, p)))

    if (n == 3):
        return (self.hasse_invariant(p) != hilbert_symbol(-1, -D, p))

    if (n == 2):
        return (not QQ(-D).is_padic_square(p))

    if (n == 1):
        return (self[0, 0] != 0)

    raise NotImplementedError(
        "Oops!  We haven't established a convention for 0-dim'l quadratic forms... =("
    )
Exemplo n.º 5
0
def hasse_invariant__OMeara(self, p):
    """
    Computes the O'Meara Hasse invariant at a prime `p`, as given on
    p167 of O'Meara's book.  If Q is diagonal with coefficients `a_i`,
    then the (Cassels) Hasse invariant is given by

    .. math::

        c_p = \prod_{i <= j} (a_i, a_j)_p

    where `(a,b)_p` is the Hilbert symbol at `p`.

    WARNING: This is different from the (Cassels) Hasse invariant, which
    only allows `i < j` in the product.  That is given by the method
    hasse_invariant(p).


    INPUT:
        `p` -- a prime number > 0

    OUTPUT:
        1 or -1

    EXAMPLES::

        sage: Q = QuadraticForm(ZZ, 2, [1,2,3])
        sage: Q.rational_diagonal_form()
        Quadratic form in 2 variables over Rational Field with coefficients:
        [ 1 0 ]
        [ * 2 ]
        sage: [Q.hasse_invariant(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]
        sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]

    ::

        sage: Q = DiagonalQuadraticForm(ZZ, [1,-1])
        sage: [Q.hasse_invariant(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]
        sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]

    ::

        sage: Q = DiagonalQuadraticForm(ZZ, [1,-1, -1])
        sage: [Q.hasse_invariant(p) for p in prime_range(20)]
        [-1, 1, 1, 1, 1, 1, 1, 1]
        sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]

    """
    ## TO DO: Need to deal with the case n=1 separately somewhere!

    Diag = self.rational_diagonal_form()

    ## DIAGNOSTIC
    #print "\n Q = " + str(self)
    #print "\n Q diagonalized at p = " + str(p) + " gives " + str(Diag)

    hasse_temp = 1
    n = Diag.dim()

    for j in range(n - 1):
        for k in range(j, n):
            hasse_temp = hasse_temp * hilbert_symbol(Diag[j, j], Diag[k, k], p)

    return hasse_temp
def is_anisotropic(self, p):
    """
    Checks if the quadratic form is anisotropic over the p-adic numbers `Q_p`. 

    INPUT:
        `p` -- a prime number > 0

    OUTPUT:
        boolean

    EXAMPLES::
    
        sage: Q = DiagonalQuadraticForm(ZZ, [1,1])
        sage: Q.is_anisotropic(2)
        True
        sage: Q.is_anisotropic(3)
        True
        sage: Q.is_anisotropic(5)
        False
        
    ::

        sage: Q = DiagonalQuadraticForm(ZZ, [1,-1])
        sage: Q.is_anisotropic(2)
        False
        sage: Q.is_anisotropic(3)
        False
        sage: Q.is_anisotropic(5)
        False

    ::
    
        sage: [DiagonalQuadraticForm(ZZ, [1, -least_quadratic_nonresidue(p)]).is_anisotropic(p)  for p in prime_range(3, 30)]
        [True, True, True, True, True, True, True, True, True]
        
    ::
    
        sage: [DiagonalQuadraticForm(ZZ, [1, -least_quadratic_nonresidue(p), p, -p*least_quadratic_nonresidue(p)]).is_anisotropic(p)  for p in prime_range(3, 30)]
        [True, True, True, True, True, True, True, True, True]

    """ 
    n = self.dim()
    D = self.det()

    ## TO DO: Should check that p is prime

    if (n >= 5):
        return False;

    if (n == 4):
        return ( QQ(D).is_padic_square(p) and (self.hasse_invariant(p) == - hilbert_symbol(-1,-1,p)) )

    if (n == 3):
        return (self.hasse_invariant(p) != hilbert_symbol(-1, -D, p))

    if (n == 2):
        return (not QQ(-D).is_padic_square(p))

    if (n == 1):
        return (self[0,0] != 0)

    raise NotImplementedError, "Oops!  We haven't established a convention for 0-dim'l quadratic forms... =(" 
def hasse_invariant__OMeara(self, p):
    """
    Computes the O'Meara Hasse invariant at a prime `p`, as given on
    p167 of O'Meara's book.  If Q is diagonal with coefficients `a_i`,
    then the (Cassels) Hasse invariant is given by
    
    .. math::
    
        c_p = \prod_{i <= j} (a_i, a_j)_p

    where `(a,b)_p` is the Hilbert symbol at `p`.

    WARNING: This is different from the (Cassels) Hasse invariant, which
    only allows `i < j` in the product.  That is given by the method
    hasse_invariant(p).


    INPUT:
        `p` -- a prime number > 0 

    OUTPUT:
        1 or -1

    EXAMPLES::
    
        sage: Q = QuadraticForm(ZZ, 2, [1,2,3])
        sage: Q.rational_diagonal_form()
        Quadratic form in 2 variables over Rational Field with coefficients:
        [ 1 0 ]
        [ * 2 ]
        sage: [Q.hasse_invariant(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]
        sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]
        
    ::

        sage: Q = DiagonalQuadraticForm(ZZ, [1,-1])
        sage: [Q.hasse_invariant(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]
        sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]
        
    ::

        sage: Q = DiagonalQuadraticForm(ZZ, [1,-1, -1])
        sage: [Q.hasse_invariant(p) for p in prime_range(20)]
        [-1, 1, 1, 1, 1, 1, 1, 1]
        sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]

    """
    ## TO DO: Need to deal with the case n=1 separately somewhere!

    Diag = self.rational_diagonal_form()

    ## DIAGNOSTIC
    #print "\n Q = " + str(self)
    #print "\n Q diagonalized at p = " + str(p) + " gives " + str(Diag)

    hasse_temp = 1
    n = Diag.dim()

    for j in range(n-1):
        for k in range(j, n):
            hasse_temp = hasse_temp * hilbert_symbol(Diag[j,j], Diag[k,k], p)

    return hasse_temp
def hasse_invariant(self, p):
    """
    Computes the Hasse invariant at a prime `p`, as given on p55 of
    Cassels's book.  If Q is diagonal with coefficients `a_i`, then the
    (Cassels) Hasse invariant is given by

    .. math::

        c_p = \prod_{i < j} (a_i, a_j)_p

    where `(a,b)_p` is the Hilbert symbol at `p`.  The underlying
    quadratic form must be non-degenerate over `Q_p` for this to make
    sense.

    WARNING: This is different from the O'Meara Hasse invariant, which
    allows `i <= j` in the product.  That is given by the method
    hasse_invariant__OMeara(p).

    NOTE: We should really rename this hasse_invariant__Cassels(), and
    set hasse_invariant() as a front-end to it.


    INPUT:
        `p` -- a prime number > 0

    OUTPUT:
        1 or -1

    EXAMPLES::

        sage: Q = QuadraticForm(ZZ, 2, [1,2,3])
        sage: Q.rational_diagonal_form()
        Quadratic form in 2 variables over Rational Field with coefficients:
        [ 1 0 ]
        [ * 2 ]
        sage: [Q.hasse_invariant(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]
        sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]

    ::

        sage: Q = DiagonalQuadraticForm(ZZ, [1,-1])
        sage: [Q.hasse_invariant(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]
        sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)]
        [-1, 1, 1, 1, 1, 1, 1, 1]

    ::

        sage: Q = DiagonalQuadraticForm(ZZ, [1,-1,5])
        sage: [Q.hasse_invariant(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]
        sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)]
        [-1, 1, 1, 1, 1, 1, 1, 1]

    ::

        sage: K.<a>=NumberField(x^2-23)
        sage: Q=DiagonalQuadraticForm(K,[-a,a+2])
        sage: [Q.hasse_invariant(p) for p in K.primes_above(19)]
        [-1, 1]

    """
    ## TO DO: Need to deal with the case n=1 separately somewhere!

    Diag = self.rational_diagonal_form()
    R = Diag.base_ring()

    ## DIAGNOSTIC
    #print "\n Q = " + str(self)
    #print "\n Q diagonalized at p = " + str(p) + " gives " + str(Diag)

    hasse_temp = 1
    n = Diag.dim()

    if R == QQ:
        for j in range(n-1):
            for k in range(j+1, n):
                hasse_temp = hasse_temp * hilbert_symbol(Diag[j,j], Diag[k,k], p)

    else:
        for j in range(n-1):
            for k in range(j+1, n):
                hasse_temp = hasse_temp * R.hilbert_symbol(Diag[j,j], Diag[k,k], p)

    return hasse_temp
Exemplo n.º 9
0
def hasse_invariant(self, p):
    """
    Computes the Hasse invariant at a prime `p`, as given on p55 of
    Cassels's book.  If Q is diagonal with coefficients `a_i`, then the
    (Cassels) Hasse invariant is given by

    .. math::

        c_p = \prod_{i < j} (a_i, a_j)_p

    where `(a,b)_p` is the Hilbert symbol at `p`.  The underlying
    quadratic form must be non-degenerate over `Q_p` for this to make
    sense.

    WARNING: This is different from the O'Meara Hasse invariant, which
    allows `i <= j` in the product.  That is given by the method
    hasse_invariant__OMeara(p).

    NOTE: We should really rename this hasse_invariant__Cassels(), and
    set hasse_invariant() as a front-end to it.


    INPUT:

        `p` -- a prime number > 0

    OUTPUT:

        1 or -1

    EXAMPLES::

        sage: Q = QuadraticForm(ZZ, 2, [1,2,3])
        sage: Q.rational_diagonal_form()
        Quadratic form in 2 variables over Rational Field with coefficients:
        [ 1 0 ]
        [ * 2 ]
        sage: [Q.hasse_invariant(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]
        sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]

    ::

        sage: Q = DiagonalQuadraticForm(ZZ, [1,-1])
        sage: [Q.hasse_invariant(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]
        sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)]
        [-1, 1, 1, 1, 1, 1, 1, 1]

    ::

        sage: Q = DiagonalQuadraticForm(ZZ, [1,-1,5])
        sage: [Q.hasse_invariant(p) for p in prime_range(20)]
        [1, 1, 1, 1, 1, 1, 1, 1]
        sage: [Q.hasse_invariant__OMeara(p) for p in prime_range(20)]
        [-1, 1, 1, 1, 1, 1, 1, 1]

    ::

        sage: K.<a>=NumberField(x^2-23)
        sage: Q=DiagonalQuadraticForm(K,[-a,a+2])
        sage: [Q.hasse_invariant(p) for p in K.primes_above(19)]
        [-1, 1]

    """
    ## TO DO: Need to deal with the case n=1 separately somewhere!

    Diag = self.rational_diagonal_form()
    R = Diag.base_ring()

    ## DIAGNOSTIC
    #print "\n Q = " + str(self)
    #print "\n Q diagonalized at p = " + str(p) + " gives " + str(Diag)

    hasse_temp = 1
    n = Diag.dim()

    if R == QQ:
        for j in range(n-1):
            for k in range(j+1, n):
                hasse_temp = hasse_temp * hilbert_symbol(Diag[j,j], Diag[k,k], p)

    else:
        for j in range(n-1):
            for k in range(j+1, n):
                hasse_temp = hasse_temp * R.hilbert_symbol(Diag[j,j], Diag[k,k], p)

    return hasse_temp
Exemplo n.º 10
0
def has_equivalent_Jordan_decomposition_at_prime(self, other, p):
    """
    Determines if the given quadratic form has a Jordan decomposition
    equivalent to that of self.

    INPUT:
        a QuadraticForm

    OUTPUT:
        boolean

    EXAMPLES::

        sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 1, 0, 3])
        sage: Q2 = QuadraticForm(ZZ, 3, [1, 0, 0, 2, -2, 6])
        sage: Q3 = QuadraticForm(ZZ, 3, [1, 0, 0, 1, 0, 11])
        sage: [Q1.level(), Q2.level(), Q3.level()]
        [44, 44, 44]
        sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,2)
        False
        sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,11)
        False
        sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,2)
        False
        sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,11)
        True
        sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,2)
        True
        sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,11)
        False

    """
    ## Sanity Checks
    #if not isinstance(other, QuadraticForm):
    if not isinstance(other, type(self)):
        raise TypeError(
            "Oops!  The first argument must be of type QuadraticForm.")
    if not is_prime(p):
        raise TypeError("Oops!  The second argument must be a prime number.")

    ## Get the relevant local normal forms quickly
    self_jordan = self.jordan_blocks_by_scale_and_unimodular(p,
                                                             safe_flag=False)
    other_jordan = other.jordan_blocks_by_scale_and_unimodular(p,
                                                               safe_flag=False)

    ## DIAGNOSTIC
    #print "self_jordan = ", self_jordan
    #print "other_jordan = ", other_jordan

    ## Check for the same number of Jordan components
    if len(self_jordan) != len(other_jordan):
        return False

    ## Deal with odd primes:  Check that the Jordan component scales, dimensions, and discriminants are the same
    if p != 2:
        for i in range(len(self_jordan)):
            if (self_jordan[i][0] != other_jordan[i][0]) \
               or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \
               or (legendre_symbol(self_jordan[i][1].det() * other_jordan[i][1].det(), p) != 1):
                return False

        ## All tests passed for an odd prime.
        return True

    ## For p = 2:  Check that all Jordan Invariants are the same.
    elif p == 2:

        ## Useful definition
        t = len(self_jordan)  ## Define t = Number of Jordan components

        ## Check that all Jordan Invariants are the same (scale, dim, and norm)
        for i in range(t):
            if (self_jordan[i][0] != other_jordan[i][0]) \
               or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \
               or (valuation(GCD(self_jordan[i][1].coefficients()), p) != valuation(GCD(other_jordan[i][1].coefficients()), p)):
                return False

        ## DIAGNOSTIC
        #print "Passed the Jordan invariant test."

        ## Use O'Meara's isometry test 93:29 on p277.
        ## ------------------------------------------

        ## List of norms, scales, and dimensions for each i
        scale_list = [ZZ(2)**self_jordan[i][0] for i in range(t)]
        norm_list = [
            ZZ(2)**(self_jordan[i][0] +
                    valuation(GCD(self_jordan[i][1].coefficients()), 2))
            for i in range(t)
        ]
        dim_list = [(self_jordan[i][1].dim()) for i in range(t)]

        ## List of Hessian determinants and Hasse invariants for each Jordan (sub)chain
        ## (Note: This is not the same as O'Meara's Gram determinants, but ratios are the same!)  -- NOT SO GOOD...
        ## But it matters in condition (ii), so we multiply all by 2 (instead of dividing by 2 since only square-factors matter, and it's easier.)
        j = 0
        self_chain_det_list = [
            self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])
        ]
        other_chain_det_list = [
            other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])
        ]
        self_hasse_chain_list = [
            self_jordan[j][1].scale_by_factor(
                ZZ(2)**self_jordan[j][0]).hasse_invariant__OMeara(2)
        ]
        other_hasse_chain_list = [
            other_jordan[j][1].scale_by_factor(
                ZZ(2)**other_jordan[j][0]).hasse_invariant__OMeara(2)
        ]

        for j in range(1, t):
            self_chain_det_list.append(self_chain_det_list[j - 1] *
                                       self_jordan[j][1].Gram_det() *
                                       (scale_list[j]**dim_list[j]))
            other_chain_det_list.append(other_chain_det_list[j - 1] *
                                        other_jordan[j][1].Gram_det() *
                                        (scale_list[j]**dim_list[j]))
            self_hasse_chain_list.append(self_hasse_chain_list[j-1] \
                                         * hilbert_symbol(self_chain_det_list[j-1], self_jordan[j][1].Gram_det(), 2) \
                                         * self_jordan[j][1].hasse_invariant__OMeara(2))
            other_hasse_chain_list.append(other_hasse_chain_list[j-1] \
                                          * hilbert_symbol(other_chain_det_list[j-1], other_jordan[j][1].Gram_det(), 2) \
                                          * other_jordan[j][1].hasse_invariant__OMeara(2))

        ## SANITY CHECK -- check that the scale powers are strictly increasing
        for i in range(1, len(scale_list)):
            if scale_list[i - 1] >= scale_list[i]:
                raise RuntimeError(
                    "Oops!  There is something wrong with the Jordan Decomposition -- the given scales are not strictly increasing!"
                )

        ## DIAGNOSTIC
        #print "scale_list = ", scale_list
        #print "norm_list = ", norm_list
        #print "dim_list = ", dim_list
        #print
        #print "self_chain_det_list = ", self_chain_det_list
        #print "other_chain_det_list = ", other_chain_det_list
        #print "self_hasse_chain_list = ", self_hasse_chain_list
        #print "other_hasse_chain_det_list = ", other_hasse_chain_list

        ## Test O'Meara's two conditions
        for i in range(t - 1):

            ## Condition (i): Check that their (unit) ratio is a square (but it suffices to check at most mod 8).
            modulus = norm_list[i] * norm_list[i + 1] / (scale_list[i]**2)
            if modulus > 8:
                modulus = 8
            if (modulus > 1) and ((
                (self_chain_det_list[i] / other_chain_det_list[i]) % modulus)
                                  != 1):
                #print "Failed when i =", i, " in condition 1."
                return False

            ## Check O'Meara's condition (ii) when appropriate
            if norm_list[i + 1] % (4 * norm_list[i]) == 0:
                if self_hasse_chain_list[i] * hilbert_symbol(norm_list[i] * other_chain_det_list[i], -self_chain_det_list[i], 2) \
                       != other_hasse_chain_list[i] * hilbert_symbol(norm_list[i], -other_chain_det_list[i], 2):      ## Nipp conditions
                    #print "Failed when i =", i, " in condition 2."
                    return False

        ## All tests passed for the prime 2.
        return True

    else:
        raise TypeError("Oops!  This should not have happened.")
def has_equivalent_Jordan_decomposition_at_prime(self, other, p):
    """
    Determines if the given quadratic form has a Jordan decomposition
    equivalent to that of self.
    
    INPUT:
        a QuadraticForm

    OUTPUT:
        boolean
    
    EXAMPLES::

        sage: Q1 = QuadraticForm(ZZ, 3, [1, 0, -1, 1, 0, 3])
        sage: Q2 = QuadraticForm(ZZ, 3, [1, 0, 0, 2, -2, 6])
        sage: Q3 = QuadraticForm(ZZ, 3, [1, 0, 0, 1, 0, 11])
        sage: [Q1.level(), Q2.level(), Q3.level()]
        [44, 44, 44]
        sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,2)
        False
        sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q2,11)
        False
        sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,2)
        False
        sage: Q1.has_equivalent_Jordan_decomposition_at_prime(Q3,11)
        True 
        sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,2)
        True 
        sage: Q2.has_equivalent_Jordan_decomposition_at_prime(Q3,11)
        False
    
    """
    ## Sanity Checks
    #if not isinstance(other, QuadraticForm):
    if type(other) != type(self):
        raise TypeError, "Oops!  The first argument must be of type QuadraticForm."
    if not is_prime(p):
        raise TypeError, "Oops!  The second argument must be a prime number."

    ## Get the relevant local normal forms quickly
    self_jordan = self.jordan_blocks_by_scale_and_unimodular(p, safe_flag= False)
    other_jordan = other.jordan_blocks_by_scale_and_unimodular(p, safe_flag=False)    

    ## DIAGNOSTIC
    #print "self_jordan = ", self_jordan
    #print "other_jordan = ", other_jordan


    ## Check for the same number of Jordan components
    if len(self_jordan) != len(other_jordan):
        return False


    ## Deal with odd primes:  Check that the Jordan component scales, dimensions, and discriminants are the same
    if p != 2:
        for i in range(len(self_jordan)):
            if (self_jordan[i][0] != other_jordan[i][0]) \
               or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \
               or (legendre_symbol(self_jordan[i][1].det() * other_jordan[i][1].det(), p) != 1):
                return False

        ## All tests passed for an odd prime.
        return True


    ## For p = 2:  Check that all Jordan Invariants are the same.
    elif p == 2:

        ## Useful definition
        t = len(self_jordan)          ## Define t = Number of Jordan components


        ## Check that all Jordan Invariants are the same (scale, dim, and norm)
        for i in range(t):
            if (self_jordan[i][0] != other_jordan[i][0]) \
               or (self_jordan[i][1].dim() != other_jordan[i][1].dim()) \
               or (valuation(GCD(self_jordan[i][1].coefficients()), p) != valuation(GCD(other_jordan[i][1].coefficients()), p)):
                return False

        ## DIAGNOSTIC
        #print "Passed the Jordan invariant test."


        ## Use O'Meara's isometry test 93:29 on p277.
        ## ------------------------------------------

        ## List of norms, scales, and dimensions for each i
        scale_list = [ZZ(2)**self_jordan[i][0]  for i in range(t)]
        norm_list = [ZZ(2)**(self_jordan[i][0] + valuation(GCD(self_jordan[i][1].coefficients()), 2))  for i in range(t)]
        dim_list = [(self_jordan[i][1].dim())  for i in range(t)]

        ## List of Hessian determinants and Hasse invariants for each Jordan (sub)chain
        ## (Note: This is not the same as O'Meara's Gram determinants, but ratios are the same!)  -- NOT SO GOOD...
        ## But it matters in condition (ii), so we multiply all by 2 (instead of dividing by 2 since only square-factors matter, and it's easier.)
        j = 0
        self_chain_det_list = [ self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])]
        other_chain_det_list = [ other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j])]
        self_hasse_chain_list = [ self_jordan[j][1].scale_by_factor(ZZ(2)**self_jordan[j][0]).hasse_invariant__OMeara(2) ]
        other_hasse_chain_list = [ other_jordan[j][1].scale_by_factor(ZZ(2)**other_jordan[j][0]).hasse_invariant__OMeara(2) ]
                   
        for j in range(1, t):
            self_chain_det_list.append(self_chain_det_list[j-1] * self_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j]))
            other_chain_det_list.append(other_chain_det_list[j-1] * other_jordan[j][1].Gram_det() * (scale_list[j]**dim_list[j]))
            self_hasse_chain_list.append(self_hasse_chain_list[j-1] \
                                         * hilbert_symbol(self_chain_det_list[j-1], self_jordan[j][1].Gram_det(), 2) \
                                         * self_jordan[j][1].hasse_invariant__OMeara(2))
            other_hasse_chain_list.append(other_hasse_chain_list[j-1] \
                                          * hilbert_symbol(other_chain_det_list[j-1], other_jordan[j][1].Gram_det(), 2) \
                                          * other_jordan[j][1].hasse_invariant__OMeara(2))


        ## SANITY CHECK -- check that the scale powers are strictly increasing        
        for i in range(1, len(scale_list)):
            if scale_list[i-1] >= scale_list[i]:
                   raise RuntimeError, "Oops!  There is something wrong with the Jordan Decomposition -- the given scales are not strictly increasing!"


        ## DIAGNOSTIC
        #print "scale_list = ", scale_list
        #print "norm_list = ", norm_list
        #print "dim_list = ", dim_list
        #print
        #print "self_chain_det_list = ", self_chain_det_list
        #print "other_chain_det_list = ", other_chain_det_list
        #print "self_hasse_chain_list = ", self_hasse_chain_list
        #print "other_hasse_chain_det_list = ", other_hasse_chain_list

        
        ## Test O'Meara's two conditions
        for i in range(t-1):

            ## Condition (i): Check that their (unit) ratio is a square (but it suffices to check at most mod 8).
            modulus = norm_list[i] * norm_list[i+1] / (scale_list[i] ** 2)
            if modulus > 8:
                   modulus = 8 
            if (modulus > 1) and (((self_chain_det_list[i] / other_chain_det_list[i]) % modulus) != 1):
                #print "Failed when i =", i, " in condition 1."
                return False
            
            ## Check O'Meara's condition (ii) when appropriate
            if norm_list[i+1] % (4 * norm_list[i]) == 0:
                if self_hasse_chain_list[i] * hilbert_symbol(norm_list[i] * other_chain_det_list[i], -self_chain_det_list[i], 2) \
                       != other_hasse_chain_list[i] * hilbert_symbol(norm_list[i], -other_chain_det_list[i], 2):      ## Nipp conditions
                    #print "Failed when i =", i, " in condition 2."
                    return False


        ## All tests passed for the prime 2.
        return True
            
    else:
        raise TypeError, "Oops!  This should not have happened."