# Double complexes – the user's interface

We briefly review the mathematical notion of a double complex. Let $\mathcal A$ be an Abelian category. A double complex $D_{\bullet, \bullet}$ consists of a collection of objects $D_{i, j}$ in $\mathcal A$ with indices $(i, j) \in \mathbb Z^2$ and usually arranged in a matrix-like grid, together with two collections of morphisms $D_{i, j} \to D_{i \pm 1, j}$, the *horizontal* morphisms, and $D_{i, j} \to D_{i, j \pm 1}$, the *vertical* morphisms, so that both the rows and the columns of $D_{\bullet, \bullet}$ are complexes in the classical sense and such that all resulting squares of maps commute.

In practice one usually encounters complexes which are *bounded* in the sense that outside some specified area of indices $(i, j) \in \mathbb Z^2$ the entries $D_{i, j}$ are all zero. Such entries are then usually omitted.

## Basic getters and attributes

In OSCAR the generic functionality for double complexes is declared for the abstract type `AbsDoubleComplexOfMorphisms`

. These functions comprise

` getindex(D::AbsDoubleComplexOfMorphisms, i::Int, j::Int) # Get the `(i,j)`-th entry of `D``

`horizontal_map`

— Method`horizontal_map(dc::AbsDoubleComplexOfMorphisms, i::Int, j::Int)`

Return the morphism $dc[i, j] → dc[i ± 1, j]$ (the sign depending on the `horizontal_direction`

of `dc`

).

`vertical_map`

— Method`vertical_map(dc::AbsDoubleComplexOfMorphisms, i::Int, j::Int)`

Return the morphism $dc[i, j] → dc[i, j ± 1]$ (the sign depending on the `vertical_direction`

of `dc`

).

In which direction the maps in the rows and columns go can be asked with the following methods:

`horizontal_direction`

— Method`horizontal_direction(dc::AbsDoubleComplexOfMorphisms)`

Return a symbol `:chain`

or `:cochain`

depending on whether the morphisms of the rows of `dc`

decrease or increase the (co-)homological index.

`vertical_direction`

— Method`vertical_direction(dc::AbsDoubleComplexOfMorphisms)`

Return a symbol `:chain`

or `:cochain`

depending on whether the morphisms of the columns of `dc`

decrease or increase the (co-)homological index.

Double complexes can be bounded or unbounded. It is important to note that even if such bounds exist and are known, this is a priori **not** related to whether or not certain entries are computable! I.e. even in the case of a bounded complex `dc`

it might still be valid to call `dc[i, j]`

beyond that bound. In general, one should use the following functions to determine whether or not it is legitimate to ask for a specific entry.

`has_index`

— Method`has_index(D::AbsDoubleComplexOfMorphisms, i::Int, j::Int)`

Return `true`

if the `(i, j)`

-th entry of `D`

is already known, `false`

otherwise.

If the result is `false`

, then it might nevertheless still be possible to compute `D[i, j]`

; use `can_compute_index`

for such queries.

`can_compute_index`

— Method`can_compute_index(D::AbsDoubleComplexOfMorphisms, i::Int, j::Int)`

Returns `true`

if the entry `D[i, j]`

is known or `D`

knows how to compute it.

`has_horizontal_map`

— Method`has_horizontal_map(dc::AbsDoubleComplexOfMorphisms, i::Int, j::Int)`

Checks whether the double complex `dc`

has the horizontal morphism `dc[i, j] → dc[i ± 1, j]`

, the sign depending on the `horizontal_direction`

of `dc`

.

If this returns `false`

this might just mean that the map has not been computed, yet. Use `can_compute_horizontal_map`

to learn whether or not this is possible.

`can_compute_horizontal_map`

— Method`can_compute_horizontal_map(dc::AbsDoubleComplexOfMorphisms, i::Int, j::Int)`

Returns `true`

if `dc`

can compute the horizontal morphism `dc[i, j] → dc[i ± 1, j]`

, the sign depending on the `horizontal_direction`

of `dc`

, and `false`

otherwise.

`has_vertical_map`

— Method`has_vertical_map(dc::AbsDoubleComplexOfMorphisms, i::Int, j::Int)`

Checks whether the double complex `dc`

has the vertical morphism `dc[i, j] → dc[i, j ± 1]`

, the sign depending on the `vertical_direction`

of `dc`

.

If this returns `false`

this might just mean that the map has not been computed, yet. Use `can_compute_vertical_map`

to learn whether or not this is possible.

`can_compute_vertical_map`

— Method`can_compute_vertical_map(dc::AbsDoubleComplexOfMorphisms, i::Int, j::Int)`

Returns `true`

if `dc`

can compute the vertical morphism `dc[i, j] → dc[i, j ± 1]`

, the sign depending on the `vertical_direction`

of `dc`

, and `false`

otherwise.

Explicitly known bounds for the non-zero entries of a complex are nevertheless relevant for various generic functionalities. For example, computing a total complex is only possible in practice if one has an a priori estimate where the non-zero entries are located. For such purposes, we provide the following functionality:

`has_upper_bound`

— Method`has_upper_bound(D::AbsDoubleComplexOfMorphisms)`

Returns `true`

if a universal upper bound $j ≤ B$ for non-zero `D[i, j]`

is known; `false`

otherwise.

`has_lower_bound`

— Method`has_lower_bound(D::AbsDoubleComplexOfMorphisms)`

Returns `true`

if a universal upper bound $B ≤ j$ for non-zero `D[i, j]`

is known; `false`

otherwise.

`has_right_bound`

— Method`has_right_bound(D::AbsDoubleComplexOfMorphisms)`

Returns `true`

if a universal upper bound $i ≤ B$ for non-zero `D[i, j]`

is known; `false`

otherwise.

`has_left_bound`

— Method`has_left_bound(D::AbsDoubleComplexOfMorphisms)`

Returns `true`

if a universal upper bound $B ≤ i$ for non-zero `D[i, j]`

is known; `false`

otherwise.

If they exist, these bounds can be asked for using

`right_bound`

— Method`right_bound(D::AbsDoubleComplexOfMorphisms)`

Returns a bound $B$ such that `D[i, j]`

can be assumed to be zero for $i > B$. Whether or not requests for `D[i, j]`

beyond that bound are legitimate can be checked using `can_compute_index`

.

`left_bound`

— Method`left_bound(D::AbsDoubleComplexOfMorphisms)`

Returns a bound $B$ such that `D[i, j]`

can be assumed to be zero for $i < B$. Whether or not requests for `D[i, j]`

beyond that bound are legitimate can be checked using `can_compute_index`

.

`upper_bound`

— Method`upper_bound(D::AbsDoubleComplexOfMorphisms)`

Returns a bound $B$ such that `D[i, j]`

can be assumed to be zero for $j > B$. Whether or not requests for `D[i, j]`

beyond that bound are legitimate can be checked using `can_compute_index`

.

`lower_bound`

— Method`lower_bound(D::AbsDoubleComplexOfMorphisms)`

Returns a bound $B$ such that `D[i, j]`

can be assumed to be zero for $j < B$. Whether or not requests for `D[i, j]`

beyond that bound are legitimate can be checked using `can_compute_index`

.

It is also possible to query whether or not a double complex is already complete in the sense that it knows about all of its non-zero entries.

`is_complete`

— Method`is_complete(dc::AbsDoubleComplexOfMorphisms)`

Returns `true`

if for all indices `(i, j)`

with `has_index(dc, i, j) = true`

and `dc[i, j]`

non-zero, the vertex `(i, j)`

is lying on an "island" of non-zero entries in the grid of the double complex, which is bounded by either zero entries or entries for indices `(i', j')`

where `can_compute_index(dc, i', j') = false`

. At least one index `dc[i, j]`

must be known for this to return `true`

.

```
⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
… → ? → ? → ? → ? → ? → ? → ? → ? → ? → ? → ? → 0 → 0 → ? → - → - → …
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
… → ? → ? → 0 → 0 → 0 → ? → ? → 0 → 0 → 0 → 0 → * → * → 0 → - → - → …
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
… → 0 → 0 → * → * → * → 0 → ? → 0 → ? → 0 → 0 → * → * → * → - → - → …
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
… → 0 → * → * → * → 0 → ? → ? → 0 → 0 → ? → 0 → * → 0 → * → - → - → …
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
… → 0 → * → * → * → 0 → ? → 0 → 0 → 0 → 0 → * → * → 0 → 0 → - → - → …
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
… → ? → 0 → 0 → 0 → ? → ? → ? → ? → ? → ? → 0 → 0 → ? → ? → - → - → …
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
```

Example of a pattern of a double complex with `is_complete = true`

. `0`

: zero entry `-`

: entry can not be computed (`can_compute_index`

returns `false`

) `*`

: non-zero entry which has been computed `?`

: entry can be computed, but that has not yet been done

!!! note If the double complex has several of the above "islands", then `is_complete`

might return `true`

even though one or more of the "islands" have not yet been uncovered. Use this carefully if your full double complex might be separated by zero entries!

## Generic functionality

`total_complex`

— Method`total_complex(D::AbsDoubleComplexOfMorphisms)`

Construct the total complex of the double complex `D`

.

Note that `D`

needs to be reasonably bounded for this to work so that the strands $⨁ ᵢ₊ⱼ₌ₖ Dᵢⱼ$ are finite for every `k`

. Moreover, the generic code uses the internal function `_direct_sum`

. See the docstring of that function to learn more.

## Constructors

`tensor_product`

— Method`tensor_product(C::ComplexOfMorphisms{ChainType}, D::ComplexOfMorphisms{ChainType}) where {ChainType}`

Create the tensor product of two complexes `C`

and `D`

as a double complex.

In order for the generic implementation to work for your specific `ChainType`

the following needs to be implemented.

`morphism_type(ChainType)`

must produce the type of morphisms between objects of type`ChainType`

;- the call signature for
`function (fac::TensorProductFactory{ChainType})(dc::AbsDoubleComplexOfMorphisms, i::Int, j::Int)`

needs to be overwritten for your specific instance of`ChainType`

to produce the`(i, j)`

-th entry of the double complex, i.e. the tensor product of`C[i]`

and`D[j]`

; - the call signature for
`function (fac::HorizontalTensorMapFactory{ChainType})(dc::AbsDoubleComplexOfMorphisms, i::Int, j::Int)`

needs to be overwritten to produce the map on tensor products`C[i] ⊗ D[j] → C[i±1] ⊗ D[j]`

induced by the (co-)boundary map on`C`

(the sign depending on the`typ`

of`C`

); - similarly for the
`VerticalTensorMapFactory`

.

See the file `experimental/DoubleComplex/src/tensor_products.jl`

for examples.