The abstract type Group
encompasses all groups. Since these are already abstract, we skip the Abstract
prefix.
GroupsCore
implements some methods with default values, which may not be generally true for all groups. The intent is to limit the extent of the required interface. This require special care when implementing groups that need to override these default methods.
The methods we currently predefine are:
GroupsCore.hasgens(::Group) = true
This is based on the assumption that reasonably generic functions manipulating groups can be implemented only with access to a generating set.
- For finite groups only we define
Base.length(G) = order(Int, G)
Here we list the minimal set of functions that a group object must extend to implement the Group
interface.
one(G::Group)
Return the identity element of the group $G$.
sourceorder(::Type{T} = BigInt, G::Group) where T
Return the order of $G$ as an instance of T
. If $G$ is of infinite order, GroupsCore.InfiniteOrder
exception will be thrown.
Only arbitrary sized integers are required to return a mathematically correct answer.
sourcegens(G::Group)
Return a random-access collection of generators of $G$.
The result of this function is undefined unless GroupsCore.hasgens(G)
return true
.
sourceMissing docstring for rand
. Check Documenter's build log for details.
If a group is defined by generators (i.e. hasgens(G)
returns true
) an important aspect of this interface is the iteration over a group.
Iteration over infinite objects seem to be useful only when the returned elements explore the whole group. To be precise, for the free group $F_2 = ⟨a,b⟩$, one could implement iteration by sequence
\[a, a^2, a^3, \ldots,\]
which is arguably less useful than
\[a, b, a^{-1}, b^{-1}, ab, \ldots.\]
Therefore we put the following assumptions on iteration.
- Iteration is mandatory only if
hasgens(G)
returns true
. - The first element of the iteration (e.g. given by
Base.first
) is the group identity. - Iteration over a finitely generated group should exhaust every fixed radius ball around the identity (in word-length metric associated to
gens(G)
) in finite time.
These are just the conventions, the iteration interface consists of standard julia methods:
IteratorSize(::Type{Gr}) where {Gr <: Group}
Given the type of a group, return one of the following values:
Base.IsInfinite()
if all instances of groups of type Gr
are infinite.Base.HasLength()
or Base.HasShape{N}()
if all instances are finite.Base.SizeUnknown()
otherwise, [the default].
sourceIn contrast to julia we default to Base.SizeUnknown()
to provide a mathematically correct fallback. If your group is finite by definition, implementing the correct IteratorSize
(i.e. Base.HasLength()
, or Base.HasShape{N}()
) will simplify several other methods, which will be then optimized to work only based on the type of the group. In particular when the information is derivable from the type, there is no need to extend Base.isfinite
.
In the case that IteratorSize(Gr) == IsInfinite()
, one should define Base.length(Gr)
to be a "best effort", length of the group iterator.
For practical reasons the largest group you could iterate over in your lifetime is of order that fits into an Int
. For example, $2^{63}$ nanoseconds comes to 290 years.
isfinite(G::Group)
Test whether group $G$ is finite.
The default implementation is based on Base.IteratorSize
. Only groups of returning Base.SizeUnknown()
should extend this method.
sourceistrivial(G::Group)
Test whether group $G$ is trivial.
The default implementation is based on isfinite
and order
.
source