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
8 changes: 5 additions & 3 deletions docs/src/methods/walls-and-chambers.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Walls and Chambers decomposition

QuiverTools implements the walls-and-chambers decomposition
of the GIT problem of quiver moduli using the polyhedral geometry
interface of `Oscar.jl`.
of the GIT problem of quiver moduli.
The boolean equivalence test [`git_equivalent`](@ref) is implemented combinatorially
and has no optional dependencies.
Constructing the polyhedral cones and fans uses the geometry interface of `Oscar.jl`.

This functionality lives in a *package extension*
The polyhedral functionality lives in a *package extension*
that depends on the `Oscar` algebra system.

To use it, one must
Expand Down
3 changes: 2 additions & 1 deletion docs/src/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,8 @@ The provided methods are described in [Walls and Chambers decomposition](@ref).

### Technical note

The walls and chambers functionality is contained in a *package extension*
The boolean function [`git_equivalent`](@ref) does not require an optional dependency.
Constructing the walls, chambers, and VGIT fan uses a *package extension*
that depends on the `Oscar` algebra system.

To use it, one must
Expand Down
38 changes: 12 additions & 26 deletions ext/QuiverToolsOscarExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ using IterTools: IterTools
import Memoization: @memoize
import QuiverTools:
is_special_subdimension_vector, all_special_subdimension_vectors, sst,
vgit_walls, wall_system, vgit_chambers, vgit_fan, git_equivalent, all_stability_parameters
vgit_walls, wall_system, vgit_chambers, vgit_fan, all_stability_parameters

# Disambiguate between Singular's and Oscar's overloads of `(::PolyRing)(::spoly)`,
# which collide once both packages are loaded.
Expand Down Expand Up @@ -86,13 +86,14 @@ function __helper_accelerate(P::Oscar.Polyhedron)
end

@memoize Dict function sst(Q, e)
e_perp = Oscar.polyhedron([e, -e], [0, 0]) #e^{\perp}
all_gen = filter(
eprime -> !all(ei == 0 for ei in eprime) && eprime != e,
all_general_subdimension_vectors(Q, e),
data = QuiverTools.__semistable_cone_data(Q, e)
equations = vcat(data.equations, .-data.equations)
e_perp = Oscar.polyhedron(equations, zeros(Int, length(equations)))
isempty(data.inequalities) && return e_perp
return intersect(
e_perp,
Oscar.polyhedron(data.inequalities, zeros(Int, length(data.inequalities))),
)
isempty(all_gen) && return e_perp
return intersect(e_perp, Oscar.polyhedron(all_gen, zeros(Int, length(all_gen))))
end

@memoize Dict function vgit_walls(Q, d; inner=false, top_dimension=true)
Expand Down Expand Up @@ -280,19 +281,6 @@ function vgit_fan(Q, d; verbose=false)
)
end

function git_equivalent(Q, d, theta1, theta2)
theta1 == theta2 && return true
line = Oscar.convex_hull([theta1, theta2]) # 1-dimensional iif theta1 != theta2

# either the line lies in a wall or it intersects none of them
for w in vgit_walls(Q, d; top_dimension=false)
if !Oscar.issubset(line, w) && Oscar.is_feasible(Oscar.intersect(line, w))
return false
end
end
return true
end

"""
__sst_cone(Q::Quiver, e::AbstractVector{Int})

Expand All @@ -302,12 +290,10 @@ but seen as an Oscar `Cone` object.
For internal use only for now.
"""
function __sst_cone(Q::Quiver, e::AbstractVector{Int})
all_gen = filter(
eprime -> !all(ei == 0 for ei in eprime) && eprime != e,
all_general_subdimension_vectors(Q, e),
)
isempty(all_gen) && return Oscar.cone_from_inequalities([e, -e])
return Oscar.cone_from_inequalities(all_gen, [e])
data = QuiverTools.__semistable_cone_data(Q, e)
isempty(data.inequalities) &&
return Oscar.cone_from_inequalities(vcat(data.equations, .-data.equations))
return Oscar.cone_from_inequalities(data.inequalities, data.equations)
end

"""
Expand Down
2 changes: 1 addition & 1 deletion src/QuiverTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,12 @@ include("Misc.jl")
include("Stability.jl")
include("RepresentationTheory.jl")
include("Constructors.jl")
include("WallsAndChambers.jl")
include("Moduli.jl")
include("Hodge.jl")
include("Chow.jl")
include("Teleman.jl")
include("Bundles.jl")
include("WallsAndChambers.jl")

# Warm the JIT for the shared Chow/Hodge computation path so the user's first
# invariant computation is near-instant. Compilation is input-independent, so a
Expand Down
168 changes: 160 additions & 8 deletions src/WallsAndChambers.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,145 @@
# Walls-and-chambers and VGIT functionality relies on Oscar's polyhedral geometry
# and therefore lives in the Oscar package extension (ext/QuiverToolsOscarExt.jl).
# The functions below are only method stubs: the extension adds the real methods
# once Oscar is loaded. Until then, calling any of them raises a clear error.
# The combinatorial VGIT layer is independent of Oscar. It records the equations and
# inequalities defining semistable cones and walls, and provides exact point and line
# segment predicates. The Oscar extension consumes the same data to construct the
# corresponding polyhedra.

# Equations and inequalities defining sst(d). The inequalities are Schofield's general
# subdimension vectors; zero and d impose no inequalities and are omitted.
function __semistable_cone_data(Q::Quiver, d::AbstractVector{Int})
return __semistable_cone_data(Q, Vector{Int}(d))
end

@memoize Dict function __semistable_cone_data(Q::Quiver, d::Vector{Int})
inequalities = filter(
e -> any(!=(0), e) && e != d,
all_general_subdimension_vectors(Q, d),
)
return (equations=[d], inequalities=inequalities)
end

# Equations and inequalities defining W_e = sst(e) ∩ sst(d-e) ∩ sst(d).
function __vgit_wall_data(
Q::Quiver,
d::AbstractVector{Int},
e::AbstractVector{Int},
)
return __vgit_wall_data(Q, Vector{Int}(d), Vector{Int}(e))
end

@memoize Dict function __vgit_wall_data(Q::Quiver, d::Vector{Int}, e::Vector{Int})
data = __semistable_cone_data.(Ref(Q), (e, d - e, d))
equations = unique!(vcat((datum.equations for datum in data)...))
inequalities = unique!(vcat((datum.inequalities for datum in data)...))
return (equations=equations, inequalities=inequalities)
end

# Evaluate a scalar product without losing exactness when the parameter is rational.
function __exact_dot(x::AbstractVector{Int}, y::AbstractVector)
length(x) == length(y) || throw(DimensionMismatch("vectors must have equal lengths"))
value = big(0) // big(1)
for i in eachindex(x, y)
value += big(x[i]) * y[i]
end
return value
end

function __in_rational_cone(data, theta::AbstractVector)
return all(normal -> iszero(__exact_dot(normal, theta)), data.equations) &&
all(normal -> __exact_dot(normal, theta) <= 0, data.inequalities)
end

"""Return whether `theta` belongs to the semistable cone `sst(d)`."""
function __in_semistable_cone(
Q::Quiver,
d::AbstractVector{Int},
theta::AbstractVector,
)
return __in_rational_cone(__semistable_cone_data(Q, d), theta)
end

"""Return whether `theta` belongs to the VGIT wall `W_e`."""
function __in_vgit_wall(
Q::Quiver,
d::AbstractVector{Int},
e::AbstractVector{Int},
theta::AbstractVector,
)
return __in_rational_cone(__vgit_wall_data(Q, d, e), theta)
end

# Intersect the segment theta1--theta2 with a rational polyhedral cone. The result is
# the exact closed interval of parameters t in [0,1] for which
# (1-t)theta1 + t theta2 belongs to the cone, or nothing when it is empty.
function __segment_cone_intersection(data, theta1::AbstractVector, theta2::AbstractVector)
length(theta1) == length(theta2) ||
throw(DimensionMismatch("stability parameters must have equal lengths"))
lower = big(0) // big(1)
upper = big(1) // big(1)

for normal in data.equations
initial = __exact_dot(normal, theta1)
delta = __exact_dot(normal, theta2) - initial
if iszero(delta)
iszero(initial) || return nothing
else
crossing = -initial / delta
lower = max(lower, crossing)
upper = min(upper, crossing)
lower <= upper || return nothing
end
end

for normal in data.inequalities
initial = __exact_dot(normal, theta1)
delta = __exact_dot(normal, theta2) - initial
if iszero(delta)
initial <= 0 || return nothing
elseif delta > 0
upper = min(upper, -initial / delta)
else
lower = max(lower, -initial / delta)
end
lower <= upper || return nothing
end

return (lower, upper)
end

"""Return whether `theta` lies in a VGIT chamber rather than on a wall."""
function __is_vgit_chamber_parameter(
Q::Quiver,
d::AbstractVector{Int},
theta::AbstractVector,
)
__in_semistable_cone(Q, d, theta) || return false
return all(all_subdimension_vectors(d; nonzero=true, strict=true)) do e
!__in_vgit_wall(Q, d, e, theta)
end
end

# The target may meet a wall at the endpoint of the segment, but the segment must not
# meet any wall earlier. Using complete cone data also handles smaller walls when the
# whole segment lies in their defining hyperplane.
function __in_closure_of_vgit_chamber(
Q::Quiver,
d::AbstractVector{Int},
theta::AbstractVector,
thetabar::AbstractVector,
)
__is_vgit_chamber_parameter(Q, d, theta) || return false
__in_semistable_cone(Q, d, thetabar) || return false
endpoint = big(1) // big(1)
for e in all_subdimension_vectors(d; nonzero=true, strict=true)
intersection = __segment_cone_intersection(__vgit_wall_data(Q, d, e), theta, thetabar)
intersection === nothing && continue
intersection == (endpoint, endpoint) || return false
end
return true
end

# Polyhedral wall and chamber objects rely on Oscar and therefore live in the Oscar
# package extension (ext/QuiverToolsOscarExt.jl). The declarations below are method
# stubs which load that extension on demand.
#
# To enable them, load Oscar alongside QuiverTools. Use `import Oscar` rather than
# `using Oscar`: it activates the extension without bringing Oscar's exports into
Expand Down Expand Up @@ -201,7 +339,8 @@ By [[Corollary 4.4, MR5007902](https://mathscinet.ams.org/mathscinet-getitem?mr=
this is equivalent to their convex hull
either lying in a wall or not intersecting any of them.

Requires Oscar: run `import Oscar` to enable this function.
This computation uses the combinatorial equations and inequalities defining the VGIT
walls and does not require Oscar.

# Example

Expand Down Expand Up @@ -251,7 +390,21 @@ julia> any(y in w for w in W)
true
```
"""
function git_equivalent end
function git_equivalent(
Q::Quiver,
d::AbstractVector{Int},
theta1::AbstractVector,
theta2::AbstractVector,
)
theta1 == theta2 && return true
for e in all_subdimension_vectors(d; nonzero=true, strict=true)
data = __vgit_wall_data(Q, d, e)
__segment_cone_intersection(data, theta1, theta2) === nothing && continue
__in_rational_cone(data, theta1) && __in_rational_cone(data, theta2) && continue
return false
end
return true
end

"""
all_stability_parameters(Q::Quiver, d::AbstractVector{Int}; generic::Bool=false)
Expand Down Expand Up @@ -356,8 +509,7 @@ end
# take precedence once Oscar has been loaded.
for f in (
:is_special_subdimension_vector, :all_special_subdimension_vectors, :sst,
:vgit_walls, :wall_system, :vgit_chambers, :vgit_fan, :git_equivalent,
:all_stability_parameters,
:vgit_walls, :wall_system, :vgit_chambers, :vgit_fan, :all_stability_parameters,
)
@eval @oscar_stub $f
end
62 changes: 62 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,74 @@ using Test, QuiverTools, Documenter

@info "Almost all the tests are in the documentation."

@testset "Oscar-independent VGIT predicates" begin
# VGIT boolean queries must not load the optional Oscar extension.
@test Base.get_extension(QuiverTools, :QuiverToolsOscarExt) === nothing

Q = three_vertex_quiver(2, 3, 4)
d = [1, 2, 2]
theta1 = [2, -1 // 2, -1 // 2]
theta2 = [2, 1 // 2, -3 // 2]
@test !git_equivalent(Q, d, theta1, theta2)
@test git_equivalent(Q, d, theta1, 2 .* theta1)

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 QuiverTools.__is_vgit_chamber_parameter(S, d, theta)
@test QuiverTools.__in_closure_of_vgit_chamber(S, d, theta, thetabar)
@test !QuiverTools.__is_vgit_chamber_parameter(S, d, thetabar)

@test Base.get_extension(QuiverTools, :QuiverToolsOscarExt) === nothing
end;

# `import Oscar` (not `using`) loads Oscar so the walls-and-chambers / VGIT extension
# activates and its doctests resolve `Oscar.*`, without pulling Oscar's exports into
# scope (which would clash with QuiverTools names such as `index`, `todd_class`, ...).
DocMeta.setdocmeta!(QuiverTools, :DocTestSetup, :(using QuiverTools; import Oscar))
doctest(QuiverTools; manual=false, testset="Doctests")

import Oscar

@testset "VGIT predicates agree with Oscar" begin
Q = three_vertex_quiver(2, 3, 4)
d = [1, 2, 2]
parameters = [
[2, -1 // 2, -1 // 2],
[2, 1 // 2, -3 // 2],
[1, 3 // 2, -2],
[0, 1, -1],
[1, 0, -1 // 2],
[0, 0, 0],
]

cone = sst(Q, d)
for theta in parameters
@test QuiverTools.__in_semistable_cone(Q, d, theta) == (theta in cone)
end

for e in QuiverTools.all_subdimension_vectors(d; nonzero=true, strict=true)
wall = reduce(Oscar.intersect, (sst(Q, e), sst(Q, d - e), cone))
for theta in parameters
@test QuiverTools.__in_vgit_wall(Q, d, e, theta) == (theta in wall)
end
end

walls = vgit_walls(Q, d; top_dimension=false)
function oscar_git_equivalent(theta1, theta2)
theta1 == theta2 && return true
line = Oscar.convex_hull([theta1, theta2])
return all(walls) do wall
Oscar.issubset(line, wall) || !Oscar.is_feasible(Oscar.intersect(line, wall))
end
end
for theta1 in parameters, theta2 in parameters
@test git_equivalent(Q, d, theta1, theta2) ==
oscar_git_equivalent(theta1, theta2)
end
end;

@testset "strict sst" begin
# proper-semistability
Q = kronecker_quiver(2)
Expand Down