Skip to content

Commit

Permalink
Merge pull request #53 from kalmarek/mk/monoids
Browse files Browse the repository at this point in the history
add Monoids to the interface
  • Loading branch information
kalmarek authored Nov 18, 2024
2 parents 24a7335 + 24799c2 commit 2f95567
Show file tree
Hide file tree
Showing 18 changed files with 486 additions and 468 deletions.
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 <kalmar@mailbox.org>"]
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

2 comments on commit 2f95567

@kalmarek
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/119654

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.5.1 -m "<description of version>" 2f95567922d86b87cbb2f0447c11bd3fdb10c435
git push origin v0.5.1

Please sign in to comment.