# Integer Lattices

An integer lattice $L$ is a finitely generated $\mathbb{Z}$-submodule of a quadratic vector space $V = \mathbb{Q}^n$ over the rational numbers. Integer lattices are also known as quadratic forms over the integers. We will refer to them as $\mathbb{Z}$-lattices.

A $\mathbb{Z}$-lattice $L$ has the type `ZZLat`

. It is given in terms of its ambient quadratic space $V$ together with a basis matrix $B$ whose rows span $L$, i.e. $L = \mathbb{Z}^r B$ where $r$ is the ($\mathbb{Z}$-module) rank of $L$.

To access $V$ and $B$ see `ambient_space(L::ZZLat)`

and `basis_matrix(L::ZZLat)`

.

## Creation of integer lattices

### From a gram matrix

`integer_lattice`

— Method`integer_lattice([B::MatElem]; gram) -> ZZLat`

Return the Z-lattice with basis matrix $B$ inside the quadratic space with Gram matrix `gram`

.

If the keyword `gram`

is not specified, the Gram matrix is the identity matrix. If $B$ is not specified, the basis matrix is the identity matrix.

**Examples**

```
julia> L = integer_lattice(matrix(QQ, 2, 2, [1//2, 0, 0, 2]));
julia> gram_matrix(L) == matrix(QQ, 2, 2, [1//4, 0, 0, 4])
true
julia> L = integer_lattice(gram = matrix(ZZ, [2 -1; -1 2]));
julia> gram_matrix(L) == matrix(ZZ, [2 -1; -1 2])
true
```

### In a quadratic space

`lattice`

— Method`lattice(V::AbstractSpace, basis::MatElem ; check::Bool = true) -> AbstractLat`

Given an ambient space `V`

and a matrix `basis`

, return the lattice spanned by the rows of `basis`

inside `V`

. If `V`

is hermitian (resp. quadratic) then the output is a hermitian (resp. quadratic) lattice.

By default, `basis`

is checked to be of full rank. This test can be disabled by setting `check`

to false.

### Special lattices

`root_lattice`

— Method`root_lattice(R::Symbol, n::Int) -> ZZLat`

Return the root lattice of type `R`

given by `:A`

, `:D`

or `:E`

with parameter `n`

.

`hyperbolic_plane_lattice`

— Method`hyperbolic_plane_lattice(n::RationalUnion = 1) -> ZZLat`

Return the hyperbolic plane with intersection form of scale `n`

, that is, the unique (up to isometry) even unimodular hyperbolic $\mathbb Z$-lattice of rank 2, rescaled by `n`

.

**Examples**

```
julia> L = hyperbolic_plane_lattice(6);
julia> gram_matrix(L)
[0 6]
[6 0]
julia> L = hyperbolic_plane_lattice(ZZ(-13));
julia> gram_matrix(L)
[ 0 -13]
[-13 0]
```

`integer_lattice`

— Method`integer_lattice(S::Symbol, n::RationalUnion = 1) -> ZZlat`

Given `S = :H`

or `S = :U`

, return a $\mathbb Z$-lattice admitting $n*J_2$ as Gram matrix in some basis, where $J_2$ is the 2-by-2 matrix with 0's on the main diagonal and 1's elsewhere.

`leech_lattice`

— Function`leech_lattice() -> ZZLat`

Return the Leech lattice.

`leech_lattice(niemeier_lattice::ZZLat) -> ZZLat, QQMatrix, Int`

Return a triple `L, v, h`

where `L`

is the Leech lattice.

L is an `h`

-neighbor of the Niemeier lattice `N`

with respect to `v`

. This means that `L / L ∩ N ≅ ℤ / h ℤ`

. Here `h`

is the Coxeter number of the Niemeier lattice.

This implements the 23 holy constructions of the Leech lattice in J. H. Conway, N. J. A. Sloane (1999).

**Examples**

```
julia> R = integer_lattice(gram=2 * identity_matrix(ZZ, 24));
julia> N = maximal_even_lattice(R) # Some Niemeier lattice
Integer lattice of rank 24 and degree 24
with gram matrix
[2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 0 0 0 0]
[1 2 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 0]
[1 1 2 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0]
[1 1 1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 2 1 1 1 0 0 0 0 1 0 1 1 0 0 0 0 0 0 0 0]
[0 0 0 0 1 2 1 1 0 0 0 0 1 1 0 1 0 0 0 0 0 0 0 0]
[0 0 0 0 1 1 2 1 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0]
[0 0 0 0 1 1 1 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 2 1 1 1 0 0 0 0 0 0 0 0 1 1 1 0]
[0 0 0 0 0 0 0 0 1 2 1 1 0 0 0 0 0 0 0 0 1 0 1 1]
[0 0 0 0 0 0 0 0 1 1 2 1 0 0 0 0 0 0 0 0 1 1 0 1]
[0 0 0 0 0 0 0 0 1 1 1 2 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 1 1 1 0 0 0 0 0 2 1 1 1 0 0 0 0 0 0 0 0]
[0 0 0 0 0 1 1 0 0 0 0 0 1 2 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 1 0 1 0 0 0 0 0 1 0 2 0 0 0 0 0 0 0 0 0]
[0 0 0 0 1 1 0 0 0 0 0 0 1 0 0 2 0 0 0 0 0 0 0 0]
[1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 2 1 1 1 0 0 0 0]
[0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 0 0 0 0 0 0]
[1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 2 0 0 0 0 0]
[1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 2 0 0 0 0]
[0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 2 1 1 1]
[0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 1 2 0 0]
[0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 0 2 0]
[0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 0 0 2]
julia> minimum(N)
2
julia> det(N)
1
julia> L, v, h = leech_lattice(N);
julia> minimum(L)
4
julia> det(L)
1
julia> h == index(L, intersect(L, N))
true
```

We illustrate how the Leech lattice is constructed from `N`

, `h`

and `v`

.

```
julia> Zmodh = residue_ring(ZZ, h);
julia> V = ambient_space(N);
julia> vG = map_entries(x->Zmodh(ZZ(x)), inner_product(V, v, basis_matrix(N)));
julia> LN = transpose(lift(kernel(vG)[2]))*basis_matrix(N); # vectors whose inner product with `v` is divisible by `h`.
julia> lattice(V, LN) == intersect(L, N)
true
julia> gensL = vcat(LN, 1//h * v);
julia> lattice(V, gensL, isbasis=false) == L
true
```

### From a genus

Integer lattices can be created as representatives of a genus. See (`representative(L::ZZGenus)`

)

### Rescaling the Quadratic Form

`rescale`

— Method`rescale(L::ZZLat, r::RationalUnion) -> ZZLat`

Return the lattice `L`

in the quadratic space with form `r \Phi`

.

**Examples**

This can be useful to apply methods intended for positive definite lattices.

```
julia> L = integer_lattice(gram=ZZ[-1 0; 0 -1])
Integer lattice of rank 2 and degree 2
with gram matrix
[-1 0]
[ 0 -1]
julia> shortest_vectors(rescale(L, -1))
2-element Vector{Vector{ZZRingElem}}:
[0, 1]
[1, 0]
```

## Attributes

`ambient_space`

— Method`ambient_space(L::AbstractLat) -> AbstractSpace`

Return the ambient space of the lattice `L`

. If the ambient space is not known, an error is raised.

`basis_matrix`

— Method`basis_matrix(L::ZZLat) -> QQMatrix`

Return the basis matrix $B$ of the integer lattice $L$.

The lattice is given by the row span of $B$ seen inside of the ambient quadratic space of $L$.

`gram_matrix`

— Method`gram_matrix(L::ZZLat) -> QQMatrix`

Return the gram matrix of $L$.

**Examples**

```
julia> L = integer_lattice(matrix(ZZ, [2 0; -1 2]));
julia> gram_matrix(L)
[ 4 -2]
[-2 5]
```

`rational_span`

— Method`rational_span(L::ZZLat) -> QuadSpace`

Return the rational span of $L$, which is the quadratic space with Gram matrix equal to `gram_matrix(L)`

.

**Examples**

```
julia> L = integer_lattice(matrix(ZZ, [2 0; -1 2]));
julia> rational_span(L)
Quadratic space of dimension 2
over rational field
with gram matrix
[ 4 -2]
[-2 5]
```

## Invariants

`rank`

— Method`rank(L::AbstractLat) -> Int`

Return the rank of the underlying module of the lattice `L`

.

`det`

— Method`det(L::ZZLat) -> QQFieldElem`

Return the determinant of the gram matrix of `L`

.

`scale`

— Method`scale(L::ZZLat) -> QQFieldElem`

Return the scale of `L`

.

The scale of `L`

is defined as the positive generator of the $\mathbb Z$-ideal generated by $\{\Phi(x, y) : x, y \in L\}$.

`norm`

— Method`norm(L::ZZLat) -> QQFieldElem`

Return the norm of `L`

.

The norm of `L`

is defined as the positive generator of the $\mathbb Z$- ideal generated by $\{\Phi(x,x) : x \in L\}$.

`iseven`

— Method`iseven(L::ZZLat) -> Bool`

Return whether `L`

is even.

An integer lattice `L`

in the rational quadratic space $(V,\Phi)$ is called even if $\Phi(x,x) \in 2\mathbb{Z}$ for all $x in L$.

`is_integral`

— Method`is_integral(L::AbstractLat) -> Bool`

Return whether the lattice `L`

is integral.

`is_primary_with_prime`

— Method`is_primary_with_prime(L::ZZLat) -> Bool, ZZRingElem`

Given a $\mathbb Z$-lattice `L`

, return whether `L`

is primary, that is whether `L`

is integral and its discriminant group (see `discriminant_group`

) is a `p`

-group for some prime number `p`

. In case it is, `p`

is also returned as second output.

Note that for unimodular lattices, this function returns `(true, 1)`

. If the lattice is not primary, the second return value is `-1`

by default.

`is_primary`

— Method`is_primary(L::ZZLat, p::Union{Integer, ZZRingElem}) -> Bool`

Given an integral $\mathbb Z$-lattice `L`

and a prime number `p`

, return whether `L`

is `p`

-primary, that is whether its discriminant group (see `discriminant_group`

) is a `p`

-group.

`is_elementary_with_prime`

— Method`is_elementary_with_prime(L::ZZLat) -> Bool, ZZRingElem`

Given a $\mathbb Z$-lattice `L`

, return whether `L`

is elementary, that is whether `L`

is integral and its discriminant group (see `discriminant_group`

) is an elemenentary `p`

-group for some prime number `p`

. In case it is, `p`

is also returned as second output.

Note that for unimodular lattices, this function returns `(true, 1)`

. If the lattice is not elementary, the second return value is `-1`

by default.

`is_elementary`

— Method`is_elementary(L::ZZLat, p::Union{Integer, ZZRingElem}) -> Bool`

Given an integral $\mathbb Z$-lattice `L`

and a prime number `p`

, return whether `L`

is `p`

-elementary, that is whether its discriminant group (see `discriminant_group`

) is an elementary `p`

-group.

### The Genus

For an integral lattice The genus of an integer lattice collects its local invariants. `genus(::ZZLat)`

`mass`

— Method`mass(L::ZZLat) -> QQFieldElem`

Return the mass of the genus of `L`

.

`genus_representatives`

— Method`genus_representatives(L::ZZLat) -> Vector{ZZLat}`

Return representatives for the isometry classes in the genus of `L`

.

### Real invariants

`signature_tuple`

— Method`signature_tuple(L::ZZLat) -> Tuple{Int,Int,Int}`

Return the number of (positive, zero, negative) inertia of `L`

.

`is_positive_definite`

— Method`is_positive_definite(L::AbstractLat) -> Bool`

Return whether the rational span of the lattice `L`

is positive definite.

`is_negative_definite`

— Method`is_negative_definite(L::AbstractLat) -> Bool`

Return whether the rational span of the lattice `L`

is negative definite.

`is_definite`

— Method`is_definite(L::AbstractLat) -> Bool`

Return whether the rational span of the lattice `L`

is definite.

## Isometries

`automorphism_group_generators`

— Method`automorphism_group_generators(E::EllCrv) -> Vector{EllCrvIso}`

Return generators of the automorphism group of $E$.

```
automorphism_group_generators(L::AbstractLat; ambient_representation::Bool = true)
-> Vector{MatElem}
```

Given a definite lattice `L`

, return generators for the automorphism group of `L`

. If `ambient_representation == true`

(the default), the transformations are represented with respect to the ambient space of `L`

. Otherwise, the transformations are represented with respect to the (pseudo-)basis of `L`

.

`automorphism_group_order`

— Method`automorphism_group_order(L::AbstractLat) -> Int`

Given a definite lattice `L`

, return the order of the automorphism group of `L`

.

`is_isometric`

— Method`is_isometric(L::AbstractLat, M::AbstractLat) -> Bool`

Return whether the lattices `L`

and `M`

are isometric.

`is_locally_isometric`

— Method`is_locally_isometric(L::ZZLat, M::ZZLat, p::Int) -> Bool`

Return whether `L`

and `M`

are isometric over the `p`

-adic integers.

i.e. whether $L \otimes \mathbb{Z}_p \cong M\otimes \mathbb{Z}_p$.

# Root lattices

`root_lattice_recognition`

— Method`root_lattice_recognition(L::ZZLat)`

Return the ADE type of the root sublattice of `L`

.

Input:

`L`

– a definite and integral $\mathbb{Z}$-lattice.

Output:

Two lists, the first one containing the ADE types and the second one the irreducible root sublattices.

For more recognizable gram matrices use `root_lattice_recognition_fundamental`

.

**Examples**

```
julia> L = integer_lattice(gram=ZZ[4 0 0 0 3 0 3 0;
0 16 8 12 2 12 6 10;
0 8 8 6 2 8 4 5;
0 12 6 10 2 9 5 8;
3 2 2 2 4 2 4 2;
0 12 8 9 2 12 6 9;
3 6 4 5 4 6 6 5;
0 10 5 8 2 9 5 8])
Integer lattice of rank 8 and degree 8
with gram matrix
[4 0 0 0 3 0 3 0]
[0 16 8 12 2 12 6 10]
[0 8 8 6 2 8 4 5]
[0 12 6 10 2 9 5 8]
[3 2 2 2 4 2 4 2]
[0 12 8 9 2 12 6 9]
[3 6 4 5 4 6 6 5]
[0 10 5 8 2 9 5 8]
julia> R = root_lattice_recognition(L)
([(:A, 1), (:D, 6)], ZZLat[Integer lattice of rank 1 and degree 8, Integer lattice of rank 6 and degree 8])
```

`root_lattice_recognition_fundamental`

— Method`root_lattice_recognition_fundamental(L::ZZLat)`

Return the ADE type of the root sublattice of `L`

as well as the corresponding irreducible root sublattices with basis given by a fundamental root system.

Input:

`L`

– a definite and integral $\mathbb Z$-lattice.

Output:

- the root sublattice, with basis given by a fundamental root system
- the ADE types
- a Vector consisting of the irreducible root sublattices.

**Examples**

```
julia> L = integer_lattice(gram=ZZ[4 0 0 0 3 0 3 0;
0 16 8 12 2 12 6 10;
0 8 8 6 2 8 4 5;
0 12 6 10 2 9 5 8;
3 2 2 2 4 2 4 2;
0 12 8 9 2 12 6 9;
3 6 4 5 4 6 6 5;
0 10 5 8 2 9 5 8])
Integer lattice of rank 8 and degree 8
with gram matrix
[4 0 0 0 3 0 3 0]
[0 16 8 12 2 12 6 10]
[0 8 8 6 2 8 4 5]
[0 12 6 10 2 9 5 8]
[3 2 2 2 4 2 4 2]
[0 12 8 9 2 12 6 9]
[3 6 4 5 4 6 6 5]
[0 10 5 8 2 9 5 8]
julia> R = root_lattice_recognition_fundamental(L);
julia> gram_matrix(R[1])
[2 0 0 0 0 0 0]
[0 2 0 -1 0 0 0]
[0 0 2 -1 0 0 0]
[0 -1 -1 2 -1 0 0]
[0 0 0 -1 2 -1 0]
[0 0 0 0 -1 2 -1]
[0 0 0 0 0 -1 2]
```

`ADE_type`

— Method`ADE_type(G::MatrixElem) -> Tuple{Symbol,Int64}`

Return the type of the irreducible root lattice with gram matrix `G`

.

See also `root_lattice_recognition`

.

**Examples**

```
julia> Hecke.ADE_type(gram_matrix(root_lattice(:A,3)))
(:A, 3)
```

`coxeter_number`

— Method`coxeter_number(ADE::Symbol, n) -> Int`

Return the Coxeter number of the corresponding ADE root lattice.

If $L$ is a root lattice and $R$ its set of roots, then the Coxeter number $h$ is $|R|/n$ where `n`

is the rank of $L$.

**Examples**

```
julia> coxeter_number(:D, 4)
6
```

`highest_root`

— Method`highest_root(ADE::Symbol, n) -> ZZMatrix`

Return coordinates of the highest root of `root_lattice(ADE, n)`

.

**Examples**

```
julia> highest_root(:E, 6)
[1 2 3 2 1 2]
```

## Module operations

Most module operations assume that the lattices live in the same ambient space. For instance only lattices in the same ambient space compare.

`==`

— MethodReturn `true`

if both lattices have the same ambient quadratic space and the same underlying module.

`is_sublattice`

— Method`is_sublattice(L::AbstractLat, M::AbstractLat) -> Bool`

Return whether `M`

is a sublattice of the lattice `L`

.

`is_sublattice_with_relations`

— Method`is_sublattice_with_relations(M::ZZLat, N::ZZLat) -> Bool, QQMatrix`

Returns whether $N$ is a sublattice of $M$. In this case, the second return value is a matrix $B$ such that $B B_M = B_N$, where $B_M$ and $B_N$ are the basis matrices of $M$ and $N$ respectively.

`+`

— Method`+(L::AbstractLat, M::AbstractLat) -> AbstractLat`

Return the sum of the lattices `L`

and `M`

.

The lattices `L`

and `M`

must have the same ambient space.

`*`

— Method`*(a::RationalUnion, L::ZZLat) -> ZZLat`

Return the lattice $aM$ inside the ambient space of $M$.

`intersect`

— Method`intersect(L::AbstractLat, M::AbstractLat) -> AbstractLat`

Return the intersection of the lattices `L`

and `M`

.

The lattices `L`

and `M`

must have the same ambient space.

`in`

— Method`Base.in(v::Vector, L::ZZLat) -> Bool`

Return whether the vector `v`

lies in the lattice `L`

.

`in`

— Method`Base.in(v::QQMatrix, L::ZZLat) -> Bool`

Return whether the row span of `v`

lies in the lattice `L`

.

`primitive_closure`

— Method`primitive_closure(M::ZZLat, N::ZZLat) -> ZZLat`

Given two $\mathbb Z$-lattices `M`

and `N`

with $N \subseteq \mathbb{Q} M$, return the primitive closure $M \cap \mathbb{Q} N$ of `N`

in `M`

.

**Examples**

```
julia> M = root_lattice(:D, 6);
julia> N = lattice_in_same_ambient_space(M, 3*basis_matrix(M)[1,:]);
julia> basis_matrix(N)
[3 0 0 0 0 0]
julia> N2 = primitive_closure(M, N)
Integer lattice of rank 1 and degree 6
with gram matrix
[2]
julia> basis_matrix(N2)
[1 0 0 0 0 0]
julia> M2 = primitive_closure(dual(M), M);
julia> is_integral(M2)
false
```

`is_primitive`

— Method`is_primitive(M::ZZLat, N::ZZLat) -> Bool`

Given two $\mathbb Z$-lattices $N \subseteq M$, return whether `N`

is a primitive sublattice of `M`

.

**Examples**

```
julia> U = hyperbolic_plane_lattice(3);
julia> bU = basis_matrix(U);
julia> e1, e2 = bU[1,:], bU[2,:]
([1 0], [0 1])
julia> N = lattice_in_same_ambient_space(U, e1 + e2)
Integer lattice of rank 1 and degree 2
with gram matrix
[6]
julia> is_primitive(U, N)
true
julia> M = root_lattice(:A, 3);
julia> f = matrix(QQ, 3, 3, [0 1 1; -1 -1 -1; 1 1 0]);
julia> N = kernel_lattice(M, f+1)
Integer lattice of rank 1 and degree 3
with gram matrix
[4]
julia> is_primitive(M, N)
true
```

`is_primitive`

— Method`is_primitive(L::ZZLat, v::Union{Vector, QQMatrix}) -> Bool`

Return whether the vector `v`

is primitive in `L`

.

A vector `v`

in a $\mathbb Z$-lattice `L`

is called primitive if for all `w`

in `L`

such that $v = dw$ for some integer `d`

, then $d = \pm 1$.

`divisibility`

— Method`divisibility(L::ZZLat, v::Union{Vector, QQMatrix}) -> QQFieldElem`

Return the divisibility of `v`

with respect to `L`

.

For a vector `v`

in the ambient quadratic space $(V, \Phi)$ of `L`

, we call the divisibility of `v`

with the respect to `L`

the non-negative generator of the fractional $\mathbb Z$-ideal $\Phi(v, L)$.

## Embeddings

### Categorical constructions

`direct_sum`

— Method```
direct_sum(x::Vararg{ZZLat}) -> ZZLat, Vector{AbstractSpaceMor}
direct_sum(x::Vector{ZZLat}) -> ZZLat, Vector{AbstractSpaceMor}
```

Given a collection of $\mathbb Z$-lattices $L_1, \ldots, L_n$, return their direct sum $L := L_1 \oplus \ldots \oplus L_n$, together with the injections $L_i \to L$. (seen as maps between the corresponding ambient spaces).

For objects of type `ZZLat`

, finite direct sums and finite direct products agree and they are therefore called biproducts. If one wants to obtain `L`

as a direct product with the projections $L \to L_i$, one should call `direct_product(x)`

. If one wants to obtain `L`

as a biproduct with the injections $L_i \to L$ and the projections $L \to L_i$, one should call `biproduct(x)`

.

`direct_product`

— Method```
direct_product(x::Vararg{ZZLat}) -> ZZLat, Vector{AbstractSpaceMor}
direct_product(x::Vector{ZZLat}) -> ZZLat, Vector{AbstractSpaceMor}
```

Given a collection of $\mathbb Z$-lattices $L_1, \ldots, L_n$, return their direct product $L := L_1 \times \ldots \times L_n$, together with the projections $L \to L_i$. (seen as maps between the corresponding ambient spaces).

For objects of type `ZZLat`

, finite direct sums and finite direct products agree and they are therefore called biproducts. If one wants to obtain `L`

as a direct sum with the injections $L_i \to L$, one should call `direct_sum(x)`

. If one wants to obtain `L`

as a biproduct with the injections $L_i \to L$ and the projections $L \to L_i$, one should call `biproduct(x)`

.

`biproduct`

— Method```
biproduct(x::Vararg{ZZLat}) -> ZZLat, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor}
biproduct(x::Vector{ZZLat}) -> ZZLat, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor}
```

Given a collection of $\mathbb Z$-lattices $L_1, \ldots, L_n$, return their biproduct $L := L_1 \oplus \ldots \oplus L_n$, together with the injections $L_i \to L$ and the projections $L \to L_i$. (seen as maps between the corresponding ambient spaces).

For objects of type `ZZLat`

, finite direct sums and finite direct products agree and they are therefore called biproducts. If one wants to obtain `L`

as a direct sum with the injections $L_i \to L$, one should call `direct_sum(x)`

. If one wants to obtain `L`

as a direct product with the projections $L \to L_i$, one should call `direct_product(x)`

.

### Orthogonal sublattices

`orthogonal_submodule`

— Method`orthogonal_submodule(L::ZZLat, S::ZZLat) -> ZZLat`

Return the largest submodule of `L`

orthogonal to `S`

.

`irreducible_components`

— Method`irreducible_components(L::ZZLat) -> Vector{ZZLat}`

Return the irreducible components $L_i$ of the positive definite lattice $L$.

This yields a maximal orthogonal splitting of `L`

as

\[L = \bigoplus_i L_i.\]

### Dual lattice

`dual`

— Method`dual(L::AbstractLat) -> AbstractLat`

Return the dual lattice of the lattice `L`

.

### Discriminant group

See `discriminant_group(L::ZZLat)`

.

### Overlattices

`glue_map`

— Method```
glue_map(L::ZZLat, S::ZZLat, R::ZZLat; check=true)
-> Tuple{TorQuadModuleMor, TorQuadModuleMor, TorQuadModuleMor}
```

Given three integral $\mathbb Z$-lattices `L`

, `S`

and `R`

, with `S`

and `R`

primitive sublattices of `L`

and such that the sum of the ranks of `S`

and `R`

is equal to the rank of `L`

, return the glue map $\gamma$ of the primitive extension $S+R \subseteq L$, as well as the inclusion maps of the domain and codomain of $\gamma$ into the respective discriminant groups of `S`

and `R`

.

**Example**

```
julia> M = root_lattice(:E,8);
julia> f = matrix(QQ, 8, 8, [-1 -1 0 0 0 0 0 0;
1 0 0 0 0 0 0 0;
0 1 1 0 0 0 0 0;
0 0 0 1 0 0 0 0;
0 0 0 0 1 0 0 0;
0 0 0 0 0 1 1 0;
-2 -4 -6 -5 -4 -3 -2 -3;
0 0 0 0 0 0 0 1]);
julia> S = kernel_lattice(M ,f-1)
Integer lattice of rank 4 and degree 8
with gram matrix
[12 -3 0 -3]
[-3 2 -1 0]
[ 0 -1 2 0]
[-3 0 0 2]
julia> R = kernel_lattice(M , f^2+f+1)
Integer lattice of rank 4 and degree 8
with gram matrix
[ 2 -1 0 0]
[-1 2 -6 0]
[ 0 -6 30 -3]
[ 0 0 -3 2]
julia> glue, iS, iR = glue_map(M, S, R)
(Map with following data
Domain:
=======
Finite quadratic module: (Z/3)^2 -> Q/2Z
Codomain:
=========
Finite quadratic module: (Z/3)^2 -> Q/2Z, Map with following data
Domain:
=======
Finite quadratic module: (Z/3)^2 -> Q/2Z
Codomain:
=========
Finite quadratic module: (Z/3)^2 -> Q/2Z, Map with following data
Domain:
=======
Finite quadratic module: (Z/3)^2 -> Q/2Z
Codomain:
=========
Finite quadratic module: (Z/3)^2 -> Q/2Z)
julia> is_bijective(glue)
true
```

`overlattice`

— Method`overlattice(glue_map::TorQuadModuleMor) -> ZZLat`

Given the glue map of a primitive extension of $\mathbb Z$-lattices $S+R \subseteq L$, return `L`

.

**Example**

```
julia> M = root_lattice(:E,8);
julia> f = matrix(QQ, 8, 8, [ 1 0 0 0 0 0 0 0;
0 1 0 0 0 0 0 0;
1 2 4 4 3 2 1 2;
-2 -4 -6 -5 -4 -3 -2 -3;
2 4 6 4 3 2 1 3;
-1 -2 -3 -2 -1 0 0 -2;
0 0 0 0 0 -1 0 0;
-1 -2 -3 -3 -2 -1 0 -1]);
julia> S = kernel_lattice(M ,f-1)
Integer lattice of rank 4 and degree 8
with gram matrix
[ 2 -1 0 0]
[-1 2 -1 0]
[ 0 -1 12 -15]
[ 0 0 -15 20]
julia> R = kernel_lattice(M , f^4+f^3+f^2+f+1)
Integer lattice of rank 4 and degree 8
with gram matrix
[10 -4 0 1]
[-4 2 -1 0]
[ 0 -1 4 -3]
[ 1 0 -3 4]
julia> glue, iS, iR = glue_map(M, S, R);
julia> overlattice(glue) == M
true
```

`local_modification`

— Method`local_modification(M::ZZLat, L::ZZLat, p)`

Return a local modification of `M`

that matches `L`

at `p`

.

INPUT:

- $M$ – a
`\mathbb{Z}_p`

-maximal lattice - $L$ – the a lattice isomorphic to
`M`

over`\QQ_p`

- $p$ – a prime number

OUTPUT:

an integral lattice `M'`

in the ambient space of `M`

such that `M`

and `M'`

are locally equal at all completions except at `p`

where `M'`

is locally isometric to the lattice `L`

.

`maximal_integral_lattice`

— Method`maximal_integral_lattice(L::AbstractLat) -> AbstractLat`

Given a lattice `L`

, return a lattice `M`

in the ambient space of `L`

which is maximal integral and which contains `L`

.

### Sublattices defined by endomorphisms

`kernel_lattice`

— Method```
kernel_lattice(L::ZZLat, f::MatElem;
ambient_representation::Bool = true) -> ZZLat
```

Given a $\mathbf{Z}$-lattice $L$ and a matrix $f$ inducing an endomorphism of $L$, return $\ker(f)$ is a sublattice of $L$.

If `ambient_representation`

is `true`

(the default), the endomorphism is represented with respect to the ambient space of $L$. Otherwise, the endomorphism is represented with respect to the basis of $L$.

`invariant_lattice`

— Method```
invariant_lattice(L::ZZLat, G::Vector{MatElem};
ambient_representation::Bool = true) -> ZZLat
invariant_lattice(L::ZZLat, G::MatElem;
ambient_representation::Bool = true) -> ZZLat
```

Given a $\mathbf{Z}$-lattice $L$ and a list of matrices $G$ inducing endomorphisms of $L$ (or just one matrix $G$), return the lattice $L^G$, consisting on elements fixed by $G$.

If `ambient_representation`

is `true`

(the default), the endomorphism is represented with respect to the ambient space of $L$. Otherwise, the endomorphism is represented with respect to the basis of $L$.

`coinvariant_lattice`

— Method```
coinvariant_lattice(L::ZZLat, G::Vector{MatElem};
ambient_representation::Bool = true) -> ZZLat
coinvariant_lattice(L::ZZLat, G::MatElem;
ambient_representation::Bool = true) -> ZZLat
```

Given a $\mathbf{Z}$-lattice $L$ and a list of matrices $G$ inducing endomorphisms of $L$ (or just one matrix $G$), return the orthogonal complement $L_G$ in $L$ of the fixed lattice $L^G$ (see `invariant_lattice`

).

If `ambient_representation`

is `true`

(the default), the endomorphism is represented with respect to the ambient space of $L$. Otherwise, the endomorphism is represented with respect to the basis of $L$.

### Computing embeddings

`embed`

— Method`embed(S::ZZLat, G::Genus, primitive::Bool=true) -> Bool, embedding`

Return a (primitive) embedding of the integral lattice `S`

into some lattice in the genus of `G`

.

```
julia> G = integer_genera((8,0), 1, even=true)[1];
julia> L, S, i = embed(root_lattice(:A,5), G);
```

`embed_in_unimodular`

— Method`embed_in_unimodular(S::ZZLat, pos::Int, neg::Int, primitive=true, even=true) -> Bool, L, S', iS, iR`

Return a (primitive) embedding of the integral lattice `S`

into some (even) unimodular lattice of signature `(pos, neg)`

.

For now this works only for even lattices.

```
julia> NS = direct_sum(integer_lattice(:U), rescale(root_lattice(:A, 16), -1))[1];
julia> LK3, iNS, i = embed_in_unimodular(NS, 3, 19);
julia> genus(LK3)
Genus symbol for integer lattices
Signatures: (3, 0, 19)
Local symbol:
Local genus symbol at 2: 1^22
julia> iNS
Integer lattice of rank 18 and degree 22
with gram matrix
[0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 -2 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 1 -2 1 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 1 -2 1 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 1 -2 1 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 1 -2 1 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 1 -2 1 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 1 -2 1 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 1 -2 1 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 1 -2 1 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 1 -2 1 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 1 -2 1 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 1 -2 1 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 1 -2 1 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 -2 1 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 -2 1]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 -2]
julia> is_primitive(LK3, iNS)
true
```

## LLL, Short and Close Vectors

### LLL and indefinite LLL

`lll`

— Method`lll(L::ZZLat, same_ambient::Bool = true) -> ZZLat`

Given an integral $\mathbb Z$-lattice `L`

with basis matrix `B`

, compute a basis `C`

of `L`

such that the gram matrix $G_C$ of `L`

with respect to `C`

is LLL-reduced.

By default, it creates the lattice in the same ambient space as `L`

. This can be disabled by setting `same_ambient = false`

. Works with both definite and indefinite lattices.

### Short Vectors

`short_vectors`

— Function```
short_vectors(L::ZZLat, [lb = 0], ub, [elem_type = ZZRingElem]; check::Bool = true)
-> Vector{Tuple{Vector{elem_type}, QQFieldElem}}
```

Returns all tuples `(v, n)`

such that `n = v G v^t`

satisfies `lb <= n <= ub`

, where `G`

is the Gram matrix of `L`

and `v`

is non-zero.

Note that the vectors are computed up to sign (so only one of `v`

and `-v`

appears).

It is assumed and checked that `L`

is positive definite.

See also `short_vectors_iterator`

for an iterator version.

`shortest_vectors`

— Function```
shortest_vectors(L::ZZLat, [elem_type = ZZRingElem]; check::Bool = true)
-> QQFieldElem, Vector{elem_type}, QQFieldElem}
```

Returns the list of shortest non-zero vectors. Note that the vectors are computed up to sign (so only one of `v`

and `-v`

appears).

It is assumed and checked that `L`

is positive definite.

See also `minimum`

.

`short_vectors_iterator`

— Function```
short_vectors_iterator(L::ZZLat, [lb = 0], ub,
[elem_type = ZZRingElem]; check::Bool = true)
-> Tuple{Vector{elem_type}, QQFieldElem} (iterator)
```

Returns an iterator for all tuples `(v, n)`

such that `n = v G v^t`

satisfies `lb <= n <= ub`

, where `G`

is the Gram matrix of `L`

and `v`

is non-zero.

Note that the vectors are computed up to sign (so only one of `v`

and `-v`

appears).

It is assumed and checked that `L`

is positive definite.

See also `short_vectors`

.

`minimum`

— Method`minimum(L::ZZLat) -> QQFieldElem`

Return the minimum squared length among the non-zero vectors in `L`

.

`kissing_number`

— Method`kissing_number(L::ZZLat) -> Int`

Return the Kissing number of the sphere packing defined by `L`

.

This is the number of non-overlapping spheres touching any other given sphere.

### Close Vectors

`close_vectors`

— Method```
close_vectors(L:ZZLat, v:Vector, [lb,], ub; check::Bool = false)
-> Vector{Tuple{Vector{Int}}, QQFieldElem}
```

Return all tuples `(x, d)`

where `x`

is an element of `L`

such that `d = b(v - x, v - x) <= ub`

. If `lb`

is provided, then also `lb <= d`

.

If `filter`

is not `nothing`

, then only those `x`

with `filter(x)`

evaluating to `true`

are returned.

By default, it will be checked whether `L`

is positive definite. This can be disabled setting `check = false`

.

Both input and output are with respect to the basis matrix of `L`

.

**Examples**

```
julia> L = integer_lattice(matrix(QQ, 2, 2, [1, 0, 0, 2]));
julia> close_vectors(L, [1, 1], 1)
3-element Vector{Tuple{Vector{ZZRingElem}, QQFieldElem}}:
([2, 1], 1)
([0, 1], 1)
([1, 1], 0)
julia> close_vectors(L, [1, 1], 1, 1)
2-element Vector{Tuple{Vector{ZZRingElem}, QQFieldElem}}:
([2, 1], 1)
([0, 1], 1)
```