diff --git a/src/Constructors.jl b/src/Constructors.jl index 5cb0842..75475f0 100644 --- a/src/Constructors.jl +++ b/src/Constructors.jl @@ -203,6 +203,76 @@ function thickened_subspace_quiver(m::Int, k::Int) return Quiver(A, "thickened subspace quiver with $m sources and multiplicity $k") end +""" + framed_quiver(Q::Quiver, n::AbstractVector{Int}) + +Construct the framed quiver ``\\widehat{Q}`` of `Q` with framing datum `n`. + +A new framing vertex ``i_0`` is prepended as the **first** vertex, together with `n[i]` +arrows ``i_0 \\to i`` for every vertex `i` of `Q`. See the framing construction in +[arXiv:2607.12895](https://arxiv.org/abs/2607.12895). + +# Input + +- `Q`: a quiver. +- `n`: a vector of length `n_vertices(Q)`; `n[i]` is the number of framing arrows to vertex `i`. + +# Output + +The framed quiver, with the framing vertex as vertex `1`. + +# Examples + +```jldoctest +julia> framed_quiver(kronecker_quiver(2), [0, 1]) +framing of 2-Kronecker quiver +``` +""" +function framed_quiver(Q::Quiver, n::AbstractVector{Int}) + length(n) == n_vertices(Q) || + throw(ArgumentError("length of n must equal the number of vertices")) + N = n_vertices(Q) + A = zeros(Int, N + 1, N + 1) + A[2:end, 2:end] .= Q.adjacency + A[1, 2:end] .= n + return Quiver(A, "framing of " * Q.name) +end + +""" + coframed_quiver(Q::Quiver, n::AbstractVector{Int}) + +Construct the coframed quiver of `Q` with coframing datum `n`. + +A new coframing vertex ``i_0`` is appended as the **last** vertex, together with `n[i]` +arrows ``i \\to i_0`` for every vertex `i` of `Q`. This is the linear dual of +[`framed_quiver`](@ref); see [arXiv:2607.12895](https://arxiv.org/abs/2607.12895). + +# Input + +- `Q`: a quiver. +- `n`: a vector of length `n_vertices(Q)`; `n[i]` is the number of coframing arrows from vertex `i`. + +# Output + +The coframed quiver, with the coframing vertex as the last vertex. + +# Examples + +```jldoctest +julia> coframed_quiver(kronecker_quiver(2), [0, 1]) +coframing of 2-Kronecker quiver +``` +""" +function coframed_quiver(Q::Quiver, n::AbstractVector{Int}) + length(n) == n_vertices(Q) || + throw(ArgumentError("length of n must equal the number of vertices")) + N = n_vertices(Q) + A = zeros(Int, N + 1, N + 1) + A[1:N, 1:N] .= Q.adjacency + A[1:N, N + 1] .= n + return Quiver(A, "coframing of " * Q.name) +end + # Split a Dynkin label like "A3" or "D10" into its letter type and integer rank. function _parse_dynkin_label(Tn::String) m = match(r"^([A-Za-z]+)([0-9]+)$", Tn) diff --git a/src/Moduli.jl b/src/Moduli.jl index 5a40a2c..d0240c3 100644 --- a/src/Moduli.jl +++ b/src/Moduli.jl @@ -577,6 +577,99 @@ function __is_smooth_stratum(M::QuiverModuli, tau) return is_coregular(setting.Q, setting.d) end +# Induced stability parameter on the framed quiver: C * theta - kappa + kappa(d) * i_0^*, +# with kappa = (1, ..., 1) and C = kappa(d) + 1 (arXiv:2607.12895). The framing vertex carries +# kappa(d) = sum(d); base vertex i carries C * theta_i - 1. This lies in the framing chamber +# for every base theta, and for theta = 0 (fibres) reduces to kappa(d) * i_0^* - kappa, e.g. +# [2, -1, -1] for the local quiver of the first fibre type in arXiv:2607.12895. +function _framed_stability( + theta::AbstractVector{Int}, d::AbstractVector{Int}, coframed::Bool +) + kappa_d = sum(d) + C = kappa_d + 1 + base_part = C .* theta .- 1 + return coframed ? [base_part; kappa_d] : [kappa_d; base_part] +end + +""" + base(X::FramedQuiverModuliSpace) + +The base moduli space ``M^{\\Theta}(Q, \\mathbf{d})``, i.e. the codomain of the projection +`p` from the framed quiver moduli space `X`. +""" +base(X::FramedQuiverModuliSpace) = + QuiverModuliSpace(X.Q, X.d, X.theta, "semistable", X.denom) + +""" + total_space(X::FramedQuiverModuliSpace) + +The framed quiver moduli space `X` as an ordinary [`QuiverModuliSpace`](@ref) of the framed +quiver ``\\widehat{Q}`` for dimension vector ``\\widehat{\\mathbf{d}}`` and induced stability +parameter ``\\widehat{\\Theta}``. +""" +function total_space(X::FramedQuiverModuliSpace) + Qhat = X.coframed ? coframed_quiver(X.Q, X.n) : framed_quiver(X.Q, X.n) + dhat = X.coframed ? [X.d; 1] : [1; X.d] + theta_hat = _framed_stability(X.theta, X.d, X.coframed) + return QuiverModuliSpace(Qhat, dhat, theta_hat, "semistable", X.denom) +end + +""" + framing_vector(X::FramedQuiverModuliSpace) + +The framing datum ``\\mathbf{n}`` of the framed quiver moduli space `X`. +""" +framing_vector(X::FramedQuiverModuliSpace) = X.n + +""" + ambient(N::NilpotentLocus) + +The ambient moduli space of the nilpotent locus `N` (see [`NilpotentLocus`](@ref)). +""" +ambient(N::NilpotentLocus) = N.ambient + +""" + local_structure(M::QuiverModuli, tau) + +The etale-local model of the singularity of the moduli space `M` along the Luna stratum +`S_tau`, as a [`QuiverModuliSpace`](@ref). + +By [MR1972892] (see also arXiv:2607.12895) the singularity of `M` at any point of `S_tau` is +etale equivalent to the singularity of the origin in ``M^{0}(Q_\\tau, \\mathbf{d}_\\tau)``, +the moduli space of the local quiver `Q_tau` with local dimension vector `d_tau` (see +[`local_quiver_setting`](@ref)) at the trivial stability parameter. +""" +function local_structure(M::QuiverModuli, tau) + lqs = local_quiver_setting(M, tau) + return QuiverModuliSpace(lqs.Q, lqs.d, zero_vector(lqs.Q)) +end + +""" + fibre(X::FramedQuiverModuliSpace, tau) + +The fibre of the projection `p` from the framed quiver moduli space `X` over the Luna stratum +`S_tau` of the base ``M^{\\Theta}(Q, \\mathbf{d})``, as a [`NilpotentLocus`](@ref) inside a +framed moduli space of the local quiver. + +By the description of the fibres (arXiv:2607.12895), the fibre over ``V \\in S_\\tau`` is the +nilpotent locus of ``M^{0}(Q_\\tau, \\mathbf{d}_\\tau, \\mathbf{n}_\\tau)``, where `Q_tau` +and `d_tau` are the local quiver and local dimension vector (see [`local_quiver_setting`](@ref)) +and the local framing datum is +``\\mathbf{n}_\\tau = \\sum_k (\\mathbf{n} \\cdot \\mathbf{d}_k)\\, i_k``. +""" +function fibre(X::FramedQuiverModuliSpace, tau) + setting = local_quiver_setting(base(X), tau) + # local framing datum n_tau = sum_k (n . d_k) i_k, one entry per stable summand d_k; + # `summands` is ordered compatibly with the local dimension vector `d`. + nloc = [X.n' * e for e in setting.summands] + return NilpotentLocus( + FramedQuiverModuliSpace( + setting.Q, setting.d; n=nloc, theta=zero_vector(setting.Q), + coframed=X.coframed, + ), + ) +end + """ semistable_equals_stable(M::QuiverModuli) diff --git a/src/QuiverTools.jl b/src/QuiverTools.jl index 21944b9..455101c 100644 --- a/src/QuiverTools.jl +++ b/src/QuiverTools.jl @@ -36,6 +36,8 @@ import Singular: polynomial_ring, degree, coeff, constant_coefficient, # Types export Quiver, HNType, LunaType, QuiverModuli, QuiverModuliSpace, QuiverModuliStack, Bundle +export FramedQuiverModuliSpace, NilpotentLocus +export base, total_space, framing_vector, fibre, ambient, local_structure # Quivers export n_vertices, @@ -47,7 +49,7 @@ export kronecker_quiver, loop_quiver, jordan_quiver, subspace_quiver, star_quive generalized_subspace_quiver, thickened_subspace_quiver, three_vertex_quiver, cyclic_quiver, bipartite_quiver, opposite_quiver, double_quiver, disjoint_union, dynkin_quiver, - extended_dynkin_quiver + extended_dynkin_quiver, framed_quiver, coframed_quiver export kronecker_moduli, subspace_quiver_moduli # Stability @@ -64,7 +66,7 @@ export euler_form, euler_matrix, is_root, is_schur_root, is_real_root, is_imagin bocklandt_reduction, is_coregular, is_cofree # Moduli -export all_luna_types, is_luna_type, dimension_of_luna_stratum +export all_luna_types, is_luna_type, dimension_of_luna_stratum, local_quiver_setting export is_nonempty, codimension_unstable_locus, codimension_singular_locus, dimension, is_smooth, is_projective, is_strongly_amply_stable, semistable_equals_stable, semisimple_moduli_space diff --git a/src/Types.jl b/src/Types.jl index bfb6fa9..923a2d3 100644 --- a/src/Types.jl +++ b/src/Types.jl @@ -354,6 +354,99 @@ end """ # Summary +`struct FramedQuiverModuliSpace` + +The framed quiver moduli space ``M^{\\Theta\\text{-fr}}(Q, \\mathbf{d}, \\mathbf{n})`` of a +quiver `Q`, base dimension vector `d`, base stability parameter `theta` and framing datum +`n`. It is realized as the quiver moduli space of the framed quiver ``\\widehat{Q}`` (see +[`framed_quiver`](@ref) / [`coframed_quiver`](@ref)) for dimension vector +``\\widehat{\\mathbf{d}}`` and an induced stability parameter ``\\widehat{\\Theta}``; use +[`total_space`](@ref) to obtain that ordinary [`QuiverModuliSpace`](@ref) and [`base`](@ref) +to obtain the codomain ``M^{\\Theta}(Q, \\mathbf{d})`` of the projection +``p\\colon M^{\\Theta\\text{-fr}}(Q, \\mathbf{d}, \\mathbf{n}) \\to M^{\\Theta}(Q, \\mathbf{d})``. + +The fibres of `p` over a Luna stratum are described by [`fibre`](@ref); see +[arXiv:2607.12895](https://arxiv.org/abs/2607.12895). + +# Fields + +`Q :: Quiver` base quiver.\\ +`d :: AbstractVector{Int}` base dimension vector.\\ +`theta :: AbstractVector{Int}` base stability parameter.\\ +`denom :: Function` slope denominator.\\ +`n :: AbstractVector{Int}` framing datum.\\ +`coframed :: Bool` `false`: arrows ``i_0 \\to i`` (framing vertex first); + `true`: arrows ``i \\to i_0`` (coframing vertex last). +""" +struct FramedQuiverModuliSpace + Q::Quiver + d::AbstractVector{Int} + theta::AbstractVector{Int} + denom::Function + n::AbstractVector{Int} + coframed::Bool +end +function FramedQuiverModuliSpace( + Q::Quiver, + d::AbstractVector{Int}; + n::AbstractVector{Int}, + theta::AbstractVector{Int}=canonical_stability(Q, d), + denom::Function=sum, + coframed::Bool=false, +) + length(d) == n_vertices(Q) || + throw(ArgumentError("length of d must equal the number of vertices")) + length(theta) == n_vertices(Q) || + throw(ArgumentError("length of theta must equal the number of vertices")) + length(n) == n_vertices(Q) || + throw(ArgumentError("length of n must equal the number of vertices")) + return FramedQuiverModuliSpace( + Q, coerce_vector(d), coerce_vector(theta), denom, coerce_vector(n), coframed + ) +end + +function show(io::IO, X::FramedQuiverModuliSpace) + print( + io, + "$(X.coframed ? "Coframed" : "Framed") quiver moduli space defined as follows: + - base quiver: $(X.Q) + - base dimension vector: $(X.d) + - base stability parameter: $(X.theta) + - framing datum: $(X.n) + ", + ) +end + +""" +# Summary + +`struct NilpotentLocus` + +A marker for the closed sublocus of nilpotent representations inside a quiver moduli space +`ambient` (here a [`FramedQuiverModuliSpace`](@ref)): the representations whose underlying +representation of the quiver (ignoring the framing) is nilpotent. + +The nilpotency is recorded, not computed: there is no general routine here for the +nullcone of a non-acyclic quiver, so `ambient(N)` is the ambient moduli space and the +nilpotent locus is identified by hand, as in +[arXiv:2607.12895](https://arxiv.org/abs/2607.12895). It arises as the fibre of the framed +projection, see [`fibre`](@ref). + +# Fields + +`ambient :: FramedQuiverModuliSpace` +""" +struct NilpotentLocus{M} + ambient::M +end + +function show(io::IO, N::NilpotentLocus) + print(io, "Nilpotent locus inside\n ", N.ambient) +end + +""" +# Summary + `struct HNType` A Harder-Narasimhan type for a quiver, a dimension vector `d` diff --git a/test/runtests.jl b/test/runtests.jl index fc79524..cc43059 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -320,3 +320,61 @@ end; @test is_cofree(wedged, [2, 3, 4, 1, 3]) @test !is_cofree(wedged, [2, 3, 4, 1, 1]) end; + +@testset "framed quiver moduli" begin + # Quiver-level framing (Sage parity): the framing vertex is prepended (arrows i0 -> i), + # the coframing vertex appended (arrows i -> i0). See arXiv:2607.12895. + kro = kronecker_quiver(2) + @test Matrix(framed_quiver(kro, [0, 1]).adjacency) == [0 0 1; 0 0 2; 0 0 0] + @test Matrix(coframed_quiver(kro, [0, 1]).adjacency) == [0 2 0; 0 0 1; 0 0 0] + @test_throws ArgumentError framed_quiver(kro, [1]) + + # A framed quiver moduli space: `base` is the codomain of the projection p, `total_space` + # the framed moduli space as an ordinary QuiverModuliSpace. + X = FramedQuiverModuliSpace(kro, [2, 2]; n=[0, 2]) + @test base(X).d == [2, 2] + @test framing_vector(X) == [0, 2] + @test n_vertices(total_space(X).Q) == 3 + + # local_quiver_setting is public API (issue #41): the local quiver and dimension vector of + # a Luna stratum. For the 3-Kronecker quiver and the type [1,1] with multiplicity 3, the + # local quiver is the two-loop quiver on one vertex. + M = QuiverModuliSpace(kronecker_quiver(3), [3, 3]) + @test local_quiver_setting(M, Dict([1, 1] => [3])).d == [3] + + # Proposition 5: the etale-local model of the singularity along a Luna stratum is the local + # quiver at the trivial stability parameter. + L = local_structure(M, Dict([1, 1] => [3])) + @test L isa QuiverModuliSpace + @test L.d == [3] + @test Matrix(L.Q.adjacency) == fill(2, 1, 1) # one vertex, two loops + @test L.theta == [0] + + # Proposition 6: fibres of the framed projection for the framed affine-D4 quiver, base + # dimension 2*(1,1,1,1;2). This is the four-dimensional QM_1 of arXiv:2607.12895, whose + # five fibre types are Lemmas 18-22. The fibre over each stratum is a NilpotentLocus in a + # framed moduli of the local quiver, with local framing datum n_tau = sum_k (n.d_k) i_k and + # framed stability [sum d_tau, -1, ..., -1] (framing vertex first). Local vertex order is + # not canonical, so the framing datum is compared sorted. Rows: (Lemma, tau, d_tau, n_tau). + delta = [1, 1, 1, 1, 2] + XD4 = FramedQuiverModuliSpace(subspace_quiver(4), 2 .* delta; n=[0, 0, 0, 0, 1]) + @test dimension(total_space(XD4)) == 4 + eK, eKb = [1, 1, 0, 0, 1], [0, 0, 1, 1, 1] + eL, eLb = [1, 0, 1, 0, 1], [0, 1, 0, 1, 1] + types = [ + (18, Dict(delta => [1, 1]), [1, 1], [2, 2]), + (19, Dict(delta => [1], eK => [1], eKb => [1]), [1, 1, 1], [1, 1, 2]), + (20, Dict(eK => [1], eKb => [1], eL => [1], eLb => [1]), [1, 1, 1, 1], [1, 1, 1, 1]), + (21, Dict(delta => [2]), [2], [2]), + (22, Dict(eK => [2], eKb => [2]), [2, 2], [1, 1]), + ] + for (lemma, tau, dloc, nloc) in types + F = fibre(XD4, tau) + @test F isa NilpotentLocus + A = ambient(F) + @test A isa FramedQuiverModuliSpace + @test sort(A.d) == dloc + @test sort(framing_vector(A)) == nloc + @test total_space(A).theta == [sum(dloc); fill(-1, length(dloc))] + end +end;