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

Macro for entering permutations in cycle notation #1307

Merged
merged 26 commits into from
May 14, 2022
Merged
Show file tree
Hide file tree
Changes from 20 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
165 changes: 165 additions & 0 deletions src/Groups/perm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,168 @@ function cycle_structure(g::PermGroupElem)
# TODO: use SortedDict from DataStructures.jl ?
return Pair{Int,Int}[ i+1 => c[i] for i in 1:length(c) if GAP.Globals.ISB_LIST(c, i) ]
end


# The following code implements a new way to input permutations in Julia. For example
# it is possible to create a permutation as follow
# pi = Oscar.Permutations.@perm (1,2,3)(4,5)(6,7,8)
# > (1,2,3)(4,5)(6,7,8)
# For this we use macros to modify the syntax tree of (1,2,3)(4,5)(6,7,8) such that
# Julia can deal with the expression.


################################################################################
#
# perm
#
@doc Markdown.doc"""
@perm(ex)
Macro to input a permutation as
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved
`pi = @perm (1,2,3)(4,5)(6,7,8)` to obtain
the permutation `(1,2,3)(4,5)(6,7,8)`, that is, the output of
`cperm([1,2,3],[4,5],[6,7,8])`.
# Examples
```jldoctest
julia> @perm (1,2,3)(4,5)(6,7,8)
(1,2,3)(4,5)(6,7,8)
```
"""
macro perm(ex)
res = []

if typeof(ex) != Expr
error("Input is not a permutation.")
end
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved

if ex.head != :call && ex.head != :tuple
error("Input is not a permutation.")
end

danielrademacher marked this conversation as resolved.
Show resolved Hide resolved
while ex.head == :call
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved
pushfirst!(res, Expr(:vect, ex.args[2:end]...))
ex = ex.args[1]
if typeof(ex) != Expr
error("Input is not a permutation.")
end
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved
if ex.head != :call && ex.head != :tuple
error("Input is not a permutation.")
end
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved
end

if ex.head != :tuple
error("Input is not a permutation.")
end

pushfirst!(res, Expr(:vect,ex.args...))

return esc(:(Oscar.cperm($(res...))))
end


################################################################################
#
# perm(n,gens)
#
@doc Markdown.doc"""
@perm(n,gens)
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved
Macro to input a list of permutations which are generated as elements of
the `symmetric_group(n)` with the function `cperm`.
# Examples
```jldoctest
julia> gens = Oscar.Permutations.@perm 14 [
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved
(1,10)
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved
(2,11)
(3,12)
(4,13)
(5,14)
(6,8)
(7,9)
(1,2,3,4,5,6,7)(8,9,10,11,12,13,14)
(1,2)(10,11)
]
9-element Vector{PermGroupElem}:
(1,10)
(2,11)
(3,12)
(4,13)
(5,14)
(6,8)
(7,9)
(1,2,3,4,5,6,7)(8,9,10,11,12,13,14)
(1,2)(10,11)
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved
julia> gens[1].parent
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved
Sym( [ 1 .. 14 ] )
```
"""
macro perm(n,gens)

s = symmetric_group(n)
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved
ores = Vector{Expr}(undef,length(gens.args))
i = 1
for ex in gens.args
res = []

if typeof(ex) != Expr
throw(ArgumentError("Input is not a permutation."))
error("Input is not a permutation.")
end

if ex.head != :call && ex.head != :tuple
error("Input is not a permutation.")
end

while ex.head == :call
pushfirst!(res, Expr(:vect, ex.args[2:end]...))
ex = ex.args[1]
if typeof(ex) != Expr
error("Input is not a permutation.")
end
if ex.head != :call && ex.head != :tuple
error("Input is not a permutation.")
end
end

if ex.head != :tuple
error("Input is not a permutation.")
end

pushfirst!(res, Expr(:vect,ex.args...))
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved

ores[i] = esc(:(Oscar.cperm(symmetric_group($n),$(res...))))
Copy link
Member

Choose a reason for hiding this comment

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

This calls symmetric_group again and again, which isn't great, as we create a new group object each time. This could be improved by using let block; but I don't want to complicate things even further, so I suggest we first iron out everything else and get this merged, then we can worry about fine tuning.

Just for the record, here is what I'd do: I'd only collect the res values here:

Suggested change
ores[i] = esc(:(Oscar.cperm(symmetric_group($n),$(res...))))
ores[i] = esc(:( [$(res...)] ))

Then, the final return of this macro could look something like this:

return quote
   let g = symmetric_group($n)
       [ cperm(g, pi) for pi in [$(ores...)] ]
   end
end

Note: normally we'd have to worry that g and pi could clash with symbols defined by the programmer in the surrounding code, and referenced in the permutation expression. This is where the esc function comes into play: we applied it to the expressions we got from the user, but not to the outer expression above. This leads to the Julia macro hygiene functionality kicking in, which determines that g and pi (unlike symmetric_group and cperm) are not known variables, and replaces them by special generated names. You can check the effect of this via macroexpand:

julia> g = 7  # introduce a variable g and use it in the macro expression:
7

julia> macroexpand(Main, :( Oscar.@perm 14 [
                     (g,9)
                     (1,2)(10,11)
                    ]))
quote
    let var"#332#g" = Oscar.symmetric_group(14)
        [Oscar.cperm(var"#332#g", var"#333#pi"...) for var"#333#pi" = [[[g, 9]], [[1, 2], [10, 11]]]]
    end
end

i = i + 1
Copy link
Member

Choose a reason for hiding this comment

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

You could just let ores start out as an empty vector and then do push!(ores, ...), then you don't need i.

end

return Expr(:vect,ores...)
end


################################################################################
#
# permgroup(n::Int64,gens::Vector{PermGroupElem})
#
@doc Markdown.doc"""
permgroup(n::Int64,gens::Vector{PermGroupElem})
Generates a `PermGroup` with generators gens as a subgroup of `symmetric_group(n)`.
# Examples
```jldoctest
julia> gens = Oscar.Permutations.@perm 14 [
(1,10)
(2,11)
(3,12)
(4,13)
(5,14)
(6,8)
(7,9)
(1,2,3,4,5,6,7)(8,9,10,11,12,13,14)
(1,2)(10,11)
];
julia> G = Oscar.Permutations.permgroup(14,gens)
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved
<permutation group with 9 generators>
```
"""
function permgroup(n::Int64,gens::Vector{PermGroupElem})

return PermGroup(GAP.Globals.Subgroup(GAP.Globals.SymmetricGroup(GAP.Obj(n)),GAP.Obj([GAP.Obj(x) for x in gens ])))
end

export @perm,permgroup
44 changes: 44 additions & 0 deletions test/Groups/Permutations.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@testset "Examples.Permutations" begin
p = @perm (1,2)(3,4)(5,6)
@test p == cperm([1,2],[3,4],[5,6])
p = @perm (1,2)(3,4,5)(7,8,9)
@test p == cperm([1,2],[3,4,5],[7,8,9])

a=1;b=2;f=x->3*x + 2;
p = @perm (a,f(a),b)(a+1,b*2)
@test p == cperm([1,5,4,2])

@test_throws ErrorException @perm (-1, 1)
@test_throws LoadError @perm "bla"
danielrademacher marked this conversation as resolved.
Show resolved Hide resolved
@test_throws LoadError @perm 1 + 1

gens = @perm 14 [
(1,10)
(2,11)
(3,12)
(4,13)
(5,14)
(6,8)
(7,9)
(1,2,3,4,5,6,7)(8,9,10,11,12,13,14)
(1,2)(10,11)
]
p = Vector{PermGroupElem}(undef,9)
p[1] = cperm(symmetric_group(14),[1,10])
p[2] = cperm(symmetric_group(14),[2,11])
p[3] = cperm(symmetric_group(14),[3,12])
p[4] = cperm(symmetric_group(14),[4,13])
p[5] = cperm(symmetric_group(14),[5,14])
p[6] = cperm(symmetric_group(14),[6,8])
p[7] = cperm(symmetric_group(14),[7,9])
p[8] = cperm(symmetric_group(14),[1,2,3,4,5,6,7],[8,9,10,11,12,13,14])
p[9] = cperm(symmetric_group(14),[1,2],[10,11])
@test gens == p

@test_throws ArgumentError @perm 10 [(1,11)]

G = permgroup(14,gens)
@test order(G) == 645120
end


1 change: 1 addition & 0 deletions test/Groups/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ include("MatrixDisplay.jl")
include("group_characters.jl")
include("FiniteFormOrthogonalGroup.jl")
include("GrpAb.jl")
include("Permutations.jl")