Skip to content

Commit

Permalink
Cartan eilenberg resolutions (#4248)
Browse files Browse the repository at this point in the history
  • Loading branch information
HechtiDerLachs authored Nov 26, 2024
1 parent f849adc commit ea8bd99
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ include("Objects/new_complex_template.jl")
include("Objects/linear_strands.jl")

include("Morphisms/Types.jl")
include("Objects/cartan_eilenberg_resolution.jl")
include("Morphisms/cartan_eilenberg_resolutions.jl")
include("Morphisms/ext.jl")
include("Morphisms/simplified_complexes.jl")
Expand All @@ -25,3 +26,4 @@ include("Exports.jl")

include("base_change_types.jl")
include("base_change.jl")

20 changes: 20 additions & 0 deletions experimental/DoubleAndHyperComplexes/src/Morphisms/Types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,23 @@ end
underlying_complex(c::SimpleFreeResolution) = c.underlying_complex
original_module(c::SimpleFreeResolution) = c.M

### Lifting morphisms through projective resolutions
mutable struct MapLifter{MorphismType} <: HyperComplexMorphismFactory{MorphismType}
dom::AbsHyperComplex
cod::AbsHyperComplex
orig_map::Map
start_index::Int
offset::Int
check::Bool

function MapLifter(::Type{MorphismType}, dom::AbsHyperComplex, cod::AbsHyperComplex, phi::Map;
start_index::Int=0, offset::Int=0, check::Bool=true
) where {MorphismType}
@assert dim(dom) == 1 && dim(cod) == 1 "lifting of maps is only implemented in dimension one"
@assert (is_chain_complex(dom) && is_chain_complex(cod)) || (is_cochain_complex(dom) && is_cochain_complex(cod))
@assert domain(phi) === dom[start_index]
@assert codomain(phi) === cod[start_index + offset]
return new{MorphismType}(dom, cod, phi, start_index, offset)
end
end

Original file line number Diff line number Diff line change
@@ -1,22 +1,4 @@
mutable struct MapLifter{MorphismType} <: HyperComplexMorphismFactory{MorphismType}
dom::AbsHyperComplex
cod::AbsHyperComplex
orig_map::Map
start_index::Int
offset::Int
check::Bool

function MapLifter(::Type{MorphismType}, dom::AbsHyperComplex, cod::AbsHyperComplex, phi::Map;
start_index::Int=0, offset::Int=0, check::Bool=true
) where {MorphismType}
@assert dim(dom) == 1 && dim(cod) == 1 "lifting of maps is only implemented in dimension one"
@assert (is_chain_complex(dom) && is_chain_complex(cod)) || (is_cochain_complex(dom) && is_cochain_complex(cod))
@assert domain(phi) === dom[start_index]
@assert codomain(phi) === cod[start_index + offset]
return new{MorphismType}(dom, cod, phi, start_index, offset)
end
end

### Lifting maps through projective resolutions
is_chain_complex(c::AbsHyperComplex) = (dim(c) == 1 ? direction(c, 1) == (:chain) : error("complex is not one-dimensional"))
is_cochain_complex(c::AbsHyperComplex) = (dim(c) == 1 ? direction(c, 1) == (:cochain) : error("complex is not one-dimensional"))

Expand Down Expand Up @@ -57,3 +39,6 @@ function lift_map(dom::AbsHyperComplex{DomChainType}, cod::AbsHyperComplex{CodCh
end

morphism_type(::Type{T1}, ::Type{T2}) where {T1<:ModuleFP, T2<:ModuleFP} = ModuleFPHom{<:T1, <:T2}

### Cartan Eilenberg resolutions

2 changes: 1 addition & 1 deletion experimental/DoubleAndHyperComplexes/src/Objects/Types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ end
upper_bounds::Vector=[nothing for i in 1:d],
lower_bounds::Vector=[nothing for i in 1:d]
) where {ChainType, MorphismType}
@assert d > 0 "can not create zero or negative dimensional hypercomplex"
@assert d >= 0 "can not create negative dimensional hypercomplex"
chains = Dict{Tuple, ChainType}()
morphisms = Dict{Tuple, Dict{Int, <:MorphismType}}()
return new{ChainType, MorphismType}(d, chains, morphisms,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#= Cartan Eilenberg resolutions of 1-dimensional complexes
#
# Suppose
#
# 0 ← C₀ ← C₁ ← C₂ ← …
#
# is a bounded below complex. We compute a double complex
#
# 0 0 0
# ↑ ↑ ↑
# 0 ← P₀₀ ← P₀₁ ← P₀₂ ← …
# ↑ ↑ ↑
# 0 ← P₁₀ ← P₁₁ ← P₁₂ ← …
# ↑ ↑ ↑
# 0 ← P₂₀ ← P₂₁ ← P₂₂ ← …
# ↑ ↑ ↑
# ⋮ ⋮ ⋮
#
# whose total complex is quasi-isomorphic to C via some augmentation map
#
# ε = (εᵢ : P₀ᵢ → Cᵢ)ᵢ
#
# The challenge is that if we were only computing resolutions of the Cᵢ's
# and lifting the maps, then the rows of the resulting diagrams would
# not necessarily form complexes. To accomplish that, we split the original
# complex into short exact sequences
#
# 0 ← Bᵢ ← Cᵢ ← Zᵢ ← 0
#
# and apply the Horse shoe lemma to these. Together with the induced maps
# from Bᵢ ↪ Zᵢ₋₁ we get the desired double complex.
#
# If the original complex C is known to be exact, then there is no need
# to compute the resolutions of both Bᵢ and Zᵢ and we can shorten the procedure.
=#
### Production of the chains
struct CEChainFactory{ChainType} <: HyperComplexChainFactory{ChainType}
c::AbsHyperComplex
is_exact::Bool
kernel_resolutions::Dict{Int, <:AbsHyperComplex} # the kernels of Cᵢ → Cᵢ₋₁
boundary_resolutions::Dict{Int, <:AbsHyperComplex} # the boundaries of Cᵢ₊₁ → Cᵢ
induced_maps::Dict{Int, <:AbsHyperComplexMorphism} # the induced maps from the free
# resolutions of the boundary and kernel

function CEChainFactory(c::AbsHyperComplex; is_exact::Bool=false)
@assert dim(c) == 1 "complex must be 1-dimensional"
#@assert has_lower_bound(c, 1) "complex must be bounded from below"
return new{chain_type(c)}(c, is_exact, Dict{Int, AbsHyperComplex}(), Dict{Int, AbsHyperComplex}(), Dict{Int, AbsHyperComplexMorphism}())
end
end

function kernel_resolution(fac::CEChainFactory, i::Int)
if !haskey(fac.kernel_resolutions, i)
Z, _ = kernel(fac.c, i)
fac.kernel_resolutions[i] = free_resolution(SimpleFreeResolution, Z)[1]
end
return fac.kernel_resolutions[i]
end

function boundary_resolution(fac::CEChainFactory, i::Int)
if !haskey(fac.boundary_resolutions, i)
Z, _ = boundary(fac.c, i)
fac.boundary_resolutions[i] = free_resolution(SimpleFreeResolution, Z)[1]
end
return fac.boundary_resolutions[i]
end

function induced_map(fac::CEChainFactory, i::Int)
if !haskey(fac.induced_maps, i)
Z, inc = kernel(fac.c, i)
B, pr = boundary(fac.c, i)
@assert ambient_free_module(Z) === ambient_free_module(B)
img_gens = elem_type(Z)[Z(g) for g in ambient_representatives_generators(B)]
res_Z = kernel_resolution(fac, i)
res_B = boundary_resolution(fac, i)
aug_Z = augmentation_map(res_Z)
aug_B = augmentation_map(res_B)
img_gens = gens(res_B[0])
img_gens = aug_B[0].(img_gens)
img_gens = elem_type(res_Z[0])[preimage(aug_Z[0], Z(repres(aug_B[0](g)))) for g in gens(res_B[0])]
psi = hom(res_B[0], res_Z[0], img_gens; check=true) # TODO: Set to false
@assert domain(psi) === boundary_resolution(fac, i)[0]
@assert codomain(psi) === kernel_resolution(fac, i)[0]
fac.induced_maps[i] = lift_map(boundary_resolution(fac, i), kernel_resolution(fac, i), psi; start_index=0)
end
return fac.induced_maps[i]
end

function (fac::CEChainFactory)(self::AbsHyperComplex, I::Tuple)
(i, j) = I # i the resolution index, j the index in C

res_Z = kernel_resolution(fac, j)

if can_compute_map(fac.c, 1, (j,))
if fac.is_exact # Use the next kernel directly
res_B = kernel_resolution(fac, j-1)
return direct_sum(res_B[i], res_Z[i])[1]
else
res_B = boundary_resolution(fac, j-1)
return direct_sum(res_B[i], res_Z[i])[1]
end
end
# We may assume that the next map can not be computed and is, hence, zero.
return res_Z[i]
end

function can_compute(fac::CEChainFactory, self::AbsHyperComplex, I::Tuple)
(i, j) = I
can_compute_index(fac.c, (j,)) || return false
return i >= 0
end

### Production of the morphisms
struct CEMapFactory{MorphismType} <: HyperComplexMapFactory{MorphismType} end

function (fac::CEMapFactory)(self::AbsHyperComplex, p::Int, I::Tuple)
(i, j) = I
cfac = chain_factory(self)
if p == 1 # vertical upwards maps
if can_compute_map(cfac.c, 1, (j,))
# both dom and cod are direct sums in this case
dom = self[I]
cod = self[(i-1, j)]
pr1 = canonical_projection(dom, 1)
pr2 = canonical_projection(dom, 2)
@assert domain(pr1) === domain(pr2) === dom
inc1 = canonical_injection(cod, 1)
inc2 = canonical_injection(cod, 2)
@assert codomain(inc1) === codomain(inc2) === cod
res_Z = kernel_resolution(cfac, j)
@assert domain(map(res_Z, i)) === codomain(pr2)
@assert codomain(map(res_Z, i)) === domain(inc2)
res_B = boundary_resolution(cfac, j-1)
@assert domain(map(res_B, i)) === codomain(pr1)
@assert codomain(map(res_B, i)) === domain(inc1)
return compose(pr1, compose(map(res_B, i), inc1)) + compose(pr2, compose(map(res_Z, i), inc2))
else
res_Z = kernel_resolution(cfac, j)
return map(res_Z, i)
end
error("execution should never reach this point")
elseif p == 2 # the horizontal maps
dom = self[I]
cod = self[(i, j-1)]
if can_compute_map(cfac.c, 1, (j-1,))
# the codomain is also a direct sum
if !cfac.is_exact
psi = induced_map(cfac, j-1)
phi = psi[i]
inc = canonical_injection(cod, 2)
pr = canonical_projection(dom, 1)
@assert codomain(phi) === domain(inc)
@assert codomain(pr) === domain(phi)
return compose(pr, compose(phi, inc))
else
inc = canonical_injection(cod, 2)
pr = canonical_projection(dom, 1)
return compose(pr, inc)
end
error("execution should never reach this point")
else
# the codomain is just the kernel
if !cfac.is_exact
psi = induced_map(cfac, j-1)
phi = psi[i]
pr = canonical_projection(dom, 1)
return compose(pr, phi)
else
pr = canonical_projection(dom, 1)
return pr
end
error("execution should never reach this point")
end
error("execution should never reach this point")
end
error("direction $p out of bounds")
end

function can_compute(fac::CEMapFactory, self::AbsHyperComplex, p::Int, I::Tuple)
(i, j) = I
if p == 1 # vertical maps
return i > 0 && can_compute(chain_factory(self).c, j)
elseif p == 2 # horizontal maps
return i >= 0 && can_compute_map(chain_factory(self).c, j)
end
return false
end

### The concrete struct
@attributes mutable struct CartanEilenbergResolution{ChainType, MorphismType} <: AbsHyperComplex{ChainType, MorphismType}
internal_complex::HyperComplex{ChainType, MorphismType}

function CartanEilenbergResolution(
c::AbsHyperComplex{ChainType, MorphismType};
is_exact::Bool=false
) where {ChainType, MorphismType}
@assert dim(c) == 1 "complexes must be 1-dimensional"
@assert has_lower_bound(c, 1) "complexes must be bounded from below"
@assert direction(c, 1) == :chain "resolutions are only implemented for chain complexes"
chain_fac = CEChainFactory(c; is_exact)
map_fac = CEMapFactory{MorphismType}() # TODO: Do proper type inference here!

# Assuming d is the dimension of the new complex
internal_complex = HyperComplex(2, chain_fac, map_fac, [:chain, :chain]; lower_bounds = Union{Int, Nothing}[0, lower_bound(c, 1)])
# Assuming that ChainType and MorphismType are provided by the input
return new{ChainType, MorphismType}(internal_complex)
end
end

### Implementing the AbsHyperComplex interface via `underlying_complex`
underlying_complex(c::CartanEilenbergResolution) = c.internal_complex

Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ end
map_fac = BaseChangeMapFactory(phi, orig)

d = dim(orig)
internal_complex = HyperComplex(d, chain_fac, map_fac, [direction(orig, i) for i in 1:d])
upper_bounds = Vector{Union{Int, Nothing}}([(has_upper_bound(orig, i) ? upper_bound(orig, i) : nothing) for i in 1:d])
lower_bounds = Vector{Union{Int, Nothing}}([(has_lower_bound(orig, i) ? lower_bound(orig, i) : nothing) for i in 1:d])
internal_complex = HyperComplex(d, chain_fac, map_fac,
[direction(orig, i) for i in 1:d],
lower_bounds = lower_bounds,
upper_bounds = upper_bounds
)
return new{ModuleFP, ModuleFPHom}(orig, internal_complex)
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@testset "Cartan-Eilenberg resolutions" begin
R, (x, y, z, w) = QQ[:x, :y, :z, :w]

A = R[x y z; y z w]

R1 = free_module(R, 1)
I, inc = sub(R1, [a*R1[1] for a in minors(A, 2)])
M = cokernel(inc)
R4 = free_module(R, 4)
theta = sum(a*g for (a, g) in zip(gens(R), gens(R4)); init=zero(R4))
K = koszul_complex(Oscar.KoszulComplex, theta)

comp = tensor_product(K, Oscar.ZeroDimensionalComplex(M))
res = Oscar.CartanEilenbergResolution(comp);
tot = total_complex(res);
tot_simp = simplify(tot);

res_M, _ = free_resolution(Oscar.SimpleFreeResolution, M)
comp2 = tensor_product(K, res_M)
tot2 = total_complex(comp2)
tot_simp2 = simplify(tot2);

@test [ngens(tot_simp[i]) for i in 0:5] == [ngens(tot_simp2[i]) for i in 0:5]
end

1 change: 1 addition & 0 deletions experimental/DoubleAndHyperComplexes/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ include("linear_strands.jl")
include("printing.jl")
include("degree_zero_complex.jl")
include("base_change.jl")
include("cartan_eilenberg_resolutions.jl")
6 changes: 3 additions & 3 deletions src/Modules/ModulesGraded.jl
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ function set_grading!(M::FreeMod, W::Vector{<:IntegerUnion})
M.d = [W[i] * A[1] for i in 1:length(W)]
end

function degrees(M::FreeMod)
function degrees(M::FreeMod; check::Bool=true)
@assert is_graded(M)
return M.d::Vector{FinGenAbGroupElem}
end
Expand All @@ -437,8 +437,8 @@ julia> degrees_of_generators(F)
[0]
```
"""
function degrees_of_generators(F::FreeMod)
return degrees(F)
function degrees_of_generators(F::FreeMod; check::Bool=true)
return degrees(F; check)
end

###############################################################################
Expand Down

0 comments on commit ea8bd99

Please sign in to comment.