Compare commits
21 Commits
Author | SHA1 | Date |
---|---|---|
Marek Kaluba | 1fbd7b875b | |
Marek Kaluba | c13226a625 | |
Marek Kaluba | 7776ac4c6e | |
Marek Kaluba | 6ca9497dab | |
Marek Kaluba | f4d018f087 | |
Marek Kaluba | add9a6f287 | |
Marek Kaluba | ba6d58ec77 | |
Marek Kaluba | a33b871754 | |
Marek Kaluba | efbb4eada8 | |
Marek Kaluba | 148a472dd2 | |
Marek Kaluba | 5993cb328f | |
Marek Kaluba | 8137e40998 | |
Marek Kaluba | 866e431c1a | |
Marek Kaluba | 22cf6297a9 | |
Marek Kaluba | 1a51a87771 | |
Marek Kaluba | d385992e92 | |
Marek Kaluba | c8805d6890 | |
Marek Kaluba | 3260e66d37 | |
Marek Kaluba | eacb32af68 | |
Marek Kaluba | 126c8bbc22 | |
Marek Kaluba | 93a841359b |
|
@ -1,7 +1,7 @@
|
|||
name = "Groups"
|
||||
uuid = "5d8bd718-bd84-11e8-3b40-ad14f4a32557"
|
||||
authors = ["Marek Kaluba <kalmar@amu.edu.pl>"]
|
||||
version = "0.7.6"
|
||||
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.3"
|
||||
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")
|
||||
|
|
|
@ -14,12 +14,7 @@ end
|
|||
λ(i, j) = Transvection(:λ, i, j)
|
||||
|
||||
function Base.show(io::IO, t::Transvection)
|
||||
id = if t.id === :ϱ
|
||||
'ϱ'
|
||||
else # if t.id === :λ
|
||||
'λ'
|
||||
end
|
||||
print(io, id, subscriptify(t.i), '.', subscriptify(t.j))
|
||||
print(io, t.id, subscriptify(t.i), '.', subscriptify(t.j))
|
||||
return t.inv && print(io, "^-1")
|
||||
end
|
||||
|
||||
|
|
|
@ -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,5 +1,4 @@
|
|||
import PermutationGroups:
|
||||
AbstractPermutationGroup, AbstractPerm, degree, SymmetricGroup
|
||||
import PermutationGroups as PG
|
||||
|
||||
"""
|
||||
WreathProduct(G::Group, P::AbstractPermutationGroup) <: Group
|
||||
|
@ -14,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<:AbstractPerm,
|
||||
PEl<:PG.AP.AbstractPermutation,
|
||||
Wr<:WreathProduct,
|
||||
} <: GroupsCore.GroupElement
|
||||
n::DPEl
|
||||
|
@ -36,7 +35,7 @@ struct WreathProductElement{
|
|||
|
||||
function WreathProductElement(
|
||||
n::DirectPowerElement,
|
||||
p::AbstractPerm,
|
||||
p::PG.AP.AbstractPermutation,
|
||||
W::WreathProduct,
|
||||
)
|
||||
return new{typeof(n),typeof(p),typeof(W)}(n, p, W)
|
||||
|
@ -53,16 +52,19 @@ function Base.iterate(G::WreathProduct)
|
|||
itr = Iterators.product(G.N, G.P)
|
||||
res = iterate(itr)
|
||||
@assert res !== nothing
|
||||
elt = WreathProductElement(first(res)..., G)
|
||||
return elt, (iterator = itr, state = last(res))
|
||||
ab, st = res
|
||||
(a, b) = ab
|
||||
elt = WreathProductElement(a, b, G)
|
||||
return elt, (itr, st)
|
||||
end
|
||||
|
||||
function Base.iterate(G::WreathProduct, state)
|
||||
itr, st = state.iterator, state.state
|
||||
itr, st = state
|
||||
res = iterate(itr, st)
|
||||
res === nothing && return nothing
|
||||
elt = WreathProductElement(first(res)..., G)
|
||||
return elt, (iterator = itr, state = last(res))
|
||||
(a::eltype(G.N), b::eltype(G.P)), st = res
|
||||
elt = WreathProductElement(a, b, G)
|
||||
return elt, (itr, st)
|
||||
end
|
||||
|
||||
function Base.IteratorSize(::Type{<:WreathProduct{DP,PGr}}) where {DP,PGr}
|
||||
|
@ -92,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)
|
||||
|
@ -110,18 +104,13 @@ 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),
|
||||
function _act(p::PG.AP.AbstractPermutation, n::DirectPowerElement)
|
||||
return DirectPowerElement(
|
||||
ntuple(i -> n.elts[i^p], length(n.elts)),
|
||||
parent(n),
|
||||
)
|
||||
end
|
||||
|
||||
function _act(p::AbstractPerm, n::DirectPowerElement)
|
||||
return DirectPowerElement(n.elts^p, parent(n))
|
||||
end
|
||||
|
||||
function Base.inv(g::WreathProductElement)
|
||||
pinv = inv(g.p)
|
||||
return WreathProductElement(_act(pinv, inv(g.n)), pinv, parent(g))
|
||||
|
@ -132,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
|
||||
|
|
|
@ -37,6 +37,7 @@ function _update_savedhash!(g::AbstractFPGroupElement, data)
|
|||
end
|
||||
|
||||
function Base.hash(g::AbstractFPGroupElement, h::UInt)
|
||||
g = normalform!(g)
|
||||
_isvalidhash(g) || _update_savedhash!(g, equality_data(g))
|
||||
return hash(g.savedhash >> count_ones(__BITFLAGS_MASK), h)
|
||||
end
|
||||
|
|
|
@ -23,7 +23,7 @@ end
|
|||
|
||||
function Base.show(io::IO, s::ElementarySymplectic)
|
||||
i, j = Groups.subscriptify(s.i), Groups.subscriptify(s.j)
|
||||
print(io, s.symbol, i, j)
|
||||
print(io, s.symbol, i, '.', j)
|
||||
return !isone(s.val) && print(io, "^$(s.val)")
|
||||
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))
|
||||
|
|
|
@ -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
|
88
src/types.jl
88
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
|
||||
|
@ -88,6 +77,9 @@ end
|
|||
|
||||
abstract type AbstractFPGroupElement{Gr} <: GroupElement end
|
||||
|
||||
Base.copy(g::AbstractFPGroupElement) = one(g) * g
|
||||
word(f::AbstractFPGroupElement) = f.word
|
||||
|
||||
mutable struct FPGroupElement{Gr<:AbstractFPGroup,W<:AbstractWord} <:
|
||||
AbstractFPGroupElement{Gr}
|
||||
word::W
|
||||
|
@ -107,12 +99,10 @@ mutable struct FPGroupElement{Gr<:AbstractFPGroup,W<:AbstractWord} <:
|
|||
end
|
||||
end
|
||||
|
||||
function Base.show(io::IO, ::Type{<:FPGroupElement{Gr}}) where {Gr}
|
||||
return print(io, FPGroupElement, "{$Gr, …}")
|
||||
function Base.copy(f::FPGroupElement)
|
||||
return FPGroupElement(copy(word(f)), parent(f), f.savedhash)
|
||||
end
|
||||
|
||||
word(f::AbstractFPGroupElement) = f.word
|
||||
|
||||
#convenience
|
||||
KnuthBendix.alphabet(g::AbstractFPGroupElement) = alphabet(parent(g))
|
||||
|
||||
|
@ -129,12 +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)
|
||||
return FPGroupElement(copy(word(g)), 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}
|
||||
|
@ -181,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)}()
|
||||
|
@ -192,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)
|
||||
|
@ -254,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
|
||||
|
||||
|
|
|
@ -26,8 +26,7 @@ function wlmetric_ball(
|
|||
new = collect(
|
||||
op(o, s) for o in @view(ball[sizes[end-1]:end]) for s in S
|
||||
)
|
||||
append!(ball, new)
|
||||
unique!(ball)
|
||||
ball = union!(ball, new)
|
||||
push!(sizes, length(ball))
|
||||
end
|
||||
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)
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
@test contains(sprint(print, π₁Σ), "surface")
|
||||
|
||||
Groups.PermRightAut(p::Perm) = Groups.PermRightAut(p.d)
|
||||
Groups.PermRightAut(p::Perm) = Groups.PermRightAut([i^p for i in 1:2genus])
|
||||
# Groups.PermLeftAut(p::Perm) = Groups.PermLeftAut(p.d)
|
||||
autπ₁Σ = let autπ₁Σ = AutomorphismGroup(π₁Σ)
|
||||
pauts = let p = perm"(1,3,5)(2,4,6)"
|
||||
|
@ -50,8 +50,9 @@
|
|||
@test π₁Σ.(word.(z)) == Groups.domain(first(S))
|
||||
d = Groups.domain(first(S))
|
||||
p = perm"(1,3,5)(2,4,6)"
|
||||
@test Groups.evaluate!(deepcopy(d), τ) == d^inv(p)
|
||||
@test Groups.evaluate!(deepcopy(d), τ^2) == d^p
|
||||
@test Groups.evaluate!(deepcopy(d), τ) ==
|
||||
ntuple(i -> d[i^inv(p)], length(d))
|
||||
@test Groups.evaluate!(deepcopy(d), τ^2) == ntuple(i -> d[i^p], length(d))
|
||||
|
||||
E, sizes = Groups.wlmetric_ball(S, radius=3)
|
||||
@test sizes == [49, 1813, 62971]
|
||||
|
|
|
@ -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)
|
||||
|
@ -14,7 +14,8 @@
|
|||
G = FPGroup(F, [a * b => b * a, a * c => c * a, b * c => c * b])
|
||||
|
||||
@test G isa FPGroup
|
||||
@test sprint(show, G) == "⟨ a b c | \n\t a*b => b*a a*c => c*a b*c => c*b ⟩"
|
||||
@test sprint(show, G) ==
|
||||
"⟨ a b c | \n\t a*b => b*a a*c => c*a b*c => c*b ⟩"
|
||||
@test rand(G) isa FPGroupElement
|
||||
|
||||
f = a * c * b
|
||||
|
@ -40,7 +41,7 @@
|
|||
end
|
||||
|
||||
# quotient of G
|
||||
H = FPGroup(G, [aG^2 => cG, bG * cG => aG], max_rules=200)
|
||||
H = FPGroup(G, [aG^2 => cG, bG * cG => aG]; max_rules = 200)
|
||||
|
||||
h = H(word(g))
|
||||
|
||||
|
@ -48,15 +49,21 @@
|
|||
@test_throws AssertionError h == g
|
||||
@test_throws MethodError h * g
|
||||
|
||||
H′ = FPGroup(G, [aG^2 => cG, bG * cG => aG], max_rules=200)
|
||||
H′ = FPGroup(G, [aG^2 => cG, bG * cG => aG]; max_rules = 200)
|
||||
@test_throws AssertionError one(H) == one(H′)
|
||||
|
||||
Groups.normalform!(h)
|
||||
@test h == H([5])
|
||||
|
||||
@test_logs (:warn, "using generic isfiniteorder(::AbstractFPGroupElement): the returned `false` might be wrong") isfiniteorder(h)
|
||||
@test_logs (
|
||||
:warn,
|
||||
"using generic isfiniteorder(::AbstractFPGroupElement): the returned `false` might be wrong",
|
||||
) isfiniteorder(h)
|
||||
|
||||
@test_logs (:warn, "using generic isfinite(::AbstractFPGroup): the returned `false` might be wrong") isfinite(H)
|
||||
@test_logs (
|
||||
:warn,
|
||||
"using generic isfinite(::AbstractFPGroup): the returned `false` might be wrong",
|
||||
) isfinite(H)
|
||||
|
||||
Logging.with_logger(Logging.NullLogger()) do
|
||||
@testset "GroupsCore conformance: H" begin
|
||||
|
@ -64,4 +71,24 @@
|
|||
test_GroupElement_interface(rand(H, 2)...)
|
||||
end
|
||||
end
|
||||
|
||||
@testset "hash/normalform #28" begin
|
||||
function cyclic_group(n::Integer)
|
||||
A = Alphabet([:a, :A], [2, 1])
|
||||
F = FreeGroup(A)
|
||||
a, = Groups.gens(F)
|
||||
e = one(F)
|
||||
Cₙ = FPGroup(F, [a^n => e])
|
||||
|
||||
return Cₙ
|
||||
end
|
||||
|
||||
n = 15
|
||||
G = cyclic_group(n)
|
||||
ball, sizes = Groups.wlmetric_ball(gens(G); radius = n)
|
||||
@test first(sizes) == 2
|
||||
@test last(sizes) == n
|
||||
|
||||
@test Set(ball) == Set([first(gens(G))^i for i in 0:n-1])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
@testset "GroupConstructions" begin
|
||||
|
||||
symmetric_group(n) = PermGroup(perm"(1,2)", Perm([2:n; 1]))
|
||||
|
||||
@testset "DirectProduct" begin
|
||||
GH =
|
||||
let G = PermutationGroups.SymmetricGroup(3),
|
||||
H = PermutationGroups.SymmetricGroup(4)
|
||||
let G = symmetric_group(3), H = symmetric_group(4)
|
||||
|
||||
Groups.Constructions.DirectProduct(G, H)
|
||||
end
|
||||
|
@ -17,7 +18,7 @@
|
|||
|
||||
@testset "DirectPower" begin
|
||||
GGG = Groups.Constructions.DirectPower{3}(
|
||||
PermutationGroups.SymmetricGroup(3),
|
||||
symmetric_group(3),
|
||||
)
|
||||
test_Group_interface(GGG)
|
||||
test_GroupElement_interface(rand(GGG, 2)...)
|
||||
|
@ -28,8 +29,7 @@
|
|||
end
|
||||
@testset "WreathProduct" begin
|
||||
W =
|
||||
let G = PermutationGroups.SymmetricGroup(2),
|
||||
P = PermutationGroups.SymmetricGroup(4)
|
||||
let G = symmetric_group(2), P = symmetric_group(4)
|
||||
|
||||
Groups.Constructions.WreathProduct(G, P)
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue