Skip to content

cluster_morphism

quiverlab.tautilting.cluster_morphism

The tau-cluster morphism category W(A) + picture group (Plan 66 / R29).

A thin COMBINATORIAL layer over the merged Plan-45 tau-tilting engine, the Plan-64 wide-subcategory poset, and the Plan-65 Jasso tau-perpendicular reduction -- NO new representation theory (the bricks, wides and reductions are all inherited).

Objects and morphisms (Buan-Marsh, A category of wide subcategories, IMRN 2021):

  • objects = the tau-perpendicular wide subcategories of mod A; for a tau-tilting-FINITE A these are ALL the finitely many wide subcategories, so #objects == wide_subcategories(A).size (the Enomoto core-label-order count, P64 -- the R29 "object count = #wide" cross-engine oracle);
  • morphisms [U]: W -> W' = the support tau-rigid pairs U of W with W' = J_W(U) its tau-perpendicular category, graded by rank = |U|.

Everything is read straight off A's own g-fan (the fan of the support tau-rigid pairs -- a triangulated (n-1)-sphere): each face is a subset of the signed g-vector columns of a support tau-tilting facet, and the closed star of a face U (the faces V >= U, graded by |V| - |U|) IS the g-fan of the reduction C(U) (the Jasso link identity, a theorem). So

  • objects = the faces deduped by the wide J(U) they cut out (J(U) identified by the set of bricks it contains -- beta in J(U) iff Hom(M, beta) = 0, Hom(beta, tau M) = 0 and beta vanishes on the support of P, for the pair U = (M, P));
  • the per-object out-degree = |closedStar(U_W)| = #faces(C_W);
  • the Hanson-Igusa classifying-space cube complex face_vector has f_0 = #objects = #wide (one 0-cell per wide) and f_k = #(rank-k morphisms) = sum_W #(rank-k faces of C_W's g-fan) (one k-cell per rank-k morphism), sum(face_vector) = morphism_count, f_n = #sTt -- this is the ACTUAL topology, NOT the g-fan sphere (kept separately as g_fan_face_vector).

The picture group pi_1(|W(A)|) (Igusa-Todorov-Weyman arXiv:1609.02636; Hanson-Igusa): one generator per brick, one relation per rank-2 wide (commutation for k x k, an atom/pentagon for a connected kA_2 wide -- split by Ext^1 between the two simple bricks), and the abelianization by exact Smith normal form.

Honest gate (STRICTER than a bounded region; P65 M1). W(A) is a FINITE category iff A is tau-tilting-finite. A partial category / face vector / picture group off a budget-truncated (or status != "complete") exchange graph would be a LIE, so P66 is certified iff eg.is_complete and eg.status == "complete"; otherwise every invariant is None and the honest note is set.

Scope (inherited from P45/P64/P65). Runs over QQ by default; bricks / hom / tau / the exchange-graph BFS are rigorous over char 0 or char > dim (Dickson/CIW), a brick decides End(B) = k over the algebraically-closed / char-0 base (the GF(p^n) proper-division-ring caveat is honest-scope). Off scope the underlying engine refuses loudly.

Float-free: faces are frozenset of int tuples, counts are int, the abelianization is exact SNF over ZZ. All refusals are QuiverlabError.

References: buan_marsh_wide (IMRN 2021 -- the category), hanson_igusa (Comm. Alg. 2021 -- the cube complex + Nakayama K(pi,1) + picture group), igusa_todorov_weyman (arXiv:1609.02636 -- the presentation), igusa_todorov_cat0 (arXiv:2203.16679 -- hereditary-Dynkin K(pi,1) / honest-scope); enomoto_wide_ice / jasso_reduction / air_tau_tilting / demonet_iyama_jasso (P64/P65/P45).

PictureGroup dataclass

PictureGroup(algebra: object, generators: tuple, relations: tuple, num_generators: object, num_relations: object, num_atom: object, num_commutation: object, abelianization: tuple, abelianization_rank: object, is_kpi1: object, kpi1_reason: str, is_complete: bool, status: str, note: str)

The picture-group presentation pi_1(|W(A)|) as DATA (Igusa-Todorov-Weyman; Hanson-Igusa). See the module docstring; every invariant is None on a non-complete exchange graph.

TauClusterCategory dataclass

TauClusterCategory(algebra: object, objects: tuple, object_count: object, morphisms: tuple, morphism_count: object, out_degree: dict, face_vector: object, g_fan_face_vector: object, euler_characteristic: object, is_kpi1: object, kpi1_reason: str, is_complete: bool, status: str, note: str)

The finite category W(A) (Buan-Marsh IMRN 2021) + its Hanson-Igusa classifying space. See the module docstring for the field semantics; every invariant is None on a non-complete exchange graph (the honest refusal).

picture_group

picture_group(A, *, budget=512) -> PictureGroup

The picture-group presentation pi_1(|W(A)|) as DATA (Plan 66 / R29; Igusa-Todorov-Weyman arXiv:1609.02636; Hanson-Igusa): generators = the bricks (num_generators == #bricks), relations = the rank-2 wides (num_relations == #rank-2 wides, each typed commutation for k x k / atom for a connected kA_2 via Ext^1), and the abelianization by exact SNF (abelianization_rank = #bricks - rank(relation matrix) = #bricks - #(distinct extension bricks among the atoms), H2 -- NOT #bricks - #atom). Certified complete iff A is tau-tilting-finite; else an honest is_complete=False value with every invariant None.

Source code in src/quiverlab/tautilting/cluster_morphism.py
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
def picture_group(A, *, budget=512) -> PictureGroup:
    """The picture-group presentation ``pi_1(|W(A)|)`` as DATA (Plan 66 / R29;
    Igusa-Todorov-Weyman arXiv:1609.02636; Hanson-Igusa): generators = the bricks
    (``num_generators == #bricks``), relations = the rank-2 wides (``num_relations == #rank-2
    wides``, each typed ``commutation`` for ``k x k`` / ``atom`` for a connected ``kA_2`` via
    ``Ext^1``), and the abelianization by exact SNF (``abelianization_rank = #bricks -
    rank(relation matrix) = #bricks - #(distinct extension bricks among the atoms)``, H2 -- NOT
    ``#bricks - #atom``). Certified complete iff ``A`` is tau-tilting-finite; else an honest
    ``is_complete=False`` value with every invariant ``None``."""
    _require_quiver(A, "picture_group")
    eg = _cached_eg(A, budget)
    if not eg.is_complete or eg.status != "complete":
        return PictureGroup(
            algebra=A, generators=(), relations=(), num_generators=None, num_relations=None,
            num_atom=None, num_commutation=None, abelianization=(), abelianization_rank=None,
            is_kpi1=None, kpi1_reason=_KPI1_UNCERTIFIED, is_complete=False, status=eg.status,
            note=_INCOMPLETE_NOTE.format(status=eg.status, budget=budget))
    _prewarm_universe(A, eg)
    from quiverlab.modules.hom import hom_dim
    from quiverlab.tautilting.torsion import bricks as _bricks
    brick_list = list(_bricks(A, budget=budget))
    generators = tuple((i, _dvt(B), _std_name(B)) for i, B in enumerate(brick_list))

    def _orth(i, j):
        return hom_dim(brick_list[i], brick_list[j]) == 0 \
            and hom_dim(brick_list[j], brick_list[i]) == 0

    relations = []
    num_atom = num_commutation = 0
    for i, j in combinations(range(len(brick_list)), 2):
        if not _orth(i, j):
            continue
        typ, ext_id = _relation_type(A, i, j, brick_list)
        if typ == "atom":
            num_atom += 1
            word = _atom_word(i, j, ext_id, generators)
        else:
            num_commutation += 1
            word = _commutation_word(i, j, generators)
        relations.append((typ, (i, j), ext_id, word))
    relations = tuple(relations)
    factors, ab_rank = _abelianization(len(brick_list), relations)
    kpi1, reason = _kpi1_verdict(A)
    return PictureGroup(
        algebra=A, generators=generators, relations=relations,
        num_generators=len(brick_list), num_relations=len(relations), num_atom=num_atom,
        num_commutation=num_commutation, abelianization=factors, abelianization_rank=ab_rank,
        is_kpi1=kpi1, kpi1_reason=reason, is_complete=True, status="complete", note="")

tau_cluster_block

tau_cluster_block(A, *, budget=512) -> dict

The shared tau_cluster compute-kind payload (Plan 66 Task D): the category summary (objects, morphisms, ranks), the Hanson-Igusa classifying-space cube complex (face_vector, Euler characteristic, K(pi,1) verdict) alongside the g-fan/cluster SPHERE (g_fan_face_vector, f_n = #sTt), and the picture-group presentation (generators, typed relations, abelianization). Certified complete iff A is tau-tilting-finite; on an incomplete exchange graph category = picture_group = None and a note (never a partial-category lie). Consumed byte-identically by both runners (each adds citations).

Source code in src/quiverlab/tautilting/cluster_morphism.py
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
def tau_cluster_block(A, *, budget=512) -> dict:
    """The shared ``tau_cluster`` compute-kind payload (Plan 66 Task D): the category summary
    (objects, morphisms, ranks), the Hanson-Igusa classifying-space cube complex (``face_vector``,
    Euler characteristic, ``K(pi,1)`` verdict) alongside the ``g``-fan/cluster SPHERE
    (``g_fan_face_vector``, ``f_n = #sTt``), and the picture-group presentation (generators,
    typed relations, abelianization). Certified complete iff ``A`` is tau-tilting-finite; on an
    incomplete exchange graph ``category = picture_group = None`` and a ``note`` (never a
    partial-category lie). Consumed byte-identically by both runners (each adds ``citations``)."""
    if getattr(A, "quiver", None) is None:
        return {"kind": "tau_cluster",
                "error": ("the tau-cluster morphism category requires a quiver-presented "
                          "algebra kQ/I (a structure-constant-only algebra has no tau-tilting "
                          "/ wide-subcategory surface)"),
                "references": list(_REFERENCES)}
    n = len(list(A.quiver.vertices))
    C = tau_cluster_category(A, budget=budget)
    block = {
        "kind": "tau_cluster", "n": n,
        "complete": C.is_complete, "status": C.status,
        "references": list(_REFERENCES), "note": None,
    }
    if not C.is_complete:
        block["category"] = None
        block["picture_group"] = None
        block["note"] = C.note
        return block
    objects_by_rank = Counter(rank for (_oid, rank, _sb, _cw) in C.objects)
    morphisms_by_rank = {k: f for k, f in enumerate(C.face_vector)}
    block["category"] = {
        "object_count": C.object_count,
        "morphism_count": C.morphism_count,
        "objects_by_rank": {int(r): int(c) for r, c in sorted(objects_by_rank.items())},
        "morphisms_by_rank": {int(r): int(c) for r, c in sorted(morphisms_by_rank.items())},
        "face_vector": list(C.face_vector),
        "g_fan_face_vector": list(C.g_fan_face_vector),
        "euler_characteristic": C.euler_characteristic,
        "is_kpi1": C.is_kpi1, "kpi1_reason": C.kpi1_reason,
    }
    G = picture_group(A, budget=budget)
    gens = [_brick_entry(dv, name, A) for (_i, dv, name) in G.generators]
    rels = []
    for (typ, (i, j), ext_id, word) in G.relations:
        gi, gj = G.generators[i], G.generators[j]
        entry = {
            "type": typ,
            "simple_bricks": [_brick_entry(gi[1], gi[2], A), _brick_entry(gj[1], gj[2], A)],
            "ext_brick": (None if ext_id is None
                          else _brick_entry(G.generators[ext_id][1],
                                            G.generators[ext_id][2], A)),
            "word": word,
        }
        rels.append(entry)
    block["picture_group"] = {
        "num_generators": G.num_generators,
        "generators": gens,
        "num_relations": G.num_relations,
        "num_atom": G.num_atom,
        "num_commutation": G.num_commutation,
        "relations": rels,
        "abelianization": list(G.abelianization),
        "abelianization_rank": G.abelianization_rank,
    }
    return block

tau_cluster_category

tau_cluster_category(A, *, budget=512) -> TauClusterCategory

The tau-cluster morphism category W(A) (Plan 66 / R29; Buan-Marsh IMRN 2021 + Hanson-Igusa Comm. Alg. 2021): objects = the tau-perpendicular wide subcategories (object_count == wide_subcategories(A).size, cross-tied to P64), morphisms = support tau-rigid pairs of the source graded by rank (out-degree = closed star = #faces(C_W)), the Hanson-Igusa classifying-space face_vector (f_0 = #wide, f_k = #(rank-k morphisms)), the separate g_fan_face_vector (the g-fan SPHERE, f_n = #sTt), the Euler characteristic, and the theorem-anchored K(pi,1) verdict. Certified complete iff A is tau-tilting-finite; else an honest is_complete=False value with every invariant None (the P65 M1 loud refusal, never a partial-category lie).

Source code in src/quiverlab/tautilting/cluster_morphism.py
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
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
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
def tau_cluster_category(A, *, budget=512) -> TauClusterCategory:
    """The tau-cluster morphism category ``W(A)`` (Plan 66 / R29; Buan-Marsh IMRN 2021 +
    Hanson-Igusa Comm. Alg. 2021): objects = the tau-perpendicular wide subcategories
    (``object_count == wide_subcategories(A).size``, cross-tied to P64), morphisms = support
    tau-rigid pairs of the source graded by rank (out-degree = closed star = ``#faces(C_W)``),
    the Hanson-Igusa classifying-space ``face_vector`` (``f_0 = #wide``, ``f_k = #(rank-k
    morphisms)``), the separate ``g_fan_face_vector`` (the g-fan SPHERE, ``f_n = #sTt``), the
    Euler characteristic, and the theorem-anchored ``K(pi,1)`` verdict. Certified complete iff
    ``A`` is tau-tilting-finite; else an honest ``is_complete=False`` value with every
    invariant ``None`` (the P65 M1 loud refusal, never a partial-category lie)."""
    _require_quiver(A, "tau_cluster_category")
    eg, data = _build_gfan(A, budget)
    if data is None:
        # W(A) is infinite here -- the K(pi,1) verdict is moot (no finite classifying space),
        # so it is honestly None regardless of the algebra's Nakayama/Dynkin type.
        return TauClusterCategory(
            algebra=A, objects=(), object_count=None, morphisms=(), morphism_count=None,
            out_degree={}, face_vector=None, g_fan_face_vector=None,
            euler_characteristic=None, is_kpi1=None, kpi1_reason=_KPI1_UNCERTIFIED,
            is_complete=False, status=eg.status,
            note=_INCOMPLETE_NOTE.format(status=eg.status, budget=budget))
    n, faces, col_kind = data
    from quiverlab.tautilting.torsion import bricks as _bricks
    brick_list = list(_bricks(A, budget=budget))

    # 1. the wide of every face (dedup key), grouped -> objects.
    tables = _perp_tables(A, col_kind, brick_list)
    face_key = {U: _wide_key(U, col_kind, brick_list, tables) for U in faces}
    groups = {}
    for U, key in face_key.items():
        groups.setdefault(key, []).append(U)

    # 2. object ids (deterministic: by rank then key), rank = n - |U_W|, representative face.
    def _rank_of(key):
        return n - len(groups[key][0])
    keys_sorted = sorted(groups, key=lambda k: (_rank_of(k), tuple(sorted(k))))
    key_to_id = {k: i for i, k in enumerate(keys_sorted)}
    reps = {k: min(groups[k], key=lambda U: (len(U), tuple(sorted(U)))) for k in groups}

    # 3. closed star of each representative = C_W's g-fan (Jasso link identity); face_vector.
    def _graded_star(U):
        c = Counter()
        for V in faces:
            if U <= V:
                c[len(V) - len(U)] += 1
        return tuple(c[k] for k in range(max(c) + 1))

    objects = []
    out_degree = {}
    face_vec = Counter()
    star_of = {}
    for k in keys_sorted:
        oid = key_to_id[k]
        U_W = reps[k]
        rank = n - len(U_W)
        star = _graded_star(U_W)
        star_of[k] = star
        out_degree[oid] = sum(star)
        for kk, val in enumerate(star):
            face_vec[kk] += val
        objects.append((oid, rank, _simple_bricks(k, brick_list), star))
    face_vector = tuple(face_vec[kk] for kk in range(max(face_vec) + 1))
    object_count = len(groups)
    morphism_count = sum(out_degree.values())

    # 4. the explicit morphism list: each object W (rep U_W) -> J(V) for every face V >= U_W.
    morphisms = []
    for k in keys_sorted:
        src = key_to_id[k]
        U_W = reps[k]
        for V in faces:
            if U_W <= V:
                tgt = key_to_id[face_key[V]]
                extra = tuple(sorted(V - U_W))
                morphisms.append((src, tgt, len(V) - len(U_W), extra))
    morphisms = tuple(morphisms)

    # 5. loud self-certs (H1): f_0 == #wide, sum == morphism_count, f_n == #sTt; the P64 tie.
    stt = len(eg.vertices)
    g_fan = Counter(len(f) for f in faces)
    g_fan_face_vector = tuple(g_fan[kk] for kk in range(max(g_fan) + 1))
    from quiverlab.tautilting.congruence import wide_subcategories
    wide_size = wide_subcategories(A, budget=budget).size
    if object_count != wide_size:
        raise QuiverlabError(
            f"tau_cluster_category: enumerated {object_count} objects but "
            f"wide_subcategories(A).size == {wide_size} -- the tau-perpendicular enumeration "
            "or the Enomoto core-label order is wrong (they must agree: objects = #wide)",
            hint="report the algebra")
    if face_vector[0] != object_count:
        raise QuiverlabError(
            f"tau_cluster_category: face_vector[0]={face_vector[0]} != object_count="
            f"{object_count} -- f_0 must be #wide (H1); the g-fan sphere was re-emitted as "
            "the classifying space", hint="report the algebra")
    if sum(face_vector) != morphism_count:
        raise QuiverlabError(
            f"tau_cluster_category: sum(face_vector)={sum(face_vector)} != morphism_count="
            f"{morphism_count}", hint="report the algebra")
    if face_vector[-1] != stt or g_fan_face_vector[-1] != stt:
        raise QuiverlabError(
            f"tau_cluster_category: face_vector[-1]={face_vector[-1]}, "
            f"g_fan_face_vector[-1]={g_fan_face_vector[-1]} -- both top cells must equal "
            f"#sTt={stt}", hint="report the algebra")
    _composition_spotcheck(objects, morphisms)

    euler = sum((-1) ** kk * f for kk, f in enumerate(face_vector))
    kpi1, reason = _kpi1_verdict(A)
    return TauClusterCategory(
        algebra=A, objects=tuple(objects), object_count=object_count, morphisms=morphisms,
        morphism_count=morphism_count, out_degree=out_degree, face_vector=face_vector,
        g_fan_face_vector=g_fan_face_vector, euler_characteristic=euler, is_kpi1=kpi1,
        kpi1_reason=reason, is_complete=True, status="complete", note="")