diff --git a/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/free_modules.md b/docs/src/CommutativeAlgebra/ModulesOverMultivariateRings/free_modules.md index 3bcbef245c93..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`, or `MPolyQuoLocRing`. +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, @@ -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(::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 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/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/docs/src/CommutativeAlgebra/homological_algebra.md b/docs/src/CommutativeAlgebra/homological_algebra.md index 8c7dd334bd52..e1fb72b05740 100644 --- a/docs/src/CommutativeAlgebra/homological_algebra.md +++ b/docs/src/CommutativeAlgebra/homological_algebra.md @@ -17,6 +17,15 @@ supporting computations in homological algebra. ```@docs prune_with_map(M::ModuleFP) ``` +## 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 diff --git a/experimental/InjectiveResolutions/src/InjectiveResolutions.jl b/experimental/InjectiveResolutions/src/InjectiveResolutions.jl index 85ec4bc753c3..3a9bd0f7ef2e 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, @@ -40,6 +42,7 @@ import ..Oscar: oscar_free_module, oscar_generators, primitive_generator, + singular_freemodule, singular_generators, singular_module, singular_poly_ring, @@ -51,7 +54,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..c4d9e4e3ec99 100644 --- a/experimental/InjectiveResolutions/src/ModuleFunctionality.jl +++ b/experimental/InjectiveResolutions/src/ModuleFunctionality.jl @@ -57,6 +57,14 @@ 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 +84,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 +128,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/FreeMod.jl b/src/Modules/UngradedModules/FreeMod.jl index 8aff69ed7568..8a833561fb0d 100644 --- a/src/Modules/UngradedModules/FreeMod.jl +++ b/src/Modules/UngradedModules/FreeMod.jl @@ -22,6 +22,37 @@ function FreeMod(R::AdmissibleModuleFPRing, names::Vector{Symbol}; cached::Bool= return FreeMod{elem_type(R)}(length(names), R, names) end + +@doc raw""" + 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 +returns a free module with a dense implementation. + +The string `name` specifies how the basis vectors are printed. + +# Examples +```jldoctest +julia> F = free_module(FreeMod, ZZ, 3, "f") +Free module of rank 3 over integer ring + +julia> F[1] +f[1] + +julia> K = GF(7); + +julia> FK = free_module(FreeMod, K, 2) +Free module of rank 2 over K + +julia> FK[1] +e[1] +``` +""" +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""" 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..a039b42a09d4 100644 --- a/src/Modules/UngradedModules/FreeModuleHom.jl +++ b/src/Modules/UngradedModules/FreeModuleHom.jl @@ -450,40 +450,39 @@ 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 +@attr Any function kernel_ctx(h::FreeModuleHom{<:FreeMod{T}, <:FreeMod{T}, Nothing}) where {T<:Union{ZZRingElem, FieldElem}} + 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) +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..da4ab9945acb 100644 --- a/src/Modules/UngradedModules/FreeResolutions.jl +++ b/src/Modules/UngradedModules/FreeResolutions.jl @@ -543,35 +543,38 @@ function free_resolution(M::SubquoModule{T}; return FreeResolution(cc) end -function free_resolution(M::SubquoModule{T}) where {T<:RingElem} +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 + # 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)) + 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..1300cd97ce25 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,23 +419,23 @@ 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 - if task == :auto - if coefficient_ring(base_ring(parent(a))) isa Field #base_ring(base_ring(...)) does not work for MPolyQuos - task = :via_transform - else - task = :via_lift - end - end - for i in 1:ngens(M) - g = gen(M,i) + for (i, g) in enumerate(gens(M)) if a == g - R = base_ring(M) - return sparse_row(R, [(i,R(1))]) + return sparse_row(R, [i], [one(R)]) 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 + end if task == :via_transform std, _ = lift_std(M) return coordinates_via_transform(a, std) @@ -446,6 +446,22 @@ function coordinates(a::FreeModElem, M::SubModuleOfFreeModule, task::Symbol = :a 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) +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""" normal_form(M::ModuleGens, GB::ModuleGens) diff --git a/src/Modules/UngradedModules/Presentation.jl b/src/Modules/UngradedModules/Presentation.jl index f15139544efb..79cc6e225f9c 100644 --- a/src/Modules/UngradedModules/Presentation.jl +++ b/src/Modules/UngradedModules/Presentation.jl @@ -532,13 +532,19 @@ e[2] ``` """ 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 +588,64 @@ function prune_with_map(M::ModuleFP{T}) where {T<:Union{MPolyRingElem, MPolyQuoR return M_new, phi 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 + 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 + 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}} + 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}; minimal_kernel::Bool=true) where {T <: Union{MPolyRingElem, MPolyQuoRingElem}} R = base_ring(SQ) @@ -638,3 +702,144 @@ 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}} + +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. + +# Examples +```jldoctest +julia> R = ZZ; + +julia> F = free_module(FreeMod, 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(FreeMod, 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(FreeMod, 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 && nc > 0 + elseif isa(R, Field) + if is_finite(R) + return true + else + return nc == 0 + end + else + 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 + else + error("Unhandled base ring: $(typeof(R))") + end +end diff --git a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl index 22a6bee5c6cb..082b634e40b6 100644 --- a/src/Modules/UngradedModules/SubModuleOfFreeModule.jl +++ b/src/Modules/UngradedModules/SubModuleOfFreeModule.jl @@ -475,10 +475,38 @@ end Check if `a` is an element of `M`. """ function in(a::FreeModElem, M::SubModuleOfFreeModule) + iszero(a) && return true + a in 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)))) 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) +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) +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 diff --git a/test/Modules/UngradedModules.jl b/test/Modules/UngradedModules.jl index c2bdc52f6603..2bb5bd878246 100644 --- a/test/Modules/UngradedModules.jl +++ b/test/Modules/UngradedModules.jl @@ -1402,3 +1402,357 @@ end @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 + 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 "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) + 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 + end + + + + @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]]) + + @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[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 + end + + @testset "gradings over non-graded rings" begin + @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