Skip to content

Commit

Permalink
Use @varnames_interface free_group (oscar-system#3317)
Browse files Browse the repository at this point in the history
Co-authored-by: Lars Göttgens <[email protected]>
  • Loading branch information
fingolfin and lgoettgens authored Aug 25, 2024
1 parent 27a47be commit 910b61b
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 51 deletions.
3 changes: 2 additions & 1 deletion docs/src/Groups/fpgroup.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ FPGroup
FPGroupElem
SubFPGroup
SubFPGroupElem
free_group(n::Int)
free_group
@free_group
full_group(G::Union{SubFPGroup, SubPcGroup})
relators(G::FPGroup)
length(g::Union{FPGroupElem, SubFPGroupElem})
Expand Down
4 changes: 1 addition & 3 deletions docs/src/Groups/quotients.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ This is the typical way to build finitely presented groups.

**Example:**
```jldoctest
julia> F=free_group(2);
julia> (f1,f2)=gens(F);
julia> F = @free_group(2);
julia> G,_=quo(F,[f1^2,f2^3,(f1*f2)^2]);
Expand Down
2 changes: 1 addition & 1 deletion experimental/GModule/src/Brueckner.jl
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ In particular Satz 15 c should be implemented.
=#

#= EXAMPLE
F = free_group("a", "b"); a,b = gens(F);
F = @free_group(:a, :b)
G, hom = quo(F, [a^2*b*a^-1*b*a^-1*b^-1*a*b^-2, a^2*b^-1*a*b^-1*a*b*a^
-1*b^2])
f1 = Oscar.RepPc.solvable_quotient(G)
Expand Down
20 changes: 10 additions & 10 deletions src/Groups/GAPGroups.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1912,18 +1912,18 @@ end
relators(G::FPGroup)
Return a vector of relators for the full finitely presented group `G`, i.e.,
elements $[x_1, x_2, \ldots, x_n]$ in $F =$ `free_group(ngens(G))` such that
`G` is isomorphic with $F/[x_1, x_2, \ldots, x_n]$.
elements $[w_1, w_2, \ldots, w_n]$ in $F =$ `free_group(ngens(G))` such that
`G` is isomorphic with $F/[w_1, w_2, \ldots, w_n]$.
# Examples
```jldoctest
julia> f = free_group(2); (x, y) = gens(f);
julia> f = @free_group(:x, :y);
julia> q = quo(f, [x^2, y^2, comm(x, y)])[1]; relators(q)
3-element Vector{FPGroupElem}:
f1^2
f2^2
f1^-1*f2^-1*f1*f2
x^2
y^2
x^-1*y^-1*x*y
```
"""
function relators(G::FPGroup)
Expand Down Expand Up @@ -1973,7 +1973,7 @@ whenever it is possible that the elements in `genimgs` do not support `one`.
# Examples
```jldoctest
julia> F = free_group(2); F1 = gen(F, 1); F2 = gen(F, 2);
julia> F = @free_group(:F1, :F2);
julia> imgs = gens(symmetric_group(4))
2-element Vector{PermGroupElem}:
Expand Down Expand Up @@ -2161,7 +2161,7 @@ Return the syllables of `g` as a list of pairs `gen => exp` where
# Examples
```jldoctest
julia> F = free_group(2); F1, F2 = gens(F);
julia> F = @free_group(:F1, :F2);
julia> syllables(F1^5*F2^-3)
2-element Vector{Pair{Int64, Int64}}:
Expand Down Expand Up @@ -2194,7 +2194,7 @@ numbers.
# Examples
```jldoctest
julia> F = free_group(2); F1, F2 = gens(F);
julia> F = @free_group(:F1, :F2);
julia> letters(F1^5*F2^-3)
8-element Vector{Int64}:
Expand Down Expand Up @@ -2239,7 +2239,7 @@ otherwise an exception is thrown.
# Examples
```jldoctest
julia> F = free_group(2); F1 = gen(F, 1); F2 = gen(F, 2);
julia> F = @free_group(:F1, :F2);
julia> length(F1*F2^-2)
3
Expand Down
197 changes: 161 additions & 36 deletions src/Groups/group_constructors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -475,27 +475,47 @@ projective_omega_group(n::Int, q::Int) = projective_omega_group(0, n, q)
#
################################################################################

"""
@doc raw"""
free_group(n::Int, s::VarName = :f; eltype::Symbol = :letter) -> FPGroup
free_group(L::Vector{<:VarName}) -> FPGroup
free_group(L::VarName...) -> FPGroup
The first form returns the free group of rank `n`, where the generators are
printed as `s1`, `s2`, ..., the default being `f1`, `f2`, ...
If `eltype` has the value `:syllable` then each element in the free group is
internally represented by a vector of syllables,
whereas a representation by a vector of integers is chosen in the default case
of `eltype == :letter`.
free_group(L::VarName... ; eltype::Symbol = :letter) -> FPGroup
free_group(varnames_specifiers... ; eltype::Symbol = :letter) -> FPGroup
Return a free group.
The first form returns a free group of rank `n`, where the generators are
printed as `"$s1"`, `"$s2"`, ..., the default being `f1`, `f2`, ...
The second form returns a free group of rank `n`, where `n` is the length of `L`,
and `L` consists of strings, symbols or characters giving the variable names.
In the final form, the argument list consists of a sequence of one or
more of the following:
1. A vector `L` of variable names.
2. A pair of the form `A => B`, where `A` is a `VarName` (so a string, symbol
or character) and `B` is a range or more generally an `AbstractVector`.
Then `length(B)` generators are defined whose names derive from a combination
of `A` and the respective element of `B`.
For example `:x => 1:3` defines three generators `x[1], x[2], x[3]`.
3. A pair of the form `A => C`, where `A` is again a `VarName`, and `C` is
a tuple of ranges or v. For example `"a" => (1:2, 1:2)` defines four generators
`a[1, 1], a[2, 1], a[1, 2], a[2, 2]`.
For the second and third type, optionally the `A` part can contain the
placeholder `#` to modify where the indices are inserted. For example
`"a#" => (1:2, 1:2)` defines four generators `a11, a21, a12, a22`.
Also, instead of a range, any vector can be used. For example `"#" => ([:x,:y], [:A, :B])`
defines four generators `xA, yA, xB, yB`.
In all variants, if the optional keyword argument `eltype` is given and
has the value `:syllable` then each element in the free group is
internally represented by a vector of syllables, whereas a
representation by a vector of integers is chosen in the default case of
`eltype == :letter`.
The second form, if `L` has length `n`, returns the free group of rank `n`,
where the `i`-th generator is printed as `L[i]`.
The third form, if there are `n` arguments `L...`,
returns the free group of rank `n`,
where the `i`-th generator is printed as `L[i]`.
!!! warning "Note"
Variables named like the group generators are *not* created by this function.
!!! warning
Julia variables named like the group generators are *not* created by this function.
However, the macro [`@free_group`](@ref) does just that.
# Examples
```jldoctest
Expand All @@ -505,34 +525,139 @@ Free group of rank 2
julia> w = F[1]^3 * F[2]^F[1] * F[-2]^2
a^2*b*a*b^-2
```
Here we show some of the different ways to create a free group.
```jldoctest
julia> gens(free_group(2))
2-element Vector{FPGroupElem}:
f1
f2
julia> gens(free_group(2, :a))
2-element Vector{FPGroupElem}:
a1
a2
julia> gens(free_group(:u, :v))
2-element Vector{FPGroupElem}:
u
v
julia> gens(free_group([:a, :b], "x" => 1:2, 'y' => (1:2, 1:2)))
8-element Vector{FPGroupElem}:
a
b
x[1]
x[2]
y[1, 1]
y[2, 1]
y[1, 2]
y[2, 2]
```
"""
function free_group(n::Int, s::VarName = :f; eltype::Symbol = :letter)
@req n >= 0 "n must be a non-negative integer"
t = s isa Char ? string(s) : s
function free_group(L::Vector{<:Symbol}; eltype::Symbol = :letter)
@req allunique(L) "generator names must be unique"
J = GapObj(L, recursive = true)
if eltype == :syllable
G = FPGroup(GAP.Globals.FreeGroup(n, GapObj(t); FreeGroupFamilyType = GapObj("syllable"))::GapObj)
G = FPGroup(GAP.Globals.FreeGroup(J; FreeGroupFamilyType = GapObj("syllable"))::GapObj)
elseif eltype == :letter
G = FPGroup(GAP.Globals.FreeGroup(J)::GapObj)
else
G = FPGroup(GAP.Globals.FreeGroup(n, GapObj(t))::GapObj)
error("eltype must be :letter or :syllable, not ", eltype)
end
GAP.Globals.SetRankOfFreeGroup(GapObj(G), n)
GAP.Globals.SetRankOfFreeGroup(GapObj(G), length(J))
return G
end

function free_group(L::Vector{<:VarName})
J = GapObj(L; recursive = true)
G = FPGroup(GAP.Globals.FreeGroup(J)::GapObj)
GAP.Globals.SetRankOfFreeGroup(GapObj(G), length(J))
return G
# HACK: we want to use `AbstractAlgebra.@varnames_interface` for free groups,
# but by default this requires the "constructor" function to have two return
# values: the ring/group/whatever, and its generator. But `free_group` has
# traditionally just one return value and we can't change that without badly
# breaking backwards compatibility. (Besides, I don't really *want* to change
# this, but that's a different matter).
#
# So we use a trick: we introduce `_free_group` with the right return value,
# and then delegate `free_group` to it, and similarly define the macro
# `@free_group` by delegating to the `@_free_group` macros (plus some extra
# shenigans).
function _free_group(L::Vector{<:Symbol}; eltype::Symbol = :letter)
G = free_group(L; eltype)
return G, gens(G)
end

function free_group(L::Vector{<:Char})
J = GapObj(Symbol.(L); recursive = true)
G = FPGroup(GAP.Globals.FreeGroup(J)::GapObj)
GAP.Globals.SetRankOfFreeGroup(GapObj(G), length(J))
return G
AbstractAlgebra.@varnames_interface _free_group(s)

free_group(L0::VarName, Ls::VarName...; kw...) = free_group([L0, Ls...]; kw...)
free_group(a0, args...; kw...) = _free_group(a0, args...; kw...)[1]
free_group(; kw...) = _free_group(0; kw...)[1]

# HACK to get the default variable name stem `:f` instead of `:x`
# but also to insert validation for `n`.
function free_group(n::Int, s::VarName = :f; kw...)
@req n >= 0 "n must be a non-negative integer"
_free_group(n, s; kw...)[1]
end

"""
@free_group(args...)
Return the free group obtained from `free_group(args...)` and introduce
its generators as Julia variables into the current scope.
# Examples
```jldoctest
julia> F = @free_group(:a, :b)
Free group of rank 2
julia> a^2*b*a*b^-2
a^2*b*a*b^-2
```
Note that the `varname => vector` syntax for specifying a vector or
matrix or general array of variables behaves slightly differently
compared to `free_group`, as the following example demonstrates.
```jldoctest
julia> U1 = free_group("x" => 1:3); gens(U1)
3-element Vector{FPGroupElem}:
x[1]
x[2]
x[3]
julia> U2 = @free_group("x" => 1:3); gens(U2)
3-element Vector{FPGroupElem}:
x1
x2
x3
julia> (x2^x1)^-1
x1^-1*x2^-1*x1
```
"""
macro free_group(args...)
if all(n -> n isa VarName || (n isa QuoteNode && n.value isa VarName), args)
# if the arguments are varnames, put them into a vector before delegating
# to @_free_group
esc(quote
Oscar.@_free_group([$(args...)])
end)
else
# by default just delegate to `@_free_group`
esc(quote
Oscar.@_free_group($(args...))
end)
end
end

macro free_group(n::Int, sym = :f)
if sym isa QuoteNode
sym = sym.value
end
s = Symbol(sym)::Symbol
esc(quote
Oscar.@_free_group($(QuoteNode(s)) => 1:$n)
end)
end

free_group(L::VarName...) = free_group(collect(L))

# FIXME: a function `free_abelian_group` with the same signature is
# already being defined by Hecke
Expand Down
1 change: 1 addition & 0 deletions src/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# execute: LC_COLLATE=C sort < src/exports.jl > dummy ; mv dummy src/exports.jl
export *
export @check
export @free_group
export @pbw_relations
export @perm
export @permutation_group
Expand Down

0 comments on commit 910b61b

Please sign in to comment.