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

Add letters function for PcGroupElem #4202

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions src/GAP/wrappers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ GAP.@wrap ElementsFamily(x::GapObj)::GapObj
GAP.@wrap ELMS_LIST(x::GapObj, y::GapObj)::GapObj
GAP.@wrap Embedding(x::GapObj, y::Int)::GapObj
GAP.@wrap EpimorphismSchurCover(x::GapObj)::GapObj
GAP.@wrap Exponents(x::GapObj)::GapObj
GAP.@wrap ExponentsOfPcElement(x::GapObj, y::GapObj)::GapObj
GAP.@wrap ExtRepOfObj(x::GapObj)::GapObj
GAP.@wrap ExtRepPolynomialRatFun(x::GapObj)::GapObj
Expand All @@ -104,6 +105,7 @@ GAP.@wrap FusionConjugacyClasses(x::GapObj, y::GapObj)::GapObj
GAP.@wrap GaloisCyc(x::GAP.Obj, GapInt)::GAP.Obj
GAP.@wrap GeneratorsOfField(x::GapObj)::GapObj
GAP.@wrap GeneratorsOfGroup(x::GapObj)::GapObj
GAP.@wrap GenExpList(x::GapObj)::GapObj
GAP.@wrap GetFusionMap(x::GapObj, y::GapObj)::GapObj
GAP.@wrap GF(x::Any)::GapObj
GAP.@wrap GF(x::Any, y::Any)::GapObj
Expand Down Expand Up @@ -221,6 +223,7 @@ GAP.@wrap IsomorphismFpGroupByPcgs(x::GapObj, y::GapObj)::GapObj
GAP.@wrap IsOne(x::Any)::Bool
GAP.@wrap IsPcGroup(x::Any)::Bool
GAP.@wrap IsPcpGroup(x::Any)::Bool
GAP.@wrap IsPcpElement(x::Any)::Bool
GAP.@wrap IsPerfectGroup(x::Any)::Bool
GAP.@wrap IsPermGroup(x::Any)::Bool
GAP.@wrap IsPGroup(x::Any)::Bool
Expand Down Expand Up @@ -304,6 +307,7 @@ GAP.@wrap OnTuples(x::GapObj, y::GapObj)::GapObj
GAP.@wrap Order(x::Any)::GapInt
GAP.@wrap OrthogonalComponents(x::GapObj, y::GapObj, z::GapInt)::GapObj
GAP.@wrap PcElementByExponentsNC(x::GapObj, y::GapObj)::GapObj
GAP.@wrap PcpElementByExponentsNC(x::GapObj, y::GapObj)::GapObj
GAP.@wrap Pcgs(x::GapObj)::GapObj
GAP.@wrap PcpGroupByCollectorNC(x::GapObj)::GapObj
GAP.@wrap PCore(x::GapObj, y::GapInt)::GapObj
Expand Down
154 changes: 153 additions & 1 deletion src/Groups/pcgroup.jl
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,6 @@ function _GAP_collector_from_the_left(c::GAP_Collector)
return cGAP::GapObj
end


# Create the collector on the GAP side on demand
function underlying_gap_object(c::GAP_Collector)
if ! isdefined(c, :X)
Expand Down Expand Up @@ -473,6 +472,159 @@ function pc_group(c::GAP_Collector)
end
end

"""
letters(g::Union{PcGroupElem, SubPcGroupElem})

Return the letters of `g` as a list of integers, each entry corresponding to
a group generator.
Copy link
Member

@fingolfin fingolfin Nov 13, 2024

Choose a reason for hiding this comment

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

Note that we can also produce negative numbers: e.g. -3 means "inverse of 3rd generator". This should be explained, and perhaps an example added showing that. E.g. based on this:

julia> x = (gg[1]*gg[2]*gg[3])^-2
g1*g2^-2*g3^3

Perhaps also add something like this (and then mirror it in the other function)

See also [`syllables`](@ref).

Copy link
Author

Choose a reason for hiding this comment

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

I have added a small example with some brief explanation to letters for this. However I am unsure if the example is good as I was not able to get elements with negative exponents and test.

Copy link
Member

Choose a reason for hiding this comment

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

For that you need an infinite group. E.g.

julia> g = dihedral_group(PosInf())
Pc group of infinite order

julia> g[1]^-3 * g[2]^-3
g1*g2^-3

or

julia> g = abelian_group(PcGroup, [5, 0])
Pc group of infinite order

julia> g[1]^-3 * g[2]^-3
g1^2*g2^-3


This method can produce letters represented by negative numbers. A negative number
indicates the inverse of the generator at the corresponding positive index.

For example, as shown below, an output of `-1` refers to the "inverse of the first generator".

See also [`syllables(::Union{PcGroupElem, SubPcGroupElem})`](@ref).

# Examples

```jldoctest
julia> g = abelian_group(PcGroup, [0, 5])
Pc group of infinite order

julia> x = g[1]^-3 * g[2]^-3
g1^-3*g2^2

julia> letters(x)
5-element Vector{Int64}:
-1
-1
-1
2
2
```

```jldoctest
julia> gg = small_group(6, 1)
Pc group of order 6

julia> x = gg[1]^5*gg[2]^-4
f1*f2^2

julia> letters(x)
3-element Vector{Int64}:
1
2
2
```
"""
function letters(g::Union{PcGroupElem, SubPcGroupElem})
# check if we have a PcpGroup element
if GAPWrap.IsPcpElement(GapObj(g))
exp = GAPWrap.Exponents(GapObj(g))

# Should we check if the output is not larger than the
# amount of generators? Requires use of `parent`.
# @assert length(exp) == length(gens(parent(g)))

w = [sign(e) * i for (i, e) in enumerate(exp) for _ in 1:abs(e)]
return Vector{Int}(w)
else # finite PcGroup
w = GAPWrap.UnderlyingElement(GapObj(g))
return Vector{Int}(GAPWrap.LetterRepAssocWord(w))
end
end

"""
syllables(g::Union{PcGroupElem, SubPcGroupElem})

Return the syllables of `g` as a list of pairs of integers, each entry corresponding to
a group generator and its exponent.
fingolfin marked this conversation as resolved.
Show resolved Hide resolved

# Examples

```jldoctest
julia> gg = small_group(6, 1)
Pc group of order 6

julia> x = gg[1]^5*gg[2]^-4
f1*f2^2

julia> s = syllables(x)
2-element Vector{Pair{Int64, ZZRingElem}}:
1 => 1
2 => 2

julia> gg(s)
f1*f2^2

julia> gg(s) == x
true
```

```jldoctest
julia> g = abelian_group(PcGroup, [5, 0])
Pc group of infinite order

julia> x = g[1]^-3 * g[2]^-3
g1^2*g2^-3

julia> s = syllables(x)
2-element Vector{Pair{Int64, ZZRingElem}}:
1 => 2
2 => -3

julia> g(s)
g1^2*g2^-3

julia> g(s) == x
true
```
"""
lgoettgens marked this conversation as resolved.
Show resolved Hide resolved
function syllables(g::Union{PcGroupElem, SubPcGroupElem})
# check if we have a PcpGroup element
if GAPWrap.IsPcpElement(GapObj(g))
l = GAPWrap.GenExpList(GapObj(g))
else # finite PcGroup
l = GAPWrap.ExtRepOfObj(GapObj(g))
end

@assert iseven(length(l))
return Pair{Int, ZZRingElem}[l[i-1] => l[i] for i = 2:2:length(l)]
end

# Convert syllables in canonical form into exponent vector
function _exponent_vector(sylls::Vector{Pair{Int64, ZZRingElem}}, n)
res = zeros(ZZRingElem, n)
for pair in sylls
@assert res[pair.first] == 0 #just to make sure
res[pair.first] = pair.second
end
return res
end

# Convert syllables in canonical form into group element
fingolfin marked this conversation as resolved.
Show resolved Hide resolved
function (G::PcGroup)(sylls::Vector{Pair{Int64, ZZRingElem}}; check::Bool=true)
Copy link
Member

Choose a reason for hiding this comment

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

Can you also add a similar constructor which takes an exponent vector, i.e., an inverse to letters?

Copy link
Member

Choose a reason for hiding this comment

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

One needs to watch out for the semantic difference to the already existing function for FPGroups in

function (G::FPGroup)(extrep::AbstractVector{T}) where T <: IntegerUnion
, which expects a flattened list of syllable pairs instead.

Copy link
Member

Choose a reason for hiding this comment

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

ugh, OK. perhaps we should kill that (is it documented?) first then. In GAP it made some sense to use such a flat list to avoid memory, as there are no tuples in GAP, only lists. But in Julia there is no real benefit of this over a Vector{Pair{Int64, ZZRingElem}}.

But that is way beyond this PR. So let's leave out the constructor I mentioned.

Copy link
Member

Choose a reason for hiding this comment

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

No, not documented, but used for serialization. I haven't looked into how it is used there, so maybe we can just adapt the deserialization function, in the worst case it needs an upgrade script.

Copy link
Member

Choose a reason for hiding this comment

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

ugh, OK. perhaps we should kill that (is it documented?) first then.

maybe @ThomasBreuer can look into that?

# check if the syllables are in canonical form
if check
indices = map(p -> p.first, sylls)
@req allunique(indices) "given syllables have repeating generators"
@req issorted(indices) "given syllables must be in ascending order"
end

e = _exponent_vector(sylls, ngens(G))

# check if G is an underlying PcpGroup
GG = GapObj(G)
if GAPWrap.IsPcpGroup(GG)
coll = GAPWrap.Collector(GG)
x = GAPWrap.PcpElementByExponentsNC(coll, GapObj(e, true))
else # finite PcGroup
pcgs = GAPWrap.FamilyPcgs(GG)
x = GAPWrap.PcElementByExponentsNC(pcgs, GapObj(e, true))
end

return Oscar.group_element(G, x)
end

# Create an Oscar collector from a GAP collector.

Expand Down
52 changes: 52 additions & 0 deletions test/Groups/pcgroup.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,58 @@ end
@test cgg !== c.X
end

@testset "create letters from polycyclic group elements" begin

# finite polycyclic groups
G = small_group(6, 1)
@test letters(G[1]^5*G[2]^-4) == [1, 2, 2]
@test letters(G[1]^5*G[2]^4) == [1, 2] # all positive exp
@test letters(G[1]^-5*G[2]^-7) == [1, 2, 2] # all negative exp
@test letters(G[1]^2*G[2]^3) == [] # both identity elements

# finite polycyclic subgroups
G = pc_group(symmetric_group(4))
H = derived_subgroup(G)[1]
@test letters(H[1]^2) == [2, 2]
@test letters(H[1]^2*H[2]^3*H[3]^3) == [2, 2, 3, 4] # all positive exp
@test letters(H[1]^-2*H[2]^-3*H[3]^-3) == [2, 3, 4] # all negative exp
@test letters(H[1]^3*H[2]^4*H[3]^2) == [] # all identity elements

# infinite polycyclic groups
G = abelian_group(PcGroup, [5, 0])
@test letters(G[1]^3) == [1, 1, 1]
@test letters(G[1]^4*G[2]^3) == [1, 1, 1, 1, 2, 2, 2] # all positive exp
@test letters(G[1]^-2*G[2]^-5) == [1, 1, 1, -2, -2, -2, -2, -2] # all negative exp
@test letters(G[1]^5*G[2]^-3) == [-2, -2, -2] # one identity element
end

@testset "create polycyclic group element from syllables" begin
# finite polycyclic groups
G = small_group(6, 1)

x = G[1]^5*G[2]^-4
sylls = syllables(x)
@test sylls == [1 => ZZ(1), 2 => ZZ(2)] # check general usage
@test G(sylls) == x # check if equivalent

sylls = [1 => ZZ(1), 2 => ZZ(2), 1 => ZZ(3)]
@test_throws ArgumentError G(sylls) # repeating generators

sylls = [2 => ZZ(1), 1 => ZZ(2)]
@test_throws ArgumentError G(sylls) # not in ascending order

sylls = [2 => ZZ(1), 1 => ZZ(2), 1 => ZZ(3)]
@test_throws ArgumentError G(sylls) # both conditions

# infinite polycyclic groups
G = abelian_group(PcGroup, [5, 0])

x = G[1]^3*G[2]^-5
sylls = syllables(x)
@test sylls == [1 => ZZ(3), 2 => ZZ(-5)] # check general usage
@test G(sylls) == x # check if equivalent
end

@testset "create collectors from polycyclic groups" begin
for i in rand(1:number_of_small_groups(96), 10)
g = small_group(96, i)
Expand Down
Loading