mirror of
https://github.com/kalmarek/Groups.jl.git
synced 2024-11-30 17:20:27 +01:00
Compare commits
12 Commits
866e431c1a
...
1fbd7b875b
Author | SHA1 | Date | |
---|---|---|---|
|
1fbd7b875b | ||
c13226a625 | |||
|
7776ac4c6e | ||
6ca9497dab | |||
f4d018f087 | |||
add9a6f287 | |||
ba6d58ec77 | |||
a33b871754 | |||
efbb4eada8 | |||
148a472dd2 | |||
5993cb328f | |||
8137e40998 |
@ -1,7 +1,7 @@
|
||||
name = "Groups"
|
||||
uuid = "5d8bd718-bd84-11e8-3b40-ad14f4a32557"
|
||||
authors = ["Marek Kaluba <kalmar@amu.edu.pl>"]
|
||||
version = "0.7.8"
|
||||
version = "0.8"
|
||||
|
||||
[deps]
|
||||
GroupsCore = "d5909c97-4eac-4ecc-a3dc-fdd0858a4120"
|
||||
@ -14,10 +14,10 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
|
||||
|
||||
[compat]
|
||||
GroupsCore = "0.4"
|
||||
GroupsCore = "0.5"
|
||||
KnuthBendix = "0.4"
|
||||
OrderedCollections = "1"
|
||||
PermutationGroups = "0.4"
|
||||
PermutationGroups = "0.6"
|
||||
StaticArrays = "1"
|
||||
julia = "1.6"
|
||||
|
||||
|
@ -27,6 +27,7 @@ include(joinpath("constructions", "constructions.jl"))
|
||||
import .Constructions
|
||||
|
||||
include("types.jl")
|
||||
include("rand.jl")
|
||||
include("hashing.jl")
|
||||
include("normalform.jl")
|
||||
include("autgroups.jl")
|
||||
|
@ -36,8 +36,8 @@ function Base.:(==)(
|
||||
hash(g) != hash(h) && return false
|
||||
end
|
||||
|
||||
length(word(g)) > 8 && normalform!(g)
|
||||
length(word(h)) > 8 && normalform!(h)
|
||||
normalform!(g)
|
||||
normalform!(h)
|
||||
|
||||
word(g) == word(h) && return true
|
||||
|
||||
@ -138,43 +138,47 @@ end
|
||||
|
||||
# forward evaluate by substitution
|
||||
|
||||
struct LettersMap{T,A}
|
||||
indices_map::Dict{Int,T}
|
||||
struct LettersMap{W<:AbstractWord,A}
|
||||
indices_map::Dict{Int,W}
|
||||
A::A
|
||||
end
|
||||
|
||||
function LettersMap(a::FPGroupElement{<:AutomorphismGroup})
|
||||
dom = domain(a)
|
||||
@assert all(isone ∘ length ∘ word, dom)
|
||||
A = alphabet(first(dom))
|
||||
first_letters = first.(word.(dom))
|
||||
img = evaluate!(dom, a)
|
||||
if all(isone ∘ length ∘ word, dom)
|
||||
A = alphabet(first(dom))
|
||||
first_letters = first.(word.(dom))
|
||||
img = evaluate!(dom, a)
|
||||
|
||||
# (dom[i] → img[i] is a map from domain to images)
|
||||
# we need a map from alphabet indices → (gens, gens⁻¹) → images
|
||||
# here we do it for elements of the domain
|
||||
# (trusting it's a set of generators that define a)
|
||||
@assert length(dom) == length(img)
|
||||
# (dom[i] → img[i] is a map from domain to images)
|
||||
# we need a map from alphabet indices → (gens, gens⁻¹) → images
|
||||
# here we do it for elements of the domain
|
||||
# (trusting it's a set of generators that define a)
|
||||
@assert length(dom) == length(img)
|
||||
|
||||
indices_map =
|
||||
Dict(A[A[fl]] => word(im) for (fl, im) in zip(first_letters, img))
|
||||
# inverses of generators are dealt lazily in getindex
|
||||
indices_map =
|
||||
Dict(Int(fl) => word(im) for (fl, im) in zip(first_letters, img))
|
||||
# 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)
|
||||
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
|
||||
@boundscheck 1 ≤ i ≤ length(lm.A)
|
||||
|
||||
if !haskey(lm.indices_map, i)
|
||||
img = if haskey(lm.indices_map, inv(i, lm.A))
|
||||
inv(lm.indices_map[inv(i, lm.A)], lm.A)
|
||||
I = inv(i, lm.A)
|
||||
if haskey(lm.indices_map, I)
|
||||
img = inv(lm.indices_map[I], lm.A)
|
||||
lm.indices_map[i] = img
|
||||
else
|
||||
@warn "LetterMap: neither $i nor its inverse has assigned value"
|
||||
one(valtype(lm.indices_map))
|
||||
lm.indices_map[i] = W([i])
|
||||
lm.indices_map[I] = W([I])
|
||||
end
|
||||
lm.indices_map[i] = img
|
||||
end
|
||||
return lm.indices_map[i]
|
||||
end
|
||||
@ -185,9 +189,10 @@ function (a::FPGroupElement{<:AutomorphismGroup})(g::FPGroupElement)
|
||||
return parent(g)(img_w)
|
||||
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)
|
||||
resize!(res, 0)
|
||||
for i in w
|
||||
append!(res, lm[i])
|
||||
end
|
||||
|
@ -59,15 +59,6 @@ end
|
||||
|
||||
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)
|
||||
N = _nfold(G)
|
||||
S = gens(G.group)
|
||||
@ -78,14 +69,6 @@ end
|
||||
|
||||
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
|
||||
|
||||
function Base.:(==)(g::DirectPowerElement, h::DirectPowerElement)
|
||||
@ -94,13 +77,6 @@ end
|
||||
|
||||
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))
|
||||
|
||||
function Base.:(*)(g::DirectPowerElement, h::DirectPowerElement)
|
||||
@ -108,6 +84,39 @@ function Base.:(*)(g::DirectPowerElement, h::DirectPowerElement)
|
||||
return DirectPowerElement(g.elts .* h.elts, parent(g))
|
||||
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}
|
||||
return convert(I, reduce(lcm, (order(I, h) for h in g.elts); init = one(I)))
|
||||
end
|
||||
@ -117,10 +126,13 @@ Base.isone(g::DirectPowerElement) = all(isone, g.elts)
|
||||
function Base.show(io::IO, G::DirectPower)
|
||||
n = _nfold(G)
|
||||
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
|
||||
|
||||
function Base.show(io::IO, g::DirectPowerElement)
|
||||
return print(io, "( ", join(g.elts, ", "), " )")
|
||||
print(io, "( ")
|
||||
join(io, g.elts, ", ")
|
||||
return print(" )")
|
||||
end
|
||||
|
||||
# convienience:
|
||||
|
@ -72,14 +72,6 @@ end
|
||||
|
||||
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
|
||||
|
||||
function Base.:(==)(g::DirectProductElement, h::DirectProductElement)
|
||||
@ -88,13 +80,6 @@ end
|
||||
|
||||
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)
|
||||
return DirectProductElement(inv.(g.elts), parent(g))
|
||||
end
|
||||
@ -104,6 +89,30 @@ function Base.:(*)(g::DirectProductElement, h::DirectProductElement)
|
||||
return DirectProductElement(g.elts .* h.elts, parent(g))
|
||||
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}
|
||||
return convert(I, lcm(order(I, first(g.elts)), order(I, last(g.elts))))
|
||||
end
|
||||
@ -111,10 +120,13 @@ end
|
||||
Base.isone(g::DirectProductElement) = all(isone, g.elts)
|
||||
|
||||
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
|
||||
|
||||
function Base.show(io::IO, g::DirectProductElement)
|
||||
return print(io, "( $(join(g.elts, ",")) )")
|
||||
print(io, "( ")
|
||||
join(io, g.elts, ", ")
|
||||
return print(io, " )")
|
||||
end
|
||||
|
||||
# convienience:
|
||||
|
@ -1,7 +1,4 @@
|
||||
import PermutationGroups:
|
||||
AbstractPermutationGroup,
|
||||
AbstractPermutation,
|
||||
degree
|
||||
import PermutationGroups as PG
|
||||
|
||||
"""
|
||||
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
|
||||
`d`-tuples of elements from `G`.
|
||||
"""
|
||||
struct WreathProduct{DP<:DirectPower,PGr<:AbstractPermutationGroup} <:
|
||||
struct WreathProduct{DP<:DirectPower,PGr<:PG.AbstractPermutationGroup} <:
|
||||
GroupsCore.Group
|
||||
N::DP
|
||||
P::PGr
|
||||
|
||||
function WreathProduct(G::Group, P::AbstractPermutationGroup)
|
||||
N = DirectPower{degree(P)}(G)
|
||||
function WreathProduct(G::Group, P::PG.AbstractPermutationGroup)
|
||||
N = DirectPower{PG.AP.degree(P)}(G)
|
||||
return new{typeof(N),typeof(P)}(N, P)
|
||||
end
|
||||
end
|
||||
|
||||
struct WreathProductElement{
|
||||
DPEl<:DirectPowerElement,
|
||||
PEl<:AbstractPermutation,
|
||||
PEl<:PG.AP.AbstractPermutation,
|
||||
Wr<:WreathProduct,
|
||||
} <: GroupsCore.GroupElement
|
||||
n::DPEl
|
||||
@ -38,7 +35,7 @@ struct WreathProductElement{
|
||||
|
||||
function WreathProductElement(
|
||||
n::DirectPowerElement,
|
||||
p::AbstractPermutation,
|
||||
p::PG.AP.AbstractPermutation,
|
||||
W::WreathProduct,
|
||||
)
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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)))
|
||||
end
|
||||
|
||||
function Base.deepcopy_internal(g::WreathProductElement, stackdict::IdDict)
|
||||
return WreathProductElement(
|
||||
Base.deepcopy_internal(g.n, stackdict),
|
||||
Base.deepcopy_internal(g.p, stackdict),
|
||||
parent(g),
|
||||
)
|
||||
end
|
||||
|
||||
function _act(p::AbstractPermutation, n::DirectPowerElement)
|
||||
function _act(p::PG.AP.AbstractPermutation, n::DirectPowerElement)
|
||||
return DirectPowerElement(
|
||||
ntuple(i -> n.elts[i^p], length(n.elts)),
|
||||
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))
|
||||
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)
|
||||
|
||||
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
|
||||
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
|
||||
|
@ -2,8 +2,10 @@
|
||||
normalform!(g::FPGroupElement)
|
||||
Compute the normal form of `g`, possibly modifying `g` in-place.
|
||||
"""
|
||||
@inline function normalform!(g::AbstractFPGroupElement)
|
||||
isnormalform(g) && return g
|
||||
@inline function normalform!(g::AbstractFPGroupElement; force = false)
|
||||
if !force
|
||||
isnormalform(g) && return g
|
||||
end
|
||||
|
||||
let w = one(word(g))
|
||||
w = normalform!(w, g)
|
||||
@ -36,9 +38,9 @@ end
|
||||
|
||||
"""
|
||||
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)
|
||||
isone(res) && isnormalform(g) && return append!(res, word(g))
|
||||
|
62
src/rand.jl
Normal file
62
src/rand.jl
Normal 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
|
89
src/types.jl
89
src/types.jl
@ -67,17 +67,6 @@ function GroupsCore.gens(G::AbstractFPGroup)
|
||||
return [gens(G, i) for i in 1:GroupsCore.ngens(G)]
|
||||
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)
|
||||
return (
|
||||
@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
|
||||
|
||||
function Base.show(io::IO, ::Type{<:FPGroupElement{Gr}}) where {Gr}
|
||||
return print(io, FPGroupElement, "{$Gr, …}")
|
||||
end
|
||||
|
||||
function Base.copy(f::FPGroupElement)
|
||||
return FPGroupElement(copy(word(f)), parent(f), f.savedhash)
|
||||
end
|
||||
@ -134,18 +119,33 @@ function Base.:(==)(g::AbstractFPGroupElement, h::AbstractFPGroupElement)
|
||||
@boundscheck @assert parent(g) === parent(h)
|
||||
normalform!(g)
|
||||
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
|
||||
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
|
||||
|
||||
function Base.deepcopy_internal(g::FPGroupElement, stackdict::IdDict)
|
||||
haskey(stackdict, objectid(g)) && return stackdict[objectid(g)]
|
||||
cw = if haskey(stackdict, objectid(word(g)))
|
||||
stackdict[objectid(word(g))]
|
||||
else
|
||||
copy(word(g))
|
||||
end
|
||||
return FPGroupElement(cw, parent(g), g.savedhash)
|
||||
haskey(stackdict, g) && return stackdict[g]
|
||||
cw = Base.deepcopy_internal(word(g), stackdict)
|
||||
h = FPGroupElement(cw, parent(g), g.savedhash)
|
||||
stackdict[g] = h
|
||||
return h
|
||||
end
|
||||
|
||||
function Base.inv(g::GEl) where {GEl<:AbstractFPGroupElement}
|
||||
@ -192,9 +192,16 @@ struct FreeGroup{T,O} <: AbstractFPGroup
|
||||
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)
|
||||
gens = Vector{eltype(A)}()
|
||||
invs = Vector{eltype(A)}()
|
||||
@ -203,20 +210,12 @@ function FreeGroup(A::Alphabet)
|
||||
push!(gens, l)
|
||||
push!(invs, inv(l, A))
|
||||
end
|
||||
|
||||
return FreeGroup(gens, A)
|
||||
return gens
|
||||
end
|
||||
|
||||
function FreeGroup(n::Integer)
|
||||
symbols = Symbol[]
|
||||
inverses = Int[]
|
||||
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))
|
||||
function FreeGroup(O::KnuthBendix.WordOrdering)
|
||||
grp_gens = __group_gens(alphabet(O))
|
||||
return FreeGroup(grp_gens, O)
|
||||
end
|
||||
|
||||
function Base.show(io::IO, F::FreeGroup)
|
||||
@ -265,10 +264,18 @@ function FPGroup(
|
||||
end
|
||||
|
||||
function Base.show(io::IO, ::MIME"text/plain", G::FPGroup)
|
||||
print(io, "Finitely presented group generated by:\n\t{")
|
||||
Base.print_array(io, permutedims(gens(G)))
|
||||
println(io, " },")
|
||||
println(io, "subject to relations:")
|
||||
println(
|
||||
io,
|
||||
"Finitely presented group generated by $(ngens(G)) element",
|
||||
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))
|
||||
end
|
||||
|
||||
|
@ -38,7 +38,7 @@
|
||||
[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)
|
||||
D = ntuple(i -> gens(F4, i), 4)
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
@test FreeGroup(A) isa FreeGroup
|
||||
@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"
|
||||
|
||||
a, b, c = gens(F)
|
||||
|
@ -1,7 +1,7 @@
|
||||
@testset "FreeGroup" begin
|
||||
|
||||
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 gens(F3) isa Vector
|
||||
|
Loading…
Reference in New Issue
Block a user