diff --git a/docs/src/methods/quiver-moduli.md b/docs/src/methods/quiver-moduli.md index 4ee95d4..3ee6082 100644 --- a/docs/src/methods/quiver-moduli.md +++ b/docs/src/methods/quiver-moduli.md @@ -26,6 +26,10 @@ Black-box methods are provided to study some of their properties. is_nonempty dimension is_smooth +codimension_singular_locus +fibre_dimension +is_flat +is_semismall is_projective index motive 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/docs/src/methods/representation-theory.md b/docs/src/methods/representation-theory.md index 77697f9..96561d2 100644 --- a/docs/src/methods/representation-theory.md +++ b/docs/src/methods/representation-theory.md @@ -18,3 +18,44 @@ 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). + +The *nullcone* is the locus of nilpotent representations, i.e., the fibre of the +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 +``` diff --git a/src/Moduli.jl b/src/Moduli.jl index 4dc9f19..c7394da 100644 --- a/src/Moduli.jl +++ b/src/Moduli.jl @@ -530,7 +530,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) @@ -549,7 +551,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 """ @@ -762,6 +771,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. @@ -776,6 +794,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 ``` @@ -787,7 +825,333 @@ 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(tau -> __is_smooth_stratum(M, tau), all_luna_types(M)) +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(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 + +######################################################################################## +# 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, 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). + +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, +) + # 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), "semistable", denom) + + # the local quiver setting of tau, with the stability induced by theta + 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), setting["Q"].adjacency) && return -Inf + f = __dimension_nilpotent_moduli(setting["Q"], setting["d"], 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, 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`. + +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), "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), "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) + 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, 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`. + +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), "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) + f == -Inf || dimension_of_luna_stratum(Mbar, tau) + 2 * f <= dM + end end """ diff --git a/src/QuiverTools.jl b/src/QuiverTools.jl index 7186e68..b37efb0 100644 --- a/src/QuiverTools.jl +++ b/src/QuiverTools.jl @@ -40,7 +40,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, star_quiver, @@ -60,11 +60,15 @@ 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, is_cofree, dimension_nullcone, nullcone_motive, + defect # 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, + fibre_dimension, is_flat, is_semismall, + is_smooth, is_projective, is_strongly_amply_stable, semistable_equals_stable, semisimple_moduli_space # Hodge diff --git a/src/Quivers.jl b/src/Quivers.jl index 5ed7182..efb365b 100644 --- a/src/Quivers.jl +++ b/src/Quivers.jl @@ -104,6 +104,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) diff --git a/src/RepresentationTheory.jl b/src/RepresentationTheory.jl index 8872212..d9dff65 100644 --- a/src/RepresentationTheory.jl +++ b/src/RepresentationTheory.jl @@ -271,3 +271,677 @@ 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) + # 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) + 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 + +######################################################################################## +# 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 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) + 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 findall(>(0), A[x, :]) + 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 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 findall(v -> d[v] >= 2 && A[v, v] == 0, 1:n) + outs, ins = findall(>(0), A[v, :]), findall(>(0), A[:, v]) + 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) + 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 + +# 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]. +function __prime_components(A::Matrix{Int}, d::Vector{Int}) + n = length(d) + 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) + 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 + keep = sort!(vcat(others[piece], v)) + B = A[keep, keep] + w = findfirst(==(v), keep) + B[w, w] = 0 + append!(out, __prime_components(B, d[keep])) + end + return out + end + return [(A, d)] +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) + # 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] + 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 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 + 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 + + # (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 + shared = [y] + while shared[end] != x + length(shared) > n && return false + push!(shared, findfirst(>(0), A[shared[end], :])) + end + function branch(cur::Int) + b = Int[] + 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, 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[vcat(shared, rest)]) + return count(==(m), d[shared]) == 1 || + (count(==(m), d[shared]) == 0 && count(==(m), d[rest]) == 1) + end + end + + # (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 + +""" + 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 + +######################################################################################## +# Nullcones +######################################################################################## + +# 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 +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 + +- `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`` or ``4 \\times 4`` 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 + +julia> dimension_nullcone(jordan_quiver(2), [4]) +18 +``` + +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")) + + 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 + +""" + 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 __weakly_connected_components(A); + init=0, + ) +end diff --git a/test/runtests.jl b/test/runtests.jl index 11aa70a..1b86332 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -140,3 +140,183 @@ 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]) + + # 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])) + + # 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])) + + # 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])) + + # 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; + +@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; + +@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 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 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 + @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; + +@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]) + + # 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;