Merge pull request #31 from kalmarek/mk/update_to_PG_0.6

Update to PermutationGroups-0.6
This commit is contained in:
Marek Kaluba 2024-02-13 12:08:48 +01:00 committed by GitHub
commit 1fbd7b875b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 253 additions and 146 deletions

View File

@ -1,7 +1,7 @@
name = "Groups" name = "Groups"
uuid = "5d8bd718-bd84-11e8-3b40-ad14f4a32557" uuid = "5d8bd718-bd84-11e8-3b40-ad14f4a32557"
authors = ["Marek Kaluba <kalmar@amu.edu.pl>"] authors = ["Marek Kaluba <kalmar@amu.edu.pl>"]
version = "0.7.8" version = "0.8"
[deps] [deps]
GroupsCore = "d5909c97-4eac-4ecc-a3dc-fdd0858a4120" GroupsCore = "d5909c97-4eac-4ecc-a3dc-fdd0858a4120"
@ -14,10 +14,10 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
[compat] [compat]
GroupsCore = "0.4" GroupsCore = "0.5"
KnuthBendix = "0.4" KnuthBendix = "0.4"
OrderedCollections = "1" OrderedCollections = "1"
PermutationGroups = "0.4" PermutationGroups = "0.6"
StaticArrays = "1" StaticArrays = "1"
julia = "1.6" julia = "1.6"

View File

@ -27,6 +27,7 @@ include(joinpath("constructions", "constructions.jl"))
import .Constructions import .Constructions
include("types.jl") include("types.jl")
include("rand.jl")
include("hashing.jl") include("hashing.jl")
include("normalform.jl") include("normalform.jl")
include("autgroups.jl") include("autgroups.jl")

View File

@ -36,8 +36,8 @@ function Base.:(==)(
hash(g) != hash(h) && return false hash(g) != hash(h) && return false
end end
length(word(g)) > 8 && normalform!(g) normalform!(g)
length(word(h)) > 8 && normalform!(h) normalform!(h)
word(g) == word(h) && return true word(g) == word(h) && return true
@ -138,43 +138,47 @@ end
# forward evaluate by substitution # forward evaluate by substitution
struct LettersMap{T,A} struct LettersMap{W<:AbstractWord,A}
indices_map::Dict{Int,T} indices_map::Dict{Int,W}
A::A A::A
end end
function LettersMap(a::FPGroupElement{<:AutomorphismGroup}) function LettersMap(a::FPGroupElement{<:AutomorphismGroup})
dom = domain(a) dom = domain(a)
@assert all(isone length word, dom) if all(isone length word, dom)
A = alphabet(first(dom)) A = alphabet(first(dom))
first_letters = first.(word.(dom)) first_letters = first.(word.(dom))
img = evaluate!(dom, a) img = evaluate!(dom, a)
# (dom[i] → img[i] is a map from domain to images) # (dom[i] → img[i] is a map from domain to images)
# we need a map from alphabet indices → (gens, gens⁻¹) → images # we need a map from alphabet indices → (gens, gens⁻¹) → images
# here we do it for elements of the domain # here we do it for elements of the domain
# (trusting it's a set of generators that define a) # (trusting it's a set of generators that define a)
@assert length(dom) == length(img) @assert length(dom) == length(img)
indices_map = indices_map =
Dict(A[A[fl]] => word(im) for (fl, im) in zip(first_letters, img)) Dict(Int(fl) => word(im) for (fl, im) in zip(first_letters, img))
# inverses of generators are dealt lazily in getindex # inverses of generators are dealt lazily in getindex
else
throw("LettersMap is not implemented for non-generators in domain")
end
return LettersMap(indices_map, A) return LettersMap(indices_map, A)
end end
function Base.getindex(lm::LettersMap, i::Integer) function Base.getindex(lm::LettersMap{W}, i::Integer) where {W}
# here i is an index of an alphabet # here i is an index of an alphabet
@boundscheck 1 i length(lm.A) @boundscheck 1 i length(lm.A)
if !haskey(lm.indices_map, i) if !haskey(lm.indices_map, i)
img = if haskey(lm.indices_map, inv(i, lm.A)) I = inv(i, lm.A)
inv(lm.indices_map[inv(i, lm.A)], lm.A) if haskey(lm.indices_map, I)
img = inv(lm.indices_map[I], lm.A)
lm.indices_map[i] = img
else else
@warn "LetterMap: neither $i nor its inverse has assigned value" lm.indices_map[i] = W([i])
one(valtype(lm.indices_map)) lm.indices_map[I] = W([I])
end end
lm.indices_map[i] = img
end end
return lm.indices_map[i] return lm.indices_map[i]
end end
@ -185,9 +189,10 @@ function (a::FPGroupElement{<:AutomorphismGroup})(g::FPGroupElement)
return parent(g)(img_w) return parent(g)(img_w)
end end
evaluate(w::AbstractWord, lm::LettersMap) = evaluate!(one(w), w, lm) evaluate(w::AbstractWord, lm::LettersMap) = evaluate!(similar(w), w, lm)
function evaluate!(res::AbstractWord, w::AbstractWord, lm::LettersMap) function evaluate!(res::AbstractWord, w::AbstractWord, lm::LettersMap)
resize!(res, 0)
for i in w for i in w
append!(res, lm[i]) append!(res, lm[i])
end end

View File

@ -59,15 +59,6 @@ end
GroupsCore.ngens(G::DirectPower) = _nfold(G) * ngens(G.group) GroupsCore.ngens(G::DirectPower) = _nfold(G) * ngens(G.group)
function GroupsCore.gens(G::DirectPower, i::Integer)
k = ngens(G.group)
ci = CartesianIndices((k, _nfold(G)))
@boundscheck checkbounds(ci, i)
r, c = Tuple(ci[i])
tup = ntuple(j -> j == c ? gens(G.group, r) : one(G.group), _nfold(G))
return DirectPowerElement(tup, G)
end
function GroupsCore.gens(G::DirectPower) function GroupsCore.gens(G::DirectPower)
N = _nfold(G) N = _nfold(G)
S = gens(G.group) S = gens(G.group)
@ -78,14 +69,6 @@ end
Base.isfinite(G::DirectPower) = isfinite(G.group) Base.isfinite(G::DirectPower) = isfinite(G.group)
function Base.rand(
rng::Random.AbstractRNG,
rs::Random.SamplerTrivial{<:DirectPower},
)
G = rs[]
return DirectPowerElement(rand(rng, G.group, _nfold(G)), G)
end
GroupsCore.parent(g::DirectPowerElement) = g.parent GroupsCore.parent(g::DirectPowerElement) = g.parent
function Base.:(==)(g::DirectPowerElement, h::DirectPowerElement) function Base.:(==)(g::DirectPowerElement, h::DirectPowerElement)
@ -94,13 +77,6 @@ end
Base.hash(g::DirectPowerElement, h::UInt) = hash(g.elts, hash(parent(g), h)) Base.hash(g::DirectPowerElement, h::UInt) = hash(g.elts, hash(parent(g), h))
function Base.deepcopy_internal(g::DirectPowerElement, stackdict::IdDict)
return DirectPowerElement(
Base.deepcopy_internal(g.elts, stackdict),
parent(g),
)
end
Base.inv(g::DirectPowerElement) = DirectPowerElement(inv.(g.elts), parent(g)) Base.inv(g::DirectPowerElement) = DirectPowerElement(inv.(g.elts), parent(g))
function Base.:(*)(g::DirectPowerElement, h::DirectPowerElement) function Base.:(*)(g::DirectPowerElement, h::DirectPowerElement)
@ -108,6 +84,39 @@ function Base.:(*)(g::DirectPowerElement, h::DirectPowerElement)
return DirectPowerElement(g.elts .* h.elts, parent(g)) return DirectPowerElement(g.elts .* h.elts, parent(g))
end end
# to make sure that parents are never copied i.e.
# g and deepcopy(g) share their parent
Base.deepcopy_internal(G::DirectPower, ::IdDict) = G
################## Implementing Group Interface Done!
function GroupsCore.gens(G::DirectPower, i::Integer)
k = ngens(G.group)
ci = CartesianIndices((k, _nfold(G)))
@boundscheck checkbounds(ci, i)
r, c = Tuple(ci[i])
tup = ntuple(j -> j == c ? gens(G.group, r) : one(G.group), _nfold(G))
return DirectPowerElement(tup, G)
end
# Overloading rand: the PRA of GroupsCore is known for not performing
# well on direct sums
function Random.Sampler(
RNG::Type{<:Random.AbstractRNG},
G::DirectPower,
repetition::Random.Repetition = Val(Inf),
)
return Random.SamplerTrivial(G)
end
function Base.rand(
rng::Random.AbstractRNG,
rs::Random.SamplerTrivial{<:DirectPower},
)
G = rs[]
return DirectPowerElement(rand(rng, G.group, _nfold(G)), G)
end
function GroupsCore.order(::Type{I}, g::DirectPowerElement) where {I<:Integer} function GroupsCore.order(::Type{I}, g::DirectPowerElement) where {I<:Integer}
return convert(I, reduce(lcm, (order(I, h) for h in g.elts); init = one(I))) return convert(I, reduce(lcm, (order(I, h) for h in g.elts); init = one(I)))
end end
@ -117,10 +126,13 @@ Base.isone(g::DirectPowerElement) = all(isone, g.elts)
function Base.show(io::IO, G::DirectPower) function Base.show(io::IO, G::DirectPower)
n = _nfold(G) n = _nfold(G)
nn = n == 1 ? "1-st" : n == 2 ? "2-nd" : n == 3 ? "3-rd" : "$n-th" nn = n == 1 ? "1-st" : n == 2 ? "2-nd" : n == 3 ? "3-rd" : "$n-th"
return print(io, "Direct $(nn) power of $(G.group)") return print(io, "Direct $(nn) power of ", G.group)
end end
function Base.show(io::IO, g::DirectPowerElement) function Base.show(io::IO, g::DirectPowerElement)
return print(io, "( ", join(g.elts, ", "), " )") print(io, "( ")
join(io, g.elts, ", ")
return print(" )")
end end
# convienience: # convienience:

View File

@ -72,14 +72,6 @@ end
Base.isfinite(G::DirectProduct) = isfinite(G.first) && isfinite(G.last) Base.isfinite(G::DirectProduct) = isfinite(G.first) && isfinite(G.last)
function Base.rand(
rng::Random.AbstractRNG,
rs::Random.SamplerTrivial{<:DirectProduct},
)
G = rs[]
return DirectProductElement((rand(rng, G.first), rand(rng, G.last)), G)
end
GroupsCore.parent(g::DirectProductElement) = g.parent GroupsCore.parent(g::DirectProductElement) = g.parent
function Base.:(==)(g::DirectProductElement, h::DirectProductElement) function Base.:(==)(g::DirectProductElement, h::DirectProductElement)
@ -88,13 +80,6 @@ end
Base.hash(g::DirectProductElement, h::UInt) = hash(g.elts, hash(parent(g), h)) Base.hash(g::DirectProductElement, h::UInt) = hash(g.elts, hash(parent(g), h))
function Base.deepcopy_internal(g::DirectProductElement, stackdict::IdDict)
return DirectProductElement(
Base.deepcopy_internal(g.elts, stackdict),
parent(g),
)
end
function Base.inv(g::DirectProductElement) function Base.inv(g::DirectProductElement)
return DirectProductElement(inv.(g.elts), parent(g)) return DirectProductElement(inv.(g.elts), parent(g))
end end
@ -104,6 +89,30 @@ function Base.:(*)(g::DirectProductElement, h::DirectProductElement)
return DirectProductElement(g.elts .* h.elts, parent(g)) return DirectProductElement(g.elts .* h.elts, parent(g))
end end
# to make sure that parents are never copied i.e.
# g and deepcopy(g) share their parent
Base.deepcopy_internal(G::DirectProduct, ::IdDict) = G
################## Implementing Group Interface Done!
# Overloading rand: the PRA of GroupsCore is known for not performing
# well on direct sums
function Random.Sampler(
RNG::Type{<:Random.AbstractRNG},
G::DirectProduct,
repetition::Random.Repetition = Val(Inf),
)
return Random.SamplerTrivial(G)
end
function Base.rand(
rng::Random.AbstractRNG,
rs::Random.SamplerTrivial{<:DirectProduct},
)
G = rs[]
return DirectProductElement((rand(rng, G.first), rand(rng, G.last)), G)
end
function GroupsCore.order(::Type{I}, g::DirectProductElement) where {I<:Integer} function GroupsCore.order(::Type{I}, g::DirectProductElement) where {I<:Integer}
return convert(I, lcm(order(I, first(g.elts)), order(I, last(g.elts)))) return convert(I, lcm(order(I, first(g.elts)), order(I, last(g.elts))))
end end
@ -111,10 +120,13 @@ end
Base.isone(g::DirectProductElement) = all(isone, g.elts) Base.isone(g::DirectProductElement) = all(isone, g.elts)
function Base.show(io::IO, G::DirectProduct) function Base.show(io::IO, G::DirectProduct)
return print(io, "Direct product of $(G.first) and $(G.last)") return print(io, "Direct product of ", G.first, " and ", G.last)
end end
function Base.show(io::IO, g::DirectProductElement) function Base.show(io::IO, g::DirectProductElement)
return print(io, "( $(join(g.elts, ",")) )") print(io, "( ")
join(io, g.elts, ", ")
return print(io, " )")
end end
# convienience: # convienience:

View File

@ -1,7 +1,4 @@
import PermutationGroups: import PermutationGroups as PG
AbstractPermutationGroup,
AbstractPermutation,
degree
""" """
WreathProduct(G::Group, P::AbstractPermutationGroup) <: Group WreathProduct(G::Group, P::AbstractPermutationGroup) <: Group
@ -16,20 +13,20 @@ product is defined as
where `m^σ` denotes the action (from the right) of the permutation `σ` on where `m^σ` denotes the action (from the right) of the permutation `σ` on
`d`-tuples of elements from `G`. `d`-tuples of elements from `G`.
""" """
struct WreathProduct{DP<:DirectPower,PGr<:AbstractPermutationGroup} <: struct WreathProduct{DP<:DirectPower,PGr<:PG.AbstractPermutationGroup} <:
GroupsCore.Group GroupsCore.Group
N::DP N::DP
P::PGr P::PGr
function WreathProduct(G::Group, P::AbstractPermutationGroup) function WreathProduct(G::Group, P::PG.AbstractPermutationGroup)
N = DirectPower{degree(P)}(G) N = DirectPower{PG.AP.degree(P)}(G)
return new{typeof(N),typeof(P)}(N, P) return new{typeof(N),typeof(P)}(N, P)
end end
end end
struct WreathProductElement{ struct WreathProductElement{
DPEl<:DirectPowerElement, DPEl<:DirectPowerElement,
PEl<:AbstractPermutation, PEl<:PG.AP.AbstractPermutation,
Wr<:WreathProduct, Wr<:WreathProduct,
} <: GroupsCore.GroupElement } <: GroupsCore.GroupElement
n::DPEl n::DPEl
@ -38,7 +35,7 @@ struct WreathProductElement{
function WreathProductElement( function WreathProductElement(
n::DirectPowerElement, n::DirectPowerElement,
p::AbstractPermutation, p::PG.AP.AbstractPermutation,
W::WreathProduct, W::WreathProduct,
) )
return new{typeof(n),typeof(p),typeof(W)}(n, p, W) return new{typeof(n),typeof(p),typeof(W)}(n, p, W)
@ -97,14 +94,6 @@ end
Base.isfinite(G::WreathProduct) = isfinite(G.N) && isfinite(G.P) Base.isfinite(G::WreathProduct) = isfinite(G.N) && isfinite(G.P)
function Base.rand(
rng::Random.AbstractRNG,
rs::Random.SamplerTrivial{<:WreathProduct},
)
G = rs[]
return WreathProductElement(rand(rng, G.N), rand(rng, G.P), G)
end
GroupsCore.parent(g::WreathProductElement) = g.parent GroupsCore.parent(g::WreathProductElement) = g.parent
function Base.:(==)(g::WreathProductElement, h::WreathProductElement) function Base.:(==)(g::WreathProductElement, h::WreathProductElement)
@ -115,15 +104,7 @@ function Base.hash(g::WreathProductElement, h::UInt)
return hash(g.n, hash(g.p, hash(g.parent, h))) return hash(g.n, hash(g.p, hash(g.parent, h)))
end end
function Base.deepcopy_internal(g::WreathProductElement, stackdict::IdDict) function _act(p::PG.AP.AbstractPermutation, n::DirectPowerElement)
return WreathProductElement(
Base.deepcopy_internal(g.n, stackdict),
Base.deepcopy_internal(g.p, stackdict),
parent(g),
)
end
function _act(p::AbstractPermutation, n::DirectPowerElement)
return DirectPowerElement( return DirectPowerElement(
ntuple(i -> n.elts[i^p], length(n.elts)), ntuple(i -> n.elts[i^p], length(n.elts)),
parent(n), parent(n),
@ -140,11 +121,36 @@ function Base.:(*)(g::WreathProductElement, h::WreathProductElement)
return WreathProductElement(g.n * _act(g.p, h.n), g.p * h.p, parent(g)) return WreathProductElement(g.n * _act(g.p, h.n), g.p * h.p, parent(g))
end end
# to make sure that parents are never copied i.e.
# g and deepcopy(g) share their parent
Base.deepcopy_internal(G::WreathProduct, ::IdDict) = G
################## Implementing Group Interface Done!
# Overloading rand: the PRA of GroupsCore is known for not performing
# well on direct sums
function Random.Sampler(
RNG::Type{<:Random.AbstractRNG},
G::WreathProduct,
repetition::Random.Repetition = Val(Inf),
)
return Random.SamplerTrivial(G)
end
function Base.rand(
rng::Random.AbstractRNG,
rs::Random.SamplerTrivial{<:WreathProduct},
)
G = rs[]
return WreathProductElement(rand(rng, G.N), rand(rng, G.P), G)
end
Base.isone(g::WreathProductElement) = isone(g.n) && isone(g.p) Base.isone(g::WreathProductElement) = isone(g.n) && isone(g.p)
function Base.show(io::IO, G::WreathProduct) function Base.show(io::IO, G::WreathProduct)
return print(io, "Wreath product of $(G.N.group) by $(G.P)") return print(io, "Wreath product of ", G.N.group, " by ", G.P)
end end
Base.show(io::IO, g::WreathProductElement) = print(io, "( $(g.n)$(g.p) )")
Base.copy(g::WreathProductElement) = WreathProductElement(g.n, g.p, parent(g)) function Base.show(io::IO, g::WreathProductElement)
return print(io, "( ", g.n, "", g.p, " )")
end

View File

@ -2,8 +2,10 @@
normalform!(g::FPGroupElement) normalform!(g::FPGroupElement)
Compute the normal form of `g`, possibly modifying `g` in-place. Compute the normal form of `g`, possibly modifying `g` in-place.
""" """
@inline function normalform!(g::AbstractFPGroupElement) @inline function normalform!(g::AbstractFPGroupElement; force = false)
isnormalform(g) && return g if !force
isnormalform(g) && return g
end
let w = one(word(g)) let w = one(word(g))
w = normalform!(w, g) w = normalform!(w, g)
@ -36,9 +38,9 @@ end
""" """
normalform!(res::AbstractWord, g::FPGroupElement) normalform!(res::AbstractWord, g::FPGroupElement)
Append the normal form of `g` to word `res`, modifying `res` in place. Write the normal form of `g` to word `res`, modifying `res` in place.
Defaults to the rewriting in the free group. The particular implementation of the normal form depends on `parent(g)`.
""" """
@inline function normalform!(res::AbstractWord, g::AbstractFPGroupElement) @inline function normalform!(res::AbstractWord, g::AbstractFPGroupElement)
isone(res) && isnormalform(g) && return append!(res, word(g)) isone(res) && isnormalform(g) && return append!(res, word(g))

62
src/rand.jl Normal file
View File

@ -0,0 +1,62 @@
"""
PoissonSampler
For a finitely presented group PoissonSampler returns group elements represented
by words of length at most `R ~ Poisson(λ)` chosen uniformly at random.
For finitely presented groups the Product Replacement Algorithm
(see `PRASampler` from `GroupsCore.jl`) doesn't make much sense due to
overly long words it produces. We therefore resort to a pseudo-random method,
where a word `w` of length `R` is chosen uniformly at random among all
words of length `R` where `R` follows the Poisson distribution.
!!! note
Due to the choice of the parameters (`λ=8`) and the floating point
arithmetic the sampler will always return group elements represented by
words of length at most `42`.
"""
struct PoissonSampler{G,T} <: Random.Sampler{T}
group::G
λ::Int
end
function PoissonSampler(G::AbstractFPGroup; λ)
return PoissonSampler{typeof(G),eltype(G)}(G, λ)
end
function __poisson_invcdf(val; λ)
# __poisson_pdf(k, λ) = λ^k * ^-λ / factorial(k)
# pdf = ntuple(k -> __poisson_pdf(k - 1, λ), 21)
# cdf = accumulate(+, pdf)
# radius = something(findfirst(>(val), cdf) - 1, 0)
# this is the iterative version:
pdf = ^-λ
cdf = pdf
k = 0
while cdf < val
k += 1
pdf = pdf * λ / k
cdf += pdf
end
return k
end
function Random.rand(rng::Random.AbstractRNG, sampler::PoissonSampler)
R = __poisson_invcdf(rand(rng); λ = sampler.λ)
G = sampler.group
n = length(alphabet(G))
W = word_type(G)
T = eltype(W)
letters = rand(rng, T(1):T(n), R)
word = W(letters, false)
return G(word)
end
function Random.Sampler(
RNG::Type{<:Random.AbstractRNG},
G::AbstractFPGroup,
repetition::Random.Repetition = Val(Inf),
)
return PoissonSampler(G; λ = 8)
end

View File

@ -67,17 +67,6 @@ function GroupsCore.gens(G::AbstractFPGroup)
return [gens(G, i) for i in 1:GroupsCore.ngens(G)] return [gens(G, i) for i in 1:GroupsCore.ngens(G)]
end end
# TODO: ProductReplacementAlgorithm
function Base.rand(
rng::Random.AbstractRNG,
rs::Random.SamplerTrivial{<:AbstractFPGroup},
)
l = rand(10:100)
G = rs[]
nletters = length(alphabet(G))
return FPGroupElement(word_type(G)(rand(1:nletters, l)), G)
end
function Base.isfinite(::AbstractFPGroup) function Base.isfinite(::AbstractFPGroup)
return ( return (
@warn "using generic isfinite(::AbstractFPGroup): the returned `false` might be wrong"; false @warn "using generic isfinite(::AbstractFPGroup): the returned `false` might be wrong"; false
@ -110,10 +99,6 @@ mutable struct FPGroupElement{Gr<:AbstractFPGroup,W<:AbstractWord} <:
end end
end end
function Base.show(io::IO, ::Type{<:FPGroupElement{Gr}}) where {Gr}
return print(io, FPGroupElement, "{$Gr, …}")
end
function Base.copy(f::FPGroupElement) function Base.copy(f::FPGroupElement)
return FPGroupElement(copy(word(f)), parent(f), f.savedhash) return FPGroupElement(copy(word(f)), parent(f), f.savedhash)
end end
@ -134,18 +119,33 @@ function Base.:(==)(g::AbstractFPGroupElement, h::AbstractFPGroupElement)
@boundscheck @assert parent(g) === parent(h) @boundscheck @assert parent(g) === parent(h)
normalform!(g) normalform!(g)
normalform!(h) normalform!(h)
# I. compare hashes of the normalform
# II. compare some data associated to FPGroupElement,
# e.g. word, image of the domain etc.
hash(g) != hash(h) && return false hash(g) != hash(h) && return false
return equality_data(g) == equality_data(h) equality_data(g) == equality_data(h) && return true # compares
# if this failed it is still possible that the words together can be
# rewritten even further, so we
# 1. rewrite word(g⁻¹·h) w.r.t. rewriting(parent(g))
# 2. check if the result is empty
G = parent(g)
g⁻¹h = append!(inv(word(g), alphabet(G)), word(h))
# similar + empty preserve the storage size
# saves some re-allocations if res does not represent id
res = similar(word(g))
resize!(res, 0)
res = KnuthBendix.rewrite!(res, g⁻¹h, rewriting(G))
return isone(res)
end end
function Base.deepcopy_internal(g::FPGroupElement, stackdict::IdDict) function Base.deepcopy_internal(g::FPGroupElement, stackdict::IdDict)
haskey(stackdict, objectid(g)) && return stackdict[objectid(g)] haskey(stackdict, g) && return stackdict[g]
cw = if haskey(stackdict, objectid(word(g))) cw = Base.deepcopy_internal(word(g), stackdict)
stackdict[objectid(word(g))] h = FPGroupElement(cw, parent(g), g.savedhash)
else stackdict[g] = h
copy(word(g)) return h
end
return FPGroupElement(cw, parent(g), g.savedhash)
end end
function Base.inv(g::GEl) where {GEl<:AbstractFPGroupElement} function Base.inv(g::GEl) where {GEl<:AbstractFPGroupElement}
@ -192,9 +192,16 @@ struct FreeGroup{T,O} <: AbstractFPGroup
end end
end end
FreeGroup(gens, A::Alphabet) = FreeGroup(gens, KnuthBendix.LenLex(A)) function FreeGroup(n::Integer)
symbols =
collect(Iterators.flatten((Symbol(:f, i), Symbol(:F, i)) for i in 1:n))
inverses = collect(Iterators.flatten((2i, 2i - 1) for i in 1:n))
return FreeGroup(Alphabet(symbols, inverses))
end
function FreeGroup(A::Alphabet) FreeGroup(A::Alphabet) = FreeGroup(KnuthBendix.LenLex(A))
function __group_gens(A::Alphabet)
@boundscheck @assert all(KnuthBendix.hasinverse(l, A) for l in A) @boundscheck @assert all(KnuthBendix.hasinverse(l, A) for l in A)
gens = Vector{eltype(A)}() gens = Vector{eltype(A)}()
invs = Vector{eltype(A)}() invs = Vector{eltype(A)}()
@ -203,20 +210,12 @@ function FreeGroup(A::Alphabet)
push!(gens, l) push!(gens, l)
push!(invs, inv(l, A)) push!(invs, inv(l, A))
end end
return gens
return FreeGroup(gens, A)
end end
function FreeGroup(n::Integer) function FreeGroup(O::KnuthBendix.WordOrdering)
symbols = Symbol[] grp_gens = __group_gens(alphabet(O))
inverses = Int[] return FreeGroup(grp_gens, O)
sizehint!(symbols, 2n)
sizehint!(inverses, 2n)
for i in 1:n
push!(symbols, Symbol(:f, i), Symbol(:F, i))
push!(inverses, 2i, 2i - 1)
end
return FreeGroup(symbols[1:2:2n], Alphabet(symbols, inverses))
end end
function Base.show(io::IO, F::FreeGroup) function Base.show(io::IO, F::FreeGroup)
@ -265,10 +264,18 @@ function FPGroup(
end end
function Base.show(io::IO, ::MIME"text/plain", G::FPGroup) function Base.show(io::IO, ::MIME"text/plain", G::FPGroup)
print(io, "Finitely presented group generated by:\n\t{") println(
Base.print_array(io, permutedims(gens(G))) io,
println(io, " },") "Finitely presented group generated by $(ngens(G)) element",
println(io, "subject to relations:") ngens(G) > 1 ? 's' : "",
": ",
)
join(io, gens(G), ", ", ", and ")
println(
io,
"\n subject to relation",
length(relations(G)) > 1 ? 's' : "",
)
return Base.print_array(io, relations(G)) return Base.print_array(io, relations(G))
end end

View File

@ -38,7 +38,7 @@
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9] [2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
) )
F4 = FreeGroup([:a, :b, :c, :d], A4) F4 = FreeGroup(A4)
a, b, c, d = gens(F4) a, b, c, d = gens(F4)
D = ntuple(i -> gens(F4, i), 4) D = ntuple(i -> gens(F4, i), 4)

View File

@ -4,7 +4,7 @@
@test FreeGroup(A) isa FreeGroup @test FreeGroup(A) isa FreeGroup
@test sprint(show, FreeGroup(A)) == "free group on 3 generators" @test sprint(show, FreeGroup(A)) == "free group on 3 generators"
F = FreeGroup([:a, :b, :c], A) F = FreeGroup([:a, :b, :c], Groups.KnuthBendix.LenLex(A))
@test sprint(show, F) == "free group on 3 generators" @test sprint(show, F) == "free group on 3 generators"
a, b, c = gens(F) a, b, c = gens(F)

View File

@ -1,7 +1,7 @@
@testset "FreeGroup" begin @testset "FreeGroup" begin
A3 = Alphabet([:a, :b, :c, :A, :B, :C], [4,5,6,1,2,3]) A3 = Alphabet([:a, :b, :c, :A, :B, :C], [4,5,6,1,2,3])
F3 = FreeGroup([:a, :b, :c], A3) F3 = FreeGroup(A3)
@test F3 isa FreeGroup @test F3 isa FreeGroup
@test gens(F3) isa Vector @test gens(F3) isa Vector