Exemplo n.º 1
0
        def _split_hyperbolic(L) :
            cur_cor = 2
            Lcor = L + cur_cor * identity_matrix(L.nrows())
            while not is_positive_definite(Lcor) :
                cur_cor += 2
                Lcor = L + cur_cor * identity_matrix(L.nrows())

            a = FreeModule(ZZ, L.nrows()).gen(0)
            if a * L * a >= 0 :
                Lcor_length_inc = max(3, a * L * a)
                cur_Lcor_length = Lcor_length_inc
                while True :
                    short_vectors = flatten(QuadraticForm(Lcor).short_vector_list_up_to_length( a * Lcor * a )[cur_Lcor_length - Lcor_length_inc: cur_Lcor_length], max_level = 1)
                    for a in short_vectors :
                        if a * L * a < 0 :
                            break
                    else :
                        continue
                    break
            n = -a * L * a // 2

            short_vectors = E8.short_vector_list_up_to_length(n + 1)[-1]

            for v in short_vectors :
                for w in short_vectors :
                    if v * E8_gram * w == 2 * n - 1 :
                        LE8_mat = L.block_sum(E8_gram)
                        v_form = vector( list(a) + list(v) ) * LE8_mat
                        w_form = vector( list(a) + list(w) ) * LE8_mat
                        Lred_basis = matrix(ZZ, [v_form, w_form]).right_kernel().basis_matrix().transpose()
                        Lred_basis = matrix(ZZ, Lred_basis)

                        return Lred_basis.transpose() * LE8_mat * Lred_basis
Exemplo n.º 2
0
    def identity(self):
        r"""
        Return identity morphism in an endomorphism ring.

        EXAMPLE::

            sage: V=FreeModule(ZZ,5)
            sage: H=V.Hom(V)
            sage: H.identity()
            Free module morphism defined by the matrix
            [1 0 0 0 0]
            [0 1 0 0 0]
            [0 0 1 0 0]
            [0 0 0 1 0]
            [0 0 0 0 1]
            Domain: Ambient free module of rank 5 over the principal ideal domain ...
            Codomain: Ambient free module of rank 5 over the principal ideal domain ...
        """
        if self.is_endomorphism_set():
            return self(
                matrix.identity_matrix(self.base_ring(),
                                       self.domain().rank()))
        else:
            raise TypeError(
                "Identity map only defined for endomorphisms. Try natural_map() instead."
            )
Exemplo n.º 3
0
    def split_off_unimodular_lattice(self, L_basis_matrix, check = True) :
        r"""
        Split off a unimodular lattice that is an orthogonal summand of self.
        
        INPUT:
        
        - ``L_basis_matrix`` -- A matrix over `\ZZ` whose number of rows equals the
                                size of the underlying lattice.
        
        - ``check`` -- A boolean (default: ``True``).  If ``True`` it will be
                       checked whether the given sublattice is a direct summand.

        
        OUTPUT:
        
        - A pair of a discriminant group and a homomorphism from self to this group.
        """
        if check and L_basis_matrix.base_ring() is not ZZ :
            raise ValueError( "L_basis_matrix must define a sublattice" )
            
        pre_bases = matrix(ZZ, [ [ l * self._L * b for l in L_basis_matrix.columns()]
                                 for b in FreeModule(ZZ, self._L.nrows()).gens() ]) \
                            .augment(identity_matrix(ZZ, self._L.nrows())).echelon_form()
        
        if check and pre_bases[:L_basis_matrix.ncols(),:L_basis_matrix.ncols()] != identity_matrix(ZZ, L_basis_matrix.ncols()) :
            raise ValueError( "The sublattice defined by L_basis_matrix must be unimodular" )
            
        K_basis_matrix = pre_bases[L_basis_matrix.ncols():,L_basis_matrix.ncols():].transpose()
        if check and L_basis_matrix.column_module() + K_basis_matrix.column_module() != FreeModule(ZZ, self._L.nrows()) :
            raise ValueError( "The sublattice defined by L_basis_matrix must be an orthogonal summand" )

        K = K_basis_matrix.transpose() * self._L * K_basis_matrix
        n_disc = DiscriminantForm(K)
        
        total_basis_matrix = L_basis_matrix.change_ring(QQ).augment(K_basis_matrix * n_disc._dual_basis)

        basis_images = [ sum(map(operator.mul, b.lift(), self._dual_basis.columns()))
                         for b in self.smith_form_gens() ]
        basis_images = [ total_basis_matrix.solve_right(b)[-K_basis_matrix.ncols():]
                         for b in basis_images ]
        
        coercion_hom = self.hom([ sum(map(operator.mul, map(ZZ, b), map(n_disc, FreeModule(ZZ, K.nrows()).gens()) ))
                                  for b in basis_images ])
        
        return (n_disc, coercion_hom)
Exemplo n.º 4
0
    def P(self, n, names='z+', base_ring=QQ):
        r"""
        Construct the ``n``-dimensional projective space `\mathbb{P}^n`.

        INPUT:

        - ``n`` -- positive integer. The dimension of the projective space.

        - ``names`` -- string. Names for the homogeneous
          coordinates. See
          :func:`~sage.schemes.toric.variety.normalize_names`
          for acceptable formats.

        - ``base_ring`` -- a ring (default: `\QQ`). The base ring for
          the toric variety.

        OUTPUT:

        A :class:`CPR-Fano toric variety
        <sage.schemes.toric.fano_variety.CPRFanoToricVariety_field>`.

        EXAMPLES::

            sage: P3 = toric_varieties.P(3)
            sage: P3
            3-d CPR-Fano toric variety covered by 4 affine patches
            sage: P3.fan().rays()
            N( 1,  0,  0),
            N( 0,  1,  0),
            N( 0,  0,  1),
            N(-1, -1, -1)
            in 3-d lattice N
            sage: P3.gens()
            (z0, z1, z2, z3)
        """
        # We are going to eventually switch off consistency checks, so we need
        # to be sure that the input is acceptable.
        try:
            n = ZZ(n)  # make sure that we got a "mathematical" integer
        except TypeError:
            raise TypeError("dimension of the projective space must be a "
                            "positive integer!\nGot: %s" % n)
        if n <= 0:
            raise ValueError("only projective spaces of positive dimension "
                             "can be constructed!\nGot: %s" % n)
        m = identity_matrix(n).augment(matrix(n, 1, [-1] * n))
        charts = [
            list(range(i)) + list(range(i + 1, n + 1)) for i in range(n + 1)
        ]
        return CPRFanoToricVariety(Delta_polar=LatticePolytope(
            m.columns(), lattice=ToricLattice(n)),
                                   charts=charts,
                                   check=self._check,
                                   coordinate_names=names,
                                   base_ring=base_ring)
Exemplo n.º 5
0
    def P(self, n, names='z+', base_ring=QQ):
        r"""
        Construct the ``n``-dimensional projective space `\mathbb{P}^n`.

        INPUT:

        - ``n`` -- positive integer. The dimension of the projective space.

        - ``names`` -- string. Names for the homogeneous
          coordinates. See
          :func:`~sage.schemes.toric.variety.normalize_names`
          for acceptable formats.

        - ``base_ring`` -- a ring (default: `\QQ`). The base ring for
          the toric variety.

        OUTPUT:

        A :class:`CPR-Fano toric variety
        <sage.schemes.toric.fano_variety.CPRFanoToricVariety_field>`.

        EXAMPLES::

            sage: P3 = toric_varieties.P(3)
            sage: P3
            3-d CPR-Fano toric variety covered by 4 affine patches
            sage: P3.fan().rays()
            N( 1,  0,  0),
            N( 0,  1,  0),
            N( 0,  0,  1),
            N(-1, -1, -1)
            in 3-d lattice N
            sage: P3.gens()
            (z0, z1, z2, z3)
        """
        # We are going to eventually switch off consistency checks, so we need
        # to be sure that the input is acceptable.
        try:
            n = ZZ(n)   # make sure that we got a "mathematical" integer
        except TypeError:
            raise TypeError("dimension of the projective space must be a "
                            "positive integer!\nGot: %s" % n)
        if n <= 0:
            raise ValueError("only projective spaces of positive dimension "
                             "can be constructed!\nGot: %s" % n)
        m = identity_matrix(n).augment(matrix(n, 1, [-1]*n))
        charts = [list(range(i)) + list(range(i + 1, n + 1))
                  for i in range(n + 1)]
        return CPRFanoToricVariety(
            Delta_polar=LatticePolytope(m.columns(), lattice=ToricLattice(n)),
            charts=charts, check=self._check, coordinate_names=names,
            base_ring=base_ring)
Exemplo n.º 6
0
        def _split_hyperbolic(L):
            cur_cor = 2
            Lcor = L + cur_cor * identity_matrix(L.nrows())
            while not is_positive_definite(Lcor):
                cur_cor += 2
                Lcor = L + cur_cor * identity_matrix(L.nrows())

            a = FreeModule(ZZ, L.nrows()).gen(0)
            if a * L * a >= 0:
                Lcor_length_inc = max(3, a * L * a)
                cur_Lcor_length = Lcor_length_inc
                while True:
                    short_vectors = flatten(
                        QuadraticForm(Lcor).short_vector_list_up_to_length(
                            a * Lcor * a)[cur_Lcor_length -
                                          Lcor_length_inc:cur_Lcor_length],
                        max_level=1)
                    for a in short_vectors:
                        if a * L * a < 0:
                            break
                    else:
                        continue
                    break
            n = -a * L * a // 2

            short_vectors = E8.short_vector_list_up_to_length(n + 1)[-1]

            for v in short_vectors:
                for w in short_vectors:
                    if v * E8_gram * w == 2 * n - 1:
                        LE8_mat = L.block_sum(E8_gram)
                        v_form = vector(list(a) + list(v)) * LE8_mat
                        w_form = vector(list(a) + list(w)) * LE8_mat
                        Lred_basis = matrix(
                            ZZ, [v_form, w_form
                                 ]).right_kernel().basis_matrix().transpose()
                        Lred_basis = matrix(ZZ, Lred_basis)

                        return Lred_basis.transpose() * LE8_mat * Lred_basis
Exemplo n.º 7
0
    def A(self, n, names='z+', base_ring=QQ):
        r"""
        Construct the ``n``-dimensional affine space.

        INPUT:

        - ``n`` -- positive integer. The dimension of the affine space.

        - ``names`` -- string. Names for the homogeneous
          coordinates. See
          :func:`~sage.schemes.toric.variety.normalize_names`
          for acceptable formats.

        - ``base_ring`` -- a ring (default: `\QQ`). The base ring for
          the toric variety.

        OUTPUT:

        A :class:`toric variety
        <sage.schemes.toric.variety.ToricVariety_field>`.

        EXAMPLES::

            sage: A3 = toric_varieties.A(3)
            sage: A3
            3-d affine toric variety
            sage: A3.fan().rays()
            N(1, 0, 0),
            N(0, 1, 0),
            N(0, 0, 1)
            in 3-d lattice N
            sage: A3.gens()
            (z0, z1, z2)
        """
        # We are going to eventually switch off consistency checks, so we need
        # to be sure that the input is acceptable.
        try:
            n = ZZ(n)  # make sure that we got a "mathematical" integer
        except TypeError:
            raise TypeError("dimension of the affine space must be a "
                            "positive integer!\nGot: %s" % n)
        if n <= 0:
            raise ValueError("only affine spaces of positive dimension can "
                             "be constructed!\nGot: %s" % n)
        rays = identity_matrix(n).columns()
        cones = [list(range(n))]
        fan = Fan(cones, rays, check=self._check)
        return ToricVariety(fan, coordinate_names=names)
Exemplo n.º 8
0
    def A(self, n, names='z+', base_ring=QQ):
        r"""
        Construct the ``n``-dimensional affine space.

        INPUT:

        - ``n`` -- positive integer. The dimension of the affine space.

        - ``names`` -- string. Names for the homogeneous
          coordinates. See
          :func:`~sage.schemes.toric.variety.normalize_names`
          for acceptable formats.

        - ``base_ring`` -- a ring (default: `\QQ`). The base ring for
          the toric variety.

        OUTPUT:

        A :class:`toric variety
        <sage.schemes.toric.variety.ToricVariety_field>`.

        EXAMPLES::

            sage: A3 = toric_varieties.A(3)
            sage: A3
            3-d affine toric variety
            sage: A3.fan().rays()
            N(1, 0, 0),
            N(0, 1, 0),
            N(0, 0, 1)
            in 3-d lattice N
            sage: A3.gens()
            (z0, z1, z2)
        """
        # We are going to eventually switch off consistency checks, so we need
        # to be sure that the input is acceptable.
        try:
            n = ZZ(n)   # make sure that we got a "mathematical" integer
        except TypeError:
            raise TypeError("dimension of the affine space must be a "
                            "positive integer!\nGot: %s" % n)
        if n <= 0:
            raise ValueError("only affine spaces of positive dimension can "
                             "be constructed!\nGot: %s" % n)
        rays = identity_matrix(n).columns()
        cones = [ range(0,n) ]
        fan = Fan(cones, rays, check=self._check)
        return ToricVariety(fan, coordinate_names=names)
Exemplo n.º 9
0
    def P(self, n, names="z+"):
        r"""
        Construct the ``n``-dimensional projective space `\mathbb{P}^n`.
        
        INPUT: 

        - ``n`` -- positive integer. The dimension of the projective space. 

        - ``names`` -- string. Names for the homogeneous
          coordinates. See
          :func:`~sage.schemes.generic.toric_variety.normalize_names`
          for acceptable formats.

        OUTPUT:

        A :class:`CPR-Fano toric variety
        <sage.schemes.generic.fano_toric_variety.CPRFanoToricVariety_field>`.

        EXAMPLES::
        
            sage: P3 = toric_varieties.P(3)
            sage: P3
            3-d CPR-Fano toric variety covered by 4 affine patches
            sage: P3.fan().ray_matrix()
            [ 1  0  0 -1]
            [ 0  1  0 -1]
            [ 0  0  1 -1]
            sage: P3.gens()
            (z0, z1, z2, z3)
        """
        # We are going to eventually switch off consistency checks, so we need
        # to be sure that the input is acceptable.
        try:
            n = ZZ(n)  # make sure that we got a "mathematical" integer
        except TypeError:
            raise TypeError("dimension of the projective space must be a " "positive integer!\nGot: %s" % n)
        if n <= 0:
            raise ValueError("only projective spaces of positive dimension " "can be constructed!\nGot: %s" % n)
        m = identity_matrix(n).augment(matrix(n, 1, [-1] * n))
        charts = [range(0, i) + range(i + 1, n + 1) for i in range(0, n + 1)]
        return CPRFanoToricVariety(
            Delta_polar=LatticePolytope(m), charts=charts, check=self._check, coordinate_names=names
        )
Exemplo n.º 10
0
    def identity(self):
        r"""
        Return identity morphism in an endomorphism ring.

        EXAMPLE::

            sage: V=FreeModule(ZZ,5)
            sage: H=V.Hom(V)
            sage: H.identity()
            Free module morphism defined by the matrix
            [1 0 0 0 0]
            [0 1 0 0 0]
            [0 0 1 0 0]
            [0 0 0 1 0]
            [0 0 0 0 1]
            Domain: Ambient free module of rank 5 over the principal ideal domain ...
            Codomain: Ambient free module of rank 5 over the principal ideal domain ...
        """
        if self.is_endomorphism_set():
            return self(matrix.identity_matrix(self.base_ring(),self.domain().rank()))
        else:
            raise TypeError, "Identity map only defined for endomorphisms. Try natural_map() instead."
Exemplo n.º 11
0
        def to_matrix(self, side="right", on_space="primal"):
            r"""
            Return ``self`` as a matrix acting on the underlying vector
            space.

            - ``side`` -- optional (default: ``"right"``) whether the
              action of ``self`` is on the ``"left"`` or on the ``"right"``

            - ``on_space`` -- optional (default: ``"primal"``) whether
              to act as the reflection representation on the given
              basis, or to act on the dual reflection representation
              on the dual basis

            EXAMPLES::

                sage: W = ReflectionGroup(['A',2])           # optional - gap3
                sage: for w in W:                            # optional - gap3
                ....:     w.reduced_word()                   # optional - gap3
                ....:     [w.to_matrix(), w.to_matrix(on_space="dual")] # optional - gap3
                []
                [
                [1 0]  [1 0]
                [0 1], [0 1]
                ]
                [2]
                [
                [ 1  1]  [ 1  0]
                [ 0 -1], [ 1 -1]
                ]
                [1]
                [
                [-1  0]  [-1  1]
                [ 1  1], [ 0  1]
                ]
                [1, 2]
                [
                [-1 -1]  [ 0 -1]
                [ 1  0], [ 1 -1]
                ]
                [2, 1]
                [
                [ 0  1]  [-1  1]
                [-1 -1], [-1  0]
                ]
                [1, 2, 1]
                [
                [ 0 -1]  [ 0 -1]
                [-1  0], [-1  0]
                ]

            TESTS::

                sage: W = ReflectionGroup(['F',4])           # optional - gap3
                sage: all(w.to_matrix(side="left") == W.from_reduced_word(reversed(w.reduced_word())).to_matrix(side="right").transpose() for w in W) # optional - gap3
                True
                sage: all(w.to_matrix(side="right") == W.from_reduced_word(reversed(w.reduced_word())).to_matrix(side="left").transpose() for w in W) # optional - gap3
                True
            """
            W = self.parent()
            if W._reflection_representation is None:
                if side == "left":
                    w = ~self
                elif side == "right":
                    w = self
                else:
                    raise ValueError('side must be "left" or "right"')

                Delta = W.independent_roots()
                Phi = W.roots()
                M = Matrix([Phi[w(Phi.index(alpha) + 1) - 1] for alpha in Delta])
                mat = W.base_change_matrix() * M
            else:
                refl_repr = W._reflection_representation
                id_mat = identity_matrix(QQ, refl_repr[W.index_set()[0]].nrows())
                mat = prod([refl_repr[i] for i in self.reduced_word()], id_mat)

            if on_space == "primal":
                if side == "left":
                    mat = mat.transpose()
            elif on_space == "dual":
                if side == "left":
                    mat = mat.inverse()
                else:
                    mat = mat.inverse().transpose()
            else:
                raise ValueError('on_space must be "primal" or "dual"')

            mat.set_immutable()
            return mat
Exemplo n.º 12
0
    def _compute_q_expansion_basis(self, prec=None):
        r"""
        Compute q-expansion basis using Schaeffer's algorithm.

        EXAMPLES::

            sage: CuspForms(GammaH(31, [7]), 1).q_expansion_basis() # indirect doctest
            [
            q - q^2 - q^5 + O(q^6)
            ]

        A more elaborate example (two Galois-conjugate characters each giving a
        2-dimensional space)::

            sage: CuspForms(GammaH(124, [85]), 1).q_expansion_basis()
            [
            q - q^4 - q^6 + O(q^7),
            q^2 + O(q^7),
            q^3 + O(q^7),
            q^5 - q^6 + O(q^7)
            ]
        """
        if prec is None:
            prec = self.prec()
        else:
            prec = Integer(prec)

        chars=self.group().characters_mod_H(sign=-1, galois_orbits=True)

        B = []
        dim = 0
        for c in chars:
            chi = c.minimize_base_ring()
            Bchi = [weight1.modular_ratio_to_prec(chi, f, prec)
                for f in weight1.hecke_stable_subspace(chi) ]
            if Bchi == []:
                continue
            if chi.base_ring() == QQ:
                B += [f.padded_list(prec) for f in Bchi]
                dim += len(Bchi)
            else:
                d = chi.base_ring().degree()
                dim += d * len(Bchi)
                for f in Bchi:
                    w = f.padded_list(prec)
                    for i in range(d):
                        B.append([x[i] for x in w])

        basis_mat = Matrix(QQ, B)
        if basis_mat.is_zero():
            return []
        # Daft thing: "transformation=True" parameter to echelonize
        # is ignored for rational matrices!
        big_mat = basis_mat.augment(identity_matrix(dim))
        big_mat.echelonize()
        c = big_mat.pivots()[-1]

        echelon_basis_mat = big_mat[:, :prec]

        R = self._q_expansion_ring()

        if c >= prec:
            verbose("Precision %s insufficient to determine basis" % prec, level=1)
        else:
            verbose("Minimal precision for basis: %s" % (c+1), level=1)
            t = big_mat[:, prec:]
            assert echelon_basis_mat == t * basis_mat
            self.__transformation_matrix = t
            self._char_basis = [R(f.list(), c+1) for f in basis_mat.rows()]

        return [R(f.list(), prec) for f in echelon_basis_mat.rows() if f != 0]
Exemplo n.º 13
0
        def to_matrix(self, side="right", on_space="primal"):
            r"""
            Return ``self`` as a matrix acting on the underlying vector
            space.

            - ``side`` -- optional (default: ``"right"``) whether the
              action of ``self`` is on the ``"left"`` or on the ``"right"``

            - ``on_space`` -- optional (default: ``"primal"``) whether
              to act as the reflection representation on the given
              basis, or to act on the dual reflection representation
              on the dual basis

            EXAMPLES::

                sage: W = ReflectionGroup(['A',2])           # optional - gap3
                sage: for w in W:                            # optional - gap3
                ....:     w.reduced_word()                   # optional - gap3
                ....:     [w.to_matrix(), w.to_matrix(on_space="dual")] # optional - gap3
                []
                [
                [1 0]  [1 0]
                [0 1], [0 1]
                ]
                [2]
                [
                [ 1  1]  [ 1  0]
                [ 0 -1], [ 1 -1]
                ]
                [1]
                [
                [-1  0]  [-1  1]
                [ 1  1], [ 0  1]
                ]
                [1, 2]
                [
                [-1 -1]  [ 0 -1]
                [ 1  0], [ 1 -1]
                ]
                [2, 1]
                [
                [ 0  1]  [-1  1]
                [-1 -1], [-1  0]
                ]
                [1, 2, 1]
                [
                [ 0 -1]  [ 0 -1]
                [-1  0], [-1  0]
                ]

            TESTS::

                sage: W = ReflectionGroup(['F',4])           # optional - gap3
                sage: all(w.to_matrix(side="left") == W.from_reduced_word(reversed(w.reduced_word())).to_matrix(side="right").transpose() for w in W) # optional - gap3
                True
                sage: all(w.to_matrix(side="right") == W.from_reduced_word(reversed(w.reduced_word())).to_matrix(side="left").transpose() for w in W) # optional - gap3
                True
            """
            W = self.parent()
            if W._reflection_representation is None:
                if side == "left":
                    w = ~self
                elif side == "right":
                    w = self
                else:
                    raise ValueError('side must be "left" or "right"')

                Delta = W.independent_roots()
                Phi = W.roots()
                M = Matrix([Phi[w(Phi.index(alpha)+1)-1] for alpha in Delta])
                mat = W.base_change_matrix() * M
            else:
                refl_repr = W._reflection_representation
                id_mat = identity_matrix(QQ, refl_repr[W.index_set()[0]].nrows())
                mat = prod([refl_repr[i] for i in self.reduced_word()], id_mat)

            if on_space == "primal":
                if side == "left":
                    mat = mat.transpose()
            elif on_space == "dual":
                if side == "left":
                    mat = mat.inverse()
                else:
                    mat = mat.inverse().transpose()
            else:
                raise ValueError('on_space must be "primal" or "dual"')

            mat.set_immutable()
            return mat
Exemplo n.º 14
0
 def eigenvalue_multiplicity(mat, ev) :
     mat = matrix(CC, mat - ev * identity_matrix(subspace_dimension))
     return len(filter( lambda row: all( e.contains_zero() for e in row), _qr(mat).rows() ))
Exemplo n.º 15
0
 def eigenvalue_multiplicity(mat, ev):
     mat = matrix(CC, mat - ev * identity_matrix(subspace_dimension))
     return len(
         filter(lambda row: all(e.contains_zero() for e in row),
                _qr(mat).rows()))
Exemplo n.º 16
0
    def split_off_unimodular_lattice(self, L_basis_matrix, check=True):
        r"""
        Split off a unimodular lattice that is an orthogonal summand of self.
        
        INPUT:
        
        - ``L_basis_matrix`` -- A matrix over `\ZZ` whose number of rows equals the
                                size of the underlying lattice.
        
        - ``check`` -- A boolean (default: ``True``).  If ``True`` it will be
                       checked whether the given sublattice is a direct summand.

        
        OUTPUT:
        
        - A pair of a discriminant group and a homomorphism from self to this group.
        """
        if check and L_basis_matrix.base_ring() is not ZZ:
            raise ValueError("L_basis_matrix must define a sublattice")

        pre_bases = matrix(ZZ, [ [ l * self._L * b for l in L_basis_matrix.columns()]
                                 for b in FreeModule(ZZ, self._L.nrows()).gens() ]) \
                            .augment(identity_matrix(ZZ, self._L.nrows())).echelon_form()

        if check and pre_bases[:L_basis_matrix.ncols(), :L_basis_matrix.ncols(
        )] != identity_matrix(ZZ, L_basis_matrix.ncols()):
            raise ValueError(
                "The sublattice defined by L_basis_matrix must be unimodular")

        K_basis_matrix = pre_bases[L_basis_matrix.ncols():,
                                   L_basis_matrix.ncols():].transpose()
        if check and L_basis_matrix.column_module(
        ) + K_basis_matrix.column_module() != FreeModule(ZZ, self._L.nrows()):
            raise ValueError(
                "The sublattice defined by L_basis_matrix must be an orthogonal summand"
            )

        K = K_basis_matrix.transpose() * self._L * K_basis_matrix
        n_disc = DiscriminantForm(K)

        total_basis_matrix = L_basis_matrix.change_ring(QQ).augment(
            K_basis_matrix * n_disc._dual_basis)

        basis_images = [
            sum(map(operator.mul, b.lift(), self._dual_basis.columns()))
            for b in self.smith_form_gens()
        ]
        basis_images = [
            total_basis_matrix.solve_right(b)[-K_basis_matrix.ncols():]
            for b in basis_images
        ]

        coercion_hom = self.hom([
            sum(
                map(operator.mul, map(ZZ, b),
                    map(n_disc,
                        FreeModule(ZZ, K.nrows()).gens())))
            for b in basis_images
        ])

        return (n_disc, coercion_hom)