From 8d470c75a6f452587b011986f15a5f03eae1efd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20R=C3=B6hrich?= Date: Mon, 15 Apr 2024 11:58:33 +0200 Subject: [PATCH 1/6] poset draft --- experimental/Posets/src/Poset.jl | 195 +++++++++++++++++++++++++ experimental/Posets/src/Posets.jl | 24 +++ experimental/Posets/test/Poset-test.jl | 72 +++++++++ 3 files changed, 291 insertions(+) create mode 100644 experimental/Posets/src/Poset.jl create mode 100644 experimental/Posets/src/Posets.jl create mode 100644 experimental/Posets/test/Poset-test.jl diff --git a/experimental/Posets/src/Poset.jl b/experimental/Posets/src/Poset.jl new file mode 100644 index 000000000000..79e8f8e38d9e --- /dev/null +++ b/experimental/Posets/src/Poset.jl @@ -0,0 +1,195 @@ +# Poset + +struct Poset + cov::Matrix{Int} # covering relations of the poset + rel::BitMatrix # general relations + set::BitMatrix # indicates whether a general relation was computed + elems::Vector{Symbol} # symbols to use for the elements +end + +function Base.length(P::Poset) + return length(P.elems) +end + +function Base.show(io::IO, P::Poset) + print(io, "poset with $(length(P)) elements") +end + +# PosetElem + +struct PosetElem + i::Int + parent::Poset +end + +function index(x::PosetElem) + return x.i +end + +function parent(x::PosetElem) + return x.parent +end + +function Base.show(io::IO, elem::PosetElem) + return parent(elem).elems[index(elem)] +end + +# MaximalChainsIterator + +struct MaximalChainsIterator + poset::Poset + inplace::Bool + + function MaximalChainsIterator(P::Poset, inplace::Bool=false) + return new(P, inplace) + end +end + +# Poset constructors + +function poset(cov::Matrix{Int}, elems::Vector{<:VarName}=["x_$i" for i in 1:ncols(cov)]) + @req is_upper_triangular(cov, 1) "matrix must be strictly upper triangular" + @req nrows(cov) == ncols(cov) "must be a square matrix" + @req ncols(cov) == length(elems) "size of matrix must match number of elements" + + d = nrows(cov) + rel = BitMatrix(!iszero(cov[i, j]) for i in 1:d, j in 1:d) + return Poset(cov, rel, copy(rel), Symbol.(elems)) +end + +# PosetElem constructors + +function poset_elem(P::Poset, i::Int) + @req 1 <= i <= length(P.elems) "index out of range" + return PosetElem(i, P) +end + +function poset_elem(P::Poset, elem::VarName) + i = findfirst(==(Symbol(elem)), P.elems) + if isnothing(i) + error("unknown element") + end + + return poset_elem(P, i) +end + +function (P::Poset)(i::Int) + return poset_elem(P, i) +end + +function (P::Poset)(elem::VarName) + return poset_elem(P, elem) +end + +# MaximalChainsIterator constructors + +function maximal_chains(P::Poset) + return MaximalChainsIterator(P, false) +end + +# PosetElem functions + +function Base.:(<)(x::PosetElem, y::PosetElem) + @req parent(x) === parent(y) "elements must belong to the same poset" + ps = parent(x) + + # upper triangularity + if y.i <= x.i + return false + end + + # linearised index + len = ncols(ps.cov) + + # fast path + if ps.set[x.i, y.i] + return ps.rel[x.i, y.i] + end + + # slow path using covering relations + q = Int[x.i] + + while !isempty(q) + @label outer + n = last(q) + + # because of upper triangularity we only need to go to y.i + for k in (n + 1):(y.i) + if !iszero(ps.cov[n, k]) + # set the relation for all previous elements + for m in q + ps.set[m, k] = true + ps.rel[m, k] = true + end + + # we are done + if k == y.i + return ps.rel[x.i, y.i] + end + + if ps.set[k, y.i] + # check if can take the fast path + if ps.rel[k, y.i] + # set relation for elements in the stack + for m in q + ps.set[m, y.i] = true + ps.rel[m, y.i] = true + end + return ps.rel[x.i, y.i] + else + # k is not comparable to y + continue + end + end + + # add k to the stack and continue from k + push!(q, k) + @goto outer + end + end + + # we now know that n is not comparable y + ps.set[n, y.i] = true + ps.rel[n, y.i] = false + + # continue with previous element + pop!(q) + end + + return false +end + +# MaximalChainsIterator implementation + +function Base.IteratorSize(::Type{MaximalChainsIterator}) + return Base.SizeUnknown() +end + +function Base.eltype(::Type{MaximalChainsIterator}) + return Vector{Int} +end + +function Base.iterate(iter::MaximalChainsIterator, chain::Vector{Int}=[1, 1]) + j = 0 + while true + s = pop!(chain) + 1 + if isempty(chain) + return nothing + end + + j = findnext(!=(0), cov[last(chain), :], s) + if !isnothing(j) + break + end + end + + while !isnothing(j) + push!(c, j) + j = findnext(!=(0), cov[last(chain), :], j) + end + + if iter.inplace + return chain, chain + end + return deepcopy(chain), chain +end diff --git a/experimental/Posets/src/Posets.jl b/experimental/Posets/src/Posets.jl new file mode 100644 index 000000000000..e76f641b48f5 --- /dev/null +++ b/experimental/Posets/src/Posets.jl @@ -0,0 +1,24 @@ +module Posets + +using ..Oscar + +import Base: length, parent +import Oscar: index + +export MaximalChainsIterator +export Poset, PosetElem + +export poset, poset_elem +export maximal_chains + +include("Poset.jl") + +end + +using .Posets + +export MaximalChainsIterator +export Poset, PosetElem + +export poset, poset_elem +export maximal_chains diff --git a/experimental/Posets/test/Poset-test.jl b/experimental/Posets/test/Poset-test.jl new file mode 100644 index 000000000000..d19dd9bbcebb --- /dev/null +++ b/experimental/Posets/test/Poset-test.jl @@ -0,0 +1,72 @@ +@testset "Posets" begin + # covering relations, A2 adjoint rep + a2_adj_cov = [ + 0 1 1 0 0 0 + 0 0 0 2 1 0 + 0 0 0 1 2 0 + 0 0 0 0 0 1 + 0 0 0 0 0 1 + 0 0 0 0 0 0 + ] + + # general relations, A2 adjoint rep + a2_adj_rel = BitMatrix( + [ + 0 1 1 1 1 1 + 0 0 0 1 1 1 + 0 0 0 1 1 1 + 0 0 0 0 0 1 + 0 0 0 0 0 1 + 0 0 0 0 0 0 + ] + ) + + # covering relations, B2 adjoint rep + b2_adj_cov = [ + 0 1 1 0 0 0 0 0 + 0 0 0 3 1 0 0 0 + 0 0 0 1 2 0 0 0 + 0 0 0 0 0 2 1 0 + 0 0 0 0 0 1 3 0 + 0 0 0 0 0 0 0 1 + 0 0 0 0 0 0 0 1 + 0 0 0 0 0 0 0 0 + ] + + # general relations, B2 adjoint rep + b2_adj_rel = BitMatrix( + [ + 0 1 1 1 1 1 1 1 + 0 0 0 1 1 1 1 1 + 0 0 0 1 1 1 1 1 + 0 0 0 0 0 1 1 1 + 0 0 0 0 0 1 1 1 + 0 0 0 0 0 0 0 1 + 0 0 0 0 0 0 0 1 + 0 0 0 0 0 0 0 0 + ], + ) + + @testset "<(x::PosetElem, y::PosetElem)" begin + # rel: expected relations + function test_poset(cov::Matrix{Int}, rel::BitMatrix) + sz = ncols(cov) + for _ in 1:10 + ps = poset(cov) + for _ in 1:5 + x = rand(1:sz) + y = rand(1:sz) + + @test rel[x, y] == (ps(x) < ps(y)) + @test all( + ps.set[i, j] == false || ps.rel[i, j] == rel[i, j] for i in 1:sz for + j in (i + 1):sz + ) + end + end + end + + test_poset(a2_adj_cov, a2_adj_rel) + test_poset(b2_adj_cov, b2_adj_rel) + end +end From 02adfb264bd69b8ad1080fa992a5a7a860a02f57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20R=C3=B6hrich?= Date: Mon, 15 Apr 2024 12:18:09 +0200 Subject: [PATCH 2/6] add doc for poset constructor --- experimental/Posets/src/Poset.jl | 7 +++++++ experimental/Posets/test/Poset-test.jl | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/experimental/Posets/src/Poset.jl b/experimental/Posets/src/Poset.jl index 79e8f8e38d9e..a93a8a0a2a7e 100644 --- a/experimental/Posets/src/Poset.jl +++ b/experimental/Posets/src/Poset.jl @@ -47,6 +47,13 @@ end # Poset constructors +@doc raw""" + poset(cov::Matrix{Int}, elems::Vector{<:VarName}) -> Poset + +Construct a poset from covering relations `cov`, given in the form of the adjacency matrix +of a Hasse diagram. The covering relations must be given in topological order, i.e. `cov` +must be strictly upper triangular. By default the elements of the poset are named `x_i`. +""" function poset(cov::Matrix{Int}, elems::Vector{<:VarName}=["x_$i" for i in 1:ncols(cov)]) @req is_upper_triangular(cov, 1) "matrix must be strictly upper triangular" @req nrows(cov) == ncols(cov) "must be a square matrix" diff --git a/experimental/Posets/test/Poset-test.jl b/experimental/Posets/test/Poset-test.jl index d19dd9bbcebb..bb26e8a544a3 100644 --- a/experimental/Posets/test/Poset-test.jl +++ b/experimental/Posets/test/Poset-test.jl @@ -65,7 +65,7 @@ end end end - + test_poset(a2_adj_cov, a2_adj_rel) test_poset(b2_adj_cov, b2_adj_rel) end From 9e19fd0337e95182b5aec45db91e2977be74ea0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20R=C3=B6hrich?= Date: Mon, 15 Apr 2024 12:26:57 +0200 Subject: [PATCH 3/6] clean up --- experimental/Posets/src/Poset.jl | 21 +++++++++------------ experimental/Posets/src/Posets.jl | 4 ++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/experimental/Posets/src/Poset.jl b/experimental/Posets/src/Poset.jl index a93a8a0a2a7e..02443e3082c8 100644 --- a/experimental/Posets/src/Poset.jl +++ b/experimental/Posets/src/Poset.jl @@ -66,32 +66,29 @@ end # PosetElem constructors -function poset_elem(P::Poset, i::Int) +function (P::Poset)(i::Int) @req 1 <= i <= length(P.elems) "index out of range" return PosetElem(i, P) end -function poset_elem(P::Poset, elem::VarName) +function (P::Poset)(elem::VarName) i = findfirst(==(Symbol(elem)), P.elems) if isnothing(i) error("unknown element") end - return poset_elem(P, i) -end - -function (P::Poset)(i::Int) - return poset_elem(P, i) -end - -function (P::Poset)(elem::VarName) - return poset_elem(P, elem) + return Poset(i, P) end # MaximalChainsIterator constructors +@doc raw""" + maximal_chains(P::Poset) -> MaximalChainsIterator + +Returns an iterator over the maximal chains of `P`. +""" function maximal_chains(P::Poset) - return MaximalChainsIterator(P, false) + return MaximalChainsIterator(P) end # PosetElem functions diff --git a/experimental/Posets/src/Posets.jl b/experimental/Posets/src/Posets.jl index e76f641b48f5..a911394adb3e 100644 --- a/experimental/Posets/src/Posets.jl +++ b/experimental/Posets/src/Posets.jl @@ -8,8 +8,8 @@ import Oscar: index export MaximalChainsIterator export Poset, PosetElem -export poset, poset_elem export maximal_chains +export poset include("Poset.jl") @@ -20,5 +20,5 @@ using .Posets export MaximalChainsIterator export Poset, PosetElem -export poset, poset_elem export maximal_chains +export poset From ef8ca1568734641ee808446ab850b367b296e390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20R=C3=B6hrich?= Date: Mon, 15 Apr 2024 14:03:46 +0200 Subject: [PATCH 4/6] add tests for MaximalChainsIterator --- experimental/Posets/src/Poset.jl | 6 +++--- experimental/Posets/test/Poset-test.jl | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/experimental/Posets/src/Poset.jl b/experimental/Posets/src/Poset.jl index 02443e3082c8..afb3f0eeb7d0 100644 --- a/experimental/Posets/src/Poset.jl +++ b/experimental/Posets/src/Poset.jl @@ -181,15 +181,15 @@ function Base.iterate(iter::MaximalChainsIterator, chain::Vector{Int}=[1, 1]) return nothing end - j = findnext(!=(0), cov[last(chain), :], s) + j = findnext(!=(0), iter.poset.cov[last(chain), :], s) if !isnothing(j) break end end while !isnothing(j) - push!(c, j) - j = findnext(!=(0), cov[last(chain), :], j) + push!(chain, j) + j = findnext(!=(0), iter.poset.cov[last(chain), :], j) end if iter.inplace diff --git a/experimental/Posets/test/Poset-test.jl b/experimental/Posets/test/Poset-test.jl index bb26e8a544a3..9cfb4cb92b2c 100644 --- a/experimental/Posets/test/Poset-test.jl +++ b/experimental/Posets/test/Poset-test.jl @@ -69,4 +69,22 @@ test_poset(a2_adj_cov, a2_adj_rel) test_poset(b2_adj_cov, b2_adj_rel) end + + @testset "iterate(::MaximalChainsIterator, ::Vector{Int})" begin + ps = poset(a2_adj_cov) + @test collect(maximal_chains(ps)) == + [[1, 2, 4, 6], [1, 2, 5, 6], [1, 3, 4, 6], [1, 3, 5, 6]] + + ps = poset(b2_adj_cov) + @test collect(maximal_chains(ps)) == [ + [1, 2, 4, 6, 8], + [1, 2, 4, 7, 8], + [1, 2, 5, 6, 8], + [1, 2, 5, 7, 8], + [1, 3, 4, 6, 8], + [1, 3, 4, 7, 8], + [1, 3, 5, 6, 8], + [1, 3, 5, 7, 8], + ] + end end From d0b4b98cc46e70daffdef6d9cbc6f498890f992e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20R=C3=B6hrich?= <47457568+felix-roehrich@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:20:16 +0200 Subject: [PATCH 5/6] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Lars Göttgens --- experimental/Posets/src/Poset.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/experimental/Posets/src/Poset.jl b/experimental/Posets/src/Poset.jl index afb3f0eeb7d0..f6bbaa56b3d3 100644 --- a/experimental/Posets/src/Poset.jl +++ b/experimental/Posets/src/Poset.jl @@ -12,7 +12,7 @@ function Base.length(P::Poset) end function Base.show(io::IO, P::Poset) - print(io, "poset with $(length(P)) elements") + print(io, "Poset with $(ItemQuantity(length(P), "element")") end # PosetElem @@ -77,7 +77,7 @@ function (P::Poset)(elem::VarName) error("unknown element") end - return Poset(i, P) + return PosetElem(i, P) end # MaximalChainsIterator constructors From 2d78e9f3c054f42079799fc3418b56cddcd29a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20R=C3=B6hrich?= Date: Mon, 15 Apr 2024 15:52:42 +0200 Subject: [PATCH 6/6] fix error from suggested code --- experimental/Posets/src/Poset.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/experimental/Posets/src/Poset.jl b/experimental/Posets/src/Poset.jl index f6bbaa56b3d3..0bd83fc63746 100644 --- a/experimental/Posets/src/Poset.jl +++ b/experimental/Posets/src/Poset.jl @@ -12,14 +12,14 @@ function Base.length(P::Poset) end function Base.show(io::IO, P::Poset) - print(io, "Poset with $(ItemQuantity(length(P), "element")") + print(io, "Poset with $(ItemQuantity(length(P), "element"))") end # PosetElem struct PosetElem - i::Int parent::Poset + i::Int end function index(x::PosetElem) @@ -58,6 +58,7 @@ function poset(cov::Matrix{Int}, elems::Vector{<:VarName}=["x_$i" for i in 1:nco @req is_upper_triangular(cov, 1) "matrix must be strictly upper triangular" @req nrows(cov) == ncols(cov) "must be a square matrix" @req ncols(cov) == length(elems) "size of matrix must match number of elements" + @req allunique(elems) "element names must be unique" d = nrows(cov) rel = BitMatrix(!iszero(cov[i, j]) for i in 1:d, j in 1:d) @@ -68,7 +69,7 @@ end function (P::Poset)(i::Int) @req 1 <= i <= length(P.elems) "index out of range" - return PosetElem(i, P) + return PosetElem(P, i) end function (P::Poset)(elem::VarName) @@ -77,7 +78,7 @@ function (P::Poset)(elem::VarName) error("unknown element") end - return PosetElem(i, P) + return PosetElem(P, i) end # MaximalChainsIterator constructors