2 · Quivers and algebras¶
A quiver is a finite directed multigraph; its path algebra $kQ$ has the
paths as a basis, with multiplication given by concatenation. Quotienting by an
admissible ideal $I$ of relations gives a finite-dimensional algebra $kQ/I$,
and every basic finite-dimensional algebra over an algebraically closed field
is of this form (Gabriel). This notebook builds such algebras with quiverlab,
using only the public surface: Quiver, its .algebra(...) method, and the
starter families.
from quiverlab import Quiver, CC, GF
from quiverlab import truncated_polynomial, linear_path_algebra
from quiverlab import RelationError, AdmissibilityError, NotFiniteDimensionalError
Building a quiver¶
Quiver(vertices=[...], arrows={name: (source, target)}). Arrow names must be
identifiers, because they double as the tokens you write in relation strings.
Q = Quiver(vertices=[1, 2, 3], arrows={"a": (1, 2), "b": (2, 3), "c": (1, 3)})
print(Q)
Quiver with vertices [1, 2, 3] and arrows: 1 --a--> 2 2 --b--> 3 1 --c--> 3
Composition is LEFT TO RIGHT¶
This is the single convention that trips people up, so it is worth stating in
full. quiverlab reads paths left to right: the word a*b means
$$\text{"first } a\text{, then } b\text{"},$$
and it is defined precisely when $\operatorname{target}(a)=\operatorname{source}(b)$. This is the Assem–Simson–Skowroński convention. If you are used to writing $b\circ a$ for "first $a$, then $b$" (the function-composition order), mentally reverse every product you read here.
print("a then b (a: 1->2, b: 2->3):", Q.compose_ok(('a', 'b'))) # target(a)=2 = source(b)
print("a then c (a: 1->2, c: 1->3):", Q.compose_ok(('a', 'c'))) # target(a)=2 != source(c)=1
a then b (a: 1->2, b: 2->3): True a then c (a: 1->2, c: 1->3): False
a*b composes (it is the path $1\to2\to3$); a*c does not, because $a$
ends at vertex $2$ while $c$ starts at vertex $1$.
Relations and the relation parser¶
A relation is a string: terms joined by +/-, an optional exact rational
coefficient written first, and p^k to repeat an arrow $k$ times. Every
summand must be parallel (share source and target). A relation with a
single path is monomial; a monomial ideal is what this preview builds.
Here we impose $a\!\cdot\!b = 0$, killing the length-two path $1\to2\to3$.
A = Q.algebra(relations=["a*b"], field=CC)
print(A)
print("dimension:", A.dim)
Algebra of dimension 6 over CC (computing exactly in QQ) basis: e_1, e_2, e_3, a, b, c vertices: 1, 2, 3 arrows: a: 1 -> 2; b: 2 -> 3; c: 1 -> 3 relations: a*b dimension: 6
The basis is the trivial paths $e_1,e_2,e_3$ together with the paths that survive: $a,b,c$. The path $a\!\cdot\!b$ is gone, so $kQ/I$ is $6$-dimensional.
The parser is a gate, not a guesser¶
Malformed relations do not get silently repaired — they raise. A
non-composable product is a RelationError; a relation that is not inside the
square of the arrow ideal (a path of length $<2$) is an AdmissibilityError,
because such a relation would change the quiver rather than cut the algebra.
for rel, kind in [("a*c", RelationError), ("a", AdmissibilityError)]:
try:
Q.algebra(relations=[rel], field=CC)
except kind as err:
print(f"relations=[{rel!r}] ->", type(err).__name__)
print(" ", err, "\n")
relations=['a*c'] -> RelationError
path a*c is not composable: target(a) = 2 but source(c) = 1 [hint: paths compose left to right: a*b needs target(a) == source(b)]
relations=['a'] -> AdmissibilityError
relation a has a path of length 1: the ideal is not inside the square of the arrow ideal [hint: admissible relations use paths of length >= 2]
Certified finiteness¶
For a monomial presentation, $kQ/I$ is finite-dimensional iff there are only finitely many irreducible paths — words containing no forbidden path as a contiguous subword. quiverlab decides this with a suffix-window automaton. If infinitely many irreducible paths exist, it refuses to build the algebra and names an offending cycle, instead of looping forever or returning a fiction.
loop = Quiver(vertices=[1], arrows={"x": (1, 1)})
try:
loop.algebra(field=CC) # k[x], infinite-dimensional
except NotFiniteDimensionalError as err:
print(type(err).__name__, "\n ", err)
NotFiniteDimensionalError
kQ/I is infinite-dimensional: irreducible paths grow forever along the cycle x [hint: add relations killing a power of this cycle (monomial), or check your quiver]
The one-loop quiver with no relations is the polynomial algebra $k[x]$,
which is infinite-dimensional — and quiverlab says so, pointing at the
cycle x. Killing a power of the loop repairs it:
T = truncated_polynomial(3, field=CC) # k[x] / (x^3)
print(T)
Algebra of dimension 3 over CC (computing exactly in QQ) basis: e_1, x, x*x vertices: 1 arrows: x: 1 -> 1 relations: x*x*x
Now $x^3=0$, the irreducible paths are $e_1, x, x^2$, and the algebra is the finite-dimensional $k[x]/(x^3)$.
Starter families¶
Two building blocks are exposed directly:
truncated_polynomial(n)$= k[x]/(x^n)$ — the one-loop quiver with relation $x^n$ (a local, self-injective algebra);linear_path_algebra(n)$=$ the path algebra of $1\to2\to\cdots\to n$, which is hereditary and has dimension $\binom{n+1}{2}$.
L = linear_path_algebra(3, field=CC) # 1 -> 2 -> 3
print(L)
print("dimension:", L.dim, " = 3*4/2")
Algebra of dimension 6 over CC (computing exactly in QQ) basis: e_1, e_2, e_3, a1, a2, a1*a2 vertices: 1, 2, 3 arrows: a1: 1 -> 2; a2: 2 -> 3 dimension: 6 = 3*4/2
The Algebra object¶
An Algebra carries an exact structure-constant table; A.multiply(u, v)
multiplies two coordinate vectors in the labelled basis. Because composition is
left to right, $a_1\!\cdot\!a_2$ is the path $1\to2\to3$, while $a_2\!\cdot\!a_1$
is $0$ (not composable).
idx = {name: i for i, name in enumerate(L.basis_labels)}
def elt(name):
v = [L.domain.zero()] * L.dim
v[idx[name]] = L.domain.one()
return v
def support(v):
return [L.basis_labels[i] for i, c in enumerate(v) if not L.domain.is_zero(c)] or ["0"]
print("basis :", L.basis_labels)
print("a1 * a2 =", support(L.multiply(elt("a1"), elt("a2"))))
print("a2 * a1 =", support(L.multiply(elt("a2"), elt("a1"))))
basis : ['e_1', 'e_2', 'e_3', 'a1', 'a2', 'a1*a2'] a1 * a2 = ['a1*a2'] a2 * a1 = ['0']
This structure-constant table is exactly the input the Hochschild engine consumes. On to the (co)homology.
Next: 03 · Hochschild (co)homology.