Skip to content
The Lumenary

Math laboratory

Mapping cones and the non-functoriality of cones

Builds mapping cones of chain maps between small complexes of rational vector spaces with exact fractions, checks d^2 = 0 and that f is a quasi-isomorphism iff Cone(f) is acyclic, and shows that the map induced on cones by a square depends on the chosen homotopy: the same square in the homotopy category induces multiplication by any scalar. Exact, on small examples only.

Back to the labExact computation
Mapping cones and the non-functoriality of conesPythonExact computationComputationally tested
"""Mapping cones of chain complexes, and why cones are not functorial.

Chain complexes of finite-dimensional rational vector spaces are a model for
the stable infinity-category D(Q): a morphism is a chain map, and a
commutative square up to homotopy is a square together with a chosen chain
homotopy. We compute exactly, with fractions:

1. the mapping cone Cone(f) of a chain map and its homology, checking that
   f is a quasi-isomorphism exactly when Cone(f) is acyclic (three examples);
2. a square X -> Y, X' -> Y' with a = 0 and b = 0, together with a homotopy
   h of degree +1. The induced map Cone(f) -> Cone(f') depends on h: with
   X = Q[0], Y = 0, X' = 0, Y' = Q[1], every scalar h gives a chain map on cones
   that acts on homology as multiplication by h. In the triangulated homotopy
   category all of these squares are the same square (a = b = 0), so the map on
   cones is not determined by the square: cones are not functorial. Remember
   the homotopy, as a stable infinity-category does, and the map is determined.

Exact rational arithmetic on small examples; illustrative, not a proof.
"""

from fractions import Fraction

def zeros(rows, cols):
    """A rows x cols zero matrix of Fractions."""
    return [[Fraction(0)] * cols for _ in range(rows)]

def matmul(a, b, inner, cols):
    """Product of an r x inner matrix and an inner x cols matrix (lists of
    rows). Shapes are passed explicitly so that empty matrices work."""
    return [[sum((a[i][k] * b[k][j] for k in range(inner)), Fraction(0))
             for j in range(cols)] for i in range(len(a))]

def rank(m):
    """Rank of a matrix of Fractions, by Gaussian elimination."""
    rows = [list(r) for r in m]
    r = 0
    cols = len(rows[0]) if rows else 0
    for c in range(cols):
        pivot = next((i for i in range(r, len(rows)) if rows[i][c] != 0), None)
        if pivot is None:
            continue
        rows[r], rows[pivot] = rows[pivot], rows[r]
        for i in range(len(rows)):
            if i != r and rows[i][c] != 0:
                factor = rows[i][c] / rows[r][c]
                rows[i] = [x - factor * y for x, y in zip(rows[i], rows[r])]
        r += 1
    return r

class Complex:
    """A bounded chain complex: dims[n] = dim C_n, diff[n]: C_n -> C_(n-1)."""

    def __init__(self, dims, diff=None):
        self.dims = {n: d for n, d in dims.items() if d > 0}
        self.diff = diff or {}

    def dim(self, n):
        return self.dims.get(n, 0)

    def d(self, n):
        """The differential C_n -> C_(n-1) as a dim(n-1) x dim(n) matrix."""
        if n in self.diff:
            return self.diff[n]
        return zeros(self.dim(n - 1), self.dim(n))

    def degrees(self):
        return range(min(self.dims, default=0) - 1, max(self.dims, default=0) + 2)

def is_complex(c):
    """Check d_(n-1) d_n = 0 in every degree."""
    return all(rank(matmul(c.d(n - 1), c.d(n), c.dim(n - 1), c.dim(n))) == 0
               for n in c.degrees() if c.dim(n) and c.dim(n - 2))

def betti(c):
    """Homology dimensions {n: dim H_n} (only nonzero ones)."""
    out = {}
    for n in c.degrees():
        h = c.dim(n) - rank(c.d(n)) - rank(c.d(n + 1))
        if h:
            out[n] = h
    return out

def block(top_left, top_right, bottom_left, bottom_right):
    """Assemble a 2 x 2 block matrix (blocks given as lists of rows)."""
    top = [l + r for l, r in zip(top_left, top_right)]
    bottom = [l + r for l, r in zip(bottom_left, bottom_right)]
    return top + bottom

def neg(m):
    return [[-x for x in row] for row in m]

def cone(f, x, y):
    """Mapping cone of f: X -> Y. Cone_n = X_(n-1) + Y_n with differential
    (u, v) |-> (-d u, f u + d v). ``f[n]`` is the matrix Y_n x X_n."""
    degrees = list(x.dims) + list(y.dims) + [0]
    dims = {n: x.dim(n - 1) + y.dim(n)
            for n in range(min(degrees), max(degrees) + 2)}
    diff = {}
    for n in dims:
        fm = f.get(n - 1, zeros(y.dim(n - 1), x.dim(n - 1)))
        diff[n] = block(neg(x.d(n - 1)), zeros(x.dim(n - 2), y.dim(n)),
                        fm, y.d(n))
    return Complex(dims, diff)

def is_chain_map(f, x, y):
    """Check d_Y f_n = f_(n-1) d_X in every degree."""
    get = lambda n: f.get(n, zeros(y.dim(n), x.dim(n)))
    return all(matmul(y.d(n), get(n), y.dim(n), x.dim(n)) ==
               matmul(get(n - 1), x.d(n), x.dim(n - 1), x.dim(n))
               for n in x.degrees() if x.dim(n) and y.dim(n - 1))

def q(*rows):
    """A matrix of Fractions from integer rows."""
    return [[Fraction(v) for v in row] for row in rows]

def mat(maps, n, rows, cols):
    """The degree-n component of a graded map, or the zero matrix."""
    return maps.get(n, zeros(rows, cols))

def is_homotopy(h, a, b, f, f2, x, y, x2, y2):
    """Check b f - f' a = d h + h d, for h_n: X_n -> Y'_(n+1)."""
    for n in x.degrees():
        if not (x.dim(n) and y2.dim(n)):
            continue
        cols = x.dim(n)
        bf = matmul(mat(b, n, y2.dim(n), y.dim(n)),
                    mat(f, n, y.dim(n), cols), y.dim(n), cols)
        fa = matmul(mat(f2, n, y2.dim(n), x2.dim(n)),
                    mat(a, n, x2.dim(n), cols), x2.dim(n), cols)
        dh = matmul(y2.d(n + 1), mat(h, n, y2.dim(n + 1), cols),
                    y2.dim(n + 1), cols)
        hd = matmul(mat(h, n - 1, y2.dim(n), x.dim(n - 1)), x.d(n),
                    x.dim(n - 1), cols)
        for i in range(y2.dim(n)):
            for j in range(x.dim(n)):
                if bf[i][j] - fa[i][j] != dh[i][j] + hd[i][j]:
                    return False
    return True

def cone_map(a, b, h, x, y, x2, y2):
    """The chain map Cone(f) -> Cone(f') induced by a square (a, b) and a
    homotopy h with b f - f' a = d h + h d: (u, v) |-> (a u, b v + h u)."""
    out = {}
    for n in range(min(x.degrees()[0], y.degrees()[0]),
                   max(x.degrees()[-1], y.degrees()[-1]) + 1):
        out[n] = block(mat(a, n - 1, x2.dim(n - 1), x.dim(n - 1)),
                       zeros(x2.dim(n - 1), y.dim(n)),
                       mat(h, n - 1, y2.dim(n), x.dim(n - 1)),
                       mat(b, n, y2.dim(n), y.dim(n)))
    return out

def square_example(h):
    """X = Q[0], Y = 0, X' = 0, Y' = Q[1], f = f' = 0, a = b = 0, and the
    homotopy h: X_0 -> Y'_1 given by the scalar h. Returns (is the homotopy
    valid, is the induced map a chain map of cones, its degree-1 matrix)."""
    x, y, x2, y2 = Complex({0: 1}), Complex({}), Complex({}), Complex({1: 1})
    f, f2, a, b = {}, {}, {}, {}
    hom = {0: [[Fraction(h)]]}
    phi = cone_map(a, b, hom, x, y, x2, y2)
    cf, cf2 = cone(f, x, y), cone(f2, x2, y2)
    return (is_homotopy(hom, a, b, f, f2, x, y, x2, y2),
            is_chain_map(phi, cf, cf2), phi[1])

EXAMPLES = {
    "Q[0] -> (Q =id=> Q), not a q-iso": (
        Complex({0: 1}), Complex({1: 1, 0: 1}, {1: q([1])}), {0: q([1])}),
    "Q[0] -> (Q -> Q^2), a q-iso": (
        Complex({0: 1}), Complex({1: 1, 0: 2}, {1: q([1], [0])}),
        {0: q([0], [1])}),
    "(Q =2=> Q) -> 0, both acyclic": (
        Complex({1: 1, 0: 1}, {1: q([2])}), Complex({}), {}),
}

def main():
    print("1. Cones detect quasi-isomorphisms (homology dims as {degree: dim})")
    for name, (x, y, f) in EXAMPLES.items():
        c = cone(f, x, y)
        print(f"   {name}")
        print(f"     chain map: {is_chain_map(f, x, y)}, Cone d^2 = 0: "
              f"{is_complex(c)}")
        print(f"     H(X) = {betti(x)}, H(Y) = {betti(y)}, "
              f"H(Cone) = {betti(c)}")
    print()
    print("2. One square up to homotopy, many maps on cones")
    print("   X = Q[0], Y = 0, X' = 0, Y' = Q[1], f = f' = 0, a = b = 0.")
    print("   Homotopy h: X_0 -> Y'_1 (any scalar: d h + h d = 0 = b f - f' a).")
    print("   Cone f = Q[1] = Cone f' (zero differentials), so H_1 = Cone_1.")
    for h in [0, 1, 2, Fraction(-1, 3)]:
        valid, chain, m = square_example(h)
        print(f"   h = {str(h):>4}: homotopy {valid}, chain map {chain}, "
              f"map on H_1 = multiply by {m[0][0]}")
    print("   In the homotopy category these are one square (a = b = 0), and each")
    print("   map is a valid TR3 fill: the triangulated axioms cannot choose one.")
    print()
    print("Epistemic status: exact rational arithmetic on small, explicit")
    print("complexes. It illustrates the general facts proved in the lesson")
    print("(cones detect quasi-isomorphisms; maps of cones depend on homotopies)")
    print("but checks only these examples.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

Used in