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

Matroid serialisation and matroid encodings #3886

Merged
merged 19 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
127 changes: 127 additions & 0 deletions src/Combinatorics/Matroids/properties.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,133 @@
return _revlex_basis_from_vector(min_rvlx)
end

function _to_padded_char(x::Vector{Int},pad::Int=6)
for _ in 1:(pad-length(x)%pad)
push!(x,false)
end
x = reshape(x,pad,:)
x = [sum([x[i,j]*2^(pad-i) for i in 1:pad])+63 |> Char for j in 1:size(x)[2]]
return x
end

function _from_padded_char(x::Vector{Char})
x = [Int(c)-63 |> x->digits(x,base=2,pad=6) |> reverse for c in x]
x = foldl(append!,x)
return x
end
function _from_padded_char(x::String)
split(x,"") |> _from_padded_char

Check warning on line 1048 in src/Combinatorics/Matroids/properties.jl

View check run for this annotation

Codecov / codecov/patch

src/Combinatorics/Matroids/properties.jl#L1047-L1048

Added lines #L1047 - L1048 were not covered by tests
end

@doc raw"""
matroid6(M::Matroid)

Stores a matroid as a a string of ASCII characters. All Characters are between 64 and 127. The first character is a '<'. The String is of the form <r:n> where r is the rank of the matroid and n is the number of elements. This is followed by the matroid encoded as the revlex basis encoding. The encoding is done by converting the basis encoding to a vector of bits and then to a string of characters. The bits are padded to a multiple of 6 and then converted to characters. The characters are shifted by 63 to be in the ASCII range.

# Examples
To get the matroid6 encoding of the fano matroid write:
```jldoctest
julia> matroid6(fano_matroid())
"<o?:w?>^nv^j]"

```
"""
function matroid6(M::Matroid)::String
rvlx = min_revlex_basis_encoding(M)
v = _revlex_basis_to_vector(rvlx)
v = _to_padded_char(v)

r=rank(M)
r_vec = digits(r, base=2, pad=6)
r_vec = _to_padded_char(r_vec)

n=length(M)
n_vec = digits(n, base=2, pad=6)
n_vec = _to_padded_char(n_vec)

return "<$(join(r_vec)):$(join(n_vec))>" * join(v)
end

@doc raw"""
matroid_from_matroid6(str::AbstractString)

Returns the matroid from a matroid6 string.

# Examples
To retrieve the fano matroid from its matroid6 encoding write:
```jldoctest
julia> matroid_from_matroid6("<o?:w?>^nv^j]")
Matroid of rank 3 on 7 elements

```
"""
function matroid_from_matroid6(str::AbstractString)::Matroid
@req str[1] == '<' "Not a valid matroid6 string"

sep = split(str,">")
(r,n) = split(sep[1][2:end],":")
r = parse(Int,join(_from_padded_char(collect(r))|>reverse), base=2)
n = parse(Int,join(_from_padded_char(collect(n))|>reverse), base=2)

S = sep[2]
v = [Int(c)-63 |> x->digits(x,base=2,pad=6) |> reverse for c in S[1:end]]
S = join(isone(x) ? '*' : '0' for x in foldl(append!,v)[1:binomial(n,r)])

return matroid_from_revlex_basis_encoding(S,r,n)
end

@doc raw"""
matroid_hex(M::Matroid)

Stores a matroid as a string of hex characters. The first part of the string is "r" followed by the rank of the matroid. This is followed by "n" and the number of elements. The rest of the string is the revlex basis encoding. The encoding is done by converting the basis encoding to a vector of bits and then to a string of characters. The bits are padded to a multiple of 4 and then converted to hex characters.

# Examples
To get the hex encoding of the fano matroid write:
```jldoctest
julia> matroid_hex(fano_matroid())
"r3n7_3f7eefd6f"

```
"""
function matroid_hex(M::Matroid)::String
rvlx = min_revlex_basis_encoding(M)
r,n = rank(M), length(M)

v = _revlex_basis_to_vector(rvlx)
for _ in 1:(4-length(v)%4)
pushfirst!(v,0)
end
v = reshape(v,4,:)
v = [string(parse(Int,join(v[:,j]),base=2),base=16) for j in 1:size(v)[2]]
Sequenzer marked this conversation as resolved.
Show resolved Hide resolved

return "r$(r)n$(n)_" * join(v)
end

@doc raw"""
matroid_from_matroid_hex(str::AbstractString)

Returns a matroid from a string of hex characters.

# Examples
To retrieve the fano matroid from its hex encoding write:

```
Sequenzer marked this conversation as resolved.
Show resolved Hide resolved
julia> matroid_from_matroid_hex("r3n7_3f7eefd6f")
Matroid of rank 3 on 7 elements

```
"""
function matroid_from_matroid_hex(str::AbstractString)::Matroid
sep = split(str,"_")
(r,n) = parse.(Int,split(sep[1][2:end],"n"))

Check warning on line 1151 in src/Combinatorics/Matroids/properties.jl

View check run for this annotation

Codecov / codecov/patch

src/Combinatorics/Matroids/properties.jl#L1149-L1151

Added lines #L1149 - L1151 were not covered by tests
antonydellavecchia marked this conversation as resolved.
Show resolved Hide resolved

v = [digits(parse(Int,x,base=16),base=2,pad=4) |> reverse for x in sep[2]]
v = foldl(append!,v)
v = v[(length(v)-binomial(n,r)+1):end]

Check warning on line 1155 in src/Combinatorics/Matroids/properties.jl

View check run for this annotation

Codecov / codecov/patch

src/Combinatorics/Matroids/properties.jl#L1153-L1155

Added lines #L1153 - L1155 were not covered by tests

return matroid_from_revlex_basis_encoding(_revlex_basis_from_vector(v),r,n)

Check warning on line 1157 in src/Combinatorics/Matroids/properties.jl

View check run for this annotation

Codecov / codecov/patch

src/Combinatorics/Matroids/properties.jl#L1157

Added line #L1157 was not covered by tests
end

@doc raw"""
is_isomorphic(M1::Matroid, M2::Matroid)

Expand Down
24 changes: 24 additions & 0 deletions src/Serialization/Combinatorics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,30 @@ function load_object(s::DeserializerState, g::Type{Graph{T}}) where T <: Union{D
return g(smallobj)
end

###############################################################################
## Matroid
###############################################################################
@register_serialization_type Matroid "Matroid"

function save_object(s::SerializerState, m::Matroid)
@req m.groundset isa Vector{Int} "Groundset must be a Vector{Int}"
Sequenzer marked this conversation as resolved.
Show resolved Hide resolved

save_data_dict(s) do
save_object(s, pm_object(m), :matroid)
save_object(s, m.groundset, :groundset)
Sequenzer marked this conversation as resolved.
Show resolved Hide resolved
save_object(s, m.gs2num, :gs2num)
Sequenzer marked this conversation as resolved.
Show resolved Hide resolved
end
end


function load_object(s::DeserializerState, m::Type{Matroid})
mt = load_object(s, Polymake.BigObject, :matroid)
grds = load_object(s, Vector{Int}, :groundset)
gs2num = load_object(s, Dict{Int,Int}, :gs2num)
return m(mt, grds, gs2num)
end


###############################################################################
## IncidenceMatrix
###############################################################################
Expand Down
4 changes: 4 additions & 0 deletions src/Serialization/containers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,10 @@ function load_object(s::DeserializerState, ::Type{Dict{String, Int}})
return Dict{String, Int}(string(k) => parse(Int, v) for (k,v) in s.obj)
end

function load_object(s::DeserializerState, ::Type{Dict{Int, Int}})
return Dict{Int, Int}(parse(Int,string(k)) => parse(Int, v) for (k,v) in s.obj)
end

function load_object(s::DeserializerState, ::Type{<:Dict}, params::Dict{Symbol, Any})
key_type = params[:key_type]
value_type = haskey(params, :value_type) ? params[:value_type] : Any
Expand Down
4 changes: 4 additions & 0 deletions src/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -977,12 +977,16 @@ export mathieu_group
export matrix_group
export matrix_kernel
export matrix_ordering
export matroid6
export matroid_hex
export matroid_base_polytope
export matroid_from_bases
export matroid_from_circuits
export matroid_from_hyperplanes
export matroid_from_matrix_columns
export matroid_from_matrix_rows
export matroid_from_matroid6
export matroid_from_matroid_hex
export matroid_from_nonbases
export matroid_from_prime_ideal
export matroid_from_revlex_basis_encoding
Expand Down
9 changes: 9 additions & 0 deletions test/Combinatorics/Matroids/Matroids.jl
Original file line number Diff line number Diff line change
Expand Up @@ -368,4 +368,13 @@
@test_throws ArgumentError is_quotient(M2, M1)
@test_throws ArgumentError is_quotient(Q2, M2)
end

@testset "matroid6 and matroid_hex" begin
M = fano_matroid()
N = uniform_matroid(2, 4)
M1 = matroid_from_matroid6(matroid6(M))
N1 = matroid_from_matroid6(matroid6(N))
@test is_isomorphic(M, M1)
@test is_isomorphic(N, N1)
end
lgoettgens marked this conversation as resolved.
Show resolved Hide resolved
end
19 changes: 19 additions & 0 deletions test/Serialization/PolyhedralGeometry.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@ using Oscar: _integer_variables
end
end

@testset "Matroid" begin
@testset "Fano" begin
M = fano_matroid()
test_save_load_roundtrip(path, M) do loaded
@test sort(bases(M)) == sort(bases(loaded))
@test length(M) == length(loaded)
@test rank(M) == rank(loaded)
end
end
@testset "uniform" begin
M = uniform_matroid(2, 4)
test_save_load_roundtrip(path, M) do loaded
@test sort(bases(M)) == sort(bases(loaded))
@test length(M) == length(loaded)
@test rank(M) == rank(loaded)
end
end
end

@testset "Cone" begin
C = positive_hull([1 0; 0 1])
test_save_load_roundtrip(path, C) do loaded
Expand Down
Loading