Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Monoids to the interface #53

Merged
merged 6 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 23 additions & 23 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,39 @@ on:
push:
branches:
- main
tags: ['*']
pull_request:
workflow_dispatch:
concurrency:
# Skip intermediate builds: always.
# Cancel intermediate builds: only if it is a pull request build.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}

# needed to allow julia-actions/cache to delete old caches that it has created
permissions:
actions: write
contents: read

jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
version:
- '1.6'
- '1'
- 'nightly'
os:
- ubuntu-latest
arch:
- x64
julia-version: ['lts', '1', 'pre']
julia-arch: [x64]
os: [ubuntu-latest]
exclude:
- os: macOS-latest
julia-arch: x86

steps:
- uses: actions/checkout@v3
- uses: julia-actions/setup-julia@v1
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: julia-actions/cache@v1
version: ${{ matrix.julia-version }}
arch: ${{ matrix.julia-arch }}
- uses: julia-actions/cache@v2
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
# with:
# annotate: true
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v4
with:
files: lcov.info
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "GroupsCore"
uuid = "d5909c97-4eac-4ecc-a3dc-fdd0858a4120"
authors = ["Marek Kaluba <[email protected]>"]
version = "0.5.0"
version = "0.5.1"

[deps]
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
----

The aim of this package is to standardize common assumptions on and functions
for groups, i.e. to create Group interface. Packages using it include:
for groups and monoids, i.e. to create Group/Monoid interface. Packages using it include:
* [PermutationGroups.jl](https://github.com/kalmarek/PermutationGroups.jl)
* [Groups.jl](https://github.com/kalmarek/Groups.jl),
* [SymbolicWedderburn.jl](https://github.com/kalmarek/SymbolicWedderburn.jl),
Expand All @@ -28,10 +28,12 @@ To test the conformance of a group implementation one can run
```julia
using GroupsCore
include(joinpath(pathof(GroupsCore), "..", "..", "test", "conformance_test.jl"))
include("my_group.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
```
93 changes: 0 additions & 93 deletions docs/Manifest.toml

This file was deleted.

3 changes: 0 additions & 3 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
GroupsCore = "d5909c97-4eac-4ecc-a3dc-fdd0858a4120"

[compat]
Documenter = "0.26"
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(::MonoidElement)
:(==)(::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
Loading
Loading