Exemplo n.º 1
0
    def order_at_cusp(self, cusp: 'CuspFamily') -> Integer:
        r"""
        Return the order of vanishing of ``self`` at the given cusp.

        INPUT:

        -  ``cusp`` --  a :class:`CuspFamily` object

        OUTPUT:

        - an integer

        EXAMPLES::

            sage: e = EtaProduct(2, {2:24, 1:-24})
            sage: e.order_at_cusp(CuspFamily(2, 1)) # cusp at infinity
            1
            sage: e.order_at_cusp(CuspFamily(2, 2)) # cusp 0
            -1
        """
        if not isinstance(cusp, CuspFamily):
            raise TypeError("argument (=%s) should be a CuspFamily" % cusp)
        if cusp.level() != self._N:
            raise ValueError("cusp not on right curve")
        sigma = sum(ell * self._rdict[ell] / cusp.width() *
                    (gcd(cusp.width(), self._N // ell))**2
                    for ell in self._rdict)
        return sigma / ZZ(24) / gcd(cusp.width(), self._N // cusp.width())
Exemplo n.º 2
0
    def kernel_vector(self, way='LLL', verbose=False):
        r"""
        todo: clean this

        EXAMPLES::

            sage: from slabbe import ChristoffelGraph
            sage: C = ChristoffelGraph((2,5,7))
            sage: C.kernel_vector()
            [(-1, -1, 1), (3, -4, 0)]

        """
        from sage.arith.misc import gcd
        if way == 'vect_gcd':
            a,b,c = self._v
            gcd_ac = gcd(a,c)
            gcd_bc = gcd(b,c)
            U = ua,ub,uc = vector((c,0,-a)) / gcd(a,c)
            V = va,vb,vc = vector((0,c,-b)) / gcd(b,c)
            rows = U,V
        elif way == 'echelon':
            a,b,c = self._v
            m = matrix(ZZ, 4, [1,1,1,c,0,-a,0,c,-b,b,-a,0])
            me = m.echelon_form()
            if verbose:
                print(me)
            rows = me[1],me[2]
        elif way == 'LLL':
            dim = self.dimension()
            if dim == 3:
                a,b,c = self._v
                M = matrix(ZZ, 4, [1,1,1,c,0,-a,0,c,-b,b,-a,0])
            elif dim == 4:
                a,b,c,d = self._v
                M = matrix(ZZ, 7, (1,1,1,1,b,-a,0,0,c,0,-a,0,0,c,-b,0,d,0,0,-a,0,d,0,-b,0,0,d,-c))
            else:
                raise ValueError("dimension (=%s) must be 3 or 4" % dim)
            rows = M.LLL().rows()
            VS = rows[0].parent()
            zero = VS(0)
            un = VS((1,)*dim)
            assert zero in rows, "(0,0,0) not in LLL result"
            assert un in rows, "(1,1,1) not in LLL result"
            while zero in rows: rows.remove(zero)
            while un in rows: rows.remove(un)
        elif way == 'vect':
            a,b,c = self._v
            U = ua,ub,uc = vector((c,0,-a))
            V = va,vb,vc = vector((0,c,-b))
            rows = U,V
        else:
            raise ValueError("unknown way")
        R = matrix(rows)
        if sum(map(abs, R.minors(dim-1))) != sum(map(abs,self._v)):
            print(R)
            print(R.minors(dim-1))
            print(sum(map(abs, R.minors(dim))))
            print(sum(map(abs,self._v)))
            raise Exception("The result (=%s) is false " % rows)
        return rows
Exemplo n.º 3
0
    def __init__(self,
                 V,
                 W,
                 gens=None,
                 modulus=None,
                 modulus_qf=None,
                 check=True):
        r"""
        Initialize ``self``.

        TESTS::

            sage: from sage.modules.torsion_quadratic_module import TorsionQuadraticModule
            sage: T = TorsionQuadraticModule(ZZ^3, 6*ZZ^3)
            sage: TestSuite(T).run()
        """
        if check:
            if V.rank() != W.rank():
                raise ValueError("modules must be of the same rank")
            if V.base_ring() is not ZZ:
                raise NotImplementedError("only currently implemented over ZZ")
            if V.inner_product_matrix() != V.inner_product_matrix().transpose(
            ):
                raise ValueError(
                    "the cover must have a symmetric inner product")

            if gens is not None and V.span(gens) + W != V:
                raise ValueError("provided gens do not generate the quotient")

        FGP_Module_class.__init__(self, V, W, check=check)
        if gens is not None:
            self._gens_user = tuple(self(v) for v in gens)
        else:
            # this is taken care of in the .gens method
            # we do not want this at initialization
            self._gens_user = None

        # compute the modulus - this may be expensive
        if modulus is None or check:
            # The inner product of two elements `b(v1+W,v2+W)`
            # is defined `mod (V,W)`
            num = V.basis_matrix() * V.inner_product_matrix() * W.basis_matrix(
            ).T
            self._modulus = gcd(num.list())

        if modulus is not None:
            if check and self._modulus / modulus not in self.base_ring():
                raise ValueError("the modulus must divide (V, W)")
            self._modulus = modulus

        if modulus_qf is None or check:
            # The quadratic_product of an element `q(v+W)` is defined
            # `\mod 2(V,W) + ZZ\{ (w,w) | w in w\}`
            norm = gcd(self.W().gram_matrix().diagonal())
            self._modulus_qf = gcd(norm, 2 * self._modulus)

        if modulus_qf is not None:
            if check and self._modulus_qf / modulus_qf not in self.base_ring():
                raise ValueError("the modulus_qf must divide (V, W)")
            self._modulus_qf = modulus_qf
Exemplo n.º 4
0
def is_irreducible_2cyl(w1,h1,t1,w2,h2,t2):
    r"""
    Test of irreducibility.
    """
    print "testing", w1, h1, t1, w2, h2, t2
    print "  result heights gcd(%d,%d) = %d" %(h1,h2,gcd(h1,h2)), "and", gcd([w1, w2, h2*t1-h1*t2]) == 1
    return gcd(h1,h2) == 1 and gcd([w1, w2, h2*t1-h1*t2]) == 1
Exemplo n.º 5
0
    def __classcall__(cls,
                      V,
                      W,
                      gens=None,
                      modulus=None,
                      modulus_qf=None,
                      check=True):
        r"""
        Return a :class:`TorsionQuadraticModule`.

        This method does the preprocessing for :meth:`sage.structure.CachedRepresentation`.

        TESTS::

            sage: q = matrix([1/2])
            sage: D1 = TorsionQuadraticForm(q)
            sage: D2 = TorsionQuadraticForm(q)
            sage: D1 is D2
            True
        """
        if check:
            if V.rank() != W.rank():
                raise ValueError("modules must be of the same rank")
            if V.base_ring() is not ZZ:
                raise NotImplementedError("only currently implemented over ZZ")
            if V.inner_product_matrix() != V.inner_product_matrix().transpose(
            ):
                raise ValueError(
                    "the cover must have a symmetric inner product")

            if gens is not None and V.span(gens) + W != V:
                raise ValueError("provided gens do not generate the quotient")

        # compute the modulus - this may be expensive
        if modulus is None or check:
            # The inner product of two elements `b(v1+W,v2+W)`
            # is defined `mod (V,W)`
            num = V.basis_matrix() * V.inner_product_matrix() * W.basis_matrix(
            ).T
            max_modulus = gcd(num.list())

        if modulus is None:
            modulus = max_modulus
        elif check and max_modulus / modulus not in V.base_ring():
            raise ValueError("the modulus must divide (V, W)")

        if modulus_qf is None or check:
            # The quadratic_product of an element `q(v+W)` is defined
            # `\mod 2(V,W) + ZZ\{ (w,w) | w in w\}`
            norm = gcd(W.gram_matrix().diagonal())
            max_modulus_qf = gcd(norm, 2 * modulus)

        if modulus_qf is None:
            modulus_qf = max_modulus_qf
        elif check and max_modulus_qf / modulus_qf not in V.base_ring():
            raise ValueError("the modulus_qf must divide (V, W)")
        return super(TorsionQuadraticModule,
                     cls).__classcall__(cls, V, W, gens, modulus, modulus_qf)
Exemplo n.º 6
0
def shift_cusp(gamma, t):
    r"""
    Returns gamma_2, delta such that B_t * gamma = gamma_2 * delta
    INPUT:
    - gamma - SL_2(Z) matrix
    - t - int, corresponds to the level shift B_t
    OUTPUT:
    - gamma, delta - gamma in SL_2(Z), delta triangular with B_t * gamma = gamma_2 * delta
    """
    a, c = gamma[0][0], gamma[1][0]
    gamma_2 = complete_column(a * t / gcd(a * t, c), c / gcd(a * t, c))
    delta = gamma_2**(-1) * Matrix([[t, 0], [0, 1]]) * gamma
    return gamma_2, delta
Exemplo n.º 7
0
    def is_primitive(self, M):
        r"""
        Return whether ``M`` is a primitive submodule of this lattice.

        A `\ZZ`-submodule ``M`` of a `\ZZ`-module ``L`` is called primitive if
        the quotient ``L/M`` is torsion free.

        INPUT:

        - ``M`` -- a submodule of this lattice

        EXAMPLES::

            sage: U = IntegralLattice("U")
            sage: L1 = U.span([vector([1,1])])
            sage: L2 = U.span([vector([1,-1])])
            sage: U.is_primitive(L1)
            True
            sage: U.is_primitive(L2)
            True
            sage: U.is_primitive(L1+L2)
            False

        We can also compute the index::

            sage: (L1+L2).index_in(U)
            2
        """
        return (gcd((self / M).invariants()) == 0)
    def is_primitive(self, M):
        r"""
        Return whether ``M`` is a primitive submodule of this lattice.

        A `\ZZ`-submodule ``M`` of a `\ZZ`-module ``L`` is called primitive if
        the quotient ``L/M`` is torsion free.

        INPUT:

        - ``M`` -- a submodule of this lattice

        EXAMPLES::

            sage: U = IntegralLattice("U")
            sage: L1 = U.span([vector([1,1])])
            sage: L2 = U.span([vector([1,-1])])
            sage: U.is_primitive(L1)
            True
            sage: U.is_primitive(L2)
            True
            sage: U.is_primitive(L1+L2)
            False

        We can also compute the index::

            sage: (L1+L2).index_in(U)
            2
        """
        return (gcd((self/M).invariants()) == 0)
Exemplo n.º 9
0
    def is_primitive(self, M):
        r"""
        Return whether ``M`` is a primitive submodule of this lattice.

        A `\Z`-submodule ``M`` of a `\Z`-module ``L`` is called primitive if
        the quotient ``L/M`` is torsion free.

        INPUT:

        - ``M`` -- a submodule of this lattice

        EXAMPLES::

            sage: from sage.modules.free_quadratic_module_integer_symmetric import IntegralLattice
            sage: U = IntegralLattice(Matrix(ZZ,2,2,[0,1,1,0]))
            sage: L1 = U.span([vector([1,1])])
            sage: L2 = U.span([vector([1,-1])])
            sage: U.is_primitive(L1)
            True
            sage: U.is_primitive(L2)
            True
            sage: U.is_primitive(L1+L2)
            False

        We can also compute the index::

            sage: (L1+L2).index_in(U)
            2
        """
        return (gcd((self/M).invariants()) == 0)
Exemplo n.º 10
0
def trace_of_frobenius_mod_2(E):
    F = E.base_field()
    x = PolynomialRing(F, 'x').gen()
    f = x ** 3 + E.a4() * x + E.a6()
    q = F.order()
    if gcd(f, power_mod(x, q, f) - x).is_constant():
        return 1
    else:
        return 0
Exemplo n.º 11
0
def taut_polynomial(tri, angle, cycles = [], alpha = True, mode = "taut"):
    # set up
    ZH = group_ring(tri, angle, cycles, alpha = alpha)
    P = ZH.polynomial_ring()

    ET = edges_to_triangles_matrix(tri, angle, cycles, ZH, P, mode = mode)

    # compute via minors
    minors = ET.minors(tri.countTetrahedra())
    return normalise_poly(gcd(minors), ZH, P)
Exemplo n.º 12
0
def complete_row(a, c, i=1):
    """
    Completes a row a,c with gcd(a,c) = 1 to an SL_2(Z)-matrix.
    INPUT:
    - a - int, the top entry of the column.
    - c - int, the bottom entry of the column.
    - i - int.
    OUTPUT:
    - A matrix in SL_2(Z) with i-th column [a,c]
    """
    if gcd(a, c) > 1:
        raise ValueError('Input needs to be coprime')
    return complete_column(a, c, i).T
Exemplo n.º 13
0
def origami_H2_2cyl_iterator(n, reducible=False, output="coordinates"):
    r"""
    INPUT:

    - ``n`` - positive integer - the number of squares

    - ``reducible`` - bool (default: False) - if True returns also the reducible
      origamis.

    - ``output`` - either "coordinates" or "origami" or "standard origami"
    """
    for n1,n2 in OrderedPartitions(n,k=2):
        for w1 in divisors(n1):
          h1 = n1//w1
          for w2 in filter(lambda x: x < w1, divisors(n2)):
            h2 = n2//w2
            if reducible or gcd(h1,h2) == 1:
              d = gcd(w1,w2)
              if d == 1:
                for t1 in xrange(w1):
                  for t2 in xrange(w2):
                    if output == "coordinates":
                      yield w1,h1,t1,w2,h2,t2
                    elif output == "origami":
                      yield origami_H2_2cyl(w1,h1,t1,w2,h2,t2)
                    elif output == "standard_origami":
                      yield origami_H2_2cyl(w1,h1,t1,w2,h2,t2).standard_form()

              else:
                for t1 in xrange(w1):
                  for t2 in xrange(w2):
                      if reducible or gcd(d,h2*t1-h1*t2) == 1:
                        if output == "coordinates":
                          yield w1,h1,t1,w2,h2,t2
                        elif output == "origami":
                          yield origami_H2_2cyl(w1,h1,t1,w2,h2,t2)
                        elif output == "standard_origami":
                          yield origami_H2_2cyl(w1,h1,t1,w2,h2,t2).standard_form()
    def __init__(self, V, W, gens=None, modulus=None, modulus_qf=None, check=True):
        r"""
        Initialize ``self``.

        TESTS::

            sage: from sage.modules.torsion_quadratic_module import TorsionQuadraticModule
            sage: T = TorsionQuadraticModule(ZZ^3, 6*ZZ^3)
            sage: TestSuite(T).run()
        """
        if check:
            if V.rank() != W.rank():
                raise ValueError("modules must be of the same rank")
            if V.base_ring() is not ZZ:
                raise NotImplementedError("only currently implemented over ZZ")
            if V.inner_product_matrix() != V.inner_product_matrix().transpose():
                raise ValueError("the cover must have a symmetric inner product")

            if gens is not None and V.span(gens) + W != V:
                raise ValueError("provided gens do not generate the quotient")

        FGP_Module_class.__init__(self, V, W, check=check)
        if gens is None:
            self._gens = FGP_Module_class.gens(self)
        else:
            self._gens = [self(v) for v in gens]

        if modulus is not None:
            if check:
                # The inner product of two elements `b(v1+W,v2+W)` is defined `mod (V,W)`
                num = gcd([x.inner_product(y) for x in V.gens()
                           for y in W.gens()])
                if num / modulus not in self.base_ring():
                    raise ValueError("the modulus must divide (V, W)")
            self._modulus = modulus
        else:
            # The inner product of two elements `b(v1+W,v2+W)` is defined `mod (V,W)`
            self._modulus = gcd([x.inner_product(y) for x in V.gens()
                                 for y in W.gens()])


        if modulus_qf is not None:
            if check:
                # The quadratic_product of an element `q(v+W)` is defined
                # `\mod 2(V,W) + ZZ\{ (w,w) | w in w\}`
                norm = gcd(self.W().gram_matrix().diagonal())
                num = gcd(norm, 2 * self._modulus)
                if num / modulus_qf not in self.base_ring():
                    raise ValueError("the modulus_qf must divide (V, W)")
            self._modulus_qf = modulus_qf
        else:
            # The quadratic_product of an element `q(v+W)` is defined
            # `\mod 2(V,W) + ZZ\{ (w,w) | w in w\}`
            norm = gcd(self.W().gram_matrix().diagonal())
            self._modulus_qf = gcd(norm, 2 * self._modulus)
Exemplo n.º 15
0
    def __init__(self,
                 base,
                 names,
                 degrees,
                 max_degree,
                 category=None,
                 **kwargs):
        r"""
        Construct a commutative graded algebra with finite degree.

        TESTS::

            sage: A.<x,y,z,t> = GradedCommutativeAlgebra(QQ, max_degree=6)
            sage: TestSuite(A).run()
            sage: A = GradedCommutativeAlgebra(QQ, ('x','y','z'), [2,3,4], max_degree=8)
            sage: TestSuite(A).run()
            sage: A = GradedCommutativeAlgebra(QQ, ('x','y','z','t'), [1,2,3,4], max_degree=10)
            sage: TestSuite(A).run()

        """
        from sage.arith.misc import gcd

        if max_degree not in ZZ:
            raise TypeError('max_degree must be an integer')
        if max_degree < max(degrees):
            raise ValueError(f'max_degree must not deceed {max(degrees)}')
        self._names = names
        self.__ngens = len(self._names)
        self._degrees = degrees
        self._max_deg = max_degree
        self._weighted_vectors = WeightedIntegerVectors(degrees)
        self._mul_symbol = kwargs.pop('mul_symbol', '*')
        self._mul_latex_symbol = kwargs.pop('mul_latex_symbol', '')
        step = gcd(degrees)
        universe = DisjointUnionEnumeratedSets(
            self._weighted_vectors.subset(k)
            for k in range(0, max_degree, step))
        base_cat = Algebras(
            base).WithBasis().Super().Supercommutative().FiniteDimensional()
        category = base_cat.or_subcategory(category, join=True)
        indices = ConditionSet(universe, self._valid_index)
        sorting_key = self._weighted_vectors.grading
        CombinatorialFreeModule.__init__(self,
                                         base,
                                         indices,
                                         sorting_key=sorting_key,
                                         category=category)
Exemplo n.º 16
0
def complete_column(a, c, i=1):
    """
    Completes a column a,c with gcd(a,c) = 1 to an SL_2(Z)-matrix.
    INPUT:
    - a - int, the top entry of the column.
    - c - int, the bottom entry of the column.
    - i - int.
    OUTPUT:
    - A matrix in SL_2(Z) with i-th column [a,c]
    """
    if gcd(a, c) > 1:
        raise ValueError('Input needs to be coprime')
    _, d, b = xgcd(a, c)
    b = -b
    if i == 1:
        return Matrix([[a, b], [c, d]])
    elif i == 2:
        return Matrix([[-b, a], [-d, c]])
Exemplo n.º 17
0
    def primitive_index(self):
        """
        Return the primitive index.

        .. SEEALSO::

            :meth:`is_primitive`, :meth:`primitive_data`

        EXAMPLES::

            sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp
            sage: Hyp(cyclotomic=([3],[4])).primitive_index()
            1
            sage: Hyp(gamma_list=[-2, 4, 6, -8]).primitive_index()
            2
            sage: Hyp(gamma_list=[-3, 6, 9, -12]).primitive_index()
            3
        """
        return gcd(self.gamma_list())
Exemplo n.º 18
0
    def primitive_data(self):
        """
        Return a primitive version.

        .. SEEALSO::

            :meth:`is_primitive`, :meth:`primitive_index`,

        EXAMPLES::

            sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp
            sage: H = Hyp(cyclotomic=([3],[4]))
            sage: H2 = Hyp(gamma_list=[-2, 4, 6, -8])
            sage: H2.primitive_data() == H
            True
        """
        g = self.gamma_list()
        d = gcd(g)
        return HypergeometricData(gamma_list=[x / d for x in g])
Exemplo n.º 19
0
    def primitive_index(self):
        """
        Return the primitive index.

        .. SEEALSO::

            :meth:`is_primitive`, :meth:`primitive_data`

        EXAMPLES::

            sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp
            sage: Hyp(cyclotomic=([3],[4])).primitive_index()
            1
            sage: Hyp(gamma_list=[-2, 4, 6, -8]).primitive_index()
            2
            sage: Hyp(gamma_list=[-3, 6, 9, -12]).primitive_index()
            3
        """
        return gcd(self.gamma_list())
Exemplo n.º 20
0
    def primitive_data(self):
        """
        Return a primitive version.

        .. SEEALSO::

            :meth:`is_primitive`, :meth:`primitive_index`

        EXAMPLES::

            sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp
            sage: H = Hyp(cyclotomic=([3],[4]))
            sage: H2 = Hyp(gamma_list=[-2, 4, 6, -8])
            sage: H2.primitive_data() == H
            True
        """
        g = self.gamma_list()
        d = gcd(g)
        return HypergeometricData(gamma_list=[x / d for x in g])
Exemplo n.º 21
0
def _reduce_invariants(invariants, weights):
    """
    Reduce a list of invariants of given weights.

    Reduces the given invariants over a field whose rings of integers is a gcd
    domain, in such a way that their greatest common weighted divisor is a unit
    in this ring.

    INPUT:

    - ``invariants`` -- The values of the invariants.

    - ``weights`` -- The respective weights of the invariants.

    OUTPUT:

    A list of invariants that is equivalent to the input and
    has no common weighted divisor.

    EXAMPLES::

        sage: from sage.rings.invariants.reconstruction import _reduce_invariants
        sage: invariants = [6/5, 12, 16]
        sage: weights = [1, 2, 3]
        sage: _reduce_invariants(invariants, weights)
        [3, 75, 250]
    """
    from sage.rings.integer_ring import ZZ
    factors = [dict(I.factor()) for I in invariants]
    scalar = ZZ(1)
    n = len(weights)
    from sage.arith.misc import gcd
    for prime in gcd(invariants).factor():
        p = prime[0]
        for D in factors:
            if p not in D:
                D[p] = 0
        scalar = scalar * p**min(
            [factors[i][p] // weights[i] for i in range(n)])
    return [invariants[i] * scalar**-weights[i] for i in range(n)]
Exemplo n.º 22
0
def taut_polynomial_via_tree(tri, angle, cycles = [], alpha = True, mode = "taut"):
    """
    If cycles = [] then this is the taut polynomial.  If cycles != []
    then this is the zero-Fitting invariant of the module obtained by
    'extension of scalars' - that is, form the epimorphism obtained by
    killing the given boundary cycles, apply it to the presentation
    matrix, and only then compute the gcd of the (correct minors).       
    """

    # set up 
    ZH = group_ring(tri, angle, cycles, alpha = alpha)
    P = ZH.polynomial_ring()

    ET = edges_to_triangles_matrix(tri, angle, cycles, ZH, P, mode = mode)
    _, non_tree_faces, _ = spanning_dual_tree(tri)

    ET = ET.transpose()
    ET = Matrix([row for i, row in enumerate(ET) if i in non_tree_faces]).transpose()

    # compute via minors
    minors = ET.minors(tri.countTetrahedra())
    return normalise_poly(gcd(minors), ZH, P)
Exemplo n.º 23
0
def AL(N, S):
    r"""
    INPUT:

    - N - a positive integer
    - S - a maximal divisor of N or a set of primes

    OUPUT:

    - A matrix representing the partial Atkin-Lehner operator W_S

    """
    try:
        S = prod([p**(N.valuation(p)) for p in S])
    except TypeError:
        if gcd(S, N / S) > 1:
            S = S.prime_divisors()
            S = prod([p**(N.valuation(p)) for p in S])

    _, w, z = xgcd(S, N / S)
    z = -z
    return Matrix([[S, 1], [N * z, S * w]])
Exemplo n.º 24
0
def origami_H2_1cyl_iterator(n, reducible=False, output='coordinates'):
    r"""
    INPUT:

    - ``n`` - positive integer - the number of squares

    - ``reducible`` - bool (default: False) - if True returns also the reducible
      origamis.

    - ``output`` - either "coordinates" or "origami" or "standard origami"
    """
    if reducible:
        hlist = divisors(n)
    else:
        hlist = [1]

    for h in hlist:  
     for p in Partitions(Integer(n/h),length=3):
      for l in CyclicPermutationsOfPartition([p]):
        l1,l2,l3 = l[0]
        if l1 == l2 and l2 == l3:
          if reducible or l1 == 1:
            for t in xrange(l1):
              if output == "coordinates":
                yield l1,l2,l3,h,t
              elif output == "origami":
                yield origami_H2_1cyl(l1,l2,l3,h,t)
              elif output == "standard_origami":
                yield origami_H2_1cyl(l1,l2,l3,h,t).standard_form()

        elif reducible or gcd([l1,l2,l3]) == 1:
          for t in xrange(n):
            if output == "coordinates":
              yield l1,l2,l3,h,t
            elif output == "origami":
              yield origami_H2_1cyl(l1,l2,l3,h,t)
            elif output == "standard_origami":
              yield origami_H2_1cyl(l1,l2,l3,h,t).standard_form()
Exemplo n.º 25
0
def is_irreducible_1cyl(l1,l2,l3,h,t):
    r"""
    Test of irreducibility.
    """
    return h == 1 and gcd([l1,l2,l3]) == 1
Exemplo n.º 26
0
def order_of_euler_class(delta, E):
    """
    Given the coboundary operator delta and an Euler two-cocycle E,
    returns k if [E] is k--torsion.  By convention, returns zero if
    [E] is non-torsion.  Note that the trivial element is 1--torsion.
    """

    delta = Matrix(delta)
    E = vector(E)

    # Note that E is a coboundary if there is a one-cocycle C solving
    #
    # E = C*delta
    #
    # We can find C (if it exists at all) using Smith normal form.

    D, U, V = delta.smith_form()
    assert D == U*delta*V

    # So we are trying to solve
    #
    # C*delta = C*U.inverse()*D*V.inverse() = E
    #
    # for a one-cochain C.  Multiply by V to get
    #
    # C*delta*V = C*U.inverse()*D = E*V
    #
    # Now set
    #
    # B = C*U.inverse(), and so B*U = C
    #
    # and rewrite to get
    #
    # B*U*delta*V = B*D = E*V
    #
    # So define E' by:

    Ep = E*V

    # Finally we attempt to solve B * D = Ep.  Note that D is
    # diagonal: so if we can solve all of the equations

    # B[i] * D[i][i] == Ep[i]

    # with B[i] integers, then [E] = 0 in cohomology.

    diag = diagonal(D)

    if any( (diag[i] == 0 and Ep[i] != 0) for i in range(len(Ep)) ):
        return 0

    # All zeros are at the end in Smith normal form.  Since we've
    # passed the above we can now remove them.

    first_zero = diag.index(0)
    diag = diag[:first_zero]
    Ep = Ep[:first_zero]

    # Since diag[i] is (now) never zero we can divide to get the
    # fractions Ep[i]/diag[i] and then find the scaling that makes
    # them simultaneously integral.

    denoms = [ diag[i] / gcd(Ep[i], diag[i]) for i in range(len(Ep)) ]
    return lcm(denoms)
Exemplo n.º 27
0
def extract_a_solution(L, m, s, A, homogeneous=False, sol_type="any"):
    r"""
    Return a single solution from the kernel of the matrix A.

    INPUT:

    - ``L`` -- a free Lie algebra quotient
    - ``m`` -- the lowest degree of covectors in the matrix
    - ``s`` -- the highest degree of covectors in the matrix
    - ``A`` -- a matrix of the form given by :func:`abnormal_factor_system`
    - ``homogeneous`` -- a boolean; whether the solved polynomial is homogeneous
    - ``sol_type`` -- one of "any", "all", "nice"

    With sol_type="any", the output is the solution from the first nonpivot
    column of the echelon form of A.
    With sol_type="all", the output is the basis of the kernel given by
    the nonpivot columns of the echelon form of A.
    With sol_type="nice", the output is a linear combination that attempts
    to decrease the number of nonzero coefficients and their magnitudes.

    OUTPUT:

    A dictionary {X: c} describing a covector. Each `X` is an element of the
    Lie algebra `L` and each `c` is the coefficient of the covector
    for the dual element of `X`.
    """
    # extract names of the covector variables
    if homogeneous:
        vecs = list(L.graded_basis(s))
    else:
        vecs = [X for k in range(m, s + 1) for X in L.graded_basis(k)]

    # check that the dimensions are correct
    assert A.subdivisions()[1][0] == len(vecs)
    covecdim = len(vecs)

    # compute the echelon form of A
    EA = A.echelon_form()

    def covec_to_dict(v):
        covec = {}
        for k, vk in enumerate(v):
            if vk:
                covec[vecs[k]] = vk
        return covec

    nonpivot = covecdim
    covecs = []
    while True:
        # find next non-pivot column

        while nonpivot in A.pivots():
            nonpivot += 1
        if nonpivot >= A.dimensions()[1]:
            if sol_type != "any":
                break

            # no solution found, something is wrong
            raise ValueError(
                "something went wrong: no nonpivot column in the multiplier variables"
            )

        if sol_type == "any":
            verbose("computing a solution")
        else:
            verbose("computing solution #%d" % (len(covecs) + 1))

        # the non-pivot columns of the echelon form define the valid solutions
        # rescale the column so that the covector has simplified coefficients
        cv = EA.column(nonpivot)[:covecdim]
        cv = cv / gcd(cv)

        if sol_type == "any":
            return covec_to_dict(cv)

        covecs.append(cv)
        nonpivot += 1

    if sol_type == "all":
        return [covec_to_dict(v) for v in covecs]

    if A.base_ring() == QQ:
        # over rationals, start with the covector with smallest coefficients
        covecs = sorted(covecs, key=lambda v: min(abs(vk) for vk in v))
    covec = covecs[0]
    covecs = covecs[1:]

    while covecs:
        other = covecs[0]
        covecs = covecs[1:]
        # eliminate the coefficient with the largest gcd
        gcdvec = [gcd(a, b) for a, b in zip(covec, other)]
        i = gcdvec.index(max(gcdvec))
        covec = covec[i] * other - other[i] * covec
        covec = covec / gcd(covec)

    # make leading coefficient positive
    i = 0
    while not covec[i]:
        i += 1
    if covec[i] < 0:
        covec = -covec

    return covec_to_dict(covec)
def test_norm_gcd():
    for K, q, p, result in test_cases_true + test_cases_false:
        qq = (q * K).factor()[0][0]
        is_coprime = gcd(qq.absolute_norm(), p) == 1
        assert is_coprime == result
Exemplo n.º 29
0
def binary_quintic_coefficients_from_invariants(invariants, K=None, invariant_choice='default', scaling='none'):
    r"""
    Reconstruct a binary quintic from the values of its (Clebsch) invariants.

    INPUT:

    - ``invariants`` -- A list or tuple of values of the three or four
      invariants. The default option requires the Clebsch invariants `A`, `B`,
      `C` and `R` of the binary quintic.

    - ``K`` -- The field over which the quintic is defined.

    - ``invariant_choice`` -- The type of invariants provided. The accepted
      options are ``'clebsch'`` and ``'default'``, which are the same. No
      other options are implemented.

    - ``scaling`` -- How the coefficients should be scaled. The accepted
      values are ``'none'`` for no scaling, ``'normalized'`` to scale in such
      a way that the resulting coefficients are independent of the scaling of
      the input invariants and ``'coprime'`` which scales the input invariants
      by dividing them by their gcd.

    OUTPUT:

    A set of coefficients of a binary quintic, whose invariants are equal to
    the given ``invariants`` up to a scaling.

    EXAMPLES:

    First we check the general case, where the invariant `M` is non-zero::

        sage: R.<x0, x1> = QQ[]
        sage: p = 3*x1^5 + 6*x1^4*x0 + 3*x1^3*x0^2 + 4*x1^2*x0^3 - 5*x1*x0^4 + 4*x0^5
        sage: quintic = invariant_theory.binary_quintic(p, x0, x1)
        sage: invs = quintic.clebsch_invariants(as_tuple=True)
        sage: reconstructed = invariant_theory.binary_form_from_invariants(5, invs, variables=quintic.variables()) # indirect doctest
        sage: reconstructed
        Binary quintic with coefficients (9592267437341790539005557/244140625000000,
        2149296928207625556323004064707/610351562500000000,
        11149651890347700974453304786783/76293945312500000,
        122650775751894638395648891202734239/47683715820312500000,
        323996630945706528474286334593218447/11920928955078125000,
        1504506503644608395841632538558481466127/14901161193847656250000)

    We can see that the invariants of the reconstructed form match the ones of
    the original form by scaling the invariants `B` and `C`::

        sage: scale = invs[0]/reconstructed.A_invariant()
        sage: invs[1] == reconstructed.B_invariant()*scale^2
        True
        sage: invs[2] == reconstructed.C_invariant()*scale^3
        True

    If we compare the form obtained by this reconstruction to the one found by
    letting the covariants `\alpha` and `\beta` be the coordinates of the form,
    we find the forms are the same up to a power of the determinant of `\alpha`
    and `\beta`::

        sage: alpha = quintic.alpha_covariant()
        sage: beta = quintic.beta_covariant()
        sage: g = matrix([[alpha(x0=1,x1=0),alpha(x0=0,x1=1)],[beta(x0=1,x1=0),beta(x0=0,x1=1)]])^-1
        sage: transformed = tuple([g.determinant()^-5*x for x in quintic.transformed(g).coeffs()])
        sage: transformed == reconstructed.coeffs()
        True

    This can also be seen by computing the `\alpha` covariant of the obtained
    form::

        sage: reconstructed.alpha_covariant().coefficient(x1)
        0
        sage: reconstructed.alpha_covariant().coefficient(x0) != 0
        True

    If the invariant `M` vanishes, then the coefficients are computed in a
    different way::

        sage: [A,B,C] = [3,1,2]
        sage: M = 2*A*B - 3*C
        sage: M
        0
        sage: from sage.rings.invariants.reconstruction import binary_quintic_coefficients_from_invariants
        sage: reconstructed = binary_quintic_coefficients_from_invariants([A,B,C])
        sage: reconstructed
        (-66741943359375/2097152,
         -125141143798828125/134217728,
         0,
         52793920040130615234375/34359738368,
         19797720015048980712890625/1099511627776,
         -4454487003386020660400390625/17592186044416)
        sage: newform = sum([ reconstructed[i]*x0^i*x1^(5-i) for i in range(6) ])
        sage: newquintic = invariant_theory.binary_quintic(newform, x0, x1)
        sage: scale = 3/newquintic.A_invariant()
        sage: [3, newquintic.B_invariant()*scale^2, newquintic.C_invariant()*scale^3]
        [3, 1, 2]

    Several special cases::

        sage: quintic = invariant_theory.binary_quintic(x0^5 - x1^5, x0, x1)
        sage: invs = quintic.clebsch_invariants(as_tuple=True)
        sage: binary_quintic_coefficients_from_invariants(invs)
        (1, 0, 0, 0, 0, 1)
        sage: quintic = invariant_theory.binary_quintic(x0*x1*(x0^3-x1^3), x0, x1)
        sage: invs = quintic.clebsch_invariants(as_tuple=True)
        sage: binary_quintic_coefficients_from_invariants(invs)
        (0, 1, 0, 0, 1, 0)
        sage: quintic = invariant_theory.binary_quintic(x0^5 + 10*x0^3*x1^2 - 15*x0*x1^4, x0, x1)
        sage: invs = quintic.clebsch_invariants(as_tuple=True)
        sage: binary_quintic_coefficients_from_invariants(invs)
        (1, 0, 10, 0, -15, 0)
        sage: quintic = invariant_theory.binary_quintic(x0^2*(x0^3 + x1^3), x0, x1)
        sage: invs = quintic.clebsch_invariants(as_tuple=True)
        sage: binary_quintic_coefficients_from_invariants(invs)
        (1, 0, 0, 1, 0, 0)
        sage: quintic = invariant_theory.binary_quintic(x0*(x0^4 + x1^4), x0, x1)
        sage: invs = quintic.clebsch_invariants(as_tuple=True)
        sage: binary_quintic_coefficients_from_invariants(invs)
        (1, 0, 0, 0, 1, 0)

    For fields of characteristic 2, 3 or 5, there is no reconstruction
    implemented. This is part of :trac:`26786`.::

        sage: binary_quintic_coefficients_from_invariants([3,1,2], K=GF(5))
        Traceback (most recent call last):
        ...
        NotImplementedError: no reconstruction of binary quintics implemented for fields of characteristic 2, 3 or 5

    TESTS::

        sage: from sage.rings.invariants.reconstruction import binary_quintic_coefficients_from_invariants
        sage: binary_quintic_coefficients_from_invariants([1,2,3], scaling='unknown')
        Traceback (most recent call last):
        ...
        ValueError: unknown scaling option 'unknown'
    """
    if invariant_choice not in ['default', 'clebsch']:
        raise ValueError('unknown choice of invariants {} for a binary quintic'
                         .format(invariant_choice))
    if scaling not in ['none', 'normalized', 'coprime']:
        raise ValueError("unknown scaling option '%s'" % scaling)
    if scaling == 'coprime':
        if len(invariants) == 3:
            invariants = _reduce_invariants(invariants, [1,2,3])
        elif len(invariants) == 4:
            invariants = _reduce_invariants(invariants, [2,4,6,9])
    A, B, C = invariants[0:3]
    if K is None:
        from sage.rings.fraction_field import FractionField
        K = FractionField(A.parent())
    if K.characteristic() in [2, 3, 5]:
        raise NotImplementedError('no reconstruction of binary quintics '
                          'implemented for fields of characteristic 2, 3 or 5')
    M = 2*A*B - 3*C
    N = K(2)**-1 * (A*C-B**2)
    R2 = -K(2)**-1 * (A*N**2-2*B*M*N+C*M**2)
    scale = [1,1,1,1,1,1]
    from sage.functions.all import binomial, sqrt
    if len(invariants) == 3:
        if R2.is_square():
            R = sqrt(R2)
        else:
            # if R2 is not a square, we scale the invariants in a suitable way
            # so that the 'new' R2 is a square
            [A, B, C] = [R2*A, R2**2*B, R2**3*C]
            [M, N] = [R2**3*M, R2**4*N]
            R = R2**5
    elif len(invariants) == 4:
        if invariants[3]**2 != R2:
            raise ValueError('provided invariants do not satisfy the syzygy '
                             'for Clebsch invariants of a binary quintic')
        R = invariants[3]
    else:
        raise ValueError('incorrect number of invariants provided, this '
                         'method requires 3 or 4 invariants')
    if M == 0:
        if N == 0:
            if A == 0:
                raise ValueError('no unique reconstruction possible for '
                                 'quintics with a treefold linear factor')
            else:
                if B == 0:
                    return (1,0,0,0,0,1)
                else:
                    return (0,1,0,0,1,0)
        else:
            # case corresponding to using alpha and gamma as coordinates
            if A == 0:
                return (1,0,0,0,1,0)
            else:
                if scaling == 'normalized':
                    # scaling z by (R/A**3)
                    scale = [ (-N)**-5*A**6*(R/A**3)**i for i in range(6) ]
                D = -N
                Delta = C
                a = [0]
                a.append((2*K(3)**-1*A**2-B)*N*B*K(2)**-1 - N**2*K(2)**-1)
                B0 = 2*K(3)**-1*A*R
                B1 = A*N*B*K(3)**-1
                C0 = 2*K(3)**-1*R
                C1 = B*N
    else:
        # case corresponding to using alpha and beta as coordinates
        if R == 0:
            if A == 0:
                return (1,0,10,0,-15,0)
            elif scaling == 'normalized':
                # scaling x by A and z by sqrt(A)
                scale = [ (-M)**(-5)*sqrt(A)**(12+i) for i in range(6) ]
        else:
            if A == 0:
                if B == 0:
                    return (1,0,0,1,0,0)
                elif scaling == 'normalized':
                    # scaling y by R/B**2
                    scale = [ (-M)**(-3)*(R/B**2)**i for i in range(6) ]
            elif scaling == 'normalized':
                # scaling y by R/A**4
                scale = [ (-M)**(-3)*(R/A**4)**i for i in range(6) ]
        D = -M
        Delta = A
        a = [0]
        a.append((2*K(3)**-1*A**2-B)*(N*A-M*B)*K(2)**-1 \
                    - M*(N*K(2)**-1-M*A*K(3)**-1))
        B0 = R
        B1 = K(2)**-1*(N*A-M*B)
        C0 = 0
        C1 = -M
    a[0] = (2*K(3)**-1*A**2-B)*R
    a.append(-D*B0 - K(2)**-1*Delta*a[0])
    a.append(-D*B1 - K(2)**-1*Delta*a[1])
    a.append(D**2*C0 + D*Delta*B0 + K(4)**-1*Delta**2*a[0])
    a.append(D**2*C1 + D*Delta*B1 + K(4)**-1*Delta**2*a[1])
    coeffs = tuple([K((-1)**i*binomial(5,i)*scale[5-i]*a[i]) for i in range(6)])
    if scaling == 'coprime':
        from sage.arith.misc import gcd
        return tuple([coeffs[i]/gcd(coeffs) for i in range(6)])
    else:
        return coeffs
Exemplo n.º 30
0
    def basis(self, reduce=True):
        r"""
        Produce a basis for the free abelian group of eta-products of level
        N (under multiplication), attempting to find basis vectors of the
        smallest possible degree.

        INPUT:

        -  ``reduce`` - a boolean (default True) indicating
           whether or not to apply LLL-reduction to the calculated basis

        EXAMPLES::

            sage: EtaGroup(5).basis()
            [Eta product of level 5 : (eta_1)^6 (eta_5)^-6]
            sage: EtaGroup(12).basis()
            [Eta product of level 12 : (eta_1)^-3 (eta_2)^2 (eta_3)^1 (eta_4)^-1 (eta_6)^-2 (eta_12)^3,
             Eta product of level 12 : (eta_1)^-4 (eta_2)^2 (eta_3)^4 (eta_6)^-2,
             Eta product of level 12 : (eta_1)^6 (eta_2)^-9 (eta_3)^-2 (eta_4)^3 (eta_6)^3 (eta_12)^-1,
             Eta product of level 12 : (eta_1)^-1 (eta_2)^3 (eta_3)^3 (eta_4)^-2 (eta_6)^-9 (eta_12)^6,
             Eta product of level 12 : (eta_1)^3 (eta_3)^-1 (eta_4)^-3 (eta_12)^1]
            sage: EtaGroup(12).basis(reduce=False) # much bigger coefficients
            [Eta product of level 12 : (eta_1)^384 (eta_2)^-576 (eta_3)^-696 (eta_4)^216 (eta_6)^576 (eta_12)^96,
             Eta product of level 12 : (eta_2)^24 (eta_12)^-24,
             Eta product of level 12 : (eta_1)^-40 (eta_2)^116 (eta_3)^96 (eta_4)^-30 (eta_6)^-80 (eta_12)^-62,
             Eta product of level 12 : (eta_1)^-4 (eta_2)^-33 (eta_3)^-4 (eta_4)^1 (eta_6)^3 (eta_12)^37,
             Eta product of level 12 : (eta_1)^15 (eta_2)^-24 (eta_3)^-29 (eta_4)^9 (eta_6)^24 (eta_12)^5]

        ALGORITHM: An eta product of level `N` is uniquely
        determined by the integers `r_d` for `d | N` with
        `d < N`, since `\sum_{d | N} r_d = 0`. The valid
        `r_d` are those that satisfy two congruences modulo 24,
        and one congruence modulo 2 for every prime divisor of N. We beef
        up the congruences modulo 2 to congruences modulo 24 by multiplying
        by 12. To calculate the kernel of the ensuing map
        `\ZZ^m \to (\ZZ/24\ZZ)^n`
        we lift it arbitrarily to an integer matrix and calculate its Smith
        normal form. This gives a basis for the lattice.

        This lattice typically contains "large" elements, so by default we
        pass it to the reduce_basis() function which performs
        LLL-reduction to give a more manageable basis.
        """
        N = self.level()
        divs = divisors(N)[:-1]
        s = len(divs)
        primedivs = prime_divisors(N)

        rows = []
        for di in divs:
            # generate a row of relation matrix
            row = [Mod(di, 24) - Mod(N, 24), Mod(N // di, 24) - Mod(1, 24)]
            for p in primedivs:
                row.append(Mod(12 * (N // di).valuation(p), 24))
            rows.append(row)

        M = matrix(IntegerModRing(24), rows)
        Mlift = M.change_ring(ZZ)
        # now we compute elementary factors of Mlift
        S, U, V = Mlift.smith_form()
        good_vects = []
        for i in range(U.nrows()):
            vect = U.row(i)
            nf = (i < S.ncols() and S[i, i]) or 0  # ?
            good_vects.append((vect * 24 / gcd(nf, 24)).list())
        for v in good_vects:
            v.append(-sum([r for r in v]))
        dicts = []
        for v in good_vects:
            dicts.append({})
            for i in range(s):
                dicts[-1][divs[i]] = v[i]
            dicts[-1][N] = v[-1]
        if reduce:
            return self.reduce_basis([self(d) for d in dicts])
        else:
            return [self(d) for d in dicts]
Exemplo n.º 31
0
def prime_norm_representative(I, O, D, ell):
    """
    Given an order O and a left O-ideal I return another
    left O-ideal J in the same class, but with prime norm.

    This corresponds to Step 1 in the notes. So given an ideal I it returns
    an ideal in the same class but with reduced norm N where N != ell is
    a large prime coprime to both D and p, and ell is a quadratic
    nonresidue module N.


    Args:
        I: A left O-ideal.
        O: An order in a quaternion algebra.
        D: An integer.
        ell: A prime.
    Returns:
        A pair (J, gamma) where J = I * gamma is a left O-ideal in the same
        class with prime norm N. N will be coprime to both D and p, and ell
        will be a nonquadratic residue module N.
    """
    # TODO: Change so O is not an argument.
    if not is_minkowski_basis(I.basis()):
        print("Warning: The ideal I does not have a minkowski basis"
              " precomputed and Sage can not do it for you.")

    nrd_I = I.norm()
    B = I.quaternion_algebra()
    p = B.discriminant()
    alpha = B(0)
    normalized_norm = Integer(alpha.reduced_norm() / nrd_I)
    # Choose random elements in I until one is found with norm N*nrd(I) where N
    # is prime.
    m_power = 3
    m = Integer(2)**m_power  # TODO: Change this to a proper bound.
    count = 0
    while (not is_prime(normalized_norm) or normalized_norm.divides(D)
           or normalized_norm == ell or normalized_norm == p
           or mod(ell, normalized_norm).is_square()):
        # Make a new random element.
        alpha = random_combination(I.basis(), bound=m)
        normalized_norm = Integer(alpha.reduced_norm() / nrd_I)

        # Increase the box we search in if we've been trying for too long. Note
        # this was just a random heuristic I came up with, it's not in the
        # paper.
        count += 1
        if count > 4 * m_power:
            m_power += 1
            m = Integer(2)**m_power
            count = 0

    # We now have an element alpha with norm N*nrd(I) where N is prime. The
    # ideal J = I*gamma has prime norm where gamma = conjugate(alpha) / nrd(I).
    gamma = alpha.conjugate() / nrd_I
    J = I.scale(gamma)

    assert is_prime(Integer(J.norm()))
    assert not mod(ell, Integer(J.norm())).is_square()
    assert gcd(Integer(J.norm()), D) == 1
    return J, gamma
Exemplo n.º 32
0
    def __init__(self,
                 V,
                 W,
                 gens=None,
                 modulus=None,
                 modulus_qf=None,
                 check=True):
        r"""
        Initialize ``self``.

        TESTS::

            sage: from sage.modules.torsion_quadratic_module import TorsionQuadraticModule
            sage: T = TorsionQuadraticModule(ZZ^3, 6*ZZ^3)
            sage: TestSuite(T).run()
        """
        if check:
            if V.rank() != W.rank():
                raise ValueError("modules must be of the same rank")
            if V.base_ring() is not ZZ:
                raise NotImplementedError("only currently implemented over ZZ")
            if V.inner_product_matrix() != V.inner_product_matrix().transpose(
            ):
                raise ValueError(
                    "the cover must have a symmetric inner product")

            if gens is not None and V.span(gens) + W != V:
                raise ValueError("provided gens do not generate the quotient")

        FGP_Module_class.__init__(self, V, W, check=check)
        if gens is None:
            self._gens = FGP_Module_class.gens(self)
        else:
            self._gens = [self(v) for v in gens]

        if modulus is not None:
            if check:
                # The inner product of two elements `b(v1+W,v2+W)` is defined `mod (V,W)`
                num = gcd(
                    [x.inner_product(y) for x in V.gens() for y in W.gens()])
                if num / modulus not in self.base_ring():
                    raise ValueError("the modulus must divide (V, W)")
            self._modulus = modulus
        else:
            # The inner product of two elements `b(v1+W,v2+W)` is defined `mod (V,W)`
            self._modulus = gcd(
                [x.inner_product(y) for x in V.gens() for y in W.gens()])

        if modulus_qf is not None:
            if check:
                # The quadratic_product of an element `q(v+W)` is defined
                # `\mod 2(V,W) + ZZ\{ (w,w) | w in w\}`
                norm = gcd(self.W().gram_matrix().diagonal())
                num = gcd(norm, 2 * self._modulus)
                if num / modulus_qf not in self.base_ring():
                    raise ValueError("the modulus_qf must divide (V, W)")
            self._modulus_qf = modulus_qf
        else:
            # The quadratic_product of an element `q(v+W)` is defined
            # `\mod 2(V,W) + ZZ\{ (w,w) | w in w\}`
            norm = gcd(self.W().gram_matrix().diagonal())
            self._modulus_qf = gcd(norm, 2 * self._modulus)