Skip to content

Commit

Permalink
document Monoids
Browse files Browse the repository at this point in the history
  • Loading branch information
kalmarek committed Nov 18, 2024
1 parent 9e850ea commit d4a8da2
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 61 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ using GroupsCore
include(joinpath(pathof(GroupsCore), "..", "..", "test", "conformance_test.jl"))
include("my_fancy_group.jl") # the implementation of MyFancyGroup
let G = MyFancyGroup(...)
test_Group_interface(G)
test_GroupElement_interface(rand(G, 2)...)
test_GroupsCore_interface(G)
# optionally if particular two group elements are to be tested:
# g,h = rand(G, 2)
# test_GroupsCore_interface(g, h)
nothing
end
```
34 changes: 16 additions & 18 deletions docs/src/group_elements.md
Original file line number Diff line number Diff line change
@@ -1,63 +1,61 @@
# [Group elements](@id H1_group_elements)

`GroupsCore` defines abstract type `GroupElement`, which all implementations
of group elements should subtype.
`GroupsCore` defines abstract types `GroupElement <: MonoidElement`, which all implementations of group/monoid elements should subtype.

## Obligatory methods

```@docs
parent(::GroupElement)
:(==)(::GEl, ::GEl) where {GEl <: GroupElement}
isfiniteorder(::GroupElement)
parent(::Monoid)
:(==)(::El, ::El) where {El <: MonoidElement}
isfiniteorder(::MonoidElement)
```

As well as the two arithmetic operations:

```julia
Base.:(*)(::El, ::El) where {El <: MonoidElement}
Base.inv(::GroupElement)
Base.:(*)(::GEl, ::GEl) where {GEl <: GroupElement}
```

### A note on `deepcopy`

The elements which are not of `isbitstype` should extend

```julia
Base.deepcopy_internal(g::GroupElement, ::IdDict)
Base.deepcopy_internal(g::MonoidElement, ::IdDict)
```

according to
[`Base.deepcopy`](https://docs.julialang.org/en/v1/base/base/#Base.deepcopy)
docstring. Due to our assumption on parents of group elements (acting as local
singleton objects), a group element and its `deepcopy` should have identical
(i.e. `===`) parents.
docstring. Due to our assumption on parents of group/monoid elements
(acting as local singleton objects), a monoid element and its `deepcopy` should
have identical (i.e. `===`) parents.

## Implemented methods

Using the obligatory methods we implement the rest of the functions in
`GroupsCore`. For starters, the first of these are:

```julia
Base.:(^)(::GroupElement, ::Integer)
Base.:(/)(::GEl, ::GEl) where {GEl <: GroupElement}
Base.one(::GroupElement)
Base.one(::MonoidElement)
Base.:(/)(::El, ::El) where {El <: GroupElement}
```

and

```@docs
order(::Type{T}, ::GroupElement) where T
order(::Type{T}, ::MonoidElement) where T
conj
:(^)(::GEl, ::GEl) where {GEl <: GroupElement}
commutator
```

Moreover we provide basic implementation which should be altered for performance
Moreover we provide basic implementation which could be altered for performance
reasons:
```julia
Base.:(^)(g::GroupElement, n::Integer)
Groups.Core.order([::Type{T}], g::GroupElement) where T
Base.hash(::GroupElement, ::UInt)
Base.:(^)(g::MonoidElement, n::Integer)
Groups.Core.order([::Type{T}], g::MonoidElement) where T
Base.hash(::MonoidElement, ::UInt)
```

### Mutable API
Expand Down
64 changes: 33 additions & 31 deletions docs/src/groups.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,50 @@
# [Groups](@id H1_groups)
# [Groups and Monoids](@id H1_groups)

The abstract type `Group` encompasses all **multiplicative groups**.
Since these are already abstract, we skip the `Abstract` prefix.
The abstract types `Group <: Monoid` encompass all **multiplicative groups**
and **monoids**. Since these are already abstract, we skip the `Abstract` prefix.

## Assumptions

`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.
interface. **This requires special care** when implementing groups/monoids 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.
* `GroupsCore.hasgens(::Monoid) = true`
This is based on the broad assumption that reasonably generic functions
manipulating groups/monoids can be implemented only with an access to
a generating set.

* **For finite groups only** we define `Base.length(G) = order(Int, G)`
* **For finite groups/monoids only** we define `Base.length(M) = order(Int, M)`

!!! danger
In general `length` is used **for iteration purposes only**.
If you are interested in the number of distinct elements of a group, use
[`order(::Type{<:Integer}, ::Group)`](@ref). For more information see
In general `length` should be used **for iteration purposes only**.
If you are interested in the number of distinct elements of a groups/monoids,
use [`order(::Type{<:Integer}, ::Group)`](@ref). For more information see
[Iteration](@ref).

## Obligatory methods

Here we list the minimal set of functions that a group object must extend to
implement the `Group` interface:
implement the `Monoid` interface:

* `Base.one(::Group)` and
* `Base.one(::Monoid)` and

```@docs
order(::Type{T}, ::Group) where T
gens(::Group)
order(::Type{T}, ::Monoid) where T
gens(::Monoid)
```

### Iteration

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.
If a group/monoid is defined by generators (i.e. `hasgens(M)` 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
elements explore the whole group or monoid. To be precise, for the example of
the free group ``F_2 = ⟨a,b⟩``, one could implement iteration by sequence

```math
a, a^2, a^3, \ldots,
Expand All @@ -57,11 +58,12 @@ 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`.
* Iteration is mandatory only if `hasgens(M)` 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.
* Iteration over an infinite group/monoid should exhaust every fixed radius
ball around the identity (in word-length metric associated to `gens(M)`) in
finite time.
* There is no requirement that in the iteration sequence elements are returned
only once.

Expand All @@ -72,23 +74,23 @@ julia methods:
* [`Base.eltype`](https://docs.julialang.org/en/v1/base/collections/#Base.eltype)

```@docs
Base.IteratorSize(::Type{<:Group})
Base.IteratorSize(::Type{<:Monoid})
```

In contrast to julia we default to `Base.SizeUnknown()` to provide a
mathematically correct fallback. If your group is finite by definition,
mathematically correct fallback. If a group or monoid 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
optimized to work only based on the type of the object. In particular when the
information is derivable from the type, there is no need to extend
[`Base.isfinite`](@ref).

!!! note
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.
In the case that `IteratorSize(Gr) == IsInfinite()`, one should could
`Base.length(Gr)` to be a "best effort", length of the group/monoid iterator.
For practical reasons the largest object you could iterate over in your
lifetime is of order that fits well into an `Int` ($2^{63}$ nanoseconds
comes to 290 years).

## Additional methods

Expand Down
23 changes: 13 additions & 10 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ CurrentModule = GroupsCore
```

The aim of this package is to standardize the common assumptions and functions
on group i.e. to create Group interface.
on group i.e. to create Group/Monoid interface.

The protocol consists of two parts:
Due to the fact that hardly any information can be encoded in `Type`, the
interface consists of two parts:

* [`Group`](@ref H1_groups) (parent object) methods,
* [`GroupElement`](@ref H1_group_elements) methods.
* [`Group` or `Monoid`](@ref H1_groups) (parent object) methods,
* [`GroupElement` or `MonoidElement`](@ref H1_group_elements) methods.

This is due to the fact that hardly any information can be encoded in `Type`, we
rely on parent objects that represent groups, as well as ordinary group
elements. It is assumed that all elements of a group have **identical** parent
(i.e. `===`) so that parent objects behave locally as singletons.
We rely on parent objects that represent groups/monoids, and separate types
for elements. It is assumed that all elements of a group or monoid have
**identical** parent (i.e. `===`) so that parent objects behave locally as
singletons.

## Examples and Conformance testing

Expand All @@ -31,8 +32,10 @@ using GroupsCore
include(joinpath(pathof(GroupsCore), "..", "..", "test", "conformance_test.jl"))
include(joinpath(pathof(GroupsCore), "..", "..", "test", "cyclic.jl"))
let C = CyclicGroup(15)
test_Group_interface(C)
test_GroupElement_interface(rand(C, 2)...)
test_GroupsCore_interface(C)
# optionally if particular two group elements are to be tested:
# g, h = rand(C, 2)
# test_GroupsCore_interface(g, h)
nothing
end
```
Expand Down

0 comments on commit d4a8da2

Please sign in to comment.