04 — Hochschild (co)homology via the bar complex¶
The mathematics¶
For A unital over k, the normalized bar complex computes Hochschild (co)homology. Using the reduced space Abar = A/k.1, the chain space is C_n = A ⊗ Abar^{⊗n} with the Hochschild boundary b, and the cochain space is C^n = Hom_k(Abar^{⊗n}, A) with the coboundary d. Then dim HH_n = dim C_n − rank(b_n) − rank(b_{n+1}) and dim HH^n = dim C^n − rank(d^n) − rank(d^{n−1}). This is the exact but exponential "oracle" backend: dim C_n = m·(m−1)^n grows fast, so it is used on small algebras and over any field, and as the ground truth against which faster engines are checked.
How it is represented¶
Work in a unit-adapted algebra (Chapter 03), so 1_A = b_0 and Abar = span of basis vectors
1, ..., m−1. A cochain basis element is a tuple (s, J) (hochschild/bar.py,
_cochain_basis):
sis an integer in0..m−1— the output slot, the basis vector b_s that this cochain returns;Jis a tuple of n integers, each in1..m−1— the input reduced tensor b_{J[0]} ⊗ ... ⊗ b_{J[n−1]}.
So (s, J) is the elementary cochain that sends the reduced tensor J to b_s and every
other reduced tensor to 0. For a homology chain the same tuple shape (s, J) is reused,
now meaning b_s ⊗ b_{J[0]} ⊗ ... (boundary_matrix). Verified by execution: for k[x]/(x^2)
(m = 2, reduced basis just {x} = index 1),
C^0 basis = [(0, ()), (1, ())]
C^1 basis = [(0, (1,)), (1, (1,))]
C^2 basis = [(0, (1,1)), (1, (1,1))]
The coboundary is stored as a plain matrix D — a list of lists of field elements — with
one row per C^{n+1} basis element and one column per C^n basis element. A dict
row_index maps each output cochain to its row.
How the computation runs¶
coboundary_matrix(A, n, max_cells) fills D one contribution at a time. Reading the
normalized coboundary
(d f)(a_1,...,a_{n+1}) = a_1·f(a_2,...,a_{n+1}) + Σ_{i=1}^{n} (−1)^i f(...,a_i a_{i+1},...)
+ (−1)^{n+1} f(a_1,...,a_n)·a_{n+1}, the code loops over each input cochain column (s, J)
and each candidate output tensor K (an (n+1)-fold reduced tuple), and adds the three
kinds of term into D via a helper bump(t, K, ci, val) that writes val into row
(t, K), column ci:
- Front term
b_{K0}·f(K[1:]): fires whenK[1:] == J. It multiplies b_{K[0]} into the output using the table rowA.T[K[0]][s], and each nonzero coordinatetbumps row(t, K). - Interior terms i = 1..n: fire when K matches J everywhere except that positions
i−1, i of K contract to J's position i−1. The coefficient is the Abar component of
b_{K[i-1]}·b_{K[i]} — read straight from the table
A.T[K[i-1]][K[i]][J[i-1]]— with sign (−1)^i (the unit coordinate is excluded, i.e. the projection to Abar). - Back term
(−1)^{n+1} f(K[:n])·b_{K[n]}: fires whenK[:n] == J, multiplying by b_{K[n]} on the right viaA.T[s][K[n]], with the alternating sign.
hochschild_cohomology_dims(A, top, max_cells) first calls unit_adapted(), then for each
n takes rank(D, dom) over the domain (the exact rref of Chapter 01), and finally does the
Euler-characteristic bookkeeping: dim HH^n = m·(m−1)^n − rank(d^n) − rank(d^{n−1}). The
homology twin (boundary_matrix, hochschild_homology_dims) is identical in spirit with b
in place of d. Results are returned as an HHTable (hochschild/table.py) — a small
object that prints as HH^0 = ... HH^1 = ... and compares equal to another table with the
same kind and dimensions (this equality is what the cross-check batteries of Chapter 07
use).
The max_cells guard¶
Because dim C_n is exponential, a differential matrix can be astronomically large.
_check_cells(rows, cols, max_cells, what) computes rows * cols before allocating and
raises DepthLimitError if it exceeds max_cells (default 4,000,000). The message states
exactly which differential and how big it would have been, and the hint explains that the
bar oracle is exponential and deeper engines (Bardzell, minimal, Chouhy–Solotar) are the
right tool past this wall. So DepthLimitError is never a crash — it is the code telling
you the certified range ran out and pointing at the faster path.
A worked micro-example — one coboundary entry for k[x]/(x^3)¶
m = 3, unit-adapted basis {1, x, x^2}, reduced indices {1, 2} = {x, x^2}. Consider the
1-cochain f = (0, (1,)): it sends the reduced input x to b_0 = 1, and x^2 to 0. We compute
the column of d^1 for this f, at the output tensor K = (x, x) i.e. (1, 1).
By the formula, (d^1 f)(x ⊗ x) = x·f(x) − f(x·x) + f(x)·x. Now f(x) = 1 and f(x^2) = 0, so this is x·1 − f(x^2) + 1·x = x − 0 + x = 2x = 2·b_1. In the code:
- the front term fires (
K[1:] = (1,) = J): b_1·f(x) usesA.T[1][0] = [0,1,0], bumping +1 into row(1, (1,1)); - the interior term i = 1 needs the Abar component of x·x = x^2 at slot J[0] = x
(index 1):
A.T[1][1][1] = 0, so it contributes nothing (indeed f(x^2) = 0); - the back term fires (
K[:1] = (1,) = J): f(x)·b_1 usesA.T[0][1] = [0,1,0]with sign (−1)^{2} = +1, bumping +1 into row(1, (1,1)).
The two +1's add to the entry D[row (1,(1,1))][col (0,(1,))] = 2 — exactly the coefficient
of x in 2x. (This matrix was printed by running the code. Restricted to the output tensor
(x,x), the column has this single nonzero 2; the full column for (0,(1,)) also carries
D[(2,(1,2))] = 1 and D[(2,(2,1))] = 1 from the other output tensors.)
The product surface (Plan 35) — cup, cap, bracket, Connes B¶
On top of the (co)homology dimensions, the Tamarkin–Tsygan calculus is exposed as
structure-constant tables: for each pair of degrees, the product of a basis class
on the left with a basis class on the right, written back in the basis of the target
degree. Four public methods on Algebra return frozen result objects:
| method | operation | returns |
|---|---|---|
cup_products(top, engine="auto", max_cells=4_000_000) |
HH^p ⊗ HH^q → HH^{p+q}, all p+q ≤ top |
HHProducts |
cap_products(top, engine="auto", max_cells=4_000_000) |
HH^p ⊗ HH_n → HH_{n−p}, p ≤ n ≤ top |
HHProducts |
gerstenhaber_brackets(top, engine="auto", max_cells=4_000_000) |
HH^p ⊗ HH^q → HH^{p+q−1}, p,q ≥ 1 |
HHProducts |
connes_differentials(top, max_cells=4_000_000) |
induced B : HH_n → HH_{n+1}, 0 ≤ n < top |
ConnesB |
HHProducts.tables is a dict keyed by the degree pair (p, q) (for cap, (p, n))
whose values are ProductTables; a ProductTable carries dims = (dim_left,
dim_right, dim_out) and constants[k][i][j] — the coefficient of the k-th output
class in left_i · right_j, as an exact string (int mod p on the GF(p) route,
a Domain repr on the CS route; the float ban forbids anything else). ConnesB carries
hh_dims, the per-degree matrices (rows indexed by HH_{n+1}, columns by HH_n),
and their ranks.
Engine routing (core/algebra.py::_product_dispatch). engine="auto" uses the
GF(p) bar/tt-calculus route over a prime field (hochschild/products.py::
gfp_product_tables → engine/tt_calculus.py), and for a presented algebra over any
other exact Domain the Chouhy–Solotar native diagonal route
(resolutions_cs/products.py); over GF(p) it also falls back to CS when the bar route
hits DepthLimitError (except for the bracket, which has no CS route). engine="cs"
forces the CS route; engine="bar" forces the GF(p) tt facade (loud off GF(p)). The
Gerstenhaber bracket is GF(p)-only and window-bounded — the result records the
served window, and the degree-0 insertion action is out of scope. Connes B needs
no engine choice: GF(p) via the fast (b,B) engine, any other exact Domain via the
generic bar (b,B) mixed complex.
Basis-provenance caveat. The structure constants are basis-dependent — they
are read on whatever HH basis the route produced, and that basis is not canonical
across routes. Every product object therefore records which basis it used:
HHProducts.basis is a provenance string ("bar/GF(p)" or "cs/…") and ConnesB
carries its engine string. Do not compare raw constants across engines; the
cross-engine gate compares only basis-independent data (dims and the flattened rank of
the constants tensor mod p). Within one object the constants are exact and the
Gerstenhaber-algebra identities (graded-commutative associative cup, antisymmetric
cup-Leibniz bracket, cap module law, B²=0) hold on the nose — see the identity
batteries in tests/hochschild/test_products_identities.py.
Where to look in the code¶
| concept | file | function / class |
|---|---|---|
cochain / chain basis element (s, J) |
hochschild/bar.py |
_cochain_basis, _abar_tuples |
| public product methods | core/algebra.py |
cup_products, cap_products, gerstenhaber_brackets, connes_differentials |
| product result objects | hochschild/products.py |
HHProducts, ProductTable, ConnesB |
| GF(p) bar/tt product tables | hochschild/products.py |
gfp_product_tables |
| CS-native (any Domain) product tables | resolutions_cs/products.py |
cs_product_tables |
induced Connes B |
hochschild/products.py |
connes_b_tables |
| coboundary matrix, entry by entry | hochschild/bar.py |
coboundary_matrix (the bump helper) |
| boundary matrix (homology) | hochschild/bar.py |
boundary_matrix |
| dims from ranks | hochschild/bar.py |
hochschild_cohomology_dims, hochschild_homology_dims |
| the size guard | hochschild/bar.py |
_check_cells |
| the result table | hochschild/table.py |
HHTable |
| depth-limit exception | errors.py |
DepthLimitError |