Skip to content

ses

quiverlab.modules.ses

Short exact sequences, split test, pushout/pullback (Plan 37 / C1).

Thin exact-linear-algebra layer over :class:quiverlab.modules.morphism.ModuleHom: a :class:ShortExactSequence certifies its own exactness at construction (the rank identity that im f = ker g), :meth:ShortExactSequence.is_split solves for a section over the Hom(N, M) basis, and :func:pushout / :func:pullback realize the two universal squares as quotient / kernel of a biproduct. Float-free.

ShortExactSequence

ShortExactSequence(f: ModuleHom, g: ModuleHom, check=True)

0 -> L --f--> M --g--> N -> 0, exactness certified at construction: f mono, g epi, g.f = 0, and rank f + rank g = dim M (together these force im f = ker g).

Source code in src/quiverlab/modules/ses.py
21
22
23
24
25
26
27
28
29
30
31
32
def __init__(self, f: ModuleHom, g: ModuleHom, check=True):
    if f.tgt is not g.src:
        raise QuiverlabError("not composable: f.tgt is not g.src")
    self.f, self.g = f, g
    self.L, self.M, self.N = f.src, f.tgt, g.tgt
    if check:
        ok = (f.is_mono() and g.is_epi() and f.then(g).is_zero()
              and f.rank() + g.rank() == self.M.dim)
        if not ok:
            raise QuiverlabError(
                "sequence is not exact "
                "(mono/epi/g.f=0/rank identity failed)")

is_split

is_split() -> bool

True iff a section s: N -> M with s.then(g) = id_N exists. The composite s.then(g) is linear in s, so expand s over the Hom(N, M) basis and solve for coefficients hitting vec(id_N).

Source code in src/quiverlab/modules/ses.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def is_split(self) -> bool:
    """True iff a section ``s: N -> M`` with ``s.then(g) = id_N`` exists.
    The composite ``s.then(g)`` is linear in ``s``, so expand ``s`` over the
    ``Hom(N, M)`` basis and solve for coefficients hitting ``vec(id_N)``."""
    dom = self.M.domain
    n = self.N.dim
    if n == 0:
        return True                       # N = 0: 0 -> L -> M -> 0 splits (L ~ M)
    basis = hom_basis(self.N, self.M)
    if not basis:
        return False                      # N != 0 but no section can exist
    cols = []
    for s in basis:
        comp = s.then(self.g).matrix      # N -> N, n x n
        cols.append([comp[i][j] for j in range(n) for i in range(n)])
    ident = lm.identity(n, dom)
    target = [[ident[i][j]] for j in range(n) for i in range(n)]   # vec(id_N) column
    B = lm.cols_to_matrix(cols)           # (n*n) x len(basis)
    return lm.solve_columns(B, target, dom) is not None

pullback

pullback(f: ModuleHom, g: ModuleHom)

Pullback of B --f--> D <--g-- C (shared target D): P = ker(B (+) C --[f,-g]--> D). Returns (P, prB, prC) with prB.then(f) == prC.then(g) (the pullback square commutes).

Source code in src/quiverlab/modules/ses.py
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
def pullback(f: ModuleHom, g: ModuleHom):
    """Pullback of ``B --f--> D <--g-- C`` (shared target ``D``):
    ``P = ker(B (+) C --[f,-g]--> D)``. Returns ``(P, prB, prC)`` with
    ``prB.then(f) == prC.then(g)`` (the pullback square commutes)."""
    if f.tgt is not g.tgt:
        raise QuiverlabError("pullback needs a shared target")
    D, (iB, iC), (pB, pC) = direct_sum(f.src, g.src)
    dom = D.domain
    diff = ModuleHom(
        D, f.tgt,
        [[*f.matrix[i], *[dom.neg(x) for x in g.matrix[i]]]
         for i in range(f.tgt.dim)],
        check=False)                          # [f | -g] : B (+) C -> D
    P, iota = diff.kernel()
    return P, iota.then(pB), iota.then(pC)

pushout

pushout(f: ModuleHom, g: ModuleHom)

Pushout of B <--f-- A --g--> C (shared source A): P = (B (+) C) / <(f(a), -g(a))>. Returns (P, inB, inC) with f.then(inB) == g.then(inC) (the pushout square commutes).

Source code in src/quiverlab/modules/ses.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def pushout(f: ModuleHom, g: ModuleHom):
    """Pushout of ``B <--f-- A --g--> C`` (shared source ``A``):
    ``P = (B (+) C) / <(f(a), -g(a))>``. Returns ``(P, inB, inC)`` with
    ``f.then(inB) == g.then(inC)`` (the pushout square commutes)."""
    if f.src is not g.src:
        raise QuiverlabError("pushout needs a shared source")
    from quiverlab.modules.yoneda import _quotient_with_maps
    D, (iB, iC), _ = direct_sum(f.tgt, g.tgt)
    dom = D.domain
    A = f.src
    diag_cols = []
    for j in range(A.dim):
        fb = [f.matrix[i][j] for i in range(f.tgt.dim)]
        gc = [dom.neg(g.matrix[i][j]) for i in range(g.tgt.dim)]
        diag_cols.append(fb + gc)             # psi(a) = (f(a), -g(a)); a module map
    P, proj_mat, _ = _quotient_with_maps(D, diag_cols, dom, name="pushout")
    proj = ModuleHom(D, P, proj_mat, check=False)
    return P, iB.then(proj), iC.then(proj)