Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0567d28
Allow a function to generate symbols lazily.
HechtiDerLachs Jun 4, 2025
9b95238
Update the other files accordingly to not access the field directly.
HechtiDerLachs Jun 4, 2025
397f439
Use symbols instead of accessing fields.
HechtiDerLachs Jun 4, 2025
b3ec640
Use symbols again for comparison of free modules.
HechtiDerLachs Jun 4, 2025
f7d7fb6
Fix tests.
HechtiDerLachs Jun 4, 2025
2fc3d50
Catch some more direct accesses.
HechtiDerLachs Jun 4, 2025
b10ca98
Use inner constructor.
HechtiDerLachs Jun 5, 2025
8304f32
Support is_known(is_zero, M) for modules.
HechtiDerLachs May 28, 2025
418b15d
Make is_zero an attribute for modules.
HechtiDerLachs May 28, 2025
55106b1
Add some type stability in the modules.
HechtiDerLachs May 21, 2025
101803b
Rewrap some type assertion.
HechtiDerLachs May 20, 2025
cdf82cb
Cache sparse transformation matrix.
HechtiDerLachs May 20, 2025
c501584
Avoid hashing of orderings for lift_std.
HechtiDerLachs May 15, 2025
eefd449
Make nothing default for ordering to avoid comparisons.
HechtiDerLachs May 12, 2025
137fddf
Make the image module an attribute.
HechtiDerLachs May 9, 2025
d566883
Fix up flattenings?
HechtiDerLachs May 2, 2025
a93e861
Repair ordering of symbols for tensor products.
HechtiDerLachs Jun 5, 2025
bfc9468
More lazy symbols.
HechtiDerLachs Jun 5, 2025
49ad128
Apply Max's suggestions.
HechtiDerLachs Jun 6, 2025
3e71ef2
Fix bracket.
HechtiDerLachs Jun 11, 2025
cc20a07
Put return statement in correct place.
HechtiDerLachs Jun 11, 2025
f326813
Add some more explanatory comments for the lazy symbols.
HechtiDerLachs Jun 20, 2025
41561b2
Put commented code back into place.
HechtiDerLachs Jun 20, 2025
3f467d5
Introduce extra fields for wild-card gbs.
HechtiDerLachs Jun 25, 2025
c7b7551
Delete the use of nothing.
HechtiDerLachs Jun 25, 2025
2147be4
Dont use default_ordering on ModuleGens.
HechtiDerLachs Jun 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions src/Modules/ExteriorPowers/FreeModules.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,29 +65,27 @@ function exterior_power(F::FreeMod, p::Int; cached::Bool=true)

# Set the variable names for printing
orig_symb = String.(symbols(F))
new_symb = Symbol[]
if iszero(p)
new_symb = [Symbol("1")]
result.S = [Symbol("1")]
else
for ind in combinations(n, p)
symb_str = orig_symb[ind[1]]
for i in 2:p
symb_str = symb_str * (is_unicode_allowed() ? "∧" : "^") * orig_symb[ind[i]]
result.S = function _get_koszul_symbols()
new_symb = Symbol[]
for ind in combinations(n, p)
symb_str = orig_symb[ind[1]]
for i in 2:p
symb_str = symb_str * (is_unicode_allowed() ? "∧" : "^") * orig_symb[ind[i]]
end
push!(new_symb, Symbol(symb_str))
end
push!(new_symb, Symbol(symb_str))
return new_symb
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
Expand Down
4 changes: 3 additions & 1 deletion src/Modules/ExteriorPowers/SubQuo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ function exterior_power(M::SubquoModule, p::Int; cached::Bool=true)
else
C = presentation(M)
phi = map(C, 1)
codomain(phi).S = Symbol.(["$e" for e in gens(M)])
codomain(phi).S = function _get_symbol()
return [Symbol("$e") for e in gens(M)]
end
result, mm = _exterior_power(phi, p)
end

Expand Down
2 changes: 1 addition & 1 deletion src/Modules/FreeModElem-orderings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ end
function expressify(a::OscarPair{<:FreeModElem{<:MPolyRingElem}, Vector{Tuple{Int, Int}}}; context = nothing)
f = a.first
x = symbols(base_ring(parent(f)))
e = generator_symbols(parent(f))
e = symbols(parent(f))
s = Expr(:call, :+)
for (i, j) in a.second
prod = Expr(:call, :*)
Expand Down
24 changes: 18 additions & 6 deletions src/Modules/ModuleTypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ option is set in suitable functions.
@attributes mutable struct FreeMod{T <: AdmissibleModuleFPRingElem} <: AbstractFreeMod{T}
R::NCRing
n::Int
S::Vector{Symbol}
S::Union{Function, Vector{Symbol}} # The symbols for printing. This is either a
# ready-made list, or a function to provide them
# in a lazy way.
d::Union{Vector{FinGenAbGroupElem}, Nothing}
default_ordering::ModuleOrdering

Expand Down Expand Up @@ -110,6 +112,14 @@ option is set in suitable functions.
r.outgoing = WeakKeyIdDict{ModuleFP, Tuple{SMat, Any}}()
return r
end

function FreeMod{T}(n::Int, R::AdmissibleModuleFPRing, symbol_fun::Function) where T <: AdmissibleModuleFPRingElem
r = new{elem_type(R)}(R, n, symbol_fun, nothing)

r.incoming = WeakKeyIdDict{ModuleFP, Tuple{SMat, Any}}()
r.outgoing = WeakKeyIdDict{ModuleFP, Tuple{SMat, Any}}()
return r
end
end

@doc raw"""
Expand Down Expand Up @@ -215,18 +225,20 @@ generate the submodule) (computed via `generator_matrix()`) are cached.
"""
@attributes mutable struct SubModuleOfFreeModule{T <: AdmissibleModuleFPRingElem} <: ModuleFP{T}
F::FreeMod{T}
gens::ModuleGens{T}
groebner_basis::Dict{ModuleOrdering, ModuleGens{T}}
gens::ModuleGens{T}
default_ordering::ModuleOrdering
any_gb::ModuleGens{T} # A field to store the first groebner basis ever computed.
# Lookups in the above dictionary is tentatively expensive.
# So this field stores any gb for cases where the actual
# ordering does not matter. Then this field here can be used.
any_gb_with_transition::ModuleGens{T} # The same but for one with transition matrix
matrix::MatElem
is_graded::Bool

function SubModuleOfFreeModule{R}(F::FreeMod{R}) where {R}
# this does not construct a valid SubModuleOfFreeModule
r = new{R}()
r.F = F
r.groebner_basis = Dict()
return r
return new{R}(F, Dict{ModuleOrdering, ModuleGens{R}}())
end
end

Expand Down
8 changes: 4 additions & 4 deletions src/Modules/ModulesGraded.jl
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@
# Graded Free Module homomorphisms functions
###############################################################################

function set_grading(f::FreeModuleHom{T1, T2}; check::Bool=true) where {T1 <: FreeMod, T2 <: Union{FreeMod, SubquoModule, Oscar.SubModuleOfFreeModule}}
function set_grading(f::FreeModuleHom{T1, T2}; check::Bool=true) where {T1 <: FreeMod, T2 <: Union{FreeMod, SubquoModule, SubModuleOfFreeModule}}
if !is_graded(domain(f)) || !is_graded(codomain(f))
return f
end
Expand Down Expand Up @@ -2291,12 +2291,12 @@


@doc raw"""
generator_symbols(F::FreeMod_dec)
symbols(F::FreeMod_dec)

Return the list of symbols of the standard unit vectors.
"""
function generator_symbols(F::FreeMod_dec)
return generator_symbols(forget_decoration(F))
function symbols(F::FreeMod_dec)

Check warning on line 2298 in src/Modules/ModulesGraded.jl

View check run for this annotation

Codecov / codecov/patch

src/Modules/ModulesGraded.jl#L2298

Added line #L2298 was not covered by tests
return symbols(forget_decoration(F))
end
@enable_all_show_via_expressify FreeModElem_dec

Expand Down
6 changes: 4 additions & 2 deletions src/Modules/UngradedModules/DirectSum.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ function direct_product(F::Vector{<:FreeMod{T}}; task::Symbol = :prod) where T
push!(ranges, i+1:j)
i = j
end
G.S = vcat([Symbol[Symbol("("*join(vcat(["0" for k in 1:j-1],
[string(F[j].S[i])],
G.S = function _direct_sum_symbols()
return vcat([Symbol[Symbol("("*join(vcat(["0" for k in 1:j-1],
[string(symbol(F[j], i))],
["0" for k in j+1:length(F)]), ", ")
*")") for i in 1:ngens(F[j])] for j in 1:length(F)]...)
end
set_attribute!(G, :projection_morphisms => projection_dictionary, :injection_morphisms => injection_dictionary, :ranges => ranges)
i = 0
for f = F
Expand Down
38 changes: 35 additions & 3 deletions src/Modules/UngradedModules/FreeMod.jl
Original file line number Diff line number Diff line change
Expand Up @@ -207,16 +207,16 @@
# TODO it this enough or e.g. stored morphisms also be considered?
is_graded(F) == is_graded(G) || return false
if is_graded(F) && is_graded(G)
return F.R == G.R && F.d == G.d && F.S == G.S
return F.R == G.R && F.d == G.d && symbols(F) == symbols(G)
end
return F.R == G.R && rank(F) == rank(G) && F.S == G.S
return F.R == G.R && rank(F) == rank(G) && symbols(F) == symbols(G)
end

function hash(F::FreeMod, h::UInt)
b = is_graded(F) ? (0x2d55d561d3f7e215 % UInt) : (0x62ca4181ff3a12f4 % UInt)
h = hash(base_ring(F), h)
h = hash(rank(F), h)
h = hash(F.S, h)
h = hash(symbols(F), h)
is_graded(F) && (h = hash(F.d, h))
return xor(h, b)
end
Expand Down Expand Up @@ -404,3 +404,35 @@
return elem_type(F)[F(s[i]) for i=1:Singular.ngens(s)]
end

# access to the symbols for printing of `F`
#
# Creating the symbols turns out to be rather expensive in some edge cases; for
# instance for direct products with many (>1000) summands. Therefore, we lazyfied
# this process and a given `FreeMod` will usually not store the symbols themselves
# in its field `.S`, but a function to compute them.
#
# If the user needs the concrete symbols, then a lookup is done: Do we have them already?
# Or do we need to compute them? This lookup is decided by dispatch on the contents of
# the field `.S` with the internal functions below. If the field is filled with a list
# of symbols, then these are returned. If not, then the function stored in `.S` is
# called and the result stored in `.S`, instead.
function symbols(F::FreeMod)
return _get_symbols!(F, F.S)
end

function symbol(F::FreeMod, i::Int)
return symbols(F)[i]
end

function _get_symbols!(F::FreeMod, S::Vector{Symbol})
return S
end

function _get_symbols!(F::FreeMod, symbol_fun::Function)
F.S = symbol_fun()
return F.S::Vector{Symbol}
end

# an alias for backwards compatibility
generator_symbols(F::FreeMod) = symbols(F)

Check warning on line 437 in src/Modules/UngradedModules/FreeMod.jl

View check run for this annotation

Codecov / codecov/patch

src/Modules/UngradedModules/FreeMod.jl#L437

Added line #L437 was not covered by tests

8 changes: 2 additions & 6 deletions src/Modules/UngradedModules/FreeModElem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,11 @@ end
elem_type(::Type{FreeMod{T}}) where {T} = FreeModElem{T}
parent_type(::Type{FreeModElem{T}}) where {T} = FreeMod{T}

function generator_symbols(F::FreeMod)
return F.S
end

function expressify(e::AbstractFreeModElem; context = nothing)
sum = Expr(:call, :+)
for (pos, val) in coordinates(e)
# assuming generator_symbols(parent(e)) is an array of strings/symbols
push!(sum.args, Expr(:call, :*, expressify(val, context = context), generator_symbols(parent(e))[pos]))
# assuming symbols(parent(e)) is an array of strings/symbols
push!(sum.args, Expr(:call, :*, expressify(val, context = context), symbols(parent(e))[pos]))
end
return sum
end
Expand Down
6 changes: 4 additions & 2 deletions src/Modules/UngradedModules/FreeModuleHom.jl
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,9 @@ function hom(F::FreeMod, G::FreeMod)
else
GH = FreeMod(F.R, rank(F) * rank(G))
end
GH.S = [Symbol("($i -> $j)") for i = F.S for j = G.S]
GH.S = function _get_hom_symbols()
return [Symbol("($i -> $j)") for i = symbols(F) for j = symbols(G)]
end

#list is g1 - f1, g2-f1, g3-f1, ...
X = Hecke.MapParent(F, G, "homomorphisms")
Expand Down Expand Up @@ -582,7 +584,7 @@ represented as subquotient with no relations -> G)

```
"""
function image(h::FreeModuleHom)
@attr Tuple{<:SubquoModule, <:SubQuoHom} function image(h::FreeModuleHom)
si = filter(!iszero, images_of_generators(h))
s = sub_object(codomain(h), si)
phi = hom(s, codomain(h), si, check=false)
Expand Down
4 changes: 2 additions & 2 deletions src/Modules/UngradedModules/Methods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ end
function change_base_ring(S::Ring, F::FreeMod)
R = base_ring(F)
r = ngens(F)
FS = is_graded(F) ? graded_free_module(S, degrees_of_generators(F)) : FreeMod(S, F.S) # the symbols of F
FS = is_graded(F) ? graded_free_module(S, degrees_of_generators(F)) : FreeMod{elem_type(S)}(ngens(F), S, F.S) # the symbols of F
map = hom(F, FS, gens(FS), MapFromFunc(R, S, S))
return FS, map
end
Expand All @@ -400,7 +400,7 @@ function change_base_ring(f::Map{DomType, CodType}, F::FreeMod) where {DomType<:
domain(f) == base_ring(F) || error("ring map not compatible with the module")
S = codomain(f)
r = ngens(F)
FS = is_graded(F) ? graded_free_module(S, degrees_of_generators(F)) : FreeMod(S, F.S) # the symbols of F
FS = is_graded(F) ? graded_free_module(S, degrees_of_generators(F)) : FreeMod{elem_type(S)}(ngens(F), S, F.S)
map = hom(F, FS, gens(FS), f)
return FS, map
end
Expand Down
12 changes: 7 additions & 5 deletions src/Modules/UngradedModules/ModuleGens.jl
Original file line number Diff line number Diff line change
Expand Up @@ -370,11 +370,13 @@
If no such `r` exists, an exception is thrown.
"""
function coordinates_via_transform(a::FreeModElem{T}, generators::ModuleGens{T}) where T
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)))
iszero(a) && return sparse_row(base_ring(parent(a)))
SA = get_attribute!(generators, :sparse_transformation_matrix) do
A = get_attribute(generators, :transformation_matrix)

Check warning on line 375 in src/Modules/UngradedModules/ModuleGens.jl

View check run for this annotation

Codecov / codecov/patch

src/Modules/UngradedModules/ModuleGens.jl#L375

Added line #L375 was not covered by tests
A === nothing && error("No transformation matrix in the Gröbner basis.")
sparse_matrix(A)
end
Comment on lines +374 to 378
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hannes14 : This is a temporary workaround to do things with sparse matrices for the lifting. Did you already look into providing methods to get a sparse data structure from Singular directly? Then I could adapt this PR to use that one directly.


@assert generators.isGB
if base_ring(generators) isa Union{MPolyQuoRing,MPolyRing}
if !is_global(generators.ordering)
Expand All @@ -392,7 +394,7 @@
Rx = base_ring(generators)
coords_wrt_groebner_basis = sparse_row(Rx, s[1], 1:ngens(generators))

return coords_wrt_groebner_basis * sparse_matrix(A)
return coords_wrt_groebner_basis * SA
end

@doc raw"""
Expand Down
55 changes: 34 additions & 21 deletions src/Modules/UngradedModules/SubModuleOfFreeModule.jl
Original file line number Diff line number Diff line change
Expand Up @@ -167,18 +167,26 @@
Compute a standard basis of `submod` with respect to the given `ordering`.
The return type is `ModuleGens`.
"""
function standard_basis(submod::SubModuleOfFreeModule; ordering::Union{ModuleOrdering, Nothing} = default_ordering(submod))
# This is to circumvent hashing of the ordering in the obviously avoidable cases
if ordering===default_ordering(submod)
for (ord, gb) in submod.groebner_basis
ord === ordering && return gb
end
function standard_basis(
submod::SubModuleOfFreeModule;
ordering::ModuleOrdering = default_ordering(submod)
)
if isdefined(submod, :any_gb)
ordering === submod.any_gb.ordering && return submod.any_gb
end

@req is_exact_type(elem_type(base_ring(submod))) "This functionality is only supported over exact fields."
gb = get!(submod.groebner_basis, ordering) do
return compute_standard_basis(submod, ordering)
compute_standard_basis(submod, ordering)
end::ModuleGens

# Cache a newly computed groebner basis
if !isdefined(submod, :any_gb)
submod.any_gb = gb
end
if !isdefined(submod, :any_gb_with_transition) && !isnothing(get_attribute(gb, :transformation_matrix))
submod.any_gb_with_transition = gb

Check warning on line 188 in src/Modules/UngradedModules/SubModuleOfFreeModule.jl

View check run for this annotation

Codecov / codecov/patch

src/Modules/UngradedModules/SubModuleOfFreeModule.jl#L188

Added line #L188 was not covered by tests
end
return gb
end

Expand All @@ -202,12 +210,17 @@
@assert is_global(ordering)

gb = get!(submod.groebner_basis, ordering) do
return compute_standard_basis(submod, ordering, true)
end::ModuleGens
gb.is_reduced && return gb
return get_attribute!(gb, :reduced_groebner_basis) do
return compute_standard_basis(submod, ordering, true)
compute_standard_basis(submod, ordering, true)
end::ModuleGens
@assert gb.is_reduced
# Cache a newly computed groebner basis
if !isdefined(submod, :any_gb)
submod.any_gb = gb
end
if !isdefined(submod, :any_gb_with_transition) && !isnothing(get_attribute(gb, :transformation_matrix))
submod.any_gb_with_transition = gb

Check warning on line 221 in src/Modules/UngradedModules/SubModuleOfFreeModule.jl

View check run for this annotation

Codecov / codecov/patch

src/Modules/UngradedModules/SubModuleOfFreeModule.jl#L221

Added line #L221 was not covered by tests
end
return gb
end

function leading_module(submod::SubModuleOfFreeModule, ordering::ModuleOrdering = default_ordering(submod))
Expand Down Expand Up @@ -432,21 +445,21 @@
Base.:+(M::SubModuleOfFreeModule, N::SubModuleOfFreeModule) = sum(M, N)

function lift_std(M::SubModuleOfFreeModule)
if haskey(M.groebner_basis, default_ordering(M))
gb = M.groebner_basis[default_ordering(M)]
transform = get_attribute(gb, :transformation_matrix)
if transform !== nothing
return gb, transform
end
if isdefined(M, :any_gb_with_transition)
return M.any_gb_with_transition, get_attribute(M.any_gb_with_transition, :transformation_matrix)::MatrixElem
end
for gb in values(M.groebner_basis)

for (ord, gb) in M.groebner_basis
transform = get_attribute(gb, :transformation_matrix)
if transform !== nothing
return gb, transform
end
end
gb, transform = lift_std(M.gens, default_ordering(M))
M.groebner_basis[default_ordering(M)] = gb
if !isdefined(M, :any_gb_with_transition)
M.any_gb_with_transition = gb
end
return gb, transform
end

Expand Down Expand Up @@ -482,7 +495,7 @@

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))))
return iszero(reduce(a, standard_basis(M)))
end

@attr Any function solve_ctx(M::SubModuleOfFreeModule)
Expand Down
2 changes: 1 addition & 1 deletion src/Modules/UngradedModules/SubQuoHom.jl
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ function kernel(h::SubQuoHom)
@assert domain(inc_K) === K
@assert codomain(inc_K) === F
v = gens(D)
imgs = Vector{elem_type(D)}(filter(!iszero, [sum(a*v[i] for (i, a) in coordinates(g); init=zero(D)) for g in images_of_generators(inc_K)]))
imgs = filter!(!iszero, elem_type(D)[sum(a*v[i] for (i, a) in coordinates(g); init=zero(D)) for g in images_of_generators(inc_K)])
k = sub_object(D, imgs)
return k, hom(k, D, imgs, check=false)
end
Expand Down
Loading
Loading