Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/Constructors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
93 changes: 93 additions & 0 deletions src/Moduli.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
6 changes: 4 additions & 2 deletions src/QuiverTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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
Expand Down
93 changes: 93 additions & 0 deletions src/Types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
58 changes: 58 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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;