Compare commits
76 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 | |
Marek Kaluba | 7230106bfc | |
Marek Kaluba | 751850568c | |
Marek Kaluba | 038fc29b81 | |
Marek Kaluba | c69eff1540 | |
Marek Kaluba | a1bc334fb2 | |
Marek Kaluba | 1f1e51917a | |
Marek Kaluba | 8bd3f7ede6 | |
Marek Kaluba | 448857ef03 | |
Marek Kaluba | af3913d085 | |
Marek Kaluba | 3cdef72977 | |
Marek Kaluba | 29af9659d9 | |
Marek Kaluba | a0d2186477 | |
Marek Kaluba | b52de81e06 | |
Marek Kaluba | 46c95dbb83 | |
Marek Kaluba | 40cd92e3c4 | |
Marek Kaluba | 116439d074 | |
Marek Kaluba | a380959614 | |
Marek Kaluba | f096a869b8 | |
Marek Kaluba | b7a6f4e952 | |
Marek Kaluba | 9006f8a4a1 | |
Marek Kaluba | 22c21706d8 | |
Marek Kaluba | 161c146642 | |
Marek Kaluba | 12f2ff92a7 | |
Marek Kaluba | fe955850c3 | |
Marek Kaluba | 93c6b96c5d | |
Marek Kaluba | 6b707b5ff6 | |
Marek Kaluba | e2646709fe | |
Marek Kaluba | 29e2097f2f | |
Marek Kaluba | 5752d67009 | |
Marek Kaluba | 827969ae84 | |
Marek Kaluba | c75ff00aa9 | |
Marek Kaluba | 1345516521 | |
Marek Kaluba | 42d4c41d90 | |
Marek Kaluba | 30d58445df | |
Marek Kaluba | 2e544d623f | |
Marek Kaluba | 53773efac7 | |
Marek Kaluba | 6774d40d11 | |
Marek Kaluba | c9a4b00ceb | |
Marek Kaluba | f68a0c2f27 | |
Marek Kaluba | de600de0a2 | |
Marek Kaluba | 4431ae7144 | |
Marek Kaluba | 9e867a58e6 | |
Marek Kaluba | 93d97ff75f | |
Marek Kaluba | 22a8809c6f | |
Marek Kaluba | e78520f90b | |
Marek Kaluba | dd6588f018 | |
Marek Kaluba | cb933c3f87 | |
Marek Kaluba | db5d60134c | |
Marek Kaluba | adb42c8919 | |
Marek Kaluba | 2070d0efad | |
Marek Kaluba | 20711aa1e8 | |
Marek Kaluba | 72ea5c8123 | |
Marek Kaluba | 54c96c1f2a | |
Marek Kaluba | f2d2d4ee52 | |
Marek Kaluba | 5b5f3e3811 |
|
@ -0,0 +1 @@
|
|||
../.JuliaFormatter.toml
|
|
@ -0,0 +1,17 @@
|
|||
name: CompatHelper
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 0 * * *
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
CompatHelper:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Pkg.add("CompatHelper")
|
||||
run: julia -e 'using Pkg; Pkg.add("CompatHelper")'
|
||||
- name: CompatHelper.main()
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }}
|
||||
run: julia -e 'using CompatHelper; CompatHelper.main()'
|
||||
|
|
@ -4,6 +4,11 @@ on:
|
|||
types:
|
||||
- created
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
lookback:
|
||||
default: 3
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
TagBot:
|
||||
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- pull_request
|
||||
- push
|
||||
jobs:
|
||||
test:
|
||||
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
|
||||
|
@ -13,7 +9,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- '1.3'
|
||||
- '1.6'
|
||||
- '1'
|
||||
- 'nightly'
|
||||
os:
|
||||
|
@ -22,21 +18,31 @@ jobs:
|
|||
- windows-latest
|
||||
arch:
|
||||
- x64
|
||||
allow_failures:
|
||||
- julia: nightly
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: julia-actions/setup-julia@v1
|
||||
with:
|
||||
version: ${{ matrix.version }}
|
||||
arch: ${{ matrix.arch }}
|
||||
- uses: actions/cache@v3
|
||||
env:
|
||||
cache-name: cache-artifacts
|
||||
with:
|
||||
path: ~/.julia/artifacts
|
||||
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-test-${{ env.cache-name }}-
|
||||
${{ runner.os }}-test-
|
||||
${{ runner.os }}-
|
||||
- uses: julia-actions/julia-buildpkg@latest
|
||||
- uses: julia-actions/julia-runtest@latest
|
||||
- uses: julia-actions/julia-processcoverage@v1
|
||||
- uses: codecov/codecov-action@v1
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
file: ./lcov.info
|
||||
flags: unittests
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: false
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
21
Project.toml
21
Project.toml
|
@ -1,29 +1,30 @@
|
|||
name = "Groups"
|
||||
uuid = "5d8bd718-bd84-11e8-3b40-ad14f4a32557"
|
||||
authors = ["Marek Kaluba <kalmar@amu.edu.pl>"]
|
||||
version = "0.7.1"
|
||||
version = "0.8"
|
||||
|
||||
[deps]
|
||||
GroupsCore = "d5909c97-4eac-4ecc-a3dc-fdd0858a4120"
|
||||
KnuthBendix = "c2604015-7b3d-4a30-8a26-9074551ec60a"
|
||||
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
|
||||
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
|
||||
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
|
||||
PermutationGroups = "8bc5a954-2dfc-11e9-10e6-cd969bffa420"
|
||||
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
|
||||
ThreadsX = "ac1d9e8a-700a-412c-b207-f0111f4b6c0d"
|
||||
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
|
||||
|
||||
[compat]
|
||||
AbstractAlgebra = "0.22"
|
||||
GroupsCore = "0.4"
|
||||
KnuthBendix = "0.3"
|
||||
GroupsCore = "0.5"
|
||||
KnuthBendix = "0.4"
|
||||
OrderedCollections = "1"
|
||||
PermutationGroups = "0.3"
|
||||
ThreadsX = "0.1"
|
||||
julia = "1.3"
|
||||
PermutationGroups = "0.6"
|
||||
StaticArrays = "1"
|
||||
julia = "1.6"
|
||||
|
||||
[extras]
|
||||
AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d"
|
||||
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
|
||||
PermutationGroups = "8bc5a954-2dfc-11e9-10e6-cd969bffa420"
|
||||
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
|
||||
|
||||
[targets]
|
||||
test = ["Test", "BenchmarkTools", "AbstractAlgebra", "PermutationGroups"]
|
||||
test = ["Test", "BenchmarkTools"]
|
||||
|
|
114
README.md
114
README.md
|
@ -1,5 +1,5 @@
|
|||
# Groups
|
||||
[![CI](https://github.com/kalmarek/Groups.jl/actions/workflows/runtests.yml/badge.svg)](https://github.com/kalmarek/Groups.jl/actions/workflows/runtests.yml)
|
||||
[![CI](https://github.com/kalmarek/Groups.jl/actions/workflows/ci.yml/badge.svg)](https://github.com/kalmarek/Groups.jl/actions/workflows/ci.yml)
|
||||
[![codecov](https://codecov.io/gh/kalmarek/Groups.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/kalmarek/Groups.jl)
|
||||
|
||||
An implementation of finitely-presented groups together with normalization (using Knuth-Bendix procedure).
|
||||
|
@ -10,25 +10,25 @@ The package implements `AbstractFPGroup` with three concrete types: `FreeGroup`,
|
|||
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)⁻¹
|
||||
Alphabet of Symbol
|
||||
1. a (inverse of: A)
|
||||
2. A (inverse of: a)
|
||||
3. b (inverse of: B)
|
||||
4. B (inverse of: b)
|
||||
5. c (inverse of: C)
|
||||
6. C (inverse of: 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}}}:
|
||||
3-element Vector{FPGroupElement{FreeGroup{Symbol, KnuthBendix.LenLex{Symbol}}, …}}:
|
||||
a
|
||||
b
|
||||
c
|
||||
|
||||
julia> a*inv(a)
|
||||
(empty word)
|
||||
(id)
|
||||
|
||||
julia> (a*b)^2
|
||||
a*b*a*b
|
||||
|
@ -40,65 +40,75 @@ julia> x = a*b; y = inv(b)*a;
|
|||
|
||||
julia> x*y
|
||||
a^2
|
||||
|
||||
```
|
||||
|
||||
## FPGroup
|
||||
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)⟩
|
||||
julia> ε = one(F)
|
||||
(id)
|
||||
|
||||
julia> G = FPGroup(F, [a^2 => ε, b^3=> ε, (a*b)^7=>ε, (a*b*a*inv(b))^6 => ε, commutator(a, c) => ε, commutator(b, c) => ε ], max_rules=100)
|
||||
┌ Warning: Maximum number of rules (100) reached.
|
||||
│ The rewriting system may not be confluent.
|
||||
│ You may retry `knuthbendix` with a larger `max_rules` kwarg.
|
||||
└ @ KnuthBendix ~/.julia/packages/KnuthBendix/6ME1b/src/knuthbendix_base.jl:8
|
||||
Finitely presented group generated by:
|
||||
{ a b c },
|
||||
subject to relations:
|
||||
a^2 => (id)
|
||||
b^3 => (id)
|
||||
a*b*a*b*a*b*a*b*a*b*a*b*a*b => (id)
|
||||
a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B => (id)
|
||||
A*C*a*c => (id)
|
||||
B*C*b*c => (id)
|
||||
```
|
||||
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.
|
||||
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)⟩
|
||||
julia> G = FPGroup(F, [a^2 => ε, b^3=> ε, (a*b)^7=>ε, (a*b*a*inv(b))^6 => ε, commutator(a, c) => ε, commutator(b, c) => ε ], max_rules=500)
|
||||
Finitely presented group generated by:
|
||||
{ a b c },
|
||||
subject to relations:
|
||||
a^2 => (id)
|
||||
b^3 => (id)
|
||||
a*b*a*b*a*b*a*b*a*b*a*b*a*b => (id)
|
||||
a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B*a*b*a*B => (id)
|
||||
A*C*a*c => (id)
|
||||
B*C*b*c => (id)
|
||||
|
||||
```
|
||||
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`.
|
||||
This time there was no warning, i.e. Knuth-Bendix completion was successful and we may treat the equality (`==`) as the **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> using Random; Random.seed!(1); w = Groups.Word(rand(1:length(A), 16));
|
||||
|
||||
julia> F(w) # freely reduced w
|
||||
B*C*a^4*c*A*b*C*A*B*A*C
|
||||
julia> length(w), w # word of itself
|
||||
(16, 1·3·5·4·6·2·5·5·5·2·4·3·2·1·4·4)
|
||||
|
||||
julia> G(w) # w as an element of G
|
||||
B*a*b*a*B*a*C^2
|
||||
julia> f = F(w) # freely reduced w
|
||||
a*b*c*B*C*A*c^3*A*B^2
|
||||
|
||||
julia> F(w) # freely reduced w
|
||||
B*C*a^4*c*A*b*C*A*B*A*C
|
||||
julia> length(word(f)), word(f) # the underlying word in F
|
||||
(12, 1·3·5·4·6·2·5·5·5·2·4·4)
|
||||
|
||||
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
|
||||
julia> g = G(w) # w as an element of G
|
||||
a*b*c^3
|
||||
|
||||
julia> length(word(g)), word(g) # the underlying word in G
|
||||
(5, 1·3·5·5·5)
|
||||
```
|
||||
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.
|
||||
Note that a word `w` (of type `Word <: AbstractWord`) is just a sequence of numbers -- indices of letters of an `Alphabet`. Without the alphabet `w` has no intrinsic meaning.
|
||||
|
||||
### Automorphism Groups
|
||||
## Automorphism Groups
|
||||
|
||||
Relatively complete is the support for the automorphisms of free groups, as given by Gersten presentation:
|
||||
Relatively complete is the support for the automorphisms of free groups generated by transvections (or Nielsen generators):
|
||||
```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
|
||||
julia> saut = SpecialAutomorphismGroup(F, max_rules=1000)
|
||||
automorphism group of free group on 3 generators
|
||||
|
||||
julia> S = gens(saut)
|
||||
12-element Vector{Automorphism{FreeGroup{Symbol},…}}:
|
||||
12-element Vector{Automorphism{FreeGroup{Symbol, KnuthBendix.LenLex{Symbol}}, …}}:
|
||||
ϱ₁.₂
|
||||
ϱ₁.₃
|
||||
ϱ₂.₁
|
||||
|
@ -114,17 +124,15 @@ julia> S = gens(saut)
|
|||
|
||||
julia> x, y, z = S[1], S[12], S[6];
|
||||
|
||||
julia> f = x*y*inv(z)
|
||||
ϱ₁.₂*λ₃.₂*ϱ₃.₂^-1
|
||||
julia> f = x*y*inv(z);
|
||||
|
||||
julia> g = inv(z)*y*x
|
||||
ϱ₃.₂^-1*ϱ₁.₂*λ₃.₂
|
||||
julia> g = inv(z)*y*x;
|
||||
|
||||
julia> word(f), word(g)
|
||||
(KnuthBendix.Word{UInt8}: 1·12·18, KnuthBendix.Word{UInt8}: 18·1·12)
|
||||
(1·23·12, 12·23·1)
|
||||
|
||||
```
|
||||
Even though Knuth-Bendix did not finish successfully in automorphism groups we have another ace in our sleeve to solve the word problem: evaluation.
|
||||
Even though there is no known finite, confluent rewriting system for automorphism groupsof the free group (so Knuth-Bendix did not finish successfully) 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...
|
||||
|
@ -147,7 +155,7 @@ This is what is happening behind the scenes:
|
|||
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).
|
||||
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.
|
||||
|
|
|
@ -1,24 +1,45 @@
|
|||
module Groups
|
||||
|
||||
import Logging
|
||||
|
||||
using GroupsCore
|
||||
using ThreadsX
|
||||
import KnuthBendix
|
||||
import KnuthBendix: AbstractWord, Alphabet, Word
|
||||
import KnuthBendix: alphabet
|
||||
import Random
|
||||
|
||||
import OrderedCollections: OrderedSet
|
||||
import KnuthBendix
|
||||
import KnuthBendix: AbstractWord, Alphabet, Word
|
||||
import KnuthBendix: alphabet, ordering
|
||||
|
||||
export MatrixGroups
|
||||
|
||||
export Alphabet,
|
||||
AutomorphismGroup,
|
||||
FreeGroup,
|
||||
FreeGroup,
|
||||
FPGroup,
|
||||
FPGroupElement,
|
||||
SpecialAutomorphismGroup,
|
||||
Homomorphism
|
||||
|
||||
export Alphabet, AutomorphismGroup, FreeGroup, FreeGroup, FPGroup, FPGroupElement, SpecialAutomorphismGroup
|
||||
export alphabet, evaluate, word, gens
|
||||
|
||||
# general constructions
|
||||
include(joinpath("constructions", "constructions.jl"))
|
||||
import .Constructions
|
||||
|
||||
include("types.jl")
|
||||
include("rand.jl")
|
||||
include("hashing.jl")
|
||||
include("normalform.jl")
|
||||
include("autgroups.jl")
|
||||
include("homomorphisms.jl")
|
||||
|
||||
include("groups/sautFn.jl")
|
||||
include("groups/mcg.jl")
|
||||
include("aut_groups/sautFn.jl")
|
||||
include("aut_groups/mcg.jl")
|
||||
|
||||
include("matrix_groups/MatrixGroups.jl")
|
||||
using .MatrixGroups
|
||||
|
||||
include("abelianize.jl")
|
||||
|
||||
include("wl_ball.jl")
|
||||
end # of module Groups
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
function _abelianize(
|
||||
i::Integer,
|
||||
source::AutomorphismGroup{<:FreeGroup},
|
||||
target::MatrixGroups.SpecialLinearGroup{N,T},
|
||||
) where {N,T}
|
||||
n = ngens(object(source))
|
||||
@assert n == N
|
||||
aut = alphabet(source)[i]
|
||||
if aut isa Transvection
|
||||
# we change (i,j) to (j, i) to be consistent with the action:
|
||||
# Automorphisms act on the right which corresponds to action on
|
||||
# the columns in the matrix case
|
||||
eij = MatrixGroups.ElementaryMatrix{N}(
|
||||
aut.j,
|
||||
aut.i,
|
||||
ifelse(aut.inv, -one(T), one(T)),
|
||||
)
|
||||
k = alphabet(target)[eij]
|
||||
return word_type(target)([k])
|
||||
else
|
||||
throw("unexpected automorphism symbol: $(typeof(aut))")
|
||||
end
|
||||
end
|
||||
|
||||
function _abelianize(
|
||||
i::Integer,
|
||||
source::AutomorphismGroup{<:Groups.SurfaceGroup},
|
||||
target::MatrixGroups.SpecialLinearGroup{N,T},
|
||||
) where {N,T}
|
||||
n = ngens(Groups.object(source))
|
||||
@assert n == N
|
||||
g = alphabet(source)[i].autFn_word
|
||||
result = one(target)
|
||||
for l in word(g)
|
||||
append!(word(result), _abelianize(l, parent(g), target))
|
||||
end
|
||||
|
||||
return word(result)
|
||||
end
|
||||
|
||||
function Groups._abelianize(
|
||||
i::Integer,
|
||||
source::AutomorphismGroup{<:Groups.SurfaceGroup},
|
||||
target::MatrixGroups.SymplecticGroup{N,T},
|
||||
) where {N,T}
|
||||
@assert iseven(N)
|
||||
As = alphabet(source)
|
||||
At = alphabet(target)
|
||||
|
||||
SlN = let genus = Groups.genus(Groups.object(source))
|
||||
@assert 2genus == N
|
||||
MatrixGroups.SpecialLinearGroup{2genus}(T)
|
||||
end
|
||||
|
||||
ab = Groups.Homomorphism(Groups._abelianize, source, SlN; check = false)
|
||||
|
||||
matrix_spn_map = let S = gens(target)
|
||||
Dict(MatrixGroups.matrix(g) => word(g) for g in union(S, inv.(S)))
|
||||
end
|
||||
|
||||
# renumeration:
|
||||
# (f1, f2, f3, f4, f5, f6) = (a₁, a₂, a₃, b₁, b₂, b₃) →
|
||||
# → (b₃, a₃, b₂, a₂, b₁, a₁)
|
||||
# hence p = [6, 4, 2, 5, 3, 1]
|
||||
p = [reverse(2:2:N); reverse(1:2:N)]
|
||||
|
||||
g = source([i])
|
||||
Mg = MatrixGroups.matrix(ab(g))[p, p]
|
||||
|
||||
return matrix_spn_map[Mg]
|
||||
end
|
|
@ -6,7 +6,7 @@ function gersten_alphabet(n::Integer; commutative::Bool = true)
|
|||
append!(S, [λ(i, j) for (i, j) in indexing])
|
||||
end
|
||||
|
||||
return Alphabet(S)
|
||||
return Alphabet(mapreduce(x -> [x, inv(x)], union, S))
|
||||
end
|
||||
|
||||
function _commutation_rule(
|
||||
|
@ -40,13 +40,18 @@ function _hexagonal_rule(
|
|||
return W(T[A[x], A[inv(y)], A[z]]) => W(T[A[z], A[w^-1], A[x]])
|
||||
end
|
||||
|
||||
gersten_relations(n::Integer; commutative) =
|
||||
gersten_relations(Word{UInt8}, n, commutative = commutative)
|
||||
function gersten_relations(n::Integer; commutative)
|
||||
return gersten_relations(Word{UInt8}, n; commutative = commutative)
|
||||
end
|
||||
|
||||
function gersten_relations(::Type{W}, n::Integer; commutative) where {W<:AbstractWord}
|
||||
function gersten_relations(
|
||||
::Type{W},
|
||||
n::Integer;
|
||||
commutative,
|
||||
) where {W<:AbstractWord}
|
||||
@assert n > 1 "Gersten relations are defined only for n>1, got n=$n"
|
||||
A = gersten_alphabet(n, commutative = commutative)
|
||||
@assert length(A) <= KnuthBendix._max_alphabet_length(W) "Type $W can not represent words over alphabet with $(length(A)) letters."
|
||||
A = gersten_alphabet(n; commutative = commutative)
|
||||
@assert length(A) <= typemax(eltype(W)) "Type $W can not represent words over alphabet with $(length(A)) letters."
|
||||
|
||||
rels = Pair{W,W}[]
|
||||
|
||||
|
@ -74,7 +79,10 @@ function gersten_relations(::Type{W}, n::Integer; commutative) where {W<:Abstrac
|
|||
|
||||
for (i, j, k) in Iterators.product(1:n, 1:n, 1:n)
|
||||
if (i ≠ j && k ≠ i && k ≠ j)
|
||||
push!(rels, _pentagonal_rule(W, A, ϱ(i, j)^-1, ϱ(j, k)^-1, ϱ(i, k)^-1))
|
||||
push!(
|
||||
rels,
|
||||
_pentagonal_rule(W, A, ϱ(i, j)^-1, ϱ(j, k)^-1, ϱ(i, k)^-1),
|
||||
)
|
||||
push!(rels, _pentagonal_rule(W, A, ϱ(i, j)^-1, ϱ(j, k), ϱ(i, k)))
|
||||
|
||||
commutative && continue
|
||||
|
@ -83,7 +91,10 @@ function gersten_relations(::Type{W}, n::Integer; commutative) where {W<:Abstrac
|
|||
push!(rels, _pentagonal_rule(W, A, ϱ(i, j), λ(j, k)^-1, ϱ(i, k)))
|
||||
|
||||
# the same as above, but with ϱ ↔ λ:
|
||||
push!(rels, _pentagonal_rule(W, A, λ(i, j)^-1, λ(j, k)^-1, λ(i, k)^-1))
|
||||
push!(
|
||||
rels,
|
||||
_pentagonal_rule(W, A, λ(i, j)^-1, λ(j, k)^-1, λ(i, k)^-1),
|
||||
)
|
||||
push!(rels, _pentagonal_rule(W, A, λ(i, j)^-1, λ(j, k), λ(i, k)))
|
||||
|
||||
push!(rels, _pentagonal_rule(W, A, λ(i, j), ϱ(j, k), λ(i, k)^-1))
|
||||
|
@ -94,9 +105,12 @@ function gersten_relations(::Type{W}, n::Integer; commutative) where {W<:Abstrac
|
|||
if !commutative
|
||||
for (i, j) in Iterators.product(1:n, 1:n)
|
||||
if i ≠ j
|
||||
push!(rels, _hexagonal_rule(W, A, ϱ(i, j), ϱ(j, i), λ(i, j), λ(j, i)))
|
||||
push!(
|
||||
rels,
|
||||
_hexagonal_rule(W, A, ϱ(i, j), ϱ(j, i), λ(i, j), λ(j, i)),
|
||||
)
|
||||
w = W([A[ϱ(i, j)], A[ϱ(j, i)^-1], A[λ(i, j)]])
|
||||
push!(rels, w^2 => inv(A, w)^2)
|
||||
push!(rels, w^2 => inv(w, A)^2)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,131 @@
|
|||
struct SurfaceGroup{T,S,RW} <: AbstractFPGroup
|
||||
genus::Int
|
||||
boundaries::Int
|
||||
gens::Vector{T}
|
||||
relations::Vector{<:Pair{S,S}}
|
||||
rw::RW
|
||||
end
|
||||
|
||||
include("symplectic_twists.jl")
|
||||
|
||||
genus(S::SurfaceGroup) = S.genus
|
||||
|
||||
function Base.show(io::IO, S::SurfaceGroup)
|
||||
print(io, "π₁ of the orientable surface of genus $(genus(S))")
|
||||
if S.boundaries > 0
|
||||
print(io, " with $(S.boundaries) boundary components")
|
||||
end
|
||||
end
|
||||
|
||||
function SurfaceGroup(genus::Integer, boundaries::Integer, W = Word{Int16})
|
||||
@assert genus > 1
|
||||
|
||||
# The (confluent) rewriting systems comes from
|
||||
# S. Hermiller, Rewriting systems for Coxeter groups
|
||||
# Journal of Pure and Applied Algebra
|
||||
# Volume 92, Issue 2, 7 March 1994, Pages 137-148
|
||||
# https://doi.org/10.1016/0022-4049(94)90019-1
|
||||
# Note: the notation is "inverted":
|
||||
# a_g of the article becomes A_g here.
|
||||
|
||||
ltrs = String[]
|
||||
for i in 1:genus
|
||||
subscript = join('₀' + d for d in reverse(digits(i)))
|
||||
append!(
|
||||
ltrs,
|
||||
[
|
||||
"A" * subscript,
|
||||
"a" * subscript,
|
||||
"B" * subscript,
|
||||
"b" * subscript,
|
||||
],
|
||||
)
|
||||
end
|
||||
Al = Alphabet(reverse!(ltrs))
|
||||
|
||||
for i in 1:genus
|
||||
subscript = join('₀' + d for d in reverse(digits(i)))
|
||||
KnuthBendix.setinverse!(Al, "a" * subscript, "A" * subscript)
|
||||
KnuthBendix.setinverse!(Al, "b" * subscript, "B" * subscript)
|
||||
end
|
||||
|
||||
if boundaries == 0
|
||||
word = Int[]
|
||||
|
||||
for i in reverse(1:genus)
|
||||
x = 4 * i
|
||||
append!(word, [x, x - 2, x - 1, x - 3])
|
||||
end
|
||||
comms = W(word)
|
||||
word_rels = [comms => one(comms)]
|
||||
|
||||
rws =
|
||||
let R = KnuthBendix.RewritingSystem(
|
||||
word_rels,
|
||||
KnuthBendix.Recursive(Al),
|
||||
)
|
||||
KnuthBendix.IndexAutomaton(KnuthBendix.knuthbendix(R))
|
||||
end
|
||||
elseif boundaries == 1
|
||||
word_rels = Pair{W,W}[]
|
||||
rws = let R = RewritingSystem(word_rels, KnuthBendix.LenLex(Al))
|
||||
KnuthBendix.IndexAutomaton(KnuthBendix.knuthbendix(R))
|
||||
end
|
||||
else
|
||||
throw("Not Implemented for MCG with $boundaryies boundary components")
|
||||
end
|
||||
|
||||
F = FreeGroup(Al)
|
||||
rels = [F(lhs) => F(rhs) for (lhs, rhs) in word_rels]
|
||||
|
||||
return SurfaceGroup(
|
||||
genus,
|
||||
boundaries,
|
||||
[Al[i] for i in 2:2:length(Al)],
|
||||
rels,
|
||||
rws,
|
||||
)
|
||||
end
|
||||
|
||||
rewriting(S::SurfaceGroup) = S.rw
|
||||
relations(S::SurfaceGroup) = S.relations
|
||||
|
||||
function symplectic_twists(π₁Σ::SurfaceGroup)
|
||||
g = genus(π₁Σ)
|
||||
|
||||
saut = SpecialAutomorphismGroup(FreeGroup(2g); max_rules = 1000)
|
||||
|
||||
Aij = [
|
||||
SymplecticMappingClass(saut, :A, i, j) for i in 1:g for
|
||||
j in 1:g if i ≠ j
|
||||
]
|
||||
|
||||
Bij = [
|
||||
SymplecticMappingClass(saut, :B, i, j) for i in 1:g for
|
||||
j in 1:g if i ≠ j
|
||||
]
|
||||
|
||||
mBij = [
|
||||
SymplecticMappingClass(saut, :B, i, j; minus = true) for i in 1:g
|
||||
for j in 1:g if i ≠ j
|
||||
]
|
||||
|
||||
Bii = [SymplecticMappingClass(saut, :B, i, i) for i in 1:g]
|
||||
|
||||
mBii = [SymplecticMappingClass(saut, :B, i, i; minus = true) for i in 1:g]
|
||||
|
||||
return [Aij; Bij; mBij; Bii; mBii]
|
||||
end
|
||||
|
||||
KnuthBendix.alphabet(G::AutomorphismGroup{<:SurfaceGroup}) = rewriting(G)
|
||||
|
||||
function AutomorphismGroup(π₁Σ::SurfaceGroup; kwargs...)
|
||||
S = vcat(symplectic_twists(π₁Σ)...)
|
||||
A = Alphabet(S)
|
||||
|
||||
# this is to fix the definitions of symplectic twists:
|
||||
# with i->gens(π₁Σ, i) the corresponding automorphisms return
|
||||
# reversed words
|
||||
domain = ntuple(i -> inv(gens(π₁Σ, i)), 2genus(π₁Σ))
|
||||
return AutomorphismGroup(π₁Σ, S, A, domain)
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
include("transvections.jl")
|
||||
include("gersten_relations.jl")
|
||||
|
||||
function SpecialAutomorphismGroup(
|
||||
F::FreeGroup;
|
||||
ordering = KnuthBendix.LenLex,
|
||||
kwargs...,
|
||||
)
|
||||
n = length(alphabet(F)) ÷ 2
|
||||
A, rels = gersten_relations(n; commutative = false)
|
||||
S = [A[i] for i in 1:2:length(A)]
|
||||
|
||||
max_rules = 1000 * n
|
||||
|
||||
rws = Logging.with_logger(Logging.NullLogger()) do
|
||||
rws = KnuthBendix.RewritingSystem(rels, ordering(A))
|
||||
# the rws is not confluent, let's suppress warning about it
|
||||
return KnuthBendix.knuthbendix(
|
||||
rws,
|
||||
KnuthBendix.Settings(; max_rules = max_rules, kwargs...),
|
||||
)
|
||||
end
|
||||
|
||||
idxA = KnuthBendix.IndexAutomaton(rws)
|
||||
return AutomorphismGroup(F, S, idxA, ntuple(i -> gens(F, i), n))
|
||||
end
|
||||
|
||||
function relations(G::AutomorphismGroup{<:FreeGroup})
|
||||
n = length(alphabet(object(G))) ÷ 2
|
||||
return last(gersten_relations(n; commutative = false))
|
||||
end
|
|
@ -25,7 +25,7 @@ function Te_diagonal(λ::Groups.ΡΛ, ϱ::Groups.ΡΛ, i::Integer)
|
|||
|
||||
if i == n
|
||||
τ = rotation_element(λ, ϱ)
|
||||
return inv(A, τ) * Te_diagonal(λ, ϱ, 1) * τ
|
||||
return inv(τ, A) * Te_diagonal(λ, ϱ, 1) * τ
|
||||
end
|
||||
|
||||
@assert 1 <= i < n
|
||||
|
@ -37,32 +37,40 @@ function Te_diagonal(λ::Groups.ΡΛ, ϱ::Groups.ΡΛ, i::Integer)
|
|||
|
||||
g = one(Word(Int[]))
|
||||
g *= λ[NJ, NI] # β ↦ α*β
|
||||
g *= λ[NI, I] * inv(A, ϱ[NI, J]) # α ↦ a*α*b^-1
|
||||
g *= inv(A, λ[NJ, NI]) # β ↦ b*α^-1*a^-1*α*β
|
||||
g *= λ[J, NI] * inv(A, λ[J, I]) # b ↦ α
|
||||
g *= inv(A, λ[J, NI]) # b ↦ b*α^-1*a^-1*α
|
||||
g *= inv(A, ϱ[J, NI]) * ϱ[J, I] # b ↦ b*α^-1*a^-1*α*b*α^-1
|
||||
g *= λ[NI, I] * inv(ϱ[NI, J], A) # α ↦ a*α*b^-1
|
||||
g *= inv(λ[NJ, NI], A) # β ↦ b*α^-1*a^-1*α*β
|
||||
g *= λ[J, NI] * inv(λ[J, I], A) # b ↦ α
|
||||
g *= inv(λ[J, NI], A) # b ↦ b*α^-1*a^-1*α
|
||||
g *= inv(ϱ[J, NI], A) * ϱ[J, I] # b ↦ b*α^-1*a^-1*α*b*α^-1
|
||||
g *= ϱ[J, NI] # b ↦ b*α^-1*a^-1*α*b*α^-1*a*α*b^-1
|
||||
|
||||
return g
|
||||
end
|
||||
|
||||
function Te_lantern(A::Alphabet, b₀::T, a₁::T, a₂::T, a₃::T, a₄::T, a₅::T) where {T}
|
||||
a₀ = (a₁ * a₂ * a₃)^4 * inv(A, b₀)
|
||||
function Te_lantern(
|
||||
A::Alphabet,
|
||||
b₀::T,
|
||||
a₁::T,
|
||||
a₂::T,
|
||||
a₃::T,
|
||||
a₄::T,
|
||||
a₅::T,
|
||||
) where {T}
|
||||
a₀ = (a₁ * a₂ * a₃)^4 * inv(b₀, A)
|
||||
X = a₄ * a₅ * a₃ * a₄ # from Primer
|
||||
b₁ = inv(A, X) * a₀ * X # from Primer
|
||||
b₁ = inv(X, A) * a₀ * X # from Primer
|
||||
Y = a₂ * a₃ * a₁ * a₂
|
||||
return inv(A, Y) * b₁ * Y # b₂ from Primer
|
||||
return inv(Y, A) * b₁ * Y # b₂ from Primer
|
||||
end
|
||||
|
||||
function Ta(λ::Groups.ΡΛ, i::Integer)
|
||||
@assert λ.id == :λ;
|
||||
return λ[mod1(λ.N-2i+1, λ.N), mod1(λ.N-2i+2, λ.N)]
|
||||
@assert λ.id == :λ
|
||||
return λ[mod1(λ.N - 2i + 1, λ.N), mod1(λ.N - 2i + 2, λ.N)]
|
||||
end
|
||||
|
||||
function Tα(λ::Groups.ΡΛ, i::Integer)
|
||||
@assert λ.id == :λ;
|
||||
return inv(λ.A, λ[mod1(λ.N-2i+2, λ.N), mod1(λ.N-2i+1, λ.N)])
|
||||
@assert λ.id == :λ
|
||||
return inv(λ[mod1(λ.N - 2i + 2, λ.N), mod1(λ.N - 2i + 1, λ.N)], λ.A)
|
||||
end
|
||||
|
||||
function Te(λ::ΡΛ, ϱ::ΡΛ, i, j)
|
||||
|
@ -73,7 +81,7 @@ function Te(λ::ΡΛ, ϱ::ΡΛ, i, j)
|
|||
@assert λ.id == :λ && ϱ.id == :ϱ
|
||||
|
||||
@assert iseven(λ.N)
|
||||
genus = λ.N÷2
|
||||
genus = λ.N ÷ 2
|
||||
i = mod1(i, genus)
|
||||
j = mod1(j, genus)
|
||||
|
||||
|
@ -85,16 +93,19 @@ function Te(λ::ΡΛ, ϱ::ΡΛ, i, j)
|
|||
if mod(j - (i + 1), genus) == 0
|
||||
return Te_diagonal(λ, ϱ, i)
|
||||
else
|
||||
return inv(A, Te_lantern(
|
||||
return inv(
|
||||
Te_lantern(
|
||||
A,
|
||||
# Our notation: # Primer notation:
|
||||
inv(Ta(λ, i + 1), A), # b₀
|
||||
inv(Ta(λ, i), A), # a₁
|
||||
inv(Tα(λ, i), A), # a₂
|
||||
inv(Te_diagonal(λ, ϱ, i), A), # a₃
|
||||
inv(Tα(λ, i + 1), A), # a₄
|
||||
inv(Te(λ, ϱ, i + 1, j), A), # a₅
|
||||
),
|
||||
A,
|
||||
# Our notation: # Primer notation:
|
||||
inv(A, Ta(λ, i + 1)), # b₀
|
||||
inv(A, Ta(λ, i)), # a₁
|
||||
inv(A, Tα(λ, i)), # a₂
|
||||
inv(A, Te_diagonal(λ, ϱ, i)), # a₃
|
||||
inv(A, Tα(λ, i + 1)), # a₄
|
||||
inv(A, Te(λ, ϱ, i + 1, j)), # a₅
|
||||
))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -105,7 +116,6 @@ Return the element of `G` which corresponds to shifting generators of the free g
|
|||
In the corresponding mapping class group this element acts by rotation of the surface anti-clockwise.
|
||||
"""
|
||||
function rotation_element(G::AutomorphismGroup{<:FreeGroup})
|
||||
|
||||
A = alphabet(G)
|
||||
@assert iseven(ngens(object(G)))
|
||||
genus = ngens(object(G)) ÷ 2
|
||||
|
@ -118,29 +128,32 @@ end
|
|||
|
||||
function rotation_element(λ::ΡΛ, ϱ::ΡΛ)
|
||||
@assert iseven(λ.N)
|
||||
genus = λ.N÷2
|
||||
genus = λ.N ÷ 2
|
||||
A = λ.A
|
||||
|
||||
halftwists = map(1:genus-1) do i
|
||||
j = i + 1
|
||||
x = Ta(λ, j) * inv(A, Ta(λ, i)) * Tα(λ, j) * Te_diagonal(λ, ϱ, i)
|
||||
δ = x * Tα(λ, i) * inv(A, x)
|
||||
x = Ta(λ, j) * inv(Ta(λ, i), A) * Tα(λ, j) * Te_diagonal(λ, ϱ, i)
|
||||
δ = x * Tα(λ, i) * inv(x, A)
|
||||
c =
|
||||
inv(A, Ta(λ, j)) *
|
||||
inv(Ta(λ, j), A) *
|
||||
Te(λ, ϱ, i, j) *
|
||||
Tα(λ, i)^2 *
|
||||
inv(A, δ) *
|
||||
inv(A, Ta(λ, j)) *
|
||||
inv(δ, A) *
|
||||
inv(Ta(λ, j), A) *
|
||||
Ta(λ, i) *
|
||||
δ
|
||||
z =
|
||||
Te_diagonal(λ, ϱ, i) *
|
||||
inv(A, Ta(λ, i)) *
|
||||
inv(Ta(λ, i), A) *
|
||||
Tα(λ, i) *
|
||||
Ta(λ, i) *
|
||||
inv(A, Te_diagonal(λ, ϱ, i))
|
||||
inv(Te_diagonal(λ, ϱ, i), A)
|
||||
|
||||
Ta(λ, i) * inv(A, Ta(λ, j) * Tα(λ, j))^6 * (Ta(λ, j) * Tα(λ, j) * z)^4 * c
|
||||
return Ta(λ, i) *
|
||||
inv(Ta(λ, j) * Tα(λ, j), A)^6 *
|
||||
(Ta(λ, j) * Tα(λ, j) * z)^4 *
|
||||
c
|
||||
end
|
||||
|
||||
τ = (Ta(λ, 1) * Tα(λ, 1))^6 * prod(halftwists)
|
||||
|
@ -148,7 +161,6 @@ function rotation_element(λ::ΡΛ, ϱ::ΡΛ)
|
|||
end
|
||||
|
||||
function mcg_twists(G::AutomorphismGroup{<:FreeGroup})
|
||||
|
||||
@assert iseven(ngens(object(G)))
|
||||
genus = ngens(object(G)) ÷ 2
|
||||
|
||||
|
@ -168,7 +180,7 @@ function mcg_twists(G::AutomorphismGroup{<:FreeGroup})
|
|||
return Tas, Tαs, Tes
|
||||
end
|
||||
|
||||
struct SymplecticMappingClass{T, F} <: GSymbol
|
||||
struct SymplecticMappingClass{T,F} <: GSymbol
|
||||
id::Symbol # :A, :B
|
||||
i::UInt
|
||||
j::UInt
|
||||
|
@ -178,7 +190,9 @@ struct SymplecticMappingClass{T, F} <: GSymbol
|
|||
f::F
|
||||
end
|
||||
|
||||
Base.:(==)(a::SymplecticMappingClass, b::SymplecticMappingClass) = a.autFn_word == b.autFn_word
|
||||
function Base.:(==)(a::SymplecticMappingClass, b::SymplecticMappingClass)
|
||||
return a.autFn_word == b.autFn_word
|
||||
end
|
||||
|
||||
Base.hash(a::SymplecticMappingClass, h::UInt) = hash(a.autFn_word, h)
|
||||
|
||||
|
@ -193,7 +207,7 @@ function SymplecticMappingClass(
|
|||
@assert i > 0 && j > 0
|
||||
id === :A && @assert i ≠ j
|
||||
@assert iseven(ngens(object(sautFn)))
|
||||
genus = ngens(object(sautFn))÷2
|
||||
genus = ngens(object(sautFn)) ÷ 2
|
||||
|
||||
A = alphabet(sautFn)
|
||||
λ = ΡΛ(:λ, A, 2genus)
|
||||
|
@ -201,24 +215,24 @@ function SymplecticMappingClass(
|
|||
|
||||
w = if id === :A
|
||||
Te(λ, ϱ, i, j) *
|
||||
inv(A, Ta(λ, i)) *
|
||||
inv(Ta(λ, i), A) *
|
||||
Tα(λ, i) *
|
||||
Ta(λ, i) *
|
||||
inv(A, Te(λ, ϱ, i, j)) *
|
||||
inv(A, Tα(λ, i)) *
|
||||
inv(A, Ta(λ, j))
|
||||
inv(Te(λ, ϱ, i, j), A) *
|
||||
inv(Tα(λ, i), A) *
|
||||
inv(Ta(λ, j), A)
|
||||
elseif id === :B
|
||||
if !minus
|
||||
if i ≠ j
|
||||
x = Ta(λ, j) * inv(A, Ta(λ, i)) * Tα(λ, j) * Te(λ, ϱ, i, j)
|
||||
δ = x * Tα(λ, i) * inv(A, x)
|
||||
Tα(λ, i) * Tα(λ, j) * inv(A, δ)
|
||||
x = Ta(λ, j) * inv(Ta(λ, i), A) * Tα(λ, j) * Te(λ, ϱ, i, j)
|
||||
δ = x * Tα(λ, i) * inv(x, A)
|
||||
Tα(λ, i) * Tα(λ, j) * inv(δ, A)
|
||||
else
|
||||
inv(A, Tα(λ, i))
|
||||
inv(Tα(λ, i), A)
|
||||
end
|
||||
else
|
||||
if i ≠ j
|
||||
Ta(λ, i) * Ta(λ, j) * inv(A, Te(λ, ϱ, i, j))
|
||||
Ta(λ, i) * Ta(λ, j) * inv(Te(λ, ϱ, i, j), A)
|
||||
else
|
||||
Ta(λ, i)
|
||||
end
|
||||
|
@ -241,12 +255,12 @@ end
|
|||
|
||||
function Base.show(io::IO, smc::SymplecticMappingClass)
|
||||
smc.minus && print(io, 'm')
|
||||
if smc.i < 10 && smc.j < 10
|
||||
if smc.i < 10 && smc.j < 10
|
||||
print(io, smc.id, subscriptify(smc.i), subscriptify(smc.j))
|
||||
else
|
||||
print(io, smc.id, subscriptify(smc.i), ".", subscriptify(smc.j))
|
||||
end
|
||||
smc.inv && print(io, "^-1")
|
||||
return smc.inv && print(io, "^-1")
|
||||
end
|
||||
|
||||
function Base.inv(m::SymplecticMappingClass)
|
||||
|
@ -259,7 +273,7 @@ end
|
|||
function evaluate!(
|
||||
t::NTuple{N,T},
|
||||
smc::SymplecticMappingClass,
|
||||
tmp=nothing,
|
||||
tmp = nothing,
|
||||
) where {N,T}
|
||||
t = smc.f(t)
|
||||
for i in 1:N
|
|
@ -14,21 +14,19 @@ 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))
|
||||
t.inv && print(io, "^-1")
|
||||
print(io, t.id, subscriptify(t.i), '.', subscriptify(t.j))
|
||||
return t.inv && print(io, "^-1")
|
||||
end
|
||||
|
||||
Base.inv(t::Transvection) = Transvection(t.id, t.i, t.j, !t.inv)
|
||||
|
||||
Base.:(==)(t::Transvection, s::Transvection) =
|
||||
t.id === s.id && t.i == s.i && t.j == s.j && t.inv == s.inv
|
||||
function Base.:(==)(t::Transvection, s::Transvection)
|
||||
return t.id === s.id && t.i == s.i && t.j == s.j && t.inv == s.inv
|
||||
end
|
||||
|
||||
Base.hash(t::Transvection, h::UInt) = hash(hash(t.id, hash(t.i)), hash(t.j, hash(t.inv, h)))
|
||||
function Base.hash(t::Transvection, h::UInt)
|
||||
return hash(hash(t.id, hash(t.i)), hash(t.j, hash(t.inv, h)))
|
||||
end
|
||||
|
||||
Base.@propagate_inbounds @inline function evaluate!(
|
||||
v::NTuple{T,N},
|
||||
|
@ -45,9 +43,9 @@ Base.@propagate_inbounds @inline function evaluate!(
|
|||
if !t.inv
|
||||
append!(word(v[i]), word(v[j]))
|
||||
else
|
||||
# append!(word(v[i]), inv(A, word(v[j])))
|
||||
# append!(word(v[i]), inv(word(v[j]), A))
|
||||
for l in Iterators.reverse(word(v[j]))
|
||||
push!(word(v[i]), inv(A, l))
|
||||
push!(word(v[i]), inv(l, A))
|
||||
end
|
||||
end
|
||||
else # if t.id === :λ
|
||||
|
@ -57,9 +55,9 @@ Base.@propagate_inbounds @inline function evaluate!(
|
|||
pushfirst!(word(v[i]), l)
|
||||
end
|
||||
else
|
||||
# prepend!(word(v[i]), inv(A, word(v[j])))
|
||||
# prepend!(word(v[i]), inv(word(v[j]), A))
|
||||
for l in word(v[j])
|
||||
pushfirst!(word(v[i]), inv(A, l))
|
||||
pushfirst!(word(v[i]), inv(l, A))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -84,7 +82,7 @@ end
|
|||
|
||||
function Base.show(io::IO, p::PermRightAut)
|
||||
print(io, 'σ')
|
||||
join(io, (subscriptify(Int(i)) for i in p.perm))
|
||||
return join(io, (subscriptify(Int(i)) for i in p.perm))
|
||||
end
|
||||
|
||||
Base.inv(p::PermRightAut) = PermRightAut(invperm(p.perm))
|
||||
|
@ -92,4 +90,6 @@ Base.inv(p::PermRightAut) = PermRightAut(invperm(p.perm))
|
|||
Base.:(==)(p::PermRightAut, q::PermRightAut) = p.perm == q.perm
|
||||
Base.hash(p::PermRightAut, h::UInt) = hash(p.perm, hash(PermRightAut, h))
|
||||
|
||||
evaluate!(v::NTuple{T,N}, p::PermRightAut, tmp = nothing) where {T,N} = v[p.perm]
|
||||
function evaluate!(v::NTuple{T,N}, p::PermRightAut, tmp = nothing) where {T,N}
|
||||
return v[p.perm]
|
||||
end
|
113
src/autgroups.jl
113
src/autgroups.jl
|
@ -1,18 +1,18 @@
|
|||
function KnuthBendix.Alphabet(S::AbstractVector{<:GSymbol})
|
||||
S = unique!([S; inv.(S)])
|
||||
S = union(S, inv.(S))
|
||||
inversions = [findfirst(==(inv(s)), S) for s in S]
|
||||
return Alphabet(S, inversions)
|
||||
end
|
||||
|
||||
struct AutomorphismGroup{G<:Group,T,R,S} <: AbstractFPGroup
|
||||
mutable struct AutomorphismGroup{G<:Group,T,RW,S} <: AbstractFPGroup
|
||||
group::G
|
||||
gens::Vector{T}
|
||||
rws::R
|
||||
rw::RW
|
||||
domain::S
|
||||
end
|
||||
|
||||
object(G::AutomorphismGroup) = G.group
|
||||
rewriting(G::AutomorphismGroup) = G.rws
|
||||
rewriting(G::AutomorphismGroup) = G.rw
|
||||
|
||||
function equality_data(f::AbstractFPGroupElement{<:AutomorphismGroup})
|
||||
imf = evaluate(f)
|
||||
|
@ -26,15 +26,18 @@ function equality_data(f::AbstractFPGroupElement{<:AutomorphismGroup})
|
|||
return imf
|
||||
end
|
||||
|
||||
function Base.:(==)(g::A, h::A) where {A<:AbstractFPGroupElement{<:AutomorphismGroup}}
|
||||
function Base.:(==)(
|
||||
g::A,
|
||||
h::A,
|
||||
) where {A<:AbstractFPGroupElement{<:AutomorphismGroup}}
|
||||
@assert parent(g) === parent(h)
|
||||
|
||||
if _isvalidhash(g) && _isvalidhash(h)
|
||||
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
|
||||
|
||||
|
@ -79,33 +82,47 @@ end
|
|||
|
||||
# eye-candy
|
||||
|
||||
Base.show(io::IO, ::Type{<:FPGroupElement{<:AutomorphismGroup{T}}}) where {T} =
|
||||
print(io, "Automorphism{$T, …}")
|
||||
function Base.show(
|
||||
io::IO,
|
||||
::Type{<:FPGroupElement{<:AutomorphismGroup{T}}},
|
||||
) where {T}
|
||||
return print(io, "Automorphism{$T, …}")
|
||||
end
|
||||
|
||||
Base.show(io::IO, A::AutomorphismGroup) = print(io, "automorphism group of ", object(A))
|
||||
function Base.show(io::IO, A::AutomorphismGroup)
|
||||
return print(io, "automorphism group of ", object(A))
|
||||
end
|
||||
|
||||
function Base.show(io::IO, ::MIME"text/plain", a::AbstractFPGroupElement{<:AutomorphismGroup})
|
||||
function Base.show(
|
||||
io::IO,
|
||||
::MIME"text/plain",
|
||||
a::AbstractFPGroupElement{<:AutomorphismGroup},
|
||||
)
|
||||
println(io, " ┌ $(a):")
|
||||
d = domain(a)
|
||||
im = evaluate(a)
|
||||
for (x, imx) in zip(d, im[1:end-1])
|
||||
println(io, " │ $x ↦ $imx")
|
||||
end
|
||||
println(io, " └ $(last(d)) ↦ $(last(im))")
|
||||
return println(io, " └ $(last(d)) ↦ $(last(im))")
|
||||
end
|
||||
|
||||
## Automorphism Evaluation
|
||||
|
||||
domain(f::AbstractFPGroupElement{<:AutomorphismGroup}) = deepcopy(parent(f).domain)
|
||||
function domain(f::AbstractFPGroupElement{<:AutomorphismGroup})
|
||||
return deepcopy(parent(f).domain)
|
||||
end
|
||||
# tuple(gens(object(parent(f)))...)
|
||||
|
||||
evaluate(f::AbstractFPGroupElement{<:AutomorphismGroup}) = evaluate!(domain(f), f)
|
||||
function evaluate(f::AbstractFPGroupElement{<:AutomorphismGroup})
|
||||
return evaluate!(domain(f), f)
|
||||
end
|
||||
|
||||
function evaluate!(
|
||||
t::NTuple{N,T},
|
||||
f::AbstractFPGroupElement{<:AutomorphismGroup{<:Group}},
|
||||
tmp = one(first(t)),
|
||||
) where {N, T<:FPGroupElement}
|
||||
) where {N,T<:FPGroupElement}
|
||||
A = alphabet(f)
|
||||
for idx in word(f)
|
||||
t = @inbounds evaluate!(t, A[idx], tmp)::NTuple{N,T}
|
||||
|
@ -113,47 +130,55 @@ function evaluate!(
|
|||
return t
|
||||
end
|
||||
|
||||
evaluate!(t::NTuple{N, T}, s::GSymbol, tmp=nothing) where {N, T} = throw("you need to implement `evaluate!(::$(typeof(t)), ::$(typeof(s)), ::Alphabet, tmp=one(first(t)))`")
|
||||
function evaluate!(t::NTuple{N,T}, s::GSymbol, tmp = nothing) where {N,T}
|
||||
throw(
|
||||
"you need to implement `evaluate!(::$(typeof(t)), ::$(typeof(s)), ::Alphabet, tmp=one(first(t)))`",
|
||||
)
|
||||
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(KnuthBendix.letters(lm.A))
|
||||
@boundscheck 1 ≤ i ≤ length(lm.A)
|
||||
|
||||
if !haskey(lm.indices_map, i)
|
||||
img = if haskey(lm.indices_map, inv(lm.A, i))
|
||||
inv(lm.A, lm.indices_map[inv(lm.A, i)])
|
||||
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
|
||||
|
@ -164,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
|
||||
|
@ -193,7 +219,7 @@ function generated_evaluate(a::FPGroupElement{<:AutomorphismGroup})
|
|||
push!(args[idx].args, :(d[$k]))
|
||||
continue
|
||||
end
|
||||
k = findfirst(==(inv(A, l)), first_ltrs)
|
||||
k = findfirst(==(inv(l, A)), first_ltrs)
|
||||
if k !== nothing
|
||||
push!(args[idx].args, :(inv(d[$k])))
|
||||
continue
|
||||
|
@ -201,13 +227,14 @@ function generated_evaluate(a::FPGroupElement{<:AutomorphismGroup})
|
|||
throw("Letter $l doesn't seem to be mapped anywhere!")
|
||||
end
|
||||
end
|
||||
locals = Dict{Expr, Symbol}()
|
||||
locals = Dict{Expr,Symbol}()
|
||||
locals_counter = 0
|
||||
for (i,v) in enumerate(args)
|
||||
for (i, v) in enumerate(args)
|
||||
@assert length(v.args) >= 2
|
||||
if length(v.args) > 2
|
||||
for (j, a) in pairs(v.args)
|
||||
if a isa Expr && a.head == :call "$a"
|
||||
if a isa Expr && a.head == :call
|
||||
"$a"
|
||||
@assert a.args[1] == :inv
|
||||
if !(a in keys(locals))
|
||||
locals[a] = Symbol("var_#$locals_counter")
|
||||
|
@ -222,7 +249,7 @@ function generated_evaluate(a::FPGroupElement{<:AutomorphismGroup})
|
|||
end
|
||||
|
||||
q = quote
|
||||
$([:(local $v = $k) for (k,v) in locals]...)
|
||||
$([:(local $v = $k) for (k, v) in locals]...)
|
||||
end
|
||||
|
||||
# return args, locals
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
module Constructions
|
||||
|
||||
using GroupsCore
|
||||
import GroupsCore.Random
|
||||
|
||||
include("direct_product.jl")
|
||||
include("direct_power.jl")
|
||||
include("wreath_product.jl")
|
||||
|
||||
end # of module Constructions
|
|
@ -0,0 +1,144 @@
|
|||
struct DirectPower{Gr,N,GEl<:GroupsCore.GroupElement} <: GroupsCore.Group
|
||||
group::Gr
|
||||
|
||||
function DirectPower{N}(G::GroupsCore.Group) where {N}
|
||||
@assert N > 1
|
||||
return new{typeof(G),N,eltype(G)}(G)
|
||||
end
|
||||
end
|
||||
|
||||
struct DirectPowerElement{GEl,N,Gr<:GroupsCore.Group} <: GroupsCore.GroupElement
|
||||
elts::NTuple{N,GEl}
|
||||
parent::DirectPower{Gr,N,GEl}
|
||||
end
|
||||
|
||||
function DirectPowerElement(
|
||||
elts::AbstractVector{<:GroupsCore.GroupElement},
|
||||
G::DirectPower,
|
||||
)
|
||||
return DirectPowerElement(ntuple(i -> elts[i], _nfold(G)), G)
|
||||
end
|
||||
|
||||
_nfold(::DirectPower{Gr,N}) where {Gr,N} = N
|
||||
|
||||
function Base.one(G::DirectPower)
|
||||
return DirectPowerElement(ntuple(_ -> one(G.group), _nfold(G)), G)
|
||||
end
|
||||
|
||||
function Base.eltype(::Type{<:DirectPower{Gr,N,GEl}}) where {Gr,N,GEl}
|
||||
return DirectPowerElement{GEl,N,Gr}
|
||||
end
|
||||
|
||||
function Base.iterate(G::DirectPower)
|
||||
itr = Iterators.ProductIterator(ntuple(i -> G.group, _nfold(G)))
|
||||
res = iterate(itr)
|
||||
@assert res !== nothing
|
||||
elt = DirectPowerElement(first(res), G)
|
||||
return elt, (iterator = itr, state = last(res))
|
||||
end
|
||||
|
||||
function Base.iterate(G::DirectPower, state)
|
||||
itr, st = state.iterator, state.state
|
||||
res = iterate(itr, st)
|
||||
res === nothing && return nothing
|
||||
elt = DirectPowerElement(first(res), G)
|
||||
return elt, (iterator = itr, state = last(res))
|
||||
end
|
||||
|
||||
function Base.IteratorSize(::Type{<:DirectPower{Gr,N}}) where {Gr,N}
|
||||
Base.IteratorSize(Gr) isa Base.HasLength && return Base.HasShape{N}()
|
||||
Base.IteratorSize(Gr) isa Base.HasShape && return Base.HasShape{N}()
|
||||
return Base.IteratorSize(Gr)
|
||||
end
|
||||
|
||||
Base.size(G::DirectPower) = ntuple(_ -> length(G.group), _nfold(G))
|
||||
|
||||
function GroupsCore.order(::Type{I}, G::DirectPower) where {I<:Integer}
|
||||
return convert(I, order(I, G.group)^_nfold(G))
|
||||
end
|
||||
|
||||
GroupsCore.ngens(G::DirectPower) = _nfold(G) * ngens(G.group)
|
||||
|
||||
function GroupsCore.gens(G::DirectPower)
|
||||
N = _nfold(G)
|
||||
S = gens(G.group)
|
||||
tups = [ntuple(j -> (i == j ? s : one(s)), N) for i in 1:N for s in S]
|
||||
|
||||
return [DirectPowerElement(elts, G) for elts in tups]
|
||||
end
|
||||
|
||||
Base.isfinite(G::DirectPower) = isfinite(G.group)
|
||||
|
||||
GroupsCore.parent(g::DirectPowerElement) = g.parent
|
||||
|
||||
function Base.:(==)(g::DirectPowerElement, h::DirectPowerElement)
|
||||
return (parent(g) === parent(h) && g.elts == h.elts)
|
||||
end
|
||||
|
||||
Base.hash(g::DirectPowerElement, h::UInt) = hash(g.elts, hash(parent(g), h))
|
||||
|
||||
Base.inv(g::DirectPowerElement) = DirectPowerElement(inv.(g.elts), parent(g))
|
||||
|
||||
function Base.:(*)(g::DirectPowerElement, h::DirectPowerElement)
|
||||
@assert parent(g) === parent(h)
|
||||
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
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
function Base.show(io::IO, g::DirectPowerElement)
|
||||
print(io, "( ")
|
||||
join(io, g.elts, ", ")
|
||||
return print(" )")
|
||||
end
|
||||
|
||||
# convienience:
|
||||
Base.@propagate_inbounds function Base.getindex(
|
||||
g::DirectPowerElement,
|
||||
i::Integer,
|
||||
)
|
||||
return g.elts[i]
|
||||
end
|
|
@ -0,0 +1,138 @@
|
|||
struct DirectProduct{Gt,Ht,GEl,HEl} <: GroupsCore.Group
|
||||
first::Gt
|
||||
last::Ht
|
||||
|
||||
function DirectProduct(G::GroupsCore.Group, H::GroupsCore.Group)
|
||||
return new{typeof(G),typeof(H),eltype(G),eltype(H)}(G, H)
|
||||
end
|
||||
end
|
||||
|
||||
struct DirectProductElement{GEl,HEl,Gt,Ht} <: GroupsCore.GroupElement
|
||||
elts::Tuple{GEl,HEl}
|
||||
parent::DirectProduct{Gt,Ht,GEl,HEl}
|
||||
end
|
||||
|
||||
DirectProductElement(g, h, G::DirectProduct) = DirectProduct((g, h), G)
|
||||
|
||||
function Base.one(G::DirectProduct)
|
||||
return DirectProductElement((one(G.first), one(G.last)), G)
|
||||
end
|
||||
|
||||
function Base.eltype(
|
||||
::Type{<:DirectProduct{Gt,Ht,GEl,HEl}},
|
||||
) where {Gt,Ht,GEl,HEl}
|
||||
return DirectProductElement{GEl,HEl,Gt,Ht}
|
||||
end
|
||||
|
||||
function Base.iterate(G::DirectProduct)
|
||||
itr = Iterators.product(G.first, G.last)
|
||||
res = iterate(itr)
|
||||
@assert res !== nothing
|
||||
elt = DirectProductElement(first(res), G)
|
||||
return elt, (iterator = itr, state = last(res))
|
||||
end
|
||||
|
||||
function Base.iterate(G::DirectProduct, state)
|
||||
itr, st = state.iterator, state.state
|
||||
res = iterate(itr, st)
|
||||
res === nothing && return nothing
|
||||
elt = DirectProductElement(first(res), G)
|
||||
return elt, (iterator = itr, state = last(res))
|
||||
end
|
||||
|
||||
function Base.IteratorSize(::Type{<:DirectProduct{Gt,Ht}}) where {Gt,Ht}
|
||||
Gi = Base.IteratorSize(Gt)
|
||||
Hi = Base.IteratorSize(Ht)
|
||||
if Gi isa Base.IsInfinite || Hi isa Base.IsInfinite
|
||||
return Base.IsInfinite()
|
||||
elseif Gi isa Base.SizeUnknown || Hi isa Base.SizeUnknown
|
||||
return Base.SizeUnknown()
|
||||
else
|
||||
return Base.HasShape{2}()
|
||||
end
|
||||
end
|
||||
|
||||
Base.size(G::DirectProduct) = (length(G.first), length(G.last))
|
||||
|
||||
function GroupsCore.order(::Type{I}, G::DirectProduct) where {I<:Integer}
|
||||
return convert(I, order(I, G.first) * order(I, G.last))
|
||||
end
|
||||
|
||||
GroupsCore.ngens(G::DirectProduct) = ngens(G.first) + ngens(G.last)
|
||||
|
||||
function GroupsCore.gens(G::DirectProduct)
|
||||
gens_first =
|
||||
[DirectProductElement((g, one(G.last)), G) for g in gens(G.first)]
|
||||
|
||||
gens_last =
|
||||
[DirectProductElement((one(G.first), g), G) for g in gens(G.last)]
|
||||
|
||||
return [gens_first; gens_last]
|
||||
end
|
||||
|
||||
Base.isfinite(G::DirectProduct) = isfinite(G.first) && isfinite(G.last)
|
||||
|
||||
GroupsCore.parent(g::DirectProductElement) = g.parent
|
||||
|
||||
function Base.:(==)(g::DirectProductElement, h::DirectProductElement)
|
||||
return (parent(g) === parent(h) && g.elts == h.elts)
|
||||
end
|
||||
|
||||
Base.hash(g::DirectProductElement, h::UInt) = hash(g.elts, hash(parent(g), h))
|
||||
|
||||
function Base.inv(g::DirectProductElement)
|
||||
return DirectProductElement(inv.(g.elts), parent(g))
|
||||
end
|
||||
|
||||
function Base.:(*)(g::DirectProductElement, h::DirectProductElement)
|
||||
@assert parent(g) === parent(h)
|
||||
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
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
function Base.show(io::IO, g::DirectProductElement)
|
||||
print(io, "( ")
|
||||
join(io, g.elts, ", ")
|
||||
return print(io, " )")
|
||||
end
|
||||
|
||||
# convienience:
|
||||
Base.@propagate_inbounds function Base.getindex(
|
||||
g::DirectProductElement,
|
||||
i::Integer,
|
||||
)
|
||||
return g.elts[i]
|
||||
end
|
|
@ -0,0 +1,156 @@
|
|||
import PermutationGroups as PG
|
||||
|
||||
"""
|
||||
WreathProduct(G::Group, P::AbstractPermutationGroup) <: Group
|
||||
Return the wreath product of a group `G` by permutation group `P`, usually
|
||||
written as `G ≀ P`.
|
||||
|
||||
As set `G ≀ P` is the same as `Gᵈ × P` and the group can be understood as a
|
||||
semi-direct product of `P` acting on `d`-fold cartesian product of `G` by
|
||||
permuting coordinates. To be more precise, the multiplication inside wreath
|
||||
product is defined as
|
||||
> `(n, σ) * (m, τ) = (n*(m^σ), σ*τ)`
|
||||
where `m^σ` denotes the action (from the right) of the permutation `σ` on
|
||||
`d`-tuples of elements from `G`.
|
||||
"""
|
||||
struct WreathProduct{DP<:DirectPower,PGr<:PG.AbstractPermutationGroup} <:
|
||||
GroupsCore.Group
|
||||
N::DP
|
||||
P::PGr
|
||||
|
||||
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<:PG.AP.AbstractPermutation,
|
||||
Wr<:WreathProduct,
|
||||
} <: GroupsCore.GroupElement
|
||||
n::DPEl
|
||||
p::PEl
|
||||
parent::Wr
|
||||
|
||||
function WreathProductElement(
|
||||
n::DirectPowerElement,
|
||||
p::PG.AP.AbstractPermutation,
|
||||
W::WreathProduct,
|
||||
)
|
||||
return new{typeof(n),typeof(p),typeof(W)}(n, p, W)
|
||||
end
|
||||
end
|
||||
|
||||
Base.one(W::WreathProduct) = WreathProductElement(one(W.N), one(W.P), W)
|
||||
|
||||
function Base.eltype(::Type{<:WreathProduct{DP,PGr}}) where {DP,PGr}
|
||||
return WreathProductElement{eltype(DP),eltype(PGr),WreathProduct{DP,PGr}}
|
||||
end
|
||||
|
||||
function Base.iterate(G::WreathProduct)
|
||||
itr = Iterators.product(G.N, G.P)
|
||||
res = iterate(itr)
|
||||
@assert res !== nothing
|
||||
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
|
||||
res = iterate(itr, st)
|
||||
res === nothing && return nothing
|
||||
(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}
|
||||
dpI = Base.IteratorSize(DP)
|
||||
pgI = Base.IteratorSize(PGr)
|
||||
|
||||
if dpI isa Base.IsInfinite || pgI isa Base.IsInfinite
|
||||
return Base.IsInfinite()
|
||||
elseif dpI isa Base.SizeUnknown || pgI isa Base.SizeUnknown
|
||||
return Base.SizeUnknown()
|
||||
else
|
||||
return Base.HasShape{2}()
|
||||
end
|
||||
end
|
||||
|
||||
Base.size(G::WreathProduct) = (length(G.N), length(G.P))
|
||||
|
||||
function GroupsCore.order(::Type{I}, G::WreathProduct) where {I<:Integer}
|
||||
return convert(I, order(I, G.N) * order(I, G.P))
|
||||
end
|
||||
|
||||
function GroupsCore.gens(G::WreathProduct)
|
||||
N_gens = [WreathProductElement(n, one(G.P), G) for n in gens(G.N)]
|
||||
P_gens = [WreathProductElement(one(G.N), p, G) for p in gens(G.P)]
|
||||
return [N_gens; P_gens]
|
||||
end
|
||||
|
||||
Base.isfinite(G::WreathProduct) = isfinite(G.N) && isfinite(G.P)
|
||||
|
||||
GroupsCore.parent(g::WreathProductElement) = g.parent
|
||||
|
||||
function Base.:(==)(g::WreathProductElement, h::WreathProductElement)
|
||||
return parent(g) === parent(h) && g.n == h.n && g.p == h.p
|
||||
end
|
||||
|
||||
function Base.hash(g::WreathProductElement, h::UInt)
|
||||
return hash(g.n, hash(g.p, hash(g.parent, h)))
|
||||
end
|
||||
|
||||
function _act(p::PG.AP.AbstractPermutation, n::DirectPowerElement)
|
||||
return DirectPowerElement(
|
||||
ntuple(i -> n.elts[i^p], length(n.elts)),
|
||||
parent(n),
|
||||
)
|
||||
end
|
||||
|
||||
function Base.inv(g::WreathProductElement)
|
||||
pinv = inv(g.p)
|
||||
return WreathProductElement(_act(pinv, inv(g.n)), pinv, parent(g))
|
||||
end
|
||||
|
||||
function Base.:(*)(g::WreathProductElement, h::WreathProductElement)
|
||||
@assert parent(g) === parent(h)
|
||||
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)
|
||||
end
|
||||
|
||||
function Base.show(io::IO, g::WreathProductElement)
|
||||
return print(io, "( ", g.n, "≀", g.p, " )")
|
||||
end
|
|
@ -1,103 +0,0 @@
|
|||
struct SurfaceGroup{T, S, R} <: AbstractFPGroup
|
||||
genus::Int
|
||||
boundaries::Int
|
||||
gens::Vector{T}
|
||||
relations::Vector{<:Pair{S,S}}
|
||||
rws::R
|
||||
end
|
||||
|
||||
include("symplectic_twists.jl")
|
||||
|
||||
genus(S::SurfaceGroup) = S.genus
|
||||
|
||||
function Base.show(io::IO, S::SurfaceGroup)
|
||||
print(io, "π₁ of the orientable surface of genus $(genus(S))")
|
||||
if S.boundaries > 0
|
||||
print(io, " with $(S.boundaries) boundary components")
|
||||
end
|
||||
end
|
||||
|
||||
function SurfaceGroup(genus::Integer, boundaries::Integer)
|
||||
@assert genus > 1
|
||||
|
||||
# The (confluent) rewriting systems comes from
|
||||
# S. Hermiller, Rewriting systems for Coxeter groups
|
||||
# Journal of Pure and Applied Algebra
|
||||
# Volume 92, Issue 2, 7 March 1994, Pages 137-148
|
||||
# https://doi.org/10.1016/0022-4049(94)90019-1
|
||||
# Note: the notation is "inverted":
|
||||
# a_g of the article becomes A_g here.
|
||||
|
||||
ltrs = String[]
|
||||
for i in 1:genus
|
||||
subscript = join('₀'+d for d in reverse(digits(i)))
|
||||
append!(ltrs, ["A" * subscript, "a" * subscript, "B" * subscript, "b" * subscript])
|
||||
end
|
||||
Al = Alphabet(reverse!(ltrs))
|
||||
|
||||
for i in 1:genus
|
||||
subscript = join('₀'+d for d in reverse(digits(i)))
|
||||
KnuthBendix.set_inversion!(Al, "a" * subscript, "A" * subscript)
|
||||
KnuthBendix.set_inversion!(Al, "b" * subscript, "B" * subscript)
|
||||
end
|
||||
|
||||
if boundaries == 0
|
||||
word = Int[]
|
||||
|
||||
for i in reverse(1:genus)
|
||||
x = 4 * i
|
||||
append!(word, [x, x-2, x-1, x-3])
|
||||
end
|
||||
comms = Word(word)
|
||||
word_rels = [ comms => one(comms) ]
|
||||
|
||||
rws = KnuthBendix.RewritingSystem(word_rels, KnuthBendix.RecursivePathOrder(Al))
|
||||
KnuthBendix.knuthbendix!(rws)
|
||||
elseif boundaries == 1
|
||||
S = typeof(one(Word(Int[])))
|
||||
word_rels = Pair{S, S}[]
|
||||
rws = RewritingSystem(word_rels, KnuthBendix.LenLex(Al))
|
||||
else
|
||||
throw("Not Implemented")
|
||||
end
|
||||
|
||||
F = FreeGroup(alphabet(rws))
|
||||
rels = [F(lhs)=>F(rhs) for (lhs,rhs) in word_rels]
|
||||
|
||||
return SurfaceGroup(genus, boundaries, KnuthBendix.letters(Al)[2:2:end], rels, rws)
|
||||
end
|
||||
|
||||
rewriting(S::SurfaceGroup) = S.rws
|
||||
KnuthBendix.alphabet(S::SurfaceGroup) = alphabet(rewriting(S))
|
||||
relations(S::SurfaceGroup) = S.relations
|
||||
|
||||
function symplectic_twists(π₁Σ::SurfaceGroup)
|
||||
g = genus(π₁Σ)
|
||||
|
||||
saut = SpecialAutomorphismGroup(FreeGroup(2g), maxrules=100)
|
||||
|
||||
Aij = [SymplecticMappingClass(saut, :A, i, j) for i in 1:g for j in 1:g if i≠j]
|
||||
|
||||
Bij = [SymplecticMappingClass(saut, :B, i, j) for i in 1:g for j in 1:g if i≠j]
|
||||
|
||||
mBij = [SymplecticMappingClass(saut, :B, i, j, minus=true) for i in 1:g for j in 1:g if i≠j]
|
||||
|
||||
Bii = [SymplecticMappingClass(saut, :B, i, i) for i in 1:g]
|
||||
|
||||
mBii = [SymplecticMappingClass(saut, :B, i, i, minus=true) for i in 1:g]
|
||||
|
||||
return [Aij; Bij; mBij; Bii; mBii]
|
||||
end
|
||||
|
||||
KnuthBendix.alphabet(G::AutomorphismGroup{<:SurfaceGroup}) = rewriting(G)
|
||||
|
||||
function AutomorphismGroup(π₁Σ::SurfaceGroup; kwargs...)
|
||||
S = vcat(symplectic_twists(π₁Σ)...)
|
||||
A = Alphabet(S)
|
||||
|
||||
# this is to fix the definitions of symplectic twists:
|
||||
# with i->gens(π₁Σ, i) the corresponding automorphisms return
|
||||
# reversed words
|
||||
domain = ntuple(i->inv(gens(π₁Σ, i)), 2genus(π₁Σ))
|
||||
return AutomorphismGroup(π₁Σ, S, A, domain)
|
||||
end
|
|
@ -1,22 +0,0 @@
|
|||
include("transvections.jl")
|
||||
include("gersten_relations.jl")
|
||||
|
||||
function SpecialAutomorphismGroup(F::FreeGroup; ordering = KnuthBendix.LenLex, kwargs...)
|
||||
|
||||
n = length(alphabet(F)) ÷ 2
|
||||
A, rels = gersten_relations(n, commutative = false)
|
||||
S = KnuthBendix.letters(A)[1:2(n^2-n)]
|
||||
|
||||
maxrules = 1000*n
|
||||
|
||||
rws = KnuthBendix.RewritingSystem(rels, ordering(A))
|
||||
KnuthBendix.knuthbendix!(rws; maxrules=maxrules, kwargs...)
|
||||
return AutomorphismGroup(F, S, rws, ntuple(i -> gens(F, i), n))
|
||||
end
|
||||
|
||||
KnuthBendix.alphabet(G::AutomorphismGroup{<:FreeGroup}) = alphabet(rewriting(G))
|
||||
|
||||
function relations(G::AutomorphismGroup{<:FreeGroup})
|
||||
n = length(alphabet(object(G))) ÷ 2
|
||||
return last(gersten_relations(n, commutative = false))
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
## Hashing
|
||||
|
||||
equality_data(g::AbstractFPGroupElement) = (normalform!(g); word(g))
|
||||
equality_data(g::AbstractFPGroupElement) = word(g)
|
||||
|
||||
bitget(h::UInt, n::Int) = Bool((h & (1 << n)) >> n)
|
||||
bitclear(h::UInt, n::Int) = h & ~(1 << n)
|
||||
|
@ -20,8 +20,12 @@ _isvalidhash(g::AbstractFPGroupElement) = bitget(g.savedhash, 1)
|
|||
_setnormalform(h::UInt, v::Bool) = bitset(h, v, 0)
|
||||
_setvalidhash(h::UInt, v::Bool) = bitset(h, v, 1)
|
||||
|
||||
_setnormalform!(g::AbstractFPGroupElement, v::Bool) = g.savedhash = _setnormalform(g.savedhash, v)
|
||||
_setvalidhash!(g::AbstractFPGroupElement, v::Bool) = g.savedhash = _setvalidhash(g.savedhash, v)
|
||||
function _setnormalform!(g::AbstractFPGroupElement, v::Bool)
|
||||
return g.savedhash = _setnormalform(g.savedhash, v)
|
||||
end
|
||||
function _setvalidhash!(g::AbstractFPGroupElement, v::Bool)
|
||||
return g.savedhash = _setvalidhash(g.savedhash, v)
|
||||
end
|
||||
|
||||
# To update hash use this internal method, possibly only after computing the
|
||||
# normal form of `g`:
|
||||
|
@ -33,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
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
"""
|
||||
Homomorphism(f, G::AbstractFPGroup, H::AbstractFPGroup[, check=true])
|
||||
Struct representing homomorphism map from `G` to `H` given by map `f`.
|
||||
|
||||
To define `h = Homomorphism(f, G, H)` function (or just callable) `f` must
|
||||
implement method `f(i::Integer, source, target)::AbstractWord` with the
|
||||
following meaning. Suppose that word `w = Word([i])` consists of a single
|
||||
letter in the `alphabet` of `source` (usually it means that in `G` it
|
||||
represents a generator or its inverse). Then `f(i, G, H)` must return the
|
||||
**word** representing the image in `H` of `G(w)` under the homomorphism.
|
||||
|
||||
In more mathematical terms it means that if `h(G(w)) == h`, then
|
||||
`f(i, G, H) == word(h)`.
|
||||
|
||||
Images of both `AbstractWord`s and elements of `G` can be obtained by simply
|
||||
calling `h(w)`, or `h(g)`.
|
||||
|
||||
If `check=true` then the correctness of the definition of `h` will be performed
|
||||
when creating the homomorphism.
|
||||
|
||||
!!! note
|
||||
`f(i, G, H)` must be implemented for all letters in the alphabet of `G`,
|
||||
not only for those `i` which represent `gens(G)`. Function `f` will be
|
||||
evaluated exactly once per letter of `alphabet(G)` and the results will be
|
||||
cached.
|
||||
|
||||
# Examples
|
||||
```julia
|
||||
julia> F₂ = FreeGroup(2)
|
||||
free group on 2 generators
|
||||
|
||||
julia> g,h = gens(F₂)
|
||||
2-element Vector{FPGroupElement{FreeGroup{Symbol, KnuthBendix.LenLex{Symbol}}, …}}:
|
||||
f1
|
||||
f2
|
||||
|
||||
julia> ℤ² = FPGroup(F₂, [g*h => h*g])
|
||||
Finitely presented group generated by:
|
||||
{ f1 f2 },
|
||||
subject to relations:
|
||||
f1*f2 => f2*f1
|
||||
|
||||
julia> hom = Groups.Homomorphism(
|
||||
(i, G, H) -> Groups.word_type(H)([i]),
|
||||
F₂,
|
||||
ℤ²
|
||||
)
|
||||
Homomorphism
|
||||
from : free group on 2 generators
|
||||
to : ⟨ f1 f2 |
|
||||
f1*f2 => f2*f1 ⟩
|
||||
|
||||
julia> hom(g*h*inv(g))
|
||||
f2
|
||||
|
||||
julia> hom(g*h*inv(g)) == hom(h)
|
||||
true
|
||||
```
|
||||
|
||||
"""
|
||||
struct Homomorphism{Gr1,Gr2,I,W}
|
||||
gens_images::Dict{I,W}
|
||||
source::Gr1
|
||||
target::Gr2
|
||||
|
||||
function Homomorphism(
|
||||
f,
|
||||
source::AbstractFPGroup,
|
||||
target::AbstractFPGroup;
|
||||
check = true,
|
||||
)
|
||||
A = alphabet(source)
|
||||
dct = Dict(
|
||||
i => convert(word_type(target), f(i, source, target)) for
|
||||
i in 1:length(A)
|
||||
)
|
||||
I = eltype(word_type(source))
|
||||
W = word_type(target)
|
||||
hom = new{typeof(source),typeof(target),I,W}(dct, source, target)
|
||||
|
||||
if check
|
||||
@assert hom(one(source)) == one(target)
|
||||
for x in gens(source)
|
||||
@assert hom(x^-1) == hom(x)^-1
|
||||
|
||||
for y in gens(source)
|
||||
@assert hom(x * y) == hom(x) * hom(y)
|
||||
@assert hom(x * y)^-1 == hom(y^-1) * hom(x^-1)
|
||||
end
|
||||
end
|
||||
for (lhs, rhs) in relations(source)
|
||||
relator = lhs * inv(rhs, alphabet(source))
|
||||
im_r = hom.target(hom(relator))
|
||||
@assert isone(im_r) "Map does not define a homomorphism: h($relator) = $(im_r) ≠ $(one(target))."
|
||||
end
|
||||
end
|
||||
return hom
|
||||
end
|
||||
end
|
||||
|
||||
function (h::Homomorphism)(w::AbstractWord)
|
||||
result = one(word_type(h.target)) # Word
|
||||
for l in w
|
||||
append!(result, h.gens_images[l])
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function (h::Homomorphism)(g::AbstractFPGroupElement)
|
||||
@assert parent(g) === h.source
|
||||
w = h(word(g))
|
||||
return h.target(w)
|
||||
end
|
||||
|
||||
function Base.show(io::IO, h::Homomorphism)
|
||||
return print(io, "Homomorphism\n from : $(h.source)\n to : $(h.target)")
|
||||
end
|
|
@ -1,4 +1,6 @@
|
|||
mutable struct FPGroupIter{S, T, GEl}
|
||||
import OrderedCollections: OrderedSet
|
||||
|
||||
mutable struct FPGroupIter{S,T,GEl}
|
||||
seen::S
|
||||
seen_iter_state::T
|
||||
current::GEl
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
module MatrixGroups
|
||||
|
||||
import LinearAlgebra # Identity matrix
|
||||
|
||||
using StaticArrays
|
||||
|
||||
using GroupsCore
|
||||
import GroupsCore.Random # GroupsCore rand
|
||||
using ..Groups
|
||||
using Groups.KnuthBendix
|
||||
|
||||
export MatrixGroup, SpecialLinearGroup, SymplecticGroup
|
||||
|
||||
include("abstract.jl")
|
||||
|
||||
include("matrix_group.jl")
|
||||
include("SLn.jl")
|
||||
include("Spn.jl")
|
||||
|
||||
end # module
|
|
@ -0,0 +1,35 @@
|
|||
include("eltary_matrices.jl")
|
||||
|
||||
struct SpecialLinearGroup{N,T,R,S} <: AbstractMatrixGroup{N,T}
|
||||
base_ring::R
|
||||
alphabet::Alphabet{S}
|
||||
gens::Vector{S}
|
||||
|
||||
function SpecialLinearGroup{N}(base_ring) where {N}
|
||||
S = [
|
||||
ElementaryMatrix{N}(i, j, one(base_ring)) for i in 1:N for
|
||||
j in 1:N if i ≠ j
|
||||
]
|
||||
alphabet = Alphabet(S)
|
||||
|
||||
T = eltype(base_ring)
|
||||
R = typeof(base_ring)
|
||||
St = eltype(S)
|
||||
|
||||
return new{N,T,R,St}(base_ring, alphabet, S)
|
||||
end
|
||||
end
|
||||
|
||||
GroupsCore.ngens(SL::SpecialLinearGroup) = length(SL.gens)
|
||||
|
||||
function Base.show(io::IO, ::SpecialLinearGroup{N,T}) where {N,T}
|
||||
return print(io, "SL{$N,$T}")
|
||||
end
|
||||
|
||||
function Base.show(
|
||||
io::IO,
|
||||
::MIME"text/plain",
|
||||
SL::SpecialLinearGroup{N,T},
|
||||
) where {N,T}
|
||||
return print(io, "special linear group of $N×$N matrices over $T")
|
||||
end
|
|
@ -0,0 +1,70 @@
|
|||
include("eltary_symplectic.jl")
|
||||
|
||||
struct SymplecticGroup{N,T,R,S} <: AbstractMatrixGroup{N,T}
|
||||
base_ring::R
|
||||
alphabet::Alphabet{S}
|
||||
gens::Vector{S}
|
||||
|
||||
function SymplecticGroup{N}(base_ring) where {N}
|
||||
S = symplectic_gens(N, eltype(base_ring))
|
||||
alphabet = Alphabet(S)
|
||||
|
||||
T = eltype(base_ring)
|
||||
R = typeof(base_ring)
|
||||
St = eltype(S)
|
||||
|
||||
return new{N,T,R,St}(base_ring, alphabet, S)
|
||||
end
|
||||
end
|
||||
|
||||
GroupsCore.ngens(Sp::SymplecticGroup) = length(Sp.gens)
|
||||
|
||||
Base.show(io::IO, ::SymplecticGroup{N,T}) where {N,T} = print(io, "Sp{$N,$T}")
|
||||
|
||||
function Base.show(io::IO, ::MIME"text/plain", ::SymplecticGroup{N}) where {N}
|
||||
return print(io, "group of $N×$N symplectic matrices")
|
||||
end
|
||||
|
||||
function symplectic_gens(N, T = Int8)
|
||||
iseven(N) || throw(ArgumentError("N needs to be even!"))
|
||||
n = N ÷ 2
|
||||
|
||||
_offdiag_idcs(n) = ((i, j) for i in 1:n for j in 1:n if i ≠ j)
|
||||
|
||||
a_ijs = [
|
||||
ElementarySymplectic{N}(:A, i, j, one(T)) for (i, j) in _offdiag_idcs(n)
|
||||
]
|
||||
b_is = [ElementarySymplectic{N}(:B, n + i, i, one(T)) for i in 1:n]
|
||||
c_ijs = [
|
||||
ElementarySymplectic{N}(:B, n + i, j, one(T)) for
|
||||
(i, j) in _offdiag_idcs(n)
|
||||
]
|
||||
|
||||
S = [a_ijs; b_is; c_ijs]
|
||||
|
||||
S = [S; transpose.(S)]
|
||||
|
||||
return unique(S)
|
||||
end
|
||||
|
||||
function _std_symplectic_form(m::AbstractMatrix)
|
||||
r, c = size(m)
|
||||
r == c || return false
|
||||
iseven(r) || return false
|
||||
|
||||
n = r ÷ 2
|
||||
𝕆 = zeros(eltype(m), n, n)
|
||||
𝕀 = one(eltype(m)) * LinearAlgebra.I
|
||||
Ω = [
|
||||
𝕆 -𝕀
|
||||
𝕀 𝕆
|
||||
]
|
||||
return Ω
|
||||
end
|
||||
|
||||
function issymplectic(
|
||||
mat::M,
|
||||
Ω = _std_symplectic_form(mat),
|
||||
) where {M<:AbstractMatrix}
|
||||
return Ω == transpose(mat) * Ω * mat
|
||||
end
|
|
@ -0,0 +1,89 @@
|
|||
abstract type AbstractMatrixGroup{N,T} <: Groups.AbstractFPGroup end
|
||||
const MatrixGroupElement{N,T} =
|
||||
Groups.AbstractFPGroupElement{<:AbstractMatrixGroup{N,T}}
|
||||
|
||||
function Base.isone(g::MatrixGroupElement{N,T}) where {N,T}
|
||||
return isone(word(g)) || isone(matrix(g))
|
||||
end
|
||||
|
||||
function Base.:(==)(
|
||||
m1::M1,
|
||||
m2::M2,
|
||||
) where {M1<:MatrixGroupElement,M2<:MatrixGroupElement}
|
||||
parent(m1) === parent(m2) || return false
|
||||
word(m1) == word(m2) && return true
|
||||
return matrix(m1) == matrix(m2)
|
||||
end
|
||||
|
||||
Base.size(::MatrixGroupElement{N}) where {N} = (N, N)
|
||||
Base.size(::MatrixGroupElement{N}, d) where {N} = ifelse(d::Integer <= 2, N, 1)
|
||||
Base.eltype(::MatrixGroupElement{N,T}) where {N,T} = T
|
||||
|
||||
# three structural assumptions about matrix groups
|
||||
Groups.word(m::MatrixGroupElement) = m.word
|
||||
Base.parent(m::MatrixGroupElement) = m.parent
|
||||
Groups.alphabet(M::AbstractMatrixGroup) = M.alphabet
|
||||
Groups.rewriting(M::AbstractMatrixGroup) = alphabet(M)
|
||||
|
||||
Base.hash(m::MatrixGroupElement, h::UInt) = hash(matrix(m), hash(parent(m), h))
|
||||
|
||||
function matrix(m::MatrixGroupElement{N,T}) where {N,T}
|
||||
if isone(word(m))
|
||||
return StaticArrays.SMatrix{N,N,T}(LinearAlgebra.I)
|
||||
end
|
||||
A = alphabet(parent(m))
|
||||
return prod(matrix(A[l]) for l in word(m))
|
||||
end
|
||||
|
||||
function Base.convert(
|
||||
::Type{M},
|
||||
m::MatrixGroupElement,
|
||||
) where {M<:AbstractMatrix}
|
||||
return convert(M, matrix(m))
|
||||
end
|
||||
(M::Type{<:AbstractMatrix})(m::MatrixGroupElement) = convert(M, m)
|
||||
|
||||
function Base.rand(
|
||||
rng::Random.AbstractRNG,
|
||||
rs::Random.SamplerTrivial{<:AbstractMatrixGroup},
|
||||
)
|
||||
Mgroup = rs[]
|
||||
S = gens(Mgroup)
|
||||
return prod(
|
||||
g -> rand(rng, Bool) ? g : inv(g),
|
||||
rand(rng, S, rand(rng, 1:30)),
|
||||
)
|
||||
end
|
||||
|
||||
function Base.show(io::IO, M::AbstractMatrixGroup)
|
||||
g = gens(M, 1)
|
||||
N = size(g, 1)
|
||||
return print(io, "H ⩽ GL{$N,$(eltype(g))}")
|
||||
end
|
||||
|
||||
function Base.show(io::IO, ::MIME"text/plain", M::AbstractMatrixGroup)
|
||||
N = size(gens(M, 1), 1)
|
||||
ng = GroupsCore.ngens(M)
|
||||
return print(
|
||||
io,
|
||||
"subgroup of $N×$N invertible matrices with $(ng) generators",
|
||||
)
|
||||
end
|
||||
|
||||
function Base.show(
|
||||
io::IO,
|
||||
mat::Groups.AbstractFPGroupElement{<:AbstractMatrixGroup},
|
||||
)
|
||||
return KnuthBendix.print_repr(io, word(mat), alphabet(mat))
|
||||
end
|
||||
|
||||
function Base.show(
|
||||
io::IO,
|
||||
::MIME"text/plain",
|
||||
mat::Groups.AbstractFPGroupElement{<:AbstractMatrixGroup{N}},
|
||||
) where {N}
|
||||
Groups.normalform!(mat)
|
||||
KnuthBendix.print_repr(io, word(mat), alphabet(mat))
|
||||
println(io, " ∈ ", parent(mat))
|
||||
return Base.print_array(io, matrix(mat))
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
struct ElementaryMatrix{N,T} <: Groups.GSymbol
|
||||
i::Int
|
||||
j::Int
|
||||
val::T
|
||||
function ElementaryMatrix{N}(i, j, val = 1) where {N}
|
||||
return (@assert i ≠ j; new{N,typeof(val)}(i, j, val))
|
||||
end
|
||||
end
|
||||
|
||||
function Base.show(io::IO, e::ElementaryMatrix)
|
||||
print(io, 'E', Groups.subscriptify(e.i), Groups.subscriptify(e.j))
|
||||
return !isone(e.val) && print(io, "^$(e.val)")
|
||||
end
|
||||
|
||||
function Base.:(==)(e::ElementaryMatrix{N}, f::ElementaryMatrix{N}) where {N}
|
||||
return e.i == f.i && e.j == f.j && e.val == f.val
|
||||
end
|
||||
|
||||
function Base.hash(e::ElementaryMatrix, h::UInt)
|
||||
return hash(typeof(e), hash((e.i, e.j, e.val), h))
|
||||
end
|
||||
|
||||
function Base.inv(e::ElementaryMatrix{N}) where {N}
|
||||
return ElementaryMatrix{N}(e.i, e.j, -e.val)
|
||||
end
|
||||
|
||||
function matrix(e::ElementaryMatrix{N,T}) where {N,T}
|
||||
m = StaticArrays.MMatrix{N,N,T}(LinearAlgebra.I)
|
||||
m[e.i, e.j] = e.val
|
||||
x = StaticArrays.SMatrix{N,N}(m)
|
||||
return x
|
||||
end
|
|
@ -0,0 +1,87 @@
|
|||
struct ElementarySymplectic{N,T} <: Groups.GSymbol
|
||||
symbol::Symbol
|
||||
i::Int
|
||||
j::Int
|
||||
val::T
|
||||
function ElementarySymplectic{N}(
|
||||
s::Symbol,
|
||||
i::Integer,
|
||||
j::Integer,
|
||||
val = 1,
|
||||
) where {N}
|
||||
@assert s ∈ (:A, :B)
|
||||
@assert iseven(N)
|
||||
n = N ÷ 2
|
||||
if s === :A
|
||||
@assert 1 ≤ i ≤ n && 1 ≤ j ≤ n && i ≠ j
|
||||
elseif s === :B
|
||||
@assert xor(1 ≤ i ≤ n, 1 ≤ j ≤ n) && xor(n < i ≤ N, n < j ≤ N)
|
||||
end
|
||||
return new{N,typeof(val)}(s, i, j, val)
|
||||
end
|
||||
end
|
||||
|
||||
function Base.show(io::IO, s::ElementarySymplectic)
|
||||
i, j = Groups.subscriptify(s.i), Groups.subscriptify(s.j)
|
||||
print(io, s.symbol, i, '.', j)
|
||||
return !isone(s.val) && print(io, "^$(s.val)")
|
||||
end
|
||||
|
||||
_ind(s::ElementarySymplectic{N}) where {N} = (s.i, s.j)
|
||||
_local_ind(N_half::Integer, i::Integer) = ifelse(i <= N_half, i, i - N_half)
|
||||
function _dual_ind(s::ElementarySymplectic{N}) where {N}
|
||||
if s.symbol === :A && return _ind(s)
|
||||
else#if s.symbol === :B
|
||||
return _dual_ind(N ÷ 2, s.i, s.j)
|
||||
end
|
||||
end
|
||||
|
||||
function _dual_ind(N_half, i, j)
|
||||
@assert i <= N_half < j || j <= N_half < i
|
||||
if i <= N_half # && j > N_half
|
||||
i, j = j - N_half, i + N_half
|
||||
else
|
||||
i, j = j + N_half, i - N_half
|
||||
end
|
||||
return i, j
|
||||
end
|
||||
|
||||
function Base.:(==)(
|
||||
s::ElementarySymplectic{N},
|
||||
t::ElementarySymplectic{M},
|
||||
) where {N,M}
|
||||
N == M || return false
|
||||
s.symbol == t.symbol || return false
|
||||
s.val == t.val || return false
|
||||
return _ind(t) == _ind(s) || _ind(t) == _dual_ind(s)
|
||||
end
|
||||
|
||||
function Base.hash(s::ElementarySymplectic, h::UInt)
|
||||
return hash(Set([_ind(s); _dual_ind(s)]), hash(s.symbol, hash(s.val, h)))
|
||||
end
|
||||
|
||||
function LinearAlgebra.transpose(s::ElementarySymplectic{N}) where {N}
|
||||
return ElementarySymplectic{N}(s.symbol, s.j, s.i, s.val)
|
||||
end
|
||||
|
||||
function Base.inv(s::ElementarySymplectic{N}) where {N}
|
||||
return ElementarySymplectic{N}(s.symbol, s.i, s.j, -s.val)
|
||||
end
|
||||
|
||||
function matrix(s::ElementarySymplectic{N,T}) where {N,T}
|
||||
@assert iseven(N)
|
||||
n = div(N, 2)
|
||||
m = StaticArrays.MMatrix{N,N,T}(LinearAlgebra.I)
|
||||
i, j = _ind(s)
|
||||
m[i, j] = s.val
|
||||
if s.symbol === :A
|
||||
m[n+j, n+i] = -s.val
|
||||
else#if s.symbol === :B
|
||||
if i > n
|
||||
m[j+n, i-n] = s.val
|
||||
else
|
||||
m[j-n, i+n] = s.val
|
||||
end
|
||||
end
|
||||
return StaticArrays.SMatrix{N,N}(m)
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
struct MatrixElt{N,T,N²} <: Groups.GSymbol
|
||||
id::Symbol
|
||||
inv::Bool
|
||||
mat::StaticArrays.SMatrix{N,N,T,N²}
|
||||
|
||||
function MatrixElt{N,T}(
|
||||
id::Symbol,
|
||||
mat::AbstractMatrix,
|
||||
inv::Bool = false,
|
||||
) where {N,T}
|
||||
n = LinearAlgebra.checksquare(mat)
|
||||
@assert N == n
|
||||
@assert !iszero(LinearAlgebra.det(mat))
|
||||
return new{N,T,N^2}(id, inv, mat)
|
||||
end
|
||||
end
|
||||
|
||||
function MatrixElt{N}(
|
||||
id::Symbol,
|
||||
mat::AbstractMatrix,
|
||||
inv::Bool = false,
|
||||
) where {N}
|
||||
return MatrixElt{N,eltype(mat)}(id, mat, inv)
|
||||
end
|
||||
|
||||
Base.show(io::IO, m::MatrixElt) = print(io, m.id, m.inv ? "⁻¹" : "")
|
||||
|
||||
Base.:(==)(m::MatrixElt, n::MatrixElt) = m.mat == n.mat
|
||||
|
||||
Base.hash(m::MatrixElt, h::UInt) = hash(m.mat, hash(typeof(m), h))
|
||||
|
||||
function Base.inv(m::MatrixElt{N,T}) where {N,T}
|
||||
return MatrixElt{N,T}(m.id, round.(T, inv(m.mat)), !m.inv)
|
||||
end
|
||||
|
||||
matrix(m::MatrixElt) = m.mat
|
|
@ -0,0 +1,25 @@
|
|||
include("matrix_generators.jl")
|
||||
|
||||
struct MatrixGroup{N,T,R,S} <: AbstractMatrixGroup{N,T}
|
||||
base_ring::R
|
||||
alphabet::Alphabet{S}
|
||||
gens::Vector{S}
|
||||
end
|
||||
|
||||
function MatrixGroup{N}(
|
||||
gens::AbstractVector{<:AbstractMatrix{T}},
|
||||
base_ring = T,
|
||||
) where {N,T}
|
||||
S = map(enumerate(gens)) do (i, mat)
|
||||
id = Symbol('m', Groups.subscriptify(i))
|
||||
return MatrixElt{N}(id, mat)
|
||||
end
|
||||
alphabet = Alphabet(S)
|
||||
|
||||
R = typeof(base_ring)
|
||||
St = eltype(S)
|
||||
|
||||
return MatrixGroup{N,T,R,St}(base_ring, alphabet, S)
|
||||
end
|
||||
|
||||
GroupsCore.ngens(M::MatrixGroup) = length(M.gens)
|
|
@ -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,11 +38,11 @@ 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))
|
||||
return KnuthBendix.rewrite_from_left!(res, word(g), rewriting(parent(g)))
|
||||
return KnuthBendix.rewrite!(res, word(g), rewriting(parent(g)))
|
||||
end
|
||||
|
|
|
@ -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
|
250
src/types.jl
250
src/types.jl
|
@ -3,15 +3,18 @@
|
|||
"""
|
||||
AbstractFPGroup
|
||||
|
||||
An Abstract type representing finitely presented groups. Every instance `` must implement
|
||||
An Abstract type representing finitely presented groups. Every instance must implement
|
||||
* `KnuthBendix.alphabet(G::MyFPGroup)`
|
||||
* `rewriting(G::MyFPGroup)` : return the rewriting object which must implement
|
||||
> `KnuthBendix.rewrite_from_left!(u, v, rewriting(G))`.
|
||||
By default `alphabet(G)` is returned, which amounts to free rewriting in `G`.
|
||||
> `KnuthBendix.rewrite!(u, v, rewriting(G))`.
|
||||
E.g. for `G::FreeGroup` `alphabet(G)` is returned, which amounts to free rewriting.
|
||||
* `ordering(G::MyFPGroup)[ = KnuthBendix.ordering(rewriting(G))]` : return the
|
||||
(implicit) ordering for the alphabet of `G`.
|
||||
* `relations(G::MyFPGroup)` : return a set of defining relations.
|
||||
|
||||
AbstractFPGroup may also override `word_type(::Type{MyFPGroup}) = Word{UInt16}`,
|
||||
which controls the word type used for group elements. If a group has more than `255` generators you need to define e.g.
|
||||
AbstractFPGroup may also override `word_type(::Type{MyFPGroup}) = Word{UInt8}`,
|
||||
which controls the word type used for group elements.
|
||||
If a group has more than `255` generators you need to define e.g.
|
||||
> `word_type(::Type{MyFPGroup}) = Word{UInt16}`
|
||||
"""
|
||||
abstract type AbstractFPGroup <: GroupsCore.Group end
|
||||
|
@ -20,16 +23,26 @@ word_type(G::AbstractFPGroup) = word_type(typeof(G))
|
|||
# the default:
|
||||
word_type(::Type{<:AbstractFPGroup}) = Word{UInt8}
|
||||
|
||||
# the default (results in free rewriting)
|
||||
rewriting(G::AbstractFPGroup) = alphabet(G)
|
||||
"""
|
||||
rewriting(G::AbstractFPGroup)
|
||||
Return a "rewriting object" for elements of `G`.
|
||||
|
||||
The rewriting object must must implement
|
||||
KnuthBendix.rewrite!(u::AbstractWord, v::AbstractWord, rewriting(G))
|
||||
|
||||
For example if `G` is a `FreeGroup` then `alphabet(G)` is returned which results
|
||||
in free rewriting. For `FPGroup` a rewriting system is returned which may
|
||||
(or may not) rewrite word `v` to its normal form (depending on e.g. its confluence).
|
||||
"""
|
||||
function rewriting end
|
||||
|
||||
KnuthBendix.ordering(G::AbstractFPGroup) = ordering(rewriting(G))
|
||||
KnuthBendix.alphabet(G::AbstractFPGroup) = alphabet(ordering(G))
|
||||
|
||||
Base.@propagate_inbounds function (G::AbstractFPGroup)(
|
||||
word::AbstractVector{<:Integer},
|
||||
)
|
||||
@boundscheck @assert all(
|
||||
l -> 1 <= l <= length(KnuthBendix.alphabet(G)),
|
||||
word,
|
||||
)
|
||||
@boundscheck @assert all(l -> 1 <= l <= length(alphabet(G)), word)
|
||||
return FPGroupElement(word_type(G)(word), G)
|
||||
end
|
||||
|
||||
|
@ -37,8 +50,9 @@ end
|
|||
|
||||
Base.one(G::AbstractFPGroup) = FPGroupElement(one(word_type(G)), G)
|
||||
|
||||
Base.eltype(::Type{FPG}) where {FPG<:AbstractFPGroup} =
|
||||
FPGroupElement{FPG,word_type(FPG)}
|
||||
function Base.eltype(::Type{FPG}) where {FPG<:AbstractFPGroup}
|
||||
return FPGroupElement{FPG,word_type(FPG)}
|
||||
end
|
||||
|
||||
include("iteration.jl")
|
||||
|
||||
|
@ -49,48 +63,45 @@ function GroupsCore.gens(G::AbstractFPGroup, i::Integer)
|
|||
l = alphabet(G)[G.gens[i]]
|
||||
return FPGroupElement(word_type(G)([l]), G)
|
||||
end
|
||||
GroupsCore.gens(G::AbstractFPGroup) =
|
||||
[gens(G, i) for i in 1:GroupsCore.ngens(G)]
|
||||
|
||||
# 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)
|
||||
function GroupsCore.gens(G::AbstractFPGroup)
|
||||
return [gens(G, i) for i in 1:GroupsCore.ngens(G)]
|
||||
end
|
||||
|
||||
Base.isfinite(::AbstractFPGroup) = (
|
||||
@warn "using generic isfinite(::AbstractFPGroup): the returned `false` might be wrong"; false
|
||||
)
|
||||
function Base.isfinite(::AbstractFPGroup)
|
||||
return (
|
||||
@warn "using generic isfinite(::AbstractFPGroup): the returned `false` might be wrong"; false
|
||||
)
|
||||
end
|
||||
|
||||
## FPGroupElement
|
||||
|
||||
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
|
||||
savedhash::UInt
|
||||
parent::Gr
|
||||
|
||||
FPGroupElement(
|
||||
function FPGroupElement(
|
||||
word::W,
|
||||
G::AbstractFPGroup,
|
||||
hash::UInt = UInt(0),
|
||||
) where {W<:AbstractWord} = new{typeof(G),W}(word, hash, G)
|
||||
) where {W<:AbstractWord}
|
||||
return new{typeof(G),W}(word, hash, G)
|
||||
end
|
||||
|
||||
FPGroupElement{Gr,W}(word::AbstractWord, G::Gr) where {Gr,W} =
|
||||
new{Gr,W}(word, UInt(0), G)
|
||||
function FPGroupElement{Gr,W}(word::AbstractWord, G::Gr) where {Gr,W}
|
||||
return new{Gr,W}(word, UInt(0), G)
|
||||
end
|
||||
end
|
||||
|
||||
Base.show(io::IO, ::Type{<:FPGroupElement{Gr}}) where {Gr} =
|
||||
print(io, FPGroupElement, "{$Gr, …}")
|
||||
|
||||
word(f::AbstractFPGroupElement) = f.word
|
||||
function Base.copy(f::FPGroupElement)
|
||||
return FPGroupElement(copy(word(f)), parent(f), f.savedhash)
|
||||
end
|
||||
|
||||
#convenience
|
||||
KnuthBendix.alphabet(g::AbstractFPGroupElement) = alphabet(parent(g))
|
||||
|
@ -108,129 +119,178 @@ 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}
|
||||
G = parent(g)
|
||||
return GEl(inv(alphabet(G), word(g)), G)
|
||||
return GEl(inv(word(g), alphabet(G)), G)
|
||||
end
|
||||
|
||||
function Base.:(*)(g::GEl, h::GEl) where {GEl<:AbstractFPGroupElement}
|
||||
@boundscheck @assert parent(g) === parent(h)
|
||||
return GEl(word(g) * word(h), parent(g))
|
||||
A = alphabet(parent(g))
|
||||
k = 0
|
||||
while k + 1 ≤ min(length(word(g)), length(word(h)))
|
||||
if inv(word(g)[end-k], A) == word(h)[k+1]
|
||||
k += 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
w = @view(word(g)[1:end-k]) * @view(word(h)[k+1:end])
|
||||
res = GEl(w, parent(g))
|
||||
return res
|
||||
end
|
||||
|
||||
GroupsCore.isfiniteorder(g::AbstractFPGroupElement) =
|
||||
isone(g) ? true :
|
||||
(
|
||||
function GroupsCore.isfiniteorder(g::AbstractFPGroupElement)
|
||||
return isone(g) ? true :
|
||||
(
|
||||
@warn "using generic isfiniteorder(::AbstractFPGroupElement): the returned `false` might be wrong"; false
|
||||
)
|
||||
end
|
||||
|
||||
# additional methods:
|
||||
Base.isone(g::AbstractFPGroupElement) = (normalform!(g); isempty(word(g)))
|
||||
|
||||
## Free Groups
|
||||
|
||||
struct FreeGroup{T} <: AbstractFPGroup
|
||||
struct FreeGroup{T,O} <: AbstractFPGroup
|
||||
gens::Vector{T}
|
||||
alphabet::KnuthBendix.Alphabet{T}
|
||||
ordering::O
|
||||
|
||||
function FreeGroup(gens, A::KnuthBendix.Alphabet) where {W}
|
||||
function FreeGroup(gens, ordering::KnuthBendix.WordOrdering)
|
||||
@assert length(gens) == length(unique(gens))
|
||||
@assert all(l -> l in KnuthBendix.letters(A), gens)
|
||||
return new{eltype(gens)}(gens, A)
|
||||
@assert all(l -> l in alphabet(ordering), gens)
|
||||
return new{eltype(gens),typeof(ordering)}(gens, ordering)
|
||||
end
|
||||
end
|
||||
|
||||
function FreeGroup(A::Alphabet)
|
||||
@boundscheck @assert all(
|
||||
KnuthBendix.hasinverse(l, A) for l in KnuthBendix.letters(A)
|
||||
)
|
||||
ltrs = KnuthBendix.letters(A)
|
||||
gens = Vector{eltype(ltrs)}()
|
||||
invs = Vector{eltype(ltrs)}()
|
||||
for l in ltrs
|
||||
l ∈ invs && continue
|
||||
push!(gens, l)
|
||||
push!(invs, inv(A, l))
|
||||
end
|
||||
|
||||
return FreeGroup(gens, A)
|
||||
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))
|
||||
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
|
||||
|
||||
Base.show(io::IO, F::FreeGroup) =
|
||||
print(io, "free group on $(ngens(F)) generators")
|
||||
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)}()
|
||||
for l in A
|
||||
l ∈ invs && continue
|
||||
push!(gens, l)
|
||||
push!(invs, inv(l, A))
|
||||
end
|
||||
return gens
|
||||
end
|
||||
|
||||
function FreeGroup(O::KnuthBendix.WordOrdering)
|
||||
grp_gens = __group_gens(alphabet(O))
|
||||
return FreeGroup(grp_gens, O)
|
||||
end
|
||||
|
||||
function Base.show(io::IO, F::FreeGroup)
|
||||
return print(io, "free group on $(ngens(F)) generators")
|
||||
end
|
||||
|
||||
# mandatory methods:
|
||||
KnuthBendix.alphabet(F::FreeGroup) = F.alphabet
|
||||
relations(F::FreeGroup) = Pair{eltype(F)}[]
|
||||
KnuthBendix.ordering(F::FreeGroup) = F.ordering
|
||||
rewriting(F::FreeGroup) = alphabet(F) # alphabet(F) = alphabet(ordering(F))
|
||||
relations(F::FreeGroup) = Pair{eltype(F),eltype(F)}[]
|
||||
|
||||
# GroupsCore interface:
|
||||
# these are mathematically correct
|
||||
Base.isfinite(::FreeGroup) = false
|
||||
|
||||
GroupsCore.isfiniteorder(g::AbstractFPGroupElement{<:FreeGroup}) =
|
||||
isone(g) ? true : false
|
||||
function GroupsCore.isfiniteorder(g::AbstractFPGroupElement{<:FreeGroup})
|
||||
return isone(g) ? true : false
|
||||
end
|
||||
|
||||
## FP Groups
|
||||
|
||||
struct FPGroup{T,R,S} <: AbstractFPGroup
|
||||
struct FPGroup{T,RW,S} <: AbstractFPGroup
|
||||
gens::Vector{T}
|
||||
relations::Vector{Pair{S,S}}
|
||||
rws::R
|
||||
rw::RW
|
||||
end
|
||||
|
||||
KnuthBendix.alphabet(G::FPGroup) = alphabet(rewriting(G))
|
||||
rewriting(G::FPGroup) = G.rws
|
||||
|
||||
relations(G::FPGroup) = G.relations
|
||||
rewriting(G::FPGroup) = G.rw
|
||||
|
||||
function FPGroup(
|
||||
G::AbstractFPGroup,
|
||||
rels::AbstractVector{<:Pair{GEl,GEl}};
|
||||
ordering = KnuthBendix.LenLex,
|
||||
ordering = KnuthBendix.ordering(G),
|
||||
kwargs...,
|
||||
) where {GEl<:FPGroupElement}
|
||||
O = ordering(alphabet(G))
|
||||
for (lhs, rhs) in rels
|
||||
@assert parent(lhs) === parent(rhs) === G
|
||||
end
|
||||
word_rels = [word(lhs) => word(rhs) for (lhs, rhs) in [relations(G); rels]]
|
||||
rws = KnuthBendix.RewritingSystem(word_rels, O)
|
||||
rws = KnuthBendix.RewritingSystem(word_rels, ordering)
|
||||
|
||||
KnuthBendix.knuthbendix!(rws; kwargs...)
|
||||
rws = KnuthBendix.knuthbendix(rws, KnuthBendix.Settings(; kwargs...))
|
||||
|
||||
return FPGroup(G.gens, rels, rws)
|
||||
return FPGroup(G.gens, rels, KnuthBendix.IndexAutomaton(rws))
|
||||
end
|
||||
|
||||
function Base.show(io::IO, ::MIME"text/plain", G::FPGroup)
|
||||
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
|
||||
|
||||
function Base.show(io::IO, G::FPGroup)
|
||||
print(io, "⟨")
|
||||
join(io, gens(G), ", ")
|
||||
print(io, " | ")
|
||||
join(io, relations(G), ", ")
|
||||
return print(io, "⟩")
|
||||
Base.print_array(io, permutedims(gens(G)))
|
||||
println(io, " | ")
|
||||
print(io, "\t ")
|
||||
Base.print_array(io, permutedims(relations(G)))
|
||||
return print(io, " ⟩")
|
||||
end
|
||||
|
||||
Base.show(io::IO, ::Type{<:FPGroup{T}}) where {T} =
|
||||
print(io, FPGroup, "{$T, …}")
|
||||
function Base.show(io::IO, ::Type{<:FPGroup{T}}) where {T}
|
||||
return print(io, FPGroup, "{$T, …}")
|
||||
end
|
||||
|
||||
## GSymbol aka letter of alphabet
|
||||
|
||||
|
|
|
@ -6,41 +6,31 @@ word-length metric on the group generated by `S`. The ball is centered at `cente
|
|||
(by default: the identity element). `radius` and `op` keywords specify the
|
||||
radius and multiplication operation to be used.
|
||||
"""
|
||||
function wlmetric_ball_serial(S::AbstractVector{T}, center::T=one(first(S)); radius = 2, op = *) where {T}
|
||||
@assert radius >= 1
|
||||
old = unique!([center, [center*s for s in S]...])
|
||||
return _wlmetric_ball(S, old, radius, op, collect, unique!)
|
||||
end
|
||||
|
||||
function wlmetric_ball_thr(S::AbstractVector{T}, center::T=one(first(S)); radius = 2, op = *) where {T}
|
||||
@assert radius >= 1
|
||||
old = unique!([center, [center*s for s in S]...])
|
||||
return _wlmetric_ball(S, old, radius, op, ThreadsX.collect, ThreadsX.unique)
|
||||
end
|
||||
|
||||
function _wlmetric_ball(S, old, radius, op, collect, unique)
|
||||
sizes = [1, length(old)]
|
||||
for r in 2:radius
|
||||
old = let old = old, S = S,
|
||||
new = collect(
|
||||
(g = op(o, s); hash(g); g)
|
||||
for o in @view(old[sizes[end-1]:end]) for s in S
|
||||
)
|
||||
append!(old, new)
|
||||
unique(old)
|
||||
end
|
||||
push!(sizes, length(old))
|
||||
end
|
||||
return old, sizes[2:end]
|
||||
end
|
||||
|
||||
function wlmetric_ball(
|
||||
S::AbstractVector{T},
|
||||
center::T = one(first(S));
|
||||
radius = 2,
|
||||
op = *,
|
||||
threading = true,
|
||||
) where {T}
|
||||
threading && return wlmetric_ball_thr(S, center, radius = radius, op = op)
|
||||
return wlmetric_ball_serial(S, center, radius = radius, op = op)
|
||||
ball = [center]
|
||||
sizes = [1]
|
||||
if radius ≤ 0
|
||||
return ball, sizes[2:end]
|
||||
else
|
||||
ball = union!(ball, [center * s for s in S])
|
||||
push!(sizes, length(ball))
|
||||
if radius == 1
|
||||
return ball, sizes[2:end]
|
||||
else
|
||||
for _ in 2:radius
|
||||
new = collect(
|
||||
op(o, s) for o in @view(ball[sizes[end-1]:end]) for s in S
|
||||
)
|
||||
ball = union!(ball, new)
|
||||
push!(sizes, length(ball))
|
||||
end
|
||||
end
|
||||
return ball, sizes[2:end]
|
||||
end
|
||||
# return wlmetric_ball_serial(S, center; radius = radius, op = op)
|
||||
end
|
||||
|
|
119
test/AutFn.jl
119
test/AutFn.jl
|
@ -29,57 +29,57 @@
|
|||
end
|
||||
|
||||
A4 = Alphabet(
|
||||
[:a,:A,:b,:B,:c,:C,:d,:D],
|
||||
[ 2, 1, 4, 3, 6, 5, 8, 7]
|
||||
[:a, :A, :b, :B, :c, :C, :d, :D],
|
||||
[2, 1, 4, 3, 6, 5, 8, 7]
|
||||
)
|
||||
|
||||
A5 = Alphabet(
|
||||
[:a,:A,:b,:B,:c,:C,:d,:D,:e,:E],
|
||||
[ 2, 1, 4, 3, 6, 5, 8, 7,10, 9]
|
||||
[:a, :A, :b, :B, :c, :C, :d, :D, :e, :E],
|
||||
[2, 1, 4, 3, 6, 5, 8, 7, 10, 9]
|
||||
)
|
||||
|
||||
F4 = FreeGroup([:a, :b, :c, :d], A4)
|
||||
a,b,c,d = gens(F4)
|
||||
D = ntuple(i->gens(F4, i), 4)
|
||||
F4 = FreeGroup(A4)
|
||||
a, b, c, d = gens(F4)
|
||||
D = ntuple(i -> gens(F4, i), 4)
|
||||
|
||||
@testset "Transvection action correctness" begin
|
||||
i,j = 1,2
|
||||
r = Groups.Transvection(:ϱ,i,j)
|
||||
l = Groups.Transvection(:λ,i,j)
|
||||
i, j = 1, 2
|
||||
r = Groups.Transvection(:ϱ, i, j)
|
||||
l = Groups.Transvection(:λ, i, j)
|
||||
|
||||
(t::Groups.Transvection)(v::Tuple) = Groups.evaluate!(v, t)
|
||||
|
||||
@test r(deepcopy(D)) == (a*b, b, c, d)
|
||||
@test inv(r)(deepcopy(D)) == (a*b^-1,b, c, d)
|
||||
@test l(deepcopy(D)) == (b*a, b, c, d)
|
||||
@test inv(l)(deepcopy(D)) == (b^-1*a,b, c, d)
|
||||
@test r(deepcopy(D)) == (a * b, b, c, d)
|
||||
@test inv(r)(deepcopy(D)) == (a * b^-1, b, c, d)
|
||||
@test l(deepcopy(D)) == (b * a, b, c, d)
|
||||
@test inv(l)(deepcopy(D)) == (b^-1 * a, b, c, d)
|
||||
|
||||
i,j = 3,1
|
||||
r = Groups.Transvection(:ϱ,i,j)
|
||||
l = Groups.Transvection(:λ,i,j)
|
||||
@test r(deepcopy(D)) == (a, b, c*a, d)
|
||||
@test inv(r)(deepcopy(D)) == (a, b, c*a^-1,d)
|
||||
@test l(deepcopy(D)) == (a, b, a*c, d)
|
||||
@test inv(l)(deepcopy(D)) == (a, b, a^-1*c,d)
|
||||
i, j = 3, 1
|
||||
r = Groups.Transvection(:ϱ, i, j)
|
||||
l = Groups.Transvection(:λ, i, j)
|
||||
@test r(deepcopy(D)) == (a, b, c * a, d)
|
||||
@test inv(r)(deepcopy(D)) == (a, b, c * a^-1, d)
|
||||
@test l(deepcopy(D)) == (a, b, a * c, d)
|
||||
@test inv(l)(deepcopy(D)) == (a, b, a^-1 * c, d)
|
||||
|
||||
i,j = 4,3
|
||||
r = Groups.Transvection(:ϱ,i,j)
|
||||
l = Groups.Transvection(:λ,i,j)
|
||||
@test r(deepcopy(D)) == (a, b, c, d*c)
|
||||
@test inv(r)(deepcopy(D)) == (a, b, c, d*c^-1)
|
||||
@test l(deepcopy(D)) == (a, b, c, c*d)
|
||||
@test inv(l)(deepcopy(D)) == (a, b, c, c^-1*d)
|
||||
i, j = 4, 3
|
||||
r = Groups.Transvection(:ϱ, i, j)
|
||||
l = Groups.Transvection(:λ, i, j)
|
||||
@test r(deepcopy(D)) == (a, b, c, d * c)
|
||||
@test inv(r)(deepcopy(D)) == (a, b, c, d * c^-1)
|
||||
@test l(deepcopy(D)) == (a, b, c, c * d)
|
||||
@test inv(l)(deepcopy(D)) == (a, b, c, c^-1 * d)
|
||||
|
||||
i,j = 2,4
|
||||
r = Groups.Transvection(:ϱ,i,j)
|
||||
l = Groups.Transvection(:λ,i,j)
|
||||
@test r(deepcopy(D)) == (a, b*d, c, d)
|
||||
@test inv(r)(deepcopy(D)) == (a, b*d^-1,c, d)
|
||||
@test l(deepcopy(D)) == (a, d*b, c, d)
|
||||
@test inv(l)(deepcopy(D)) == (a, d^-1*b,c, d)
|
||||
i, j = 2, 4
|
||||
r = Groups.Transvection(:ϱ, i, j)
|
||||
l = Groups.Transvection(:λ, i, j)
|
||||
@test r(deepcopy(D)) == (a, b * d, c, d)
|
||||
@test inv(r)(deepcopy(D)) == (a, b * d^-1, c, d)
|
||||
@test l(deepcopy(D)) == (a, d * b, c, d)
|
||||
@test inv(l)(deepcopy(D)) == (a, d^-1 * b, c, d)
|
||||
end
|
||||
|
||||
A = SpecialAutomorphismGroup(F4, maxrules=1000)
|
||||
A = SpecialAutomorphismGroup(F4, max_rules=1000)
|
||||
|
||||
@testset "AutomorphismGroup constructors" begin
|
||||
@test A isa Groups.AbstractFPGroup
|
||||
|
@ -91,33 +91,33 @@
|
|||
|
||||
@testset "Automorphisms: hash and evaluate" begin
|
||||
@test Groups.domain(gens(A, 1)) == D
|
||||
g, h = gens(A, 1), gens(A, 8)
|
||||
g, h = gens(A, 1), gens(A, 8) # (ϱ₁.₂, ϱ₃.₂)
|
||||
|
||||
@test evaluate(g*h) == evaluate(h*g)
|
||||
@test (g*h).savedhash == zero(UInt)
|
||||
@test evaluate(g * h) == evaluate(h * g)
|
||||
@test (g * h).savedhash == zero(UInt)
|
||||
|
||||
@test sprint(show, typeof(g)) == "Automorphism{FreeGroup{Symbol}, …}"
|
||||
@test contains(sprint(show, typeof(g)), "Automorphism{FreeGroup{Symbol")
|
||||
|
||||
a = g*h
|
||||
b = h*g
|
||||
a = g * h
|
||||
b = h * g
|
||||
@test hash(a) != zero(UInt)
|
||||
@test hash(a) == hash(b)
|
||||
@test a.savedhash == b.savedhash
|
||||
|
||||
@test length(unique([a,b])) == 1
|
||||
@test length(unique([g*h, h*g])) == 1
|
||||
@test length(unique([a, b])) == 1
|
||||
@test length(unique([g * h, h * g])) == 1
|
||||
|
||||
# Not so simple arithmetic: applying starting on the left:
|
||||
# ϱ₁₂*ϱ₂₁⁻¹*λ₁₂*ε₂ == σ₂₁₃₄
|
||||
|
||||
g = gens(A, 1)
|
||||
x1, x2, x3, x4 = Groups.domain(g)
|
||||
@test evaluate(g) == (x1*x2, x2, x3, x4)
|
||||
@test evaluate(g) == (x1 * x2, x2, x3, x4)
|
||||
|
||||
g = g*inv(gens(A, 4)) # ϱ₂₁
|
||||
@test evaluate(g) == (x1*x2, x1^-1, x3, x4)
|
||||
g = g * inv(gens(A, 4)) # ϱ₂₁
|
||||
@test evaluate(g) == (x1 * x2, x1^-1, x3, x4)
|
||||
|
||||
g = g*gens(A, 13)
|
||||
g = g * gens(A, 13)
|
||||
@test evaluate(g) == (x2, x1^-1, x3, x4)
|
||||
end
|
||||
|
||||
|
@ -128,7 +128,7 @@
|
|||
S = gens(G)
|
||||
@test S isa Vector{<:FPGroupElement{<:AutomorphismGroup{<:FreeGroup}}}
|
||||
|
||||
@test length(S) == 2*N*(N-1)
|
||||
@test length(S) == 2 * N * (N - 1)
|
||||
@test length(unique(S)) == length(S)
|
||||
|
||||
S_sym = [S; inv.(S)]
|
||||
|
@ -136,12 +136,12 @@
|
|||
|
||||
pushfirst!(S_sym, one(G))
|
||||
|
||||
B_2 = [i*j for (i,j) in Base.product(S_sym, S_sym)]
|
||||
B_2 = [i * j for (i, j) in Base.product(S_sym, S_sym)]
|
||||
@test length(B_2) == 2401
|
||||
@test length(unique(B_2)) == 1777
|
||||
|
||||
@test all(g->isone(inv(g)*g), B_2)
|
||||
@test all(g->isone(g*inv(g)), B_2)
|
||||
@test all(g -> isone(inv(g) * g), B_2)
|
||||
@test all(g -> isone(g * inv(g)), B_2)
|
||||
end
|
||||
|
||||
@testset "Forward evaluate" begin
|
||||
|
@ -153,7 +153,7 @@
|
|||
|
||||
f = gens(F)
|
||||
|
||||
@test a(f[1]) == f[1]*f[2]
|
||||
@test a(f[1]) == f[1] * f[2]
|
||||
@test all(a(f[i]) == f[i] for i in 2:length(f))
|
||||
|
||||
S = let s = gens(G)
|
||||
|
@ -174,12 +174,13 @@
|
|||
)
|
||||
end
|
||||
|
||||
@testset "GroupsCore conformance" begin
|
||||
test_Group_interface(A)
|
||||
g = A(rand(1:length(alphabet(A)), 10))
|
||||
h = A(rand(1:length(alphabet(A)), 10))
|
||||
Logging.with_logger(Logging.NullLogger()) do
|
||||
@testset "GroupsCore conformance" begin
|
||||
test_Group_interface(A)
|
||||
g = A(rand(1:length(alphabet(A)), 10))
|
||||
h = A(rand(1:length(alphabet(A)), 10))
|
||||
|
||||
test_GroupElement_interface(g, h)
|
||||
test_GroupElement_interface(g, h)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -3,22 +3,24 @@
|
|||
|
||||
π₁Σ = Groups.SurfaceGroup(genus, 0)
|
||||
|
||||
Groups.PermRightAut(p::Perm) = Groups.PermRightAut(p.d)
|
||||
@test contains(sprint(print, π₁Σ), "surface")
|
||||
|
||||
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)"
|
||||
[Groups.PermRightAut(p^i) for i in 0:2]
|
||||
end
|
||||
T = eltype(KnuthBendix.letters(alphabet(autπ₁Σ)))
|
||||
T = eltype(alphabet(autπ₁Σ))
|
||||
S = eltype(pauts)
|
||||
|
||||
A = Alphabet(Union{T,S}[KnuthBendix.letters(alphabet(autπ₁Σ)); pauts])
|
||||
A = Alphabet(Union{T,S}[alphabet(autπ₁Σ)...; pauts])
|
||||
|
||||
autG = AutomorphismGroup(
|
||||
π₁Σ,
|
||||
autπ₁Σ.gens,
|
||||
A,
|
||||
ntuple(i->inv(gens(π₁Σ, i)), 2Groups.genus(π₁Σ))
|
||||
ntuple(i -> inv(gens(π₁Σ, i)), 2Groups.genus(π₁Σ))
|
||||
)
|
||||
|
||||
autG
|
||||
|
@ -27,9 +29,7 @@
|
|||
Al = alphabet(autπ₁Σ)
|
||||
S = [gens(autπ₁Σ); inv.(gens(autπ₁Σ))]
|
||||
|
||||
sautFn = let ltrs = KnuthBendix.letters(Al)
|
||||
parent(first(ltrs).autFn_word)
|
||||
end
|
||||
sautFn = parent(Al[1].autFn_word)
|
||||
|
||||
τ = Groups.rotation_element(sautFn)
|
||||
|
||||
|
@ -38,7 +38,7 @@
|
|||
λ = Groups.ΡΛ(:λ, A, 2genus)
|
||||
ϱ = Groups.ΡΛ(:ϱ, A, 2genus)
|
||||
@test sautFn(Groups.Te_diagonal(λ, ϱ, 1)) ==
|
||||
conj(sautFn(Groups.Te_diagonal(λ, ϱ, 2)), τ)
|
||||
conj(sautFn(Groups.Te_diagonal(λ, ϱ, 2)), τ)
|
||||
|
||||
@test sautFn(Groups.Te_diagonal(λ, ϱ, 3)) == sautFn(Groups.Te(λ, ϱ, 3, 1))
|
||||
end
|
||||
|
@ -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]
|
||||
|
|
|
@ -121,15 +121,22 @@ using Groups.KnuthBendix
|
|||
u = (a6 * a5)^-1 * b1 * (a6 * a5)
|
||||
x = (a6 * a5 * a4 * a3 * a2 * u * a1^-1 * a2^-1 * a3^-1 * a4^-1) # yet another auxillary
|
||||
# x = (a4^-1*a3^-1*a2^-1*a1^-1*u*a2*a3*a4*a5*a6)
|
||||
@time evaluate(x)
|
||||
|
||||
b3 = x * a0 * x^-1
|
||||
b3im = @time evaluate(b3)
|
||||
b3cim = @time let g = b3
|
||||
b3im = evaluate(b3)
|
||||
b3cim = let g = b3
|
||||
f = Groups.compiled(g)
|
||||
f(Groups.domain(g))
|
||||
end
|
||||
@test b3im == b3cim
|
||||
@test a0 * b2 * b1 == a1 * a3 * a5 * b3
|
||||
|
||||
@time evaluate(x)
|
||||
let g = b3
|
||||
f = Groups.compiled(g)
|
||||
f(Groups.domain(g))
|
||||
@time f(Groups.domain(g))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,18 +2,21 @@ using BenchmarkTools
|
|||
using Test
|
||||
|
||||
using Groups
|
||||
using Groups.New
|
||||
|
||||
function wl_ball(F; radius::Integer)
|
||||
g, state = iterate(F)
|
||||
while length(word(g)) <= radius
|
||||
sizes = Int[]
|
||||
while length(sizes) ≤ radius
|
||||
res = iterate(F, state)
|
||||
isnothing(res) && break
|
||||
g, state = res
|
||||
if length(word(g)) > length(sizes)
|
||||
push!(sizes, length(state.seen) - 1)
|
||||
end
|
||||
end
|
||||
elts = collect(state.seen)
|
||||
elts = resize!(elts, length(elts)-1)
|
||||
return elts
|
||||
resize!(elts, sizes[end] - 1)
|
||||
return elts, sizes[2:end]
|
||||
end
|
||||
|
||||
@testset "Benchmarks" begin
|
||||
|
@ -26,47 +29,37 @@ end
|
|||
let G = FN
|
||||
S = unique([gens(G); inv.(gens(G))])
|
||||
|
||||
sizes1 = last(Groups.wlmetric_ball(S, radius=R, threading=false))
|
||||
sizes2 = last(Groups.wlmetric_ball(S, radius=R, threading=true))
|
||||
|
||||
l = length(wl_ball(G, radius=R))
|
||||
sizes1 = last(Groups.wlmetric_ball(S; radius = R))
|
||||
sizes2 = last(wl_ball(G; radius = R))
|
||||
|
||||
@test sizes1 == sizes2
|
||||
@test last(sizes1) == l
|
||||
|
||||
@info "Ball of radius $R in $(parent(first(S)))" sizes=sizes1
|
||||
@info "Ball of radius $R in $(parent(first(S)))" sizes = sizes1
|
||||
@info "serial"
|
||||
@time Groups.wlmetric_ball(S, radius=R, threading=false)
|
||||
@info "threaded"
|
||||
@time Groups.wlmetric_ball(S, radius=R, threading=true)
|
||||
@time Groups.wlmetric_ball(S, radius = R)
|
||||
@info "iteration"
|
||||
@time wl_ball(G, radius=R)
|
||||
@time wl_ball(G, radius = R)
|
||||
end
|
||||
end
|
||||
|
||||
@testset "iteration: SAut(F_n)" begin
|
||||
R = 4
|
||||
FN = FreeGroup(N)
|
||||
SAutFN = New.SpecialAutomorphismGroup(FN)
|
||||
SAutFN = SpecialAutomorphismGroup(FN)
|
||||
|
||||
let G = SAutFN
|
||||
S = unique([gens(G); inv.(gens(G))])
|
||||
|
||||
sizes1 = last(Groups.wlmetric_ball(S, radius=R, threading=false))
|
||||
sizes2 = last(Groups.wlmetric_ball(S, radius=R, threading=true))
|
||||
|
||||
l = length(wl_ball(G, radius=R))
|
||||
sizes1 = last(Groups.wlmetric_ball(S; radius = R))
|
||||
sizes2 = last(wl_ball(G; radius = R))
|
||||
|
||||
@test sizes1 == sizes2
|
||||
@test last(sizes1) == l
|
||||
|
||||
@info "Ball of radius $R in $(parent(first(S)))" sizes=sizes1
|
||||
@info "Ball of radius $R in $(parent(first(S)))" sizes = sizes1
|
||||
@info "serial"
|
||||
@time Groups.wlmetric_ball(S, radius=R, threading=false)
|
||||
@info "threaded"
|
||||
@time Groups.wlmetric_ball(S, radius=R, threading=true)
|
||||
@time Groups.wlmetric_ball(S, radius = R)
|
||||
@info "iteration"
|
||||
@time wl_ball(G, radius=R)
|
||||
@time wl_ball(G, radius = R)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,32 +1,33 @@
|
|||
@testset "FPGroups" begin
|
||||
A = Alphabet([:a, :A, :b, :B, :c, :C], [2,1,4,3,6,5])
|
||||
A = Alphabet([:a, :A, :b, :B, :c, :C], [2, 1, 4, 3, 6, 5])
|
||||
|
||||
@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)
|
||||
@test c*b*a isa FPGroupElement
|
||||
a, b, c = gens(F)
|
||||
@test c * b * a isa FPGroupElement
|
||||
|
||||
# quotient of F:
|
||||
G = FPGroup(F, [a*b=>b*a, a*c=>c*a, b*c=>c*b])
|
||||
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 | 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
|
||||
f = a * c * b
|
||||
@test word(f) isa Word{UInt8}
|
||||
|
||||
aG,bG,cG = gens(G)
|
||||
aG, bG, cG = gens(G)
|
||||
|
||||
@test aG isa FPGroupElement
|
||||
@test_throws AssertionError aG == a
|
||||
@test word(aG) == word(a)
|
||||
|
||||
g = aG*cG*bG
|
||||
g = aG * cG * bG
|
||||
|
||||
@test_throws AssertionError f == g
|
||||
@test word(f) == word(g)
|
||||
|
@ -34,29 +35,60 @@
|
|||
Groups.normalform!(g)
|
||||
@test word(g) == [1, 3, 5]
|
||||
|
||||
let g = aG*cG*bG
|
||||
let g = aG * cG * bG
|
||||
# test that we normalize g before printing
|
||||
@test sprint(show, g) == "a*b*c"
|
||||
end
|
||||
|
||||
# quotient of G
|
||||
H = FPGroup(G, [aG^2=>cG, bG*cG=>aG], maxrules=200)
|
||||
H = FPGroup(G, [aG^2 => cG, bG * cG => aG]; max_rules = 200)
|
||||
|
||||
h = H(word(g))
|
||||
|
||||
@test h isa FPGroupElement
|
||||
@test_throws AssertionError h == g
|
||||
@test_throws MethodError h*g
|
||||
@test_throws MethodError h * g
|
||||
|
||||
H′ = FPGroup(G, [aG^2=>cG, bG*cG=>aG], maxrules=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])
|
||||
|
||||
@testset "GroupsCore conformance: H" begin
|
||||
test_Group_interface(H)
|
||||
test_GroupElement_interface(rand(H, 2)...)
|
||||
@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)
|
||||
|
||||
Logging.with_logger(Logging.NullLogger()) do
|
||||
@testset "GroupsCore conformance: H" begin
|
||||
test_Group_interface(H)
|
||||
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
|
||||
|
@ -37,6 +37,8 @@
|
|||
@test k == a*b^-1
|
||||
|
||||
@time k = test_iteration(F3, 1000)
|
||||
|
||||
@time test_iteration(F3, 1000)
|
||||
@test k == (a^2)*c^2*a^-1
|
||||
end
|
||||
|
||||
|
@ -67,5 +69,4 @@
|
|||
test_Group_interface(F3)
|
||||
test_GroupElement_interface(rand(F3, 2)...)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
@testset "GroupConstructions" begin
|
||||
|
||||
symmetric_group(n) = PermGroup(perm"(1,2)", Perm([2:n; 1]))
|
||||
|
||||
@testset "DirectProduct" begin
|
||||
GH =
|
||||
let G = symmetric_group(3), H = symmetric_group(4)
|
||||
|
||||
Groups.Constructions.DirectProduct(G, H)
|
||||
end
|
||||
test_Group_interface(GH)
|
||||
test_GroupElement_interface(rand(GH, 2)...)
|
||||
|
||||
@test collect(GH) isa Array{eltype(GH), 2}
|
||||
@test contains(sprint(print, GH), "Direct product")
|
||||
@test sprint(print, rand(GH)) isa String
|
||||
end
|
||||
|
||||
@testset "DirectPower" begin
|
||||
GGG = Groups.Constructions.DirectPower{3}(
|
||||
symmetric_group(3),
|
||||
)
|
||||
test_Group_interface(GGG)
|
||||
test_GroupElement_interface(rand(GGG, 2)...)
|
||||
|
||||
@test collect(GGG) isa Array{eltype(GGG), 3}
|
||||
@test contains(sprint(print, GGG), "Direct 3-rd power")
|
||||
@test sprint(print, rand(GGG)) isa String
|
||||
end
|
||||
@testset "WreathProduct" begin
|
||||
W =
|
||||
let G = symmetric_group(2), P = symmetric_group(4)
|
||||
|
||||
Groups.Constructions.WreathProduct(G, P)
|
||||
end
|
||||
test_Group_interface(W)
|
||||
test_GroupElement_interface(rand(W, 2)...)
|
||||
|
||||
@test collect(W) isa Array{eltype(W), 2}
|
||||
@test contains(sprint(print, W), "Wreath product")
|
||||
@test sprint(print, rand(W)) isa String
|
||||
end
|
||||
end
|
|
@ -0,0 +1,71 @@
|
|||
function test_homomorphism(hom)
|
||||
F = hom.source
|
||||
@test isone(hom(one(F)))
|
||||
@test all(inv(hom(g)) == hom(inv(g)) for g in gens(F))
|
||||
@test all(isone(hom(g) * hom(inv(g))) for g in gens(F))
|
||||
@test all(hom(g * h) == hom(g) * hom(h) for g in gens(F) for h in gens(F))
|
||||
@test all(
|
||||
hom(inv(g * h)) == inv(hom(g * h)) == hom(inv(h)) * hom(inv(g)) for
|
||||
g in gens(F) for h in gens(F)
|
||||
)
|
||||
end
|
||||
|
||||
@testset "Homomorphisms" begin
|
||||
|
||||
F₂ = FreeGroup(2)
|
||||
g,h = gens(F₂)
|
||||
|
||||
ℤ² = FPGroup(F₂, [g*h => h*g])
|
||||
|
||||
let hom = Groups.Homomorphism((i, G, H) -> Groups.word_type(H)([i]), F₂, ℤ²)
|
||||
|
||||
@test hom(word(g)) == word(g)
|
||||
|
||||
@test hom(word(g*h*inv(g))) == [1,3,2]
|
||||
|
||||
@test hom(g*h*inv(g)) == hom(h)
|
||||
@test isone(hom(g*h*inv(g)*inv(h)))
|
||||
|
||||
@test contains(sprint(print, hom), "Homomorphism")
|
||||
|
||||
test_homomorphism(hom)
|
||||
end
|
||||
|
||||
SAutF3 = SpecialAutomorphismGroup(FreeGroup(3))
|
||||
SL3Z = MatrixGroups.SpecialLinearGroup{3}(Int8)
|
||||
|
||||
let hom = Groups.Homomorphism(
|
||||
Groups._abelianize,
|
||||
SAutF3,
|
||||
SL3Z,
|
||||
)
|
||||
|
||||
A = alphabet(SAutF3)
|
||||
g = SAutF3([A[Groups.ϱ(1,2)]])
|
||||
h = SAutF3([A[Groups.λ(1,2)]])^-1
|
||||
|
||||
@test !isone(g) && !isone(hom(g))
|
||||
@test !isone(h) && !isone(hom(h))
|
||||
@test !isone(g*h) && isone(hom(g*h))
|
||||
|
||||
test_homomorphism(hom)
|
||||
end
|
||||
|
||||
@testset "Correctness of autπ₁Σ → SpN" begin
|
||||
|
||||
GENUS = 3
|
||||
π₁Σ = Groups.SurfaceGroup(GENUS, 0)
|
||||
autπ₁Σ = AutomorphismGroup(π₁Σ)
|
||||
|
||||
SpN = MatrixGroups.SymplecticGroup{2GENUS}(Int8)
|
||||
|
||||
hom = Groups.Homomorphism(
|
||||
Groups._abelianize,
|
||||
autπ₁Σ,
|
||||
SpN,
|
||||
check = false,
|
||||
)
|
||||
|
||||
test_homomorphism(hom)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,119 @@
|
|||
using Groups.MatrixGroups
|
||||
|
||||
@testset "Matrix Groups" begin
|
||||
@testset "SL(n, ℤ)" begin
|
||||
SL3Z = SpecialLinearGroup{3}(Int8)
|
||||
|
||||
S = gens(SL3Z)
|
||||
union!(S, inv.(S))
|
||||
|
||||
_, sizes = Groups.wlmetric_ball(S; radius = 4)
|
||||
|
||||
@test sizes == [13, 121, 883, 5455]
|
||||
|
||||
E(i, j) = SL3Z([A[MatrixGroups.ElementaryMatrix{3}(i, j, Int8(1))]])
|
||||
|
||||
A = alphabet(SL3Z)
|
||||
w = E(1, 2)
|
||||
r = E(2, 3)^-3
|
||||
s = E(1, 3)^2 * E(3, 2)^-1
|
||||
|
||||
S = [w, r, s]
|
||||
S = unique([S; inv.(S)])
|
||||
_, sizes = Groups.wlmetric_ball(S; radius = 4)
|
||||
@test sizes == [7, 33, 141, 561]
|
||||
|
||||
Logging.with_logger(Logging.NullLogger()) do
|
||||
@testset "GroupsCore conformance" begin
|
||||
test_Group_interface(SL3Z)
|
||||
g = SL3Z(rand(1:length(alphabet(SL3Z)), 10))
|
||||
h = SL3Z(rand(1:length(alphabet(SL3Z)), 10))
|
||||
|
||||
test_GroupElement_interface(g, h)
|
||||
end
|
||||
end
|
||||
|
||||
x = w * inv(SL3Z(word(w)[end:end])) * r
|
||||
|
||||
@test length(word(x)) == length(word(r))
|
||||
@test size(x) == (3, 3)
|
||||
@test eltype(x) == Int8
|
||||
|
||||
@test contains(sprint(show, SL3Z), "SL{3,Int8}")
|
||||
@test contains(
|
||||
sprint(show, MIME"text/plain"(), SL3Z),
|
||||
"special linear group",
|
||||
)
|
||||
@test contains(sprint(show, MIME"text/plain"(), x), "∈ SL{3,Int8}")
|
||||
@test sprint(print, x) isa String
|
||||
|
||||
@test length(word(x)) == 3
|
||||
end
|
||||
|
||||
@testset "Sp(6, ℤ)" begin
|
||||
Sp6 = MatrixGroups.SymplecticGroup{6}(Int8)
|
||||
|
||||
Logging.with_logger(Logging.NullLogger()) do
|
||||
@testset "GroupsCore conformance" begin
|
||||
test_Group_interface(Sp6)
|
||||
g = Sp6(rand(1:length(alphabet(Sp6)), 10))
|
||||
h = Sp6(rand(1:length(alphabet(Sp6)), 10))
|
||||
|
||||
test_GroupElement_interface(g, h)
|
||||
end
|
||||
end
|
||||
|
||||
x = gens(Sp6, 1) * gens(Sp6, 2)^2
|
||||
x *= inv(gens(Sp6, 2)^2) * gens(Sp6, 3)
|
||||
|
||||
@test length(word(x)) == 2
|
||||
@test size(x) == (6, 6)
|
||||
@test eltype(x) == Int8
|
||||
|
||||
@test contains(sprint(show, Sp6), "Sp{6,Int8}")
|
||||
@test contains(
|
||||
sprint(show, MIME"text/plain"(), Sp6),
|
||||
"group of 6×6 symplectic matrices",
|
||||
)
|
||||
@test contains(sprint(show, MIME"text/plain"(), x), "∈ Sp{6,Int8}")
|
||||
@test sprint(print, x) isa String
|
||||
|
||||
@test length(word(x)) == 2
|
||||
|
||||
for g in gens(Sp6)
|
||||
@test MatrixGroups.issymplectic(MatrixGroups.matrix(g))
|
||||
end
|
||||
end
|
||||
|
||||
@testset "General matrix group" begin
|
||||
Sp6 = MatrixGroups.SymplecticGroup{6}(Int8)
|
||||
G = Groups.MatrixGroup{6}(Matrix{Int16}.(gens(Sp6)))
|
||||
|
||||
Logging.with_logger(Logging.NullLogger()) do
|
||||
@testset "GroupsCore conformance" begin
|
||||
test_Group_interface(G)
|
||||
g = G(rand(1:length(alphabet(G)), 10))
|
||||
h = G(rand(1:length(alphabet(G)), 10))
|
||||
|
||||
test_GroupElement_interface(g, h)
|
||||
end
|
||||
end
|
||||
|
||||
x = gens(G, 1) * gens(G, 2)^3
|
||||
x *= gens(G, 2)^-3
|
||||
|
||||
@test length(word(x)) == 1
|
||||
@test size(x) == (6, 6)
|
||||
@test eltype(x) == Int16
|
||||
|
||||
@test contains(sprint(show, G), "H ⩽ GL{6,Int16}")
|
||||
@test contains(
|
||||
sprint(show, MIME"text/plain"(), G),
|
||||
"subgroup of 6×6 invertible matrices",
|
||||
)
|
||||
@test contains(sprint(show, MIME"text/plain"(), x), "∈ H ⩽ GL{6,Int16}")
|
||||
@test sprint(print, x) isa String
|
||||
|
||||
@test length(word(x)) == 1
|
||||
end
|
||||
end
|
|
@ -1,6 +1,8 @@
|
|||
using Test
|
||||
import AbstractAlgebra
|
||||
using Groups
|
||||
using PermutationGroups
|
||||
|
||||
import Logging
|
||||
|
||||
import KnuthBendix: Word
|
||||
|
||||
|
@ -9,27 +11,30 @@ include(joinpath(pathof(GroupsCore), "..", "..", "test", "conformance_test.jl"))
|
|||
|
||||
@testset "Groups" begin
|
||||
|
||||
@testset "wlmetric_ball" begin
|
||||
M = AbstractAlgebra.MatrixAlgebra(AbstractAlgebra.zz, 3)
|
||||
w = one(M); w[1,2] = 1;
|
||||
r = one(M); r[2,3] = -3;
|
||||
s = one(M); s[1,3] = 2; s[3,2] = -1;
|
||||
_, t = @timed include("free_groups.jl")
|
||||
@info "free_groups.jl took $(round(t, digits=2))s"
|
||||
_, t = @timed include("fp_groups.jl")
|
||||
@info "fp_groups.jl took $(round(t, digits=2))s"
|
||||
|
||||
S = [w,r,s]; S = unique([S; inv.(S)]);
|
||||
_, sizes = Groups.wlmetric_ball(S, radius=4);
|
||||
@test sizes == [7, 33, 141, 561]
|
||||
_, sizes = Groups.wlmetric_ball_serial(S, radius=4);
|
||||
@test sizes == [7, 33, 141, 561]
|
||||
_, t = @timed include("matrix_groups.jl")
|
||||
@info "matrix_groups.jl took $(round(t, digits=2))s"
|
||||
_, t = @timed include("AutFn.jl")
|
||||
@info "AutFn.jl took $(round(t, digits=2))s"
|
||||
|
||||
_, t = @timed include("homomorphisms.jl")
|
||||
@info "homomorphisms.jl took $(round(t, digits=2))s"
|
||||
|
||||
if !haskey(ENV, "CI")
|
||||
_, t = @timed include("AutSigma_41.jl")
|
||||
@info "AutSigma_41 took $(round(t, digits=2))s"
|
||||
_, t = @timed include("AutSigma3.jl")
|
||||
@info "AutSigma3 took $(round(t, digits=2))s"
|
||||
end
|
||||
|
||||
include("free_groups.jl")
|
||||
include("fp_groups.jl")
|
||||
|
||||
include("AutFn.jl")
|
||||
include("AutSigma_41.jl")
|
||||
include("AutSigma3.jl")
|
||||
|
||||
# if !haskey(ENV, "CI")
|
||||
# include("benchmarks.jl")
|
||||
# end
|
||||
_, t = @timed include("group_constructions.jl")
|
||||
@info "Constructions took $(round(t, digits=2))s"
|
||||
end
|
||||
|
||||
if !haskey(ENV, "CI")
|
||||
include("benchmarks.jl")
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue