From 85d6f685f9412e4b298e233001882bf1ebeb7630 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Wed, 21 May 2025 10:47:33 +0200 Subject: [PATCH 01/24] backend for generic modules over ZZ and fields --- src/Modules/UngradedModules/FreeMod.jl | 29 +++++++ src/Modules/UngradedModules/FreeModuleHom.jl | 51 ++++++------ .../UngradedModules/FreeResolutions.jl | 55 ++++++++++--- src/Modules/UngradedModules/Methods.jl | 12 +++ src/Modules/UngradedModules/ModuleGens.jl | 78 +++++++++++++------ src/Modules/UngradedModules/Presentation.jl | 78 ++++++++++++++++++- .../UngradedModules/SubModuleOfFreeModule.jl | 38 ++++++++- 7 files changed, 281 insertions(+), 60 deletions(-) diff --git a/src/Modules/UngradedModules/FreeMod.jl b/src/Modules/UngradedModules/FreeMod.jl index 8aff69ed7568..53523fb7c3fa 100644 --- a/src/Modules/UngradedModules/FreeMod.jl +++ b/src/Modules/UngradedModules/FreeMod.jl @@ -22,6 +22,35 @@ function FreeMod(R::AdmissibleModuleFPRing, names::Vector{Symbol}; cached::Bool= return FreeMod{elem_type(R)}(length(names), R, names) end + +@doc raw""" + free_module_sparse(R::ZZRing, n::Int, name::VarName = :e; cached::Bool = false) + free_module_sparse(R::Field, n::Int, name::VarName = :e; cached::Bool = false) + +Construct a free module of rank `n` over the ring `R` using a sparse implementation +in cases where the `free_module` constructor returns a free module with a dense one. + +The string `name` specifies how the basis vectors are printed. + +# Examples +```jldoctest +julia> F = free_module_sparse(ZZ, 3, "f") +Free module of rank 3 over ZZ + +julia> F[1] +f[1] + +julia> K = GF(7); + +julia> FK = free_module_sparse(K, 2) +Free module of rank 2 over GF(7) + +julia> FK[1] +e[1] +""" +free_module_sparse(R::Union{ZZRing, Field, MPolyRing, MPolyQuoRing, MPolyLocRing, MPolyQuoLocRing}, + n::Int, name::VarName = :e; cached::Bool = false) = FreeMod(R, n, name, cached=cached) + @doc raw""" free_module(R::MPolyRing, p::Int, name::VarName = :e; cached::Bool = false) free_module(R::MPolyQuoRing, p::Int, name::VarName = :e; cached::Bool = false) diff --git a/src/Modules/UngradedModules/FreeModuleHom.jl b/src/Modules/UngradedModules/FreeModuleHom.jl index d27760edf613..42dd6c6c970f 100644 --- a/src/Modules/UngradedModules/FreeModuleHom.jl +++ b/src/Modules/UngradedModules/FreeModuleHom.jl @@ -450,40 +450,45 @@ represented as subquotient with no relations -> F) ``` """ -function kernel(h::FreeModuleHom{<:FreeMod, <:FreeMod}) #ONLY for free modules... - error("not implemented for modules over rings of type $(typeof(base_ring(domain(h))))") -end - -# The following function is part of the requirement of atomic functions to be implemented -# in order to have the modules run over a specific type of ring. The documentation of this is -# pending and so far only orally communicated by Janko Boehm. -# -# The concrete method below uses Singular as a backend to achieve its task. In order -# to have only input which Singular can actually digest, we restrict the signature -# to those cases. The method used to be triggered eventually also for rings which -# did not have a groebner basis backend in Singular, but Singular did not complain. -# This lead to false results without notification. By restricting the signature, -# the user gets the above error message instead. -function kernel( - h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing} - ) where {S<: Union{ZZRingElem, <:FieldElem}, T <: MPolyRingElem{S}} +function kernel(h::FreeModuleHom{<:FreeMod, <:FreeMod}) is_zero(h) && return sub(domain(h), gens(domain(h))) is_graded(h) && return _graded_kernel(h) - return _simple_kernel(h) + return kernel_atomic(h) # explicitly call kernel_atomic +end + +function kernel_atomic(h::FreeModuleHom{<:FreeMod, <:FreeMod}) + error("not implemented for modules over rings of type $(typeof(base_ring(domain(h))))") end -function _simple_kernel(h::FreeModuleHom{<:FreeMod, <:FreeMod}) +function kernel_atomic(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {S<:Union{ZZRingElem, <:FieldElem}, T<:MPolyRingElem{S}} F = domain(h) G = codomain(h) - g = images_of_generators(h) - b = ModuleGens(g, G, default_ordering(G)) - M = syzygy_module(b) + gens_h = images_of_generators(h) + mod_gens = ModuleGens(gens_h, G, default_ordering(G)) + M = syzygy_module(mod_gens) v = elem_type(F)[F(coordinates(repres(w))) for w in gens(M) if !is_zero(w)] return sub(F, v) end +function ensure_kernel_ctx!(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {T<:Union{ZZRingElem, FieldElem}} + if !has_attribute(h, :kernel_ctx) + mat_h = transpose(matrix(h)) + set_attribute!(h, :kernel_ctx, solve_init(mat_h)) + end +end + +function kernel_atomic(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {T<:Union{ZZRingElem, FieldElem}} + ensure_kernel_ctx!(h) + kernel_ctx = get_attribute(h, :kernel_ctx) + K = kernel(kernel_ctx, side=:right) + F = domain(h) + v = [F(sparse_row_from_dense(base_ring(F), vec(K[:, j]))) for j in 1:ncols(K)] + return sub(F, v) +end + + function _graded_kernel(h::FreeModuleHom{<:FreeMod, <:FreeMod}) - I, inc = _simple_kernel(h) + I, inc = kernel_atomic(h) @assert is_graded(I) @assert is_homogeneous(inc) return I, inc diff --git a/src/Modules/UngradedModules/FreeResolutions.jl b/src/Modules/UngradedModules/FreeResolutions.jl index cb3d9eb4e3e8..0ac34b516133 100644 --- a/src/Modules/UngradedModules/FreeResolutions.jl +++ b/src/Modules/UngradedModules/FreeResolutions.jl @@ -543,35 +543,66 @@ function free_resolution(M::SubquoModule{T}; return FreeResolution(cc) end -function free_resolution(M::SubquoModule{T}) where {T<:RingElem} - # This generic code computes a free resolution in a lazy way. - # We start out with a presentation of M and implement - # an iterative fill function to compute every higher term - # on request. +# kept for the comments +# function free_resolution(M::SubquoModule{T}) where {T<:RingElem} +# # This generic code computes a free resolution in a lazy way. +# # We start out with a presentation of M and implement +# # an iterative fill function to compute every higher term +# # on request. +# R = base_ring(M) +# p = presentation(M) +# p.fill = function(C::Hecke.ComplexOfMorphisms, k::Int) +# # TODO: Use official getter and setter methods instead +# # of messing manually with the internals of the complex. +# for i in first(chain_range(C)):k-1 +# N = domain(map(C, i)) + +# if iszero(N) # Fill up with zero maps +# C.complete = true +# phi = hom(N, N, elem_type(N)[]; check=false) +# pushfirst!(C.maps, phi) +# continue +# end + +# K, inc = kernel(map(C, i)) +# nz = findall(!is_zero, gens(K)) +# F = FreeMod(R, length(nz)) +# phi = hom(F, C[i], iszero(length(nz)) ? elem_type(C[i])[] : inc.(gens(K)[nz]); check=false) +# pushfirst!(C.maps, phi) +# end +# return first(C.maps) +# end +# return p +# end + +function free_resolution(M::SubquoModule{T}; length::Int=0) where {T<:RingElem} R = base_ring(M) p = presentation(M) p.fill = function(C::Hecke.ComplexOfMorphisms, k::Int) - # TODO: Use official getter and setter methods instead - # of messing manually with the internals of the complex. - for i in first(chain_range(C)):k-1 - N = domain(map(C, i)) + min_index = first(chain_range(C)) + target_index = length == 0 ? k-1 : max(min_index - (length - 1), k-1) - if iszero(N) # Fill up with zero maps + for i in min_index:target_index + N = domain(map(C, i)) + if iszero(N) C.complete = true phi = hom(N, N, elem_type(N)[]; check=false) pushfirst!(C.maps, phi) continue end - K, inc = kernel(map(C, i)) nz = findall(!is_zero, gens(K)) F = FreeMod(R, length(nz)) phi = hom(F, C[i], iszero(length(nz)) ? elem_type(C[i])[] : inc.(gens(K)[nz]); check=false) pushfirst!(C.maps, phi) + if length != 0 && abs(i - min_index) + 1 >= length + C.complete = false + break + end end return first(C.maps) end - return p + return FreeResolution(p) end diff --git a/src/Modules/UngradedModules/Methods.jl b/src/Modules/UngradedModules/Methods.jl index d12f52eee88b..b829ad0184e9 100644 --- a/src/Modules/UngradedModules/Methods.jl +++ b/src/Modules/UngradedModules/Methods.jl @@ -241,6 +241,18 @@ function default_ordering(F::FreeMod) return F.default_ordering::ModuleOrdering{typeof(F)} end +function default_ordering(F::FreeMod{T}) where {T<:Union{ZZRingElem, FieldElem}} + if !isdefined(F, :default_ordering) + if iszero(F) + F.default_ordering = ModuleOrdering(F, Orderings.ModOrdering(Int[], :lex)) + else + F.default_ordering = lex(gens(F)) + end + end + return F.default_ordering::ModuleOrdering{typeof(F)} +end + + ############################## #TODO: move to Singular.jl ? diff --git a/src/Modules/UngradedModules/ModuleGens.jl b/src/Modules/UngradedModules/ModuleGens.jl index 8a4548b90455..493508c22a51 100644 --- a/src/Modules/UngradedModules/ModuleGens.jl +++ b/src/Modules/UngradedModules/ModuleGens.jl @@ -419,31 +419,65 @@ Note: `:via_lift` is typically faster than `:via_transform` for a single vector is faster if many vectors are lifted """ function coordinates(a::FreeModElem, M::SubModuleOfFreeModule, task::Symbol = :auto) - if iszero(a) - return sparse_row(base_ring(parent(a))) - end - if task == :auto - if coefficient_ring(base_ring(parent(a))) isa Field #base_ring(base_ring(...)) does not work for MPolyQuos - task = :via_transform + if iszero(a) + return sparse_row(base_ring(parent(a))) + end + return coordinates_atomic(a, M; task=task) +end + +function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task::Symbol = :auto) where {S<:Union{ZZRingElem,<:FieldElem}, T<:MPolyRingElem{S}} + if task == :auto + task = coefficient_ring(base_ring(parent(a))) isa Field ? :via_transform : :via_lift + end + for i in 1:ngens(M) + g = gen(M,i) + if a == g + R = base_ring(M) + return sparse_row(R, [(i,R(1))]) + end + end + if task == :via_transform + std, _ = lift_std(M) + return coordinates_via_transform(a, std) + elseif task == :via_lift + return coordinates(a, M.gens) else - task = :via_lift + error("Invalid task given.") end - end - for i in 1:ngens(M) - g = gen(M,i) - if a == g - R = base_ring(M) - return sparse_row(R, [(i,R(1))]) +end + +function sparse_row_from_dense(R::Ring, dense_vec::Vector{T}) where T + positions = Int[] + values = T[] + for (i, val) in enumerate(dense_vec) + if !iszero(val) + push!(positions, i) + push!(values, val) + end end - end - if task == :via_transform - std, _ = lift_std(M) - return coordinates_via_transform(a, std) - elseif task == :via_lift - return coordinates(a, M.gens) - else - error("Invalid task given.") - end + return sparse_row(R, positions, values) +end + +function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task::Symbol=:auto) where {T<:Union{ZZRingElem, FieldElem}} + ensure_solve_ctx!(M) + solve_ctx = get_attribute(M, :solve_ctx) + n = ngens(M) + R = base_ring(ambient_free_module(M)) + d = rank(ambient_free_module(M)) + vec_a = zeros(R, d) + for (i, val) in coordinates(a) + vec_a[i] = val + end + is_solvable, sol = can_solve_with_solution(solve_ctx, vec_a, side=:right) + if !is_solvable + error("Element is not contained in the module.") + end + return sparse_row_from_dense(R, sol) +end + + +function coordinates_atomic(a::FreeModElem, M::SubModuleOfFreeModule; task::Symbol = :auto) + error("The function coordinates_atomic is not implemented for modules over rings of type $(typeof(base_ring(ambient_free_module(M))))") end @doc raw""" diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index f15139544efb..35a6ecdc8e4e 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -531,14 +531,21 @@ julia> phi(first(gens(N))) e[2] ``` """ +# generic dispatcher function prune_with_map(M::ModuleFP) + return prune_with_map_atomic(M) +end + +# generic fallback +function prune_with_map_atomic(M::ModuleFP) # TODO: take special care of graded modules # by stripping off the grading and rewrapping it afterwards. N, b = simplify(M) return N, b end -function prune_with_map(M::ModuleFP{T}) where {T<:Union{MPolyRingElem, MPolyQuoRingElem}} # The case that can be handled by Singular +# MpolyRing and MPolyQuoRing case +function prune_with_map_atomic(M::ModuleFP{T}) where {T<:Union{MPolyRingElem, MPolyQuoRingElem}} # The case that can be handled by Singular # Singular presentation pm = presentation(M) @@ -582,6 +589,75 @@ function prune_with_map(M::ModuleFP{T}) where {T<:Union{MPolyRingElem, MPolyQuoR return M_new, phi end +function ensure_presentation_ctx!(M::SubquoModule) + ensure_presentation_ctx!(base_ring(M), M) +end + +function ensure_presentation_ctx!(M::SubquoModule{ZZRingElem}) + if !has_attribute(M, :presentation_ctx) + R = base_ring(M) + F = ambient_free_module(M) + m = ngens(F) + rels = relations(M) + n = length(rels) + + A = zero_matrix(R, m, n) + for (j, rel) in enumerate(rels) + for (i, v) in coordinates(rel) + A[i, j] = v + end + end + + D, U, V = snf_with_transform(A) + set_attribute!(M, :presentation_ctx, (D=D, U=U, V=V)) + end +end + +function ensure_presentation_ctx!(M::SubquoModule{T}) where {T<:FieldElem} + if !has_attribute(M, :presentation_ctx) + R = base_ring(M) + F = ambient_free_module(M) + m = ngens(F) + rels = relations(M) + n = length(rels) + + A = zero_matrix(R, m, n) + for (j, rel) in enumerate(rels) + for (i, v) in coordinates(rel) + A[i, j] = v + end + end + + D, U, V = snf_with_transform(A) + set_attribute!(M, :presentation_ctx, (D=D, U=U, V=V)) + end +end + +function prune_with_map_atomic(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} + ensure_presentation_ctx!(M) + ctx = get_attribute(M, :presentation_ctx) + R = base_ring(M) + F = ambient_free_module(M) + m = ngens(F) + D = ctx.D + U = ctx.U + diag_length = min(size(D)...) + unit_diag_indices = [i for i in 1:diag_length if is_unit(D[i,i])] + row_indices = setdiff(1:size(D, 1), unit_diag_indices) + col_indices = setdiff(1:size(D, 2), unit_diag_indices) + D_prime_matrix = sub(D, row_indices, col_indices) + F_prime = FreeMod(R, length(row_indices)) + rels_prime = [F_prime(sparse_row_from_dense(R, D_prime_matrix[:, j])) for j in 1:size(D_prime_matrix, 2)] + N_prime, _ = sub(F_prime, rels_prime) + M_prime, _ = quo(F_prime, N_prime) + U_inv = inv(U) + iso_basis_ambient = [sum(U_inv[j, row_indices[i]] * gens(F)[j] for j in 1:m) for i in 1:length(row_indices)] + proj_to_M = hom(F, M, gens(M)) + iso_basis = [proj_to_M(b) for b in iso_basis_ambient] + iso_map = hom(M_prime, M, iso_basis) + return M_prime, iso_map +end + function _presentation_minimal(SQ::ModuleFP{T}; minimal_kernel::Bool=true) where {T <: Union{MPolyRingElem, MPolyQuoRingElem}} R = base_ring(SQ) diff --git a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl index 22a6bee5c6cb..d0f44b33b743 100644 --- a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl +++ b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl @@ -475,10 +475,44 @@ end Check if `a` is an element of `M`. """ function in(a::FreeModElem, M::SubModuleOfFreeModule) - F = ambient_free_module(M) - return iszero(reduce(a, standard_basis(M, ordering=default_ordering(F)))) + return in_atomic(a, M) +end + +function in_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule) where {S<:Union{ZZRingElem,<:FieldElem}, T<:MPolyRingElem{S}} + F = ambient_free_module(M) + return iszero(reduce(a, standard_basis(M, ordering=default_ordering(F)))) +end + +function ensure_solve_ctx!(M::SubModuleOfFreeModule) + if !has_attribute(M, :solve_ctx) + F = ambient_free_module(M) + d, n = rank(F), ngens(M) + R = base_ring(F) + mat = zero_matrix(R, d, n) + for (j, g) in enumerate(gens(M)), (i, val) in coordinates(g) + mat[i, j] = val + end + set_attribute!(M, :solve_ctx, solve_init(mat)) + end end +function in_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule) where {T<:Union{ZZRingElem, FieldElem}} + ensure_solve_ctx!(M) + solve_ctx = get_attribute(M, :solve_ctx) + R = base_ring(ambient_free_module(M)) + d = rank(ambient_free_module(M)) + vec_a = zeros(R, d) + for (i, val) in coordinates(a) + vec_a[i] = val + end + return can_solve(solve_ctx, vec_a, side=:right) +end + +function in_atomic(a::FreeModElem, M::SubModuleOfFreeModule) + error("Membership test 'in' is not implemented for modules over rings of type $(typeof(base_ring(ambient_free_module(M))))") +end + + function normal_form(M::SubModuleOfFreeModule{T}, N::SubModuleOfFreeModule{T}) where {T <: MPolyRingElem} @assert is_global(default_ordering(N)) # TODO reduced flag to be implemented in Singular.jl From 78db07d0ef1cf241728ea94c27f5c92467853add Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Wed, 21 May 2025 11:13:34 +0200 Subject: [PATCH 02/24] add tests for modules over ZZ and fields --- src/exports.jl | 1 + test/Modules/UngradedModules.jl | 320 ++++++++++++++++++++++++++++++-- 2 files changed, 303 insertions(+), 18 deletions(-) diff --git a/src/exports.jl b/src/exports.jl index 77c6e8546377..66b81bec438a 100644 --- a/src/exports.jl +++ b/src/exports.jl @@ -651,6 +651,7 @@ export free_extension export free_group export free_module export free_module_dec +export free_module_sparse export free_resolution export free_resolution_via_kernels export full_group diff --git a/test/Modules/UngradedModules.jl b/test/Modules/UngradedModules.jl index c2bdc52f6603..d5453edccd84 100644 --- a/test/Modules/UngradedModules.jl +++ b/test/Modules/UngradedModules.jl @@ -1383,22 +1383,306 @@ end conj_S1_alt = hom(S1, S1, [S1[1]], f->evaluate(map_coefficients(conj, f), [u, v])) @test_throws ErrorException conj_S1 == conj_S1_alt - a = compose(compose(id_R1, f), compose(conj_S1, id_S1)) - b = compose(compose(id_R1, compose(f, conj_S1)), id_S1) - c = compose(compose(compose(id_R1, f), conj_S1), id_S1) - d = compose(id_R1, compose(f, compose(conj_S1, id_S1))) - - @test a == b - a_alt = compose(compose(id_R1, f), compose(conj_S1_alt, id_S1)) - @test_throws ErrorException f == a # non-comparable ring_maps - @test_throws ErrorException a_alt == a # same - @test_throws MethodError !(conj_S1 == id_S1) # ring map vs. no ring map - @test conj_S1 == conj_S1 # identical ring maps are OK - @test all(a(x) == b(x) for x in gens(R1)) - @test all(a(x) == c(x) for x in gens(R1)) - @test all(a(x) == d(x) for x in gens(R1)) - - @test all(i*conj_S1(x) == conj_S1(-i*x) for x in gens(S1)) - @test conj_S1 == compose(conj_S1, id_S1) # identical ring_map - @test conj_S1 == compose(id_S1, conj_S1) +@testset "Modules over ZZ and QQ" begin + + @testset "Module Constructors over ZZ" begin + F0 = free_module_sparse(ZZ,3) + @test rank(F0) == 3 + @test base_ring(F0) == ZZ + + F = FreeMod(ZZ, 3) + M, inc = sub(F, [2*F[1], 3*F[2]]) + N, proj = quo(F, [F[1] + 2*F[3]]) + + @test rank(F) == 3 + @test is_welldefined(inc) + @test is_welldefined(proj) + end + + @testset "Module Homomorphisms over ZZ" begin + F1 = FreeMod(ZZ, 2) + F2 = FreeMod(ZZ, 2) + phi = hom(F1, F2, [F2[1] + 2*F2[2], 3*F2[1]+ 6*F2[2]]) + + @test is_welldefined(phi) + + K, emb = kernel(phi) + @test ngens(K) == 1 + @test is_welldefined(emb) + + I, im = image(phi) + @test ngens(I) == 2 + @test is_welldefined(im) + end + + @testset "Presentations over ZZ" begin + F = FreeMod(ZZ, 2) + M, inc = sub(F, [2*F[1] + 3*F[2]]) + pres_M, iso = present_as_cokernel(M, :both) + + @test is_welldefined(iso) + @test is_bijective(iso) + @test pres_M isa SubquoModule + @test ngens(pres_M) == 1 + + F = FreeMod(ZZ, 3) + M, inc = sub(F, [5*F[1],3*F[2],F[3]]) + N, inc = sub(F, [10*F[1], 3*F[2]]) + W, _ = quo(M, N) + pres_W, iso = present_as_cokernel(W, :both) + @test is_welldefined(iso) + @test is_bijective(iso) + @test pres_W isa SubquoModule + @test ngens(pres_W) == 3 + presentation(W) + ppW, iso2 = prune_with_map(pres_W) + @test is_welldefined(iso2) + @test is_bijective(iso2) + @test ppW isa SubquoModule + @test ngens(ppW) == 2 + end + + @testset "Ext and Tor over ZZ" begin + F = FreeMod(ZZ, 1) + M, _ = sub(F, [2*F[1]]) + N, _ = quo(F, [3*F[1]]) + + T0 = tor(M, N, 0) + T1 = tor(M, N, 1) + + @test T0 isa SubquoModule + @test T1 isa SubquoModule + + E0 = ext(M, N, 0) + E1 = ext(M, N, 1) + + @test E0 isa SubquoModule + @test E1 isa SubquoModule + end + + @testset "Free resolutions over ZZ" begin + F = FreeMod(ZZ, 1) + M, _ = quo(F, [4*F[1]]) + + fr = free_resolution(M, length=3) + + @test length(fr.C.maps) == 3 + @test iszero(homology(fr.C)[1]) + end + + @testset "Tensoring morphisms over ZZ" begin + R = ZZ + F2 = FreeMod(R, 2) + F3 = FreeMod(R, 3) + A1 = matrix(ZZ, [1 2; 3 4; 5 6]) + B1 = matrix(ZZ, [7 8]) + A2 = matrix(ZZ, [2 0 1; 0 1 3; 1 1 1]) + B2 = matrix(ZZ, [0 1 1]) + M1 = SubquoModule(F2, A1, B1) + M2 = SubquoModule(F3, A2, B2) + M, pure_M = tensor_product(M1, M2, task=:map) + phi = hom_tensor(M, M, [identity_map(M1), identity_map(M2)]) + v = M[1] + 2*M[2] + @test phi(v) == v + F4 = FreeMod(R, 4) + A3 = matrix(ZZ, [1 2; 3 4]) + M3 = SubquoModule(Oscar.SubModuleOfFreeModule(F2, A3)) + N, pure_N = tensor_product(M3, F4, task=:map) + M3_to_M1 = SubQuoHom(M3, M1, matrix(ZZ, [1 0 0; 0 1 0])) + @test is_welldefined(M3_to_M1) + F4_to_M2 = FreeModuleHom(F4, M2, matrix(ZZ, [1 0 0; 0 1 0; 0 0 1; 0 0 0])) + phi2 = hom_tensor(N, M, [M3_to_M1, F4_to_M2]) + u1 = M3[1] + u2 = F4[1] + @test phi2(pure_N((u1, u2))) == pure_M((M3_to_M1(u1), F4_to_M2(u2))) + end + + @testset "Direct product over ZZ" begin + R = ZZ + F2 = FreeMod(R, 2) + F3 = FreeMod(R, 3) + A1 = matrix(ZZ, [1 0; 0 1; 2 2]) + B1 = matrix(ZZ, [0 1]) + M1 = SubquoModule(F2, A1, B1) + A2 = matrix(ZZ, [1 2 3; 0 0 1]) + B2 = matrix(ZZ, [1 0 1]) + M2 = SubquoModule(F3, A2, B2) + sum_M, emb = direct_sum(M1, M2) + @test domain(emb[1]) === M1 + @test domain(emb[2]) === M2 + @test codomain(emb[1]) === sum_M + @test codomain(emb[2]) === sum_M + sum_M2, pr = direct_sum(M1, M2, task=:prod) + @test codomain(pr[1]) === M1 + @test codomain(pr[2]) === M2 + @test domain(pr[1]) === sum_M2 + @test domain(pr[2]) === sum_M2 + prod_M, emb, pr = direct_sum(M1, M2, task=:both) + @test length(pr) == length(emb) == 2 + @test ngens(prod_M) == ngens(M1) + ngens(M2) + for g in gens(prod_M) + @test g == sum([emb[i](pr[i](g)) for i in 1:length(pr)]) + end + prod_N = direct_product(M1, M2, task=:none) + @test ngens(prod_N) == ngens(M1) + ngens(M2) + for g in gens(prod_N) + @test g == sum([canonical_injection(prod_N, i)(canonical_projection(prod_N, i)(g)) for i in 1:2]) + end + end + + + + @testset "Module Constructors over QQ" begin + F0 = free_module_sparse(QQ,3) + @test rank(F0) == 3 + @test base_ring(F0) == QQ + + F = FreeMod(QQ, 3) + M, inc = sub(F, [QQ(1//2)*F[1], QQ(2//3)*F[2]]) + N, proj = quo(F, [F[1] + QQ(1//3)*F[3]]) + + @test ngens(M) == 2 + @test ngens(N) == 3 + F = FreeMod(QQ, 3) + M, inc = sub(F, [QQ(1//2)*F[1], QQ(2//3)*F[2]]) + N, proj = quo(F, [F[1] + QQ(1//3)*F[3]]) + + @test is_welldefined(inc) + @test is_welldefined(proj) + @test is_welldefined(inc) + @test is_welldefined(proj) + end + + @testset "Module Homomorphisms over QQ" begin + F1 = FreeMod(QQ, 2) + F2 = FreeMod(QQ, 2) + phi = hom(F1, F2, [F2[1] + QQ(1//2)*F2[2], QQ(3//4)*F2[1]]) + + @test is_welldefined(phi) + + K, emb = kernel(phi) + @test is_welldefined(emb) + + I, im = image(phi) + @test is_welldefined(im) + end + + @testset "Presentations over QQ" begin + F = FreeMod(QQ, 2) + M, inc = sub(F, [QQ(1//2)*F[1] + QQ(1//3)*F[2]]) + pres_M, iso = present_as_cokernel(M, :both) + + @test is_welldefined(iso) + @test is_bijective(iso) + @test pres_M isa SubquoModule + @test ngens(pres_M) == 1 + + F = FreeMod(QQ, 3) + M, inc = sub(F, [5*F[1], 3*F[2], F[3]]) + N, inc = sub(F, [10*F[1], 3*F[2]]) + W, _ = quo(M, N) + pres_W, iso = present_as_cokernel(W, :both) + MP, iso2 = prune_with_map(pres_W) + @test is_welldefined(iso2) + @test is_bijective(iso2) + @test MP isa SubquoModule + @test ngens(MP) == 1 + end + + @testset "Ext and Tor over QQ" begin + F = FreeMod(QQ, 1) + M, _ = sub(F, [QQ(1//2)*F[1]]) + N, _ = quo(F, [QQ(1//3)*F[1]]) + + T0 = tor(M, N, 0) + T1 = tor(M, N, 1) + + @test T0 isa SubquoModule + @test T1 isa SubquoModule + + E0 = ext(M, N, 0) + E1 = ext(M, N, 1) + + @test E0 isa SubquoModule + @test E1 isa SubquoModule + end + + @testset "Free resolutions over QQ" begin + F = FreeMod(QQ, 1) + M, _ = quo(F, [QQ(1//4)*F[1]]) + + fr = free_resolution(M, length=3) + + @test length(fr.C.maps) == 3 + @test iszero(homology(fr.C)[1]) + MP, _ = prune_with_map(M) + @test is_zero(MP) + end + + @testset "Tensoring morphisms over QQ" begin + R = QQ + F2 = FreeMod(R, 2) + F3 = FreeMod(R, 3) + A1 = matrix(R, [1 2; 3 4; 5 6]) + B1 = matrix(R, [7 8]) + A2 = matrix(R, [2 0 1; 0 1 3; 1 1 1]) + B2 = matrix(R, [0 1 1]) + M1 = SubquoModule(F2, A1, B1) + M2 = SubquoModule(F3, A2, B2) + M, pure_M = tensor_product(M1, M2, task=:map) + phi_M1 = SubQuoHom(M1, M1, matrix(R, [1 1 0; 0 2 1; 1 0 1])) + phi_M2 = SubQuoHom(M2, M2, matrix(R, [0 1 0; 1 0 1; 1 1 0])) + phi = hom_tensor(M, M, [phi_M1, phi_M2]) + v = M[1] + 2*M[2] + @test phi(v) == pure_M((phi_M1(M1[1]), phi_M2(M2[1]))) + 2*pure_M((phi_M1(M1[2]), phi_M2(M2[1]))) + F4 = FreeMod(R, 4) + A3 = matrix(R, [1 2; 3 4]) + M3 = SubquoModule(Oscar.SubModuleOfFreeModule(F2, A3)) + N, pure_N = tensor_product(M3, F4, task=:map) + M3_to_M1 = SubQuoHom(M3, M1, matrix(R, [1 2 0; 0 1 3])) + @test is_welldefined(M3_to_M1) + F4_to_M2 = FreeModuleHom(F4, M2, matrix(R, [1 0 2; 0 1 0; 1 1 1; 2 0 0])) + phi2 = hom_tensor(N, M, [M3_to_M1, F4_to_M2]) + u1 = M3[1] + u2 = F4[1] + @test phi2(pure_N((u1, u2))) == pure_M((M3_to_M1(u1), F4_to_M2(u2))) + end + + @testset "Direct product over QQ" begin + R = QQ + F2 = FreeMod(R, 2) + F3 = FreeMod(R, 3) + A1 = matrix(R, [1 0; 0 1; 2 2]) + B1 = matrix(R, [0 1]) + M1 = SubquoModule(F2, A1, B1) + A2 = matrix(R, [1 2 3; 0 0 1]) + B2 = matrix(R, [1 0 1]) + M2 = SubquoModule(F3, A2, B2) + sum_M, emb = direct_sum(M1, M2) + @test domain(emb[1]) === M1 + @test domain(emb[2]) === M2 + @test codomain(emb[1]) === sum_M + @test codomain(emb[2]) === sum_M + sum_M2, pr = direct_sum(M1, M2, task=:prod) + @test codomain(pr[1]) === M1 + @test codomain(pr[2]) === M2 + @test domain(pr[1]) === sum_M2 + @test domain(pr[2]) === sum_M2 + prod_M, emb, pr = direct_sum(M1, M2, task=:both) + @test length(pr) == length(emb) == 2 + @test ngens(prod_M) == ngens(M1) + ngens(M2) + for g in gens(prod_M) + @test g == sum([emb[i](pr[i](g)) for i in 1:length(pr)]) + end + prod_N = direct_product(M1, M2, task=:none) + @test ngens(prod_N) == ngens(M1) + ngens(M2) + for g in gens(prod_N) + @test g == sum([canonical_injection(prod_N, i)(canonical_projection(prod_N, i)(g)) for i in 1:2]) + end + end + + @testset "gradings over non-graded rings" begin + @test_throws AssertionError graded_free_module(ZZ, [1]) + end + end From 45562e76452775565c21d1c7341dc1f0e44b1a54 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Wed, 21 May 2025 11:37:14 +0200 Subject: [PATCH 03/24] fix tests --- test/Modules/UngradedModules.jl | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/test/Modules/UngradedModules.jl b/test/Modules/UngradedModules.jl index d5453edccd84..d5b2fc717ebb 100644 --- a/test/Modules/UngradedModules.jl +++ b/test/Modules/UngradedModules.jl @@ -1383,6 +1383,26 @@ end conj_S1_alt = hom(S1, S1, [S1[1]], f->evaluate(map_coefficients(conj, f), [u, v])) @test_throws ErrorException conj_S1 == conj_S1_alt + a = compose(compose(id_R1, f), compose(conj_S1, id_S1)) + b = compose(compose(id_R1, compose(f, conj_S1)), id_S1) + c = compose(compose(compose(id_R1, f), conj_S1), id_S1) + d = compose(id_R1, compose(f, compose(conj_S1, id_S1))) + + @test a == b + a_alt = compose(compose(id_R1, f), compose(conj_S1_alt, id_S1)) + @test_throws ErrorException f == a # non-comparable ring_maps + @test_throws ErrorException a_alt == a # same + @test_throws MethodError !(conj_S1 == id_S1) # ring map vs. no ring map + @test conj_S1 == conj_S1 # identical ring maps are OK + @test all(a(x) == b(x) for x in gens(R1)) + @test all(a(x) == c(x) for x in gens(R1)) + @test all(a(x) == d(x) for x in gens(R1)) + + @test all(i*conj_S1(x) == conj_S1(-i*x) for x in gens(S1)) + @test conj_S1 == compose(conj_S1, id_S1) # identical ring_map + @test conj_S1 == compose(id_S1, conj_S1) +end + @testset "Modules over ZZ and QQ" begin @testset "Module Constructors over ZZ" begin @@ -1634,7 +1654,7 @@ end phi_M2 = SubQuoHom(M2, M2, matrix(R, [0 1 0; 1 0 1; 1 1 0])) phi = hom_tensor(M, M, [phi_M1, phi_M2]) v = M[1] + 2*M[2] - @test phi(v) == pure_M((phi_M1(M1[1]), phi_M2(M2[1]))) + 2*pure_M((phi_M1(M1[2]), phi_M2(M2[1]))) + @test phi(v) == pure_M((phi_M1(M1[1]), phi_M2(M2[1]))) + 2*pure_M((phi_M1(M1[1]), phi_M2(M2[2]))) F4 = FreeMod(R, 4) A3 = matrix(R, [1 2; 3 4]) M3 = SubquoModule(Oscar.SubModuleOfFreeModule(F2, A3)) From d48c2600369ac4729a39cc4c0edd67669006c67b Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Wed, 21 May 2025 15:16:56 +0200 Subject: [PATCH 04/24] fix tests --- test/Modules/UngradedModules.jl | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/test/Modules/UngradedModules.jl b/test/Modules/UngradedModules.jl index d5b2fc717ebb..94375d06e057 100644 --- a/test/Modules/UngradedModules.jl +++ b/test/Modules/UngradedModules.jl @@ -1493,17 +1493,6 @@ end @testset "Tensoring morphisms over ZZ" begin R = ZZ F2 = FreeMod(R, 2) - F3 = FreeMod(R, 3) - A1 = matrix(ZZ, [1 2; 3 4; 5 6]) - B1 = matrix(ZZ, [7 8]) - A2 = matrix(ZZ, [2 0 1; 0 1 3; 1 1 1]) - B2 = matrix(ZZ, [0 1 1]) - M1 = SubquoModule(F2, A1, B1) - M2 = SubquoModule(F3, A2, B2) - M, pure_M = tensor_product(M1, M2, task=:map) - phi = hom_tensor(M, M, [identity_map(M1), identity_map(M2)]) - v = M[1] + 2*M[2] - @test phi(v) == v F4 = FreeMod(R, 4) A3 = matrix(ZZ, [1 2; 3 4]) M3 = SubquoModule(Oscar.SubModuleOfFreeModule(F2, A3)) From 2cbe19a66da70686951869839ea9b3f3d74272b5 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Wed, 21 May 2025 17:13:30 +0200 Subject: [PATCH 05/24] Fix typo, add is_finite --- src/Modules/UngradedModules/FreeMod.jl | 1 + src/Modules/UngradedModules/Presentation.jl | 81 +++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/Modules/UngradedModules/FreeMod.jl b/src/Modules/UngradedModules/FreeMod.jl index 53523fb7c3fa..dc09fb83110a 100644 --- a/src/Modules/UngradedModules/FreeMod.jl +++ b/src/Modules/UngradedModules/FreeMod.jl @@ -47,6 +47,7 @@ Free module of rank 2 over GF(7) julia> FK[1] e[1] +``` """ free_module_sparse(R::Union{ZZRing, Field, MPolyRing, MPolyQuoRing, MPolyLocRing, MPolyQuoLocRing}, n::Int, name::VarName = :e; cached::Bool = false) = FreeMod(R, n, name, cached=cached) diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index 35a6ecdc8e4e..762db96bb92c 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -714,3 +714,84 @@ end function prune_with_map(F::FreeMod) return F, hom(F, F, gens(F)) end + +@doc raw""" + is_finite(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} -> Bool + +Determine whether the finitely presented module `M` over `ZZ` or a field is finite. + +This is done by computing a minimal presentation and checking if the resulting +module has rank 0, meaning all generators are torsion elements. + +# Examples +```jldoctest +julia> R = ZZ; + +julia> F = free_module_sparse(R, 2); + +julia> A = matrix(ZZ, [2 0; 0 3]); + +julia> M = cokernel(hom(F, F, A)); + +julia> is_finite(M) +true + +julia> B = matrix(ZZ, [0 0; 0 0]); + +julia> N = cokernel(hom(F, F, B)); + +julia> is_finite(N) +false + +julia> B = matrix(ZZ, [0 0; 0 0]); + +julia> N = cokernel(hom(F, F, B)); + +julia> is_finite(N) +false + +julia> K, a = finite_field(7, "a"); + +julia> G = free_module_sparse(K, 3); + +julia> C = matrix(K, [1 0 0; 0 1 0; 0 0 1]); + +julia> L = cokernel(hom(G, G, C)); + +julia> is_finite(L) +true + +julia> H = free_module_sparse(QQ, 1); + +julia> P = cokernel(hom(H, H, matrix(QQ, 1, 1, [QQ(0)]))); + +julia> is_finite(P) +false + +julia> Z = cokernel(hom(H, H, matrix(QQ, 1, 1, [QQ(1)]))); + +julia> is_finite(Z) +true +``` +""" +function is_finite(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} + M_prime, _ = prune_with_map_atomic(M) + R = base_ring(M_prime) + pres = presentation(M_prime) + rel_map = map(pres, 1) + rel_matrix = matrix(rel_map) + nc = size(rel_matrix, 2) + nr = size(rel_matrix, 1) + has_free_generator = any(j -> all(i -> iszero(rel_matrix[i, j]), 1:nr), 1:nc) + if isa(R, ZZRing) + return !has_free_generator && ncols > 0 + elseif isa(R, Field) + if is_finite(R) + return true + else + return ncols == 0 + end + else + error("The base ring $(typeof(R)) is not supported.") + end +end From b26bb2461134418a17c3cbc979a90ddac593d0fe Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Wed, 21 May 2025 19:42:12 +0200 Subject: [PATCH 06/24] fix doc --- src/Modules/UngradedModules/Presentation.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index 762db96bb92c..2999f4351d2a 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -531,7 +531,6 @@ julia> phi(first(gens(N))) e[2] ``` """ -# generic dispatcher function prune_with_map(M::ModuleFP) return prune_with_map_atomic(M) end From f86de444d4ebfb2735cb3d7f9ee9ff48fce6e7dc Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Wed, 21 May 2025 20:46:20 +0200 Subject: [PATCH 07/24] Fix issue with tensor product of morphisms, fix tests --- src/Modules/UngradedModules/ModuleGens.jl | 13 ++++++++++-- test/Modules/UngradedModules.jl | 26 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/Modules/UngradedModules/ModuleGens.jl b/src/Modules/UngradedModules/ModuleGens.jl index 493508c22a51..6efa5e231329 100644 --- a/src/Modules/UngradedModules/ModuleGens.jl +++ b/src/Modules/UngradedModules/ModuleGens.jl @@ -419,12 +419,22 @@ Note: `:via_lift` is typically faster than `:via_transform` for a single vector is faster if many vectors are lifted """ function coordinates(a::FreeModElem, M::SubModuleOfFreeModule, task::Symbol = :auto) + R = base_ring(parent(a)) if iszero(a) - return sparse_row(base_ring(parent(a))) + return sparse_row(R) + end + for (i, g) in enumerate(gens(M)) + if a == g + row = sparse_row(R) + push!(row.pos, i) + push!(row.values, one(R)) + return row + end end return coordinates_atomic(a, M; task=task) end + function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task::Symbol = :auto) where {S<:Union{ZZRingElem,<:FieldElem}, T<:MPolyRingElem{S}} if task == :auto task = coefficient_ring(base_ring(parent(a))) isa Field ? :via_transform : :via_lift @@ -461,7 +471,6 @@ end function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task::Symbol=:auto) where {T<:Union{ZZRingElem, FieldElem}} ensure_solve_ctx!(M) solve_ctx = get_attribute(M, :solve_ctx) - n = ngens(M) R = base_ring(ambient_free_module(M)) d = rank(ambient_free_module(M)) vec_a = zeros(R, d) diff --git a/test/Modules/UngradedModules.jl b/test/Modules/UngradedModules.jl index 94375d06e057..b823aba0fa76 100644 --- a/test/Modules/UngradedModules.jl +++ b/test/Modules/UngradedModules.jl @@ -1494,6 +1494,14 @@ end R = ZZ F2 = FreeMod(R, 2) F4 = FreeMod(R, 4) + A1 = matrix(ZZ, [1 2; 3 4; 5 6]) + B1 = matrix(ZZ, [7 8]) + A2 = matrix(ZZ, [2 0 1; 0 1 3; 1 1 1]) + B2 = matrix(ZZ, [0 1 1]) + M1 = SubquoModule(F2, A1, B1) + F3 = FreeMod(R, 3) + M2 = SubquoModule(F3, A2, B2) + M, pure_M = tensor_product(M1, M2, task=:map) A3 = matrix(ZZ, [1 2; 3 4]) M3 = SubquoModule(Oscar.SubModuleOfFreeModule(F2, A3)) N, pure_N = tensor_product(M3, F4, task=:map) @@ -1504,6 +1512,24 @@ end u1 = M3[1] u2 = F4[1] @test phi2(pure_N((u1, u2))) == pure_M((M3_to_M1(u1), F4_to_M2(u2))) + F3 = FreeMod(R, 3) + A1 = matrix(ZZ, [1 2; 3 4; 5 6]) + B1 = matrix(ZZ, [7 8]) + A2 = matrix(ZZ, [2 0 1; 0 1 3; 1 1 1]) + B2 = matrix(ZZ, [0 1 1]) + M1 = SubquoModule(F2, A1, B1) + M2 = SubquoModule(F3, A2, B2) + M, pure_M = tensor_product(M1, M2, task=:map) + phi_M1 = hom(M1, M1, identity_matrix(ZZ, 3)) + phi_M2 = hom(M2, M2, identity_matrix(ZZ, 3)) + phi = hom_tensor(M, M, [phi_M1, phi_M2]) + @test is_welldefined(phi) + @test is_bijective(phi) + for u1 in gens(M1), u2 in gens(M2) + input_elem = pure_M((u1, u2)) + output_elem = pure_M((phi_M1(u1), phi_M2(u2))) + @test phi(input_elem) == output_elem + end end @testset "Direct product over ZZ" begin From 9f7f16176bf311fcb75922ce0db3ff156d114990 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Wed, 21 May 2025 21:35:44 +0200 Subject: [PATCH 08/24] fix typo --- src/Modules/UngradedModules/Presentation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index 2999f4351d2a..74bffd0c6590 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -788,7 +788,7 @@ function is_finite(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} if is_finite(R) return true else - return ncols == 0 + return nc == 0 end else error("The base ring $(typeof(R)) is not supported.") From a7b39a63a8bb98b6e3f0a7602d730c74742c7bc4 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Wed, 21 May 2025 22:06:27 +0200 Subject: [PATCH 09/24] docu --- .../ModulesOverMultivariateRings/free_modules.md | 7 ++++++- docs/src/CommutativeAlgebra/homological_algebra.md | 5 +++++ src/Modules/UngradedModules/FreeMod.jl | 3 ++- src/Modules/UngradedModules/Presentation.jl | 7 +++---- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/free_modules.md b/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/free_modules.md index 3bcbef245c93..a1fee6417c25 100644 --- a/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/free_modules.md +++ b/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/free_modules.md @@ -7,7 +7,7 @@ DocTestSetup = Oscar.doctestsetup() # [Free Modules](@id free_modules) In this section, the expression *free module* refers to a free module of finite rank -over a ring of type `MPolyRing`, `MPolyQuoRing`, `MPolyLocRing`, or `MPolyQuoLocRing`. +over a ring of type `MPolyRing`, `MPolyQuoRing`, `MPolyLocRing`, `MPolyQuoLocRing`, `ZZ`, or `Field`. More concretely, given a ring $R$ of one of these types, the free $R$-modules considered are of type $R^p$, where we think of $R^p$ as a free module with a given basis, namely the basis of standard unit vectors. Accordingly, elements of free modules are represented by coordinate vectors, @@ -35,6 +35,11 @@ they are modeled as objects of the concrete type `FreeMod{T} <: AbstractFreeMod{ free_module(R::MPolyRing, n::Int, name::VarName = :e; cached::Bool = false) ``` +```@docs +free_module_sparse(R::ZZRing, n::Int, name::VarName = :e; cached::Bool = false) +free_module_sparse(R::Field, n::Int, name::VarName = :e; cached::Bool = false) +``` + Over graded multivariate polynomial rings and their quotients, there are two basic ways of creating graded free modules: While the `grade` function allows one to create a graded free module by assigning a grading to a free module already constructed, the `graded_free_module` function is diff --git a/docs/src/CommutativeAlgebra/homological_algebra.md b/docs/src/CommutativeAlgebra/homological_algebra.md index 8c7dd334bd52..f7d2ae22871f 100644 --- a/docs/src/CommutativeAlgebra/homological_algebra.md +++ b/docs/src/CommutativeAlgebra/homological_algebra.md @@ -17,6 +17,11 @@ supporting computations in homological algebra. ```@docs prune_with_map(M::ModuleFP) ``` +## Finiteness as a set + +```@docs +is_finite(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} +``` ## Presentations diff --git a/src/Modules/UngradedModules/FreeMod.jl b/src/Modules/UngradedModules/FreeMod.jl index dc09fb83110a..51b5b7f0d672 100644 --- a/src/Modules/UngradedModules/FreeMod.jl +++ b/src/Modules/UngradedModules/FreeMod.jl @@ -28,7 +28,8 @@ end free_module_sparse(R::Field, n::Int, name::VarName = :e; cached::Bool = false) Construct a free module of rank `n` over the ring `R` using a sparse implementation -in cases where the `free_module` constructor returns a free module with a dense one. +compatible with the generic modules framework, in cases where the `free_module` constructor +returns a free module with a dense implementation. The string `name` specifies how the basis vectors are printed. diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index 74bffd0c6590..9a669881fe7c 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -715,12 +715,11 @@ function prune_with_map(F::FreeMod) end @doc raw""" - is_finite(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} -> Bool + is_finite(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} -Determine whether the finitely presented module `M` over `ZZ` or a field is finite. +Determine whether the finitely presented module `M` over `ZZ` or a field is finite as a set. -This is done by computing a minimal presentation and checking if the resulting -module has rank 0, meaning all generators are torsion elements. +This is done by computing a minimal presentation. # Examples ```jldoctest From ecf4c70a8508d9a0981527213204912e9892ac18 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Wed, 21 May 2025 22:13:18 +0200 Subject: [PATCH 10/24] fix typo --- src/Modules/UngradedModules/Presentation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index 9a669881fe7c..43a0273f2831 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -782,7 +782,7 @@ function is_finite(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} nr = size(rel_matrix, 1) has_free_generator = any(j -> all(i -> iszero(rel_matrix[i, j]), 1:nr), 1:nc) if isa(R, ZZRing) - return !has_free_generator && ncols > 0 + return !has_free_generator && nc > 0 elseif isa(R, Field) if is_finite(R) return true From db1684454d1b8bc9e2ea5ca03080916c0da48602 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Wed, 21 May 2025 23:14:35 +0200 Subject: [PATCH 11/24] fix typo in doctest --- src/Modules/UngradedModules/FreeMod.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Modules/UngradedModules/FreeMod.jl b/src/Modules/UngradedModules/FreeMod.jl index 51b5b7f0d672..4b58721dbe0c 100644 --- a/src/Modules/UngradedModules/FreeMod.jl +++ b/src/Modules/UngradedModules/FreeMod.jl @@ -36,7 +36,7 @@ The string `name` specifies how the basis vectors are printed. # Examples ```jldoctest julia> F = free_module_sparse(ZZ, 3, "f") -Free module of rank 3 over ZZ +Free module of rank 3 over integer ring julia> F[1] f[1] @@ -44,7 +44,7 @@ f[1] julia> K = GF(7); julia> FK = free_module_sparse(K, 2) -Free module of rank 2 over GF(7) +Free module of rank 2 over K julia> FK[1] e[1] From af20ada7a29068b7119d34ddad1b83b0eaf0d6ac Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Fri, 23 May 2025 14:37:52 +0200 Subject: [PATCH 12/24] Comments by Lars --- src/Modules/UngradedModules/FreeModuleHom.jl | 14 ++-- src/Modules/UngradedModules/ModuleGens.jl | 30 ++------- src/Modules/UngradedModules/Presentation.jl | 67 ++++++++----------- .../UngradedModules/SubModuleOfFreeModule.jl | 30 +++------ test/Modules/UngradedModules.jl | 11 +++ 5 files changed, 59 insertions(+), 93 deletions(-) diff --git a/src/Modules/UngradedModules/FreeModuleHom.jl b/src/Modules/UngradedModules/FreeModuleHom.jl index 42dd6c6c970f..46eb4fd0dd7d 100644 --- a/src/Modules/UngradedModules/FreeModuleHom.jl +++ b/src/Modules/UngradedModules/FreeModuleHom.jl @@ -470,23 +470,17 @@ function kernel_atomic(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) wh return sub(F, v) end -function ensure_kernel_ctx!(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {T<:Union{ZZRingElem, FieldElem}} - if !has_attribute(h, :kernel_ctx) - mat_h = transpose(matrix(h)) - set_attribute!(h, :kernel_ctx, solve_init(mat_h)) - end +@attr function kernel_ctx(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {T<:Union{ZZRingElem, FieldElem}} + solve_init(transpose(matrix(h))) end function kernel_atomic(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {T<:Union{ZZRingElem, FieldElem}} - ensure_kernel_ctx!(h) - kernel_ctx = get_attribute(h, :kernel_ctx) - K = kernel(kernel_ctx, side=:right) + K = kernel(kernel_ctx(h); side=:right) F = domain(h) - v = [F(sparse_row_from_dense(base_ring(F), vec(K[:, j]))) for j in 1:ncols(K)] + v = [F(sparse_row(transpose(K[:, j:j]))) for j in 1:ncols(K)] return sub(F, v) end - function _graded_kernel(h::FreeModuleHom{<:FreeMod, <:FreeMod}) I, inc = kernel_atomic(h) @assert is_graded(I) diff --git a/src/Modules/UngradedModules/ModuleGens.jl b/src/Modules/UngradedModules/ModuleGens.jl index 6efa5e231329..d8270fad3043 100644 --- a/src/Modules/UngradedModules/ModuleGens.jl +++ b/src/Modules/UngradedModules/ModuleGens.jl @@ -425,10 +425,7 @@ function coordinates(a::FreeModElem, M::SubModuleOfFreeModule, task::Symbol = :a end for (i, g) in enumerate(gens(M)) if a == g - row = sparse_row(R) - push!(row.pos, i) - push!(row.values, one(R)) - return row + return sparse_row(R, [i], [one(R)]) end end return coordinates_atomic(a, M; task=task) @@ -456,35 +453,18 @@ function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task::S end end -function sparse_row_from_dense(R::Ring, dense_vec::Vector{T}) where T - positions = Int[] - values = T[] - for (i, val) in enumerate(dense_vec) - if !iszero(val) - push!(positions, i) - push!(values, val) - end - end - return sparse_row(R, positions, values) -end - function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task::Symbol=:auto) where {T<:Union{ZZRingElem, FieldElem}} - ensure_solve_ctx!(M) - solve_ctx = get_attribute(M, :solve_ctx) + ctx = solve_ctx(M) R = base_ring(ambient_free_module(M)) d = rank(ambient_free_module(M)) - vec_a = zeros(R, d) - for (i, val) in coordinates(a) - vec_a[i] = val - end - is_solvable, sol = can_solve_with_solution(solve_ctx, vec_a, side=:right) + vec_a = matrix(dense_row(coordinates(a), d)) + is_solvable, sol = can_solve_with_solution(ctx, vec_a; side=:left) if !is_solvable error("Element is not contained in the module.") end - return sparse_row_from_dense(R, sol) + return sparse_row(sol) end - function coordinates_atomic(a::FreeModElem, M::SubModuleOfFreeModule; task::Symbol = :auto) error("The function coordinates_atomic is not implemented for modules over rings of type $(typeof(base_ring(ambient_free_module(M))))") end diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index 43a0273f2831..24b238685729 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -588,53 +588,42 @@ function prune_with_map_atomic(M::ModuleFP{T}) where {T<:Union{MPolyRingElem, MP return M_new, phi end -function ensure_presentation_ctx!(M::SubquoModule) - ensure_presentation_ctx!(base_ring(M), M) -end - -function ensure_presentation_ctx!(M::SubquoModule{ZZRingElem}) - if !has_attribute(M, :presentation_ctx) - R = base_ring(M) - F = ambient_free_module(M) - m = ngens(F) - rels = relations(M) - n = length(rels) - - A = zero_matrix(R, m, n) - for (j, rel) in enumerate(rels) - for (i, v) in coordinates(rel) - A[i, j] = v - end +@attr function presentation_ctx(M::SubquoModule{ZZRingElem}) + R = base_ring(M) + F = ambient_free_module(M) + m = ngens(F) + rels = relations(M) + n = length(rels) + A = zero_matrix(R, m, n) + for (j, rel) in enumerate(rels) + for (i, v) in coordinates(rel) + A[i, j] = v end - - D, U, V = snf_with_transform(A) - set_attribute!(M, :presentation_ctx, (D=D, U=U, V=V)) end + D, U, V = snf_with_transform(A) + return (D=D, U=U, V=V) end -function ensure_presentation_ctx!(M::SubquoModule{T}) where {T<:FieldElem} - if !has_attribute(M, :presentation_ctx) - R = base_ring(M) - F = ambient_free_module(M) - m = ngens(F) - rels = relations(M) - n = length(rels) - - A = zero_matrix(R, m, n) - for (j, rel) in enumerate(rels) - for (i, v) in coordinates(rel) - A[i, j] = v - end - end +@attr function presentation_ctx(M::SubquoModule{T}) where {T<:FieldElem} + R = base_ring(M) + F = ambient_free_module(M) + m = ngens(F) + rels = relations(M) + n = length(rels) - D, U, V = snf_with_transform(A) - set_attribute!(M, :presentation_ctx, (D=D, U=U, V=V)) + A = zero_matrix(R, m, n) + for (j, rel) in enumerate(rels) + for (i, v) in coordinates(rel) + A[i, j] = v + end end + + D, U, V = snf_with_transform(A) + return (D=D, U=U, V=V) end function prune_with_map_atomic(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} - ensure_presentation_ctx!(M) - ctx = get_attribute(M, :presentation_ctx) + ctx = presentation_ctx(M) R = base_ring(M) F = ambient_free_module(M) m = ngens(F) @@ -646,7 +635,7 @@ function prune_with_map_atomic(M::SubquoModule{T}) where {T<:Union{ZZRingElem, F col_indices = setdiff(1:size(D, 2), unit_diag_indices) D_prime_matrix = sub(D, row_indices, col_indices) F_prime = FreeMod(R, length(row_indices)) - rels_prime = [F_prime(sparse_row_from_dense(R, D_prime_matrix[:, j])) for j in 1:size(D_prime_matrix, 2)] + rels_prime = [F_prime(sparse_row(transpose(D_prime_matrix[:, j:j]))) for j in 1:size(D_prime_matrix, 2)] N_prime, _ = sub(F_prime, rels_prime) M_prime, _ = quo(F_prime, N_prime) U_inv = inv(U) diff --git a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl index d0f44b33b743..2ff433fb9e13 100644 --- a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl +++ b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl @@ -483,29 +483,21 @@ function in_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule) where {S<:Union{ return iszero(reduce(a, standard_basis(M, ordering=default_ordering(F)))) end -function ensure_solve_ctx!(M::SubModuleOfFreeModule) - if !has_attribute(M, :solve_ctx) - F = ambient_free_module(M) - d, n = rank(F), ngens(M) - R = base_ring(F) - mat = zero_matrix(R, d, n) - for (j, g) in enumerate(gens(M)), (i, val) in coordinates(g) - mat[i, j] = val - end - set_attribute!(M, :solve_ctx, solve_init(mat)) +@attr function solve_ctx(M::SubModuleOfFreeModule) + F = ambient_free_module(M) + d, n = rank(F), ngens(M) + R = base_ring(F) + mat = zero_matrix(R, n, d) + for (j, g) in enumerate(gens(M)), (i, val) in coordinates(g) + mat[j, i] = val end + return solve_init(mat) end function in_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule) where {T<:Union{ZZRingElem, FieldElem}} - ensure_solve_ctx!(M) - solve_ctx = get_attribute(M, :solve_ctx) - R = base_ring(ambient_free_module(M)) - d = rank(ambient_free_module(M)) - vec_a = zeros(R, d) - for (i, val) in coordinates(a) - vec_a[i] = val - end - return can_solve(solve_ctx, vec_a, side=:right) + ctx = solve_ctx(M) + vec_a = dense_row(coordinates(a), rank(ambient_free_module(M))) + return can_solve(ctx, vec_a; side=:left) end function in_atomic(a::FreeModElem, M::SubModuleOfFreeModule) diff --git a/test/Modules/UngradedModules.jl b/test/Modules/UngradedModules.jl index b823aba0fa76..b62fe9db2376 100644 --- a/test/Modules/UngradedModules.jl +++ b/test/Modules/UngradedModules.jl @@ -1419,6 +1419,17 @@ end @test is_welldefined(proj) end + @testset "Submodule Membership over ZZ" begin + R = ZZ + F = FreeMod(R, 3) + gens_submodule = [2*F[1], 3*F[2]] + S, _ = sub(F, gens_submodule) + x = 4*F[1]+3*F[2] + @test in(x, S) + coord = coordinates(x, S) + @test coord == sparse_row(R, [1, 2], [2, 1]) + end + @testset "Module Homomorphisms over ZZ" begin F1 = FreeMod(ZZ, 2) F2 = FreeMod(ZZ, 2) From 34b3ea957b0705e9afb06d667bed3680e1df37a0 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Fri, 23 May 2025 14:53:48 +0200 Subject: [PATCH 13/24] attr issue --- src/Modules/UngradedModules/FreeModuleHom.jl | 2 +- src/Modules/UngradedModules/Presentation.jl | 2 +- src/Modules/UngradedModules/SubModuleOfFreeModule.jl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Modules/UngradedModules/FreeModuleHom.jl b/src/Modules/UngradedModules/FreeModuleHom.jl index 46eb4fd0dd7d..2ab3578a6aab 100644 --- a/src/Modules/UngradedModules/FreeModuleHom.jl +++ b/src/Modules/UngradedModules/FreeModuleHom.jl @@ -470,7 +470,7 @@ function kernel_atomic(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) wh return sub(F, v) end -@attr function kernel_ctx(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {T<:Union{ZZRingElem, FieldElem}} +@attr Any function kernel_ctx(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {T<:Union{ZZRingElem, FieldElem}} solve_init(transpose(matrix(h))) end diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index 24b238685729..2468c47a80a0 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -588,7 +588,7 @@ function prune_with_map_atomic(M::ModuleFP{T}) where {T<:Union{MPolyRingElem, MP return M_new, phi end -@attr function presentation_ctx(M::SubquoModule{ZZRingElem}) +@attr Any function presentation_ctx(M::SubquoModule{ZZRingElem}) R = base_ring(M) F = ambient_free_module(M) m = ngens(F) diff --git a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl index 2ff433fb9e13..93db6a95db1e 100644 --- a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl +++ b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl @@ -483,7 +483,7 @@ function in_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule) where {S<:Union{ return iszero(reduce(a, standard_basis(M, ordering=default_ordering(F)))) end -@attr function solve_ctx(M::SubModuleOfFreeModule) +@attr Any function solve_ctx(M::SubModuleOfFreeModule) F = ambient_free_module(M) d, n = rank(F), ngens(M) R = base_ring(F) From 07d2d7676934ea254f83ed4e4ea4061962114081 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Fri, 23 May 2025 15:28:55 +0200 Subject: [PATCH 14/24] attr fix --- src/Modules/UngradedModules/Presentation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index 2468c47a80a0..054de1ea8285 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -604,7 +604,7 @@ end return (D=D, U=U, V=V) end -@attr function presentation_ctx(M::SubquoModule{T}) where {T<:FieldElem} +@attr Any function presentation_ctx(M::SubquoModule{T}) where {T<:FieldElem} R = base_ring(M) F = ambient_free_module(M) m = ngens(F) From 1e68cf3b84ecb16301aaf9bfd328ef585a7a04f7 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Tue, 27 May 2025 12:14:37 +0200 Subject: [PATCH 15/24] optimization for in --- src/Modules/UngradedModules/SubModuleOfFreeModule.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl index 93db6a95db1e..150445fd5366 100644 --- a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl +++ b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl @@ -475,6 +475,8 @@ end Check if `a` is an element of `M`. """ function in(a::FreeModElem, M::SubModuleOfFreeModule) + iszero(a) && return true + any(==(a), gens(M)) && return true return in_atomic(a, M) end From 7fdba3b84d50b92e6e1263da6829faf212e91733 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Fri, 30 May 2025 15:25:44 +0200 Subject: [PATCH 16/24] comments by Lars --- .../ModulesOverMultivariateRings/free_modules.md | 6 +++--- .../ModulesOverMultivariateRings/subquotients.md | 2 +- src/Modules/UngradedModules/FreeMod.jl | 10 +++++----- src/Modules/UngradedModules/ModuleGens.jl | 2 +- src/Modules/UngradedModules/SubModuleOfFreeModule.jl | 2 +- src/exports.jl | 1 - test/Modules/UngradedModules.jl | 4 ++-- 7 files changed, 13 insertions(+), 14 deletions(-) diff --git a/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/free_modules.md b/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/free_modules.md index a1fee6417c25..a325d7fb5866 100644 --- a/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/free_modules.md +++ b/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/free_modules.md @@ -7,7 +7,7 @@ DocTestSetup = Oscar.doctestsetup() # [Free Modules](@id free_modules) In this section, the expression *free module* refers to a free module of finite rank -over a ring of type `MPolyRing`, `MPolyQuoRing`, `MPolyLocRing`, `MPolyQuoLocRing`, `ZZ`, or `Field`. +over a ring of type `MPolyRing`, `MPolyQuoRing`, `MPolyLocRing`, `MPolyQuoLocRing`, `ZZRing`, or `Field`. More concretely, given a ring $R$ of one of these types, the free $R$-modules considered are of type $R^p$, where we think of $R^p$ as a free module with a given basis, namely the basis of standard unit vectors. Accordingly, elements of free modules are represented by coordinate vectors, @@ -36,8 +36,8 @@ free_module(R::MPolyRing, n::Int, name::VarName = :e; cached::Bool = false) ``` ```@docs -free_module_sparse(R::ZZRing, n::Int, name::VarName = :e; cached::Bool = false) -free_module_sparse(R::Field, n::Int, name::VarName = :e; cached::Bool = false) +free_module(::Type{<:FreeMod}, R::Union{ZZRing, Field, MPolyRing, MPolyQuoRing, MPolyLocRing, MPolyQuoLocRing}, + n::Int, name::VarName = :e; cached::Bool = false) ``` Over graded multivariate polynomial rings and their quotients, there are two basic ways of diff --git a/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/subquotients.md b/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/subquotients.md index dd637800bbcc..65e3cde61c13 100644 --- a/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/subquotients.md +++ b/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/subquotients.md @@ -8,7 +8,7 @@ DocTestSetup = Oscar.doctestsetup() A subquotient is a submodule of a quotient of a free module. In this section, the expression *subquotient* refers to a subquotient over a ring of type `MPolyRing`, `MPolyQuoRing`, -`MPolyLocRing`, or `MPolyQuoLocRing`. That is, given a ring $R$ of one of these +`MPolyLocRing`, `MPolyQuoLocRing`, `ZZRing`, or `Field`. That is, given a ring $R$ of one of these types, a subquotient $M$ over $R$ is a module of type $M = (\text{im } a + \text{im } b)/\text{im } b,$ diff --git a/src/Modules/UngradedModules/FreeMod.jl b/src/Modules/UngradedModules/FreeMod.jl index 4b58721dbe0c..8a833561fb0d 100644 --- a/src/Modules/UngradedModules/FreeMod.jl +++ b/src/Modules/UngradedModules/FreeMod.jl @@ -24,8 +24,8 @@ end @doc raw""" - free_module_sparse(R::ZZRing, n::Int, name::VarName = :e; cached::Bool = false) - free_module_sparse(R::Field, n::Int, name::VarName = :e; cached::Bool = false) + free_module(::Type{<:FreeMod}, R::Union{ZZRing, Field, MPolyRing, MPolyQuoRing, MPolyLocRing, MPolyQuoLocRing}, + n::Int, name::VarName = :e; cached::Bool = false) Construct a free module of rank `n` over the ring `R` using a sparse implementation compatible with the generic modules framework, in cases where the `free_module` constructor @@ -35,7 +35,7 @@ The string `name` specifies how the basis vectors are printed. # Examples ```jldoctest -julia> F = free_module_sparse(ZZ, 3, "f") +julia> F = free_module(FreeMod, ZZ, 3, "f") Free module of rank 3 over integer ring julia> F[1] @@ -43,14 +43,14 @@ f[1] julia> K = GF(7); -julia> FK = free_module_sparse(K, 2) +julia> FK = free_module(FreeMod, K, 2) Free module of rank 2 over K julia> FK[1] e[1] ``` """ -free_module_sparse(R::Union{ZZRing, Field, MPolyRing, MPolyQuoRing, MPolyLocRing, MPolyQuoLocRing}, +free_module(::Type{<:FreeMod}, R::Union{ZZRing, Field, MPolyRing, MPolyQuoRing, MPolyLocRing, MPolyQuoLocRing}, n::Int, name::VarName = :e; cached::Bool = false) = FreeMod(R, n, name, cached=cached) @doc raw""" diff --git a/src/Modules/UngradedModules/ModuleGens.jl b/src/Modules/UngradedModules/ModuleGens.jl index d8270fad3043..d6a32b71a456 100644 --- a/src/Modules/UngradedModules/ModuleGens.jl +++ b/src/Modules/UngradedModules/ModuleGens.jl @@ -432,7 +432,7 @@ function coordinates(a::FreeModElem, M::SubModuleOfFreeModule, task::Symbol = :a end -function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task::Symbol = :auto) where {S<:Union{ZZRingElem,<:FieldElem}, T<:MPolyRingElem{S}} +function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task::Symbol = :auto) where {S<:Union{ZZRingElem,FieldElem}, T<:MPolyRingElem{S}} if task == :auto task = coefficient_ring(base_ring(parent(a))) isa Field ? :via_transform : :via_lift end diff --git a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl index 150445fd5366..4a17de7267f1 100644 --- a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl +++ b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl @@ -480,7 +480,7 @@ function in(a::FreeModElem, M::SubModuleOfFreeModule) return in_atomic(a, M) end -function in_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule) where {S<:Union{ZZRingElem,<:FieldElem}, T<:MPolyRingElem{S}} +function in_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule) where {S<:Union{ZZRingElem,FieldElem}, T<:MPolyRingElem{S}} F = ambient_free_module(M) return iszero(reduce(a, standard_basis(M, ordering=default_ordering(F)))) end diff --git a/src/exports.jl b/src/exports.jl index 66b81bec438a..77c6e8546377 100644 --- a/src/exports.jl +++ b/src/exports.jl @@ -651,7 +651,6 @@ export free_extension export free_group export free_module export free_module_dec -export free_module_sparse export free_resolution export free_resolution_via_kernels export full_group diff --git a/test/Modules/UngradedModules.jl b/test/Modules/UngradedModules.jl index b62fe9db2376..ba02e1d9ebac 100644 --- a/test/Modules/UngradedModules.jl +++ b/test/Modules/UngradedModules.jl @@ -1406,7 +1406,7 @@ end @testset "Modules over ZZ and QQ" begin @testset "Module Constructors over ZZ" begin - F0 = free_module_sparse(ZZ,3) + F0 = free_module(:FreeMod, ZZ,3) @test rank(F0) == 3 @test base_ring(F0) == ZZ @@ -1579,7 +1579,7 @@ end @testset "Module Constructors over QQ" begin - F0 = free_module_sparse(QQ,3) + F0 = free_module(:FreeMod, QQ,3) @test rank(F0) == 3 @test base_ring(F0) == QQ From 06ec5fe01033b8c8f628fccf33622d5c3f3395b4 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Fri, 30 May 2025 16:13:31 +0200 Subject: [PATCH 17/24] fix --- test/Modules/UngradedModules.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Modules/UngradedModules.jl b/test/Modules/UngradedModules.jl index ba02e1d9ebac..f783405cd71f 100644 --- a/test/Modules/UngradedModules.jl +++ b/test/Modules/UngradedModules.jl @@ -1406,7 +1406,7 @@ end @testset "Modules over ZZ and QQ" begin @testset "Module Constructors over ZZ" begin - F0 = free_module(:FreeMod, ZZ,3) + F0 = free_module(FreeMod, ZZ,3) @test rank(F0) == 3 @test base_ring(F0) == ZZ @@ -1579,7 +1579,7 @@ end @testset "Module Constructors over QQ" begin - F0 = free_module(:FreeMod, QQ,3) + F0 = free_module(FreeMod, QQ,3) @test rank(F0) == 3 @test base_ring(F0) == QQ From 5ae94418da5daacb0b0c36a5ab04bb78e0c68bf0 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Fri, 30 May 2025 17:15:27 +0200 Subject: [PATCH 18/24] streamline kernel --- src/Modules/UngradedModules/FreeModuleHom.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Modules/UngradedModules/FreeModuleHom.jl b/src/Modules/UngradedModules/FreeModuleHom.jl index 2ab3578a6aab..6a6527b09ce9 100644 --- a/src/Modules/UngradedModules/FreeModuleHom.jl +++ b/src/Modules/UngradedModules/FreeModuleHom.jl @@ -471,13 +471,13 @@ function kernel_atomic(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) wh end @attr Any function kernel_ctx(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {T<:Union{ZZRingElem, FieldElem}} - solve_init(transpose(matrix(h))) + solve_init(matrix(h)) end function kernel_atomic(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {T<:Union{ZZRingElem, FieldElem}} - K = kernel(kernel_ctx(h); side=:right) + K = kernel(kernel_ctx(h); side=:left) F = domain(h) - v = [F(sparse_row(transpose(K[:, j:j]))) for j in 1:ncols(K)] + v = [F(sparse_row(K[j:j, :])) for j in 1:nrows(K)] return sub(F, v) end From 3258581ce0eb7dc697164a9ad2b7d75df12e717c Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Fri, 30 May 2025 19:08:49 +0200 Subject: [PATCH 19/24] fix --- src/Modules/UngradedModules/Presentation.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index 054de1ea8285..608526e43431 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -714,7 +714,7 @@ This is done by computing a minimal presentation. ```jldoctest julia> R = ZZ; -julia> F = free_module_sparse(R, 2); +julia> F = free_module(FreeMod, R, 2); julia> A = matrix(ZZ, [2 0; 0 3]); @@ -739,7 +739,7 @@ false julia> K, a = finite_field(7, "a"); -julia> G = free_module_sparse(K, 3); +julia> G = free_module(FreeMod, K, 3); julia> C = matrix(K, [1 0 0; 0 1 0; 0 0 1]); @@ -748,7 +748,7 @@ julia> L = cokernel(hom(G, G, C)); julia> is_finite(L) true -julia> H = free_module_sparse(QQ, 1); +julia> H = free_module(FreeMod, QQ, 1); julia> P = cokernel(hom(H, H, matrix(QQ, 1, 1, [QQ(0)]))); From 6281c7f1ca733867efc1f23098852b40d29590c4 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Sun, 1 Jun 2025 14:17:47 +0200 Subject: [PATCH 20/24] fix --- .../src/InjectiveResolutions.jl | 9 ++-- .../src/ModuleFunctionality.jl | 49 +++++++++++++++++-- src/Modules/UngradedModules/ModuleGens.jl | 7 --- 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/experimental/InjectiveResolutions/src/InjectiveResolutions.jl b/experimental/InjectiveResolutions/src/InjectiveResolutions.jl index 85ec4bc753c3..d189810fe3c6 100644 --- a/experimental/InjectiveResolutions/src/InjectiveResolutions.jl +++ b/experimental/InjectiveResolutions/src/InjectiveResolutions.jl @@ -9,15 +9,14 @@ import ..Oscar: _graded_kernel, _reduce, _saturation, - _simple_kernel, annihilator, coefficient_ring, coefficients, cone, coordinates, + coordinates_atomic, + coordinates_via_transform, degree, - degree, - dim, dim, elem_type, evaluate, @@ -26,6 +25,8 @@ import ..Oscar: gens, grading_group, hyperplanes, + images_of_generators, + in_atomic, intersect, inv, is_normal, @@ -33,6 +34,7 @@ import ..Oscar: is_subset, is_zm_graded, kernel, + kernel_atomic, lift_std, ModuleGens, normal_form, @@ -51,7 +53,6 @@ import ..Oscar: zero, zonotope - import ..Oscar.Singular: FreeModule, has_global_ordering, diff --git a/experimental/InjectiveResolutions/src/ModuleFunctionality.jl b/experimental/InjectiveResolutions/src/ModuleFunctionality.jl index dd137020c120..760097202a3d 100644 --- a/experimental/InjectiveResolutions/src/ModuleFunctionality.jl +++ b/experimental/InjectiveResolutions/src/ModuleFunctionality.jl @@ -57,6 +57,15 @@ function normal_form(M::ModuleGens{T}, GB::ModuleGens{T}) where {T <: MonoidAlge return res end +function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task=:auto) where {T <: MonoidAlgebraElem} + if task != :auto && task != :via_transform + error("Only task=:via_transform is supported for MonoidAlgebra.") + end + + std, _ = lift_std(M) + return coordinates_via_transform(a, std) +end + function lift_std(M::ModuleGens{T}) where {T <: MonoidAlgebraElem} R = base_ring(M) G,Trans_mat = Singular.lift_std(singular_generators(M)) # When Singular supports reduction add it also here @@ -76,6 +85,28 @@ function lift_std(M::ModuleGens{T}, ordering::ModuleOrdering) where {T <: Monoid return mg, mat end +function coordinates_via_transform(a::FreeModElem{T}, generators::ModuleGens{T}) where {T <: MonoidAlgebraElem} + A = get_attribute(generators, :transformation_matrix) + A === nothing && error("No transformation matrix in the Gröbner basis.") + if iszero(a) + return sparse_row(base_ring(parent(a))) + end + if !is_global(generators.ordering) + error("Ordering is not global") + end + @assert generators.isGB + S = singular_generators(generators) + S.isGB = generators.isGB + b = ModuleGens([a], singular_freemodule(generators)) + s, _ = Singular.lift(S, singular_generators(b)) + if Singular.ngens(s) == 0 || iszero(s[1]) + error("The free module element is not liftable to the given generating system.") + end + Rx = base_ring(generators) + coords_wrt_groebner_basis = sparse_row(Rx, s[1], 1:ngens(generators)) + return coords_wrt_groebner_basis * sparse_matrix(A) +end + function sparse_row( A::MonoidAlgebra{<:FieldElem, <:MPolyRing}, svec::Singular.svector, rng::AbstractUnitRange @@ -98,14 +129,24 @@ function syzygy_module(F::ModuleGens{T}; sub = FreeMod(base_ring(F), length(osca return SubquoModule(sub, s) end -function kernel( +function kernel_atomic( h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing} ) where {S<:FieldElem, T <: MonoidAlgebraElem{S}} - is_zero(h) && return sub(domain(h), gens(domain(h))) - is_graded(h) && return _graded_kernel(h) - return _simple_kernel(h) + F = domain(h) + G = codomain(h) + gens_h = images_of_generators(h) + mod_gens = ModuleGens(gens_h, G, default_ordering(G)) + M = syzygy_module(mod_gens) + v = [F(coordinates(repres(w))) for w in gens(M) if !is_zero(w)] + return sub(F, v) end +function in_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule) where {S<:FieldElem, T<:MonoidAlgebraElem{S}} + F = ambient_free_module(M) + return iszero(reduce(a, standard_basis(M, ordering=default_ordering(F)))) +end + + ### Additional adjustments to get the graded aspects to run function annihilator(N::SubquoModule{T}) where T <: MonoidAlgebraElem R = base_ring(N) diff --git a/src/Modules/UngradedModules/ModuleGens.jl b/src/Modules/UngradedModules/ModuleGens.jl index d6a32b71a456..fef748172028 100644 --- a/src/Modules/UngradedModules/ModuleGens.jl +++ b/src/Modules/UngradedModules/ModuleGens.jl @@ -436,13 +436,6 @@ function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task::S if task == :auto task = coefficient_ring(base_ring(parent(a))) isa Field ? :via_transform : :via_lift end - for i in 1:ngens(M) - g = gen(M,i) - if a == g - R = base_ring(M) - return sparse_row(R, [(i,R(1))]) - end - end if task == :via_transform std, _ = lift_std(M) return coordinates_via_transform(a, std) From 841caa8f92a526b68638fbfee596c88c5ec570a1 Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Tue, 3 Jun 2025 19:26:10 +0200 Subject: [PATCH 21/24] fix --- .../src/ModuleFunctionality.jl | 53 +- src/Modules/UngradedModules/FreeModuleHom.jl | 12 +- src/Modules/UngradedModules/ModuleGens.jl | 68 +-- src/Modules/UngradedModules/Presentation.jl | 128 ++-- .../UngradedModules/SubModuleOfFreeModule.jl | 34 +- test/Modules/UngradedModules.jl | 570 +++++++++--------- 6 files changed, 432 insertions(+), 433 deletions(-) diff --git a/experimental/InjectiveResolutions/src/ModuleFunctionality.jl b/experimental/InjectiveResolutions/src/ModuleFunctionality.jl index 760097202a3d..c4d9e4e3ec99 100644 --- a/experimental/InjectiveResolutions/src/ModuleFunctionality.jl +++ b/experimental/InjectiveResolutions/src/ModuleFunctionality.jl @@ -58,12 +58,11 @@ function normal_form(M::ModuleGens{T}, GB::ModuleGens{T}) where {T <: MonoidAlge end function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task=:auto) where {T <: MonoidAlgebraElem} - if task != :auto && task != :via_transform - error("Only task=:via_transform is supported for MonoidAlgebra.") - end - - std, _ = lift_std(M) - return coordinates_via_transform(a, std) + if task != :auto && task != :via_transform + error("Only task=:via_transform is supported for MonoidAlgebra.") + end + std, _ = lift_std(M) + return coordinates_via_transform(a, std) end function lift_std(M::ModuleGens{T}) where {T <: MonoidAlgebraElem} @@ -86,25 +85,25 @@ function lift_std(M::ModuleGens{T}, ordering::ModuleOrdering) where {T <: Monoid end function coordinates_via_transform(a::FreeModElem{T}, generators::ModuleGens{T}) where {T <: MonoidAlgebraElem} - A = get_attribute(generators, :transformation_matrix) - A === nothing && error("No transformation matrix in the Gröbner basis.") - if iszero(a) - return sparse_row(base_ring(parent(a))) - end - if !is_global(generators.ordering) - error("Ordering is not global") - end - @assert generators.isGB - S = singular_generators(generators) - S.isGB = generators.isGB - b = ModuleGens([a], singular_freemodule(generators)) - s, _ = Singular.lift(S, singular_generators(b)) - if Singular.ngens(s) == 0 || iszero(s[1]) - error("The free module element is not liftable to the given generating system.") - end - Rx = base_ring(generators) - coords_wrt_groebner_basis = sparse_row(Rx, s[1], 1:ngens(generators)) - return coords_wrt_groebner_basis * sparse_matrix(A) + A = get_attribute(generators, :transformation_matrix) + A === nothing && error("No transformation matrix in the Gröbner basis.") + if iszero(a) + return sparse_row(base_ring(parent(a))) + end + if !is_global(generators.ordering) + error("Ordering is not global") + end + @assert generators.isGB + S = singular_generators(generators) + S.isGB = generators.isGB + b = ModuleGens([a], singular_freemodule(generators)) + s, _ = Singular.lift(S, singular_generators(b)) + if Singular.ngens(s) == 0 || iszero(s[1]) + error("The free module element is not liftable to the given generating system.") + end + Rx = base_ring(generators) + coords_wrt_groebner_basis = sparse_row(Rx, s[1], 1:ngens(generators)) + return coords_wrt_groebner_basis * sparse_matrix(A) end function sparse_row( @@ -142,8 +141,8 @@ function kernel_atomic( end function in_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule) where {S<:FieldElem, T<:MonoidAlgebraElem{S}} - F = ambient_free_module(M) - return iszero(reduce(a, standard_basis(M, ordering=default_ordering(F)))) + F = ambient_free_module(M) + return iszero(reduce(a, standard_basis(M, ordering=default_ordering(F)))) end diff --git a/src/Modules/UngradedModules/FreeModuleHom.jl b/src/Modules/UngradedModules/FreeModuleHom.jl index 6a6527b09ce9..a039b42a09d4 100644 --- a/src/Modules/UngradedModules/FreeModuleHom.jl +++ b/src/Modules/UngradedModules/FreeModuleHom.jl @@ -460,7 +460,7 @@ function kernel_atomic(h::FreeModuleHom{<:FreeMod, <:FreeMod}) error("not implemented for modules over rings of type $(typeof(base_ring(domain(h))))") end -function kernel_atomic(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {S<:Union{ZZRingElem, <:FieldElem}, T<:MPolyRingElem{S}} +function kernel_atomic(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {S<:Union{ZZRingElem, FieldElem}, T<:MPolyRingElem{S}} F = domain(h) G = codomain(h) gens_h = images_of_generators(h) @@ -471,14 +471,14 @@ function kernel_atomic(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) wh end @attr Any function kernel_ctx(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {T<:Union{ZZRingElem, FieldElem}} - solve_init(matrix(h)) + solve_init(matrix(h)) end function kernel_atomic(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {T<:Union{ZZRingElem, FieldElem}} - K = kernel(kernel_ctx(h); side=:left) - F = domain(h) - v = [F(sparse_row(K[j:j, :])) for j in 1:nrows(K)] - return sub(F, v) + K = kernel(kernel_ctx(h); side=:left) + F = domain(h) + v = [F(sparse_row(K[j:j, :])) for j in 1:nrows(K)] + return sub(F, v) end function _graded_kernel(h::FreeModuleHom{<:FreeMod, <:FreeMod}) diff --git a/src/Modules/UngradedModules/ModuleGens.jl b/src/Modules/UngradedModules/ModuleGens.jl index fef748172028..dcb002c79119 100644 --- a/src/Modules/UngradedModules/ModuleGens.jl +++ b/src/Modules/UngradedModules/ModuleGens.jl @@ -365,10 +365,10 @@ Compute a sparse row `r` such that `a = sum([r[i]*gen(generators,i) for i in 1:n If no such `r` exists, an exception is thrown. """ function coordinates(a::FreeModElem{T}, generators::ModuleGens{T}) where {T<:MPolyRingElem} - if !has_global_singular_ordering(generators) - error("Ordering must be global") - end - return lift(a, generators) + if !has_global_singular_ordering(generators) + error("Ordering must be global") + end + return lift(a, generators) end @doc raw""" @@ -419,47 +419,47 @@ Note: `:via_lift` is typically faster than `:via_transform` for a single vector is faster if many vectors are lifted """ function coordinates(a::FreeModElem, M::SubModuleOfFreeModule, task::Symbol = :auto) - R = base_ring(parent(a)) - if iszero(a) - return sparse_row(R) - end - for (i, g) in enumerate(gens(M)) - if a == g - return sparse_row(R, [i], [one(R)]) - end + R = base_ring(parent(a)) + if iszero(a) + return sparse_row(R) + end + for (i, g) in enumerate(gens(M)) + if a == g + return sparse_row(R, [i], [one(R)]) end - return coordinates_atomic(a, M; task=task) + end + return coordinates_atomic(a, M; task=task) end function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task::Symbol = :auto) where {S<:Union{ZZRingElem,FieldElem}, T<:MPolyRingElem{S}} - if task == :auto - task = coefficient_ring(base_ring(parent(a))) isa Field ? :via_transform : :via_lift - end - if task == :via_transform - std, _ = lift_std(M) - return coordinates_via_transform(a, std) - elseif task == :via_lift - return coordinates(a, M.gens) - else - error("Invalid task given.") - end + if task == :auto + task = coefficient_ring(base_ring(parent(a))) isa Field ? :via_transform : :via_lift + end + if task == :via_transform + std, _ = lift_std(M) + return coordinates_via_transform(a, std) + elseif task == :via_lift + return coordinates(a, M.gens) + else + error("Invalid task given.") + end end function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task::Symbol=:auto) where {T<:Union{ZZRingElem, FieldElem}} - ctx = solve_ctx(M) - R = base_ring(ambient_free_module(M)) - d = rank(ambient_free_module(M)) - vec_a = matrix(dense_row(coordinates(a), d)) - is_solvable, sol = can_solve_with_solution(ctx, vec_a; side=:left) - if !is_solvable - error("Element is not contained in the module.") - end - return sparse_row(sol) + ctx = solve_ctx(M) + R = base_ring(ambient_free_module(M)) + d = rank(ambient_free_module(M)) + vec_a = matrix(dense_row(coordinates(a), d)) + is_solvable, sol = can_solve_with_solution(ctx, vec_a; side=:left) + if !is_solvable + error("Element is not contained in the module.") + end + return sparse_row(sol) end function coordinates_atomic(a::FreeModElem, M::SubModuleOfFreeModule; task::Symbol = :auto) - error("The function coordinates_atomic is not implemented for modules over rings of type $(typeof(base_ring(ambient_free_module(M))))") + error("The function coordinates_atomic is not implemented for modules over rings of type $(typeof(base_ring(ambient_free_module(M))))") end @doc raw""" diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index 608526e43431..06c6c13316d8 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -532,7 +532,7 @@ e[2] ``` """ function prune_with_map(M::ModuleFP) - return prune_with_map_atomic(M) + return prune_with_map_atomic(M) end # generic fallback @@ -589,61 +589,61 @@ function prune_with_map_atomic(M::ModuleFP{T}) where {T<:Union{MPolyRingElem, MP end @attr Any function presentation_ctx(M::SubquoModule{ZZRingElem}) - R = base_ring(M) - F = ambient_free_module(M) - m = ngens(F) - rels = relations(M) - n = length(rels) - A = zero_matrix(R, m, n) - for (j, rel) in enumerate(rels) - for (i, v) in coordinates(rel) - A[i, j] = v - end + R = base_ring(M) + F = ambient_free_module(M) + m = ngens(F) + rels = relations(M) + n = length(rels) + A = zero_matrix(R, m, n) + for (j, rel) in enumerate(rels) + for (i, v) in coordinates(rel) + A[i, j] = v end - D, U, V = snf_with_transform(A) - return (D=D, U=U, V=V) + end + D, U, V = snf_with_transform(A) + return (D=D, U=U, V=V) end @attr Any function presentation_ctx(M::SubquoModule{T}) where {T<:FieldElem} - R = base_ring(M) - F = ambient_free_module(M) - m = ngens(F) - rels = relations(M) - n = length(rels) - - A = zero_matrix(R, m, n) - for (j, rel) in enumerate(rels) - for (i, v) in coordinates(rel) - A[i, j] = v - end + R = base_ring(M) + F = ambient_free_module(M) + m = ngens(F) + rels = relations(M) + n = length(rels) + + A = zero_matrix(R, m, n) + for (j, rel) in enumerate(rels) + for (i, v) in coordinates(rel) + A[i, j] = v end + end - D, U, V = snf_with_transform(A) - return (D=D, U=U, V=V) + D, U, V = snf_with_transform(A) + return (D=D, U=U, V=V) end function prune_with_map_atomic(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} - ctx = presentation_ctx(M) - R = base_ring(M) - F = ambient_free_module(M) - m = ngens(F) - D = ctx.D - U = ctx.U - diag_length = min(size(D)...) - unit_diag_indices = [i for i in 1:diag_length if is_unit(D[i,i])] - row_indices = setdiff(1:size(D, 1), unit_diag_indices) - col_indices = setdiff(1:size(D, 2), unit_diag_indices) - D_prime_matrix = sub(D, row_indices, col_indices) - F_prime = FreeMod(R, length(row_indices)) - rels_prime = [F_prime(sparse_row(transpose(D_prime_matrix[:, j:j]))) for j in 1:size(D_prime_matrix, 2)] - N_prime, _ = sub(F_prime, rels_prime) - M_prime, _ = quo(F_prime, N_prime) - U_inv = inv(U) - iso_basis_ambient = [sum(U_inv[j, row_indices[i]] * gens(F)[j] for j in 1:m) for i in 1:length(row_indices)] - proj_to_M = hom(F, M, gens(M)) - iso_basis = [proj_to_M(b) for b in iso_basis_ambient] - iso_map = hom(M_prime, M, iso_basis) - return M_prime, iso_map + ctx = presentation_ctx(M) + R = base_ring(M) + F = ambient_free_module(M) + m = ngens(F) + D = ctx.D + U = ctx.U + diag_length = min(size(D)...) + unit_diag_indices = [i for i in 1:diag_length if is_unit(D[i,i])] + row_indices = setdiff(1:size(D, 1), unit_diag_indices) + col_indices = setdiff(1:size(D, 2), unit_diag_indices) + D_prime_matrix = sub(D, row_indices, col_indices) + F_prime = FreeMod(R, length(row_indices)) + rels_prime = [F_prime(sparse_row(transpose(D_prime_matrix[:, j:j]))) for j in 1:size(D_prime_matrix, 2)] + N_prime, _ = sub(F_prime, rels_prime) + M_prime, _ = quo(F_prime, N_prime) + U_inv = inv(U) + iso_basis_ambient = [sum(U_inv[j, row_indices[i]] * gens(F)[j] for j in 1:m) for i in 1:length(row_indices)] + proj_to_M = hom(F, M, gens(M)) + iso_basis = [proj_to_M(b) for b in iso_basis_ambient] + iso_map = hom(M_prime, M, iso_basis) + return M_prime, iso_map end function _presentation_minimal(SQ::ModuleFP{T}; @@ -762,23 +762,23 @@ true ``` """ function is_finite(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} - M_prime, _ = prune_with_map_atomic(M) - R = base_ring(M_prime) - pres = presentation(M_prime) - rel_map = map(pres, 1) - rel_matrix = matrix(rel_map) - nc = size(rel_matrix, 2) - nr = size(rel_matrix, 1) - has_free_generator = any(j -> all(i -> iszero(rel_matrix[i, j]), 1:nr), 1:nc) - if isa(R, ZZRing) - return !has_free_generator && nc > 0 - elseif isa(R, Field) - if is_finite(R) - return true - else - return nc == 0 - end + M_prime, _ = prune_with_map_atomic(M) + R = base_ring(M_prime) + pres = presentation(M_prime) + rel_map = map(pres, 1) + rel_matrix = matrix(rel_map) + nc = size(rel_matrix, 2) + nr = size(rel_matrix, 1) + has_free_generator = any(j -> all(i -> iszero(rel_matrix[i, j]), 1:nr), 1:nc) + if isa(R, ZZRing) + return !has_free_generator && nc > 0 + elseif isa(R, Field) + if is_finite(R) + return true else - error("The base ring $(typeof(R)) is not supported.") + return nc == 0 end + else + error("The base ring $(typeof(R)) is not supported.") + end end diff --git a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl index 4a17de7267f1..9f71864c2f74 100644 --- a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl +++ b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl @@ -475,35 +475,35 @@ end Check if `a` is an element of `M`. """ function in(a::FreeModElem, M::SubModuleOfFreeModule) - iszero(a) && return true - any(==(a), gens(M)) && return true - return in_atomic(a, M) + iszero(a) && return true + any(==(a), gens(M)) && return true + return in_atomic(a, M) end function in_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule) where {S<:Union{ZZRingElem,FieldElem}, T<:MPolyRingElem{S}} - F = ambient_free_module(M) - return iszero(reduce(a, standard_basis(M, ordering=default_ordering(F)))) + F = ambient_free_module(M) + return iszero(reduce(a, standard_basis(M, ordering=default_ordering(F)))) end @attr Any function solve_ctx(M::SubModuleOfFreeModule) - F = ambient_free_module(M) - d, n = rank(F), ngens(M) - R = base_ring(F) - mat = zero_matrix(R, n, d) - for (j, g) in enumerate(gens(M)), (i, val) in coordinates(g) - mat[j, i] = val - end - return solve_init(mat) + F = ambient_free_module(M) + d, n = rank(F), ngens(M) + R = base_ring(F) + mat = zero_matrix(R, n, d) + for (j, g) in enumerate(gens(M)), (i, val) in coordinates(g) + mat[j, i] = val + end + return solve_init(mat) end function in_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule) where {T<:Union{ZZRingElem, FieldElem}} - ctx = solve_ctx(M) - vec_a = dense_row(coordinates(a), rank(ambient_free_module(M))) - return can_solve(ctx, vec_a; side=:left) + ctx = solve_ctx(M) + vec_a = dense_row(coordinates(a), rank(ambient_free_module(M))) + return can_solve(ctx, vec_a; side=:left) end function in_atomic(a::FreeModElem, M::SubModuleOfFreeModule) - error("Membership test 'in' is not implemented for modules over rings of type $(typeof(base_ring(ambient_free_module(M))))") + error("Membership test 'in' is not implemented for modules over rings of type $(typeof(base_ring(ambient_free_module(M))))") end diff --git a/test/Modules/UngradedModules.jl b/test/Modules/UngradedModules.jl index f783405cd71f..f42d338c13e0 100644 --- a/test/Modules/UngradedModules.jl +++ b/test/Modules/UngradedModules.jl @@ -1405,330 +1405,330 @@ end @testset "Modules over ZZ and QQ" begin - @testset "Module Constructors over ZZ" begin - F0 = free_module(FreeMod, ZZ,3) - @test rank(F0) == 3 - @test base_ring(F0) == ZZ - - F = FreeMod(ZZ, 3) - M, inc = sub(F, [2*F[1], 3*F[2]]) - N, proj = quo(F, [F[1] + 2*F[3]]) - - @test rank(F) == 3 - @test is_welldefined(inc) - @test is_welldefined(proj) - end + @testset "Module Constructors over ZZ" begin + F0 = free_module(FreeMod, ZZ,3) + @test rank(F0) == 3 + @test base_ring(F0) == ZZ + + F = FreeMod(ZZ, 3) + M, inc = sub(F, [2*F[1], 3*F[2]]) + N, proj = quo(F, [F[1] + 2*F[3]]) + + @test rank(F) == 3 + @test is_welldefined(inc) + @test is_welldefined(proj) + end - @testset "Submodule Membership over ZZ" begin - R = ZZ - F = FreeMod(R, 3) - gens_submodule = [2*F[1], 3*F[2]] - S, _ = sub(F, gens_submodule) - x = 4*F[1]+3*F[2] - @test in(x, S) - coord = coordinates(x, S) - @test coord == sparse_row(R, [1, 2], [2, 1]) - end + @testset "Submodule Membership over ZZ" begin + R = ZZ + F = FreeMod(R, 3) + gens_submodule = [2*F[1], 3*F[2]] + S, _ = sub(F, gens_submodule) + x = 4*F[1]+3*F[2] + @test in(x, S) + coord = coordinates(x, S) + @test coord == sparse_row(R, [1, 2], [2, 1]) + end - @testset "Module Homomorphisms over ZZ" begin - F1 = FreeMod(ZZ, 2) - F2 = FreeMod(ZZ, 2) - phi = hom(F1, F2, [F2[1] + 2*F2[2], 3*F2[1]+ 6*F2[2]]) + @testset "Module Homomorphisms over ZZ" begin + F1 = FreeMod(ZZ, 2) + F2 = FreeMod(ZZ, 2) + phi = hom(F1, F2, [F2[1] + 2*F2[2], 3*F2[1]+ 6*F2[2]]) - @test is_welldefined(phi) + @test is_welldefined(phi) - K, emb = kernel(phi) - @test ngens(K) == 1 - @test is_welldefined(emb) + K, emb = kernel(phi) + @test ngens(K) == 1 + @test is_welldefined(emb) - I, im = image(phi) - @test ngens(I) == 2 - @test is_welldefined(im) - end + I, im = image(phi) + @test ngens(I) == 2 + @test is_welldefined(im) + end - @testset "Presentations over ZZ" begin - F = FreeMod(ZZ, 2) - M, inc = sub(F, [2*F[1] + 3*F[2]]) - pres_M, iso = present_as_cokernel(M, :both) - - @test is_welldefined(iso) - @test is_bijective(iso) - @test pres_M isa SubquoModule - @test ngens(pres_M) == 1 - - F = FreeMod(ZZ, 3) - M, inc = sub(F, [5*F[1],3*F[2],F[3]]) - N, inc = sub(F, [10*F[1], 3*F[2]]) - W, _ = quo(M, N) - pres_W, iso = present_as_cokernel(W, :both) - @test is_welldefined(iso) - @test is_bijective(iso) - @test pres_W isa SubquoModule - @test ngens(pres_W) == 3 - presentation(W) - ppW, iso2 = prune_with_map(pres_W) - @test is_welldefined(iso2) - @test is_bijective(iso2) - @test ppW isa SubquoModule - @test ngens(ppW) == 2 - end + @testset "Presentations over ZZ" begin + F = FreeMod(ZZ, 2) + M, inc = sub(F, [2*F[1] + 3*F[2]]) + pres_M, iso = present_as_cokernel(M, :both) + + @test is_welldefined(iso) + @test is_bijective(iso) + @test pres_M isa SubquoModule + @test ngens(pres_M) == 1 + + F = FreeMod(ZZ, 3) + M, inc = sub(F, [5*F[1],3*F[2],F[3]]) + N, inc = sub(F, [10*F[1], 3*F[2]]) + W, _ = quo(M, N) + pres_W, iso = present_as_cokernel(W, :both) + @test is_welldefined(iso) + @test is_bijective(iso) + @test pres_W isa SubquoModule + @test ngens(pres_W) == 3 + presentation(W) + ppW, iso2 = prune_with_map(pres_W) + @test is_welldefined(iso2) + @test is_bijective(iso2) + @test ppW isa SubquoModule + @test ngens(ppW) == 2 + end - @testset "Ext and Tor over ZZ" begin - F = FreeMod(ZZ, 1) - M, _ = sub(F, [2*F[1]]) - N, _ = quo(F, [3*F[1]]) + @testset "Ext and Tor over ZZ" begin + F = FreeMod(ZZ, 1) + M, _ = sub(F, [2*F[1]]) + N, _ = quo(F, [3*F[1]]) - T0 = tor(M, N, 0) - T1 = tor(M, N, 1) + T0 = tor(M, N, 0) + T1 = tor(M, N, 1) - @test T0 isa SubquoModule - @test T1 isa SubquoModule + @test T0 isa SubquoModule + @test T1 isa SubquoModule - E0 = ext(M, N, 0) - E1 = ext(M, N, 1) + E0 = ext(M, N, 0) + E1 = ext(M, N, 1) - @test E0 isa SubquoModule - @test E1 isa SubquoModule - end + @test E0 isa SubquoModule + @test E1 isa SubquoModule + end - @testset "Free resolutions over ZZ" begin - F = FreeMod(ZZ, 1) - M, _ = quo(F, [4*F[1]]) - - fr = free_resolution(M, length=3) + @testset "Free resolutions over ZZ" begin + F = FreeMod(ZZ, 1) + M, _ = quo(F, [4*F[1]]) - @test length(fr.C.maps) == 3 - @test iszero(homology(fr.C)[1]) - end + fr = free_resolution(M, length=3) + + @test length(fr.C.maps) == 3 + @test iszero(homology(fr.C)[1]) + end - @testset "Tensoring morphisms over ZZ" begin - R = ZZ - F2 = FreeMod(R, 2) - F4 = FreeMod(R, 4) - A1 = matrix(ZZ, [1 2; 3 4; 5 6]) - B1 = matrix(ZZ, [7 8]) - A2 = matrix(ZZ, [2 0 1; 0 1 3; 1 1 1]) - B2 = matrix(ZZ, [0 1 1]) - M1 = SubquoModule(F2, A1, B1) - F3 = FreeMod(R, 3) - M2 = SubquoModule(F3, A2, B2) - M, pure_M = tensor_product(M1, M2, task=:map) - A3 = matrix(ZZ, [1 2; 3 4]) - M3 = SubquoModule(Oscar.SubModuleOfFreeModule(F2, A3)) - N, pure_N = tensor_product(M3, F4, task=:map) - M3_to_M1 = SubQuoHom(M3, M1, matrix(ZZ, [1 0 0; 0 1 0])) - @test is_welldefined(M3_to_M1) - F4_to_M2 = FreeModuleHom(F4, M2, matrix(ZZ, [1 0 0; 0 1 0; 0 0 1; 0 0 0])) - phi2 = hom_tensor(N, M, [M3_to_M1, F4_to_M2]) - u1 = M3[1] - u2 = F4[1] - @test phi2(pure_N((u1, u2))) == pure_M((M3_to_M1(u1), F4_to_M2(u2))) - F3 = FreeMod(R, 3) - A1 = matrix(ZZ, [1 2; 3 4; 5 6]) - B1 = matrix(ZZ, [7 8]) - A2 = matrix(ZZ, [2 0 1; 0 1 3; 1 1 1]) - B2 = matrix(ZZ, [0 1 1]) - M1 = SubquoModule(F2, A1, B1) - M2 = SubquoModule(F3, A2, B2) - M, pure_M = tensor_product(M1, M2, task=:map) - phi_M1 = hom(M1, M1, identity_matrix(ZZ, 3)) - phi_M2 = hom(M2, M2, identity_matrix(ZZ, 3)) - phi = hom_tensor(M, M, [phi_M1, phi_M2]) - @test is_welldefined(phi) - @test is_bijective(phi) - for u1 in gens(M1), u2 in gens(M2) - input_elem = pure_M((u1, u2)) - output_elem = pure_M((phi_M1(u1), phi_M2(u2))) - @test phi(input_elem) == output_elem - end + @testset "Tensoring morphisms over ZZ" begin + R = ZZ + F2 = FreeMod(R, 2) + F4 = FreeMod(R, 4) + A1 = matrix(ZZ, [1 2; 3 4; 5 6]) + B1 = matrix(ZZ, [7 8]) + A2 = matrix(ZZ, [2 0 1; 0 1 3; 1 1 1]) + B2 = matrix(ZZ, [0 1 1]) + M1 = SubquoModule(F2, A1, B1) + F3 = FreeMod(R, 3) + M2 = SubquoModule(F3, A2, B2) + M, pure_M = tensor_product(M1, M2, task=:map) + A3 = matrix(ZZ, [1 2; 3 4]) + M3 = SubquoModule(Oscar.SubModuleOfFreeModule(F2, A3)) + N, pure_N = tensor_product(M3, F4, task=:map) + M3_to_M1 = SubQuoHom(M3, M1, matrix(ZZ, [1 0 0; 0 1 0])) + @test is_welldefined(M3_to_M1) + F4_to_M2 = FreeModuleHom(F4, M2, matrix(ZZ, [1 0 0; 0 1 0; 0 0 1; 0 0 0])) + phi2 = hom_tensor(N, M, [M3_to_M1, F4_to_M2]) + u1 = M3[1] + u2 = F4[1] + @test phi2(pure_N((u1, u2))) == pure_M((M3_to_M1(u1), F4_to_M2(u2))) + F3 = FreeMod(R, 3) + A1 = matrix(ZZ, [1 2; 3 4; 5 6]) + B1 = matrix(ZZ, [7 8]) + A2 = matrix(ZZ, [2 0 1; 0 1 3; 1 1 1]) + B2 = matrix(ZZ, [0 1 1]) + M1 = SubquoModule(F2, A1, B1) + M2 = SubquoModule(F3, A2, B2) + M, pure_M = tensor_product(M1, M2, task=:map) + phi_M1 = hom(M1, M1, identity_matrix(ZZ, 3)) + phi_M2 = hom(M2, M2, identity_matrix(ZZ, 3)) + phi = hom_tensor(M, M, [phi_M1, phi_M2]) + @test is_welldefined(phi) + @test is_bijective(phi) + for u1 in gens(M1), u2 in gens(M2) + input_elem = pure_M((u1, u2)) + output_elem = pure_M((phi_M1(u1), phi_M2(u2))) + @test phi(input_elem) == output_elem end + end - @testset "Direct product over ZZ" begin - R = ZZ - F2 = FreeMod(R, 2) - F3 = FreeMod(R, 3) - A1 = matrix(ZZ, [1 0; 0 1; 2 2]) - B1 = matrix(ZZ, [0 1]) - M1 = SubquoModule(F2, A1, B1) - A2 = matrix(ZZ, [1 2 3; 0 0 1]) - B2 = matrix(ZZ, [1 0 1]) - M2 = SubquoModule(F3, A2, B2) - sum_M, emb = direct_sum(M1, M2) - @test domain(emb[1]) === M1 - @test domain(emb[2]) === M2 - @test codomain(emb[1]) === sum_M - @test codomain(emb[2]) === sum_M - sum_M2, pr = direct_sum(M1, M2, task=:prod) - @test codomain(pr[1]) === M1 - @test codomain(pr[2]) === M2 - @test domain(pr[1]) === sum_M2 - @test domain(pr[2]) === sum_M2 - prod_M, emb, pr = direct_sum(M1, M2, task=:both) - @test length(pr) == length(emb) == 2 - @test ngens(prod_M) == ngens(M1) + ngens(M2) - for g in gens(prod_M) - @test g == sum([emb[i](pr[i](g)) for i in 1:length(pr)]) - end - prod_N = direct_product(M1, M2, task=:none) - @test ngens(prod_N) == ngens(M1) + ngens(M2) - for g in gens(prod_N) - @test g == sum([canonical_injection(prod_N, i)(canonical_projection(prod_N, i)(g)) for i in 1:2]) - end + @testset "Direct product over ZZ" begin + R = ZZ + F2 = FreeMod(R, 2) + F3 = FreeMod(R, 3) + A1 = matrix(ZZ, [1 0; 0 1; 2 2]) + B1 = matrix(ZZ, [0 1]) + M1 = SubquoModule(F2, A1, B1) + A2 = matrix(ZZ, [1 2 3; 0 0 1]) + B2 = matrix(ZZ, [1 0 1]) + M2 = SubquoModule(F3, A2, B2) + sum_M, emb = direct_sum(M1, M2) + @test domain(emb[1]) === M1 + @test domain(emb[2]) === M2 + @test codomain(emb[1]) === sum_M + @test codomain(emb[2]) === sum_M + sum_M2, pr = direct_sum(M1, M2, task=:prod) + @test codomain(pr[1]) === M1 + @test codomain(pr[2]) === M2 + @test domain(pr[1]) === sum_M2 + @test domain(pr[2]) === sum_M2 + prod_M, emb, pr = direct_sum(M1, M2, task=:both) + @test length(pr) == length(emb) == 2 + @test ngens(prod_M) == ngens(M1) + ngens(M2) + for g in gens(prod_M) + @test g == sum([emb[i](pr[i](g)) for i in 1:length(pr)]) end + prod_N = direct_product(M1, M2, task=:none) + @test ngens(prod_N) == ngens(M1) + ngens(M2) + for g in gens(prod_N) + @test g == sum([canonical_injection(prod_N, i)(canonical_projection(prod_N, i)(g)) for i in 1:2]) + end + end - @testset "Module Constructors over QQ" begin - F0 = free_module(FreeMod, QQ,3) - @test rank(F0) == 3 - @test base_ring(F0) == QQ + @testset "Module Constructors over QQ" begin + F0 = free_module(FreeMod, QQ,3) + @test rank(F0) == 3 + @test base_ring(F0) == QQ - F = FreeMod(QQ, 3) - M, inc = sub(F, [QQ(1//2)*F[1], QQ(2//3)*F[2]]) - N, proj = quo(F, [F[1] + QQ(1//3)*F[3]]) + F = FreeMod(QQ, 3) + M, inc = sub(F, [QQ(1//2)*F[1], QQ(2//3)*F[2]]) + N, proj = quo(F, [F[1] + QQ(1//3)*F[3]]) - @test ngens(M) == 2 - @test ngens(N) == 3 - F = FreeMod(QQ, 3) - M, inc = sub(F, [QQ(1//2)*F[1], QQ(2//3)*F[2]]) - N, proj = quo(F, [F[1] + QQ(1//3)*F[3]]) + @test ngens(M) == 2 + @test ngens(N) == 3 + F = FreeMod(QQ, 3) + M, inc = sub(F, [QQ(1//2)*F[1], QQ(2//3)*F[2]]) + N, proj = quo(F, [F[1] + QQ(1//3)*F[3]]) - @test is_welldefined(inc) - @test is_welldefined(proj) - @test is_welldefined(inc) - @test is_welldefined(proj) - end + @test is_welldefined(inc) + @test is_welldefined(proj) + @test is_welldefined(inc) + @test is_welldefined(proj) + end - @testset "Module Homomorphisms over QQ" begin - F1 = FreeMod(QQ, 2) - F2 = FreeMod(QQ, 2) - phi = hom(F1, F2, [F2[1] + QQ(1//2)*F2[2], QQ(3//4)*F2[1]]) + @testset "Module Homomorphisms over QQ" begin + F1 = FreeMod(QQ, 2) + F2 = FreeMod(QQ, 2) + phi = hom(F1, F2, [F2[1] + QQ(1//2)*F2[2], QQ(3//4)*F2[1]]) - @test is_welldefined(phi) + @test is_welldefined(phi) - K, emb = kernel(phi) - @test is_welldefined(emb) + K, emb = kernel(phi) + @test is_welldefined(emb) - I, im = image(phi) - @test is_welldefined(im) - end + I, im = image(phi) + @test is_welldefined(im) + end - @testset "Presentations over QQ" begin - F = FreeMod(QQ, 2) - M, inc = sub(F, [QQ(1//2)*F[1] + QQ(1//3)*F[2]]) - pres_M, iso = present_as_cokernel(M, :both) - - @test is_welldefined(iso) - @test is_bijective(iso) - @test pres_M isa SubquoModule - @test ngens(pres_M) == 1 - - F = FreeMod(QQ, 3) - M, inc = sub(F, [5*F[1], 3*F[2], F[3]]) - N, inc = sub(F, [10*F[1], 3*F[2]]) - W, _ = quo(M, N) - pres_W, iso = present_as_cokernel(W, :both) - MP, iso2 = prune_with_map(pres_W) - @test is_welldefined(iso2) - @test is_bijective(iso2) - @test MP isa SubquoModule - @test ngens(MP) == 1 - end + @testset "Presentations over QQ" begin + F = FreeMod(QQ, 2) + M, inc = sub(F, [QQ(1//2)*F[1] + QQ(1//3)*F[2]]) + pres_M, iso = present_as_cokernel(M, :both) + + @test is_welldefined(iso) + @test is_bijective(iso) + @test pres_M isa SubquoModule + @test ngens(pres_M) == 1 + + F = FreeMod(QQ, 3) + M, inc = sub(F, [5*F[1], 3*F[2], F[3]]) + N, inc = sub(F, [10*F[1], 3*F[2]]) + W, _ = quo(M, N) + pres_W, iso = present_as_cokernel(W, :both) + MP, iso2 = prune_with_map(pres_W) + @test is_welldefined(iso2) + @test is_bijective(iso2) + @test MP isa SubquoModule + @test ngens(MP) == 1 + end - @testset "Ext and Tor over QQ" begin - F = FreeMod(QQ, 1) - M, _ = sub(F, [QQ(1//2)*F[1]]) - N, _ = quo(F, [QQ(1//3)*F[1]]) + @testset "Ext and Tor over QQ" begin + F = FreeMod(QQ, 1) + M, _ = sub(F, [QQ(1//2)*F[1]]) + N, _ = quo(F, [QQ(1//3)*F[1]]) - T0 = tor(M, N, 0) - T1 = tor(M, N, 1) + T0 = tor(M, N, 0) + T1 = tor(M, N, 1) - @test T0 isa SubquoModule - @test T1 isa SubquoModule + @test T0 isa SubquoModule + @test T1 isa SubquoModule - E0 = ext(M, N, 0) - E1 = ext(M, N, 1) + E0 = ext(M, N, 0) + E1 = ext(M, N, 1) - @test E0 isa SubquoModule - @test E1 isa SubquoModule - end + @test E0 isa SubquoModule + @test E1 isa SubquoModule + end - @testset "Free resolutions over QQ" begin - F = FreeMod(QQ, 1) - M, _ = quo(F, [QQ(1//4)*F[1]]) + @testset "Free resolutions over QQ" begin + F = FreeMod(QQ, 1) + M, _ = quo(F, [QQ(1//4)*F[1]]) - fr = free_resolution(M, length=3) + fr = free_resolution(M, length=3) - @test length(fr.C.maps) == 3 - @test iszero(homology(fr.C)[1]) - MP, _ = prune_with_map(M) - @test is_zero(MP) - end + @test length(fr.C.maps) == 3 + @test iszero(homology(fr.C)[1]) + MP, _ = prune_with_map(M) + @test is_zero(MP) + end - @testset "Tensoring morphisms over QQ" begin - R = QQ - F2 = FreeMod(R, 2) - F3 = FreeMod(R, 3) - A1 = matrix(R, [1 2; 3 4; 5 6]) - B1 = matrix(R, [7 8]) - A2 = matrix(R, [2 0 1; 0 1 3; 1 1 1]) - B2 = matrix(R, [0 1 1]) - M1 = SubquoModule(F2, A1, B1) - M2 = SubquoModule(F3, A2, B2) - M, pure_M = tensor_product(M1, M2, task=:map) - phi_M1 = SubQuoHom(M1, M1, matrix(R, [1 1 0; 0 2 1; 1 0 1])) - phi_M2 = SubQuoHom(M2, M2, matrix(R, [0 1 0; 1 0 1; 1 1 0])) - phi = hom_tensor(M, M, [phi_M1, phi_M2]) - v = M[1] + 2*M[2] - @test phi(v) == pure_M((phi_M1(M1[1]), phi_M2(M2[1]))) + 2*pure_M((phi_M1(M1[1]), phi_M2(M2[2]))) - F4 = FreeMod(R, 4) - A3 = matrix(R, [1 2; 3 4]) - M3 = SubquoModule(Oscar.SubModuleOfFreeModule(F2, A3)) - N, pure_N = tensor_product(M3, F4, task=:map) - M3_to_M1 = SubQuoHom(M3, M1, matrix(R, [1 2 0; 0 1 3])) - @test is_welldefined(M3_to_M1) - F4_to_M2 = FreeModuleHom(F4, M2, matrix(R, [1 0 2; 0 1 0; 1 1 1; 2 0 0])) - phi2 = hom_tensor(N, M, [M3_to_M1, F4_to_M2]) - u1 = M3[1] - u2 = F4[1] - @test phi2(pure_N((u1, u2))) == pure_M((M3_to_M1(u1), F4_to_M2(u2))) - end + @testset "Tensoring morphisms over QQ" begin + R = QQ + F2 = FreeMod(R, 2) + F3 = FreeMod(R, 3) + A1 = matrix(R, [1 2; 3 4; 5 6]) + B1 = matrix(R, [7 8]) + A2 = matrix(R, [2 0 1; 0 1 3; 1 1 1]) + B2 = matrix(R, [0 1 1]) + M1 = SubquoModule(F2, A1, B1) + M2 = SubquoModule(F3, A2, B2) + M, pure_M = tensor_product(M1, M2, task=:map) + phi_M1 = SubQuoHom(M1, M1, matrix(R, [1 1 0; 0 2 1; 1 0 1])) + phi_M2 = SubQuoHom(M2, M2, matrix(R, [0 1 0; 1 0 1; 1 1 0])) + phi = hom_tensor(M, M, [phi_M1, phi_M2]) + v = M[1] + 2*M[2] + @test phi(v) == pure_M((phi_M1(M1[1]), phi_M2(M2[1]))) + 2*pure_M((phi_M1(M1[1]), phi_M2(M2[2]))) + F4 = FreeMod(R, 4) + A3 = matrix(R, [1 2; 3 4]) + M3 = SubquoModule(Oscar.SubModuleOfFreeModule(F2, A3)) + N, pure_N = tensor_product(M3, F4, task=:map) + M3_to_M1 = SubQuoHom(M3, M1, matrix(R, [1 2 0; 0 1 3])) + @test is_welldefined(M3_to_M1) + F4_to_M2 = FreeModuleHom(F4, M2, matrix(R, [1 0 2; 0 1 0; 1 1 1; 2 0 0])) + phi2 = hom_tensor(N, M, [M3_to_M1, F4_to_M2]) + u1 = M3[1] + u2 = F4[1] + @test phi2(pure_N((u1, u2))) == pure_M((M3_to_M1(u1), F4_to_M2(u2))) + end - @testset "Direct product over QQ" begin - R = QQ - F2 = FreeMod(R, 2) - F3 = FreeMod(R, 3) - A1 = matrix(R, [1 0; 0 1; 2 2]) - B1 = matrix(R, [0 1]) - M1 = SubquoModule(F2, A1, B1) - A2 = matrix(R, [1 2 3; 0 0 1]) - B2 = matrix(R, [1 0 1]) - M2 = SubquoModule(F3, A2, B2) - sum_M, emb = direct_sum(M1, M2) - @test domain(emb[1]) === M1 - @test domain(emb[2]) === M2 - @test codomain(emb[1]) === sum_M - @test codomain(emb[2]) === sum_M - sum_M2, pr = direct_sum(M1, M2, task=:prod) - @test codomain(pr[1]) === M1 - @test codomain(pr[2]) === M2 - @test domain(pr[1]) === sum_M2 - @test domain(pr[2]) === sum_M2 - prod_M, emb, pr = direct_sum(M1, M2, task=:both) - @test length(pr) == length(emb) == 2 - @test ngens(prod_M) == ngens(M1) + ngens(M2) - for g in gens(prod_M) - @test g == sum([emb[i](pr[i](g)) for i in 1:length(pr)]) - end - prod_N = direct_product(M1, M2, task=:none) - @test ngens(prod_N) == ngens(M1) + ngens(M2) - for g in gens(prod_N) - @test g == sum([canonical_injection(prod_N, i)(canonical_projection(prod_N, i)(g)) for i in 1:2]) - end + @testset "Direct product over QQ" begin + R = QQ + F2 = FreeMod(R, 2) + F3 = FreeMod(R, 3) + A1 = matrix(R, [1 0; 0 1; 2 2]) + B1 = matrix(R, [0 1]) + M1 = SubquoModule(F2, A1, B1) + A2 = matrix(R, [1 2 3; 0 0 1]) + B2 = matrix(R, [1 0 1]) + M2 = SubquoModule(F3, A2, B2) + sum_M, emb = direct_sum(M1, M2) + @test domain(emb[1]) === M1 + @test domain(emb[2]) === M2 + @test codomain(emb[1]) === sum_M + @test codomain(emb[2]) === sum_M + sum_M2, pr = direct_sum(M1, M2, task=:prod) + @test codomain(pr[1]) === M1 + @test codomain(pr[2]) === M2 + @test domain(pr[1]) === sum_M2 + @test domain(pr[2]) === sum_M2 + prod_M, emb, pr = direct_sum(M1, M2, task=:both) + @test length(pr) == length(emb) == 2 + @test ngens(prod_M) == ngens(M1) + ngens(M2) + for g in gens(prod_M) + @test g == sum([emb[i](pr[i](g)) for i in 1:length(pr)]) end - - @testset "gradings over non-graded rings" begin - @test_throws AssertionError graded_free_module(ZZ, [1]) + prod_N = direct_product(M1, M2, task=:none) + @test ngens(prod_N) == ngens(M1) + ngens(M2) + for g in gens(prod_N) + @test g == sum([canonical_injection(prod_N, i)(canonical_projection(prod_N, i)(g)) for i in 1:2]) end + end + + @testset "gradings over non-graded rings" begin + @test_throws AssertionError graded_free_module(ZZ, [1]) + end end From b7bdb0f6facec20c29aa6445e8142569bafae33d Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Fri, 6 Jun 2025 07:08:11 +0200 Subject: [PATCH 22/24] polishing and size --- .../CommutativeAlgebra/homological_algebra.md | 6 +- .../UngradedModules/FreeResolutions.jl | 36 ++--------- src/Modules/UngradedModules/Presentation.jl | 61 ++++++++++++++++++- test/Modules/UngradedModules.jl | 24 ++++++++ 4 files changed, 93 insertions(+), 34 deletions(-) diff --git a/docs/src/CommutativeAlgebra/homological_algebra.md b/docs/src/CommutativeAlgebra/homological_algebra.md index f7d2ae22871f..e1fb72b05740 100644 --- a/docs/src/CommutativeAlgebra/homological_algebra.md +++ b/docs/src/CommutativeAlgebra/homological_algebra.md @@ -17,12 +17,16 @@ supporting computations in homological algebra. ```@docs prune_with_map(M::ModuleFP) ``` -## Finiteness as a set +## Finiteness and cardinality as a set ```@docs is_finite(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} ``` +```@docs +size(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} +``` + ## Presentations ```@docs diff --git a/src/Modules/UngradedModules/FreeResolutions.jl b/src/Modules/UngradedModules/FreeResolutions.jl index 0ac34b516133..da4ab9945acb 100644 --- a/src/Modules/UngradedModules/FreeResolutions.jl +++ b/src/Modules/UngradedModules/FreeResolutions.jl @@ -543,39 +543,11 @@ function free_resolution(M::SubquoModule{T}; return FreeResolution(cc) end -# kept for the comments -# function free_resolution(M::SubquoModule{T}) where {T<:RingElem} -# # This generic code computes a free resolution in a lazy way. -# # We start out with a presentation of M and implement -# # an iterative fill function to compute every higher term -# # on request. -# R = base_ring(M) -# p = presentation(M) -# p.fill = function(C::Hecke.ComplexOfMorphisms, k::Int) -# # TODO: Use official getter and setter methods instead -# # of messing manually with the internals of the complex. -# for i in first(chain_range(C)):k-1 -# N = domain(map(C, i)) - -# if iszero(N) # Fill up with zero maps -# C.complete = true -# phi = hom(N, N, elem_type(N)[]; check=false) -# pushfirst!(C.maps, phi) -# continue -# end - -# K, inc = kernel(map(C, i)) -# nz = findall(!is_zero, gens(K)) -# F = FreeMod(R, length(nz)) -# phi = hom(F, C[i], iszero(length(nz)) ? elem_type(C[i])[] : inc.(gens(K)[nz]); check=false) -# pushfirst!(C.maps, phi) -# end -# return first(C.maps) -# end -# return p -# end - function free_resolution(M::SubquoModule{T}; length::Int=0) where {T<:RingElem} + # This generic code computes a free resolution in a lazy way. + # We start out with a presentation of M and implement + # an iterative fill function to compute every higher term + # on request. R = base_ring(M) p = presentation(M) p.fill = function(C::Hecke.ComplexOfMorphisms, k::Int) diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index 06c6c13316d8..0522ea09169f 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -706,7 +706,7 @@ end @doc raw""" is_finite(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} -Determine whether the finitely presented module `M` over `ZZ` or a field is finite as a set. +Determine whether the finitely presented module `M` over `ZZRing` or a `Field` is finite as a set. This is done by computing a minimal presentation. @@ -782,3 +782,62 @@ function is_finite(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} error("The base ring $(typeof(R)) is not supported.") end end + +@doc raw""" + size(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} + +Compute the cardinality of the finitely presented module `M` over `ZZRing` or a `Field` as a set. +Returns `PosInf` if the module is infinite. + +This is done by computing a minimal presentation. + +# Examples +```jldoctest +julia> R = ZZ; + +julia> F = free_module(FreeMod, R, 2); + +julia> M = cokernel(hom(F, F, matrix(ZZ, [2 0; 0 3]))); + +julia> size(M) +6 + +julia> N = cokernel(hom(F, F, matrix(ZZ, [1 0; 0 0]))); + +julia> size(N) +infinity + +julia> K, a = finite_field(7, "a"); + +julia> G = free_module(FreeMod, K, 3); + +julia> H = free_module(FreeMod, K, 1); + +julia> L = cokernel(hom(H, G, matrix(K, [1 0 0]))); + +julia> size(L) +49 +``` +""" +function size(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} + M_prime, _ = prune_with_map_atomic(M) + R = base_ring(M_prime) + pres = presentation(M_prime) + rel_matrix = matrix(map(pres, 1)) + nc, nr = size(rel_matrix, 2), size(rel_matrix, 1) + has_free_generator = any(j -> all(i -> iszero(rel_matrix[i, j]), 1:nr), 1:nc) + if isa(R, ZZRing) + if has_free_generator + return PosInf() + end + return prod(abs(rel_matrix[i,i]) for i in 1:min(nr,nc)) + elseif isa(R, Field) + if is_finite(R) + q = order(R) + dim = ngens(M_prime) + return q^dim + else + return nc == 0 ? 1 : PosInf() + end + end +end diff --git a/test/Modules/UngradedModules.jl b/test/Modules/UngradedModules.jl index f42d338c13e0..b9513e3def1e 100644 --- a/test/Modules/UngradedModules.jl +++ b/test/Modules/UngradedModules.jl @@ -1731,4 +1731,28 @@ end @test_throws AssertionError graded_free_module(ZZ, [1]) end + @testset "size of modules" begin + R = ZZ + F = free_module(FreeMod, R, 2) + M = cokernel(hom(F, F, matrix(ZZ, [2 0; 0 3]))) + @test size(M) == 6 + + N = cokernel(hom(F, F, matrix(ZZ, [7 0; 0 0]))) + @test size(N) == PosInf() + + K, a = finite_field(7, "a") + G = free_module(FreeMod, K, 3) + H = free_module(FreeMod, K, 1) + L = cokernel(hom(H, G, matrix(K, [1 0 0]))) + @test size(L) == 49 + + K, a = finite_field(7, "a") + G = free_module(FreeMod, K, 3) + L = cokernel(hom(G, G, matrix(K, [1 0 0; 0 1 0; 0 0 1]))) + @test size(L) == 1 + + G = free_module(FreeMod, QQ, 3) + L = cokernel(hom(G, G, matrix(QQ, [1 0 0; 0 1 0; 0 0 1]))) + @test size(L) == 1 + end end From ceb12a325cdc78ae4b0c57ce05c4c8dec0cb338b Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Wed, 18 Jun 2025 11:00:25 +0200 Subject: [PATCH 23/24] Comments by Max --- src/Modules/UngradedModules/ModuleGens.jl | 2 +- src/Modules/UngradedModules/Presentation.jl | 2 ++ src/Modules/UngradedModules/SubModuleOfFreeModule.jl | 2 +- test/Modules/UngradedModules.jl | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Modules/UngradedModules/ModuleGens.jl b/src/Modules/UngradedModules/ModuleGens.jl index dcb002c79119..1300cd97ce25 100644 --- a/src/Modules/UngradedModules/ModuleGens.jl +++ b/src/Modules/UngradedModules/ModuleGens.jl @@ -438,7 +438,7 @@ function coordinates_atomic(a::FreeModElem{T}, M::SubModuleOfFreeModule; task::S end if task == :via_transform std, _ = lift_std(M) - return coordinates_via_transform(a, std) + return coordinates_via_transform(a, std) elseif task == :via_lift return coordinates(a, M.gens) else diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index 0522ea09169f..79cc6e225f9c 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -839,5 +839,7 @@ function size(M::SubquoModule{T}) where {T<:Union{ZZRingElem, FieldElem}} else return nc == 0 ? 1 : PosInf() end + else + error("Unhandled base ring: $(typeof(R))") end end diff --git a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl index 9f71864c2f74..082b634e40b6 100644 --- a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl +++ b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl @@ -476,7 +476,7 @@ Check if `a` is an element of `M`. """ function in(a::FreeModElem, M::SubModuleOfFreeModule) iszero(a) && return true - any(==(a), gens(M)) && return true + a in gens(M) && return true return in_atomic(a, M) end diff --git a/test/Modules/UngradedModules.jl b/test/Modules/UngradedModules.jl index b9513e3def1e..2bb5bd878246 100644 --- a/test/Modules/UngradedModules.jl +++ b/test/Modules/UngradedModules.jl @@ -1383,7 +1383,7 @@ end conj_S1_alt = hom(S1, S1, [S1[1]], f->evaluate(map_coefficients(conj, f), [u, v])) @test_throws ErrorException conj_S1 == conj_S1_alt - a = compose(compose(id_R1, f), compose(conj_S1, id_S1)) + a = compose(compose(id_R1, f), compose(conj_S1, id_S1)) b = compose(compose(id_R1, compose(f, conj_S1)), id_S1) c = compose(compose(compose(id_R1, f), conj_S1), id_S1) d = compose(id_R1, compose(f, compose(conj_S1, id_S1))) From e7f069eb269903b8bc708aff4af4583e5a4b97ea Mon Sep 17 00:00:00 2001 From: Janko Boehm Date: Wed, 18 Jun 2025 11:16:57 +0200 Subject: [PATCH 24/24] fix --- experimental/InjectiveResolutions/src/InjectiveResolutions.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/experimental/InjectiveResolutions/src/InjectiveResolutions.jl b/experimental/InjectiveResolutions/src/InjectiveResolutions.jl index d189810fe3c6..3a9bd0f7ef2e 100644 --- a/experimental/InjectiveResolutions/src/InjectiveResolutions.jl +++ b/experimental/InjectiveResolutions/src/InjectiveResolutions.jl @@ -42,6 +42,7 @@ import ..Oscar: oscar_free_module, oscar_generators, primitive_generator, + singular_freemodule, singular_generators, singular_module, singular_poly_ring,