From f9d829406c3cd231d09c633315d6474cfed397c1 Mon Sep 17 00:00:00 2001 From: Matthias Zach <85350711+HechtiDerLachs@users.noreply.github.com> Date: Wed, 18 Oct 2023 19:24:23 +0200 Subject: [PATCH] Exterior powers of finitely presented modules (#2879) * Implement a prototype for exterior powers of free modules. * Implement Koszul complexes associated to elements of free modules. * Preserve gradings and implement koszul dual bases. * Implement induced maps on exterior powers. * Some tweaks. * Move everything to appropriate places. * Implement iteration over module monomials of a specific degree. * Add trailing zeroes to the Koszul complex. * Allow custom parents and no caching. * Revert "Implement iteration over module monomials of a specific degree." This reverts commit 48b9752cd289ed9a0aad65abd160c475ce544cb3. * Extend optional caching to Koszul complexes. * Add extra multiplication function. * Some tweaks. * Customize printing. * Add pure and inv_pure. * Put the check back in. * Migrate the OrderedMultiIndex. * Export the new functionality. * Clean up the include statements. * Implement hom method. * Rename pure and decompose functions. * Migrate tests. * Clean up exports from LieAlgebra.jl. * Fix tests. * Some improvements in localizations. * Implement exterior powers for subquos. * Some fixes. * Reroute koszul complexes to use the non-Singular methods. * Deprecate old singular methods. * Repair Koszul homology. * Remove duplicate methods and use singular's depth. * Add tests. * Add some assertions. * Some tweaks for the ordered multiindices. * Pass on the check flag for construction of complexes. * Fix doctests and add a truely generic method for the depth computation. --- experimental/LieAlgebras/src/LieAlgebras.jl | 5 +- src/Combinatorics/OrderedMultiIndex.jl | 185 ++++++++++++++ src/Modules/ExteriorPowers/ExteriorPowers.jl | 4 + src/Modules/ExteriorPowers/FreeModules.jl | 192 +++++++++++++++ src/Modules/ExteriorPowers/Generic.jl | 184 ++++++++++++++ src/Modules/ExteriorPowers/SubQuo.jl | 81 +++++++ src/Modules/Modules.jl | 1 + src/Modules/UngradedModules.jl | 4 +- src/Modules/homological-algebra.jl | 238 ++++++++++++++----- src/Oscar.jl | 1 + src/Rings/mpoly-ideals.jl | 3 +- src/Rings/mpoly-localizations.jl | 10 + src/exports.jl | 6 + test/AlgebraicGeometry/Schemes/runtests.jl | 1 + test/Combinatorics/OrderedMultiIndex.jl | 27 +++ test/Combinatorics/runtests.jl | 1 + test/Modules/ExteriorPowers.jl | 180 ++++++++++++++ test/Modules/homological-algebra.jl | 49 +++- test/Modules/runtests.jl | 1 + 19 files changed, 1098 insertions(+), 75 deletions(-) create mode 100644 src/Combinatorics/OrderedMultiIndex.jl create mode 100644 src/Modules/ExteriorPowers/ExteriorPowers.jl create mode 100644 src/Modules/ExteriorPowers/FreeModules.jl create mode 100644 src/Modules/ExteriorPowers/Generic.jl create mode 100644 src/Modules/ExteriorPowers/SubQuo.jl create mode 100644 test/Combinatorics/OrderedMultiIndex.jl create mode 100644 test/Modules/ExteriorPowers.jl diff --git a/experimental/LieAlgebras/src/LieAlgebras.jl b/experimental/LieAlgebras/src/LieAlgebras.jl index d062077d1a39..9200fa18e83e 100644 --- a/experimental/LieAlgebras/src/LieAlgebras.jl +++ b/experimental/LieAlgebras/src/LieAlgebras.jl @@ -41,6 +41,7 @@ import ..Oscar: image, inv, is_abelian, + is_exterior_power, is_isomorphism, is_nilpotent, is_perfect, @@ -82,14 +83,12 @@ export coefficient_vector export coerce_to_lie_algebra_elem export combinations export derived_algebra -export exterior_power export general_linear_lie_algebra export highest_weight_module export hom_direct_sum export hom_power export is_direct_sum export is_dual -export is_exterior_power export is_self_normalizing export is_standard_module export is_symmetric_power @@ -143,14 +142,12 @@ export base_modules export bracket export coerce_to_lie_algebra_elem export derived_algebra -export exterior_power export general_linear_lie_algebra export highest_weight_module export hom_direct_sum export hom_power export is_direct_sum export is_dual -export is_exterior_power export is_self_normalizing export is_standard_module export is_symmetric_power diff --git a/src/Combinatorics/OrderedMultiIndex.jl b/src/Combinatorics/OrderedMultiIndex.jl new file mode 100644 index 000000000000..8e8eaf630dca --- /dev/null +++ b/src/Combinatorics/OrderedMultiIndex.jl @@ -0,0 +1,185 @@ +######################################################################## +# Ordered multiindices of the form 0 < i₁ < i₂ < … < iₚ ≤ n. +# +# For instance, these provide a coherent way to enumerate the generators +# of an exterior power ⋀ ᵖ M of a module M given a chosen set of +# generators of M. But they can also be used for other purposes +# like enumerating subsets of p elements out of a set with n elements. +######################################################################## +mutable struct OrderedMultiIndex{IntType<:IntegerUnion} + i::Vector{IntType} + n::IntType + + function OrderedMultiIndex(i::Vector{T}, n::T) where {T<:IntegerUnion} + @assert all(k->i[k](x in indices(b)), indices(a)) && return 0, indices(a) + + p = length(a) + q = length(b) + result_indices = vcat(indices(a), indices(b)) + sign = 1 + + # bubble sort result_indices and keep track of the sign + for k in p:-1:1 + l = k + c = result_indices[l] + while l < p + q && c > result_indices[l+1] + result_indices[l] = result_indices[l+1] + sign = -sign + l = l+1 + end + result_indices[l] = c + end + return sign, result_indices +end + +# For two ordered multiindices i = (0 < i₁ < i₂ < … < iₚ ≤ n) +# and j = (0 < j₁ < j₂ < … < jᵣ ≤ n) this returns a pair `(sign, a)` +# with `sign` either 0 in case that iₖ = jₗ for some k and l, +# or ±1 depending on the number of transpositions needed to put +# (i₁, …, iₚ, j₁, …, jᵣ) into a strictly increasing order to produce `a`. +function _wedge(a::OrderedMultiIndex{T}, b::OrderedMultiIndex{T}) where {T} + sign, ind = _mult(a, b) + iszero(sign) && return sign, a + return sign, OrderedMultiIndex(ind, bound(a)) +end + +function _wedge(a::Vector{T}) where {T <: OrderedMultiIndex} + isempty(a) && error("list must not be empty") + isone(length(a)) && return 1, first(a) + k = div(length(a), 2) + b = a[1:k] + c = a[k+1:end] + sign_b, ind_b = _wedge(b) + sign_c, ind_c = _wedge(c) + sign, ind = _wedge(ind_b, ind_c) + return sign * sign_b * sign_c, ind +end + +function ==(a::OrderedMultiIndex{T}, b::OrderedMultiIndex{T}) where {T} + return bound(a) == bound(b) && indices(a) == indices(b) +end + +######################################################################## +# A data type to facilitate iteration over all ordered multiindices +# of the form 0 < i₁ < i₂ < … < iₚ ≤ n for fixed 0 ≤ p ≤ n. +# +# Example: +# +# for i in OrderedMultiIndexSet(3, 5) +# # do something with i = (0 < i₁ < i₂ < i₃ ≤ 5). +# end +######################################################################## +mutable struct OrderedMultiIndexSet + n::Int + p::Int + + function OrderedMultiIndexSet(p::Int, n::Int) + @assert 0 <= p <= n "invalid bounds" + return new(n, p) + end +end + +bound(I::OrderedMultiIndexSet) = I.n +index_length(I::OrderedMultiIndexSet) = I.p + +Base.eltype(I::OrderedMultiIndexSet) = OrderedMultiIndex +Base.length(I::OrderedMultiIndexSet) = binomial(bound(I), index_length(I)) + +function Base.iterate(I::OrderedMultiIndexSet) + ind = OrderedMultiIndex([i for i in 1:index_length(I)], bound(I)) + return ind, ind +end + +function Base.iterate(I::OrderedMultiIndexSet, state::OrderedMultiIndex) + bound(I) == bound(state) || error("index not compatible with set") + ind = copy(indices(state)) + l = length(state) + while l > 0 && ind[l] == bound(I) - length(state) + l + l = l - 1 + end + iszero(l) && return nothing + ind[l] = ind[l] + 1 + l = l + 1 + while l <= length(state) + ind[l] = ind[l-1] + 1 + l = l + 1 + end + result = OrderedMultiIndex(ind, bound(I)) + return result, result +end + +function Base.show(io::IO, ind::OrderedMultiIndex) + i = indices(ind) + print(io, "0 ") + for i in indices(ind) + print(io, "< $i ") + end + print(io, "<= $(bound(ind))") +end + +# For an ordered multiindex i = (0 < i₁ < i₂ < … < iₚ ≤ n) this +# returns the number k so that i appears at the k-th spot in the +# enumeration of all ordered multiindices for this pair 0 ≤ p ≤ n. +function linear_index(ind::OrderedMultiIndex) + n = bound(ind) + p = length(ind) + iszero(p) && return 1 + isone(p) && return ind[1] + i = indices(ind) + return binomial(n, p) - binomial(n - first(i) + 1, p) + linear_index(OrderedMultiIndex(i[2:end].-first(i), n-first(i))) +end + +# For a pair 0 ≤ p ≤ n return the k-th ordered multiindex in the +# enumeration of all ordered multiindices (0 < i₁ < i₂ < … < iₚ ≤ n). +function ordered_multi_index(k::Int, p::Int, n::Int) + (k < 1 || k > binomial(n, p)) && error("index out of range") + iszero(p) && return OrderedMultiIndex(Int[], n) + isone(p) && return OrderedMultiIndex([k], n) + n == p && return OrderedMultiIndex([k for k in 1:n], n) + bin = binomial(n, p) + i1 = findfirst(j->(bin - binomial(n - j, p) > k - 1), 1:n-p) + if i1 === nothing + prev_res = ordered_multi_index(k - bin + 1, p-1, p-1) + k = n-p+1 + return OrderedMultiIndex(pushfirst!(indices(prev_res).+k, k), n) + else + prev_res = ordered_multi_index(k - bin + binomial(n - i1 + 1, p), p-1, n - i1) + return OrderedMultiIndex(pushfirst!(indices(prev_res).+i1, i1), n) + end +end + diff --git a/src/Modules/ExteriorPowers/ExteriorPowers.jl b/src/Modules/ExteriorPowers/ExteriorPowers.jl new file mode 100644 index 000000000000..d09a136908d8 --- /dev/null +++ b/src/Modules/ExteriorPowers/ExteriorPowers.jl @@ -0,0 +1,4 @@ +#include("Helpers.jl") Moved to src/Combinatorics/OrderedMultiIndex.jl +include("FreeModules.jl") +include("SubQuo.jl") +include("Generic.jl") diff --git a/src/Modules/ExteriorPowers/FreeModules.jl b/src/Modules/ExteriorPowers/FreeModules.jl new file mode 100644 index 000000000000..a40481dc0f88 --- /dev/null +++ b/src/Modules/ExteriorPowers/FreeModules.jl @@ -0,0 +1,192 @@ +######################################################################## +# Exterior powers of free modules +# +# For F = Rⁿ we provide methods to create ⋀ ᵖF for arbitrary 0 ≤ p ≤ n. +# These modules are cached in F and know that they are an exterior +# power of F. This allows us to implement the wedge product of their +# elements. +######################################################################## + +# User facing constructor for ⋀ ᵖ F. +function exterior_power(F::FreeMod, p::Int; cached::Bool=true) + (p < 0 || p > rank(F)) && error("index out of bounds") + + if cached + powers = _exterior_powers(F) + haskey(powers, p) && return powers[p]::Tuple{typeof(F), <:Map} + end + + R = base_ring(F) + n = rank(F) + result = FreeMod(R, binomial(n, p)) + + # In case F was graded, we have to take an extra detour. + if is_graded(F) + G = grading_group(F) + weights = elem_type(G)[] + for ind in OrderedMultiIndexSet(p, n) + push!(weights, sum(degree(F[i]) for i in indices(ind); init=zero(G))) + end + result = grade(result, weights) + end + + # Create the multiplication map + function my_mult(u::FreeModElem...) + isempty(u) && return result[1] # only the case p=0 + @assert all(x->parent(x)===F, u) "elements must live in the same module" + @assert length(u) == p "need a $p-tuple of elements" + return wedge(collect(u), parent=result) + end + function my_mult(u::Tuple) + return my_mult(u...) + end + + function my_decomp(u::FreeModElem) + parent(u) === result || error("element does not belong to the correct module") + k = findfirst(x->x==u, gens(result)) + k === nothing && error("element must be a generator of the module") + ind = ordered_multi_index(k, p, n) + e = gens(F) + return Tuple(e[i] for i in indices(ind)) + end + + mult_map = MapFromFunc(Hecke.TupleParent(Tuple([zero(F) for f in 1:p])), result, my_mult, my_decomp) + inv_mult_map = MapFromFunc(result, domain(mult_map), my_decomp, my_mult) + @assert domain(mult_map) === parent(Tuple(zero(F) for i in 1:p)) "something went wrong with the parents" + + # Store the map in the attributes + set_attribute!(result, :multiplication_map, mult_map) + set_attribute!(result, :wedge_pure_function, mult_map) + set_attribute!(result, :wedge_generator_decompose_function, inv_mult_map) + set_attribute!(result, :is_exterior_power, (F, p)) + + cached && (_exterior_powers(F)[p] = (result, mult_map)) + + # Set the variable names for printing + orig_symb = String.(symbols(F)) + new_symb = Symbol[] + if iszero(p) + new_symb = [Symbol("1")] + else + for ind in OrderedMultiIndexSet(p, n) + symb_str = orig_symb[ind[1]] + for i in 2:p + symb_str = symb_str * "∧" * orig_symb[ind[i]] + end + push!(new_symb, Symbol(symb_str)) + end + end + result.S = new_symb + + set_attribute!(result, :show => show_exterior_product) + + return result, mult_map +end + +function symbols(F::FreeMod) + return F.S +end + + +######################################################################## +# Koszul homology +######################################################################## + +function koszul_complex(v::FreeModElem; cached::Bool=true) + F = parent(v) + R = base_ring(F) + n = rank(F) + ext_powers = [exterior_power(F, p, cached=cached)[1] for p in 0:n] + boundary_maps = [wedge_multiplication_map(ext_powers[i+1], ext_powers[i+2], v) for i in 0:n-1] + Z = is_graded(F) ? graded_free_module(R, []) : free_module(R, 0) + pushfirst!(boundary_maps, hom(Z, domain(first(boundary_maps)), elem_type(domain(first(boundary_maps)))[])) + push!(boundary_maps, hom(codomain(last(boundary_maps)), Z, [zero(Z) for i in 1:ngens(codomain(last(boundary_maps)))])) + return chain_complex(boundary_maps, seed=-1, check=false) +end + +function koszul_complex(v::FreeModElem, M::ModuleFP; cached::Bool=true) + K = koszul_complex(v, cached=cached) + KM = tensor_product(K, M) + return KM +end + +function koszul_homology(v::FreeModElem, i::Int; cached::Bool=true) + F = parent(v) + n = rank(F) + + # Catch the edge cases + if i == n # This captures the homological degree zero due to the convention of the chain_complex constructor + phi = wedge_multiplication_map(exterior_power(F, 0, cached=cached)[1], F, v) + return kernel(phi)[1] + end + + if iszero(i) # Homology at the last entry of the complex. + phi = wedge_multiplication_map(exterior_power(F, n-1)[1], exterior_power(F, n, cached=cached)[1], v) + return cokernel(phi)[1] + end + + ext_powers = [exterior_power(F, n-p, cached=cached)[1] for p in i-1:i+1] + boundary_maps = [wedge_multiplication_map(ext_powers[p+1], ext_powers[p], v) for p in 2:-1:1] + K = chain_complex(boundary_maps, check=false) + return homology(K, 1) +end + +function koszul_homology(v::FreeModElem, M::ModuleFP, i::Int; cached::Bool=true) + F = parent(v) + n = rank(F) + + # Catch the edge cases + if i == n # This captures the homological degree zero due to the convention of the chain_complex constructor + phi = wedge_multiplication_map(exterior_power(F, 0, cached=cached)[1], F, v) + K = chain_complex([phi], check=false) + KM = tensor_product(K, M) + return kernel(map(KM, 1))[1] + end + + if iszero(i) # Homology at the last entry of the complex. + phi = wedge_multiplication_map(exterior_power(F, n-1)[1], exterior_power(F, n, cached=cached)[1], v) + K = chain_complex([phi], check=false) + KM = tensor_product(K, M) + return cokernel(map(K, 1)) # TODO: cokernel does not seem to return a map by default. Why? + end + + ext_powers = [exterior_power(F, n-p, cached=cached)[1] for p in i-1:i+1] + boundary_maps = [wedge_multiplication_map(ext_powers[p+1], ext_powers[p], v) for p in 2:-1:1] + K = chain_complex(boundary_maps, check=false) + KM = tensor_product(K, M) + return homology(KM, 1) +end + +function koszul_dual(F::FreeMod; cached::Bool=true) + success, M, p = is_exterior_power(F) + !success && error("module must be an exterior power of some other module") + return exterior_power(M, rank(M) - p, cached=cached)[1] +end + +function koszul_duals(v::Vector{T}; cached::Bool=true) where {T<:FreeModElem} + isempty(v) && error("list of elements must not be empty") + all(u->parent(u) === parent(first(v)), v[2:end]) || error("parent mismatch") + + F = parent(first(v)) + success, M, p = is_exterior_power(F) + n = rank(M) + success || error("element must be an exterior product") + k = [findfirst(x->x==u, gens(F)) for u in v] + any(x->x===nothing, k) && error("elements must be generators of the module") + ind = [ordered_multi_index(r, p, n) for r in k] + comp = [ordered_multi_index([i for i in 1:n if !(i in indices(I))], n) for I in ind] + lin_ind = linear_index.(comp) + F_dual = koszul_dual(F, cached=cached) + results = [F_dual[j] for j in lin_ind] + for r in 1:length(v) + sign, _ = _wedge(ind[r], comp[r]) + isone(sign) || (results[r] = -results[r]) + end + return results +end + +function koszul_dual(v::FreeModElem; cached::Bool=true) + return first(koszul_duals([v], cached=cached)) +end + + diff --git a/src/Modules/ExteriorPowers/Generic.jl b/src/Modules/ExteriorPowers/Generic.jl new file mode 100644 index 000000000000..40f43cec914f --- /dev/null +++ b/src/Modules/ExteriorPowers/Generic.jl @@ -0,0 +1,184 @@ +# We need to cache eventually created exterior powers. +@attr Dict{Int, Tuple{T, <:Map}} function _exterior_powers(F::T) where {T<:ModuleFP} + return Dict{Int, Tuple{typeof(F), Map}}() +end + +# User facing method to ask whether F = ⋀ ᵖ M for some M. +# This returns a triple `(true, M, p)` in the affirmative case +# and `(false, F, 0)` otherwise. +function is_exterior_power(M::ModuleFP) + if has_attribute(M, :is_exterior_power) + MM, p = get_attribute(M, :is_exterior_power) + return (true, MM, p) + end + return (false, M, 0) +end + +# Printing of exterior powers +function show_exterior_product(io::IO, M::ModuleFP) + success, F, p = is_exterior_power(M) + success || error("module is not an exterior power") + print(io, "⋀^$p($F)") +end + +function show_exterior_product(io::IO, ::MIME"text/html", M::ModuleFP) + success, F, p = is_exterior_power(M) + success || error("module is not an exterior power") + io = IOContext(io, :compact => true) + print(io, "⋀^$p$F") +end + +function multiplication_map(M::ModuleFP) + has_attribute(M, :multiplication_map) || error("module is not an exterior power") + return get_attribute(M, :multiplication_map)::Map +end + +function wedge_pure_function(M::ModuleFP) + has_attribute(M, :wedge_pure_function) || error("module is not an exterior power") + return get_attribute(M, :wedge_pure_function)::Map +end + +function wedge_generator_decompose_function(M::ModuleFP) + has_attribute(M, :wedge_generator_decompose_function) || error("module is not an exterior power") + return get_attribute(M, :wedge_generator_decompose_function)::Map +end + +# Given two exterior powers F = ⋀ ᵖM and G = ⋀ ʳM and an element +# v ∈ ⋀ ʳ⁻ᵖ M this constructs the module homomorphism associated +# to +# +# v ∧ - : F → G, u ↦ v ∧ u. +# +# We also allow v ∈ M considered as ⋀ ¹M and the same holds in +# the cases p = 1 and r = 1. +function wedge_multiplication_map(F::ModuleFP, G::ModuleFP, v::ModuleFPElem) + success, orig_mod, p = is_exterior_power(F) + if !success + Fwedge1, _ = exterior_power(F, 1) + id = hom(F, Fwedge1, gens(Fwedge1)) + tmp = wedge_multiplication_map(Fwedge1, G, v) + return compose(id, tmp) + end + + success, orig_mod_2, q = is_exterior_power(G) + if !success + Gwedge1, _ = exterior_power(G, 1) + id = hom(Gwedge1, G, gens(G)) + tmp = wedge_multiplication_map(F, Gwedge1, v) + return compose(tmp, id) + end + + orig_mod === orig_mod_2 || error("modules must be exterior powers of the same module") + H = parent(v) + + # In case v comes from the original module, convert. + if H === orig_mod + M, _ = exterior_power(orig_mod, 1) + w = M(coordinates(v)) + return wedge_multiplication_map(F, G, w) + end + + success, orig_mod_2, r = is_exterior_power(H) + success || error("element is not an exterior product") + orig_mod_2 === orig_mod || error("element is not an exterior product for the correct module") + p + r == q || error("powers are incompatible") + + # map the generators + img_gens = [wedge(v, e, parent=G) for e in gens(F)] + return hom(F, G, img_gens) +end + +# The wedge product of two or more elements. +function wedge(u::ModuleFPElem, v::ModuleFPElem; + parent::ModuleFP=begin + success, F, p = is_exterior_power(Oscar.parent(u)) + if !success + F = Oscar.parent(u) + p = 1 + end + success, _, q = is_exterior_power(Oscar.parent(v)) + !success && (q = 1) + exterior_power(F, p + q)[1] + end + ) + success1, F1, p = is_exterior_power(Oscar.parent(u)) + if !success1 + F = Oscar.parent(u) + Fwedge1, _ = exterior_power(F1, 1) + return wedge(Fwedge1(coordinates(u)), v, parent=parent) + end + + success2, F2, q = is_exterior_power(Oscar.parent(v)) + if !success2 + F = Oscar.parent(v) + Fwedge1, _ = exterior_power(F1, 1) + return wedge(u, Fwedge1(coordinates(v)), parent=parent) + end + + F1 === F2 || error("modules are not exterior powers of the same original module") + n = ngens(F1) + + result = zero(parent) + for (i, a) in coordinates(u) + ind_i = ordered_multi_index(i, p, n) + for (j, b) in coordinates(v) + ind_j = ordered_multi_index(j, q, n) + sign, k = _wedge(ind_i, ind_j) + iszero(sign) && continue + result = result + sign * a * b * parent[linear_index(k)] + end + end + return result +end + +function wedge(u::Vector{T}; + parent::ModuleFP=begin + r = 0 + isempty(u) && error("list must not be empty") + F = Oscar.parent(first(u)) # initialize variable + for v in u + success, F, p = is_exterior_power(Oscar.parent(v)) + if !success + F = Oscar.parent(v) + p = 1 + end + r = r + p + end + exterior_power(F, r)[1] + end + ) where {T<:ModuleFPElem} + isempty(u) && error("list must not be empty") + isone(length(u)) && return first(u) + k = div(length(u), 2) + result = wedge(wedge(u[1:k]), wedge(u[k+1:end]), parent=parent) + @assert Oscar.parent(result) === parent + return result +end + +function induced_map_on_exterior_power(phi::FreeModuleHom{<:FreeMod, <:FreeMod, Nothing}, p::Int; + domain::FreeMod=exterior_power(Oscar.domain(phi), p)[1], + codomain::FreeMod=exterior_power(Oscar.codomain(phi), p)[1] + ) + F = Oscar.domain(phi) + m = rank(F) + G = Oscar.codomain(phi) + n = rank(G) + + imgs = phi.(gens(F)) + img_gens = [wedge(imgs[indices(ind)], parent=codomain) for ind in OrderedMultiIndexSet(p, m)] + return hom(domain, codomain, img_gens) +end + +# The induced map on exterior powers +function hom(M::FreeMod, N::FreeMod, phi::FreeModuleHom) + success, F, p = is_exterior_power(M) + success || error("module is not an exterior power") + success, FF, q = is_exterior_power(N) + success || error("module is not an exterior power") + F === domain(phi) || error("map not compatible") + FF === codomain(phi) || error("map not compatible") + p == q || error("exponents must agree") + return induced_map_on_exterior_power(phi, p, domain=M, codomain=N) +end + + diff --git a/src/Modules/ExteriorPowers/SubQuo.jl b/src/Modules/ExteriorPowers/SubQuo.jl new file mode 100644 index 000000000000..2659f108994f --- /dev/null +++ b/src/Modules/ExteriorPowers/SubQuo.jl @@ -0,0 +1,81 @@ +function exterior_power(M::SubquoModule, p::Int; cached::Bool=true) + n = rank(ambient_free_module(M)) + R = base_ring(M) + ((p >= 0) && (p <= n)) || error("exponent out of range") + + if cached + powers = _exterior_powers(M) + haskey(powers, p) && return powers[p] + end + + result = M # Initialize variable + if iszero(p) + F = FreeMod(R, 1) + result, _ = sub(F, [F[1]]) + else + C = presentation(M) + phi = map(C, 1) + result, mm = _exterior_power(phi, p) + end + + function my_mult(u::SubquoModuleElem...) + isempty(u) && return result[1] # only the case p=0 + @assert all(x->parent(x)===M, u) "elements must live in the same module" + @assert length(u) == p "need a $p-tuple of elements" + return wedge(collect(u), parent=result) + end + function my_mult(u::Tuple) + return my_mult(u...) + end + + function my_decomp(u::SubquoModuleElem) + parent(u) === result || error("element does not belong to the correct module") + k = findfirst(x->x==u, gens(result)) + k === nothing && error("element must be a generator of the module") + ind = ordered_multi_index(k, p, n) + e = gens(M) + return Tuple(e[i] for i in indices(ind)) + end + + mult_map = MapFromFunc(Hecke.TupleParent(Tuple([zero(M) for f in 1:p])), result, my_mult, my_decomp) + inv_mult_map = MapFromFunc(result, domain(mult_map), my_decomp, my_mult) + + set_attribute!(result, :multiplication_map, mult_map) + set_attribute!(result, :wedge_pure_function, mult_map) + set_attribute!(result, :wedge_generator_decompose_function, inv_mult_map) + + set_attribute!(result, :is_exterior_power, (M, p)) + cached && (_exterior_powers(M)[p] = (result, mult_map)) + + # Set the variable names for printing + orig_symb = ["$(e)" for e in ambient_representatives_generators(M)] + new_symb = Symbol[] + if iszero(p) + new_symb = [Symbol("1")] + else + for ind in OrderedMultiIndexSet(p, n) + symb_str = orig_symb[ind[1]] + for i in 2:p + symb_str = symb_str * "∧" * orig_symb[ind[i]] + end + push!(new_symb, Symbol(symb_str)) + end + end + + symbols(result) = new_symb + return result, mult_map +end + +function _exterior_power(phi::FreeModuleHom, p::Int) + F = codomain(phi) + R = base_ring(F) + rel = domain(phi) + Fp, mult_map = exterior_power(F, p) + Fq, _ = exterior_power(F, p-1) + img_gens = [wedge(e, f) for e in gens(Fq) for f in phi.(gens(rel))] + G = FreeMod(R, length(img_gens)) + psi = hom(G, Fp, img_gens) + # TODO: the `cokernel` command does not have consistent output over all types of rings. + return (base_ring(codomain(phi)) isa Union{MPolyLocRing, MPolyQuoLocRing} ? cokernel(psi)[1] : cokernel(psi)), mult_map +end + diff --git a/src/Modules/Modules.jl b/src/Modules/Modules.jl index 2250bd9944a2..e4fb0dde98f7 100644 --- a/src/Modules/Modules.jl +++ b/src/Modules/Modules.jl @@ -8,4 +8,5 @@ include("ModulesGraded.jl") include("module-localizations.jl") include("local_rings.jl") include("mpolyquo.jl") +include("ExteriorPowers/ExteriorPowers.jl") diff --git a/src/Modules/UngradedModules.jl b/src/Modules/UngradedModules.jl index 1271711be57d..ae175fe8e861 100644 --- a/src/Modules/UngradedModules.jl +++ b/src/Modules/UngradedModules.jl @@ -4060,8 +4060,8 @@ function chain_complex(V::ModuleFPHom...; seed::Int = 0) return ComplexOfMorphisms(ModuleFP, collect(V); typ = :chain, seed = seed) end -function chain_complex(V::Vector{<:ModuleFPHom}; seed::Int = 0) - return ComplexOfMorphisms(ModuleFP, V; typ = :chain, seed = seed) +function chain_complex(V::Vector{<:ModuleFPHom}; seed::Int = 0, check::Bool=true) + return ComplexOfMorphisms(ModuleFP, V; typ = :chain, seed = seed, check=check) end #################### diff --git a/src/Modules/homological-algebra.jl b/src/Modules/homological-algebra.jl index dd89ff51b79e..a34f2ccd649f 100644 --- a/src/Modules/homological-algebra.jl +++ b/src/Modules/homological-algebra.jl @@ -289,23 +289,27 @@ julia> V = [x*y, x*z, y*z] y*z julia> koszul_homology(V, F, 0) -Submodule with 3 generators -1 -> y*z*e[1] -2 -> x*z*e[1] -3 -> x*y*e[1] -represented as subquotient with no relations. +Subquotient of Submodule with 1 generator +1 -> e[1]∧e[2]∧e[3] +by Submodule with 3 generators +1 -> y*z*e[1]∧e[2]∧e[3] +2 -> -x*z*e[1]∧e[2]∧e[3] +3 -> x*y*e[1]∧e[2]∧e[3] julia> koszul_homology(V, F, 1) -Submodule with 3 generators -1 -> -z*e[1] + z*e[2] -2 -> y*e[2] -3 -> x*e[1] -represented as subquotient with no relations. +Subquotient of Submodule with 2 generators +1 -> y*e[1]∧e[3] \otimes e[1] + z*e[2]∧e[3] \otimes e[1] +2 -> x*e[1]∧e[2] \otimes e[1] - z*e[2]∧e[3] \otimes e[1] +by Submodule with 3 generators +1 -> -x*z*e[1]∧e[2] \otimes e[1] - y*z*e[1]∧e[3] \otimes e[1] +2 -> x*y*e[1]∧e[2] \otimes e[1] - y*z*e[2]∧e[3] \otimes e[1] +3 -> x*y*e[1]∧e[3] \otimes e[1] + x*z*e[2]∧e[3] \otimes e[1] julia> koszul_homology(V, F, 2) -Submodule with 1 generator -1 -> 0 -represented as subquotient with no relations. +Subquotient of Submodule with 1 generator +1 -> x*y*e[1] \otimes e[1] + x*z*e[2] \otimes e[1] + y*z*e[3] \otimes e[1] +by Submodule with 1 generator +1 -> x*y*e[1] \otimes e[1] + x*z*e[2] \otimes e[1] + y*z*e[3] \otimes e[1] ``` ```jldoctest @@ -316,46 +320,85 @@ julia> TC = ideal(R, [x*z-y^2, w*z-x*y, w*y-x^2]); julia> F = free_module(R, 1); julia> koszul_homology(gens(TC), F, 0) -Submodule with 3 generators -1 -> (-x*z + y^2)*e[1] -2 -> (-w*z + x*y)*e[1] -3 -> (-w*y + x^2)*e[1] -represented as subquotient with no relations. +Subquotient of Submodule with 1 generator +1 -> e[1]∧e[2]∧e[3] +by Submodule with 3 generators +1 -> (w*y - x^2)*e[1]∧e[2]∧e[3] +2 -> (-w*z + x*y)*e[1]∧e[2]∧e[3] +3 -> (x*z - y^2)*e[1]∧e[2]∧e[3] julia> koszul_homology(gens(TC), F, 1) -Submodule with 3 generators -1 -> y*e[1] - z*e[2] -2 -> x*e[1] - y*e[2] -3 -> w*e[1] - x*e[2] -represented as subquotient with no relations. +Subquotient of Submodule with 2 generators +1 -> z*e[1]∧e[2] \otimes e[1] + y*e[1]∧e[3] \otimes e[1] + x*e[2]∧e[3] \otimes e[1] +2 -> y*e[1]∧e[2] \otimes e[1] + x*e[1]∧e[3] \otimes e[1] + w*e[2]∧e[3] \otimes e[1] +by Submodule with 3 generators +1 -> (-w*z + x*y)*e[1]∧e[2] \otimes e[1] + (-w*y + x^2)*e[1]∧e[3] \otimes e[1] +2 -> (x*z - y^2)*e[1]∧e[2] \otimes e[1] + (-w*y + x^2)*e[2]∧e[3] \otimes e[1] +3 -> (x*z - y^2)*e[1]∧e[3] \otimes e[1] + (w*z - x*y)*e[2]∧e[3] \otimes e[1] julia> koszul_homology(gens(TC), F, 2) -Submodule with 1 generator -1 -> 0 -represented as subquotient with no relations. +Subquotient of Submodule with 1 generator +1 -> (-x*z + y^2)*e[1] \otimes e[1] + (-w*z + x*y)*e[2] \otimes e[1] + (-w*y + x^2)*e[3] \otimes e[1] +by Submodule with 1 generator +1 -> (x*z - y^2)*e[1] \otimes e[1] + (w*z - x*y)*e[2] \otimes e[1] + (w*y - x^2)*e[3] \otimes e[1] ``` """ -function koszul_homology(V::Vector{T}, M::ModuleFP{T}, i::Int) where T <: MPolyRingElem - error("not implemented for the given type of module.") +function koszul_homology(V::Vector{T}, F::ModuleFP{T}, i::Int) where T <: MPolyRingElem + R = base_ring(F) + @assert all(x->parent(x)===R, V) "rings are incompatible" + if is_graded(F) && is_graded(R) + return _koszul_homology_graded(V, F, i) + elseif !is_graded(F) && !is_graded(R) + return _koszul_homology(V, F, i) + else + error("can not compute the koszul homology of a non-graded module over a graded ring") + end end -function koszul_homology(V::Vector{T},F::FreeMod{T}, i::Int) where T <: MPolyRingElem - R = base_ring(F) - @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" - @assert parent(V[1]) == R - @assert all(x->parent(x) == R, V) - I = ideal(R, V) - singular_assure(I) - MX = singular_module(F) - SG = Singular.Module(base_ring(MX), MX(zero(F))) - SU = Singular.LibHomolog.KoszulHomology(I.gens.S, SG, i) - FFF = free_module(R, rank(SU)) - MG = ModuleGens(FFF, SU) - UO = SubModuleOfFreeModule(FFF, MG) - return SubquoModule(UO) +function _koszul_homology(V::Vector{T}, F::ModuleFP{T}, i::Int) where T <: MPolyRingElem + r = length(V) + R = base_ring(F) + if iszero(r) + iszero(i) && return F + return FreeMod(R, 0) + end + Rr = FreeMod(R, r) + v = sum(x*e for (x, e) in zip(V, gens(Rr)); init=zero(Rr)) + return koszul_homology(v, F, i) # See src/Modules/ExteriorPowers/FreeMod.jl +end + +function _koszul_homology_graded(V::Vector{T},F::ModuleFP{T}, i::Int) where T <: MPolyRingElem + r = length(V) + R = base_ring(F) + if iszero(r) + iszero(i) && return F + return graded_free_module(R, 0) + end + Rr = graded_free_module(R, r) + v = sum(x*e for (x, e) in zip(V, gens(Rr)); init=zero(Rr)) + return koszul_homology(v, F, i) # See src/Modules/ExteriorPowers/FreeMod.jl +end + +# The old version invoking Singular is below. This proved to be slower in the assembly +# of the Koszul complex than the new one using the exterior powers of modules and +# the wedge product. We leave the code snippets here for reference. +function _koszul_homology_from_singular(V::Vector{T},F::FreeMod{T}, i::Int) where T <: MPolyRingElem{<:FieldElem} + R = base_ring(F) + @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" + @assert parent(V[1]) == R + @assert all(x->parent(x) == R, V) + I = ideal(R, V) + singular_assure(I) + MX = singular_module(F) + SG = Singular.Module(base_ring(MX), MX(zero(F))) + SU = Singular.LibHomolog.KoszulHomology(I.gens.S, SG, i) + FFF = free_module(R, rank(SU)) + MG = ModuleGens(FFF, SU) + UO = SubModuleOfFreeModule(FFF, MG) + return SubquoModule(UO) end -function koszul_homology(V::Vector{T}, M::SubquoModule{T}, i::Int) where T <: MPolyRingElem +function _koszul_homology_from_singular(V::Vector{T}, M::SubquoModule{T}, i::Int) where T <: MPolyRingElem R = base_ring(M) @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" @assert parent(V[1]) == R @@ -445,11 +488,46 @@ julia> depth(I, M) 1 ``` """ -function depth(I::MPolyIdeal{T}, M::ModuleFP{T}) where T <: MPolyRingElem - error("not implemented for the given type of module.") +function depth(I::Ideal{T}, F::ModuleFP{T}) where T <: RingElem + # One truely generic method not bound to polynomial rings + R = base_ring(F) + R === base_ring(I) || error("rings are incompatible") + f = small_generating_set(I) + n = length(f) + iszero(n) && return 0 + + i = findfirst(k->!iszero(koszul_homology(f, F, k)), n:-1:0) + i === nothing && return 0 + return i - 1 end -function depth(I::MPolyIdeal{T}, F::FreeMod{T}) where T <: MPolyRingElem +# The heuristic over units in the syzygy matrix works generically. +# However, we can not assume that this has been implemented for all +# kinds of ideals, so here's a backup. +small_generating_set(I::Ideal) = gens(I) + +# It turned out that despite the quick assembly of the Koszul complex +# in Oscar the computation of depth is much faster in singular: +function depth(I::MPolyIdeal{T}, F::ModuleFP{T}) where T <: MPolyRingElem{<:FieldElem} + return _depth_from_singular(I, F) +end + +# The Singular implementation suggests that one can compute the depth +# also from below and finding the first vanishing Koszul homology. +function _depth_from_below(I::Ideal{T}, F::ModuleFP{T}) where T <: RingElem + R = base_ring(F) + R === base_ring(I) || error("rings are incompatible") + f = small_generating_set(I) + n = length(f) + iszero(n) && return 0 + + i = findfirst(j->iszero(koszul_homology(f, F, j)), 0:n) + i === nothing && return 0 + # i is the first entry of [0, 1, 2, ..., n] for which... + return n - i + 2 +end + +function _depth_from_singular(I::MPolyIdeal{T}, F::FreeMod{T}) where T <: MPolyRingElem R = base_ring(I) @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" @assert base_ring(I) == base_ring(F) @@ -460,7 +538,7 @@ function depth(I::MPolyIdeal{T}, F::FreeMod{T}) where T <: MPolyRingElem return Singular.LibHomolog.depth(SG, I.gens.S) end -function depth(I::MPolyIdeal{T}, M::SubquoModule{T}) where T <: MPolyRingElem +function _depth_from_singular(I::MPolyIdeal{T}, M::SubquoModule{T}) where T <: MPolyRingElem R = base_ring(I) @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" @assert base_ring(I) == base_ring(M) @@ -495,20 +573,35 @@ julia> V = gens(R) z julia> koszul_matrix(V, 3) -[z -y x] +[x y z] julia> koszul_matrix(V, 2) -[-y x 0] -[-z 0 x] -[ 0 -z y] +[-y -z 0] +[ x 0 -z] +[ 0 x y] julia> koszul_matrix(V, 1) -[x] -[y] -[z] +[ z] +[-y] +[ x] ``` """ function koszul_matrix(V::Vector{T}, i::Int) where T <: MPolyRingElem + r = length(V) + iszero(r) && error("list must not be empty") + R = parent(first(V)) + @assert all(x->parent(x)===R, V) "parent mismatch" + + Rr = FreeMod(R, r) + v = sum(x*e for (x, e) in zip(V, gens(Rr)); init=zero(Rr)) + Ci, _ = exterior_power(Rr, r-i) + Cip1, _ = exterior_power(Rr, r-i+1) + phi = wedge_multiplication_map(Ci, Cip1, v) + return matrix(phi) +end + +# Deprecated code below; left for reference for how to import things from Singular +function _koszul_matrix_from_singular(V::Vector{T}, i::Int) where T <: MPolyRingElem @assert 1 <= i <= length(V) R = parent(V[1]) @assert all(x->parent(x) == R, V) @@ -535,23 +628,46 @@ julia> V = gens(R) julia> K = koszul_complex(V); julia> matrix(map(K, 2)) -[-y x 0] -[-z 0 x] -[ 0 -z y] +[-y -z 0] +[ x 0 -z] +[ 0 x y] julia> Kd = hom(K, free_module(R, 1)); julia> matrix(map(Kd, 1)) -[-y -z 0] -[ x 0 -z] -[ 0 x y] +[-y x 0] +[-z 0 x] +[ 0 -z y] ``` """ function koszul_complex(V::Vector{T}) where T <: MPolyRingElem + r = length(V) + iszero(r) && error("list must not be empty") + R = parent(first(V)) + @assert all(x->parent(x)===R, V) "parent mismatch" + + F = FreeMod(R, r) + v = sum(x*e for (x, e) in zip(V, gens(F)); init=zero(F)) + return koszul_complex(v) +end + +function koszul_complex(V::Vector{T}, M::ModuleFP{T}) where T <: MPolyRingElem + r = length(V) + iszero(r) && error("list must not be empty") + R = base_ring(M) + @assert all(x->parent(x)===R, V) "parent mismatch" + + F = FreeMod(R, r) + v = sum(x*e for (x, e) in zip(V, gens(F)); init=zero(F)) + return koszul_complex(v, M) +end + +# Deprecated code below; left for reference for how to import things from Singular +function _koszul_complex_from_singular(V::Vector{T}) where T <: MPolyRingElem R = parent(V[1]) @assert all(x->parent(x) == R, V) n = length(V) - KM = [koszul_matrix(V, i) for i=n:-1:1] + KM = [_koszul_matrix_from_singular(V, i) for i=n:-1:1] F = free_module(R, 0) G = free_module(R, 1) diff --git a/src/Oscar.jl b/src/Oscar.jl index ff63dee4df6e..c5124855181a 100644 --- a/src/Oscar.jl +++ b/src/Oscar.jl @@ -221,6 +221,7 @@ include("Polymake/polymake_to_oscar.jl") include("Combinatorics/Graphs/functions.jl") include("Combinatorics/SimplicialComplexes.jl") +include("Combinatorics/OrderedMultiIndex.jl") include("Combinatorics/Matroids/JMatroids.jl") include("Combinatorics/Matroids/matroid_strata_grassmannian.jl") diff --git a/src/Rings/mpoly-ideals.jl b/src/Rings/mpoly-ideals.jl index 349647f5f212..0f35a2369221 100644 --- a/src/Rings/mpoly-ideals.jl +++ b/src/Rings/mpoly-ideals.jl @@ -1453,13 +1453,12 @@ julia> small_generating_set(J) x*y^2 - z ``` """ -function small_generating_set(I::MPolyIdeal) +function small_generating_set(I::MPolyIdeal{T}) where {T<:MPolyElem{<:FieldElem}} # For non-homogeneous ideals, we do not have a notion of minimal generating # set, but Singular.mstd still provides a good heuristic to find a small # generating set. R = base_ring(I) - @req coefficient_ring(R) isa Field "The coefficient ring must be a field" # in the ungraded case, mstd's heuristic returns smaller gens when recomputing gb sing_gb, sing_min = Singular.mstd(singular_generators(I)) diff --git a/src/Rings/mpoly-localizations.jl b/src/Rings/mpoly-localizations.jl index 6729cb078028..b2e77299c9e1 100644 --- a/src/Rings/mpoly-localizations.jl +++ b/src/Rings/mpoly-localizations.jl @@ -1797,6 +1797,15 @@ function coordinates( ) where {LRT<:MPolyLocRing{<:Any, <:Any, <:Any, <:Any, <:MPolyPowersOfElement}} L = base_ring(I) parent(a) === L || return coordinates(L(a), I, check=check) + + b = numerator(a) + if b in pre_saturated_ideal(I) + x = coordinates(b, pre_saturated_ideal(I)) + q = denominator(a) + # multiplications sparse*dense have to be carried out this way round. + return transpose(mul(pre_saturation_data(I), transpose(L(one(q), q, check=false)*change_base_ring(L, x)))) + end + @check a in I "the given element is not in the ideal" !is_saturated(I) && _replace_pre_saturated_ideal(I, saturated_ideal(I), prod(denominators(inverted_set(L)); init=one(base_ring(L)))) # Computing the saturation first is cheaper than the generic Posur method J = pre_saturated_ideal(I) @@ -2227,6 +2236,7 @@ function ideal_membership( L = base_ring(I) parent(a) == L || return L(a) in I b = numerator(a) + b in pre_saturated_ideal(I) && return true return b in saturated_ideal(I) end diff --git a/src/exports.jl b/src/exports.jl index ffbdea7af468..0cd89e5ff4ee 100644 --- a/src/exports.jl +++ b/src/exports.jl @@ -691,6 +691,7 @@ export indicator export induce export induce_shift export induced_automorphism +export induced_map_on_exterior_power export induced_cyclic export induced_ring_ordering export initial @@ -762,6 +763,7 @@ export is_embedded export is_empty export is_equal_with_morphism export is_equidimensional +export is_exterior_power export is_faithful export is_fano export is_feasible @@ -1471,6 +1473,10 @@ export walls export wdeglex export wdegrevlex export weakly_connected_components +export wedge +export wedge_multiplication_map +export wedge_pure_function +export wedge_generator_decompose_function export weight export weight_cone export weight_ordering diff --git a/test/AlgebraicGeometry/Schemes/runtests.jl b/test/AlgebraicGeometry/Schemes/runtests.jl index 8eaa97e9139b..ad8c22ee9ae1 100644 --- a/test/AlgebraicGeometry/Schemes/runtests.jl +++ b/test/AlgebraicGeometry/Schemes/runtests.jl @@ -29,3 +29,4 @@ include("MorphismFromRationalFunctions.jl") include("AffineRationalPoint.jl") include("ProjectiveRationalPoint.jl") include("BlowupMorphism.jl") + diff --git a/test/Combinatorics/OrderedMultiIndex.jl b/test/Combinatorics/OrderedMultiIndex.jl new file mode 100644 index 000000000000..ee989802aa71 --- /dev/null +++ b/test/Combinatorics/OrderedMultiIndex.jl @@ -0,0 +1,27 @@ +@testset "ordered multiindices" begin + I = Oscar.ordered_multi_index([1, 2, 5], 5) + I1 = Oscar.ordered_multi_index([1], 5) + I2 = Oscar.ordered_multi_index([2], 5) + I5 = Oscar.ordered_multi_index([5], 5) + J = Oscar.ordered_multi_index([3, 4], 5) + K = Oscar.ordered_multi_index([2, 4], 5) + @test "$(Oscar._wedge(I, J)[2])" == "0 < 1 < 2 < 3 < 4 < 5 <= 5" + sign, ind = Oscar._wedge([I2, I5, I1]) + @test ind == I + @test sign == 1 + sign, ind = Oscar._wedge([I2, I1, I5]) + @test ind == I + @test sign == -1 + + sign, ind = Oscar._wedge(J, K) + @test sign == 0 + sign, ind = Oscar._wedge(I, K) + @test sign == 0 + + c = [ind for ind in Oscar.OrderedMultiIndexSet(3, 5)] + @test length(c) == binomial(5, 3) + + @test all(x->c[Oscar.linear_index(x)] == x, Oscar.OrderedMultiIndexSet(3, 5)) + + @test [Oscar.ordered_multi_index(k, 3, 5) for k in 1:binomial(5, 3)] == collect(Oscar.OrderedMultiIndexSet(3, 5)) +end diff --git a/test/Combinatorics/runtests.jl b/test/Combinatorics/runtests.jl index e8ff4ce363ae..413f194d83d6 100644 --- a/test/Combinatorics/runtests.jl +++ b/test/Combinatorics/runtests.jl @@ -6,3 +6,4 @@ include("Matroids/Matroids.jl") include("Matroids/Chow_Ring.jl") include("Graph.jl") include("Matroids/matroid_strata_grassmannian.jl") +include("OrderedMultiIndex.jl") diff --git a/test/Modules/ExteriorPowers.jl b/test/Modules/ExteriorPowers.jl new file mode 100644 index 000000000000..349fda9be1e5 --- /dev/null +++ b/test/Modules/ExteriorPowers.jl @@ -0,0 +1,180 @@ +@testset "exterior powers of modules" begin + R, (x, y, u, v, w) = QQ[:x, :y, :u, :v, :w] + F = FreeMod(R, 5) + Fwedge3, _ = Oscar.exterior_power(F, 3) + tmp, _ = Oscar.exterior_power(F, 3, cached=false) + @test tmp !== Fwedge3 + @test Fwedge3 === Oscar.exterior_power(F, 3)[1] + Fwedge1, _ = Oscar.exterior_power(F, 1) + Fwedge2, _ = Oscar.exterior_power(F, 2) + + success, orig_mod, q = Oscar.is_exterior_power(Fwedge3) + @test success + @test orig_mod === F + @test q == 3 + + v = sum(gens(R)[i]*F[i] for i in 1:5) + phi0 = Oscar.wedge_multiplication_map(exterior_power(F, 0)[1], F, v) + phi1 = Oscar.wedge_multiplication_map(F, exterior_power(F, 2)[1], v) + @test iszero(compose(phi0, phi1)) + @test image(phi0)[1] == kernel(phi1)[1] + phi2 = Oscar.wedge_multiplication_map(Fwedge1, Fwedge2, v) + phi2_alt = Oscar.wedge_multiplication_map(Oscar.exterior_power(F, 1, cached=false)[1], Oscar.exterior_power(F, 2, cached=false)[1], v) + @test domain(phi2) !== domain(phi2_alt) + @test codomain(phi2) !== codomain(phi2_alt) + phi3 = Oscar.wedge_multiplication_map(Fwedge2, Fwedge3, v) + @test !iszero(phi2) + @test !iszero(phi3) + img, _ = image(phi2) + ker, _ = kernel(phi3) + @test img == ker + @test iszero(compose(phi2, phi3)) + + @test Oscar.wedge([F[1], F[2], F[4]]) == - Oscar.wedge([F[2], F[1], F[4]]) == Oscar.wedge([F[4], F[1], F[2]]) + + K = koszul_complex(v) + @test all(i->iszero(homology(K, i)), 1:5) + + for i in 1:5 + @test iszero(koszul_homology(v, i)) + @test iszero(koszul_homology(v, F, i)) + end + + @test !iszero(koszul_homology(v, 0)) + @test !iszero(koszul_homology(v, F, 0)) +end + +@testset "exterior powers of graded modules" begin + R, (x, y, u, v, w) = QQ[:x, :y, :u, :v, :w] + S, (x, y, u, v, w) = grade(R) + F = graded_free_module(S, [1, 1, 1, 1, -2]) + Fwedge1, _ = Oscar.exterior_power(F, 1) + Fwedge2, _ = Oscar.exterior_power(F, 2) + Fwedge3, _ = Oscar.exterior_power(F, 3) + @test is_graded(Fwedge3) + + S1 = graded_free_module(S, 1) + I, inc_I = sub(S1, [f^3*S1[1] for f in gens(S)]) + + Oscar.koszul_dual(Fwedge2[3]) + + dual_basis = Oscar.koszul_duals(gens(Fwedge1)) + tmp = [Oscar.wedge(u, v) for (u, v) in zip(dual_basis, gens(Fwedge1))] + Fwedge5, _ = Oscar.exterior_power(F, 5) + @test all(x->x==Fwedge5[1], tmp) + + dual_basis = Oscar.koszul_duals(gens(Fwedge2)) + tmp = [Oscar.wedge(u, v) for (u, v) in zip(dual_basis, gens(Fwedge2))] + @test all(x->x==Fwedge5[1], tmp) +end + +@testset "induced maps on exterior powers" begin + R, (x, y, u, v, w) = QQ[:x, :y, :u, :v, :w] + + R5 = FreeMod(R, 5) + R4 = FreeMod(R, 4) + + A = R[x y u v; 7*y u v w; u 5*v w x; v w x y; w 4*x y u] + phi = hom(R5, R4, A) + + phi_2 = Oscar.induced_map_on_exterior_power(phi, 2) + + for ind in Oscar.OrderedMultiIndexSet(2, 5) + imgs = [phi(R5[i]) for i in Oscar.indices(ind)] + img = Oscar.wedge(imgs) + @test img == phi_2(domain(phi_2)[Oscar.linear_index(ind)]) + end + + phi_3 = Oscar.induced_map_on_exterior_power(phi, 3) + + A3 = matrix(phi_3) + for ind1 in Oscar.OrderedMultiIndexSet(3, 5) + for ind2 in Oscar.OrderedMultiIndexSet(3, 4) + @test A3[Oscar.linear_index(ind1), Oscar.linear_index(ind2)] == det(A[Oscar.indices(ind1), Oscar.indices(ind2)]) + end + end + + psi = hom(R4, R5, transpose(A)) + psi_2 = Oscar.induced_map_on_exterior_power(psi, 2) + @test compose(phi_2, psi_2) == Oscar.induced_map_on_exterior_power(compose(phi, psi), 2) + psi_3 = Oscar.induced_map_on_exterior_power(psi, 3) + @test compose(phi_3, psi_3) == Oscar.induced_map_on_exterior_power(compose(phi, psi), 3) + psi_3_alt = Oscar.induced_map_on_exterior_power(psi, 3, domain = Oscar.exterior_power(R4, 3, cached=false)[1], codomain = Oscar.exterior_power(R5, 3, cached=false)[1]) + @test matrix(psi_3) == matrix(psi_3_alt) + @test domain(psi_3) !== domain(psi_3_alt) + @test codomain(psi_3) !== codomain(psi_3_alt) + + psi_3_alt = hom(domain(psi_3), codomain(psi_3), psi) + @test psi_3_alt == psi_3 +end + +@testset "multiplication map" begin + R, (x, y, u, v, w) = QQ[:x, :y, :u, :v, :w] + + F = FreeMod(R, 5) + F3, mm = Oscar.exterior_power(F, 3) + v = (F[1], F[3], F[4]) + u = (F[1], F[4], F[3]) + @test mm(v) == -mm(u) + w = mm(v) + @test Oscar.wedge_pure_function(F3)(v) == w + @test Oscar.wedge_generator_decompose_function(F3)(w) == v + @test preimage(mm, w) == v +end + +@testset "printing" begin + R, (x, y, u, v, w) = QQ[:x, :y, :u, :v, :w] + + F = FreeMod(R, 5) + F3, mm = Oscar.exterior_power(F, 3) + v = (F[1], F[3], F[4]) + u = (F[1], F[4], F[3]) + + @test "$(mm(v))" == "e[1]∧e[3]∧e[4]" + @test "$(F3)" == "⋀^3(Free module of rank 5 over Multivariate polynomial ring in 5 variables over QQ)" + + eu = sum(f*e for (f, e) in zip(gens(R), gens(F))) + K = koszul_complex(eu) + for i in 1:5 + @test "$(K[i])" == "⋀^$(5-i)($F)" + end +end + +@testset "exterior powers of subquos" begin + R, (x, y, z, w) = QQ[:x, :y, :z, :w] + + M = R[x y z; y-1 z-2 w] + + I = ideal(R, minors(M, 2)) + + A, pr = quo(R, I) + + phi = hom(FreeMod(A, 2), FreeMod(A, 3), change_base_ring(A, M)) + + MM = cokernel(phi) + + MM2, mm2 = exterior_power(MM, 2) + MM1, mm1 = exterior_power(MM, 1) + MM0, mm0 = exterior_power(MM, 0) + MM3, mm3 = exterior_power(MM, 3) + + @test iszero(MM3) + + (u, v, w) = gens(MM1) + uv = wedge(u, v) + (U, V, W) = gens(MM) + @test uv == mm2((U, V)) + @test (U, V) == preimage(mm2, uv) + uv = wedge(u, v) + uw = wedge(u, w) + vw = wedge(v, w) + psi = hom(FreeMod(A, 3), MM2, [uv, uw, vw]) + @test !iszero(kernel(psi)[1]) + @test parent(wedge(u, v)) === MM2 + + d02 = Oscar.wedge_multiplication_map(MM0, MM2, uv + vw) + d01 = Oscar.wedge_multiplication_map(MM0, MM1, u + 2*v - w) + d12 = Oscar.wedge_multiplication_map(MM1, MM2, u + 2*v - w) + @test iszero(compose(d01, d12)) + @test iszero(wedge([u, v, w])) +end diff --git a/test/Modules/homological-algebra.jl b/test/Modules/homological-algebra.jl index d9f00d13f7b9..6c2bdd42f52f 100644 --- a/test/Modules/homological-algebra.jl +++ b/test/Modules/homological-algebra.jl @@ -33,17 +33,22 @@ end R, (x, y) = polynomial_ring(QQ, ["x", "y"]); V = gens(R) KM = koszul_matrix(V, 1) - @test nrows(KM) == 2 - @test KM[1] == R[1] + KMS = Oscar._koszul_matrix_from_singular(V, 1) + @test nrows(KM) == nrows(KMS) == 2 + @test ncols(KM) == ncols(KMS) + #@test KM[1] == R[1] # Custom test for the deprecated Singular code end @testset "mpoly_affine_homological-algebra.koszul_complex" begin R, (x, y) = polynomial_ring(QQ, ["x", "y"]); V = gens(R) K = koszul_complex(V) + KS = Oscar._koszul_complex_from_singular(V) KM = matrix(map(K, 2)) - @test ncols(KM) == 2 - @test KM[1, 1] == -R[2] + KMS = matrix(map(KS, 2)) + @test nrows(KM) == nrows(KMS) == 1 + @test ncols(KM) == ncols(KMS) + #@test KM[1, 1] == -R[2]# Custom test for the deprecated Singular code end @testset "mpoly_affine_homological-algebra.koszul_homology" begin @@ -53,7 +58,9 @@ end M = quo(F, U)[1] V = [x, x*z-z] @test is_zero(koszul_homology(V, M, 0)) == false + @test is_zero(Oscar._koszul_homology_from_singular(V, M, 0)) == false @test is_zero(koszul_homology(V, M, 1)) == true + @test is_zero(Oscar._koszul_homology_from_singular(V, M, 1)) == true end @testset "mpoly_affine_homological-algebra.depth" begin @@ -62,10 +69,40 @@ end U = matrix([x*y]) M = quo(F, U)[1] I = ideal(R, gens(R)) - @test depth(I, M) == 1 + @test depth(I, M) == Oscar._depth_from_singular(I, M) == 1 R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]); F = free_module(R, 1); I = ideal(R, [x*z-z, x*y-y, x]) - @test depth(I, F) == 3 + @test depth(I, F) == Oscar._depth_from_singular(I, F) == 3 +end + +@testset "singular vs oscar comparison" begin + n = 3 + R, _ = polynomial_ring(QQ, "x"=>1:n) + FR = FreeMod(R, 1) + IR = ideal(R, gens(R)) + IR = IR*IR + f = gens(IR) + K0 = koszul_complex(f) + K1 = koszul_complex(f, FR) + HK0 = [homology(K0, i) for i in 0:ngens(IR)] + HK1 = [homology(K1, i) for i in 0:ngens(IR)] + HK2 = [koszul_homology(f, FR, i) for i in 0:ngens(IR)] + @test iszero.(HK1) == iszero.(HK2) == iszero.(HK0) +end + +@testset "generic depth routine" begin + P, _ = QQ[:u, :v] + R, (x, y) = polynomial_ring(P, ["x", "y"]); + F = free_module(R, 1) + U = matrix([x*y]) + M = quo(F, U)[1] + I = ideal(R, gens(R)) + @test depth(I, M) == 1 + + R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]); + F = free_module(R, 1); + I = ideal(R, [x*z-z, x*y-y, x]) + @test depth(I, F) == 3 end diff --git a/test/Modules/runtests.jl b/test/Modules/runtests.jl index 2584fb970b49..371da7a1fb20 100644 --- a/test/Modules/runtests.jl +++ b/test/Modules/runtests.jl @@ -10,3 +10,4 @@ include("local_rings.jl") include("MPolyQuo.jl") include("homological-algebra.jl") include("ProjectiveModules.jl") +include("ExteriorPowers.jl")