diff --git a/Project.toml b/Project.toml index d12521b..01f4198 100644 --- a/Project.toml +++ b/Project.toml @@ -14,7 +14,7 @@ ThreadsX = "ac1d9e8a-700a-412c-b207-f0111f4b6c0d" [compat] AbstractAlgebra = "0.15, 0.16" GroupsCore = "^0.3" -KnuthBendix = "^0.2.0" +KnuthBendix = "^0.2.1" OrderedCollections = "1" ThreadsX = "^0.1.0" julia = "1.3, 1.4, 1.5, 1.6" diff --git a/README.md b/README.md index fe6af31..7a64708 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,152 @@ [![Build Status](https://travis-ci.org/kalmarek/Groups.jl.svg?branch=master)](https://travis-ci.org/kalmarek/Groups.jl) [![codecov](https://codecov.io/gh/kalmarek/Groups.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/kalmarek/Groups.jl) -A very rudimentary implementation of finitely-presented groups (syllable representation). Relatively complete are only [automorphism groups of free groups](https://github.com/kalmarek/Groups.jl/blob/master/src/AutGroup.jl) and [wreath products](https://github.com/kalmarek/Groups.jl/blob/master/src/WreathProducts.jl) (which are not finitely-presented, but based on the standard normal form). +An implementation of finitely-presented groups together with normalization (using Knuth-Bendix procedure). -Have a look into `test` directory for eample use. +The package implements `AbstractFPGroup` with three concrete types: `FreeGroup`, `FPGroup` and `AutomorphismGroup`. Here's an example usage: + +```julia +julia> using Groups, GroupsCore + +julia> A = Alphabet([:a, :A, :b, :B, :c, :C], [2, 1, 4, 3, 6, 5]) +Alphabet of Symbol: + 1. :a = (:A)⁻¹ + 2. :A = (:a)⁻¹ + 3. :b = (:B)⁻¹ + 4. :B = (:b)⁻¹ + 5. :c = (:C)⁻¹ + 6. :C = (:c)⁻¹ + +julia> F = FreeGroup(A) +free group on 3 generators + +julia> a,b,c = gens(F) +3-element Vector{FPGroupElement{FreeGroup{Symbol}, KnuthBendix.Word{UInt8}}}: + a + b + c + +julia> a*inv(a) +(empty word) + +julia> (a*b)^2 +a*b*a*b + +julia> commutator(a, b) +A*B*a*b + +julia> x = a*b; y = inv(b)*a; + +julia> x*y +a^2 + +``` +Let's create a quotient of the free group above: +```julia +julia> ε = one(F); + +julia> G = FPGroup(F, [a^2 => ε, b^3=> ε, (a*b)^7=>ε, (a*b*a*inv(b))^6 => ε, commutator(a, c) => ε, commutator(b, c) => ε ]) +┌ Warning: Maximum number of rules (100) reached. The rewriting system may not be confluent. +│ You may retry `knuthbendix` with a larger `maxrules` kwarg. +└ @ KnuthBendix ~/.julia/packages/KnuthBendix/i93Np/src/kbs.jl:6 +⟨a, b, c | a^2 => (empty word), b^3 => (empty word), a*b*a*b*a*b*a*b*a*b*a*b*a*b => (empty word), a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B => (empty word), A*C*a*c => (empty word), B*C*b*c => (empty word)⟩ + +``` +As you can see from the warning, the Knuth-Bendix procedure has not completed successfully. This means that we only are able to approximate the word problem in `G`, i.e. if the equality (`==`) of two group elements may return `false` even if group elements are equal. Let us try with a larger maximal number of rules in the underlying rewriting system. + +```julia +julia> G = FPGroup(F, [a^2 => ε, b^3=> ε, (a*b)^7=>ε, (a*b*a*inv(b))^6 => ε, commutator(a, c) => ε, commutator(b, c) => ε ], maxrules=500) +⟨a, b, c | a^2 => (empty word), b^3 => (empty word), a*b*a*b*a*b*a*b*a*b*a*b*a*b => (empty word), a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B => (empty word), A*C*a*c => (empty word), B*C*b*c => (empty word)⟩ + +``` +This time there was no warning, i.e. Knuth-Bendix completion was successful and we may treat the equality (`==`) as true mathematical equality. Note that `G` is the direct product of `ℤ = ⟨ c ⟩` and a quotient of van Dyck `(2,3,7)`-group. Let's create a random word and reduce it as an element of `G`. +```julia +julia> using Random; Random.seed!(1); w = Groups.Word(rand(1:length(A), 16)) +KnuthBendix.Word{UInt16}: 4·6·1·1·1·6·5·1·5·2·3·6·2·4·2·6 + +julia> F(w) # freely reduced w +B*C*a^4*c*A*b*C*A*B*A*C + +julia> G(w) # w as an element of G +B*a*b*a*B*a*C^2 + +julia> F(w) # freely reduced w +B*C*a^4*c*A*b*C*A*B*A*C + +julia> word(ans) # the underlying word in A +KnuthBendix.Word{UInt8}: 4·6·1·1·1·1·5·2·3·6·2·4·2·6 + +julia> G(w) # w as an element of G +B*a*b*a*B*a*C^2 + +julia> word(ans) # the underlying word in A +KnuthBendix.Word{UInt8}: 4·1·3·1·4·1·6·6 + +``` +As we can see the underlying words change according to where they are reduced. +Note that a word `w` (of type `Word <: AbstractWord`) is just a sequence of numbers -- pointers to letters of an `Alphabet`. Without the alphabet `w` has no meaning. + +### Automorphism Groups + +Relatively complete is the support for the automorphisms of free groups, as given by Gersten presentation: +```julia +julia> saut = SpecialAutomorphismGroup(F, maxrules=100) +┌ Warning: Maximum number of rules (100) reached. The rewriting system may not be confluent. +│ You may retry `knuthbendix` with a larger `maxrules` kwarg. +└ @ KnuthBendix ~/.julia/packages/KnuthBendix/i93Np/src/kbs.jl:6 +automorphism group of free group on 3 generators + +julia> S = gens(saut) +12-element Vector{Automorphism{FreeGroup{Symbol},…}}: + ϱ₁.₂ + ϱ₁.₃ + ϱ₂.₁ + ϱ₂.₃ + ϱ₃.₁ + ϱ₃.₂ + λ₁.₂ + λ₁.₃ + λ₂.₁ + λ₂.₃ + λ₃.₁ + λ₃.₂ + +julia> x, y, z = S[1], S[12], S[6]; + +julia> f = x*y*inv(z) +ϱ₁.₂*λ₃.₂*ϱ₃.₂^-1 + +julia> g = inv(z)*y*x +ϱ₃.₂^-1*ϱ₁.₂*λ₃.₂ + +julia> word(f), word(g) +(KnuthBendix.Word{UInt8}: 1·12·18, KnuthBendix.Word{UInt8}: 18·1·12) + +``` +Even though Knuth-Bendix did not finish successfully in automorphism groups we have another ace in our sleeve to solve the word problem: evaluation. +Lets have a look at the images of generators under those automorphisms: +```julia +julia> evaluate(f) # or to be more verbose... +(a*b, b, b*c*B) + +julia> Groups.domain(g) +(a, b, c) + +julia> Groups.evaluate!(Groups.domain(g), g) +(a*b, b, b*c*B) + +``` +Since these automorphism map the standard generating set to the same new generating set, they should be considered as equal! And indeed they are: +```julia +julia> f == g +true +``` +This is what is happening behind the scenes: + 1. words are reduced using a rewriting system + 2. if resulting words are equal `true` is returned + 3. if they are not equal `Groups.equality_data` is computed for each argument (here: the images of generators) and the result of comparison is returned. + +Moreover we try to amortize the cost of computing those images. That is a hash of `equality_daata` is lazily stored in each group element and used as needed. Essentially only if `true` is returned, but comparison of words returns `false` recomputation of images is needed (to guard against hash collisions). + +---- +This package was developed for computations in [1712.07167](https://arxiv.org/abs/1712.07167) and in [1812.03456](https://arxiv.org/abs/1812.03456). If you happen to use this package please cite either of them.