Skip to content

Commit

Permalink
Turn semistandard_tableaux(shape, weight) into an iterator (oscar-s…
Browse files Browse the repository at this point in the history
  • Loading branch information
joschmitt authored Aug 15, 2024
1 parent 2008cc6 commit 4026fae
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 81 deletions.
251 changes: 170 additions & 81 deletions src/Combinatorics/EnumerativeCombinatorics/tableaux.jl
Original file line number Diff line number Diff line change
Expand Up @@ -492,109 +492,198 @@ end
Return an iterator over all semistandard Young tableaux with shape `s` and given
weight. This requires that `sum(s) = sum(weight)`.
"""
function semistandard_tableaux(s::Vector{T}, weight::Vector{T}) where T <: Integer
# TODO: This function first fills an array and then returns an iterator over
# this array. This was a stopgap, to have a stable API for version 1.0.
# The output should be a dedicated iterator type which constructs the tableaux
# on demand (if possible).
n_max = sum(s)
@req n_max == sum(weight) "sum(s) == sum(weight) required"
function semistandard_tableaux(s::Partition{T}, weight::Vector{T}) where {T <: IntegerUnion}
return SemiStandardTableauxFixedShapeAndWeight(s, weight)
end

tabs = Vector{YoungTableau{T}}()
if isempty(s)
push!(tabs, young_tableau(Vector{T}[], check = false))
return (t for t in tabs)
function semistandard_tableaux(s::Vector{T}, weight::Vector{T}) where {T <: IntegerUnion}
return SemiStandardTableauxFixedShapeAndWeight(partition(s), weight)
end

shape(S::SemiStandardTableauxFixedShapeAndWeight) = S.shape
weight(S::SemiStandardTableauxFixedShapeAndWeight) = S.weight

Base.eltype(S::SemiStandardTableauxFixedShapeAndWeight{T}) where T = YoungTableau{T}

function Base.show(io::IO, S::SemiStandardTableauxFixedShapeAndWeight)
print(pretty(io), "Iterator over semistandard Young tableaux of shape ",
shape(S), " and weight ", weight(S))
end

Base.IteratorSize(::Type{<: SemiStandardTableauxFixedShapeAndWeight}) = Base.SizeUnknown()

@inline function iterate(S::SemiStandardTableauxFixedShapeAndWeight{T}, state::Union{SemiStandardTableauxFixedShapeAndWeightState, Nothing} = nothing) where T
s = shape(S)
weight = Oscar.weight(S)

if isnothing(state)
state = SemiStandardTableauxFixedShapeAndWeightState{T}()
if isempty(s)
state.n = 0
return young_tableau(Vector{T}[], check = false), state
end

state.n = 1
state.increaseN = true
state.tab = young_tableau([T[0 for j = 1:s[i]] for i = 1:length(s)], check = false)
state.boxes_filled = zeros(Int, length(s))
state.n_used_weight = zeros(Int, length(weight))
state.row_pointer = zeros(Int, length(weight), maximum(weight))
end
ls = length(s)

tab = young_tableau([ T[0 for j = 1:s[i]] for i = 1:length(s)], check = false)
sub_s = zeros(Int, length(s))
# the integer that we currently try to write into the tableau
n = state.n

#tracker_row = zeros(Integer, n_max)
is_zero(n) && return nothing

function rec_sst!(n::Int)
# whether we should increase or decrease n in the next iteration of the while loop
increaseN = state.increaseN
# the tableau filled in-place
tab = state.tab
# boxes_filled[i] is the number of boxes we successfully filled in row i
boxes_filled = state.boxes_filled

while n > 0
if is_zero(weight[n])
if increaseN
n += 1
else
n -= 1
end
continue
end

#fill the remaining boxes if possible, else set them to 0
# n is the last possible entry, so we try to fill the remaining boxes with n
if n == length(weight)
for i = 1:ls
for j = sub_s[i] + 1:s[i]
tab[i][j] = T(n)
if i != 1 && tab[i - 1][j] == n
for k = 1:i
for l = sub_s[k] + 1:s[k]
tab[i][j] = T(0)
end
end
return
end
end
goodTableau = _is_good_tableau!(n, S, state)
n -= 1
increaseN = false
if goodTableau
# we discovered a tableau
state.n = n
state.increaseN = increaseN
return young_tableau([copy(row) for row in tab], check = false), state
end
push!(tabs, young_tableau([copy(row) for row in tab], check = false))
# we did not discover a tableau and have to go back to the step n - 1
continue
end

return
# fill n into the boxes (modifies state in-place)
state.n = n
_fill_boxes!(n, S, state)
n = state.n
increaseN = state.increaseN
end

#skip to next step if weight[n] == 0
elseif weight[n] == 0
rec_sst!(n + 1)
return
end
return nothing
end

#here starts the main part of the function
tracker_row = zeros(Int, weight[n])
function _fill_boxes!(n::Int, S::SemiStandardTableauxFixedShapeAndWeight{T}, state::SemiStandardTableauxFixedShapeAndWeightState{T}) where T
s = shape(S)
weight = Oscar.weight(S)

# the tableau filled in-place
tab = state.tab
# boxes_filled[i] is the number of boxes we successfully filled in row i
boxes_filled = state.boxes_filled
# n_used_weight[i] is the number of entries equal to i
n_used_weight = state.n_used_weight
# row_pointer[k, l] is the row i where we put the lth entry k
row_pointer = state.row_pointer

m = n_used_weight[n]
@assert m == 0 || m == weight[n] "Internal error; this should not happen"
if m == 0
# we did not fill a box with the entry n yet, so we start looking for
# possible boxes in the "top left" corner
i = 1
while sub_s[i] == s[i]
while boxes_filled[i] == s[i]
i += 1
end
j = sub_s[i] + 1
j = boxes_filled[i] + 1
elseif m == weight[n]
# We already used the entry n as many times as possible, so we came back
# here from an earlier iteration of the loop
# We "forget" the last box filled with n and start filling one row below it
i = row_pointer[n, m] + 1
if i <= length(s)
j = boxes_filled[i] + 1
end
m -= 1
boxes_filled[i - 1] -= 1
end

while true
if m == weight[n]
# we filled n in as many boxes as possible, so we go to the step n + 1
n_used_weight[n] = m
state.n = n + 1
state.increaseN = true
return nothing
end

m = 0
while m >= 0
if m == weight[n] # jump to next recursive step
rec_sst!(n + 1)
tab[tracker_row[m]][sub_s[tracker_row[m]]] = T(0)
i = tracker_row[m] + 1
if i <= ls
j = sub_s[i] + 1
if i > length(s)
# We arrived at the bottom of the tableau
if m == 0
# We did not fill any box with n yet, so the filling so far is already
# illegal
# Go back to step n - 1
n_used_weight[n] = m
state.n = n - 1
state.increaseN = false
return nothing
else
# The last box we filled with n must have been wrong, so we put i in
# the next row after that and "forget" the box
i = row_pointer[n, m] + 1
if i <= length(s)
j = boxes_filled[i] + 1
end
m -= 1
sub_s[i - 1] -= 1

elseif i > ls
if m == 0
return
else
tab[tracker_row[m]][sub_s[tracker_row[m]]] = T(0)
i = tracker_row[m] + 1
if i <= ls
j = sub_s[i] + 1
end
m -= 1
sub_s[i - 1] -= 1
end

elseif j <= s[i] && (i == 1 || (j <= sub_s[i - 1] && n > tab[i - 1][j])) #add an entry
m += 1
tab[i][j] = T(n)
sub_s[i] += 1
tracker_row[m] = i
j += 1
boxes_filled[i - 1] -= 1
continue
end
end

else #move pointerhead
i += 1
if i <= ls
j = sub_s[i] + 1
end
if j <= s[i] && (i == 1 || (j <= boxes_filled[i - 1] && n > tab[i - 1][j]))
# We can fill a box with n
m += 1
tab[i][j] = T(n)
boxes_filled[i] += 1
row_pointer[n, m] = i
j += 1
else
# We cannot fill any box in row i with n, so we increase i
i += 1
if i <= length(s)
j = boxes_filled[i] + 1
end
end #while
end
end
return nothing
end

end #rec_sst!()
# Check whether we get a semistandard tableau by filling n in the remaining boxes
# of state.tab
function _is_good_tableau!(n::Int, S::SemiStandardTableauxFixedShapeAndWeight{T}, state::SemiStandardTableauxFixedShapeAndWeightState{T}) where T
s = shape(S)
weight = Oscar.weight(S)

rec_sst!(1)
return (t for t in tabs)
end
# the tableau filled in-place
tab = state.tab
# boxes_filled[i] is the number of boxes we successfully filled in row i
boxes_filled = state.boxes_filled

function semistandard_tableaux(s::Partition{T}, weight::Partition{T}) where T <: Integer
return semistandard_tableaux(Vector{T}(s), Vector{T}(weight))
for i = 1:length(s)
for j = boxes_filled[i] + 1:s[i]
tab[i][j] = T(n)
if i != 1 && tab[i - 1][j] >= n
# this filling does not give a semistandard tableau
return false
end
end
end
return true
end

################################################################################
Expand Down
29 changes: 29 additions & 0 deletions src/Combinatorics/EnumerativeCombinatorics/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,35 @@ struct SemiStandardTableauxFixedBoxNum{T<:IntegerUnion}
end
end

# Iterator type: all semistandard tableaux with a given shape and weight
struct SemiStandardTableauxFixedShapeAndWeight{T<:IntegerUnion}
shape::Partition{T}
weight::Vector{T}

function SemiStandardTableauxFixedShapeAndWeight(shape::Partition{T}, weight::Vector{T}) where {T <: IntegerUnion}
@req sum(shape) == sum(weight) "Sum of shape and weight must agree"
i = findlast(!iszero, weight) # Trim trailing zeros; they upset the iterator
if isnothing(i)
i = 0
end
return new{T}(shape, weight[1:i])
end
end

# Internal type: state of the iterator
mutable struct SemiStandardTableauxFixedShapeAndWeightState{T<:IntegerUnion}
n::Int
increaseN::Bool
tab::YoungTableau{T}
boxes_filled::Vector{Int}
n_used_weight::Vector{Int}
row_pointer::Matrix{Int}

function SemiStandardTableauxFixedShapeAndWeightState{T}() where {T <: IntegerUnion}
return new{T}()
end
end

# Iterator type: all standard tableaux of a given shape
struct StandardTableaux{T<:IntegerUnion}
shape::Partition{T}
Expand Down
1 change: 1 addition & 0 deletions test/Combinatorics/EnumerativeCombinatorics/tableaux.jl
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
end
end
@test collect(semistandard_tableaux(T[], T[])) == [young_tableau(Vector{T}[])]
@test length(collect(semistandard_tableaux(partition(T[5, 3, 1, 1]), T[4, 3, 2, 1]))) == 2

#semistandard_tableaux(box_num, max_val)
BoxNum = T(0):T(5)
Expand Down

0 comments on commit 4026fae

Please sign in to comment.