Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add lll_gram[_with_transform] for semidefinite matrices #2011

Merged
merged 1 commit into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 108 additions & 13 deletions src/flint/fmpz_mat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -957,10 +957,7 @@
"""
function lll_with_transform(x::ZZMatrix, ctx::LLLContext = LLLContext(0.99, 0.51))
z = deepcopy(x)
u = similar(x, nrows(x), nrows(x))
for i in 1:nrows(u)
u[i, i] = 1
end
u = identity_matrix(ZZ, nrows(x))
@ccall libflint.fmpz_lll(z::Ref{ZZMatrix}, u::Ref{ZZMatrix}, ctx::Ref{LLLContext})::Nothing
return z, u
end
Expand Down Expand Up @@ -1003,6 +1000,30 @@
return x
end

# for lll_gram, we use the following helpers to deal with the degenerate case.

function _is_definitely_full_rank(x::ZZMatrix)
if Int == Int64
p = 1099511627791 % Int64
else
p = 1048583 % Int32

Check warning on line 1009 in src/flint/fmpz_mat.jl

View check run for this annotation

Codecov / codecov/patch

src/flint/fmpz_mat.jl#L1009

Added line #L1009 was not covered by tests
end
F = Nemo.Native.GF(p; cached = false)
xmodp = F.(x)
return rank(xmodp) == nrows(x)
end

function _complete_to_basis(x::ZZMatrix)
@assert nrows(x) <= ncols(x)
# compute column HNF and take the inverse
h, t = hnf_with_transform(transpose(x))
for i in 1:nrows(x)
@assert is_one(h[i, i])
end
transpose!(t)
return inv!(t)
end

@doc raw"""
lll_gram_with_transform(x::ZZMatrix, ctx::LLLContext = LLLContext(0.99, 0.51, :gram))

Expand All @@ -1015,43 +1036,117 @@
supplying an optional context object.
"""
function lll_gram_with_transform(x::ZZMatrix, ctx::LLLContext = LLLContext(0.99, 0.51, :gram))
z = deepcopy(x)
u = similar(x, nrows(x), nrows(x))
for i in 1:nrows(u)
u[i, i] = 1
@req is_square(x) && is_symmetric(x) "The matrix must be a symmetric square matrix"
# try to recognize the definite case
if nrows(x) == 0
return x, x
end
if _is_definitely_full_rank(x)
u = identity_matrix(ZZ, nrows(x))
if x[1, 1] < 0
y = neg(x)
_lll_gram_with_transform!(y, u, ctx)
return neg!(y), u

Check warning on line 1049 in src/flint/fmpz_mat.jl

View check run for this annotation

Codecov / codecov/patch

src/flint/fmpz_mat.jl#L1047-L1049

Added lines #L1047 - L1049 were not covered by tests
else
y = deepcopy(x)
_lll_gram_with_transform!(y, u, ctx)
return y, u
end
end
@ccall libflint.fmpz_lll(z::Ref{ZZMatrix}, u::Ref{ZZMatrix}, ctx::Ref{LLLContext})::Nothing
return z, u
# remove the radical using a unimodular transformation
L = kernel(x; side = :left)
n = nrows(L)
r = nrows(x) - n
C = _complete_to_basis(L)
# move kernel to bottom, so that
# C * x * transpose(C) = [ * | 0 ]
# [-------]
# [ 0 | 0 ]
uu = deepcopy(reverse_rows!(C))
mul!(C, C, x * transpose(C))
u = identity_matrix(ZZ, nrows(x))
if C[1, 1] < 0
_lll_gram_with_transform!(neg!(@view(C[1:r, 1:r])), @view(u[1:r, 1:r]), ctx)
neg!(C)
else
_lll_gram_with_transform!(@view(C[1:r, 1:r]), @view(u[1:r, 1:r]), ctx)
end
return C, u * uu
end

function _lll_gram_with_transform!(x::ZZMatrix, u::ZZMatrix, ctx::LLLContext = LLLContext(0.99, 0.51, :gram))
# modifies x and u
@ccall libflint.fmpz_lll(x::Ref{ZZMatrix}, u::Ref{ZZMatrix}, ctx::Ref{LLLContext})::Nothing
return x, u
end

function _lll_gram_with_transform!(x::ZZMatrix, ctx::LLLContext = LLLContext(0.99, 0.51, :gram))
u = identity_matrix(ZZ, nrows(x))
@ccall libflint.fmpz_lll(x::Ref{ZZMatrix}, u::Ref{ZZMatrix}, ctx::Ref{LLLContext})::Nothing
return x, u

Check warning on line 1086 in src/flint/fmpz_mat.jl

View check run for this annotation

Codecov / codecov/patch

src/flint/fmpz_mat.jl#L1083-L1086

Added lines #L1083 - L1086 were not covered by tests
end

@doc raw"""
lll_gram(x::ZZMatrix, ctx::LLLContext = LLLContext(0.99, 0.51, :gram))

Return the Gram matrix $L$ of an LLL-reduced basis of the lattice given by the
Gram matrix $x$.
The matrix $x$ must be symmetric and non-singular.
The matrix $x$ must be symmetric and semidefinite. If an indefinite matrix is
provided, the output is undefined.

By default, the LLL is performed with reduction parameters $\delta = 0.99$ and
$\eta = 0.51$. These defaults can be overridden by specifying an optional context
object.
"""
function lll_gram(x::ZZMatrix, ctx::LLLContext = LLLContext(0.99, 0.51, :gram))
@req is_square(x) && is_symmetric(x) "The matrix must be a symmetric square matrix"
z = deepcopy(x)
return lll_gram!(z)
end

@doc raw"""
lll_gram!(x::ZZMatrix, ctx::LLLContext = LLLContext(0.99, 0.51, :gram))

Compute the Gram matrix of an LLL-reduced basis of the lattice given by the
Gram matrix $x$ inplace.
The matrix $x$ must be symmetric and non-singular.
The matrix $x$ must be symmetric and semidefinite.

By default, the LLL is performed with reduction parameters $\delta = 0.99$ and
$\eta = 0.51$. These defaults can be overridden by specifying an optional context
object.
"""
function lll_gram!(x::ZZMatrix, ctx::LLLContext = LLLContext(0.99, 0.51, :gram))
if nrows(x) == 0
return x
end
# try to recognize the definite case
if _is_definitely_full_rank(x)
if x[1, 1] < 0
return neg!(_lll_gram!(!neg(x), ctx))

Check warning on line 1124 in src/flint/fmpz_mat.jl

View check run for this annotation

Codecov / codecov/patch

src/flint/fmpz_mat.jl#L1124

Added line #L1124 was not covered by tests
else
return _lll_gram!(x, ctx)
end
end
# remove the radical using a unimodular transformation
L = kernel(x; side = :left)
n = nrows(L)
r = nrows(x) - n
C = _complete_to_basis(L)
# move kernel to bottom, so that
# C * x * transpose(C) = [ * | 0 ]
# [-------]
# [ 0 | 0 ]
reverse_rows!(C)
mul!(x, x, transpose(C))
C = mul!(x, C, x)
if C[1, 1] < 0
neg!(_lll_gram!(neg!(@view(x[1:r, 1:r])), ctx))
else
_lll_gram!(@view(x[1:r, 1:r]), ctx)
end
return x
end

function _lll_gram!(x::ZZMatrix, ctx::LLLContext = LLLContext(0.99, 0.51, :gram))
@ccall libflint.fmpz_lll(x::Ref{ZZMatrix}, C_NULL::Ptr{Nothing}, ctx::Ref{LLLContext})::Nothing
return x
end
Expand Down
31 changes: 31 additions & 0 deletions test/flint/fmpz_mat-test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,37 @@ end
B = gram(A)
lll_gram!(B)
@test B == lll_gram(gram(A))

# Semidefinite cases
A = ZZ[17070 17380 -4930 5840 28160;
17380 17975 -5135 6015 28835;
-4930 -5135 1485 -1715 -8155;
5840 6015 -1715 2015 9675;
28160 28835 -8155 9675 46705]
G = lll_gram(A)
GG = ZZ[5 0 0 0 0;
0 10 0 0 0;
0 0 10 0 0;
0 0 0 0 0;
0 0 0 0 0]
@test G == GG
@test lll_gram(-A) == -GG

G, T = lll_gram_with_transform(A)
@test T * A * transpose(T) == G
G, T = lll_gram_with_transform(-A)
@test T * (-A) * transpose(T) == G

A = zero_matrix(ZZ, 0, 0)
G, T = lll_gram_with_transform(A)
@test T * A * transpose(T) == G
G = lll_gram(A)
@test G == A

@test_throws ArgumentError lll_gram(ZZ[1 0])
@test_throws ArgumentError lll_gram_with_transform(ZZ[1 0])
@test_throws ArgumentError lll_gram(ZZ[1 0; 1 1])
@test_throws ArgumentError lll_gram_with_transform(ZZ[1 0; 1 1])
end

@testset "ZZMatrix.nullspace" begin
Expand Down
Loading