# Lattices

## Creation of lattices

### Inside a given ambient space

`lattice`

— Method`lattice(V::AbstractSpace) -> AbstractLat`

Given an ambient space `V`

, return the lattice with the standard basis matrix. If `V`

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

`lattice`

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

Given an ambient space `V`

and a pseudo-matrix `B`

, return the lattice spanned by the pseudo-matrix `B`

inside `V`

. If `V`

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

By default, `B`

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

to false.

`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.

`lattice`

— Method`lattice(V::AbstractSpace, gens::Vector) -> AbstractLat`

Given an ambient space `V`

and a list of generators `gens`

, return the lattice spanned by `gens`

in `V`

. If `V`

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

If `gens`

is empty, the function returns the zero lattice in `V`

.

### Quadratic lattice over a number field

`quadratic_lattice`

— Method`quadratic_lattice(K::Field ; gram::MatElem) -> Union{ZZLat, QuadLat}`

Given a matrix `gram`

and a field `K`

, return the free quadratic lattice inside the quadratic space over `K`

with Gram matrix `gram`

.

If $K = \mathbb{Q}$, then the output lattice is of type `ZZLat`

, seen as a lattice over the ring $\mathbb{Z}$.

`quadratic_lattice`

— Method```
quadratic_lattice(K::Field, B::PMat ; gram = nothing,
check:::Bool = true) -> QuadLat
```

Given a pseudo-matrix `B`

with entries in a field `K`

return the quadratic lattice spanned by the pseudo-matrix `B`

inside the quadratic space over `K`

with Gram matrix `gram`

.

If `gram`

is not supplied, the Gram matrix of the ambient space will be the identity matrix over `K`

of size the number of columns of `B`

.

By default, `B`

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

to false.

`quadratic_lattice`

— Method```
quadratic_lattice(K::Field, basis::MatElem ; gram = nothing,
check::Bool = true)
-> Union{ZZLat, QuadLat}
```

Given a matrix `basis`

and a field `K`

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

inside the quadratic space over `K`

with Gram matrix `gram`

.

If `gram`

is not supplied, the Gram matrix of the ambient space will be the identity matrix over `K`

of size the number of columns of `basis`

.

By default, `basis`

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

to false.

If $K = \mathbb{Q}$, then the output lattice is of type `ZZLat`

, seen as a lattice over the ring $\mathbb{Z}$.

`quadratic_lattice`

— Method`quadratic_lattice(K::Field, gens::Vector ; gram = nothing) -> Union{ZZLat, QuadLat}`

Given a list of vectors `gens`

and a field `K`

, return the quadratic lattice spanned by the elements of `gens`

inside the quadratic space over `K`

with Gram matrix `gram`

.

If `gram`

is not supplied, the Gram matrix of the ambient space will be the identity matrix over `K`

of size the length of the elements of `gens`

.

If `gens`

is empty, `gram`

must be supplied and the function returns the zero lattice in the quadratic space over `K`

with gram matrix `gram`

.

If $K = \mathbb{Q}$, then the output lattice is of type `ZZLat`

, seen as a lattice over the ring $\mathbb{Z}$.

### Hermitian lattice over a degree 2 extension

`hermitian_lattice`

— Method`hermitian_lattice(E::NumField; gram::MatElem) -> HermLat`

Given a matrix `gram`

and a number field `E`

of degree 2, return the free hermitian lattice inside the hermitian space over `E`

with Gram matrix `gram`

.

`hermitian_lattice`

— Method```
hermitian_lattice(E::NumField, B::PMat; gram = nothing,
check::Bool = true) -> HermLat
```

Given a pseudo-matrix `B`

with entries in a number field `E`

of degree 2, return the hermitian lattice spanned by the pseudo-matrix `B`

inside the hermitian space over `E`

with Gram matrix `gram`

.

If `gram`

is not supplied, the Gram matrix of the ambient space will be the identity matrix over `E`

of size the number of columns of `B`

.

By default, `B`

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

to false.

`hermitian_lattice`

— Method```
hermitian_lattice(E::NumField, basis::MatElem; gram = nothing,
check::Bool = true) -> HermLat
```

Given a matrix `basis`

and a number field `E`

of degree 2, return the hermitian lattice spanned by the rows of `basis`

inside the hermitian space over `E`

with Gram matrix `gram`

.

If `gram`

is not supplied, the Gram matrix of the ambient space will be the identity matrix over `E`

of size the number of columns of `basis`

.

By default, `basis`

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

to false.

`hermitian_lattice`

— Method`hermitian_lattice(E::NumField, gens::Vector ; gram = nothing) -> HermLat`

Given a list of vectors `gens`

and a number field `E`

of degree 2, return the hermitian lattice spanned by the elements of `gens`

inside the hermitian space over `E`

with Gram matrix `gram`

.

If `gram`

is not supplied, the Gram matrix of the ambient space will be the identity matrix over `E`

of size the length of the elements of `gens`

.

If `gens`

is empty, `gram`

must be supplied and the function returns the zero lattice in the hermitan space over `E`

with Gram matrix `gram`

.

#### Examples

The two following examples will be used all along this section:

`julia> K, a = rationals_as_number_field();`

`julia> Kt, t = K["t"];`

`julia> g = t^2 + 7;`

`julia> E, b = number_field(g, "b");`

`julia> D = matrix(K, 3, 3, [2, 0, 0, 0, 2, 0, 0, 0, 2]);`

`julia> gens = Vector{nf_elem}[map(K, [1, 1, 0]), map(K, [1, 0, 1]), map(K, [2, 0, 0])];`

`julia> Lquad = quadratic_lattice(K, gens, gram = D)`

`Quadratic lattice of rank 3 and degree 3 over maximal order of Number field of degree 1 over QQ with basis nf_elem[1]`

`julia> D = matrix(E, 4, 4, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);`

`julia> gens = Vector{Hecke.NfRelElem{nf_elem}}[map(E, [2, -1, 0, 0]), map(E, [-3, 0, -1, 0]), map(E, [0, 0, 0, -1]), map(E, [b, 0, 0, 0])];`

`julia> Lherm = hermitian_lattice(E, gens, gram = D)`

`Hermitian lattice of rank 4 and degree 4 over relative maximal order of Relative number field of degree 2 over number field with pseudo-basis (1, 1//1 * <1, 1>) (b + 1, 1//2 * <1, 1>)`

Note that the format used here is the one given by the internal function `Hecke.to_hecke()`

which prints REPL commands to get back the input lattice.

`julia> K, a = rationals_as_number_field();`

`julia> Kt, t = K["t"];`

`julia> g = t^2 + 7;`

`julia> E, b = number_field(g, "b");`

`julia> D = matrix(E, 4, 4, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);`

`julia> gens = Vector{Hecke.NfRelElem{nf_elem}}[map(E, [2, -1, 0, 0]), map(E, [-3, 0, -1, 0]), map(E, [0, 0, 0, -1]), map(E, [b, 0, 0, 0])];`

`julia> Lherm = hermitian_lattice(E, gens, gram = D);`

`julia> Hecke.to_hecke(Lherm)`

`Qx, x = polynomial_ring(FlintQQ, "x") f = x - 1 K, a = number_field(f, "a", cached = false) Kt, t = polynomial_ring(K, "t") g = t^2 + 7 E, b = number_field(g, "b", cached = false) D = matrix(E, 4, 4, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) gens = Vector{Hecke.NfRelElem{nf_elem}}[map(E, [2, -1, 0, 0]), map(E, [-3, 0, -1, 0]), map(E, [0, 0, 0, -1]), map(E, [b, 0, 0, 0])] L = hermitian_lattice(E, gens, gram = D)`

Finally, one can access some databases in which are stored several quadratic and hermitian lattices. Up to now, these are not automatically available while running Hecke. It can nonethelss be used in the following way:

`julia> qld = Hecke.quadratic_lattice_database()`

`Quadratic lattices of rank >= 3 with class number 1 or 2 Author: Markus Kirschmer Source: http://www.math.rwth-aachen.de/~Markus.Kirschmer/forms/ Version: 0.0.1 Number of lattices: 30250`

`julia> lattice(qld, 1)`

`Quadratic lattice of rank 3 and degree 3 over maximal order of Number field of degree 1 over QQ with basis nf_elem[1]`

`julia> hlb = Hecke.hermitian_lattice_database()`

`Hermitian lattices of rank >= 3 with class number 1 or 2 Author: Markus Kirschmer Source: http://www.math.rwth-aachen.de/~Markus.Kirschmer/forms/ Version: 0.0.1 Number of lattices: 570`

`julia> lattice(hlb, 426)`

`Hermitian lattice of rank 4 and degree 4 over relative maximal order of Relative number field of degree 2 over number field with pseudo-basis (1, 1//1 * <1, 1>) (b + 1, 1//2 * <1, 1>)`

## Ambient space and rational span

`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.

`rational_span`

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

Return the rational span of the lattice `L`

.

`basis_matrix_of_rational_span`

— Method`basis_matrix_of_rational_span(L::AbstractLat) -> MatElem`

Return a basis matrix of the rational span of the lattice `L`

.

`gram_matrix_of_rational_span`

— Method`gram_matrix_of_rational_span(L::AbstractLat) -> MatElem`

Return the Gram matrix of the rational span of the lattice `L`

.

`diagonal_of_rational_span`

— Method`diagonal_of_rational_span(L::AbstractLat) -> Vector`

Return the diagonal of the rational span of the lattice `L`

.

### Examples

`julia> K, a = rationals_as_number_field();`

`julia> Kt, t = K["t"];`

`julia> g = t^2 + 7;`

`julia> E, b = number_field(g, "b");`

`julia> D = matrix(K, 3, 3, [2, 0, 0, 0, 2, 0, 0, 0, 2]);`

`julia> gens = Vector{nf_elem}[map(K, [1, 1, 0]), map(K, [1, 0, 1]), map(K, [2, 0, 0])];`

`julia> Lquad = quadratic_lattice(K, gens, gram = D);`

`julia> D = matrix(E, 4, 4, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);`

`julia> gens = Vector{Hecke.NfRelElem{nf_elem}}[map(E, [2, -1, 0, 0]), map(E, [-3, 0, -1, 0]), map(E, [0, 0, 0, -1]), map(E, [b, 0, 0, 0])];`

`julia> Lherm = hermitian_lattice(E, gens, gram = D);`

`julia> ambient_space(Lherm)`

`Hermitian space of dimension 4 over relative number field with defining polynomial t^2 + 7 over number field with defining polynomial x - 1 over rational field with gram matrix [1 0 0 0] [0 1 0 0] [0 0 1 0] [0 0 0 1]`

`julia> rational_span(Lquad)`

`Quadratic space of dimension 3 over number field of degree 1 over QQ with gram matrix [2 2 2] [2 4 2] [2 2 4]`

`julia> basis_matrix_of_rational_span(Lherm)`

`[1 0 0 0] [5 1 0 0] [3 0 1 0] [0 0 0 1]`

`julia> gram_matrix_of_rational_span(Lherm)`

`[1 5 3 0] [5 26 15 0] [3 15 10 0] [0 0 0 1]`

`julia> diagonal_of_rational_span(Lquad)`

`3-element Vector{nf_elem}: 2 2 2`

## Rational equivalence

`hasse_invariant`

— Method`hasse_invariant(L::AbstractLat, p::Union{InfPlc, NfOrdIdl}) -> Int`

Return the Hasse invariant of the rational span of the lattice `L`

at the place `p`

. The lattice must be quadratic.

`witt_invariant`

— Method`witt_invariant(L::AbstractLat, p::Union{InfPlc, NfOrdIdl}) -> Int`

Return the Witt invariant of the rational span of the lattice `L`

at the place `p`

. The lattice must be quadratic.

`is_rationally_isometric`

— Method```
is_rationally_isometric(L::AbstractLat, M::AbstractLat, p::Union{InfPlc, NfAbsOrdIdl})
-> Bool
```

Return whether the rational spans of the lattices `L`

and `M`

are isometric over the completion at the place `p`

.

`is_rationally_isometric`

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

Return whether the rational spans of the lattices `L`

and `M`

are isometric.

### Examples

For now and for the rest of this section, the examples will include the new lattice `Lquad2`

which is quadratic. Moreover, all the completions are going to be done at the prime ideal $p = 7*\mathcal O_K$.

`julia> K, a = rationals_as_number_field();`

`julia> D = matrix(K, 3, 3, [2, 0, 0, 0, 2, 0, 0, 0, 2]);`

`julia> gens = Vector{nf_elem}[map(K, [1, 1, 0]), map(K, [1, 0, 1]), map(K, [2, 0, 0])];`

`julia> Lquad = quadratic_lattice(K, gens, gram = D);`

`julia> D = matrix(K, 3, 3, [2, 0, 0, 0, 2, 0, 0, 0, 2]);`

`julia> gens = Vector{nf_elem}[map(K, [-35, 25, 0]), map(K, [30, 40, -20]), map(K, [5, 10, -5])];`

`julia> Lquad2 = quadratic_lattice(K, gens, gram = D)`

`Quadratic lattice of rank 3 and degree 3 over maximal order of Number field of degree 1 over QQ with basis nf_elem[1]`

`julia> OK = maximal_order(K);`

`julia> p = prime_decomposition(OK, 7)[1][1]`

`<7, 7> Norm: 7 Minimum: 7 principal generator 7 two normal wrt: 7`

`julia> hasse_invariant(Lquad, p), witt_invariant(Lquad, p)`

`(1, 1)`

`julia> is_rationally_isometric(Lquad, Lquad2, p)`

`true`

`julia> is_rationally_isometric(Lquad, Lquad2)`

`true`

## Attributes

Let $L$ be a lattice over $E/K$. We call a *pseudo-basis* of $L$ any sequence of pairs $(\mathfrak A_i, x_i)_{1 \leq i \leq n}$ where the $\mathfrak A_i$'s are fractional (left) ideals of $\mathcal O_E$ and $(x_i)_{1 \leq i \leq n}$ is a basis of the rational span of $L$, and such that

\[ L = \bigoplus_{i = 1}^n \mathfrak A_ix_i.\]

Note that a pseudo-basis is not unique. Given a pseudo-basis $(\mathfrak A_i, x_i)_{1 \leq i \leq n}$ of $L$, we define the corresponding *pseudo-matrix* of $L$ to be the datum consisting of a list of *coefficient ideals* corresponding to the ideals $\mathfrak A_i$'s and a matrix whose *rows* are the coordinates of the $x_i$'s in the canonical basis of the ambient space of $L$ (conversely, given any such pseudo-matrix, one can define the corresponding pseudo-basis).

`rank`

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

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

.

`degree`

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

Return the dimension of the ambient space of the lattice `L`

.

`discriminant`

— Method`discriminant(L::AbstractLat) -> NfOrdFracIdl`

Return the discriminant of the lattice `L`

, that is, the generalized index ideal $[L^\# : L]$.

`base_field`

— Method`base_field(L::AbstractLat) -> Field`

Return the algebra over which the rational span of the lattice `L`

is defined.

`base_ring`

— Method`base_ring(L::AbstractLat) -> Ring`

Return the order over which the lattice `L`

is defined.

`fixed_field`

— Method`fixed_field(L::AbstractLat) -> Field`

Returns the fixed field of the involution of the lattice `L`

.

`fixed_ring`

— Method`fixed_ring(L::AbstractLat) -> Ring`

Return the maximal order in the fixed field of the lattice `L`

.

`involution`

— Method`involution(L::AbstractLat) -> Map`

Return the involution of the rational span of the lattice `L`

.

`pseudo_matrix`

— Method`pseudo_matrix(L::AbstractLat) -> PMat`

Return a basis pseudo-matrix of the lattice `L`

.

`pseudo_basis`

— Method`pseudo_basis(L::AbstractLat) -> Vector{Tuple{Vector, Ideal}}`

Return a pseudo-basis of the lattice `L`

.

`coefficient_ideals`

— Method`coefficient_ideals(L::AbstractLat) -> Vector{NfOrdIdl}`

Return the coefficient ideals of a pseudo-basis of the lattice `L`

.

`absolute_basis_matrix`

— Method`absolute_basis_matrix(L::AbstractLat) -> MatElem`

Return a $\mathbf{Z}$-basis matrix of the lattice `L`

.

`absolute_basis`

— Method`absolute_basis(L::AbstractLat) -> Vector`

Return a $\mathbf{Z}$-basis of the lattice `L`

.

`generators`

— Method`generators(L::AbstractLat; minimal = false) -> Vector{Vector}`

Return a set of generators of the lattice `L`

over the base ring of `L`

.

If `minimal == true`

, the number of generators is minimal. Note that computing minimal generators is expensive.

`gram_matrix_of_generators`

— Method`gram_matrix_of_generators(L::AbstractLat; minimal::Bool = false) -> MatElem`

Return the Gram matrix of a generating set of the lattice `L`

.

If `minimal == true`

, then a minimal generating set is used. Note that computing minimal generators is expensive.

### Examples

`julia> K, a = rationals_as_number_field();`

`julia> Kt, t = K["t"];`

`julia> g = t^2 + 7;`

`julia> E, b = number_field(g, "b");`

`julia> D = matrix(E, 4, 4, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);`

`julia> Lherm = hermitian_lattice(E, gens, gram = D);`

`julia> rank(Lherm), degree(Lherm)`

`(4, 4)`

`julia> discriminant(Lherm)`

`Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <7, 7>) * [1 0] (1//2 * <7, 7>) * [0 1]`

`julia> base_field(Lherm)`

`Relative number field with defining polynomial t^2 + 7 over number field with defining polynomial x - 1 over rational field`

`julia> base_ring(Lherm)`

`Relative maximal order of Relative number field of degree 2 over number field with pseudo-basis (1, 1//1 * <1, 1>) (b + 1, 1//2 * <1, 1>)`

`julia> fixed_field(Lherm)`

`Number field with defining polynomial x - 1 over rational field`

`julia> fixed_ring(Lherm)`

`Maximal order of Number field of degree 1 over QQ with basis nf_elem[1]`

`julia> involution(Lherm)`

`Map with following data Domain: ======= Relative number field of degree 2 over number field Codomain: ========= Relative number field of degree 2 over number field`

`julia> pseudo_matrix(Lherm)`

`Pseudo-matrix over Relative maximal order of Relative number field of degree 2 over number field with pseudo-basis (1, 1//1 * <1, 1>) (b + 1, 1//2 * <1, 1>) Fractional ideal with row [1 0 0 0] Fractional ideal with row [5 1 0 0] Fractional ideal with row [3 0 1 0] Fractional ideal with row [0 0 0 1]`

`julia> pseudo_basis(Lherm)`

`4-element Vector{Tuple{Vector{Hecke.NfRelElem{nf_elem}}, Hecke.NfRelOrdFracIdl{nf_elem, Hecke.NfAbsOrdFracIdl{AnticNumberField, nf_elem}, Hecke.NfRelElem{nf_elem}}}}: ([1, 0, 0, 0], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <7, 28>) * [1 0] (1//2 * <1, 1>) * [6 1]) ([5, 1, 0, 0], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//2 * <1, 1>) * [0 1]) ([3, 0, 1, 0], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//2 * <1, 1>) * [0 1]) ([0, 0, 0, 1], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//2 * <1, 1>) * [0 1])`

`julia> coefficient_ideals(Lherm)`

`4-element Vector{Hecke.NfRelOrdFracIdl{nf_elem, Hecke.NfAbsOrdFracIdl{AnticNumberField, nf_elem}, Hecke.NfRelElem{nf_elem}}}: Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <7, 28>) * [1 0] (1//2 * <1, 1>) * [6 1] Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//2 * <1, 1>) * [0 1] Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//2 * <1, 1>) * [0 1] Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//2 * <1, 1>) * [0 1]`

`julia> absolute_basis_matrix(Lherm)`

`[ 7 0 0 0] [1//2*b + 7//2 0 0 0] [ 5 1 0 0] [5//2*b + 5//2 1//2*b + 1//2 0 0] [ 3 0 1 0] [3//2*b + 3//2 0 1//2*b + 1//2 0] [ 0 0 0 1] [ 0 0 0 1//2*b + 1//2]`

`julia> absolute_basis(Lherm)`

`8-element Vector{Vector{Hecke.NfRelElem{nf_elem}}}: [7, 0, 0, 0] [1//2*b + 7//2, 0, 0, 0] [5, 1, 0, 0] [5//2*b + 5//2, 1//2*b + 1//2, 0, 0] [3, 0, 1, 0] [3//2*b + 3//2, 0, 1//2*b + 1//2, 0] [0, 0, 0, 1] [0, 0, 0, 1//2*b + 1//2]`

`julia> generators(Lherm)`

`4-element Vector{Vector{Hecke.NfRelElem{nf_elem}}}: [2, -1, 0, 0] [-3, 0, -1, 0] [0, 0, 0, -1] [b, 0, 0, 0]`

`julia> gram_matrix_of_generators(Lherm)`

`[ 5 -6 0 -2*b] [ -6 10 0 3*b] [ 0 0 1 0] [2*b -3*b 0 7]`

## Module operations

Let $L$ be a lattice over $E/K$ inside the space $(V, \Phi)$. The *dual lattice* of $L$ is defined to be the following lattice over $E/K$ in $(V, \Phi)$:

\[ L^{\#} = \left\{ x \in V \mid \Phi(x,L) \subseteq \mathcal O_E \right\}.\]

For any fractional (left) ideal $\mathfrak a$ of $\mathcal O_E$, one can define the lattice $\mathfrak aL$ to be the lattice over $E/K$, in the same space $(V, \Phi)$, obtained by rescaling the coefficient ideals of a pseudo-basis of $L$ by $\mathfrak a$. In another flavour, for any non-zero element $a \in K$, one defines the *rescaled lattice* $L^a$ to be the lattice over $E/K$ with the same underlying module as $L$ (i.e. the same pseudo-bases) but in space $(V, a\Phi)$.

`+`

— 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::NumFieldElem, L::AbstractLat) -> AbstractLat`

Return the lattice $aL$ inside the ambient space of the lattice `L`

.

`*`

— Method`*(a::NumFieldOrdIdl, L::AbstractLat) -> AbstractLat`

Return the lattice $aL$ inside the ambient space of the lattice `L`

.

`*`

— Method`*(a::NumFieldOrdFracIdl, L::AbstractLat) -> AbstractLat`

Return the lattice $aL$ inside the ambient space of the lattice `L`

.

`rescale`

— Method`rescale(L::AbstractLat, a::NumFieldElem) -> AbstractLat`

Return the rescaled lattice $L^a$. Note that this has a different ambient space than the lattice `L`

.

`dual`

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

Return the dual lattice of the lattice `L`

.

`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.

`primitive_closure`

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

Given two lattices `M`

and `N`

defined over a number field `E`

, with $N \subseteq E\otimes M$, return the primitive closure $M \cap E\otimes N$ of `N`

in `M`

.

One can also use the alias `saturate(L, M)`

.

`orthogonal_submodule`

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

Return the largest submodule of `L`

orthogonal to `M`

.

### Examples

`julia> K, a = rationals_as_number_field();`

`julia> D = matrix(K, 3, 3, [2, 0, 0, 0, 2, 0, 0, 0, 2]);`

`julia> gens = Vector{nf_elem}[map(K, [1, 1, 0]), map(K, [1, 0, 1]), map(K, [2, 0, 0])];`

`julia> Lquad = quadratic_lattice(K, gens, gram = D);`

`julia> D = matrix(K, 3, 3, [2, 0, 0, 0, 2, 0, 0, 0, 2]);`

`julia> gens = Vector{nf_elem}[map(K, [-35, 25, 0]), map(K, [30, 40, -20]), map(K, [5, 10, -5])];`

`julia> Lquad2 = quadratic_lattice(K, gens, gram = D);`

`julia> OK = maximal_order(K);`

`julia> p = prime_decomposition(OK, 7)[1][1];`

`julia> pseudo_matrix(Lquad + Lquad2)`

`Pseudo-matrix over Maximal order of Number field of degree 1 over QQ with basis nf_elem[1] 1//1 * <2, 2> with row [1 0 0] 1//1 * <1, 1> with row [1 1 0] 1//1 * <1, 1> with row [1 0 1]`

`julia> pseudo_matrix(intersect(Lquad, Lquad2))`

`Pseudo-matrix over Maximal order of Number field of degree 1 over QQ with basis nf_elem[1] 1//1 * <10, 10> with row [1 0 0] 1//1 * <25, 25> with row [1//5 1 0] 1//1 * <5, 5> with row [0 3 1]`

`julia> pseudo_matrix(p*Lquad)`

`Pseudo-matrix over Maximal order of Number field of degree 1 over QQ with basis nf_elem[1] 1//1 * <14, 126> with row [1 0 0] 1//1 * <7, 7> with row [1 1 0] 1//1 * <7, 7> with row [1 0 1]`

`julia> ambient_space(rescale(Lquad,3*a))`

`Quadratic space of dimension 3 over number field of degree 1 over QQ with gram matrix [6 0 0] [0 6 0] [0 0 6]`

`julia> pseudo_matrix(Lquad)`

`Pseudo-matrix over Maximal order of Number field of degree 1 over QQ with basis nf_elem[1] 1//1 * <2, 2> with row [1 0 0] 1//1 * <1, 1> with row [1 1 0] 1//1 * <1, 1> with row [1 0 1]`

## Categorical constructions

Given finite collections of lattices, one can construct their direct sums, which are also direct products in this context. They are also sometimes called biproducts. Depending on the user usage, it is possible to call one of the following functions.

`direct_sum`

— Method`direct_sum(M::ModuleFP{T}...; task::Symbol = :sum) where T`

Given modules $M_1\dots M_n$, say, return the direct sum $\bigoplus_{i=1}^n M_i$.

Additionally, return

- a vector containing the canonical injections $M_i\to\bigoplus_{i=1}^n M_i$ if
`task = :sum`

(default), - a vector containing the canonical projections $\bigoplus_{i=1}^n M_i\to M_i$ if
`task = :prod`

, - two vectors containing the canonical injections and projections, respectively, if
`task = :both`

, - none of the above maps if
`task = :none`

.

```
direct_sum(x::Vararg{T}) where T <: AbstractLat -> T, Vector{AbstractSpaceMor}
direct_sum(x::Vector{T}) where T <: AbstractLat -> T, Vector{AbstractSpaceMor}
```

Given a collection of quadratic or hermitian 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 `AbstractLat`

, 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_sum(g1::QuadSpaceCls, g2::QuadSpaceCls) -> QuadSpaceCls`

Return the isometry class of the direct sum of two representatives.

`direct_product`

— Method`direct_product(F::FreeMod{T}...; task::Symbol = :prod) where T`

Given free modules $F_1\dots F_n$, say, return the direct product $\prod_{i=1}^n F_i$.

Additionally, return

- a vector containing the canonical projections $\prod_{i=1}^n F_i\to F_i$ if
`task = :prod`

(default), - a vector containing the canonical injections $F_i\to\prod_{i=1}^n F_i$ if
`task = :sum`

, - two vectors containing the canonical projections and injections, respectively, if
`task = :both`

, - none of the above maps if
`task = :none`

.

`direct_product(M::ModuleFP{T}...; task::Symbol = :prod) where T`

Given modules $M_1\dots M_n$, say, return the direct product $\prod_{i=1}^n M_i$.

Additionally, return

- a vector containing the canonical projections $\prod_{i=1}^n M_i\to M_i$ if
`task = :prod`

(default), - a vector containing the canonical injections $M_i\to\prod_{i=1}^n M_i$ if
`task = :sum`

, - two vectors containing the canonical projections and injections, respectively, if
`task = :both`

, - none of the above maps if
`task = :none`

.

```
direct_product(algebras::AlgAss...; task::Symbol = :sum)
-> AlgAss, Vector{AbsAlgAssMor}, Vector{AbsAlgAssMor}
direct_product(algebras::Vector{AlgAss}; task::Symbol = :sum)
-> AlgAss, Vector{AbsAlgAssMor}, Vector{AbsAlgAssMor}
```

Returns the algebra $A = A_1 \times \cdots \times A_k$. `task`

can be ":sum", ":prod", ":both" or ":none" and determines which canonical maps are computed as well: ":sum" for the injections, ":prod" for the projections.

```
direct_product(x::Vararg{T}) where T <: AbstractLat -> T, Vector{AbstractSpaceMor}
direct_product(x::Vector{T}) where T <: AbstractLat -> T, Vector{AbstractSpaceMor}
```

Given a collection of quadratic or hermitian 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 `AbstractLat`

, 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{T}) where T <: AbstractLat -> T, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor}
biproduct(x::Vector{T}) where T <: AbstractLat -> T, Vector{AbstractSpaceMor}, Vector{AbstractSpaceMor}
```

Given a collection of quadratic or hermitian 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 `AbstractLat`

, 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)`

.

## Invariants

Let $L$ be a lattice over $E/K$, in the space $(V, \Phi)$. We define:

- the
*norm*$\mathfrak n(L)$ of $L$ to be the ideal of $\mathcal O_K$ generated by the squares $\left\{\Phi(x,x) \mid x \in L \right\}$; - the
*scale*$\mathfrak s(L)$ of $L$ to be the set $\Phi(L,L) = \left\{\Phi(x,y) \mid x,y \in L \right\}$; - the
*volume*$\mathfrak v(L)$ of $L$ to be the index ideal

\[ \lbrack L^{\#} \colon L \rbrack_{\mathcal O_E} := \langle \left\{ \sigma \mid \sigma \in \text{Hom}_{\mathcal O_E}(L^{\#}, L) \right\} \rangle_{\mathcal O_E}.\]

`norm`

— Method`norm(L::AbstractLat) -> NfAbsOrdFracIdl`

Return the norm of the lattice `L`

. This is a fractional ideal of the fixed field of `L`

.

`scale`

— Method`scale(L::AbstractLat) -> NfOrdFracIdl`

Return the scale of the lattice `L`

.

`volume`

— Method`volume(L::AbstractLat) -> NfOrdFracIdl`

Return the volume of the lattice `L`

.

### Examples

`julia> K, a = rationals_as_number_field();`

`julia> Kt, t = K["t"];`

`julia> g = t^2 + 7;`

`julia> E, b = number_field(g, "b");`

`julia> D = matrix(E, 4, 4, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);`

`julia> Lherm = hermitian_lattice(E, gens, gram = D);`

`julia> norm(Lherm)`

`1//1 * <1, 1> Norm: 1 Minimum: 1 principal generator 1 basis_matrix [1] two normal wrt: 2`

`julia> scale(Lherm)`

`Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//2 * <1, 1>) * [0 1]`

`julia> volume(Lherm)`

`Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <7, 7>) * [1 0] (1//2 * <7, 7>) * [0 1]`

## Predicates

Let $L$ be a lattice over $E/K$. It is said to be *integral* if its scale is an integral ideal, i.e. it is contained in $\mathcal O_E$. Moreover, if $\mathfrak p$ is a prime ideal in $\mathcal O_K$, then $L$ is said to be *modular* (resp. *locally modular at $\mathfrak p$*) if there exists a fractional ideal $\mathfrak a$ of $\mathcal O_E$ (resp. an integer $v$) such that $\mathfrak aL^{\#} = L$ (resp. $\mathfrak p^vL_{\mathfrak p}^{\#} = L_{\mathfrak p}$).

`is_integral`

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

Return whether the lattice `L`

is integral.

`is_modular`

— Method`is_modular(L::AbstractLat) -> Bool, NfOrdFracIdl`

Return whether the lattice `L`

is modular. In this case, the second returned value is a fractional ideal $\mathfrak a$ of the base algebra of `L`

such that $\mathfrak a L^\# = L$, where $L^\#$ is the dual of `L`

.

`is_modular`

— Method`is_modular(L::AbstractLat, p) -> Bool, Int`

Return whether the completion $L_{p}$ of the lattice `L`

at the prime ideal or integer `p`

is modular. If it is the case the second returned value is an integer `v`

such that $L_{p}$ is $p^v$-modular.

`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.

`can_scale_totally_positive`

— Method`can_scale_totally_positive(L::AbstractLat) -> Bool, NumFieldElem`

Return whether there is a totally positive rescaled lattice of the lattice `L`

. If so, the second returned value is an element $a$ such that $L^a$ is totally positive.

### Examples

`julia> K, a = rationals_as_number_field();`

`julia> Kt, t = K["t"];`

`julia> g = t^2 + 7;`

`julia> E, b = number_field(g, "b");`

`julia> D = matrix(E, 4, 4, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);`

`julia> Lherm = hermitian_lattice(E, gens, gram = D);`

`julia> OK = maximal_order(K);`

`julia> is_integral(Lherm)`

`true`

`julia> is_modular(Lherm)[1]`

`false`

`julia> p = prime_decomposition(OK, 7)[1][1];`

`julia> is_modular(Lherm, p)`

`(false, 0)`

`julia> is_positive_definite(Lherm)`

`true`

`julia> can_scale_totally_positive(Lherm)`

`(true, 1)`

## Local properties

`local_basis_matrix`

— Method`local_basis_matrix(L::AbstractLat, p::NfOrdIdl; type = :any) -> MatElem`

Given a prime ideal `p`

and a lattice `L`

, return a basis matrix of a lattice `M`

such that $M_{p} = L_{p}$. Note that if `p`

is an ideal in the base ring of `L`

, the completions are taken at the minimum of `p`

(which is an ideal in the base ring of the order of `p`

).

- If
`type == :submodule`

, the lattice`M`

will be a sublattice of`L`

. - If
`type == :supermodule`

, the lattice`M`

will be a superlattice of`L`

. - If
`type == :any`

, there may not be any containment relation between`M`

and`L`

.

`jordan_decomposition`

— Method```
jordan_decomposition(L::AbstractLat, p::NfOrdIdl)
-> Vector{MatElem}, Vector{MatElem}, Vector{Int}
```

Return a Jordan decomposition of the completion of the lattice `L`

at a prime ideal `p`

.

The returned value consists of three lists $(M_i)_i$, $(G_i)_i$ and $(s_i)_i$ of the same length $r$. The completions of the row spans of the matrices $M_i$ yield a Jordan decomposition of $L_{p}$ into modular sublattices $L_i$ with Gram matrices $G_i$ and scale of $p$-adic valuation $s_i$.

`is_isotropic`

— Method`is_isotropic(L::AbstractLat, p::Union{NfOrdIdl, InfPlc}) -> Bool`

Return whether the completion of the lattice `L`

at the place `p`

is isotropic.

### Examples

`julia> K, a = rationals_as_number_field();`

`julia> D = matrix(K, 3, 3, [2, 0, 0, 0, 2, 0, 0, 0, 2]);`

`julia> gens = Vector{nf_elem}[map(K, [1, 1, 0]), map(K, [1, 0, 1]), map(K, [2, 0, 0])];`

`julia> Lquad = quadratic_lattice(K, gens, gram = D);`

`julia> OK = maximal_order(K);`

`julia> p = prime_decomposition(OK, 7)[1][1];`

`julia> local_basis_matrix(Lquad, p)`

`[1 0 0] [1 1 0] [1 0 1]`

`julia> jordan_decomposition(Lquad, p)`

`(AbstractAlgebra.Generic.MatSpaceElem{nf_elem}[[1 0 0; 0 1 0; 0 0 1]], AbstractAlgebra.Generic.MatSpaceElem{nf_elem}[[2 0 0; 0 2 0; 0 0 2]], [0])`

`julia> is_isotropic(Lquad, p)`

`true`

## Automorphisms for definite lattices

Let $L$ and $L'$ be two lattices over the same extension $E/K$, inside their respective ambient spaces $(V, \Phi)$ and $(V', \Phi')$. Similarly to homomorphisms of spaces, we define a *homomorphism of lattices* from $L$ to $L'$ to be an $\mathcal{O}_E$-module$ homomorphism $f \colon L \to L'$ such that for all $x,y \in L$, one has

\[ \Phi'(f(x), f(y)) = \Phi(x,y).\]

Again, any automorphism of lattices is called an *isometry* and any monomorphism is called an *embedding*. We refer to the set of isometries from a lattice $L$ to itself as the *automorphism group 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`

.

`automorphism_group_generators`

— Method```
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`

.

### Examples

`julia> K, a = rationals_as_number_field();`

`julia> Kt, t = K["t"];`

`julia> g = t^2 + 7;`

`julia> E, b = number_field(g, "b");`

`julia> D = matrix(K, 3, 3, [2, 0, 0, 0, 2, 0, 0, 0, 2]);`

`julia> gens = Vector{nf_elem}[map(K, [1, 1, 0]), map(K, [1, 0, 1]), map(K, [2, 0, 0])];`

`julia> Lquad = quadratic_lattice(K, gens, gram = D);`

`julia> is_definite(Lquad)`

`true`

`julia> automorphism_group_order(Lquad)`

`48`

`julia> automorphism_group_generators(Lquad)`

`6-element Vector{AbstractAlgebra.Generic.MatSpaceElem{nf_elem}}: [-1 0 0; 0 -1 0; 0 0 -1] [1 0 0; 0 -1 0; 0 0 -1] [1 0 0; 0 0 -1; 0 -1 0] [0 -1 0; 0 0 -1; 1 0 0] [1 0 0; 0 1 0; 0 0 -1] [0 1 0; 1 0 0; 0 0 1]`

## Isometry

`is_isometric`

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

Return whether the lattices `L`

and `M`

are isometric.

`is_isometric_with_isometry`

— Method```
is_isometric_with_isometry(L::AbstractLat, M::AbstractLat; ambient_representation::Bool = true)
-> (Bool, MatElem)
```

Return whether the lattices `L`

and `M`

are isometric. If this is the case, the second returned value is an isometry `T`

from `L`

to `M`

.

By default, that isometry is represented with respect to the bases of the ambient spaces, that is, $T V_M T^t = V_L$ where $V_L$ and $V_M$ are the Gram matrices of the ambient spaces of `L`

and `M`

respectively. If `ambient_representation == false`

, then the isometry is represented with respect to the (pseudo-)bases of `L`

and `M`

, that is, $T G_M T^t = G_L$ where $G_M$ and $G_L$ are the Gram matrices of the (pseudo-)bases of `L`

and `M`

respectively.

`is_locally_isometric`

— Method`is_locally_isometric(L::AbstractLat, M::AbstractLat, p::NfOrdIdl) -> Bool`

Return whether the completions of the lattices `L`

and `M`

at the prime ideal `p`

are isometric.

### Examples

`julia> K, a = rationals_as_number_field();`

`julia> D = matrix(K, 3, 3, [2, 0, 0, 0, 2, 0, 0, 0, 2]);`

`julia> gens = Vector{nf_elem}[map(K, [1, 1, 0]), map(K, [1, 0, 1]), map(K, [2, 0, 0])];`

`julia> Lquad = quadratic_lattice(K, gens, gram = D);`

`julia> D = matrix(K, 3, 3, [2, 0, 0, 0, 2, 0, 0, 0, 2]);`

`julia> gens = Vector{nf_elem}[map(K, [-35, 25, 0]), map(K, [30, 40, -20]), map(K, [5, 10, -5])];`

`julia> Lquad2 = quadratic_lattice(K, gens, gram = D);`

`julia> OK = maximal_order(K);`

`julia> p = prime_decomposition(OK, 7)[1][1];`

`julia> is_isometric(Lquad, Lquad2)`

`false`

`julia> is_locally_isometric(Lquad, Lquad2, p)`

`true`

## Maximal integral lattices

`is_maximal_integral`

— Method`is_maximal_integral(L::AbstractLat, p::NfOrdIdl) -> Bool, AbstractLat`

Given a lattice `L`

and a prime ideal `p`

of the fixed ring $\mathcal O_K$ of `L`

, return whether the completion of `L`

at `p`

is maximal integral. If it is not the case, the second returned value is a lattice in the ambient space of `L`

whose completion at `p`

is a minimal overlattice of $L_p$.

`is_maximal_integral`

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

Given a lattice `L`

, return whether `L`

is maximal integral. If it is not, the second returned value is a minimal overlattice of `L`

with integral norm.

`is_maximal`

— Method`is_maximal(L::AbstractLat, p::NfOrdIdl) -> Bool, AbstractLat`

Given a lattice `L`

and a prime ideal `p`

in the fixed ring $\mathcal O_K$ of `L`

, check whether the norm of $L_p$ is integral and return whether `L`

is maximal at `p`

. If it is locally integral but not locally maximal, the second returned value is a lattice in the same ambient space of `L`

whose completion at `p`

has integral norm and is a proper overlattice of $L_p$.

`maximal_integral_lattice`

— Method`maximal_integral_lattice(L::AbstractLat, p::NfOrdIdl) -> AbstractLat`

Given a lattice `L`

and a prime ideal `p`

of the fixed ring $\mathcal O_K$ of `L`

, return a lattice `M`

in the ambient space of `L`

which is maximal integral at `p`

and which agrees with `L`

locally at all the places different from `p`

.

`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`

.

`maximal_integral_lattice`

— Method`maximal_integral_lattice(V::AbstractSpace) -> AbstractLat`

Given a space `V`

, return a lattice in `V`

with integral norm and which is maximal in `V`

satisfying this property.

### Examples

`julia> K, a = rationals_as_number_field();`

`julia> Kt, t = K["t"];`

`julia> g = t^2 + 7;`

`julia> E, b = number_field(g, "b");`

`julia> D = matrix(E, 4, 4, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);`

`julia> Lherm = hermitian_lattice(E, gens, gram = D);`

`julia> OK = maximal_order(K);`

`julia> p = prime_decomposition(OK, 7)[1][1];`

`julia> is_maximal_integral(Lherm, p)`

`(false, Hermitian lattice of rank 4 and degree 4)`

`julia> is_maximal_integral(Lherm)`

`(false, Hermitian lattice of rank 4 and degree 4)`

`julia> is_maximal(Lherm, p)`

`(false, Hermitian lattice of rank 4 and degree 4)`

`julia> pseudo_basis(maximal_integral_lattice(Lherm, p))`

`4-element Vector{Tuple{Vector{Hecke.NfRelElem{nf_elem}}, Hecke.NfRelOrdFracIdl{nf_elem, Hecke.NfAbsOrdFracIdl{AnticNumberField, nf_elem}, Hecke.NfRelElem{nf_elem}}}}: ([1, 0, 0, 0], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//2 * <1, 1>) * [0 1]) ([0, 1, 0, 0], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//2 * <1, 1>) * [0 1]) ([2, 4, 1, 0], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//14 * <1, 1>) * [6 1]) ([4, 5, 0, 1], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//14 * <1, 1>) * [6 1])`

`julia> pseudo_basis(maximal_integral_lattice(Lherm))`

`4-element Vector{Tuple{Vector{Hecke.NfRelElem{nf_elem}}, Hecke.NfRelOrdFracIdl{nf_elem, Hecke.NfAbsOrdFracIdl{AnticNumberField, nf_elem}, Hecke.NfRelElem{nf_elem}}}}: ([1, 0, 0, 0], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//2 * <1, 1>) * [0 1]) ([0, 1, 0, 0], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//2 * <1, 1>) * [0 1]) ([2, 4, 1, 0], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//14 * <1, 1>) * [6 1]) ([3, 2, 0, 1], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//14 * <1, 1>) * [6 1])`

`julia> pseudo_basis(maximal_integral_lattice(ambient_space(Lherm)))`

`4-element Vector{Tuple{Vector{Hecke.NfRelElem{nf_elem}}, Hecke.NfRelOrdFracIdl{nf_elem, Hecke.NfAbsOrdFracIdl{AnticNumberField, nf_elem}, Hecke.NfRelElem{nf_elem}}}}: ([1, 0, 0, 0], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//2 * <1, 1>) * [0 1]) ([0, 1, 0, 0], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//2 * <1, 1>) * [0 1]) ([3, 2, 1, 0], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//14 * <1, 1>) * [6 1]) ([5, 3, 0, 1], Fractional ideal of Relative maximal order with pseudo-basis (1) * 1//1 * <1, 1>, (b + 1) * 1//2 * <1, 1> with basis pseudo-matrix (1//1 * <1, 1>) * [1 0] (1//14 * <1, 1>) * [6 1])`