Skip to content

products

quiverlab.trace.products

Plan 35 -- the worked-steps chapter for the HH product surface.

products_chapter(A, kind, obj) turns a Task-1 product result object (an HHProducts for cup/cap/bracket, a ConnesB for connes_b) into the typed trace-event stream the renderers turn into a homework-grade chapter:

  • a prose StepNote naming the product and citing its source;
  • a ResultDims carrying the authoritative Hochschild (co)homology dimensions (rendered as the chapter's HH result table) -- and, in building it, the DRIFT GATE: every dimension the product object recorded is compared, degree by degree, against a FRESH hochschild_(co)homology on A; a mismatch raises QuiverlabError rather than shipping a chapter that misstates the computation (mirroring the module drift gates in trace.modules);
  • one ProductStep per bidegree -- the nonzero structure-constant equations for cup/cap/bracket, the induced Connes differential matrix for connes_b.

The gate is genuine, not decorative: it re-derives the HH dimensions INDEPENDENTLY of the (possibly corrupted) product object, so a tampered table dimension is caught.

Float-free: every number is an int or an exact domain-element string. The fixed per-kind definitional formula lives in render_html (the sole owner of the worked-steps math source); this module owns only the DATA-derived equations.

balanced_coeff

balanced_coeff(c, prime)

A GF(p) residue displayed as its BALANCED representative: c with c > p/2 (equivalently 2c > p) shows as c - p (so p-1 -> -1, p-2 -> -2). Display only -- the JSON keeps the raw residue string. A coefficient that is not a residue in [0, p) (a fraction, an out-of-range or already-signed value, or no prime at all) is returned verbatim.

Source code in src/quiverlab/trace/products.py
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
def balanced_coeff(c, prime):
    """A GF(p) residue displayed as its BALANCED representative: ``c`` with ``c > p/2``
    (equivalently ``2c > p``) shows as ``c - p`` (so p-1 -> -1, p-2 -> -2). Display
    only -- the JSON keeps the raw residue string. A coefficient that is not a residue
    in ``[0, p)`` (a fraction, an out-of-range or already-signed value, or no prime at
    all) is returned verbatim."""
    c = str(c)
    if prime is None:
        return c
    try:
        v = int(c)
    except ValueError:
        return c
    if 0 <= v < prime and 2 * v > prime:
        return str(v - prime)
    return c

balanced_rep_note

balanced_rep_note(prime)

The one-sentence legend for the balanced-representative display, or "" for a non-GF basis (nothing to balance).

Source code in src/quiverlab/trace/products.py
323
324
325
326
327
328
329
330
def balanced_rep_note(prime):
    """The one-sentence legend for the balanced-representative display, or ``""`` for a
    non-GF basis (nothing to balance)."""
    if prime is None:
        return ""
    return ("Coefficients are shown as balanced representatives mod %d (a residue "
            "c > %d/2 is written c-%d); the JSON record keeps the raw residues."
            % (prime, prime, prime))

beyond_window_note

beyond_window_note()

The legend for the em-dash honesty mark used by the big product table.

Source code in src/quiverlab/trace/products.py
469
470
471
472
def beyond_window_note():
    """The legend for the em-dash honesty mark used by the big product table."""
    return (EM_DASH + " marks a cell whose target degree lies beyond the computed "
            "window (not computed); a computed vanishing product is shown as 0.")

cayley_table

cayley_table(kind, degrees, out_degree, dims, constants, prime=None)

The structured Cayley multiplication table of one product bidegree: row labels (left classes α^p_i), column labels (right classes α^q_j / z^n_j for cap), the corner operator, and one TeX cell per entry -- the product in the target basis (α^out / z^out), balanced-rep coefficients. Presentation-agnostic: the HTML surfaces render it via render_html.cayley_grid_html, the GUI mirrors it. structural_note_line is carried alongside so a caller shows it above the grid.

Source code in src/quiverlab/trace/products.py
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
def cayley_table(kind, degrees, out_degree, dims, constants, prime=None):
    """The structured Cayley multiplication table of one product bidegree: row labels
    (left classes ``α^p_i``), column labels (right classes ``α^q_j`` / ``z^n_j`` for
    cap), the corner operator, and one TeX cell per entry -- the product in the target
    basis (``α^out`` / ``z^out``), balanced-rep coefficients. Presentation-agnostic:
    the HTML surfaces render it via ``render_html.cayley_grid_html``, the GUI mirrors
    it. ``structural_note_line`` is carried alongside so a caller shows it above the
    grid."""
    dl, dr, dout = dims
    left_deg, right_deg = degrees
    row_labels = [r"%s^{%d}_{%d}" % (_LEFT, left_deg, i + 1) for i in range(dl)]
    col_labels = [r"%s^{%d}_{%d}" % (_RIGHT[kind], right_deg, j + 1) for j in range(dr)]
    cells = [[cell_tex(kind, out_degree,
                       [constants[k][i][j] for k in range(dout)], prime)
              for j in range(dr)] for i in range(dl)]
    return {"corner": _CORNER[kind], "row_labels": row_labels,
            "col_labels": col_labels, "cells": cells, "dl": dl, "dr": dr,
            "note": structural_note_line(kind, degrees, dims, constants, prime)}

cell_tex

cell_tex(kind, out_degree, coeffs, prime)

One Cayley cell: the product expressed in the TARGET basis as Σ_k balanced(c_k)·g_k (g_k the k-th output generator), or "0" when every coefficient vanishes. coeffs is [constants[k][i][j] for k].

Source code in src/quiverlab/trace/products.py
351
352
353
354
355
356
357
358
359
360
361
def cell_tex(kind, out_degree, coeffs, prime):
    """One Cayley cell: the product expressed in the TARGET basis as
    ``Σ_k balanced(c_k)·g_k`` (``g_k`` the k-th output generator), or ``"0"`` when
    every coefficient vanishes. ``coeffs`` is ``[constants[k][i][j] for k]``."""
    pieces = []
    for k, c in enumerate(coeffs):
        disp = balanced_coeff(c, prime)
        if str(disp) == "0":
            continue
        pieces.append((disp, r"%s^{%d}_{%d}" % (_OUT[kind], out_degree, k + 1)))
    return _signed_join(pieces)

combined_cayley

combined_cayley(kind, tables, prime=None)

The ONE big Cayley table of a whole product family, from the per-bidegree tables (each a mapping with degrees / out_degree / dims / constants). Rows run over every left (cohomology) class degree-major; columns over every right class (cohomology for cup/bracket, homology for cap) degree-major.

Returns a struct {corner, row_labels, col_labels, row_degsep, col_degsep, cells, dl, dr, note, has_beyond} where each cell is "0" / a signed combination / the em dash (beyond the computed window). row_degsep[i] / col_degsep[j] flag the first class of a new degree block (skipping the very first) for the heavier grid rule. The table is UNCAPPED (Marco 2026-08-02): it always renders, however many classes the axes carry.

Source code in src/quiverlab/trace/products.py
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
def combined_cayley(kind, tables, prime=None):
    """The ONE big Cayley table of a whole product family, from the per-bidegree
    ``tables`` (each a mapping with ``degrees`` / ``out_degree`` / ``dims`` /
    ``constants``). Rows run over every left (cohomology) class degree-major; columns
    over every right class (cohomology for cup/bracket, homology for cap) degree-major.

    Returns a struct ``{corner, row_labels, col_labels, row_degsep, col_degsep,
    cells, dl, dr, note, has_beyond}`` where each cell is ``"0"`` / a signed
    combination / the em dash (beyond the computed window). ``row_degsep[i]`` /
    ``col_degsep[j]`` flag the first class of a new degree block (skipping the very
    first) for the heavier grid rule. The table is UNCAPPED (Marco 2026-08-02): it
    always renders, however many classes the axes carry."""
    tbl, left_dims, right_dims = {}, {}, {}
    for t in tables:
        p, q = t["degrees"]
        dl, dr, _ = t["dims"]
        tbl[(p, q)] = {"out_degree": t["out_degree"], "dims": list(t["dims"]),
                       "constants": t["constants"]}
        left_dims[p] = dl
        right_dims[q] = dr
    left_degs, right_degs = sorted(left_dims), sorted(right_dims)
    total_rows = sum(left_dims[p] for p in left_degs)
    total_cols = sum(right_dims[q] for q in right_degs)
    # The block's computed top degree: every recorded bidegree lands in HH^{out_degree}
    # (cup out=p+q, bracket out=p+q-1, cap out=n-p), and the recording covers EXACTLY
    # the in-window bidegrees, so the maximum recorded out_degree is the top. Classify
    # cells against it EXPLICITLY (out_degree > top) rather than by a missing key, so a
    # cell that is beyond the window (never computed) is told apart from a cell the
    # recording SHOULD carry -- if the latter is absent the recording broke, and we
    # raise instead of masquerading it as an em dash (Marco 2026-08-02).
    top = max((t["out_degree"] for t in tables), default=0)

    row_labels, row_degsep, row_meta = [], [], []
    for bi, p in enumerate(left_degs):
        for i in range(left_dims[p]):
            row_labels.append(r"%s^{%d}_{%d}" % (_LEFT, p, i + 1))
            row_degsep.append(i == 0 and bi > 0)
            row_meta.append((p, i))
    col_labels, col_degsep, col_meta = [], [], []
    for bj, q in enumerate(right_degs):
        for j in range(right_dims[q]):
            col_labels.append(r"%s^{%d}_{%d}" % (_RIGHT[kind], q, j + 1))
            col_degsep.append(j == 0 and bj > 0)
            col_meta.append((q, j))

    cells, has_beyond = [], False
    for (p, i) in row_meta:
        row = []
        for (q, j) in col_meta:
            target = _combined_out_degree(kind, p, q)
            if target < 0:                          # cap below degree 0: structural zero
                row.append("0")
                continue
            if target > top:                        # beyond the computed window
                if kind == "cap":
                    # cap target = n - p <= n <= top: structurally impossible.
                    raise QuiverlabError(
                        "combined Cayley cap table: cell (%d, %d) has target degree "
                        "%d > top %d, impossible for a cap (n-p <= n <= top) -- the "
                        "table data is inconsistent" % (p, q, target, top))
                has_beyond = True
                row.append(EM_DASH)
                continue
            t = tbl.get((p, q))
            if t is None:                           # in-window but not recorded: a bug
                raise QuiverlabError(
                    "combined Cayley table: in-window bidegree (%d, %d) (target degree "
                    "%d <= top %d) has no recorded structure constants -- the product "
                    "recording is incomplete" % (p, q, target, top))
            dout = t["dims"][2]
            row.append(cell_tex(kind, target,
                                [t["constants"][k][i][j] for k in range(dout)], prime))
        cells.append(row)

    return {"corner": _CORNER[kind],
            "row_labels": row_labels, "col_labels": col_labels,
            "row_degsep": row_degsep, "col_degsep": col_degsep,
            "cells": cells, "dl": total_rows, "dr": total_cols,
            "has_beyond": has_beyond,
            "note": _combined_note(kind, tbl, row_meta, prime)}

equation_lines

equation_lines(kind, degrees, out_degree, dims, constants)

A TeX equation per nonzero product left_i * right_j: the RHS lists the output generators with their exact structure-constant coefficients (the unit coefficient 1 is left implicit; every non-unit coefficient is shown, so every nonzero constant appears verbatim).

Operates on the RAW table data (degrees/out_degree/dims/ constants) rather than a ProductTable, so the SAME builder serves both the worked-steps chapter (from the frozen object, via _equation_lines) and the report's Computed-results block (from the serialized blocks() dict, via trace.results_html) -- one implementation, no divergent copy. Zero terms are skipped; a fully vanishing table yields [] (the caller emits the vanish note).

Source code in src/quiverlab/trace/products.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def equation_lines(kind, degrees, out_degree, dims, constants):
    """A TeX equation per nonzero product ``left_i * right_j``: the RHS lists the
    output generators with their exact structure-constant coefficients (the unit
    coefficient ``1`` is left implicit; every non-unit coefficient is shown, so
    every nonzero constant appears verbatim).

    Operates on the RAW table data (``degrees``/``out_degree``/``dims``/
    ``constants``) rather than a ``ProductTable``, so the SAME builder serves both
    the worked-steps chapter (from the frozen object, via ``_equation_lines``) and
    the report's Computed-results block (from the serialized ``blocks()`` dict, via
    ``trace.results_html``) -- one implementation, no divergent copy. Zero terms are
    skipped; a fully vanishing table yields ``[]`` (the caller emits the vanish
    note)."""
    dl, dr, dout = dims
    left_deg, right_deg = degrees
    lines = []
    for i in range(dl):
        for j in range(dr):
            terms = [_term(kind, constants[k][i][j], out_degree, k)
                     for k in range(dout) if str(constants[k][i][j]) != "0"]
            if terms:
                lines.append("%s = %s" % (
                    _lhs(kind, left_deg, i, right_deg, j), " + ".join(terms)))
    return lines

notation_legend

notation_legend(kind, degrees_note, basis)

One-sentence legend DEFINING the symbols the product-table equations use (Marco: "I need the definitions of alphas, betas, zetas..."). Shown immediately before each product family's tables in every render surface.

basis is the concrete recorded class-basis string (e.g. "bar/GF(7)" / "cs/...") taken from the result block -- the structure constants are basis-dependent, so the reader is told which basis; None (connes_b, which has no single basis string) omits it. degrees_note is an optional trailing clause (the degree indices p/q/n are otherwise per-table); appended when non-empty.

Plain prose with Unicode math glyphs (alpha/beta/gamma/z/w), rendered as escaped text by every surface -- it never needs typesetting. This is the SINGLE source of the legend text, consumed by render_html (via products_chapter) and results_html._product_tables_html; the GUI hardcodes the same wording.

Source code in src/quiverlab/trace/products.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def notation_legend(kind, degrees_note, basis):
    """One-sentence legend DEFINING the symbols the product-table equations use
    (Marco: "I need the definitions of alphas, betas, zetas..."). Shown immediately
    before each product family's tables in every render surface.

    ``basis`` is the concrete recorded class-basis string (e.g. ``"bar/GF(7)"`` /
    ``"cs/..."``) taken from the result block -- the structure constants are
    basis-dependent, so the reader is told which basis; ``None`` (connes_b, which has
    no single basis string) omits it. ``degrees_note`` is an optional trailing clause
    (the degree indices p/q/n are otherwise per-table); appended when non-empty.

    Plain prose with Unicode math glyphs (alpha/beta/gamma/z/w), rendered as escaped
    text by every surface -- it never needs typesetting. This is the SINGLE source of
    the legend text, consumed by ``render_html`` (via ``products_chapter``) and
    ``results_html._product_tables_html``; the GUI hardcodes the same wording."""
    on_basis = ("relative to the recorded basis %s" % basis if basis
                else "relative to the recorded class basis")
    # Marco 2026-07-31: UNIFORM notation, α^n_j = the j-th basis class of HH^n and
    # z^n_j = the j-th of HH_n (superscript = degree, subscript = index); β/γ/w are
    # gone. The legend names the symbols, states the product in that notation, and
    # points at the explicit per-degree listings (where each class is written as a
    # combination of the ordered basis elements). Coordinate vectors live in the JSON.
    _EXPLICIT = (" Each class is listed explicitly by degree above as a combination of "
                 "the ordered basis elements; the coordinate vectors are recorded in "
                 "the JSON.")
    if kind == "cup":
        s = ("α^n_j denotes the j-th basis class of HH^n (superscript = degree, "
             "subscript = index). Every table line states "
             "α^p_i ∪ α^q_j = Σ_k c·α^{p+q}_k, %s; "
             "the constants c are basis-dependent.%s" % (on_basis, _EXPLICIT))
    elif kind == "bracket":
        s = ("α^n_j denotes the j-th basis class of HH^n (superscript = degree, "
             "subscript = index). Every table line states "
             "[α^p_i, α^q_j] = Σ_k c·α^{p+q-1}_k in degree p+q−1, %s; "
             "the constants c are basis-dependent.%s" % (on_basis, _EXPLICIT))
    elif kind == "cap":
        s = ("α^p_j denotes the j-th basis class of HH^p (cohomology) and z^n_j the "
             "j-th of HH_n (homology). Every table line states "
             "α^p_i ∩ z^n_j = Σ_k c·z^{n-p}_k, %s; the constants c "
             "are basis-dependent.%s" % (on_basis, _EXPLICIT))
    elif kind == "connes_b":
        s = ("each induced Connes differential B_n: HH_n → HH_{n+1} is written "
             "on the recorded homology bases -- rows index HH_{n+1}, columns index "
             "HH_n; the entries are basis-dependent. The homology cycle classes z^n_j "
             "(z^n_j = the j-th basis class of HH_n) are listed explicitly by degree "
             "above as combinations of the ordered basis elements.")
    elif kind == "bv_operator":
        s = ("each induced BV operator Delta_n: HH^n → HH^{n-1} is written on the "
             "recorded cohomology bases -- rows index HH^{n-1}, columns index HH^n; "
             "the entries are basis-dependent. The cohomology classes α^n_j "
             "(α^n_j = the j-th basis class of HH^n) live in the recorded basis %s"
             % on_basis)
    else:
        raise QuiverlabError(
            "unknown product kind %r for the notation legend" % (kind,))
    return "%s (%s)" % (s, degrees_note) if degrees_note else s

prime_from_basis

prime_from_basis(basis)

The prime p of a recorded GF(p) basis string, else None (QQ / an extension-field repr -> coefficients shown verbatim, no balancing).

Source code in src/quiverlab/trace/products.py
296
297
298
299
300
301
302
def prime_from_basis(basis):
    """The prime ``p`` of a recorded GF(p) basis string, else ``None`` (QQ / an
    extension-field repr -> coefficients shown verbatim, no balancing)."""
    if not basis:
        return None
    m = _GF_RE.search(str(basis))
    return int(m.group(1)) if m else None

products_chapter

products_chapter(A, kind, obj)

The worked-steps event stream for the product kind computed as obj.

Raises QuiverlabError if obj's recorded dimensions drift from a fresh Hochschild (co)homology on A (the drift gate).

Source code in src/quiverlab/trace/products.py
155
156
157
158
159
160
161
162
163
164
165
166
167
def products_chapter(A, kind, obj):
    """The worked-steps event stream for the product ``kind`` computed as ``obj``.

    Raises ``QuiverlabError`` if ``obj``'s recorded dimensions drift from a fresh
    Hochschild (co)homology on ``A`` (the drift gate)."""
    if kind == "connes_b":
        return _connes_chapter(A, obj)
    if kind == "bv_operator":
        return _bv_chapter(A, obj)
    if kind in ("cup", "cap", "bracket"):
        return _table_chapter(A, kind, obj)
    raise QuiverlabError(
        "unknown product kind %r for the worked-steps chapter" % (kind,))

structural_note_line

structural_note_line(kind, degrees, dims, constants, prime)

The structural notes of one table as a single caption sentence, or "".

Source code in src/quiverlab/trace/products.py
418
419
420
421
422
423
424
def structural_note_line(kind, degrees, dims, constants, prime):
    """The structural notes of one table as a single caption sentence, or ``""``."""
    notes = structural_notes(kind, degrees, dims, constants, prime)
    if not notes:
        return ""
    s = "; ".join(notes)
    return s[0].upper() + s[1:] + "."

structural_notes

structural_notes(kind, degrees, dims, constants, prime)

Honest structural observations DERIVED from the constants (never asserted blindly):

  • "all squares are 0" when every diagonal product x·x vanishes;
  • "the table is graded-antisymmetric" / "...graded-commutative (symmetric)" when the table equals its sign-mirrored transpose, the sign read off the degrees by :func:_mirror_sign.

Only meaningful for cup/bracket on a SQUARE bidegree (p == q and dl == dr) -- the two operands then live in the same space, so the diagonal and the transpose make sense; [] otherwise. The sign-mirror note needs residue arithmetic and is emitted only over a GF(p) basis.

Source code in src/quiverlab/trace/products.py
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
def structural_notes(kind, degrees, dims, constants, prime):
    """Honest structural observations DERIVED from the constants (never asserted
    blindly):

      * ``"all squares are 0"`` when every diagonal product ``x·x`` vanishes;
      * ``"the table is graded-antisymmetric"`` / ``"...graded-commutative
        (symmetric)"`` when the table equals its sign-mirrored transpose, the sign read
        off the degrees by :func:`_mirror_sign`.

    Only meaningful for cup/bracket on a SQUARE bidegree (``p == q`` and
    ``dl == dr``) -- the two operands then live in the same space, so the diagonal and
    the transpose make sense; ``[]`` otherwise. The sign-mirror note needs residue
    arithmetic and is emitted only over a GF(p) basis."""
    if kind not in ("cup", "bracket"):
        return []
    p, q = degrees
    dl, dr, dout = dims
    if p != q or dl != dr or not dl:
        return []
    n = dl
    notes = []
    if all(str(constants[k][i][i]) == "0" for i in range(n) for k in range(dout)):
        notes.append("all squares are 0")
    if prime is not None:
        all_int = all(_is_int(constants[k][i][j])
                      for i in range(n) for j in range(n) for k in range(dout))
        if all_int:
            sign = _mirror_sign(kind, p, q)
            mirrored = all(
                (int(constants[k][i][j]) - sign * int(constants[k][j][i])) % prime == 0
                for i in range(n) for j in range(n) for k in range(dout))
            if mirrored:
                notes.append("the table is graded-antisymmetric" if sign == -1
                             else "the table is graded-commutative (symmetric)")
    return notes