Advice for the programmer
How to implement my custom double complex?
The implementation for Double complexes is generically lazy. We provide a concrete type which takes care of handling the user's requests to entries and morphisms and their caching: DoubleComplexOfMorphisms
.
In order to work properly, any DoubleComplexOfMorphisms
D
needs to be able to produce entries D[i, j]
for legitimate indices (i, j)
and the morphisms between these on request. To this end, the internal constructor of DoubleComplexOfMorphisms
requires the programmer to pass on certain "factories". For the production of the entries D[i, j]
, these must be concrete instances of
abstract type ChainFactory{ChainType} end
For this type the call syntax must be overwritten as follows:
function (fac::ChainFactory{ChainType})(D::AbsDoubleComplexOfMorphisms, i::Int, j::Int)::ChainType where {ChainType}
This will be called by the internals of DoubleComplex
whenever production of the (i, j)
-th entry is requested. The first argument will then always be the concrete double complex D
itself, so that the factory has access to all information that has already been computed when trying to compute the entry for (i, j)
. Beware not to produce infinite feedback loops when implementing this!
Moreover, any factory is supposed to be able to communicate whether or not a specific entry is computable. To this end one also needs to overwrite
function can_compute(fac::ChainFactory{ChainType}, D::AbsDoubleComplexOfMorphisms, i::Int, j::Int)::Bool where {ChainType}
Let's see this in an example. Suppose we want to implement the "zero double complex of modules" over a multivariate polynomial ring R
, i.e. the unbounded double complex which consists entirely of zero modules and the trivial homomorphisms between them. Then the factory would be
mutable struct ZeroModuleFactory{ChainType} <: ChainFactory{ChainType}
R::MPolyRing
function ZeroModuleFactory(R::MPolyRing)
return new{ModuleFP{elem_type(R)}}(R)
end
end
function (fac::ZeroModuleFactory)(D::AbsDoubleComplexOfMorphisms, i::Int, j::Int)
return FreeMod(fac.R, 0)
end
function can_compute(fac::ZeroModuleFactory, D::AbsDoubleComplexOfMorphisms, i::Int, j::Int)
return true
end
The horizontal and vertical morphisms also need their factories. Similar to the above, these need to be concrete instances of
abstract type ChainMorphismFactory{MorphismType} end
and the programmer must overwrite the functions
function (fac::ChainMorphismFactory{MorphismType})(dc::AbsDoubleComplexOfMorphisms, i::Int, j::Int)::MorphismType where {MorphismType}
function can_compute(fac::ChainMorphismFactory{MorphismType}, dc::AbsDoubleComplexOfMorphisms, i::Int, j::Int)::Bool where {MorphismType}
In the above example we would implement
mutable struct VerticalZeroMaps{MorphismType} <: ChainMorphismFactory{MorphismType}
R::MPolyRing
function VerticalZeroMaps(R::MPolyRing)
return new{ModuleFPHom}(R)
end
end
mutable struct HorizontalZeroMaps{MorphismType} <: ChainMorphismFactory{MorphismType}
R::MPolyRing
function HorizontalZeroMaps(R::MPolyRing)
return new{ModuleFPHom}(R)
end
end
Then we would overwrite the call syntax as follows.
function (fac::VerticalZeroMaps)(D::AbsDoubleComplexOfMorphisms, i::Int, j::Int)
dom = D[i, j]
inc = (vertical_direction(D) == :chain ? -1 : 1)
cod = D[i, j + inc]
return hom(dom, cod, elem_type(cod)[])
end
function (fac::HorizontalZeroMaps)(D::AbsDoubleComplexOfMorphisms, i::Int, j::Int)
dom = D[i, j]
inc = (horizontal_direction(D) == :chain ? -1 : 1)
cod = D[i + inc, j]
return hom(dom, cod, elem_type(cod)[])
end
function can_compute(fac::HorizontalZeroMaps, D::AbsDoubleComplexOfMorphisms, i::Int, j::Int)
return true
end
function can_compute(fac::VerticalZeroMaps, D::AbsDoubleComplexOfMorphisms, i::Int, j::Int)
return true
end
In order to finally create our zero double complex we implement the constructor as follows.
function zero_double_complex(R::MPolyRing)
entry_fac = ZeroModuleFactory(R)
vert_map_fac = VerticalZeroMaps(R)
horz_map_fac = HorizontalZeroMaps(R)
result = DoubleComplexOfMorphisms(entry_fac, horz_map_fac, vert_map_fac, horizontal_direction=:chain, vertical_direction=:chain)
return result
end
Note that any concrete complex Z
created by zero_double_complex
is unbounded in every direction. In particular, has_upper_bound(Z)
etc. will return false
and upper_bound(Z)
will throw an error. At the same time can_compute_index(Z, i, j)
will always return true
and calling Z[i, j]
will produce a reasonable and cached result. See the source code of the internal constructor of DoubleComplexOfMorphisms
for how to alter these settings.
Another example for an implementation of a double complex can be found in experimental/DoubleComplexes/test/double_complex_interface.jl
. There we write an implementation to turn a bounded simple ComplexOfMorphisms
for modules over polynomial rings C
into a bounded DoubleComplexOfMorphisms
D
which knows how to extend itself with zeroes to the left and to the right, but is concentrated in the zeroeth row.
How to make use of the generic functionality?
For double complexes we have some generic functionality available, e.g. total_complex(D::AbsDoubleComplexOfMorphisms{ChainType, MorphismType})
. Such generic functionality assumes certain methods to be implemented for the ChainType
and the MorphismType
of the double complex D
. For instance, it must be possible to compose two morphisms of type <:MorphismType
and get a new object of type <:MorphismType
. Sometimes, the required functionality is not streamlined throughout OSCAR (and there is little hope to achieve this). One example for this are direct sums: For finitely generated modules, the function takes a special keyword argument task
to indicate whether the inclusion and projection maps should also be returned, while for TorQuadModule
s, this keyword argument is not even available. To potentially accommodate all these different types in our double complexes, the generic code uses an internal method
_direct_sum(u::Vector{T}) where {T}
to make sure that the output has the correct format (s, inc, pr)
consisting of the direct sum s
itself, together with the vectors of inclusion- and projection maps inc
and pr
.
If any generic functionality, such as forming a total complex, does not work for your custom implementation of a double complex, check whether this might be due to a missing implementation of this method or others.