From fc614cd82e6a430cbdf3efaad7ee1050858a60ff Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Thu, 9 Jul 2026 08:21:18 +0200 Subject: [PATCH 01/22] feat: strongly connected components --- docs/src/methods/quivers.md | 1 + src/QuiverTools.jl | 2 +- src/Quivers.jl | 48 +++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/src/methods/quivers.md b/docs/src/methods/quivers.md index c75cc59..3052a3f 100644 --- a/docs/src/methods/quivers.md +++ b/docs/src/methods/quivers.md @@ -22,6 +22,7 @@ indegree outdegree is_acyclic is_connected +strongly_connected_components is_sink is_source underlying_graph diff --git a/src/QuiverTools.jl b/src/QuiverTools.jl index 7fe84e3..0d7f668 100644 --- a/src/QuiverTools.jl +++ b/src/QuiverTools.jl @@ -39,7 +39,7 @@ export Quiver, HNType, LunaType, QuiverModuli, QuiverModuliSpace, QuiverModuliSt # Quivers export n_vertices, n_arrows, arrows, indegree, outdegree, is_acyclic, is_connected, is_sink, is_source, - underlying_graph, first_hochschild_cohomology + strongly_connected_components, underlying_graph, first_hochschild_cohomology # Constructors export kronecker_quiver, loop_quiver, jordan_quiver, subspace_quiver, diff --git a/src/Quivers.jl b/src/Quivers.jl index 349fb96..3a60a23 100644 --- a/src/Quivers.jl +++ b/src/Quivers.jl @@ -106,6 +106,54 @@ function is_connected(Q::Quiver) return all(p -> p > 0, paths) end +""" + strongly_connected_components(Q::Quiver) + +Compute the strongly connected components of `Q`. + +Two vertices belong to the same strongly connected component if and only if +they are connected by paths in both directions. The reachability relation is +computed as the reflexive-transitive closure of the adjacency relation, using the +Floyd--Warshall algorithm in its original, Boolean, form +[[Warshall](https://doi.org/10.1145/321105.321107)]; its ``O(n^3)`` running time +is not an issue for the quivers we consider. + +# Input + +- `Q::Quiver`: a quiver. + +# Output + +- a list of the strongly connected components, each given as the list of its vertices. + +# Examples + +```jldoctest +julia> strongly_connected_components(cyclic_quiver(3)) +1-element Vector{Vector{Int64}}: + [1, 2, 3] + +julia> strongly_connected_components(kronecker_quiver(3)) +2-element Vector{Vector{Int64}}: + [1] + [2] + +julia> strongly_connected_components(Quiver("1-2,2-1,2-3")) +2-element Vector{Vector{Int64}}: + [1, 2] + [3] +``` +""" +function strongly_connected_components(Q::Quiver) + n = n_vertices(Q) + # reflexive-transitive closure by Floyd--Warshall [doi:10.1145/321105.321107] + reachable = [i == j || Q.adjacency[i, j] > 0 for i in 1:n, j in 1:n] + for k in 1:n, i in 1:n, j in 1:n + reachable[i, j] |= reachable[i, k] && reachable[k, j] + end + return unique([findall(j -> reachable[i, j] && reachable[j, i], 1:n) for i in 1:n]) +end + """ indegree(Q::Quiver, j::Int) From 05ab9599e1bcfd16795707ab8850cde7b4e9da60 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Thu, 9 Jul 2026 08:21:45 +0200 Subject: [PATCH 02/22] feat: bocklandt reduction and coregularity of quiver settings --- docs/src/methods/representation-theory.md | 2 + src/QuiverTools.jl | 3 +- src/RepresentationTheory.jl | 212 ++++++++++++++++++++++ test/runtests.jl | 35 ++++ 4 files changed, 251 insertions(+), 1 deletion(-) diff --git a/docs/src/methods/representation-theory.md b/docs/src/methods/representation-theory.md index 77697f9..4c7e2a7 100644 --- a/docs/src/methods/representation-theory.md +++ b/docs/src/methods/representation-theory.md @@ -16,5 +16,7 @@ all_general_subdimension_vectors is_general_subdimension_vector canonical_decomposition in_fundamental_domain +bocklandt_reduction +is_coregular first_hochschild_cohomology ``` diff --git a/src/QuiverTools.jl b/src/QuiverTools.jl index 0d7f668..7d000c0 100644 --- a/src/QuiverTools.jl +++ b/src/QuiverTools.jl @@ -59,7 +59,8 @@ export is_general_subdimension_vector, all_general_subdimension_vectors # Representation theory export euler_form, euler_matrix, is_root, is_schur_root, is_real_root, is_imaginary_root, is_isotropic_root, - general_ext, general_hom, canonical_decomposition, in_fundamental_domain + general_ext, general_hom, canonical_decomposition, in_fundamental_domain, + bocklandt_reduction, is_coregular # Moduli export all_luna_types, is_luna_type, dimension_of_luna_stratum diff --git a/src/RepresentationTheory.jl b/src/RepresentationTheory.jl index 48bd9eb..a9f8db1 100644 --- a/src/RepresentationTheory.jl +++ b/src/RepresentationTheory.jl @@ -270,3 +270,215 @@ function in_fundamental_domain(Q::Quiver, d::AbstractVector{Int}; interior::Bool simple -> euler_form(Q, d, simple) + euler_form(Q, simple, d) <= bound, simples ) end + +######################################################################################## +# Bocklandt's reduction algorithm +######################################################################################## + +# One pass of the reduction steps R_I, R_II, R_III of [MR1929191] on a strongly +# connected quiver setting. Returns the new setting, or `nothing` if no step applies, +# i.e., if the setting is reduced in the sense of [Definition 3.1, MR1929191]. +# +# The setting is given by a plain adjacency matrix and dimension vector rather than a +# Quiver: the adjacency of a Quiver is an immutable static matrix whose size is a type +# parameter, so the repeated resizing done here would allocate a new type at every +# step, and calling the memoized euler_form on such throwaway quivers would pollute +# its cache. +function __bocklandt_step(A::Matrix{Int}, d::Vector{Int}) + n = length(d) + # \chi(d, e_v) and \chi(e_v, d), for e_v the unit vector at the vertex v + chi_in(v) = d[v] - sum(d[w] * A[w, v] for w in 1:n) + chi_out(v) = d[v] - sum(A[v, w] * d[w] for w in 1:n) + for v in 1:n + # R_I [Lemma 3.2, MR1929191]: remove a loopless vertex whose incoming or outgoing + # paths carry at most d[v] dimensions, shortcutting every path through it; a lone + # vertex is kept so that the reduced coregular settings are the three settings of + # [Theorem 1.1, MR1929191] + if A[v, v] == 0 && n > 1 && (chi_in(v) >= 0 || chi_out(v) >= 0) + keep = setdiff(1:n, v) + return A[keep, keep] + A[keep, v] * A[v, keep]', d[keep] + end + # R_II [Lemma 3.3, MR1929191]: remove all loops on a vertex of dimension 1 + if A[v, v] > 0 && d[v] == 1 + B = copy(A) + B[v, v] = 0 + return B, d + end + # R_III [Lemma 3.4, MR1929191]: on a vertex of dimension k >= 2 carrying a single + # loop and, besides the loop, a single incoming (resp. outgoing) arrow from + # (resp. to) a vertex of dimension 1, remove the loop and thicken that arrow to + # k parallel arrows + if A[v, v] == 1 && d[v] >= 2 && (chi_in(v) == -1 || chi_out(v) == -1) + B = copy(A) + B[v, v] = 0 + if chi_in(v) == -1 + u = findfirst(w -> w != v && A[w, v] > 0, 1:n) + B[u, v] = d[v] + else + u = findfirst(w -> w != v && A[v, w] > 0, 1:n) + B[v, u] = d[v] + end + return B, d + end + end + return nothing +end + +# fully reduce a strongly connected quiver setting, i.e., apply reduction steps until +# the setting is reduced in the sense of [Definition 3.1, MR1929191] +function __bocklandt_reduce(A::Matrix{Int}, d::Vector{Int}) + while (step = __bocklandt_step(A, d)) !== nothing + A, d = step + end + return A, d +end + +""" + bocklandt_reduction(Q::Quiver, d::AbstractVector{Int}) + +Reduce the quiver setting `(Q, d)` using the reduction steps of +[[MR1929191](https://mathscinet.ams.org/mathscinet/relay-station?mr=1929191)]. + +The ring of invariants of a quiver setting is the tensor product of those of its +strongly connected components, and vertices of dimension `0` do not contribute, so +these are discarded first, by [Lemma 2.4, MR1929191]. Each component is then +simplified using the three reduction steps of [Section 3, MR1929191], each of which +preserves the ring of invariants up to a polynomial factor: + +- ``R_I`` [Lemma 3.2, MR1929191]: a vertex ``v`` without loops with + ``\\chi(d, e_v) \\geq 0`` or ``\\chi(e_v, d) \\geq 0`` is removed, and every pair of + arrows ``u \\to v \\to w`` is replaced by an arrow ``u \\to w``; +- ``R_{II}`` [Lemma 3.3, MR1929191]: the loops on a vertex of dimension `1` are + removed; +- ``R_{III}`` [Lemma 3.4, MR1929191]: the unique loop on a vertex ``v`` of dimension + ``k \\geq 2`` with ``\\chi(d, e_v) = -1`` (resp. ``\\chi(e_v, d) = -1``) is removed, + and the unique incoming (resp. outgoing) non-loop arrow is replaced by ``k`` + parallel arrows. + +The result, to which no further reduction step applies, is *reduced* in the sense of +[Definition 3.1, MR1929191]; it is returned as the disjoint union of the reduced +components. By [Theorem 3.5, MR1929191] the input setting is coregular if and only if +the reduced setting is, which is what [`is_coregular`](@ref) exploits. + +# Input + +- `Q::Quiver`: a quiver. +- `d::AbstractVector{Int}`: a dimension vector. + +# Output + +- a dictionary with the reduced quiver `Q` and dimension vector `d`. + +# Examples + +The setting below is reduced by applying ``R_{III}``, ``R_I``, and ``R_{II}``, +in this order: + +```jldoctest +julia> Q = Quiver("1-2, 2-2, 2-1"); + +julia> setting = bocklandt_reduction(Q, [1, 2]); + +julia> setting["Q"] +Quiver with adjacency matrix [0;;] + +julia> setting["d"] +1-element Vector{Int64}: + 1 +``` + +A reduced setting is returned unchanged: + +```jldoctest +julia> setting = bocklandt_reduction(Quiver("1--2, 2--1"), [1, 1]); + +julia> setting["Q"] +Quiver with adjacency matrix [0 2; 2 0] + +julia> setting["d"] +2-element Vector{Int64}: + 1 + 1 +``` +""" +function bocklandt_reduction(Q::Quiver, d::AbstractVector{Int}) + length(d) == n_vertices(Q) || + throw(ArgumentError("dimension vector must have length $(n_vertices(Q))")) + all(di >= 0 for di in d) || + throw(ArgumentError("dimension vector must be non-negative")) + + # vertices of dimension 0 and arrows between different strongly connected components + # play no role in the invariant theory [Lemma 2.4, MR1929191] + A = Matrix{Int}(Q.adjacency) + vertices = support(d) + components = strongly_connected_components(Quiver(A[vertices, vertices])) + + reduced = [ + __bocklandt_reduce(A[vertices[c], vertices[c]], Vector{Int}(d[vertices[c]])) for + c in components + ] + return Dict( + "Q" => reduce( + disjoint_union, [Quiver(B) for (B, _) in reduced]; init=Quiver(zeros(Int, 0, 0)) + ), + "d" => reduce(vcat, [e for (_, e) in reduced]; init=Int[]), + ) +end + +""" + is_coregular(Q::Quiver, d::AbstractVector{Int}) + +Check whether the quiver setting `(Q, d)` is coregular, i.e., whether the ring of +invariants of the `d`-dimensional representation variety of `Q` is a polynomial ring. + +Equivalently, this checks whether the affine quotient variety parametrizing +`d`-dimensional semisimple representations of `Q` is smooth, in which case it is an +affine space, by [Theorem 2.1, MR1929191]. + +By [[Theorem 1.1, MR1929191](https://mathscinet.ams.org/mathscinet/relay-station?mr=1929191)] +this is the case if and only if every strongly connected component of the +[`bocklandt_reduction`](@ref) of `(Q, d)` is one of + +- a vertex without loops, +- a vertex with one loop, +- a vertex of dimension `2` with two loops. + +# Input + +- `Q::Quiver`: a quiver. +- `d::AbstractVector{Int}`: a dimension vector. + +# Output + +- whether the ring of invariants of the setting `(Q, d)` is a polynomial ring. + +# Examples + +The invariants of pairs of ``2 \\times 2`` matrices form a polynomial ring, but those +of pairs of ``3 \\times 3`` matrices do not, by +[[Procesi](https://mathscinet.ams.org/mathscinet/relay-station?mr=419491)]; +the former is the third reduced coregular setting of [Theorem 1.1, MR1929191]: + +```jldoctest +julia> is_coregular(jordan_quiver(2), [2]) +true + +julia> is_coregular(jordan_quiver(2), [3]) +false +``` + +For an acyclic quiver the quotient variety is a point, so the setting is coregular: + +```jldoctest +julia> is_coregular(kronecker_quiver(3), [2, 3]) +true +``` +""" +function is_coregular(Q::Quiver, d::AbstractVector{Int}) + setting = bocklandt_reduction(Q, d) + A, e = setting["Q"].adjacency, setting["d"] + return all( + length(c) == 1 && (A[c[1], c[1]] <= 1 || (A[c[1], c[1]], e[c[1]]) == (2, 2)) for + c in strongly_connected_components(setting["Q"]) + ) +end diff --git a/test/runtests.jl b/test/runtests.jl index 53467fb..dd76224 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -136,3 +136,38 @@ end; # ξ5 = (e_K^2, e_Kbar^2): two vertices, a 2-cycle, local dimension (2, 2) @test fingerprint(Dict(eK => [2], eKb => [2])) == ([2, 2], [0, 0], [0, 0, 1, 1], true) end; + +@testset "Bocklandt reduction" begin + # invariants of pairs of 2x2 matrices form a polynomial ring, of 3x3 they do not, + # and neither do those of triples of 2x2 matrices; a single matrix always does + @test is_coregular(jordan_quiver(2), [2]) + @test !is_coregular(jordan_quiver(2), [3]) + @test !is_coregular(jordan_quiver(3), [2]) + @test all(is_coregular(jordan_quiver(1), [n]) for n in 1:5) + + # for acyclic quivers the quotient variety is a point + @test is_coregular(kronecker_quiver(3), [2, 3]) + @test is_coregular(subspace_quiver(4), [1, 1, 1, 1, 2]) + + # settings I, II and IV of [Theorem 4.4, MR1929191] are coregular + @test is_coregular(Quiver("1-2, 2-1"), [4, 5]) # I + @test is_coregular(Quiver("1--2, 2--1"), [1, 2]) # II with k = 2 <= n = 2 + @test !is_coregular(Quiver("1--2, 2--1"), [1, 1]) # II fails for k = 2 > n = 1 + @test is_coregular(Quiver("1-2, 2-1, 2-3, 3-2"), [3, 2, 3]) # IV + + # the reduction combines R_III, R_I and R_II to a lone vertex of dimension 1 + setting = bocklandt_reduction(Quiver("1-2, 2-2, 2-1"), [1, 2]) + @test n_vertices(setting["Q"]) == 1 + @test n_arrows(setting["Q"]) == 0 + @test setting["d"] == [1] + + # a reduced setting is returned unchanged + setting = bocklandt_reduction(Quiver("1--2, 2--1"), [1, 1]) + @test Matrix(setting["Q"].adjacency) == [0 2; 2 0] + @test setting["d"] == [1, 1] + + # vertices of dimension 0 and arrows between strongly connected components are dropped + @test bocklandt_reduction(kronecker_quiver(3), [2, 0])["d"] == [2] + @test is_coregular(Quiver("1-1, 1-2, 2-2"), [2, 2]) + @test is_coregular(kronecker_quiver(3), [0, 0]) +end; From 8b308e79cf1a110428d4125d26da3fa686f1e845 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Thu, 9 Jul 2026 08:22:11 +0200 Subject: [PATCH 03/22] feat: smoothness of moduli spaces via local quivers and coregularity --- src/Moduli.jl | 38 +++++++++++++++++++++++++++++++++++++- test/runtests.jl | 10 ++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/Moduli.jl b/src/Moduli.jl index 78cdd03..a9f3027 100644 --- a/src/Moduli.jl +++ b/src/Moduli.jl @@ -740,6 +740,15 @@ end Checks if the moduli space is smooth. +In the presence of properly semistable representations, the moduli space is +étale-locally isomorphic, around a polystable representation, to the affine quotient of +the corresponding local quiver setting near the zero representation, by +[[MR1972892](https://mathscinet.ams.org/mathscinet/relay-station?mr=1972892)]. +Following the strategy of +[[Theorem 4.2, MR1929191](https://mathscinet.ams.org/mathscinet/relay-station?mr=1929191)], +the moduli space is thus smooth if and only if the local quiver setting of every Luna +type is coregular, which is checked using [`is_coregular`](@ref). + # Input - `M::QuiverModuliSpace`: a moduli space of representations of a quiver. @@ -754,6 +763,26 @@ Setups with `d` `theta`-coprime are smooth: ```jldoctest julia> Q = kronecker_quiver(3); M = QuiverModuliSpace(Q, [2, 3]); +julia> is_smooth(M) +true +``` + +For the 3-Kronecker quiver and `d = (3, 3)` the moduli space is singular, whereas for +`d = (2, 2)` and `d = (2, 4)` one gets ``\\mathbb{P}^5``, despite the presence of +properly semistable representations: +```jldoctest +julia> M = QuiverModuliSpace(kronecker_quiver(3), [3, 3]); + +julia> is_smooth(M) +false + +julia> M = QuiverModuliSpace(kronecker_quiver(3), [2, 2]); + +julia> is_smooth(M) +true + +julia> M = QuiverModuliSpace(kronecker_quiver(3), [2, 4]); + julia> is_smooth(M) true ``` @@ -765,7 +794,14 @@ function is_smooth(M::QuiverModuliSpace) return true end - throw(NotImplementedError("Not implemented for properly semistable cases.")) + # smoothness at the polystable points of a Luna stratum is equivalent to + # coregularity of its local quiver setting, by combining the étale-local description + # of [MR1972892] with [Theorem 2.1, MR1929191]; this is the globalization of + # [Theorem 4.2, MR1929191] to arbitrary stability parameters + return all(all_luna_types(M)) do tau + setting = local_quiver_setting(M, tau) + is_coregular(setting["Q"], setting["d"]) + end end """ diff --git a/test/runtests.jl b/test/runtests.jl index dd76224..293a45c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -170,4 +170,14 @@ end; @test bocklandt_reduction(kronecker_quiver(3), [2, 0])["d"] == [2] @test is_coregular(Quiver("1-1, 1-2, 2-2"), [2, 2]) @test is_coregular(kronecker_quiver(3), [0, 0]) + + # smoothness of moduli spaces with properly semistable representations: for the + # 2-Kronecker quiver and d = (2, 2) one gets P^2, and for the 3-Kronecker quiver + # both d = (2, 2) and d = (2, 4) give P^5: the deepest local quiver setting is two + # loops on a vertex of dimension 2, the reduced coregular setting C1 of [MR1929191]; + # for d = (3, 3) that setting has dimension 3 instead, so the space is singular + @test is_smooth(QuiverModuliSpace(kronecker_quiver(2), [2, 2])) + @test is_smooth(QuiverModuliSpace(kronecker_quiver(3), [2, 2])) + @test is_smooth(QuiverModuliSpace(kronecker_quiver(3), [2, 4])) + @test !is_smooth(QuiverModuliSpace(kronecker_quiver(3), [3, 3])) end; From 8698de4699418424fd195cef0a9f47019cb49d35 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Thu, 9 Jul 2026 08:33:26 +0200 Subject: [PATCH 04/22] test: smoothness on walls for the 6-subspace quiver --- test/runtests.jl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index 293a45c..1d1a4c1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -180,4 +180,16 @@ end; @test is_smooth(QuiverModuliSpace(kronecker_quiver(3), [2, 2])) @test is_smooth(QuiverModuliSpace(kronecker_quiver(3), [2, 4])) @test !is_smooth(QuiverModuliSpace(kronecker_quiver(3), [3, 3])) + + # the 6-subspace quiver with d = (1^5, 2; 3) and stability parameters on a wall: + # for theta = (1^5, 2; -3) the moduli space is accidentally isomorphic to Gr(2, 4), + # hence smooth despite the eleven Luna strata, whereas for theta = (2^5, 1; -4) + # there are ten isolated singular points, one for each two-element subset of the + # five thin subspace vertices + S = subspace_quiver(6) + d = [1, 1, 1, 1, 1, 2, 3] + @test is_smooth(QuiverModuliSpace(S, d, [1, 1, 1, 1, 1, 2, -3])) + @test !is_smooth(QuiverModuliSpace(S, d, [2, 2, 2, 2, 2, 1, -4])) + # for d = (1^4, 2^2; 3) the analogous first wall crossing has smooth target too + @test is_smooth(QuiverModuliSpace(S, [1, 1, 1, 1, 2, 2, 3], [2, 2, 2, 2, 1, 1, -4])) end; From 156adef8551cc8b32d498a3fcf54c83825e532c3 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Thu, 9 Jul 2026 08:36:18 +0200 Subject: [PATCH 05/22] test: the Segre cubic is singular --- test/runtests.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index 1d1a4c1..a92314a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -192,4 +192,9 @@ end; @test !is_smooth(QuiverModuliSpace(S, d, [2, 2, 2, 2, 2, 1, -4])) # for d = (1^4, 2^2; 3) the analogous first wall crossing has smooth target too @test is_smooth(QuiverModuliSpace(S, [1, 1, 1, 1, 2, 2, 3], [2, 2, 2, 2, 1, 1, -4])) + + # the Segre cubic threefold, as the moduli space for the 6-subspace quiver with + # d = (1^6; 2) and canonical stability: it has ten nodes, one for each splitting + # of the six thin subspace vertices into complementary triples + @test !is_smooth(QuiverModuliSpace(S, [1, 1, 1, 1, 1, 1, 2])) end; From c8d0541f2fab8075cf60d8bb0e6f5894a00c02f9 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Thu, 9 Jul 2026 08:46:00 +0200 Subject: [PATCH 06/22] feat: codimension of the singular locus --- docs/src/methods/quiver-moduli.md | 1 + src/Moduli.jl | 52 +++++++++++++++++++++++++++++++ src/QuiverTools.jl | 3 +- test/runtests.jl | 8 +++++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/docs/src/methods/quiver-moduli.md b/docs/src/methods/quiver-moduli.md index 4ee95d4..0a7f255 100644 --- a/docs/src/methods/quiver-moduli.md +++ b/docs/src/methods/quiver-moduli.md @@ -26,6 +26,7 @@ Black-box methods are provided to study some of their properties. is_nonempty dimension is_smooth +codimension_singular_locus is_projective index motive diff --git a/src/Moduli.jl b/src/Moduli.jl index a9f3027..1b096de 100644 --- a/src/Moduli.jl +++ b/src/Moduli.jl @@ -804,6 +804,58 @@ function is_smooth(M::QuiverModuliSpace) end end +""" + codimension_singular_locus(M::QuiverModuliSpace) + +Computes the codimension of the singular locus of the moduli space. + +The singular locus is a union of Luna strata: all points of the stratum of a Luna type +are singular if the corresponding local quiver setting is not coregular, and smooth +otherwise, as in [`is_smooth`](@ref). Unlike for moduli of vector bundles on a curve, +the singular locus can be strictly smaller than the locus of properly semistable +representations, whose codimension is bounded by that of the singular locus. + +# Input + +- `M::QuiverModuliSpace`: a moduli space of representations of a quiver. + +# Output + +- the codimension of the singular locus, or `Inf` if the moduli space is smooth. + +# Examples + +The Segre cubic threefold, with its ten singular points: +```jldoctest +julia> M = QuiverModuliSpace(subspace_quiver(6), [1, 1, 1, 1, 1, 1, 2]); + +julia> codimension_singular_locus(M) +3 +``` + +For the 3-Kronecker quiver and `d = (2, 2)` the properly semistable locus is non-empty +yet the moduli space is smooth, whilst for `d = (3, 3)` there are singularities: +```jldoctest +julia> codimension_singular_locus(QuiverModuliSpace(kronecker_quiver(3), [2, 2])) +Inf + +julia> codimension_singular_locus(QuiverModuliSpace(kronecker_quiver(3), [3, 3])) +3 +``` +""" +function codimension_singular_locus(M::QuiverModuliSpace) + M.condition == "stable" && return Inf + + # the stratum of a Luna type consists of singular points if and only if its local + # quiver setting is not coregular; the stable stratum is always smooth + singular = filter(all_luna_types(M)) do tau + setting = local_quiver_setting(M, tau) + !is_coregular(setting["Q"], setting["d"]) + end + isempty(singular) && return Inf + return dimension(M) - maximum(dimension_of_luna_stratum(M, tau) for tau in singular) +end + """ is_smooth(M::QuiverModuliStack) diff --git a/src/QuiverTools.jl b/src/QuiverTools.jl index 7d000c0..3647cd7 100644 --- a/src/QuiverTools.jl +++ b/src/QuiverTools.jl @@ -64,7 +64,8 @@ export euler_form, euler_matrix, is_root, is_schur_root, is_real_root, is_imagin # Moduli export all_luna_types, is_luna_type, dimension_of_luna_stratum -export is_nonempty, codimension_unstable_locus, dimension, is_smooth, +export is_nonempty, codimension_unstable_locus, codimension_singular_locus, dimension, + is_smooth, is_projective, is_strongly_amply_stable, semistable_equals_stable, semisimple_moduli_space # Hodge diff --git a/test/runtests.jl b/test/runtests.jl index a92314a..20818ac 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -197,4 +197,12 @@ end; # d = (1^6; 2) and canonical stability: it has ten nodes, one for each splitting # of the six thin subspace vertices into complementary triples @test !is_smooth(QuiverModuliSpace(S, [1, 1, 1, 1, 1, 1, 2])) + + # codimension of the singular locus: the ten nodes of the Segre cubic; for the + # 3-Kronecker quiver and d = (2, 2) the properly semistable locus is non-empty + # while the singular locus is empty, and for d = (3, 3) the largest singular + # Luna stratum has codimension 3 in the 10-dimensional moduli space + @test codimension_singular_locus(QuiverModuliSpace(S, [1, 1, 1, 1, 1, 1, 2])) == 3 + @test codimension_singular_locus(QuiverModuliSpace(kronecker_quiver(3), [2, 2])) == Inf + @test codimension_singular_locus(QuiverModuliSpace(kronecker_quiver(3), [3, 3])) == 3 end; From c4448d00b943c34d676d1f1b1e0b7c454a031b23 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Thu, 9 Jul 2026 09:35:28 +0200 Subject: [PATCH 07/22] feat: cofree quiver settings --- docs/src/methods/representation-theory.md | 1 + src/QuiverTools.jl | 2 +- src/RepresentationTheory.jl | 258 ++++++++++++++++++++++ test/runtests.jl | 44 ++++ 4 files changed, 304 insertions(+), 1 deletion(-) diff --git a/docs/src/methods/representation-theory.md b/docs/src/methods/representation-theory.md index 4c7e2a7..078f312 100644 --- a/docs/src/methods/representation-theory.md +++ b/docs/src/methods/representation-theory.md @@ -18,5 +18,6 @@ canonical_decomposition in_fundamental_domain bocklandt_reduction is_coregular +is_cofree first_hochschild_cohomology ``` diff --git a/src/QuiverTools.jl b/src/QuiverTools.jl index 3647cd7..afcab41 100644 --- a/src/QuiverTools.jl +++ b/src/QuiverTools.jl @@ -60,7 +60,7 @@ export is_general_subdimension_vector, all_general_subdimension_vectors export euler_form, euler_matrix, is_root, is_schur_root, is_real_root, is_imaginary_root, is_isotropic_root, general_ext, general_hom, canonical_decomposition, in_fundamental_domain, - bocklandt_reduction, is_coregular + bocklandt_reduction, is_coregular, is_cofree # Moduli export all_luna_types, is_luna_type, dimension_of_luna_stratum diff --git a/src/RepresentationTheory.jl b/src/RepresentationTheory.jl index a9f8db1..5a188d3 100644 --- a/src/RepresentationTheory.jl +++ b/src/RepresentationTheory.jl @@ -482,3 +482,261 @@ function is_coregular(Q::Quiver, d::AbstractVector{Int}) c in strongly_connected_components(setting["Q"]) ) end + +######################################################################################## +# Cofree quiver settings +######################################################################################## + +# Everything below implements the classification of cofree quiver settings of +# Bocklandt--Van de Weyer [doi:10.1016/j.jalgebra.2007.08.019]: wedge away vertices +# using their reduction step W, split into prime components, and compare against the +# list of Theorem 1, whose members are recognized by the criteria of Theorems 5, 6, 8 +# and 9. Paths and cycles are quasiprimitive throughout: they use every vertex w as a +# source of at most d[w] arrows. + +# The number of quasiprimitive cycles through v, counted with arrow multiplicities and +# capped at cap + 1 to bound the enumeration; only used when every such cycle passes +# through v exactly once (it also passes through a vertex of dimension 1), so that +# counting closed walks anchored at v is correct. +function __n_quasiprimitive_cycles(A::Matrix{Int}, d::Vector{Int}, v::Int, cap::Int) + n = length(d) + budget = copy(d) + total = Ref(0) + function walk(x::Int, mult::Int) + (total[] > cap || budget[x] == 0) && return nothing + budget[x] -= 1 + for y in 1:n + A[x, y] == 0 && continue + y == v ? (total[] += mult * A[x, y]) : walk(y, mult * A[x, y]) + end + budget[x] += 1 + return nothing + end + walk(v, 1) + return total[] +end + +# One application of the wedging step W of [doi:10.1016/j.jalgebra.2007.08.019] to a +# vertex of dimension at least 2: a vertex whose unique outgoing (resp. incoming) +# arrow ends (resp. starts) at a vertex of dimension 1 is removed, redirecting its +# other arrows to that vertex, provided its dimension is at least the number of +# quasiprimitive cycles through it. Returns the new setting, or `nothing`. +# Wedging preserves cofreeness in both directions [Lemma 3]. +function __wedge_step(A::Matrix{Int}, d::Vector{Int}) + n = length(d) + for v in 1:n + (d[v] >= 2 && A[v, v] == 0) || continue + outs, ins = findall(>(0), A[v, :]), findall(>(0), A[:, v]) + wedge_out = length(outs) == 1 && A[v, outs[1]] == 1 && d[outs[1]] == 1 + wedge_in = length(ins) == 1 && A[ins[1], v] == 1 && d[ins[1]] == 1 + (wedge_out || wedge_in) || continue + __n_quasiprimitive_cycles(A, d, v, d[v]) <= d[v] || continue + B = copy(A) + wedge_out ? (B[:, outs[1]] .+= A[:, v]) : (B[ins[1], :] .+= A[v, :]) + keep = setdiff(1:n, v) + return B[keep, keep], d[keep] + end + return nothing +end + +# Split a strongly connected quiver setting into its prime components, i.e., the +# summands of its decomposition as an iterated connected sum at vertices of +# dimension 1; a setting is cofree iff its prime components are [Lemma 3]. +function __prime_components(A::Matrix{Int}, d::Vector{Int}) + n = length(d) + for v in 1:n + (d[v] == 1 && n + A[v, v] >= 2) || continue + # weakly connected components of the quiver minus v; each one, together with v and + # the arrows between them, is a summand, as is every loop at v + others = setdiff(1:n, v) + reachable = [ + i == j || A[others[i], others[j]] + A[others[j], others[i]] > 0 + for i in eachindex(others), j in eachindex(others) + ] + for k in eachindex(others), i in eachindex(others), j in eachindex(others) + reachable[i, j] |= reachable[i, k] && reachable[k, j] + end + pieces = unique([findall(reachable[i, :]) for i in eachindex(others)]) + length(pieces) + A[v, v] >= 2 || continue + out = Vector{Tuple{Matrix{Int},Vector{Int}}}() + for piece in pieces + keep = sort(vcat(others[piece], v)) + B = A[keep, keep] + B[findfirst(==(v), keep), findfirst(==(v), keep)] = 0 + append!(out, __prime_components(B, d[keep])) + end + append!(out, (fill(1, 1, 1), [1]) for _ in 1:A[v, v]) + return out + end + return [(A, d)] +end + +# [Theorem 6]: a strongly connected setting with a vertex v of dimension 1 through +# which all cycles run is cofree iff every other vertex w satisfies +# d[w] >= #{quasiprimitive paths v -> w} + #{quasiprimitive paths w -> v} - 1. +# The quiver minus v is acyclic here, so these paths are counted by powers of the +# adjacency matrix with v deleted, and quasiprimitivity is automatic. +function __is_cofree_through_vertex(A::Matrix{Int}, d::Vector{Int}, v::Int) + n = length(d) + B = copy(A) + B[v, :] .= 0 + B[:, v] .= 0 + S = sum(B^k for k in 0:(n - 1)) + return all( + d[w] >= + sum(A[v, x] * S[x, w] for x in 1:n) + sum(S[w, x] * A[x, v] for x in 1:n) - 1 for + w in 1:n if w != v + ) +end + +# Decide cofreeness of a prime strongly connected setting by recognizing the members +# of the list of [Theorem 1, doi:10.1016/j.jalgebra.2007.08.019]. +function __is_cofree_prime(A::Matrix{Int}, d::Vector{Int}) + n = length(d) + ins, outs = [sum(A[:, i]) for i in 1:n], [sum(A[i, :]) for i in 1:n] + + # a single vertex: no arrows, a cyclic quiver (one loop), any number of loops on a + # vertex of dimension 1, or the setting Q_2 (two loops on a vertex of dimension 2) + n == 1 && return A[1, 1] <= 1 || d[1] == 1 || (A[1, 1], d[1]) == (2, 2) + + # (iii) cyclic quiver settings are always cofree [Theorem 5] + all(ins[i] == 1 && outs[i] == 1 for i in 1:n) && return true + + # (i) all cycles run through a vertex of dimension 1 [Theorem 6] + for v in filter(v -> d[v] == 1, 1:n) + B = copy(A) + B[v, :] .= 0 + B[:, v] .= 0 + all(==(0), B^n) && return __is_cofree_through_vertex(A, d, v) + end + + # the remaining members of the list, (ii) and (iv), consist of two cycles sharing a + # path of s >= 1 vertices: n + 1 arrows in total, a unique vertex x of out-degree 2 + # and a unique vertex y of in-degree 2 (possibly equal), all other degrees 1 + sum(outs) == n + 1 || return false + x, y = findfirst(==(2), outs), findfirst(==(2), ins) + (isnothing(x) || isnothing(y)) && return false + + # the shared path runs from y to x; the two branches lead from x back to y + shared = [y] + while shared[end] != x + length(shared) > n && return false + push!(shared, findfirst(>(0), A[shared[end], :])) + end + function branch(start::Int) + b = Int[] + cur = start + while cur != y + (cur == x || cur in shared || cur in b || length(b) > n) && return nothing + push!(b, cur) + cur = findfirst(>(0), A[cur, :]) + end + return b + end + targets = findall(>(0), A[x, :]) + b1 = branch(targets[1]) + b2 = A[x, targets[1]] == 2 ? b1 : branch(targets[end]) + (isnothing(b1) || isnothing(b2)) && return false + length(shared) + length(b1) + length(b2) == n || return false + + # (ii) one branch is a single vertex of dimension 1: cofree iff the minimal + # dimension along the other cycle is attained exactly once in the shared path, or + # not there but exactly once in the other branch [Theorem 8] + for (c, rest) in ((b1, b2), (b2, b1)) + if length(c) == 1 && d[c[1]] == 1 + m = minimum(d[w] for w in vcat(shared, rest)) + count(w -> d[w] == m, shared) == 1 && return true + count(w -> d[w] == m, shared) == 0 && + count(w -> d[w] == m, rest) == 1 && + return true + end + end + any(length(b) == 1 && d[b[1]] == 1 for b in (b1, b2)) && return false + + # (iv) two cycles sharing a path, all branch dimensions at least 2, exactly one + # shared dimension equal to 2 and the others at least 4 [Theorem 9] + all(d[w] >= 2 for w in vcat(b1, b2)) || return false + return count(w -> d[w] == 2, shared) == 1 && + all(d[w] == 2 || d[w] >= 4 for w in shared) +end + +""" + is_cofree(Q::Quiver, d::AbstractVector{Int}) + +Check whether the quiver setting `(Q, d)` is cofree, i.e., whether the coordinate ring +of the `d`-dimensional representation variety of `Q` is a graded free module over its +ring of invariants. + +By a criterion of Popov this is the case if and only if the setting is coregular (see +[`is_coregular`](@ref)) and its nullcone is equidimensional. The implementation follows +the classification of +[[Bocklandt--Van de Weyer](https://doi.org/10.1016/j.jalgebra.2007.08.019)]: +the setting is cofree if and only if all its strongly connected components are, which +is decided by wedging away vertices (their reduction step ``W``), splitting into prime +components (the summands of the decomposition as an iterated connected sum at vertices +of dimension `1`), and comparing against the list of [Theorem 1, loc. cit.]. + +Cofreeness is stronger than coregularity: it moreover makes the quotient map from the +representation variety to the affine quotient flat. + +# Input + +- `Q::Quiver`: a quiver. +- `d::AbstractVector{Int}`: a dimension vector. + +# Output + +- whether the coordinate ring of the setting `(Q, d)` is a graded free module over the + ring of invariants. + +# Examples + +Pairs of ``2 \\times 2`` matrices are cofree, pairs of ``3 \\times 3`` matrices are +not even coregular, and cyclic quiver settings are always cofree: + +```jldoctest +julia> is_cofree(jordan_quiver(2), [2]) +true + +julia> is_cofree(jordan_quiver(2), [3]) +false + +julia> is_cofree(cyclic_quiver(3), [1, 2, 3]) +true +``` + +A coregular setting need not be cofree: + +```jldoctest +julia> Q = Quiver("1--2, 2-1"); + +julia> is_coregular(Q, [2, 2]), is_cofree(Q, [2, 2]) +(true, false) + +julia> is_coregular(Q, [2, 4]), is_cofree(Q, [2, 4]) +(true, true) +``` +""" +function is_cofree(Q::Quiver, d::AbstractVector{Int}) + length(d) == n_vertices(Q) || + throw(ArgumentError("dimension vector must have length $(n_vertices(Q))")) + all(di >= 0 for di in d) || + throw(ArgumentError("dimension vector must be non-negative")) + + # vertices of dimension 0 do not contribute, and arrows between different strongly + # connected components only contribute a free matrix factor [Lemma 3] + A = Matrix{Int}(Q.adjacency) + vertices = support(d) + for c in strongly_connected_components(Quiver(A[vertices, vertices])) + Ac, dc = A[vertices[c], vertices[c]], Vector{Int}(d[vertices[c]]) + # wedge the vertices of dimension at least 2 first, then split into prime + # components; wedges at vertices of dimension 1 only occur for cyclic quivers, + # which are cofree anyway [Remark 2] + while (step = __wedge_step(Ac, dc)) !== nothing + Ac, dc = step + end + all(__is_cofree_prime(B, e) for (B, e) in __prime_components(Ac, dc)) || + return false + end + return true +end diff --git a/test/runtests.jl b/test/runtests.jl index 20818ac..5504bfd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -206,3 +206,47 @@ end; @test codimension_singular_locus(QuiverModuliSpace(kronecker_quiver(3), [2, 2])) == Inf @test codimension_singular_locus(QuiverModuliSpace(kronecker_quiver(3), [3, 3])) == 3 end; + +@testset "cofree quiver settings" begin + # cyclic quiver settings and matrix invariants: pairs of 2x2 matrices are cofree, + # pairs of 3x3 matrices and triples of 2x2 matrices are not; any number of loops on + # a vertex of dimension 1 is cofree + @test all(is_cofree(cyclic_quiver(n), fill(k, n)) for n in 1:3, k in 1:3) + @test is_cofree(cyclic_quiver(3), [1, 2, 3]) + @test is_cofree(jordan_quiver(2), [2]) + @test !is_cofree(jordan_quiver(2), [3]) + @test !is_cofree(jordan_quiver(3), [2]) + @test is_cofree(jordan_quiver(3), [1]) + + # acyclic settings are trivially cofree, as the invariants are constants + @test is_cofree(kronecker_quiver(3), [2, 3]) + @test is_cofree(subspace_quiver(4), [1, 1, 1, 1, 2]) + + # settings with all cycles through a vertex of dimension 1: the k arrows back and + # forth give 2k - 1 as the bound on the other dimension + @test is_cofree(Quiver("1-2, 2-1"), [1, 5]) + @test is_cofree(Quiver("1--2, 2--1"), [1, 3]) + @test !is_cofree(Quiver("1--2, 2--1"), [1, 2]) + + # two cycles sharing a path: cofree iff exactly one shared dimension is 2 and the + # others are at least 4, so coregularity does not suffice + @test is_coregular(Quiver("1--2, 2-1"), [2, 2]) + @test !is_cofree(Quiver("1--2, 2-1"), [2, 2]) + @test !is_cofree(Quiver("1--2, 2-1"), [2, 3]) + @test is_cofree(Quiver("1--2, 2-1"), [2, 4]) + + # two cycles sharing a path through a vertex of dimension 1: cofree iff the minimal + # dimension along the big cycle is attained exactly once in the shared path, or not + # there but exactly once in the other branch + theta_quiver = Quiver("1-2, 2-3, 3-1, 2-4, 4-1") + @test is_cofree(theta_quiver, [2, 3, 4, 1]) + @test is_cofree(theta_quiver, [3, 3, 2, 1]) + @test !is_cofree(theta_quiver, [2, 2, 3, 1]) + + # wedging removes the vertex of dimension 3 on the path to the central vertex, + # reducing to the setting [2, 3, 4, 1] above; with dimension 1 instead there are two + # vertices of dimension 1 on a common cycle, which is never cofree + wedged = Quiver("1-2, 2-3, 3-1, 2-5, 5-4, 4-1") + @test is_cofree(wedged, [2, 3, 4, 1, 3]) + @test !is_cofree(wedged, [2, 3, 4, 1, 1]) +end; From ecc96817a4397794384dae656e52355fe43f405c Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Thu, 9 Jul 2026 12:34:45 +0200 Subject: [PATCH 08/22] refactor: tighten the cofree classification code --- src/RepresentationTheory.jl | 138 ++++++++++++++---------------------- 1 file changed, 52 insertions(+), 86 deletions(-) diff --git a/src/RepresentationTheory.jl b/src/RepresentationTheory.jl index 5a188d3..f0a2f6d 100644 --- a/src/RepresentationTheory.jl +++ b/src/RepresentationTheory.jl @@ -495,18 +495,14 @@ end # source of at most d[w] arrows. # The number of quasiprimitive cycles through v, counted with arrow multiplicities and -# capped at cap + 1 to bound the enumeration; only used when every such cycle passes -# through v exactly once (it also passes through a vertex of dimension 1), so that -# counting closed walks anchored at v is correct. +# capped to bound the enumeration; only used when every such cycle passes through v +# exactly once, so that counting closed walks anchored at v is correct. function __n_quasiprimitive_cycles(A::Matrix{Int}, d::Vector{Int}, v::Int, cap::Int) - n = length(d) - budget = copy(d) - total = Ref(0) + total, budget = Ref(0), copy(d) function walk(x::Int, mult::Int) (total[] > cap || budget[x] == 0) && return nothing budget[x] -= 1 - for y in 1:n - A[x, y] == 0 && continue + for y in findall(>(0), A[x, :]) y == v ? (total[] += mult * A[x, y]) : walk(y, mult * A[x, y]) end budget[x] += 1 @@ -517,22 +513,19 @@ function __n_quasiprimitive_cycles(A::Matrix{Int}, d::Vector{Int}, v::Int, cap:: end # One application of the wedging step W of [doi:10.1016/j.jalgebra.2007.08.019] to a -# vertex of dimension at least 2: a vertex whose unique outgoing (resp. incoming) -# arrow ends (resp. starts) at a vertex of dimension 1 is removed, redirecting its -# other arrows to that vertex, provided its dimension is at least the number of -# quasiprimitive cycles through it. Returns the new setting, or `nothing`. -# Wedging preserves cofreeness in both directions [Lemma 3]. +# vertex v of dimension at least 2 whose unique outgoing (resp. incoming) arrow ends +# (resp. starts) at a vertex of dimension 1: v is removed and its other arrows are +# redirected to that vertex, provided d[v] is at least the number of quasiprimitive +# cycles through v. Wedging preserves cofreeness in both directions [Lemma 3]. function __wedge_step(A::Matrix{Int}, d::Vector{Int}) n = length(d) - for v in 1:n - (d[v] >= 2 && A[v, v] == 0) || continue + for v in findall(v -> d[v] >= 2 && A[v, v] == 0, 1:n) outs, ins = findall(>(0), A[v, :]), findall(>(0), A[:, v]) - wedge_out = length(outs) == 1 && A[v, outs[1]] == 1 && d[outs[1]] == 1 - wedge_in = length(ins) == 1 && A[ins[1], v] == 1 && d[ins[1]] == 1 - (wedge_out || wedge_in) || continue - __n_quasiprimitive_cycles(A, d, v, d[v]) <= d[v] || continue + out = length(outs) == 1 && A[v, outs[1]] == 1 && d[outs[1]] == 1 + into = length(ins) == 1 && A[ins[1], v] == 1 && d[ins[1]] == 1 + ((out || into) && __n_quasiprimitive_cycles(A, d, v, d[v]) <= d[v]) || continue B = copy(A) - wedge_out ? (B[:, outs[1]] .+= A[:, v]) : (B[ins[1], :] .+= A[v, :]) + out ? (B[:, outs[1]] .+= A[:, v]) : (B[ins[1], :] .+= A[v, :]) keep = setdiff(1:n, v) return B[keep, keep], d[keep] end @@ -544,88 +537,65 @@ end # dimension 1; a setting is cofree iff its prime components are [Lemma 3]. function __prime_components(A::Matrix{Int}, d::Vector{Int}) n = length(d) - for v in 1:n - (d[v] == 1 && n + A[v, v] >= 2) || continue - # weakly connected components of the quiver minus v; each one, together with v and - # the arrows between them, is a summand, as is every loop at v + for v in findall(==(1), d) + # the summands at v are the weakly connected components of the quiver minus v, + # each taken together with v and the arrows between them, and every loop at v others = setdiff(1:n, v) - reachable = [ - i == j || A[others[i], others[j]] + A[others[j], others[i]] > 0 - for i in eachindex(others), j in eachindex(others) - ] - for k in eachindex(others), i in eachindex(others), j in eachindex(others) - reachable[i, j] |= reachable[i, k] && reachable[k, j] - end - pieces = unique([findall(reachable[i, :]) for i in eachindex(others)]) + U = A[others, others] + pieces = strongly_connected_components(Quiver(U + U')) length(pieces) + A[v, v] >= 2 || continue - out = Vector{Tuple{Matrix{Int},Vector{Int}}}() + out = [(fill(1, 1, 1), [1]) for _ in 1:A[v, v]] for piece in pieces - keep = sort(vcat(others[piece], v)) + keep = sort!(vcat(others[piece], v)) B = A[keep, keep] - B[findfirst(==(v), keep), findfirst(==(v), keep)] = 0 + w = findfirst(==(v), keep) + B[w, w] = 0 append!(out, __prime_components(B, d[keep])) end - append!(out, (fill(1, 1, 1), [1]) for _ in 1:A[v, v]) return out end return [(A, d)] end -# [Theorem 6]: a strongly connected setting with a vertex v of dimension 1 through -# which all cycles run is cofree iff every other vertex w satisfies -# d[w] >= #{quasiprimitive paths v -> w} + #{quasiprimitive paths w -> v} - 1. -# The quiver minus v is acyclic here, so these paths are counted by powers of the -# adjacency matrix with v deleted, and quasiprimitivity is automatic. -function __is_cofree_through_vertex(A::Matrix{Int}, d::Vector{Int}, v::Int) - n = length(d) - B = copy(A) - B[v, :] .= 0 - B[:, v] .= 0 - S = sum(B^k for k in 0:(n - 1)) - return all( - d[w] >= - sum(A[v, x] * S[x, w] for x in 1:n) + sum(S[w, x] * A[x, v] for x in 1:n) - 1 for - w in 1:n if w != v - ) -end - # Decide cofreeness of a prime strongly connected setting by recognizing the members # of the list of [Theorem 1, doi:10.1016/j.jalgebra.2007.08.019]. function __is_cofree_prime(A::Matrix{Int}, d::Vector{Int}) n = length(d) - ins, outs = [sum(A[:, i]) for i in 1:n], [sum(A[i, :]) for i in 1:n] - # a single vertex: no arrows, a cyclic quiver (one loop), any number of loops on a # vertex of dimension 1, or the setting Q_2 (two loops on a vertex of dimension 2) n == 1 && return A[1, 1] <= 1 || d[1] == 1 || (A[1, 1], d[1]) == (2, 2) # (iii) cyclic quiver settings are always cofree [Theorem 5] - all(ins[i] == 1 && outs[i] == 1 for i in 1:n) && return true + ins, outs = vec(sum(A; dims=1)), vec(sum(A; dims=2)) + all(ins .== 1) && all(outs .== 1) && return true - # (i) all cycles run through a vertex of dimension 1 [Theorem 6] - for v in filter(v -> d[v] == 1, 1:n) + # (i) all cycles run through a vertex v of dimension 1 [Theorem 6]: cofree iff + # d[w] >= #{quasiprimitive paths v -> w} + #{quasiprimitive paths w -> v} - 1 for + # all other w; the quiver minus v is acyclic, so its adjacency powers count paths + for v in findall(==(1), d) B = copy(A) B[v, :] .= 0 B[:, v] .= 0 - all(==(0), B^n) && return __is_cofree_through_vertex(A, d, v) + any(!=(0), B^n) && continue + S = sum(B^k for k in 0:(n - 1)) + return all( + d[w] >= A[v, :]' * S[:, w] + S[w, :]' * A[:, v] - 1 for w in 1:n if w != v + ) end - # the remaining members of the list, (ii) and (iv), consist of two cycles sharing a - # path of s >= 1 vertices: n + 1 arrows in total, a unique vertex x of out-degree 2 - # and a unique vertex y of in-degree 2 (possibly equal), all other degrees 1 + # (ii) and (iv) are two cycles sharing a path of s >= 1 vertices: n + 1 arrows, a + # unique vertex x of out-degree 2, a unique y of in-degree 2, all other degrees 1; + # the shared path runs from y to x, the two branches lead from x back to y sum(outs) == n + 1 || return false x, y = findfirst(==(2), outs), findfirst(==(2), ins) (isnothing(x) || isnothing(y)) && return false - - # the shared path runs from y to x; the two branches lead from x back to y shared = [y] while shared[end] != x length(shared) > n && return false push!(shared, findfirst(>(0), A[shared[end], :])) end - function branch(start::Int) + function branch(cur::Int) b = Int[] - cur = start while cur != y (cur == x || cur in shared || cur in b || length(b) > n) && return nothing push!(b, cur) @@ -634,30 +604,26 @@ function __is_cofree_prime(A::Matrix{Int}, d::Vector{Int}) return b end targets = findall(>(0), A[x, :]) - b1 = branch(targets[1]) - b2 = A[x, targets[1]] == 2 ? b1 : branch(targets[end]) - (isnothing(b1) || isnothing(b2)) && return false - length(shared) + length(b1) + length(b2) == n || return false - - # (ii) one branch is a single vertex of dimension 1: cofree iff the minimal - # dimension along the other cycle is attained exactly once in the shared path, or - # not there but exactly once in the other branch [Theorem 8] + b1, b2 = branch(targets[1]), branch(targets[end]) + (isnothing(b1) || isnothing(b2) || length(shared) + length(b1) + length(b2) != n) && + return false + + # (ii) a branch is a single vertex of dimension 1: cofree iff the minimal dimension + # along the other cycle is attained exactly once in the shared path, or not there + # but exactly once in the other branch [Theorem 8] for (c, rest) in ((b1, b2), (b2, b1)) if length(c) == 1 && d[c[1]] == 1 - m = minimum(d[w] for w in vcat(shared, rest)) - count(w -> d[w] == m, shared) == 1 && return true - count(w -> d[w] == m, shared) == 0 && - count(w -> d[w] == m, rest) == 1 && - return true + m = minimum(d[vcat(shared, rest)]) + return count(==(m), d[shared]) == 1 || + (count(==(m), d[shared]) == 0 && count(==(m), d[rest]) == 1) end end - any(length(b) == 1 && d[b[1]] == 1 for b in (b1, b2)) && return false - # (iv) two cycles sharing a path, all branch dimensions at least 2, exactly one - # shared dimension equal to 2 and the others at least 4 [Theorem 9] - all(d[w] >= 2 for w in vcat(b1, b2)) || return false - return count(w -> d[w] == 2, shared) == 1 && - all(d[w] == 2 || d[w] >= 4 for w in shared) + # (iv) all branch dimensions at least 2, exactly one shared dimension equal to 2, + # and the other shared dimensions at least 4 [Theorem 9] + return all(d[vcat(b1, b2)] .>= 2) && + count(==(2), d[shared]) == 1 && + all(w -> w == 2 || w >= 4, d[shared]) end """ From 6055af49f05b4ac1be84533bcf595035eefc8676 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Thu, 9 Jul 2026 12:35:24 +0200 Subject: [PATCH 09/22] docs: explain coregular and cofree quiver settings --- docs/src/methods/representation-theory.md | 30 ++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/src/methods/representation-theory.md b/docs/src/methods/representation-theory.md index 078f312..169065f 100644 --- a/docs/src/methods/representation-theory.md +++ b/docs/src/methods/representation-theory.md @@ -16,8 +16,36 @@ all_general_subdimension_vectors is_general_subdimension_vector canonical_decomposition in_fundamental_domain +first_hochschild_cohomology +``` + +## Invariant theory of quiver representations + +The affine quotient of the representation variety by the base change group +parametrizes semisimple representations of the quiver, and its ring of functions is +the ring of invariants, generated by traces along oriented cycles. + +A quiver setting is *coregular* if this ring of invariants is a polynomial ring, or +equivalently if the affine quotient is smooth (in which case it is an affine space). +This is decided by the reduction algorithm of +[[Bocklandt](https://mathscinet.ams.org/mathscinet/relay-station?mr=1929191)], +which simplifies a quiver setting without changing its invariant theory and then +compares the result against a short list. + +A stronger property is *cofreeness*: the coordinate ring of the representation +variety is a graded free module over the ring of invariants, which by a criterion of +Popov amounts to coregularity together with equidimensionality of the nullcone. +Cofree quiver settings are classified by +[[Bocklandt--Van de Weyer](https://doi.org/10.1016/j.jalgebra.2007.08.019)]. + +Beyond deciding smoothness of the affine quotient itself, these notions drive the +study of moduli spaces of quiver representations: étale-locally around a polystable +representation, a moduli space is the affine quotient of a *local quiver setting*, so +coregularity of local quiver settings decides smoothness of moduli spaces; see +[`is_smooth`](@ref) and [`codimension_singular_locus`](@ref). + +```@docs bocklandt_reduction is_coregular is_cofree -first_hochschild_cohomology ``` From 83e913579b61b6ac96cd3b8db52b8a5d21d2575b Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Thu, 9 Jul 2026 12:46:10 +0200 Subject: [PATCH 10/22] feat: nullcone dimension and defect of quiver settings --- docs/src/methods/representation-theory.md | 6 + src/QuiverTools.jl | 2 +- src/RepresentationTheory.jl | 173 ++++++++++++++++++++++ test/runtests.jl | 25 ++++ 4 files changed, 205 insertions(+), 1 deletion(-) diff --git a/docs/src/methods/representation-theory.md b/docs/src/methods/representation-theory.md index 169065f..fc3c167 100644 --- a/docs/src/methods/representation-theory.md +++ b/docs/src/methods/representation-theory.md @@ -44,8 +44,14 @@ representation, a moduli space is the affine quotient of a *local quiver setting coregularity of local quiver settings decides smoothness of moduli spaces; see [`is_smooth`](@ref) and [`codimension_singular_locus`](@ref). +The *nullcone* is the locus of nilpotent representations, i.e., the fibre of the +quotient map over the image of the zero representation. Its dimension controls the +failure of equidimensionality of the quotient map, measured by the *defect*. + ```@docs bocklandt_reduction is_coregular is_cofree +dimension_nullcone +defect ``` diff --git a/src/QuiverTools.jl b/src/QuiverTools.jl index afcab41..b8b8c3f 100644 --- a/src/QuiverTools.jl +++ b/src/QuiverTools.jl @@ -60,7 +60,7 @@ export is_general_subdimension_vector, all_general_subdimension_vectors export euler_form, euler_matrix, is_root, is_schur_root, is_real_root, is_imaginary_root, is_isotropic_root, general_ext, general_hom, canonical_decomposition, in_fundamental_domain, - bocklandt_reduction, is_coregular, is_cofree + bocklandt_reduction, is_coregular, is_cofree, dimension_nullcone, defect # Moduli export all_luna_types, is_luna_type, dimension_of_luna_stratum diff --git a/src/RepresentationTheory.jl b/src/RepresentationTheory.jl index f0a2f6d..2d9b8b2 100644 --- a/src/RepresentationTheory.jl +++ b/src/RepresentationTheory.jl @@ -706,3 +706,176 @@ function is_cofree(Q::Quiver, d::AbstractVector{Int}) end return true end + +######################################################################################## +# Nullcones +######################################################################################## + +# All quasiprimitive cycles of the setting, as sequences of arrow indices into the +# list of arrows ordered as in `arrows`, up to rotation. +function __quasiprimitive_cycles(A::Matrix{Int}, d::Vector{Int}) + n = length(d) + arrows_ = [(i, j) for i in 1:n for j in 1:n for _ in 1:A[i, j]] + cycles = Set{Vector{Int}}() + budget, path = copy(d), Int[] + function walk(x::Int, base::Int) + budget[x] == 0 && return nothing + budget[x] -= 1 + for (k, (i, j)) in enumerate(arrows_) + i == x || continue + push!(path, k) + # a quasiprimitive cycle may pass through the base vertex several times, so + # both close the cycle here and keep walking + j == base && + push!(cycles, minimum(vcat(path[r:end], path[1:(r - 1)]) for r in eachindex(path))) + walk(j, base) + pop!(path) + end + budget[x] += 1 + return nothing + end + for v in 1:n + walk(v, v) + end + return sort!(collect(cycles)) +end + +""" + dimension_nullcone(Q::Quiver, d::AbstractVector{Int}) + +Compute the dimension of the nullcone of the quiver setting `(Q, d)`. + +The nullcone is the fibre of the quotient map to the affine quotient over the image of +the zero representation; it consists of the nilpotent representations, i.e., those on +which every invariant of positive degree vanishes. Since the ring of invariants is +generated by traces of quasiprimitive cycles by +[[Le Bruyn--Procesi](https://mathscinet.ams.org/mathscinet/relay-station?mr=958897)], +the nullcone is the vanishing locus of these traces, and its dimension is computed as +a Krull dimension using a Groebner basis computation in Singular. + +# Input + +- `Q::Quiver`: a quiver. +- `d::AbstractVector{Int}`: a dimension vector. + +# Output + +- the dimension of the nullcone of the setting `(Q, d)`. + +# Examples + +The nullcone of the Jordan quiver consists of the nilpotent matrices; for pairs of +``2 \\times 2`` matrices it consists of the pairs that are simultaneously strictly +triangularizable: + +```jldoctest +julia> dimension_nullcone(jordan_quiver(1), [3]) +6 + +julia> dimension_nullcone(jordan_quiver(2), [2]) +3 +``` + +For an acyclic quiver there are no invariants, so the nullcone is everything: + +```jldoctest +julia> dimension_nullcone(kronecker_quiver(3), [2, 3]) +18 +``` +""" +@memoize Dict function dimension_nullcone(Q::Quiver, d::AbstractVector{Int}) + length(d) == n_vertices(Q) || + throw(ArgumentError("dimension vector must have length $(n_vertices(Q))")) + all(di >= 0 for di in d) || + throw(ArgumentError("dimension vector must be non-negative")) + + keep = support(d) + A, dd = Matrix{Int}(Q.adjacency)[keep, keep], Vector{Int}(d[keep]) + n = length(dd) + arrows_ = [(i, j) for i in 1:n for j in 1:n for _ in 1:A[i, j]] + nvars = sum(dd[i] * dd[j] for (i, j) in arrows_; init=0) + nvars == 0 && return 0 + + R, x = polynomial_ring(Singular.QQ, ["x$k" for k in 1:nvars]) + # the matrix of the arrow a: i -> j has size d[j] x d[i], with fresh variable entries + offset = 0 + matrices = map(arrows_) do (i, j) + M = [x[offset + r + (c - 1) * dd[j]] for r in 1:dd[j], c in 1:dd[i]] + offset += dd[i] * dd[j] + M + end + + cycles = __quasiprimitive_cycles(A, dd) + isempty(cycles) && return nvars + traces = map(cycles) do cycle + M = matrices[cycle[1]] + for k in cycle[2:end] + M = matrices[k] * M + end + sum(M[i, i] for i in 1:size(M, 1)) + end + return Singular.dimension(std(Singular.Ideal(R, traces...))) +end + +""" + defect(Q::Quiver, d::AbstractVector{Int}) + +Compute the defect of the quiver setting `(Q, d)`, i.e., the difference between the +dimension of the nullcone and the dimension of the generic fibre of the quotient map +to the affine quotient, + +```math +\\operatorname{def}(Q, d) = +\\dim\\operatorname{Null}(Q, d) - \\dim\\operatorname{Rep}(Q, d) + +\\dim\\operatorname{iss}(Q, d), +``` + +as in [[Definition 3, Bocklandt--Van de Weyer] +(https://doi.org/10.1016/j.jalgebra.2007.08.019)]. The defect is non-negative, and it +vanishes if and only if the quotient map is equidimensional. By a criterion of Popov, +the setting is cofree if and only if it is coregular and has defect zero, which gives +an independent verification of [`is_cofree`](@ref). + +# Input + +- `Q::Quiver`: a quiver. +- `d::AbstractVector{Int}`: a dimension vector. + +# Output + +- the defect of the setting `(Q, d)`. + +# Examples + +Cyclic quiver settings are cofree, so their defect vanishes; for pairs of +``3 \\times 3`` matrices the nullcone is too large: + +```jldoctest +julia> defect(jordan_quiver(1), [4]) +0 + +julia> defect(jordan_quiver(2), [3]) +1 +``` +""" +function defect(Q::Quiver, d::AbstractVector{Int}) + A = Matrix{Int}(Q.adjacency) + dim_rep = sum(A .* (Vector(d) * Vector(d)')) + return dimension_nullcone(Q, d) - dim_rep + __dimension_affine_quotient(Q, d) +end + +# The dimension of the affine quotient iss(Q, d), i.e., of the moduli space for the +# zero stability parameter, computed one connected component of the support at a time. +function __dimension_affine_quotient(Q::Quiver, d::AbstractVector{Int}) + keep = support(d) + A = Matrix{Int}(Q.adjacency)[keep, keep] + return sum( + Int( + dimension( + QuiverModuliSpace(Quiver(A[c, c]), Vector{Int}(d[keep[c]]), zeros(Int, length(c))) + ), + ) + for c in strongly_connected_components(Quiver(A + A')); # weakly connected components + init=0, + ) +end diff --git a/test/runtests.jl b/test/runtests.jl index 5504bfd..55f0ebb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -250,3 +250,28 @@ end; @test is_cofree(wedged, [2, 3, 4, 1, 3]) @test !is_cofree(wedged, [2, 3, 4, 1, 1]) end; + +@testset "nullcones and defect" begin + # nilpotent matrices, and pairs of matrices with a common complete flag + @test dimension_nullcone(jordan_quiver(1), [3]) == 6 + @test dimension_nullcone(jordan_quiver(2), [2]) == 3 + # acyclic settings have no invariants, so the nullcone is everything + @test dimension_nullcone(kronecker_quiver(3), [2, 3]) == 18 + + # the defect measures the failure of equidimensionality: it vanishes for cofree + # settings, and for the settings 2 <=> k it decreases to zero as k grows to 4 + @test defect(jordan_quiver(1), [4]) == 0 + @test defect(jordan_quiver(2), [2]) == 0 + @test defect(jordan_quiver(2), [3]) == 1 + @test [defect(Quiver("1--2, 2-1"), [2, k]) for k in 2:4] == [2, 1, 0] + @test defect(cyclic_quiver(3), [2, 3, 4]) == 0 + + # Popov: cofree iff coregular with vanishing defect, as an independent check of + # the classification-based is_cofree against the Groebner-based defect + for a in 0:2, b in 0:2, l1 in 0:1, l2 in 0:1, d1 in 1:2, d2 in 1:2 + a + b + l1 + l2 <= 3 || continue + Q = Quiver([l1 a; b l2]) + d = [d1, d2] + @test is_cofree(Q, d) == (is_coregular(Q, d) && defect(Q, d) == 0) + end +end; From 438401c5eebce4999dc5e58d4d877eca74645a1e Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Thu, 9 Jul 2026 12:50:14 +0200 Subject: [PATCH 11/22] feat: fibre dimensions, flatness and semismallness of projections to walls --- docs/src/methods/quiver-moduli.md | 3 + src/Moduli.jl | 271 +++++++++++++++++++++++++++++- src/QuiverTools.jl | 1 + test/runtests.jl | 27 +++ 4 files changed, 301 insertions(+), 1 deletion(-) diff --git a/docs/src/methods/quiver-moduli.md b/docs/src/methods/quiver-moduli.md index 0a7f255..3ee6082 100644 --- a/docs/src/methods/quiver-moduli.md +++ b/docs/src/methods/quiver-moduli.md @@ -27,6 +27,9 @@ is_nonempty dimension is_smooth codimension_singular_locus +fibre_dimension +is_flat +is_semismall is_projective index motive diff --git a/src/Moduli.jl b/src/Moduli.jl index 1b096de..bd0c9d8 100644 --- a/src/Moduli.jl +++ b/src/Moduli.jl @@ -43,7 +43,7 @@ end is_coprime(M::QuiverModuli) Checks if the stability parameter is coprime with the dimension vector, -i.e., if for all subdimension vectors ``e`` of ``d``, ``\\theta\\cdot e \\neq 0``. +i.e., if for all subdimension vectors ``e`` of ``d``, ``\\\theta\\cdot e \\neq 0``. # Input @@ -856,6 +856,275 @@ function codimension_singular_locus(M::QuiverModuliSpace) return dimension(M) - maximum(dimension_of_luna_stratum(M, tau) for tau in singular) end +######################################################################################## +# Projections to walls +######################################################################################## + +# The dimension of the moduli space of theta-stable nilpotent representations of +# dimension vector beta, or -Inf if there are none. The stable locus is certified to +# be dense in every top-dimensional component of the nullcone by bounding the locus of +# nilpotent representations with a destabilizing subrepresentation of dimension e by +# the dimension of the incidence variety of pairs (subrepresentation, representation), +# which fibres over the flag with nilpotent sub, nilpotent quotient and an extension +# block. If certification fails an ArgumentError is thrown. +@memoize Dict function __dimension_stable_nilpotent_moduli( + Q::Quiver, beta::Vector{Int}, theta::Vector{Int}, denom +) + has_stables(Q, beta, theta, denom) || return -Inf + N = dimension_nullcone(Q, beta) + # if the zero representation is the only nilpotent one, it is stable iff simple + N == 0 && return sum(beta) == 1 ? 0 : -Inf + + A = Matrix{Int}(Q.adjacency) + for e in all_subdimension_vectors(beta; nonzero=true, strict=true) + slope(e, theta, denom) >= slope(beta, theta, denom) || continue + bound = + sum(e .* (beta .- e)) + + dimension_nullcone(Q, Vector{Int}(e)) + + dimension_nullcone(Q, beta .- e) + + (beta .- e)' * A * e + bound < N || throw( + ArgumentError( + "cannot certify that stable representations are dense in the nullcone " * + "for the dimension vector $beta", + ), + ) + end + return N - (sum(beta .^ 2) - 1) +end + +# The dimension of the moduli space of nilpotent theta-semistable representations, +# maximized over its polystable types: a polystable representation is determined by +# its distinct stable nilpotent summands. +function __dimension_nilpotent_moduli(Q::Quiver, d::Vector{Int}, theta::Vector{Int}, denom) + all(d .== 0) && return 0 + best = -Inf + for tau in all_luna_types(Q, d, theta, denom) + value = sum( + length(tau[e]) * + __dimension_stable_nilpotent_moduli(Q, Vector{Int}(e), theta, denom) for + e in keys(tau) + ) + best = max(best, value) + end + return best +end + +""" + fibre_dimension(Q::Quiver, d, theta, thetabar, tau; denom = sum) + +Computes the dimension of the fibre of the projection to the wall over a point of the +Luna stratum of type `tau`. + +When `thetabar` lies in the closure of the chamber of the stability parameter `theta`, +every `theta`-semistable representation is `thetabar`-semistable, which induces the +projective morphism + +```math +p\\colon M^{\\theta{\\rm -ss}}(Q, d) \\longrightarrow M^{\\bar\\theta{\\rm -ss}}(Q, d) +``` + +called the *projection to the wall*. Étale-locally around a polystable representation +of Luna type `tau` the morphism `p` is the quotient map of the local quiver setting of +[[MR1972892](https://mathscinet.ams.org/mathscinet/relay-station?mr=1972892)], so the +fibre over any point of the stratum of `tau` is the moduli space of *nilpotent* +representations of the local quiver setting which are semistable for the local +stability parameter, given by evaluating `theta` on the stable summands. + +Its dimension is computed by maximizing over the polystable types of the nilpotent +moduli space, where the moduli of stable nilpotent representations of a summand has +the dimension of the nullcone minus the orbit dimension; this is certified by checking +that the loci of nilpotent representations admitting a destabilizing subrepresentation +have smaller dimension than the nullcone itself, and an `ArgumentError` is thrown when +certification fails. + +# Input + +- `Q::Quiver`: a quiver. +- `d::AbstractVector{Int}`: a dimension vector. +- `theta::AbstractVector{Int}`: the stability parameter of the source. +- `thetabar::AbstractVector{Int}`: the stability parameter of the target, assumed to + lie in the closure of the chamber containing `theta`. +- `tau`: a Luna type for `thetabar`; see [`LunaType`](@ref). + +Keyword arguments: + +- `denom::Function`: the denominator of the slope. Default is `sum`. + +# Output + +- the dimension of the fibre of the projection to the wall over any point of the Luna + stratum of `tau`, or `-Inf` if the type is not realized by nilpotent representations. + +# Examples + +For the 6-subspace quiver with `d = (1^5, 2; 3)` the projection from the moduli space +for the canonical stability parameter to the wall given by `(1^5, 2; -3)` is +birational, with fibres of dimension `1` over the five surfaces where one subspace +degenerates: + +```jldoctest +julia> Q = subspace_quiver(6); d = [1, 1, 1, 1, 1, 2, 3]; + +julia> theta = [3, 3, 3, 3, 3, 3, -7]; thetabar = [1, 1, 1, 1, 1, 2, -3]; + +julia> fibre_dimension(Q, d, theta, thetabar, Dict(d => [1])) +0 + +julia> tau = Dict([1, 0, 0, 0, 0, 1, 1] => [1], [0, 1, 1, 1, 1, 1, 2] => [1]); + +julia> fibre_dimension(Q, d, theta, thetabar, tau) +1 +``` +""" +function fibre_dimension( + Q::Quiver, + d::AbstractVector{Int}, + theta::AbstractVector{Int}, + thetabar::AbstractVector{Int}, + tau; + denom::Function=sum, +) + Mbar = QuiverModuliSpace(Q, d, coerce_vector(thetabar)) + is_luna_type(Mbar, tau) || throw(DomainError("not a Luna type for thetabar")) + + # the local quiver setting of tau, with the stability induced by theta + summands = [Vector{Int}(e) for e in keys(tau) for _ in tau[e]] + s = length(summands) + A = [(k == l ? 1 : 0) - euler_form(Q, summands[k], summands[l]) for k in 1:s, l in 1:s] + dloc = [m for e in keys(tau) for m in tau[e]] + thetaloc = [theta' * e for e in summands] + + # a repeated rigid summand is not realized by any representation + any(<(0), A) && return -Inf + f = __dimension_nilpotent_moduli(Quiver(A), dloc, thetaloc, denom) + return isfinite(f) ? Int(f) : f +end + +""" + is_flat(Q::Quiver, d, theta, thetabar; denom = sum) + +Checks whether the projection to the wall + +```math +p\\colon M^{\\theta{\\rm -ss}}(Q, d) \\longrightarrow M^{\\bar\\theta{\\rm -ss}}(Q, d) +``` + +is flat; see [`fibre_dimension`](@ref) for the setup. + +The source of `p` is a GIT quotient of a smooth variety, hence Cohen--Macaulay by +Hochster--Roberts. By miracle flatness, when the target is smooth the morphism is flat +if and only if all fibres have the same dimension, which is checked stratum by +stratum. When the target is singular this criterion does not apply, and an +`ArgumentError` is thrown. + +# Input + +- `Q::Quiver`: a quiver. +- `d::AbstractVector{Int}`: a dimension vector. +- `theta::AbstractVector{Int}`: the stability parameter of the source. +- `thetabar::AbstractVector{Int}`: the stability parameter of the target, assumed to + lie in the closure of the chamber containing `theta`. + +Keyword arguments: + +- `denom::Function`: the denominator of the slope. Default is `sum`. + +# Output + +- whether the projection to the wall is flat. + +# Examples + +The projection of the 6-subspace quiver moduli space with `d = (1^5, 2; 3)` to the +wall `(1^5, 2; -3)` is birational with positive-dimensional fibres, so it is not flat: + +```jldoctest +julia> Q = subspace_quiver(6); d = [1, 1, 1, 1, 1, 2, 3]; + +julia> is_flat(Q, d, [3, 3, 3, 3, 3, 3, -7], [1, 1, 1, 1, 1, 2, -3]) +false +``` +""" +function is_flat( + Q::Quiver, + d::AbstractVector{Int}, + theta::AbstractVector{Int}, + thetabar::AbstractVector{Int}; + denom::Function=sum, +) + Mbar = QuiverModuliSpace(Q, d, coerce_vector(thetabar)) + is_smooth(Mbar) || throw( + ArgumentError( + "the target of the projection is singular; miracle flatness does not apply" + ), + ) + generic = dimension(QuiverModuliSpace(Q, d, coerce_vector(theta))) - dimension(Mbar) + # types with fibre dimension -Inf are not realized, so they are skipped + return all(all_luna_types(Mbar)) do tau + f = fibre_dimension(Q, d, theta, thetabar, tau; denom=denom) + f == -Inf || f == generic + end +end + +""" + is_semismall(Q::Quiver, d, theta, thetabar; denom = sum) + +Checks whether the projection to the wall + +```math +p\\colon M^{\\theta{\\rm -ss}}(Q, d) \\longrightarrow M^{\\bar\\theta{\\rm -ss}}(Q, d) +``` + +is semismall, i.e., whether for every Luna stratum the sum of its dimension and twice +the fibre dimension over it is at most the dimension of the source; see +[`fibre_dimension`](@ref) for the setup. + +# Input + +- `Q::Quiver`: a quiver. +- `d::AbstractVector{Int}`: a dimension vector. +- `theta::AbstractVector{Int}`: the stability parameter of the source. +- `thetabar::AbstractVector{Int}`: the stability parameter of the target, assumed to + lie in the closure of the chamber containing `theta`. + +Keyword arguments: + +- `denom::Function`: the denominator of the slope. Default is `sum`. + +# Output + +- whether the projection to the wall is semismall. + +# Examples + +The projection of the 6-subspace quiver moduli space with `d = (1^5, 2; 3)` to the +wall `(1^5, 2; -3)`, whose target is the Grassmannian ``\\operatorname{Gr}(2, 4)``, is +semismall: + +```jldoctest +julia> Q = subspace_quiver(6); d = [1, 1, 1, 1, 1, 2, 3]; + +julia> is_semismall(Q, d, [3, 3, 3, 3, 3, 3, -7], [1, 1, 1, 1, 1, 2, -3]) +true +``` +""" +function is_semismall( + Q::Quiver, + d::AbstractVector{Int}, + theta::AbstractVector{Int}, + thetabar::AbstractVector{Int}; + denom::Function=sum, +) + Mbar = QuiverModuliSpace(Q, d, coerce_vector(thetabar)) + dM = dimension(QuiverModuliSpace(Q, d, coerce_vector(theta))) + # types with fibre dimension -Inf are not realized, so they are skipped + return all(all_luna_types(Mbar)) do tau + f = fibre_dimension(Q, d, theta, thetabar, tau; denom=denom) + f == -Inf || dimension_of_luna_stratum(Mbar, tau) + 2 * f <= dM + end +end + """ is_smooth(M::QuiverModuliStack) diff --git a/src/QuiverTools.jl b/src/QuiverTools.jl index b8b8c3f..52a111a 100644 --- a/src/QuiverTools.jl +++ b/src/QuiverTools.jl @@ -65,6 +65,7 @@ export euler_form, euler_matrix, is_root, is_schur_root, is_real_root, is_imagin # Moduli export all_luna_types, is_luna_type, dimension_of_luna_stratum export is_nonempty, codimension_unstable_locus, codimension_singular_locus, dimension, + fibre_dimension, is_flat, is_semismall, is_smooth, is_projective, is_strongly_amply_stable, semistable_equals_stable, semisimple_moduli_space diff --git a/test/runtests.jl b/test/runtests.jl index 55f0ebb..00db45f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -275,3 +275,30 @@ end; @test is_cofree(Q, d) == (is_coregular(Q, d) && defect(Q, d) == 0) end end; + +@testset "projections to walls" begin + # the projection of the 6-subspace quiver moduli with d = (1^5, 2; 3) from the + # canonical chamber to the wall (1^5, 2; -3), whose target is Gr(2, 4): birational, + # with fibres P^1 over five surfaces and P^1 x P^1 over ten points, hence semismall + # but not flat + S = subspace_quiver(6) + d = [1, 1, 1, 1, 1, 2, 3] + theta = [3, 3, 3, 3, 3, 3, -7] + thetabar = [1, 1, 1, 1, 1, 2, -3] + @test fibre_dimension(S, d, theta, thetabar, Dict(d => [1])) == 0 + e1, e3 = [1, 0, 0, 0, 0, 1, 1], [0, 1, 1, 1, 1, 1, 2] + @test fibre_dimension(S, d, theta, thetabar, Dict(e1 => [1], e3 => [1])) == 1 + e2, f2 = [0, 1, 0, 0, 0, 1, 1], [0, 0, 1, 1, 1, 0, 1] + @test fibre_dimension(S, d, theta, thetabar, Dict(e1 => [1], e2 => [1], f2 => [1])) == 2 + @test !is_flat(S, d, theta, thetabar) + @test is_semismall(S, d, theta, thetabar) + + # the projection to the other wall is a semismall resolution of the ten isolated + # singularities, with fibres P^2; the target is singular so flatness would need more + # than miracle flatness + @test is_semismall(S, d, theta, [2, 2, 2, 2, 2, 1, -4]) + @test_throws ArgumentError is_flat(S, d, theta, [2, 2, 2, 2, 2, 1, -4]) + + # the identity projection is flat + @test is_flat(kronecker_quiver(3), [2, 3], [3, -2], [3, -2]) +end; From 091e1102814171b6013ec17295fedd5e219027a1 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Thu, 9 Jul 2026 13:05:37 +0200 Subject: [PATCH 12/22] feat: complete intersection check for quiver settings --- docs/src/methods/representation-theory.md | 1 + src/QuiverTools.jl | 3 +- src/RepresentationTheory.jl | 88 ++++++++++++++++++++++- test/runtests.jl | 15 ++++ 4 files changed, 103 insertions(+), 4 deletions(-) diff --git a/docs/src/methods/representation-theory.md b/docs/src/methods/representation-theory.md index fc3c167..04eb0a4 100644 --- a/docs/src/methods/representation-theory.md +++ b/docs/src/methods/representation-theory.md @@ -54,4 +54,5 @@ is_coregular is_cofree dimension_nullcone defect +is_complete_intersection ``` diff --git a/src/QuiverTools.jl b/src/QuiverTools.jl index 52a111a..83a55bc 100644 --- a/src/QuiverTools.jl +++ b/src/QuiverTools.jl @@ -60,7 +60,8 @@ export is_general_subdimension_vector, all_general_subdimension_vectors export euler_form, euler_matrix, is_root, is_schur_root, is_real_root, is_imaginary_root, is_isotropic_root, general_ext, general_hom, canonical_decomposition, in_fundamental_domain, - bocklandt_reduction, is_coregular, is_cofree, dimension_nullcone, defect + bocklandt_reduction, is_coregular, is_cofree, dimension_nullcone, defect, + is_complete_intersection # Moduli export all_luna_types, is_luna_type, dimension_of_luna_stratum diff --git a/src/RepresentationTheory.jl b/src/RepresentationTheory.jl index 2d9b8b2..76179c4 100644 --- a/src/RepresentationTheory.jl +++ b/src/RepresentationTheory.jl @@ -789,12 +789,24 @@ julia> dimension_nullcone(kronecker_quiver(3), [2, 3]) all(di >= 0 for di in d) || throw(ArgumentError("dimension vector must be non-negative")) + presentation = __trace_presentation(Q, d) + isnothing(presentation) && return 0 + R, nvars, weights, traces = presentation + isempty(traces) && return nvars + return Singular.dimension(std(Singular.Ideal(R, traces...))) +end + +# The polynomial ring on the matrix entries of the representation space of the support +# of the setting, together with its number of variables, the lengths of the +# quasiprimitive cycles, and their traces, which generate the ring of invariants +# [MR958897]. Returns `nothing` when the representation space is a point. +function __trace_presentation(Q::Quiver, d::AbstractVector{Int}) keep = support(d) A, dd = Matrix{Int}(Q.adjacency)[keep, keep], Vector{Int}(d[keep]) n = length(dd) arrows_ = [(i, j) for i in 1:n for j in 1:n for _ in 1:A[i, j]] nvars = sum(dd[i] * dd[j] for (i, j) in arrows_; init=0) - nvars == 0 && return 0 + nvars == 0 && return nothing R, x = polynomial_ring(Singular.QQ, ["x$k" for k in 1:nvars]) # the matrix of the arrow a: i -> j has size d[j] x d[i], with fresh variable entries @@ -806,7 +818,6 @@ julia> dimension_nullcone(kronecker_quiver(3), [2, 3]) end cycles = __quasiprimitive_cycles(A, dd) - isempty(cycles) && return nvars traces = map(cycles) do cycle M = matrices[cycle[1]] for k in cycle[2:end] @@ -814,7 +825,78 @@ julia> dimension_nullcone(kronecker_quiver(3), [2, 3]) end sum(M[i, i] for i in 1:size(M, 1)) end - return Singular.dimension(std(Singular.Ideal(R, traces...))) + return R, nvars, length.(cycles), traces +end + +""" + is_complete_intersection(Q::Quiver, d::AbstractVector{Int}) + +Check whether the affine quotient variety of the quiver setting `(Q, d)`, which +parametrizes its semisimple representations, is a complete intersection. + +The ring of invariants is presented by the traces of the quasiprimitive cycles +[[Le Bruyn--Procesi](https://mathscinet.ams.org/mathscinet/relay-station?mr=958897)], +and the ideal of relations among them is computed as a kernel in Singular. As +being a complete intersection can be checked on the localization at the ideal of +positively graded elements, and is independent of the chosen graded presentation, the +setting is a complete intersection if and only if the number of minimal relations +equals the number of cycle generators minus the dimension of the quotient. + +Note that no combinatorial classification of complete intersection quiver settings is +known: [[Bocklandt](https://doi.org/10.1007/s10468-004-8324-8)] treats symmetric +settings without loops and [[Joo](https://arxiv.org/abs/1105.3067)] the settings with +one dimensional vertices, so we resort to the computational criterion. + +# Input + +- `Q::Quiver`: a quiver. +- `d::AbstractVector{Int}`: a dimension vector. + +# Output + +- whether the affine quotient of the setting `(Q, d)` is a complete intersection. + +# Examples + +Coregular settings are complete intersections; the double arrows between two vertices +of dimension 1 give the cone over a quadric surface, which is a hypersurface but not +coregular, and with triple arrows one gets a determinantal variety which is not a +complete intersection: + +```jldoctest +julia> is_complete_intersection(jordan_quiver(2), [2]) +true + +julia> Q = Quiver("1--2, 2--1"); + +julia> is_coregular(Q, [1, 1]), is_complete_intersection(Q, [1, 1]) +(false, true) + +julia> is_complete_intersection(Quiver("1---2, 2---1"), [1, 1]) +false +``` +""" +function is_complete_intersection(Q::Quiver, d::AbstractVector{Int}) + length(d) == n_vertices(Q) || + throw(ArgumentError("dimension vector must have length $(n_vertices(Q))")) + all(di >= 0 for di in d) || + throw(ArgumentError("dimension vector must be non-negative")) + + presentation = __trace_presentation(Q, d) + isnothing(presentation) && return true + R, _, weights, traces = presentation + isempty(traces) && return true + + # the relations are weighted-homogeneous for the cycle lengths, so the minimal + # number of relations is computed by mstd in a weighted polynomial ring + T, _ = polynomial_ring( + Singular.QQ, ["y$k" for k in eachindex(traces)]; ordering=Singular.ordering_wp(weights) + ) + relations = Singular.preimage( + Singular.AlgebraHomomorphism(T, R, traces), Singular.Ideal(R, R(0)) + ) + mu = count(!iszero, gens(Singular.mstd(relations)[2])) + return mu == length(traces) - Singular.dimension(std(relations)) end """ diff --git a/test/runtests.jl b/test/runtests.jl index 00db45f..85c9062 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -302,3 +302,18 @@ end; # the identity projection is flat @test is_flat(kronecker_quiver(3), [2, 3], [3, -2], [3, -2]) end; + +@testset "complete intersections" begin + # coregular settings are complete intersections + @test is_complete_intersection(jordan_quiver(2), [2]) + @test is_complete_intersection(jordan_quiver(1), [3]) + @test is_complete_intersection(kronecker_quiver(3), [2, 3]) + @test is_complete_intersection(cyclic_quiver(3), [1, 1, 1]) + + # the cone over the quadric surface: a hypersurface, hence a complete intersection, + # yet not coregular; for triple arrows one gets the cone over the Segre variety of + # rank one 3 x 3 matrices, of codimension 4 with nine minimal relations + @test !is_coregular(Quiver("1--2, 2--1"), [1, 1]) + @test is_complete_intersection(Quiver("1--2, 2--1"), [1, 1]) + @test !is_complete_intersection(Quiver("1---2, 2---1"), [1, 1]) +end; From 9b468a524d645b93bae7c61ef3e860f46093f180 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Thu, 9 Jul 2026 13:50:46 +0200 Subject: [PATCH 13/22] refactor: deduplicate local quiver plumbing --- src/Moduli.jl | 31 ++++++++++++++----------------- src/RepresentationTheory.jl | 11 +++++------ 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/Moduli.jl b/src/Moduli.jl index bd0c9d8..da7c325 100644 --- a/src/Moduli.jl +++ b/src/Moduli.jl @@ -539,7 +539,14 @@ function local_quiver_setting(M::QuiverModuli, tau) Qloc = Quiver(A) dloc = [m for e in keys(tau) for m in tau[e]] - return Dict("Q" => Qloc, "d" => dloc) + return Dict("Q" => Qloc, "d" => dloc, "summands" => summands) +end + +# whether the local quiver setting of the Luna type is coregular, i.e., whether the +# moduli space is smooth along the corresponding stratum +function __is_smooth_stratum(M::QuiverModuli, tau) + setting = local_quiver_setting(M, tau) + return is_coregular(setting["Q"], setting["d"]) end """ @@ -798,10 +805,7 @@ function is_smooth(M::QuiverModuliSpace) # coregularity of its local quiver setting, by combining the étale-local description # of [MR1972892] with [Theorem 2.1, MR1929191]; this is the globalization of # [Theorem 4.2, MR1929191] to arbitrary stability parameters - return all(all_luna_types(M)) do tau - setting = local_quiver_setting(M, tau) - is_coregular(setting["Q"], setting["d"]) - end + return all(tau -> __is_smooth_stratum(M, tau), all_luna_types(M)) end """ @@ -848,10 +852,7 @@ function codimension_singular_locus(M::QuiverModuliSpace) # the stratum of a Luna type consists of singular points if and only if its local # quiver setting is not coregular; the stable stratum is always smooth - singular = filter(all_luna_types(M)) do tau - setting = local_quiver_setting(M, tau) - !is_coregular(setting["Q"], setting["d"]) - end + singular = filter(tau -> !__is_smooth_stratum(M, tau), all_luna_types(M)) isempty(singular) && return Inf return dimension(M) - maximum(dimension_of_luna_stratum(M, tau) for tau in singular) end @@ -986,18 +987,14 @@ function fibre_dimension( denom::Function=sum, ) Mbar = QuiverModuliSpace(Q, d, coerce_vector(thetabar)) - is_luna_type(Mbar, tau) || throw(DomainError("not a Luna type for thetabar")) # the local quiver setting of tau, with the stability induced by theta - summands = [Vector{Int}(e) for e in keys(tau) for _ in tau[e]] - s = length(summands) - A = [(k == l ? 1 : 0) - euler_form(Q, summands[k], summands[l]) for k in 1:s, l in 1:s] - dloc = [m for e in keys(tau) for m in tau[e]] - thetaloc = [theta' * e for e in summands] + setting = local_quiver_setting(Mbar, tau) + thetaloc = [theta' * e for e in setting["summands"]] # a repeated rigid summand is not realized by any representation - any(<(0), A) && return -Inf - f = __dimension_nilpotent_moduli(Quiver(A), dloc, thetaloc, denom) + any(<(0), setting["Q"].adjacency) && return -Inf + f = __dimension_nilpotent_moduli(setting["Q"], setting["d"], thetaloc, denom) return isfinite(f) ? Int(f) : f end diff --git a/src/RepresentationTheory.jl b/src/RepresentationTheory.jl index 76179c4..8323ff2 100644 --- a/src/RepresentationTheory.jl +++ b/src/RepresentationTheory.jl @@ -286,15 +286,14 @@ end # its cache. function __bocklandt_step(A::Matrix{Int}, d::Vector{Int}) n = length(d) - # \chi(d, e_v) and \chi(e_v, d), for e_v the unit vector at the vertex v - chi_in(v) = d[v] - sum(d[w] * A[w, v] for w in 1:n) - chi_out(v) = d[v] - sum(A[v, w] * d[w] for w in 1:n) + # the vectors of \chi(d, e_v) and \chi(e_v, d), for e_v the unit vector at v + chi_in, chi_out = d - A' * d, d - A * d for v in 1:n # R_I [Lemma 3.2, MR1929191]: remove a loopless vertex whose incoming or outgoing # paths carry at most d[v] dimensions, shortcutting every path through it; a lone # vertex is kept so that the reduced coregular settings are the three settings of # [Theorem 1.1, MR1929191] - if A[v, v] == 0 && n > 1 && (chi_in(v) >= 0 || chi_out(v) >= 0) + if A[v, v] == 0 && n > 1 && (chi_in[v] >= 0 || chi_out[v] >= 0) keep = setdiff(1:n, v) return A[keep, keep] + A[keep, v] * A[v, keep]', d[keep] end @@ -308,10 +307,10 @@ function __bocklandt_step(A::Matrix{Int}, d::Vector{Int}) # loop and, besides the loop, a single incoming (resp. outgoing) arrow from # (resp. to) a vertex of dimension 1, remove the loop and thicken that arrow to # k parallel arrows - if A[v, v] == 1 && d[v] >= 2 && (chi_in(v) == -1 || chi_out(v) == -1) + if A[v, v] == 1 && d[v] >= 2 && (chi_in[v] == -1 || chi_out[v] == -1) B = copy(A) B[v, v] = 0 - if chi_in(v) == -1 + if chi_in[v] == -1 u = findfirst(w -> w != v && A[w, v] > 0, 1:n) B[u, v] = d[v] else From ca42298f8659399aa3e57c0e801da9feb6df34c0 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Fri, 10 Jul 2026 15:18:41 +0200 Subject: [PATCH 14/22] fix: repair escaping in is_coprime docstring --- src/Moduli.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Moduli.jl b/src/Moduli.jl index da7c325..4ebb3f4 100644 --- a/src/Moduli.jl +++ b/src/Moduli.jl @@ -43,7 +43,7 @@ end is_coprime(M::QuiverModuli) Checks if the stability parameter is coprime with the dimension vector, -i.e., if for all subdimension vectors ``e`` of ``d``, ``\\\theta\\cdot e \\neq 0``. +i.e., if for all subdimension vectors ``e`` of ``d``, ``\\theta\\cdot e \\neq 0``. # Input From 5961f6eb1df07e7e5bc002f221012a85856daa67 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Fri, 10 Jul 2026 15:19:42 +0200 Subject: [PATCH 15/22] fix: compute the nullcone dimension via the motivic recursion The Groebner computation over the quasiprimitive cycle traces cuts out a variety that can strictly contain the nullcone; the socle recursion of Goesmann--Reineke (doi:10.3842/SIGMA.2026.020) is exact and much faster. --- src/RepresentationTheory.jl | 84 +++++++++++++++++++++++++++++++------ test/runtests.jl | 5 +++ 2 files changed, 76 insertions(+), 13 deletions(-) diff --git a/src/RepresentationTheory.jl b/src/RepresentationTheory.jl index 8323ff2..c63625e 100644 --- a/src/RepresentationTheory.jl +++ b/src/RepresentationTheory.jl @@ -739,18 +739,77 @@ function __quasiprimitive_cycles(A::Matrix{Int}, d::Vector{Int}) return sort!(collect(cycles)) end +# product of dense integer polynomials, given by their coefficient vectors in +# ascending degree +function __polymul(p::Vector{BigInt}, q::Vector{BigInt}) + r = zeros(BigInt, length(p) + length(q) - 1) + for (i, a) in pairs(p), (j, b) in pairs(q) + r[i + j - 1] += a * b + end + return r +end + +# the Gaussian binomial coefficient as a polynomial in the Lefschetz motive, given by +# its coefficient vector, using the q-Pascal recursion +@memoize Dict function __gaussian_binomial(n::Int, k::Int) + (k == 0 || k == n) && return [big(1)] + p, q = __gaussian_binomial(n - 1, k - 1), __gaussian_binomial(n - 1, k) + r = zeros(BigInt, max(length(p), k + length(q))) + r[eachindex(p)] .+= p + r[k .+ eachindex(q)] .+= q + return r +end + +# The motive of the nullcone of the setting (A, d) as a polynomial in the Lefschetz +# motive L, given by its coefficient vector, following the recursion of +# [Corollary 3.1, doi:10.3842/SIGMA.2026.020], which stratifies the nullcone by the +# dimension vector of the socle: +# +# [N_d] = -\sum_{e < d} (-1)^{|d| - |e|} L^{s(e, d)} [d; e]_L [N_e], +# +# where s(e, d) = \sum_i binomial(d_i - e_i, 2) + \sum_{a: i -> j} e_i (d_j - e_j). +function __nullcone_motive(A::Matrix{Int}, d::Vector{Int}) + n = length(d) + motives = Dict{Vector{Int},Vector{BigInt}}(zeros(Int, n) => [big(1)]) + for e in sort!(all_subdimension_vectors(d; nonzero=true); by=sum) + total = BigInt[] + for f in all_subdimension_vectors(e; strict=true) + term = motives[f] + for i in 1:n + term = __polymul(term, __gaussian_binomial(e[i], f[i])) + end + shift = + sum(binomial(e[i] - f[i], 2) for i in 1:n) + + sum(A[i, j] * f[i] * (e[j] - f[j]) for i in 1:n, j in 1:n) + length(total) < shift + length(term) && + append!(total, zeros(BigInt, shift + length(term) - length(total))) + sign = isodd(sum(e) - sum(f)) ? 1 : -1 + total[shift .+ eachindex(term)] .+= sign .* term + end + while length(total) > 1 && iszero(total[end]) + pop!(total) + end + motives[e] = total + end + return motives[d] +end + """ dimension_nullcone(Q::Quiver, d::AbstractVector{Int}) Compute the dimension of the nullcone of the quiver setting `(Q, d)`. The nullcone is the fibre of the quotient map to the affine quotient over the image of -the zero representation; it consists of the nilpotent representations, i.e., those on -which every invariant of positive degree vanishes. Since the ring of invariants is -generated by traces of quasiprimitive cycles by -[[Le Bruyn--Procesi](https://mathscinet.ams.org/mathscinet/relay-station?mr=958897)], -the nullcone is the vanishing locus of these traces, and its dimension is computed as -a Krull dimension using a Groebner basis computation in Singular. +the zero representation; it consists of the nilpotent representations, i.e., those +admitting a filtration by the vertex simples, or equivalently those on which the trace +of every oriented cycle vanishes. Its class in the Grothendieck ring of varieties is a +polynomial in the Lefschetz motive, computed here by the recursion of +[[Corollary 3.1, Gösmann--Reineke](https://doi.org/10.3842/SIGMA.2026.020)], which +stratifies the nullcone by the dimension vector of the socle; the dimension of the +nullcone is the degree of this polynomial. For a symmetric quiver it is given by the +closed formula ``\\sum_i (r_{ii} + 1)\\binom{d_i}{2} + \\sum_{i < j} r_{ij} d_i d_j`` +of [Remark 3.6, loc. cit.], where ``r_{ij}`` is the number of arrows between ``i`` +and ``j``. # Input @@ -764,8 +823,8 @@ a Krull dimension using a Groebner basis computation in Singular. # Examples The nullcone of the Jordan quiver consists of the nilpotent matrices; for pairs of -``2 \\times 2`` matrices it consists of the pairs that are simultaneously strictly -triangularizable: +``2 \\times 2`` or ``4 \\times 4`` matrices it consists of the pairs that are +simultaneously strictly triangularizable: ```jldoctest julia> dimension_nullcone(jordan_quiver(1), [3]) @@ -773,6 +832,9 @@ julia> dimension_nullcone(jordan_quiver(1), [3]) julia> dimension_nullcone(jordan_quiver(2), [2]) 3 + +julia> dimension_nullcone(jordan_quiver(2), [4]) +18 ``` For an acyclic quiver there are no invariants, so the nullcone is everything: @@ -788,11 +850,7 @@ julia> dimension_nullcone(kronecker_quiver(3), [2, 3]) all(di >= 0 for di in d) || throw(ArgumentError("dimension vector must be non-negative")) - presentation = __trace_presentation(Q, d) - isnothing(presentation) && return 0 - R, nvars, weights, traces = presentation - isempty(traces) && return nvars - return Singular.dimension(std(Singular.Ideal(R, traces...))) + return length(__nullcone_motive(Matrix{Int}(Q.adjacency), Vector{Int}(d))) - 1 end # The polynomial ring on the matrix entries of the representation space of the support diff --git a/test/runtests.jl b/test/runtests.jl index 85c9062..3368b71 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -257,6 +257,11 @@ end; @test dimension_nullcone(jordan_quiver(2), [2]) == 3 # acyclic settings have no invariants, so the nullcone is everything @test dimension_nullcone(kronecker_quiver(3), [2, 3]) == 18 + # the motivic recursion reaches larger settings, and for symmetric quivers it + # matches the closed formula of [Remark 3.6, doi:10.3842/SIGMA.2026.020] + @test dimension_nullcone(jordan_quiver(2), [4]) == 18 + @test dimension_nullcone(jordan_quiver(3), [2]) == 4 + @test dimension_nullcone(Quiver("1-1, 1-2, 2-1"), [2, 3]) == 11 # the defect measures the failure of equidimensionality: it vanishes for cofree # settings, and for the settings 2 <=> k it decreases to zero as k grows to 4 From 0743f2ef39748aa88c9ef787cfd9ae3cc410c5a3 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Fri, 10 Jul 2026 15:20:15 +0200 Subject: [PATCH 16/22] refactor: name weakly connected components --- src/RepresentationTheory.jl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/RepresentationTheory.jl b/src/RepresentationTheory.jl index c63625e..552f9e9 100644 --- a/src/RepresentationTheory.jl +++ b/src/RepresentationTheory.jl @@ -531,6 +531,11 @@ function __wedge_step(A::Matrix{Int}, d::Vector{Int}) return nothing end +# the weakly connected components of the quiver with adjacency matrix A, as the +# strongly connected components of its double +__weakly_connected_components(A::Matrix{Int}) = + strongly_connected_components(Quiver(A + A')) + # Split a strongly connected quiver setting into its prime components, i.e., the # summands of its decomposition as an iterated connected sum at vertices of # dimension 1; a setting is cofree iff its prime components are [Lemma 3]. @@ -540,8 +545,7 @@ function __prime_components(A::Matrix{Int}, d::Vector{Int}) # the summands at v are the weakly connected components of the quiver minus v, # each taken together with v and the arrows between them, and every loop at v others = setdiff(1:n, v) - U = A[others, others] - pieces = strongly_connected_components(Quiver(U + U')) + pieces = __weakly_connected_components(A[others, others]) length(pieces) + A[v, v] >= 2 || continue out = [(fill(1, 1, 1), [1]) for _ in 1:A[v, v]] for piece in pieces @@ -1014,7 +1018,7 @@ function __dimension_affine_quotient(Q::Quiver, d::AbstractVector{Int}) QuiverModuliSpace(Quiver(A[c, c]), Vector{Int}(d[keep[c]]), zeros(Int, length(c))) ), ) - for c in strongly_connected_components(Quiver(A + A')); # weakly connected components + for c in __weakly_connected_components(A); init=0, ) end From 44e22e63cb4fad0dda5219f55b7e601991d6d034 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Fri, 10 Jul 2026 15:20:59 +0200 Subject: [PATCH 17/22] fix: require King normalization for projections to walls --- src/Moduli.jl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Moduli.jl b/src/Moduli.jl index 4ebb3f4..45d4c13 100644 --- a/src/Moduli.jl +++ b/src/Moduli.jl @@ -943,7 +943,8 @@ certification fails. - `Q::Quiver`: a quiver. - `d::AbstractVector{Int}`: a dimension vector. -- `theta::AbstractVector{Int}`: the stability parameter of the source. +- `theta::AbstractVector{Int}`: the stability parameter of the source, which must + satisfy ``\\theta \\cdot d = 0``; an `ArgumentError` is thrown otherwise. - `thetabar::AbstractVector{Int}`: the stability parameter of the target, assumed to lie in the closure of the chamber containing `theta`. - `tau`: a Luna type for `thetabar`; see [`LunaType`](@ref). @@ -986,6 +987,9 @@ function fibre_dimension( tau; denom::Function=sum, ) + # the local stability parameter below is King's normalization of theta + theta' * d == 0 || + throw(ArgumentError("the stability parameter theta must satisfy theta . d = 0")) Mbar = QuiverModuliSpace(Q, d, coerce_vector(thetabar)) # the local quiver setting of tau, with the stability induced by theta @@ -1019,7 +1023,8 @@ stratum. When the target is singular this criterion does not apply, and an - `Q::Quiver`: a quiver. - `d::AbstractVector{Int}`: a dimension vector. -- `theta::AbstractVector{Int}`: the stability parameter of the source. +- `theta::AbstractVector{Int}`: the stability parameter of the source, which must + satisfy ``\\theta \\cdot d = 0``. - `thetabar::AbstractVector{Int}`: the stability parameter of the target, assumed to lie in the closure of the chamber containing `theta`. @@ -1081,7 +1086,8 @@ the fibre dimension over it is at most the dimension of the source; see - `Q::Quiver`: a quiver. - `d::AbstractVector{Int}`: a dimension vector. -- `theta::AbstractVector{Int}`: the stability parameter of the source. +- `theta::AbstractVector{Int}`: the stability parameter of the source, which must + satisfy ``\\theta \\cdot d = 0``. - `thetabar::AbstractVector{Int}`: the stability parameter of the target, assumed to lie in the closure of the chamber containing `theta`. From 57bb9048fa028cf661c74cacf83902a1e8894189 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Fri, 10 Jul 2026 15:21:41 +0200 Subject: [PATCH 18/22] fix: pass denom through projections to walls --- src/Moduli.jl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Moduli.jl b/src/Moduli.jl index 45d4c13..96874d2 100644 --- a/src/Moduli.jl +++ b/src/Moduli.jl @@ -990,7 +990,7 @@ function fibre_dimension( # the local stability parameter below is King's normalization of theta theta' * d == 0 || throw(ArgumentError("the stability parameter theta must satisfy theta . d = 0")) - Mbar = QuiverModuliSpace(Q, d, coerce_vector(thetabar)) + Mbar = QuiverModuliSpace(Q, d, coerce_vector(thetabar), "semistable", denom) # the local quiver setting of tau, with the stability induced by theta setting = local_quiver_setting(Mbar, tau) @@ -1055,13 +1055,15 @@ function is_flat( thetabar::AbstractVector{Int}; denom::Function=sum, ) - Mbar = QuiverModuliSpace(Q, d, coerce_vector(thetabar)) + Mbar = QuiverModuliSpace(Q, d, coerce_vector(thetabar), "semistable", denom) is_smooth(Mbar) || throw( ArgumentError( "the target of the projection is singular; miracle flatness does not apply" ), ) - generic = dimension(QuiverModuliSpace(Q, d, coerce_vector(theta))) - dimension(Mbar) + generic = + dimension(QuiverModuliSpace(Q, d, coerce_vector(theta), "semistable", denom)) - + dimension(Mbar) # types with fibre dimension -Inf are not realized, so they are skipped return all(all_luna_types(Mbar)) do tau f = fibre_dimension(Q, d, theta, thetabar, tau; denom=denom) @@ -1119,8 +1121,8 @@ function is_semismall( thetabar::AbstractVector{Int}; denom::Function=sum, ) - Mbar = QuiverModuliSpace(Q, d, coerce_vector(thetabar)) - dM = dimension(QuiverModuliSpace(Q, d, coerce_vector(theta))) + Mbar = QuiverModuliSpace(Q, d, coerce_vector(thetabar), "semistable", denom) + dM = dimension(QuiverModuliSpace(Q, d, coerce_vector(theta), "semistable", denom)) # types with fibre dimension -Inf are not realized, so they are skipped return all(all_luna_types(Mbar)) do tau f = fibre_dimension(Q, d, theta, thetabar, tau; denom=denom) From 83ed81bc0293b4480e3f605d0558d80a9adb822c Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Fri, 10 Jul 2026 15:22:08 +0200 Subject: [PATCH 19/22] docs: document the summands of a local quiver setting --- src/Moduli.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Moduli.jl b/src/Moduli.jl index 96874d2..786890d 100644 --- a/src/Moduli.jl +++ b/src/Moduli.jl @@ -520,7 +520,9 @@ Returns the local quiver and dimension vector for the given Luna type. # Output -- a dictionary with the local quiver `Q` and dimension vector `d` for the given Luna type. +- a dictionary with the local quiver `Q`, its dimension vector `d`, and the list + `summands` of the dimension vectors of the stable summands, one for each vertex of + the local quiver, ordered compatibly with `d`. """ function local_quiver_setting(M::QuiverModuli, tau) if !is_luna_type(M, tau) From 94eabc3b0eab7ce3022e0ddcafa9a7e42113d9d0 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Fri, 10 Jul 2026 15:24:49 +0200 Subject: [PATCH 20/22] feat: motives of nullcones --- docs/src/methods/representation-theory.md | 8 +++- src/QuiverTools.jl | 4 +- src/RepresentationTheory.jl | 54 +++++++++++++++++++++++ test/runtests.jl | 4 ++ 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/docs/src/methods/representation-theory.md b/docs/src/methods/representation-theory.md index 04eb0a4..af66782 100644 --- a/docs/src/methods/representation-theory.md +++ b/docs/src/methods/representation-theory.md @@ -45,13 +45,17 @@ coregularity of local quiver settings decides smoothness of moduli spaces; see [`is_smooth`](@ref) and [`codimension_singular_locus`](@ref). The *nullcone* is the locus of nilpotent representations, i.e., the fibre of the -quotient map over the image of the zero representation. Its dimension controls the -failure of equidimensionality of the quotient map, measured by the *defect*. +quotient map over the image of the zero representation. Its class in the Grothendieck +ring of varieties is a polynomial in the Lefschetz motive, by +[[Gösmann--Reineke](https://doi.org/10.3842/SIGMA.2026.020)], and its dimension +controls the failure of equidimensionality of the quotient map, measured by the +*defect*. ```@docs bocklandt_reduction is_coregular is_cofree +nullcone_motive dimension_nullcone defect is_complete_intersection diff --git a/src/QuiverTools.jl b/src/QuiverTools.jl index 83a55bc..1132ce2 100644 --- a/src/QuiverTools.jl +++ b/src/QuiverTools.jl @@ -60,8 +60,8 @@ export is_general_subdimension_vector, all_general_subdimension_vectors export euler_form, euler_matrix, is_root, is_schur_root, is_real_root, is_imaginary_root, is_isotropic_root, general_ext, general_hom, canonical_decomposition, in_fundamental_domain, - bocklandt_reduction, is_coregular, is_cofree, dimension_nullcone, defect, - is_complete_intersection + bocklandt_reduction, is_coregular, is_cofree, dimension_nullcone, nullcone_motive, + defect, is_complete_intersection # Moduli export all_luna_types, is_luna_type, dimension_of_luna_stratum diff --git a/src/RepresentationTheory.jl b/src/RepresentationTheory.jl index 552f9e9..58dbda2 100644 --- a/src/RepresentationTheory.jl +++ b/src/RepresentationTheory.jl @@ -857,6 +857,60 @@ julia> dimension_nullcone(kronecker_quiver(3), [2, 3]) return length(__nullcone_motive(Matrix{Int}(Q.adjacency), Vector{Int}(d))) - 1 end +""" + nullcone_motive(Q::Quiver, d::AbstractVector{Int}) + +Compute the motive of the nullcone of the quiver setting `(Q, d)`. + +The class of the nullcone in the Grothendieck ring of varieties is a polynomial in the +Lefschetz motive ``\\mathbb{L}``, computed by the recursion of +[[Corollary 3.1, Gösmann--Reineke](https://doi.org/10.3842/SIGMA.2026.020)], which +stratifies the nullcone by the dimension vector of the socle. It is returned as an +element of the field ``\\mathbb{Q}(L)``, as for [`motive`](@ref), and its degree is +[`dimension_nullcone`](@ref). + +# Input + +- `Q::Quiver`: a quiver. +- `d::AbstractVector{Int}`: a dimension vector. + +# Output + +- the motive of the nullcone of the setting `(Q, d)`, as a polynomial in the Lefschetz + motive `L`. + +# Examples + +The variety of nilpotent ``d \\times d`` matrices has motive ``\\mathbb{L}^{d(d-1)}``, +whilst for tuples of matrices the motive is no longer a single power: + +```jldoctest +julia> nullcone_motive(jordan_quiver(1), [3]) +L^6 + +julia> nullcone_motive(jordan_quiver(2), [2]) +L^3 + L^2 - L +``` + +For an acyclic quiver the nullcone is the whole representation variety: + +```jldoctest +julia> nullcone_motive(kronecker_quiver(3), [2, 3]) +L^18 +``` +""" +function nullcone_motive(Q::Quiver, d::AbstractVector{Int}) + length(d) == n_vertices(Q) || + throw(ArgumentError("dimension vector must have length $(n_vertices(Q))")) + all(di >= 0 for di in d) || + throw(ArgumentError("dimension vector must be non-negative")) + + coefficients = __nullcone_motive(Matrix{Int}(Q.adjacency), Vector{Int}(d)) + K, L = Singular.FunctionField(Singular.QQ, ["L"]) + L = L[1] + return sum(K(coefficients[k]) * L^(k - 1) for k in eachindex(coefficients)) +end + # The polynomial ring on the matrix entries of the representation space of the support # of the setting, together with its number of variables, the lengths of the # quasiprimitive cycles, and their traces, which generate the ring of invariants diff --git a/test/runtests.jl b/test/runtests.jl index 3368b71..dd3fa32 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -262,6 +262,10 @@ end; @test dimension_nullcone(jordan_quiver(2), [4]) == 18 @test dimension_nullcone(jordan_quiver(3), [2]) == 4 @test dimension_nullcone(Quiver("1-1, 1-2, 2-1"), [2, 3]) == 11 + # the motive of pairs of nilpotent 3x3 matrices is + # L^9 + 2L^8 - L^6 - 2L^5 + L^3 [Example (4) in Section 3.2, loc. cit.] + @test QuiverTools.__nullcone_motive(fill(2, 1, 1), [3]) == + [0, 0, 0, 1, 0, -2, -1, 0, 2, 1] # the defect measures the failure of equidimensionality: it vanishes for cofree # settings, and for the settings 2 <=> k it decreases to zero as k grows to 4 From 734cc8d1d81641710637514ae4313f529fd64245 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Fri, 10 Jul 2026 15:48:20 +0200 Subject: [PATCH 21/22] test: cover the King normalization check --- test/runtests.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index dd3fa32..6450c02 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -310,6 +310,11 @@ end; # the identity projection is flat @test is_flat(kronecker_quiver(3), [2, 3], [3, -2], [3, -2]) + + # the source stability parameter must be King-normalized + @test_throws ArgumentError fibre_dimension( + S, d, [1, 1, 1, 1, 1, 1, -7], thetabar, Dict(d => [1]) + ) end; @testset "complete intersections" begin From bd3d16db0f01957802380f40787f0381d45a0f90 Mon Sep 17 00:00:00 2001 From: Pieter Belmans Date: Fri, 10 Jul 2026 16:00:10 +0200 Subject: [PATCH 22/22] refactor: remove is_complete_intersection The quasiprimitive cycle traces do not generate the ring of invariants, so the criterion is unreliable, and infeasible on the settings where it is not. A classification-based check (Bocklandt for symmetric settings, Joo for thin ones, Le Bruyn--Teranishi for matrix invariants) can replace it later. --- docs/src/methods/representation-theory.md | 1 - src/QuiverTools.jl | 2 +- src/RepresentationTheory.jl | 132 ---------------------- test/runtests.jl | 15 --- 4 files changed, 1 insertion(+), 149 deletions(-) diff --git a/docs/src/methods/representation-theory.md b/docs/src/methods/representation-theory.md index af66782..96561d2 100644 --- a/docs/src/methods/representation-theory.md +++ b/docs/src/methods/representation-theory.md @@ -58,5 +58,4 @@ is_cofree nullcone_motive dimension_nullcone defect -is_complete_intersection ``` diff --git a/src/QuiverTools.jl b/src/QuiverTools.jl index 1132ce2..b9eea8a 100644 --- a/src/QuiverTools.jl +++ b/src/QuiverTools.jl @@ -61,7 +61,7 @@ export euler_form, euler_matrix, is_root, is_schur_root, is_real_root, is_imagin is_isotropic_root, general_ext, general_hom, canonical_decomposition, in_fundamental_domain, bocklandt_reduction, is_coregular, is_cofree, dimension_nullcone, nullcone_motive, - defect, is_complete_intersection + defect # Moduli export all_luna_types, is_luna_type, dimension_of_luna_stratum diff --git a/src/RepresentationTheory.jl b/src/RepresentationTheory.jl index 58dbda2..966c614 100644 --- a/src/RepresentationTheory.jl +++ b/src/RepresentationTheory.jl @@ -714,35 +714,6 @@ end # Nullcones ######################################################################################## -# All quasiprimitive cycles of the setting, as sequences of arrow indices into the -# list of arrows ordered as in `arrows`, up to rotation. -function __quasiprimitive_cycles(A::Matrix{Int}, d::Vector{Int}) - n = length(d) - arrows_ = [(i, j) for i in 1:n for j in 1:n for _ in 1:A[i, j]] - cycles = Set{Vector{Int}}() - budget, path = copy(d), Int[] - function walk(x::Int, base::Int) - budget[x] == 0 && return nothing - budget[x] -= 1 - for (k, (i, j)) in enumerate(arrows_) - i == x || continue - push!(path, k) - # a quasiprimitive cycle may pass through the base vertex several times, so - # both close the cycle here and keep walking - j == base && - push!(cycles, minimum(vcat(path[r:end], path[1:(r - 1)]) for r in eachindex(path))) - walk(j, base) - pop!(path) - end - budget[x] += 1 - return nothing - end - for v in 1:n - walk(v, v) - end - return sort!(collect(cycles)) -end - # product of dense integer polynomials, given by their coefficient vectors in # ascending degree function __polymul(p::Vector{BigInt}, q::Vector{BigInt}) @@ -911,109 +882,6 @@ function nullcone_motive(Q::Quiver, d::AbstractVector{Int}) return sum(K(coefficients[k]) * L^(k - 1) for k in eachindex(coefficients)) end -# The polynomial ring on the matrix entries of the representation space of the support -# of the setting, together with its number of variables, the lengths of the -# quasiprimitive cycles, and their traces, which generate the ring of invariants -# [MR958897]. Returns `nothing` when the representation space is a point. -function __trace_presentation(Q::Quiver, d::AbstractVector{Int}) - keep = support(d) - A, dd = Matrix{Int}(Q.adjacency)[keep, keep], Vector{Int}(d[keep]) - n = length(dd) - arrows_ = [(i, j) for i in 1:n for j in 1:n for _ in 1:A[i, j]] - nvars = sum(dd[i] * dd[j] for (i, j) in arrows_; init=0) - nvars == 0 && return nothing - - R, x = polynomial_ring(Singular.QQ, ["x$k" for k in 1:nvars]) - # the matrix of the arrow a: i -> j has size d[j] x d[i], with fresh variable entries - offset = 0 - matrices = map(arrows_) do (i, j) - M = [x[offset + r + (c - 1) * dd[j]] for r in 1:dd[j], c in 1:dd[i]] - offset += dd[i] * dd[j] - M - end - - cycles = __quasiprimitive_cycles(A, dd) - traces = map(cycles) do cycle - M = matrices[cycle[1]] - for k in cycle[2:end] - M = matrices[k] * M - end - sum(M[i, i] for i in 1:size(M, 1)) - end - return R, nvars, length.(cycles), traces -end - -""" - is_complete_intersection(Q::Quiver, d::AbstractVector{Int}) - -Check whether the affine quotient variety of the quiver setting `(Q, d)`, which -parametrizes its semisimple representations, is a complete intersection. - -The ring of invariants is presented by the traces of the quasiprimitive cycles -[[Le Bruyn--Procesi](https://mathscinet.ams.org/mathscinet/relay-station?mr=958897)], -and the ideal of relations among them is computed as a kernel in Singular. As -being a complete intersection can be checked on the localization at the ideal of -positively graded elements, and is independent of the chosen graded presentation, the -setting is a complete intersection if and only if the number of minimal relations -equals the number of cycle generators minus the dimension of the quotient. - -Note that no combinatorial classification of complete intersection quiver settings is -known: [[Bocklandt](https://doi.org/10.1007/s10468-004-8324-8)] treats symmetric -settings without loops and [[Joo](https://arxiv.org/abs/1105.3067)] the settings with -one dimensional vertices, so we resort to the computational criterion. - -# Input - -- `Q::Quiver`: a quiver. -- `d::AbstractVector{Int}`: a dimension vector. - -# Output - -- whether the affine quotient of the setting `(Q, d)` is a complete intersection. - -# Examples - -Coregular settings are complete intersections; the double arrows between two vertices -of dimension 1 give the cone over a quadric surface, which is a hypersurface but not -coregular, and with triple arrows one gets a determinantal variety which is not a -complete intersection: - -```jldoctest -julia> is_complete_intersection(jordan_quiver(2), [2]) -true - -julia> Q = Quiver("1--2, 2--1"); - -julia> is_coregular(Q, [1, 1]), is_complete_intersection(Q, [1, 1]) -(false, true) - -julia> is_complete_intersection(Quiver("1---2, 2---1"), [1, 1]) -false -``` -""" -function is_complete_intersection(Q::Quiver, d::AbstractVector{Int}) - length(d) == n_vertices(Q) || - throw(ArgumentError("dimension vector must have length $(n_vertices(Q))")) - all(di >= 0 for di in d) || - throw(ArgumentError("dimension vector must be non-negative")) - - presentation = __trace_presentation(Q, d) - isnothing(presentation) && return true - R, _, weights, traces = presentation - isempty(traces) && return true - - # the relations are weighted-homogeneous for the cycle lengths, so the minimal - # number of relations is computed by mstd in a weighted polynomial ring - T, _ = polynomial_ring( - Singular.QQ, ["y$k" for k in eachindex(traces)]; ordering=Singular.ordering_wp(weights) - ) - relations = Singular.preimage( - Singular.AlgebraHomomorphism(T, R, traces), Singular.Ideal(R, R(0)) - ) - mu = count(!iszero, gens(Singular.mstd(relations)[2])) - return mu == length(traces) - Singular.dimension(std(relations)) -end - """ defect(Q::Quiver, d::AbstractVector{Int}) diff --git a/test/runtests.jl b/test/runtests.jl index 6450c02..a4f1e8c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -316,18 +316,3 @@ end; S, d, [1, 1, 1, 1, 1, 1, -7], thetabar, Dict(d => [1]) ) end; - -@testset "complete intersections" begin - # coregular settings are complete intersections - @test is_complete_intersection(jordan_quiver(2), [2]) - @test is_complete_intersection(jordan_quiver(1), [3]) - @test is_complete_intersection(kronecker_quiver(3), [2, 3]) - @test is_complete_intersection(cyclic_quiver(3), [1, 1, 1]) - - # the cone over the quadric surface: a hypersurface, hence a complete intersection, - # yet not coregular; for triple arrows one gets the cone over the Segre variety of - # rank one 3 x 3 matrices, of codimension 4 with nine minimal relations - @test !is_coregular(Quiver("1--2, 2--1"), [1, 1]) - @test is_complete_intersection(Quiver("1--2, 2--1"), [1, 1]) - @test !is_complete_intersection(Quiver("1---2, 2---1"), [1, 1]) -end;