Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Posets in OSCAR #3610

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
199 changes: 199 additions & 0 deletions experimental/Posets/src/Poset.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# 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 $(ItemQuantity(length(P), "element")")
end

# PosetElem

struct PosetElem
i::Int
parent::Poset
felix-roehrich marked this conversation as resolved.
Show resolved Hide resolved
end

function index(x::PosetElem)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The already existing index methods do something completely different. Does somebody know of other occurrences in the Oscar world of something like this and how we call it there?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its mostly called data to access the defining datum...

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

@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"
@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 (P::Poset)(i::Int)
@req 1 <= i <= length(P.elems) "index out of range"
return PosetElem(i, P)
end

function (P::Poset)(elem::VarName)
i = findfirst(==(Symbol(elem)), P.elems)
if isnothing(i)
error("unknown element")
end

return PosetElem(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)
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can just replace this goto by a break and then get rid of the @label as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I replace this with break, I will have to introduce a variable and an if statement after for loop. In the end I felt that goto is easier and the flow is clear too.

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), iter.poset.cov[last(chain), :], s)
if !isnothing(j)
break
end
end

while !isnothing(j)
push!(chain, j)
j = findnext(!=(0), iter.poset.cov[last(chain), :], j)
end

if iter.inplace
return chain, chain
end
return deepcopy(chain), chain
end
24 changes: 24 additions & 0 deletions experimental/Posets/src/Posets.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module Posets

using ..Oscar

import Base: length, parent
import Oscar: index

export MaximalChainsIterator
export Poset, PosetElem

export maximal_chains
export poset

include("Poset.jl")

end

using .Posets

export MaximalChainsIterator
export Poset, PosetElem

export maximal_chains
export poset
90 changes: 90 additions & 0 deletions experimental/Posets/test/Poset-test.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
@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

@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
Loading