Chain homotopies and chain contractions#

Chain homotopies are standard constructions in homological algebra: given chain complexes C and D and chain maps f,g:CD, say with differential of degree 1, a chain homotopy H between f and g is a collection of maps Hn:CnDn+1 satisfying

DH+HC=fg.

The presence of a chain homotopy defines an equivalence relation (chain homotopic) on chain maps. If f and g are chain homotopic, then one can show that f and g induce the same map on homology.

Chain contractions are not as well known. The papers [MAR2009], [RMA2009], and [PR2015] provide some references. Given two chain complexes C and D, a chain contraction is a chain homotopy H:CC for which there are chain maps π:CD (“projection”) and ι:DC (“inclusion”) such that

  • H is a chain homotopy between 1C and ιπ,

  • πι=1D,

  • πH=0,

  • Hι=0,

  • HH=0.

Such a chain homotopy provides a strong relation between the chain complexes C and D; for example, their homology groups are isomorphic.

class sage.homology.chain_homotopy.ChainContraction(matrices, pi, iota)[source]#

Bases: ChainHomotopy

A chain contraction.

An algebraic gradient vector field H:CC (that is a chain homotopy satisfying HH=0) for which there are chain maps π:CD (“projection”) and ι:DC (“inclusion”) such that

  • H is a chain homotopy between 1C and ιπ,

  • πι=1D,

  • πH=0,

  • Hι=0.

H is defined by a dictionary matrices of matrices.

INPUT:

  • matrices – dictionary of matrices, keyed by dimension

  • pi – a chain map CD

  • iota – a chain map DC

EXAMPLES:

sage: from sage.homology.chain_homotopy import ChainContraction
sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)})
sage: D = ChainComplex({0: matrix(ZZ, 0, 1)})
from sage.homology.chain_homotopy import ChainContraction
C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)})
D = ChainComplex({0: matrix(ZZ, 0, 1)})
>>> from sage.all import *
>>> from sage.homology.chain_homotopy import ChainContraction
>>> C = ChainComplex({Integer(0): zero_matrix(ZZ, Integer(1)), Integer(1): identity_matrix(ZZ, Integer(1))})
>>> D = ChainComplex({Integer(0): matrix(ZZ, Integer(0), Integer(1))})

The chain complex C is chain homotopy equivalent to D, which is just a copy of Z in degree 0, and we construct a chain contraction:

sage: pi = Hom(C,D)({0: identity_matrix(ZZ, 1)})
sage: iota = Hom(D,C)({0: identity_matrix(ZZ, 1)})
sage: H = ChainContraction({0: zero_matrix(ZZ, 0, 1),
....:                       1: zero_matrix(ZZ, 1),
....:                       2: identity_matrix(ZZ, 1)}, pi, iota)
pi = Hom(C,D)({0: identity_matrix(ZZ, 1)})
iota = Hom(D,C)({0: identity_matrix(ZZ, 1)})
H = ChainContraction({0: zero_matrix(ZZ, 0, 1),
                      1: zero_matrix(ZZ, 1),
                      2: identity_matrix(ZZ, 1)}, pi, iota)
>>> from sage.all import *
>>> pi = Hom(C,D)({Integer(0): identity_matrix(ZZ, Integer(1))})
>>> iota = Hom(D,C)({Integer(0): identity_matrix(ZZ, Integer(1))})
>>> H = ChainContraction({Integer(0): zero_matrix(ZZ, Integer(0), Integer(1)),
...                       Integer(1): zero_matrix(ZZ, Integer(1)),
...                       Integer(2): identity_matrix(ZZ, Integer(1))}, pi, iota)
dual()[source]#

The chain contraction dual to this one.

This is useful when switching from homology to cohomology.

EXAMPLES:

sage: S2 = simplicial_complexes.Sphere(2)                                   # needs sage.graphs
sage: phi, M = S2.algebraic_topological_model(QQ)                           # needs sage.graphs
sage: phi.iota()                                                            # needs sage.graphs
Chain complex morphism:
  From: Chain complex with at most 3 nonzero terms over Rational Field
  To: Chain complex with at most 3 nonzero terms over Rational Field
S2 = simplicial_complexes.Sphere(2)                                   # needs sage.graphs
phi, M = S2.algebraic_topological_model(QQ)                           # needs sage.graphs
phi.iota()                                                            # needs sage.graphs
>>> from sage.all import *
>>> S2 = simplicial_complexes.Sphere(Integer(2))                                   # needs sage.graphs
>>> phi, M = S2.algebraic_topological_model(QQ)                           # needs sage.graphs
>>> phi.iota()                                                            # needs sage.graphs
Chain complex morphism:
  From: Chain complex with at most 3 nonzero terms over Rational Field
  To: Chain complex with at most 3 nonzero terms over Rational Field

Lifting the degree zero homology class gives a single vertex, but the degree zero cohomology class needs to be detected on every vertex, and vice versa for degree 2:

sage: # needs sage.graphs
sage: phi.iota().in_degree(0)
[0]
[0]
[0]
[1]
sage: phi.dual().iota().in_degree(0)
[1]
[1]
[1]
[1]
sage: phi.iota().in_degree(2)
[-1]
[ 1]
[-1]
[ 1]
sage: phi.dual().iota().in_degree(2)
[0]
[0]
[0]
[1]
# needs sage.graphs
phi.iota().in_degree(0)
phi.dual().iota().in_degree(0)
phi.iota().in_degree(2)
phi.dual().iota().in_degree(2)
>>> from sage.all import *
>>> # needs sage.graphs
>>> phi.iota().in_degree(Integer(0))
[0]
[0]
[0]
[1]
>>> phi.dual().iota().in_degree(Integer(0))
[1]
[1]
[1]
[1]
>>> phi.iota().in_degree(Integer(2))
[-1]
[ 1]
[-1]
[ 1]
>>> phi.dual().iota().in_degree(Integer(2))
[0]
[0]
[0]
[1]
iota()[source]#

The chain map ι associated to this chain contraction.

EXAMPLES:

sage: S2 = simplicial_complexes.Sphere(2)                                   # needs sage.graphs
sage: phi, M = S2.algebraic_topological_model(QQ)                           # needs sage.graphs
sage: phi.iota()                                                            # needs sage.graphs
Chain complex morphism:
  From: Chain complex with at most 3 nonzero terms over Rational Field
  To: Chain complex with at most 3 nonzero terms over Rational Field
S2 = simplicial_complexes.Sphere(2)                                   # needs sage.graphs
phi, M = S2.algebraic_topological_model(QQ)                           # needs sage.graphs
phi.iota()                                                            # needs sage.graphs
>>> from sage.all import *
>>> S2 = simplicial_complexes.Sphere(Integer(2))                                   # needs sage.graphs
>>> phi, M = S2.algebraic_topological_model(QQ)                           # needs sage.graphs
>>> phi.iota()                                                            # needs sage.graphs
Chain complex morphism:
  From: Chain complex with at most 3 nonzero terms over Rational Field
  To: Chain complex with at most 3 nonzero terms over Rational Field

Lifting the degree zero homology class gives a single vertex:

sage: phi.iota().in_degree(0)                                               # needs sage.graphs
[0]
[0]
[0]
[1]
phi.iota().in_degree(0)                                               # needs sage.graphs
>>> from sage.all import *
>>> phi.iota().in_degree(Integer(0))                                               # needs sage.graphs
[0]
[0]
[0]
[1]

Lifting the degree two homology class gives the signed sum of all of the 2-simplices:

sage: phi.iota().in_degree(2)                                               # needs sage.graphs
[-1]
[ 1]
[-1]
[ 1]
phi.iota().in_degree(2)                                               # needs sage.graphs
>>> from sage.all import *
>>> phi.iota().in_degree(Integer(2))                                               # needs sage.graphs
[-1]
[ 1]
[-1]
[ 1]
pi()[source]#

The chain map π associated to this chain contraction.

EXAMPLES:

sage: # needs sage.graphs
sage: S2 = simplicial_complexes.Sphere(2)
sage: phi, M = S2.algebraic_topological_model(QQ)
sage: phi.pi()
Chain complex morphism:
  From: Chain complex with at most 3 nonzero terms over Rational Field
  To: Chain complex with at most 3 nonzero terms over Rational Field
sage: phi.pi().in_degree(0)  # Every vertex represents a homology class.
[1 1 1 1]
sage: phi.pi().in_degree(1)  # No homology in degree 1.
[]
# needs sage.graphs
S2 = simplicial_complexes.Sphere(2)
phi, M = S2.algebraic_topological_model(QQ)
phi.pi()
phi.pi().in_degree(0)  # Every vertex represents a homology class.
phi.pi().in_degree(1)  # No homology in degree 1.
>>> from sage.all import *
>>> # needs sage.graphs
>>> S2 = simplicial_complexes.Sphere(Integer(2))
>>> phi, M = S2.algebraic_topological_model(QQ)
>>> phi.pi()
Chain complex morphism:
  From: Chain complex with at most 3 nonzero terms over Rational Field
  To: Chain complex with at most 3 nonzero terms over Rational Field
>>> phi.pi().in_degree(Integer(0))  # Every vertex represents a homology class.
[1 1 1 1]
>>> phi.pi().in_degree(Integer(1))  # No homology in degree 1.
[]

The degree 2 homology generator is detected on a single simplex:

sage: phi.pi().in_degree(2)                                                 # needs sage.graphs
[0 0 0 1]
phi.pi().in_degree(2)                                                 # needs sage.graphs
>>> from sage.all import *
>>> phi.pi().in_degree(Integer(2))                                                 # needs sage.graphs
[0 0 0 1]
class sage.homology.chain_homotopy.ChainHomotopy(matrices, f, g=None)[source]#

Bases: Morphism

A chain homotopy.

A chain homotopy H between chain maps f,g:CD is a sequence of maps Hn:CnDn+1 (if the chain complexes are graded homologically) satisfying

DH+HC=fg.

INPUT:

  • matrices – dictionary of matrices, keyed by dimension

  • f – chain map CD

  • g (optional) – chain map CD

The dictionary matrices defines H by specifying the matrix defining it in each degree: the entry m corresponding to key i gives the linear transformation CiDi+1.

If f is specified but not g, then g can be recovered from the defining formula. That is, if g is not specified, then it is defined to be fDHHC.

Note that the degree of the differential on the chain complex C must agree with that for D, and those degrees determine the “degree” of the chain homotopy map: if the degree of the differential is d, then the chain homotopy consists of a sequence of maps CnCnd. The keys in the dictionary matrices specify the starting degrees.

EXAMPLES:

sage: from sage.homology.chain_homotopy import ChainHomotopy
sage: C = ChainComplex({0: identity_matrix(ZZ, 1)})
sage: D = ChainComplex({0: zero_matrix(ZZ, 1)})
sage: f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)})
sage: g = Hom(C,D)({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)})
sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1)}, f, g)
from sage.homology.chain_homotopy import ChainHomotopy
C = ChainComplex({0: identity_matrix(ZZ, 1)})
D = ChainComplex({0: zero_matrix(ZZ, 1)})
f = Hom(C,D)({0: identity_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)})
g = Hom(C,D)({0: zero_matrix(ZZ, 1), 1: zero_matrix(ZZ, 1)})
H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: identity_matrix(ZZ, 1)}, f, g)
>>> from sage.all import *
>>> from sage.homology.chain_homotopy import ChainHomotopy
>>> C = ChainComplex({Integer(0): identity_matrix(ZZ, Integer(1))})
>>> D = ChainComplex({Integer(0): zero_matrix(ZZ, Integer(1))})
>>> f = Hom(C,D)({Integer(0): identity_matrix(ZZ, Integer(1)), Integer(1): zero_matrix(ZZ, Integer(1))})
>>> g = Hom(C,D)({Integer(0): zero_matrix(ZZ, Integer(1)), Integer(1): zero_matrix(ZZ, Integer(1))})
>>> H = ChainHomotopy({Integer(0): zero_matrix(ZZ, Integer(0), Integer(1)), Integer(1): identity_matrix(ZZ, Integer(1))}, f, g)

Note that the maps f and g are stored in the attributes H._f and H._g:

sage: H._f
Chain complex morphism:
  From: Chain complex with at most 2 nonzero terms over Integer Ring
  To: Chain complex with at most 2 nonzero terms over Integer Ring
sage: H._f.in_degree(0)
[1]
sage: H._g.in_degree(0)
[0]
H._f
H._f.in_degree(0)
H._g.in_degree(0)
>>> from sage.all import *
>>> H._f
Chain complex morphism:
  From: Chain complex with at most 2 nonzero terms over Integer Ring
  To: Chain complex with at most 2 nonzero terms over Integer Ring
>>> H._f.in_degree(Integer(0))
[1]
>>> H._g.in_degree(Integer(0))
[0]

A non-example:

sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1)}, f, g)
Traceback (most recent call last):
...
ValueError: the data do not define a valid chain homotopy
H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1), 1: zero_matrix(ZZ, 1)}, f, g)
>>> from sage.all import *
>>> H = ChainHomotopy({Integer(0): zero_matrix(ZZ, Integer(0), Integer(1)), Integer(1): zero_matrix(ZZ, Integer(1))}, f, g)
Traceback (most recent call last):
...
ValueError: the data do not define a valid chain homotopy
dual()[source]#

Dual chain homotopy to this one.

That is, if this one is a chain homotopy between chain maps f,g:CD, then its dual is a chain homotopy between the dual of f and the dual of g, from D to C. It is represented in each degree by the transpose of the corresponding matrix.

EXAMPLES:

sage: from sage.homology.chain_homotopy import ChainHomotopy
sage: C = ChainComplex({1: matrix(ZZ, 0, 2)})  # one nonzero term in degree 1
sage: D = ChainComplex({0: matrix(ZZ, 0, 1)})  # one nonzero term in degree 0
sage: f = Hom(C, D)({})
sage: H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f)
sage: H.in_degree(1)
[3 1]
sage: H.dual().in_degree(0)
[3]
[1]
from sage.homology.chain_homotopy import ChainHomotopy
C = ChainComplex({1: matrix(ZZ, 0, 2)})  # one nonzero term in degree 1
D = ChainComplex({0: matrix(ZZ, 0, 1)})  # one nonzero term in degree 0
f = Hom(C, D)({})
H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f)
H.in_degree(1)
H.dual().in_degree(0)
>>> from sage.all import *
>>> from sage.homology.chain_homotopy import ChainHomotopy
>>> C = ChainComplex({Integer(1): matrix(ZZ, Integer(0), Integer(2))})  # one nonzero term in degree 1
>>> D = ChainComplex({Integer(0): matrix(ZZ, Integer(0), Integer(1))})  # one nonzero term in degree 0
>>> f = Hom(C, D)({})
>>> H = ChainHomotopy({Integer(1): matrix(ZZ, Integer(1), Integer(2), (Integer(3),Integer(1)))}, f, f)
>>> H.in_degree(Integer(1))
[3 1]
>>> H.dual().in_degree(Integer(0))
[3]
[1]
in_degree(n)[source]#

The matrix representing this chain homotopy in degree n.

INPUT:

  • n – degree

EXAMPLES:

sage: from sage.homology.chain_homotopy import ChainHomotopy
sage: C = ChainComplex({1: matrix(ZZ, 0, 2)})  # one nonzero term in degree 1
sage: D = ChainComplex({0: matrix(ZZ, 0, 1)})  # one nonzero term in degree 0
sage: f = Hom(C, D)({})
sage: H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f)
sage: H.in_degree(1)
[3 1]
from sage.homology.chain_homotopy import ChainHomotopy
C = ChainComplex({1: matrix(ZZ, 0, 2)})  # one nonzero term in degree 1
D = ChainComplex({0: matrix(ZZ, 0, 1)})  # one nonzero term in degree 0
f = Hom(C, D)({})
H = ChainHomotopy({1: matrix(ZZ, 1, 2, (3,1))}, f, f)
H.in_degree(1)
>>> from sage.all import *
>>> from sage.homology.chain_homotopy import ChainHomotopy
>>> C = ChainComplex({Integer(1): matrix(ZZ, Integer(0), Integer(2))})  # one nonzero term in degree 1
>>> D = ChainComplex({Integer(0): matrix(ZZ, Integer(0), Integer(1))})  # one nonzero term in degree 0
>>> f = Hom(C, D)({})
>>> H = ChainHomotopy({Integer(1): matrix(ZZ, Integer(1), Integer(2), (Integer(3),Integer(1)))}, f, f)
>>> H.in_degree(Integer(1))
[3 1]

This returns an appropriately sized zero matrix if the chain homotopy is not defined in degree n:

sage: H.in_degree(-3)
[]
H.in_degree(-3)
>>> from sage.all import *
>>> H.in_degree(-Integer(3))
[]
is_algebraic_gradient_vector_field()[source]#

An algebraic gradient vector field is a linear map H:CC such that HH=0.

(Some authors also require that HH=H, whereas some make this part of the definition of “homology gradient vector field. We have made the second choice.) See Molina-Abril and Réal [MAR2009] and Réal and Molina-Abril [RMA2009] for this and related terminology.

See also is_homology_gradient_vector_field().

EXAMPLES:

sage: from sage.homology.chain_homotopy import ChainHomotopy
sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)})
from sage.homology.chain_homotopy import ChainHomotopy
C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)})
>>> from sage.all import *
>>> from sage.homology.chain_homotopy import ChainHomotopy
>>> C = ChainComplex({Integer(0): zero_matrix(ZZ, Integer(1)), Integer(1): identity_matrix(ZZ, Integer(1))})

The chain complex C is chain homotopy equivalent to a copy of Z in degree 0. Two chain maps CC will be chain homotopic as long as they agree in degree 0.

sage: f = Hom(C,C)({0: identity_matrix(ZZ, 1),
....:               1: matrix(ZZ, 1, 1, [3]),
....:               2: matrix(ZZ, 1, 1, [3])})
sage: g = Hom(C,C)({0: identity_matrix(ZZ, 1),
....:               1: matrix(ZZ, 1, 1, [2]),
....:               2: matrix(ZZ, 1, 1, [2])})
sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1),
....:                    1: zero_matrix(ZZ, 1),
....:                    2: identity_matrix(ZZ, 1)}, f, g)
sage: H.is_algebraic_gradient_vector_field()
True
f = Hom(C,C)({0: identity_matrix(ZZ, 1),
              1: matrix(ZZ, 1, 1, [3]),
              2: matrix(ZZ, 1, 1, [3])})
g = Hom(C,C)({0: identity_matrix(ZZ, 1),
              1: matrix(ZZ, 1, 1, [2]),
              2: matrix(ZZ, 1, 1, [2])})
H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1),
                   1: zero_matrix(ZZ, 1),
                   2: identity_matrix(ZZ, 1)}, f, g)
H.is_algebraic_gradient_vector_field()
>>> from sage.all import *
>>> f = Hom(C,C)({Integer(0): identity_matrix(ZZ, Integer(1)),
...               Integer(1): matrix(ZZ, Integer(1), Integer(1), [Integer(3)]),
...               Integer(2): matrix(ZZ, Integer(1), Integer(1), [Integer(3)])})
>>> g = Hom(C,C)({Integer(0): identity_matrix(ZZ, Integer(1)),
...               Integer(1): matrix(ZZ, Integer(1), Integer(1), [Integer(2)]),
...               Integer(2): matrix(ZZ, Integer(1), Integer(1), [Integer(2)])})
>>> H = ChainHomotopy({Integer(0): zero_matrix(ZZ, Integer(0), Integer(1)),
...                    Integer(1): zero_matrix(ZZ, Integer(1)),
...                    Integer(2): identity_matrix(ZZ, Integer(1))}, f, g)
>>> H.is_algebraic_gradient_vector_field()
True

A chain homotopy which is not an algebraic gradient vector field:

sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1),
....:                    1: identity_matrix(ZZ, 1),
....:                    2: identity_matrix(ZZ, 1)}, f, g)
sage: H.is_algebraic_gradient_vector_field()
False
H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1),
                   1: identity_matrix(ZZ, 1),
                   2: identity_matrix(ZZ, 1)}, f, g)
H.is_algebraic_gradient_vector_field()
>>> from sage.all import *
>>> H = ChainHomotopy({Integer(0): zero_matrix(ZZ, Integer(0), Integer(1)),
...                    Integer(1): identity_matrix(ZZ, Integer(1)),
...                    Integer(2): identity_matrix(ZZ, Integer(1))}, f, g)
>>> H.is_algebraic_gradient_vector_field()
False
is_homology_gradient_vector_field()[source]#

A homology gradient vector field is an algebraic gradient vector field H:CC (i.e., a chain homotopy satisfying HH=0) such that H= and HH=H.

See Molina-Abril and Réal [MAR2009] and Réal and Molina-Abril [RMA2009] for this and related terminology.

See also is_algebraic_gradient_vector_field().

EXAMPLES:

sage: from sage.homology.chain_homotopy import ChainHomotopy
sage: C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)})

sage: f = Hom(C,C)({0: identity_matrix(ZZ, 1),
....:               1: matrix(ZZ, 1, 1, [3]),
....:               2: matrix(ZZ, 1, 1, [3])})
sage: g = Hom(C,C)({0: identity_matrix(ZZ, 1),
....:               1: matrix(ZZ, 1, 1, [2]),
....:               2: matrix(ZZ, 1, 1, [2])})
sage: H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1),
....:                    1: zero_matrix(ZZ, 1),
....:                    2: identity_matrix(ZZ, 1)}, f, g)
sage: H.is_homology_gradient_vector_field()
True
from sage.homology.chain_homotopy import ChainHomotopy
C = ChainComplex({0: zero_matrix(ZZ, 1), 1: identity_matrix(ZZ, 1)})
f = Hom(C,C)({0: identity_matrix(ZZ, 1),
              1: matrix(ZZ, 1, 1, [3]),
              2: matrix(ZZ, 1, 1, [3])})
g = Hom(C,C)({0: identity_matrix(ZZ, 1),
              1: matrix(ZZ, 1, 1, [2]),
              2: matrix(ZZ, 1, 1, [2])})
H = ChainHomotopy({0: zero_matrix(ZZ, 0, 1),
                   1: zero_matrix(ZZ, 1),
                   2: identity_matrix(ZZ, 1)}, f, g)
H.is_homology_gradient_vector_field()
>>> from sage.all import *
>>> from sage.homology.chain_homotopy import ChainHomotopy
>>> C = ChainComplex({Integer(0): zero_matrix(ZZ, Integer(1)), Integer(1): identity_matrix(ZZ, Integer(1))})

>>> f = Hom(C,C)({Integer(0): identity_matrix(ZZ, Integer(1)),
...               Integer(1): matrix(ZZ, Integer(1), Integer(1), [Integer(3)]),
...               Integer(2): matrix(ZZ, Integer(1), Integer(1), [Integer(3)])})
>>> g = Hom(C,C)({Integer(0): identity_matrix(ZZ, Integer(1)),
...               Integer(1): matrix(ZZ, Integer(1), Integer(1), [Integer(2)]),
...               Integer(2): matrix(ZZ, Integer(1), Integer(1), [Integer(2)])})
>>> H = ChainHomotopy({Integer(0): zero_matrix(ZZ, Integer(0), Integer(1)),
...                    Integer(1): zero_matrix(ZZ, Integer(1)),
...                    Integer(2): identity_matrix(ZZ, Integer(1))}, f, g)
>>> H.is_homology_gradient_vector_field()
True