Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cartan eilenberg resolutions #4248

Merged
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]

Check warning on line 97 in experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl

View check run for this annotation

Codecov / codecov/patch

experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl#L96-L97

Added lines #L96 - L97 were not covered by tests
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]

Check warning on line 104 in experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl

View check run for this annotation

Codecov / codecov/patch

experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl#L104

Added line #L104 was not covered by tests
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)

Check warning on line 139 in experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl

View check run for this annotation

Codecov / codecov/patch

experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl#L138-L139

Added lines #L138 - L139 were not covered by tests
end
error("execution should never reach this point")

Check warning on line 141 in experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl

View check run for this annotation

Codecov / codecov/patch

experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl#L141

Added line #L141 was not covered by tests
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)

Check warning on line 158 in experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl

View check run for this annotation

Codecov / codecov/patch

experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl#L156-L158

Added lines #L156 - L158 were not covered by tests
end
error("execution should never reach this point")

Check warning on line 160 in experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl

View check run for this annotation

Codecov / codecov/patch

experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl#L160

Added line #L160 was not covered by tests
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)

Check warning on line 167 in experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl

View check run for this annotation

Codecov / codecov/patch

experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl#L163-L167

Added lines #L163 - L167 were not covered by tests
else
pr = canonical_projection(dom, 1)
return pr

Check warning on line 170 in experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl

View check run for this annotation

Codecov / codecov/patch

experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl#L169-L170

Added lines #L169 - L170 were not covered by tests
end
error("execution should never reach this point")

Check warning on line 172 in experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl

View check run for this annotation

Codecov / codecov/patch

experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl#L172

Added line #L172 was not covered by tests
end
error("execution should never reach this point")

Check warning on line 174 in experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl

View check run for this annotation

Codecov / codecov/patch

experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl#L174

Added line #L174 was not covered by tests
end
error("direction $p out of bounds")

Check warning on line 176 in experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl

View check run for this annotation

Codecov / codecov/patch

experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl#L176

Added line #L176 was not covered by tests
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)

Check warning on line 184 in experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl

View check run for this annotation

Codecov / codecov/patch

experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl#L179-L184

Added lines #L179 - L184 were not covered by tests
end
return false

Check warning on line 186 in experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl

View check run for this annotation

Codecov / codecov/patch

experimental/DoubleAndHyperComplexes/src/Objects/cartan_eilenberg_resolution.jl#L186

Added line #L186 was not covered by tests
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)
Copy link
Member

Choose a reason for hiding this comment

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

What is this check argument for? I am asking because it is not used in the function body. Is it just for consistency with other module types?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's for compatibility with the other signatures, yes.

@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
Loading