Skip to content
Merged
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
89 changes: 74 additions & 15 deletions src/WeightLattice.jl
Original file line number Diff line number Diff line change
Expand Up @@ -351,17 +351,38 @@ end

# ─── Conjugation to dominant chamber ────────────────────────────────────────

# Core dominant-chamber fold: fold `v` into the dominant chamber in place and
# return the number of simple reflections applied. `@inline` so each caller
# Reject reflection nodes outside the diagram, so that a mistyped node is an
# error rather than a silently skipped reflection. `nothing` means every node.
# Called from the fold kernels themselves rather than from their callers, so no
# future entry point can reach a fold without passing through it.
function _check_nodes(nodes, R::Integer)
nodes === nothing && return nothing
for s in nodes
1 <= s <= R ||
throw(ArgumentError("Reflection node $s is out of range for rank $R"))
end
return nodes
end

# Core dominant-chamber fold: fold `v` into the dominant chamber of the root
# subsystem spanned by the simple roots `nodes`, in place, and return the number
# of simple reflections applied. `nodes === nothing` means every node, and the
# membership test then const-folds away, so the full fold stays exactly as fast
# as it was before the subsystem case existed. `@inline` so each caller
# const-folds its compile-time-constant (sparse) Cartan matrix into the loop —
# this is a hot path and the method is small enough to re-specialize cheaply.
#
# The scan runs over `1:R` and *tests* membership rather than iterating over
# `nodes`: that keeps every index into `v` and `C` provably in range under
# `@inbounds`, and accepts any container supporting `in`.
@inline function _fold_dominant!(
v::MVector{R,Int}, C::SMatrix{R,R,Int}
v::MVector{R,Int}, C::SMatrix{R,R,Int}, nodes=nothing
) where {R}
_check_nodes(nodes, R)
len = 0
s = 1
@inbounds while s <= R
if v[s] < 0
if v[s] < 0 && (nodes === nothing || s in nodes)
pairing = v[s]
for j in 1:R
v[j] -= pairing * C[j, s]
Expand All @@ -376,10 +397,19 @@ end
end

"""
conjugate_dominant_weight(w::WeightLatticeElem{DT,R}) -> WeightLatticeElem{DT,R}
conjugate_dominant_weight(w::WeightLatticeElem{DT,R}, nodes=nothing) -> WeightLatticeElem{DT,R}

Return the unique dominant weight in the Weyl orbit of `w`.

Passing `nodes` reflects only in those simple roots, giving the unique
``\\mathrm{W}_S``-orbit representative that is dominant for the root subsystem
spanned by ``S`` = `nodes`, i.e. dominant for the corresponding Levi subgroup.
The coordinates outside `nodes` are still moved along.

`nodes` may be any iterable of node indices — a tuple, vector, range or `Set` —
and `nothing` means every simple root. A node outside `1:R` throws an
`ArgumentError`.

# Examples
```jldoctest
julia> using Semisimple
Expand All @@ -390,21 +420,34 @@ julia> conjugate_dominant_weight(WeightLatticeElem(TypeA{2}, [-1, 1]))
julia> conjugate_dominant_weight(fundamental_weight(TypeA{3}, 1))
ω1
```

Reflecting only in the second node leaves the first coordinate negative, since
``s_2`` is the only reflection available:

```jldoctest
julia> using Semisimple

julia> conjugate_dominant_weight(WeightLatticeElem(TypeA{2}, [-1, -1]), (2,))
-2ω1 + ω2
```
"""
@inline function conjugate_dominant_weight(w::WeightLatticeElem{DT,R}) where {DT,R}
@inline function conjugate_dominant_weight(
w::WeightLatticeElem{DT,R}, nodes=nothing
) where {DT,R}
v = MVector{R,Int}(w.vec)
_fold_dominant!(v, cartan_matrix(DT))
_fold_dominant!(v, cartan_matrix(DT), nodes)
return WeightLatticeElem{DT,R}(SVector{R,Int}(v))
end

# Like `_fold_dominant!` but records the word of simple reflections applied.
function _fold_dominant_with_word!(
v::MVector{R,Int}, C::SMatrix{R,R,Int}
v::MVector{R,Int}, C::SMatrix{R,R,Int}, nodes=nothing
) where {R}
_check_nodes(nodes, R)
word = Int[]
s = 1
while s <= R
if v[s] < 0
if v[s] < 0 && (nodes === nothing || s in nodes)
pairing = v[s]
for j in 1:R
v[j] -= pairing * C[j, s]
Expand All @@ -419,26 +462,35 @@ function _fold_dominant_with_word!(
end

"""
conjugate_dominant_weight_with_elem(w::WeightLatticeElem{DT,R}) -> (WeightLatticeElem, Vector{Int})
conjugate_dominant_weight_with_elem(w::WeightLatticeElem{DT,R}, nodes=nothing) -> (WeightLatticeElem, Vector{Int})

Return the dominant weight and the sequence of simple reflections applied.

Passing `nodes` reflects only in those simple roots, as in
[`conjugate_dominant_weight`](@ref); the word is then a reduced word of an
element of ``\\mathrm{W}_S``.

# Examples
```jldoctest
julia> using Semisimple

julia> conjugate_dominant_weight_with_elem(WeightLatticeElem(TypeA{2}, [-1, 1]))
(ω1, [1])

julia> conjugate_dominant_weight_with_elem(WeightLatticeElem(TypeA{2}, [-1, 1]), (2,))
(-ω1 + ω2, Int64[])
```
"""
function conjugate_dominant_weight_with_elem(w::WeightLatticeElem{DT,R}) where {DT,R}
function conjugate_dominant_weight_with_elem(
w::WeightLatticeElem{DT,R}, nodes=nothing
) where {DT,R}
v = MVector{R,Int}(w.vec)
word = _fold_dominant_with_word!(v, cartan_matrix(DT))
word = _fold_dominant_with_word!(v, cartan_matrix(DT), nodes)
return WeightLatticeElem{DT,R}(SVector{R,Int}(v)), word
end

"""
conjugate_dominant_weight_with_length(w::WeightLatticeElem{DT,R}) -> (WeightLatticeElem, Int)
conjugate_dominant_weight_with_length(w::WeightLatticeElem{DT,R}, nodes=nothing) -> (WeightLatticeElem, Int)

Return the dominant weight in the Weyl orbit of `w` together with the number
of simple reflections applied (i.e. the length of the Weyl group element
Expand All @@ -447,6 +499,10 @@ mapping `w` into the dominant chamber).
This is faster than [`conjugate_dominant_weight_with_elem`](@ref) because it
only tracks a counter instead of building the full word.

Passing `nodes` reflects only in those simple roots, as in
[`conjugate_dominant_weight`](@ref); the length is then that of an element of
``\\mathrm{W}_S``.

# Examples
```jldoctest
julia> using Semisimple
Expand All @@ -456,13 +512,16 @@ julia> conjugate_dominant_weight_with_length(WeightLatticeElem(TypeA{2}, [-1, 1]

julia> conjugate_dominant_weight_with_length(fundamental_weight(TypeA{3}, 1))
(ω1, 0)

julia> conjugate_dominant_weight_with_length(WeightLatticeElem(TypeA{3}, [-1, 2, -1]), (2, 3))
(-ω1 + ω2 + ω3, 1)
```
"""
@inline function conjugate_dominant_weight_with_length(
w::WeightLatticeElem{DT,R}
w::WeightLatticeElem{DT,R}, nodes=nothing
) where {DT,R}
v = MVector{R,Int}(w.vec)
len = _fold_dominant!(v, cartan_matrix(DT))
len = _fold_dominant!(v, cartan_matrix(DT), nodes)
return WeightLatticeElem{DT,R}(SVector{R,Int}(v)), len
end

Expand Down
60 changes: 51 additions & 9 deletions src/WeylGroup.jl
Original file line number Diff line number Diff line change
Expand Up @@ -930,7 +930,7 @@ weyl_dimension(dt::DynkinType, v) = degree(typeof(dt), v)
# ─── Singularity ─────────────────────────────────────────────────────────────

"""
is_singular(w::WeightLatticeElem{DT,R}) -> Bool
is_singular(w::WeightLatticeElem{DT,R}, nodes=nothing) -> Bool

Check whether the weight `w` is singular, i.e. lies on some wall of a Weyl
chamber. Equivalently, `w` is singular iff `⟨α∨, w⟩ = 0` for some positive
Expand All @@ -940,6 +940,12 @@ For a dominant weight this simplifies to checking whether any fundamental
weight coordinate is zero. For a general weight, we first conjugate to the
dominant chamber.

Passing `nodes` restricts the question to the root subsystem spanned by
``S`` = `nodes`, as in [`conjugate_dominant_weight`](@ref): the result is then
whether ``⟨α^\\vee, w⟩ = 0`` for some positive root ``α`` of that subsystem.
This is the vanishing criterion of the relative
[`borel_weil_bott`](@ref).

# Examples
```jldoctest
julia> using Semisimple
Expand All @@ -950,16 +956,29 @@ true
julia> is_singular(weyl_vector(TypeA{2}))
false
```

For the subsystem spanned by the second node the only positive root is
``α_2``, so singularity is decided by the pairing with ``α_2^\\vee`` alone:

```jldoctest
julia> using Semisimple

julia> is_singular(fundamental_weight(TypeA{2}, 1), (2,))
true

julia> is_singular(fundamental_weight(TypeA{2}, 2), (2,))
false
```
"""
function is_singular(w::WeightLatticeElem{DT,R}) where {DT,R}
dom = conjugate_dominant_weight(w)
return any(i -> dom.vec[i] == 0, 1:R)
function is_singular(w::WeightLatticeElem{DT,R}, nodes=nothing) where {DT,R}
dom = conjugate_dominant_weight(w, nodes)
return any(s -> dom.vec[s] == 0, something(nodes, 1:R))
end

# ─── Borel–Weil–Bott ────────────────────────────────────────────────────────

"""
borel_weil_bott(λ::WeightLatticeElem{DT,R}) -> Union{Nothing, Tuple{Int, WeightLatticeElem{DT,R}}}
borel_weil_bott(λ::WeightLatticeElem{DT,R}, nodes=nothing) -> Union{Nothing, Tuple{Int, WeightLatticeElem{DT,R}}}

Apply the Borel–Weil–Bott theorem to the weight `λ`.

Expand All @@ -978,6 +997,16 @@ all cohomology vanishes and we return `nothing`. Otherwise, return

and all other cohomology groups vanish.

Passing `nodes` restricts both the reflections and the singularity test to the
root subsystem spanned by ``S`` = `nodes`, so `w` is sought in
``\\mathrm{W}_S``. This is the relative statement along a projection of flag
varieties, the higher direct images of ``\\mathcal{L}_λ`` along
``\\mathrm{G}/\\mathrm{B} \\to \\mathrm{G}/\\mathrm{P}_I`` with `nodes` the nodes
unmarked in ``I``: taking `nodes` to be all of them recovers the absolute
statement. Note that ``ρ = ρ_{\\mathrm{G}}`` remains the right shift, because
``ρ_{\\mathrm{G}} - ρ_S`` pairs to zero with every coroot in ``S`` and is
therefore ``\\mathrm{W}_S``-invariant.

# Examples
```jldoctest
julia> using Semisimple; import Semisimple: borel_weil_bott
Expand All @@ -991,17 +1020,30 @@ julia> borel_weil_bott(WeightLatticeElem(TypeA{2}, [-2, 1]))
julia> borel_weil_bott(-weyl_vector(TypeA{2})) === nothing
true
```

The same weight, but reflecting only in the second node: it is already dominant
there, so it stays put in degree zero.

```jldoctest
julia> using Semisimple; import Semisimple: borel_weil_bott

julia> borel_weil_bott(WeightLatticeElem(TypeA{2}, [-2, 1]), (2,))
(0, -2ω1 + ω2)

julia> borel_weil_bott(-weyl_vector(TypeA{2}), (2,)) === nothing # singular for s2 too
true
```
"""
function borel_weil_bott(λ::WeightLatticeElem{DT,R}) where {DT,R}
function borel_weil_bott(λ::WeightLatticeElem{DT,R}, nodes=nothing) where {DT,R}
ρ = weyl_vector(DT)
μ = λ + ρ

# Move μ to the dominant chamber; the number of reflections is the degree
μ_dom, d = conjugate_dominant_weight_with_length(μ)
μ_dom, d = conjugate_dominant_weight_with_length(μ, nodes)

# If any coordinate of μ_dom is zero, λ + ρ lies on a Weyl chamber wall
# If any coordinate of μ_dom is zero, λ + ρ lies on a wall of the chamber
# (including the case μ = 0 when λ = -ρ), so all cohomology vanishes.
any(==(0), μ_dom.vec) && return nothing
any(s -> μ_dom.vec[s] == 0, something(nodes, 1:R)) && return nothing

return (d, μ_dom - ρ)
end
Loading
Loading