From a45342d4967f4523c305570c28e4df190f47106b Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Mon, 18 Nov 2024 01:58:33 +0100 Subject: [PATCH 1/6] add Monoid and MonoidElement --- README.md | 4 +- src/GroupsCore.jl | 8 ++- src/exceptions.jl | 14 ++-- src/extensions.jl | 1 - src/group_elements.jl | 152 ++++++++++++++++++++++-------------------- src/groups.jl | 95 +++++++++++++------------- src/rand.jl | 38 +++++------ 7 files changed, 159 insertions(+), 153 deletions(-) diff --git a/README.md b/README.md index 6f9218f..03d712f 100644 --- a/README.md +++ b/README.md @@ -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), @@ -28,7 +28,7 @@ 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)...) diff --git a/src/GroupsCore.jl b/src/GroupsCore.jl index c89c547..8ab5901 100644 --- a/src/GroupsCore.jl +++ b/src/GroupsCore.jl @@ -2,10 +2,12 @@ module GroupsCore import Random -abstract type Group end -abstract type GroupElement end +abstract type Monoid end +abstract type MonoidElement end +abstract type Group <: Monoid end +abstract type GroupElement <: MonoidElement end -export Group, GroupElement +export Monoid, MonoidElement, Group, GroupElement export commutator, gens, hasgens, isfiniteorder, ngens, order export istrivial # export one!, inv!, mul!, conj!, commutator!, div_left!, div_right! diff --git a/src/exceptions.jl b/src/exceptions.jl index dd85b43..b6a07b0 100644 --- a/src/exceptions.jl +++ b/src/exceptions.jl @@ -13,8 +13,10 @@ end struct InfiniteOrder{T} <: Exception x::T msg::Any - InfiniteOrder(g::Union{GroupElement,Group}) = new{typeof(g)}(g) - InfiniteOrder(g::Union{GroupElement,Group}, msg) = new{typeof(g)}(g, msg) + InfiniteOrder(g::Union{<:MonoidElement,<:Monoid}) = new{typeof(g)}(g) + function InfiniteOrder(g::Union{<:MonoidElement,<:Monoid}, msg) + return new{typeof(g)}(g, msg) + end end function Base.showerror(io::IO, err::InfiniteOrder{T}) where {T} @@ -23,10 +25,10 @@ function Base.showerror(io::IO, err::InfiniteOrder{T}) where {T} print(io, err.msg) else print(io, "order will only return a value when it is finite. ") - f = if T <: Group - "isfinite(G)" - elseif T <: GroupElement - "isfiniteorder(g)" + f = if T <: Monoid + "isfinite(…)" + elseif T <: MonoidElement + "isfiniteorder(…)" end print(io, "You should check with `$f` first.") end diff --git a/src/extensions.jl b/src/extensions.jl index d42dbe3..c5aaad5 100644 --- a/src/extensions.jl +++ b/src/extensions.jl @@ -1,4 +1,3 @@ -_is_deepcopiable(g::GroupElement) = !isbits(g) function isabelian end function issolvable end diff --git a/src/group_elements.jl b/src/group_elements.jl index bf8f8b8..e5f3e05 100644 --- a/src/group_elements.jl +++ b/src/group_elements.jl @@ -1,21 +1,22 @@ ################################################################################ # -# group_elements.jl : Interface for group elements +# elements.jl : Interface for monoid nad group elements # ################################################################################ # Obligatory methods ################################################################################ """ - parent(g::GroupElement) + parent(g::MonoidElement) Return the parent object of the group element. """ -Base.parent(g::GroupElement) = - throw(InterfaceNotImplemented(:Group, "Base.parent(::$(typeof(g)))")) +function Base.parent(g::MonoidElement) + throw(InterfaceNotImplemented(:Monoid, "Base.parent(::$(typeof(g)))")) +end """ - ==(g::GEl, h::GEl) where {GEl <: GroupElement} -Return the _best effort_ equality for group elements. + ==(g::El, h::El) where {El <: MonoidElement} +Return the _best effort_ equality for monoid elements. If `==(g, h)` returns `true`` then the mathematical equality `g == h` holds. However `==(g, h)` may return `false` even if `g` and `h` represent @@ -27,89 +28,89 @@ of words. !!! note This function may not return due to unsolvable word problem. """ -Base.:(==)(g::GEl, h::GEl) where {GEl<:GroupElement} = - throw(InterfaceNotImplemented(:Group, "Base.:(==)(::$GEl, ::$GEl)")) - -Base.copy(g::GroupElement) = deepcopy(g) - -function Base.inv(g::GroupElement) - throw(InterfaceNotImplemented(:Group, "Base.inv(::$(typeof(g)))")) +function Base.:(==)(::El, ::El) where {El<:MonoidElement} + throw(InterfaceNotImplemented(:Monoid, "Base.:(==)(::$El, ::$El)")) end -function Base.:(*)(g::GEl, h::GEl) where {GEl<:GroupElement} - throw( - InterfaceNotImplemented( - :Group, - "Base.:(*)(::$(typeof(g)), ::$(typeof(g)))", - ), - ) +Base.copy(g::MonoidElement) = deepcopy(g) + +function Base.:(*)(::El, ::El) where {El<:MonoidElement} + throw(InterfaceNotImplemented(:Monoid, "Base.:(*)(::$El, ::$El)")) end """ - isfiniteorder(g::GroupElement) -Return `true` if `g` is of finite order, possibly without computing it. + isfiniteorder(m::MonoidElement) +Return `true` if `m` is of finite order, possibly without computing it. !!! note - If finiteness of a group can be decided based on its type there is no need - to extend `isfiniteorder` for its elements. + If finiteness of a group or monoid can be decided based on its type there + is no need to extend `isfiniteorder` for its elements. """ -function isfiniteorder(g::GroupElement) - isfinite(parent(g)) && return true +function isfiniteorder(m::MonoidElement) + isfinite(parent(m)) && return true throw( InterfaceNotImplemented( - :Group, - "GroupsCore.isfiniteorder(::$(typeof(g)))", + :Monoid, + "GroupsCore.isfiniteorder(::$(typeof(m)))", ), ) end +# ---- group elements methods + +function Base.inv(g::GroupElement) + throw(InterfaceNotImplemented(:Group, "Base.inv(::$(typeof(g)))")) +end + ################################################################################ # Default implementations ################################################################################ -Base.one(g::GroupElement) = one(parent(g)) +Base.one(m::MonoidElement) = one(parent(m)) """ - order([::Type{T} = BigInt, ]g::GroupElement) where T -Return the order of `g` as an instance of `T`. If `g` is of infinite order + order(m::MonoidElement) + order(::Type{T}, m::MonoidElement) where T +Return the order of `m` as an instance of `T`. If `m` is of infinite order `GroupsCore.InfiniteOrder` exception will be thrown. !!! warning - Only arbitrary sized integers are required to return a mathematically + Only arbitrary sized integers are required to return a mathematicaly correct answer. """ -function order(::Type{T}, g::GroupElement) where {T} - isfiniteorder(g) || throw(InfiniteOrder(g)) - isone(g) && return T(1) +function order(::Type{T}, m::MonoidElement) where {T} + isfiniteorder(m) || throw(InfiniteOrder(m)) + isone(m) && return T(1) o = T(1) - gg = deepcopy(g) - out = similar(g) - while !isone(gg) + mm = m^2 + while mm ≠ m o += T(1) - gg = mul!(out, gg, g) + mm *= m end return o end -order(g::GroupElement) = order(BigInt, g) +order(m::MonoidElement) = order(BigInt, m) + +# ---- group elements methods """ - conj(g::GEl, h::GEl) where {GEl <: GroupElement} + conj(g::El, h::El) where {El <: GroupElement} Return the conjugation of `g` by `h`, i.e. `inv(h)*g*h`. """ -Base.conj(g::GEl, h::GEl) where {GEl<:GroupElement} = conj!(similar(g), g, h) +Base.conj(g::El, h::El) where {El<:GroupElement} = conj!(similar(g), g, h) """ - ^(g::GEl, h::GEl) where {GEl <: GroupElement} + ^(g::El, h::El) where {El <: GroupElement} Alias for [`conj`](@ref GroupsCore.conj). """ -Base.:(^)(g::GEl, h::GEl) where {GEl<:GroupElement} = conj(g, h) +Base.:(^)(g::El, h::El) where {El<:GroupElement} = conj(g, h) """ - commutator(g::GEl, h::GEl, k::GEl...) where {GEl <: GroupElement} + commutator(g::El, h::El, k::El...) where {El <: GroupElement} Return the left associative iterated commutator ``[[g, h], ...]``, where ``[g, h] = g^{-1} h^{-1} g h``. """ -function commutator(g::GEl, h::GEl, k::GEl...) where {GEl<:GroupElement} +function commutator(g::El, h::El, k::El...) where {El<:GroupElement} res = commutator!(similar(g), g, h) for l in k res = commutator!(res, res, l) @@ -119,7 +120,7 @@ end Base.literal_pow(::typeof(^), g::GroupElement, ::Val{-1}) = inv(g) -function Base.:(/)(g::GEl, h::GEl) where {GEl<:GroupElement} +function Base.:(/)(g::El, h::El) where {El<:GroupElement} return div_right!(similar(g), g, h) end @@ -127,18 +128,18 @@ end # Default implementations that (might) need performance modification ################################################################################ -Base.similar(g::GroupElement) = one(g) -Base.isone(g::GroupElement) = g == one(g) +Base.similar(g::MonoidElement) = one(g) +Base.isone(g::MonoidElement) = g == one(g) -function Base.:(^)(g::GroupElement, n::Integer) - n < 0 && return inv(g)^-n - return Base.power_by_squaring(g, n) +function Base.:(^)(m::MonoidElement, n::Integer) + n < 0 && return inv(m)^-n + return Base.power_by_squaring(m, n) end -function Base.hash(g::GroupElement, h::UInt) - h = hash(typeof(g), h) - for fn in fieldnames(typeof(g)) - h = hash(getfield(g, fn), h) +function Base.hash(m::MonoidElement, h::UInt) + h = hash(typeof(m), h) + for fn in fieldnames(typeof(m)) + h = hash(getfield(m, fn), h) end return h end @@ -148,60 +149,63 @@ end ################################################################################ """ - one!(g::GroupElement) -Return `one(g)`, possibly modifying `g`. + one!(m::MonoidElement) +Return `one(m)`, possibly modifying `m`. """ -one!(g::GroupElement) = one(parent(g)) +one!(m::MonoidElement) = one(parent(m)) """ - inv!(out::GEl, g::GEl) where {GEl <: GroupElement} -Return `inv(g)`, possibly modifying `out`. Aliasing of `g` with `out` is + mul!(out::El, g::El, h::El) where {El <: MonoidElement} +Return `g*h`, possibly modifying `out`. Aliasing of `g` or `h` with `out` is allowed. """ -inv!(out::GEl, g::GEl) where {GEl<:GroupElement} = inv(g) +mul!(out::El, g::El, h::El) where {El<:MonoidElement} = g * h + +# ---- group elements methods """ - mul!(out::GEl, g::GEl, h::GEl) where {GEl <: GroupElement} -Return `g*h`, possibly modifying `out`. Aliasing of `g` or `h` with `out` is + inv!(out::El, g::El) where {El <: GroupElement} +Return `inv(g)`, possibly modifying `out`. Aliasing of `g` with `out` is allowed. """ -mul!(out::GEl, g::GEl, h::GEl) where {GEl<:GroupElement} = g * h +inv!(out::El, g::El) where {El<:GroupElement} = inv(g) """ - div_right!(out::GEl, g::GEl, h::GEl) where {GEl <: GroupElement} + div_right!(out::El, g::El, h::El) where {El <: GroupElement} Return `g*inv(h)`, possibly modifying `out`. Aliasing of `g` or `h` with `out` is allowed. """ -div_right!(out::GEl, g::GEl, h::GEl) where {GEl<:GroupElement} = - mul!(out, g, inv(h)) +function div_right!(out::El, g::El, h::El) where {El<:GroupElement} + return mul!(out, g, inv(h)) +end """ - div_left!(out::GEl, g::GEl, h::GEl) where {GEl <: GroupElement} + div_left!(out::El, g::El, h::El) where {El <: GroupElement} Return `inv(h)*g`, possibly modifying `out`. Aliasing of `g` or `h` with `out` is allowed. """ -function div_left!(out::GEl, g::GEl, h::GEl) where {GEl<:GroupElement} +function div_left!(out::El, g::El, h::El) where {El<:GroupElement} out = (out === g || out === h) ? inv(h) : inv!(out, h) return mul!(out, out, g) end """ - conj!(out::GEl, g::GEl, h::GEl) where {GEl <: GroupElement} + conj!(out::El, g::El, h::El) where {El <: GroupElement} Return `inv(h)*g*h`, possibly modifying `out`. Aliasing of `g` or `h` with `out` is allowed. """ -function conj!(out::GEl, g::GEl, h::GEl) where {GEl<:GroupElement} +function conj!(out::El, g::El, h::El) where {El<:GroupElement} out = (out === g || out === h) ? inv(h) : inv!(out, h) out = mul!(out, out, g) return mul!(out, out, h) end """ - commutator!(out::GEl, g::GEl, h::GEl) where {GEl <: GroupElement} + commutator!(out::El, g::El, h::El) where {El <: GroupElement} Return `inv(g)*inv(h)*g*h`, possibly modifying `out`. Aliasing of `g` or `h` with `out` is allowed. """ -function commutator!(out::GEl, g::GEl, h::GEl) where {GEl<:GroupElement} +function commutator!(out::El, g::El, h::El) where {El<:GroupElement} # TODO: can we make commutator! with 3 arguments without allocation?? out = conj!(out, g, h) return div_left!(out, out, g) diff --git a/src/groups.jl b/src/groups.jl index cbb3c9f..1de4c9c 100644 --- a/src/groups.jl +++ b/src/groups.jl @@ -1,86 +1,87 @@ ################################################################################ # -# groups.jl : Interface for group parents +# monoids_groups.jl : Interface for group and monoid parents # ################################################################################ # Obligatory methods ################################################################################ -function Base.one(G::Group) - throw(InterfaceNotImplemented(:Group, "Base.one(::$(typeof(G)))")) +function Base.one(M::Monoid) + throw(InterfaceNotImplemented(:Monoid, "Base.one(::$(typeof(M)))")) end """ - order([::Type{T} = BigInt, ]G::Group) where T -Return the order of `G` as an instance of `T`. If `G` is of infinite order, + order([::Type{T} = BigInt, ]M::Monoid) where T +Return the order of `M` as an instance of `T`. If `M` is of infinite order, `GroupsCore.InfiniteOrder` exception will be thrown. !!! warning Only arbitrary sized integers are required to return a mathematically correct answer. """ -function order(::Type{T}, G::Group) where {T} - if !isfinite(G) - throw(InfiniteOrder(G)) +function order(::Type{T}, M::Monoid) where {T} + if !isfinite(M) + throw(InfiniteOrder(M)) end throw( InterfaceNotImplemented( - :Group, - "GroupsCore.order(::Type{$T}, ::$(typeof(G)))", + :Monoid, + "GroupsCore.order(::Type{$T}, ::$(typeof(M)))", ), ) end -order(G::Group) = order(BigInt, G) +order(M::Monoid) = order(BigInt, M) """ - gens(G::Group) + gens(M::Monoid) Return a random-access collection of generators of `G`. """ -gens(G::Group) = - throw(InterfaceNotImplemented(:Group, "GroupsCore.gens(::$(typeof(G)))")) +function gens(M::Monoid) + throw(InterfaceNotImplemented(:Monoid, "GroupsCore.gens(::$(typeof(M)))")) +end ################################################################################ # Iterators ################################################################################ -function Base.eltype(::Type{Gr}) where {Gr<:Group} - throw(InterfaceNotImplemented(:Iteration, "Base.eltype(::Type{$Gr})")) +function Base.eltype(::Type{M}) where {M<:Monoid} + throw(InterfaceNotImplemented(:Iteration, "Base.eltype(::Type{$M})")) end -function Base.iterate(G::Group) - hasgens(G) && throw( - InterfaceNotImplemented(:Iteration, "Base.iterate(::$(typeof(G)))"), +function Base.iterate(M::Monoid) + hasgens(M) && throw( + InterfaceNotImplemented(:Iteration, "Base.iterate(::$(typeof(M)))"), ) throw(ArgumentError("Group does not have assigned generators.")) end -function Base.iterate(G::Group, state) - hasgens(G) && throw( +function Base.iterate(M::Group, state) + hasgens(M) && throw( InterfaceNotImplemented( :Iteration, - "Base.iterate(::$(typeof(G)), state)", + "Base.iterate(::$(typeof(M)), state)", ), ) throw(ArgumentError("Group does not have assigned generators.")) end """ - 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. + IteratorSize(::Type{M}) where {M <: Monoid} +Given the type of a monoid, return one of the following values: + * `Base.IsInfinite()` if all instances of groups of type `M` are infinite. * `Base.HasLength()` or `Base.HasShape{N}()` if all instances are finite. * `Base.SizeUnknown()` otherwise, [the default]. """ -Base.IteratorSize(::Type{<:Group}) = Base.SizeUnknown() +Base.IteratorSize(::Type{<:Monoid}) = Base.SizeUnknown() # NOTE: cheating here, not great, but nobody should use this function except # iteration. -function Base.length(G::Group) - return isfinite(G) ? order(Int, G) : +function Base.length(M::Monoid) + return isfinite(M) ? order(Int, M) : throw( """You're trying to iterate over an infinite group. If you know what you're doing, choose an appropriate integer and redefine - `Base.length(::$(typeof(G)))::Int`.""", + `Base.length(::$(typeof(M)))::Int`.""", ) end @@ -89,47 +90,47 @@ end ################################################################################ """ - isfinite(G::Group) -Test whether group `G` is finite. + isfinite(M::Monoid) +Test whether monoid `M` is finite. The default implementation is based on `Base.IteratorSize`. Only groups of returning `Base.SizeUnknown()` should extend this method. """ -function Base.isfinite(G::Group) - IS = Base.IteratorSize(G) +function Base.isfinite(M::Monoid) + IS = Base.IteratorSize(M) IS isa Base.HasLength && return true IS isa Base.HasShape && return true IS isa Base.IsInfinite && return false # else : IS isa Base.SizeUnknown throw( ArgumentError( - """The finiteness of $G could not be determined based on its iterator type. - You need to implement `Base.isfinite(::$(typeof(G))) yourself.""", + """The finiteness of $M could not be determined based on its iterator type. + You need to implement `Base.isfinite(::$(typeof(M))) yourself.""", ), ) end """ - istrivial(G::Group) -Test whether group `G` is trivial. + istrivial(M::Monoid) +Test whether monoid `M` is trivial. The default implementation is based on `isfinite` and `order`. """ -function istrivial(G::Group) - hasgens(G) && all(isone, gens(G)) && return true - isfinite(G) && return isone(order(G)) +function istrivial(M::Monoid) + hasgens(M) && all(isone, gens(M)) && return true + isfinite(M) && return isone(order(M)) return false end -hasgens(G::Group) = true +hasgens(::Monoid) = true -function gens(G::Group, i::Integer) - hasgens(G) && return gens(G)[i] - throw(ArgumentError("Group does not have assigned generators.")) +function gens(M::Monoid, i::Integer) + hasgens(M) && return gens(M)[i] + throw(ArgumentError("Monoid does not have assigned generators.")) end -function ngens(G::Group) - hasgens(G) && return length(gens(G)) +function ngens(M::Monoid) + hasgens(M) && return length(gens(M)) # TODO: throw something more specific - throw("Group does not have assigned generators.") + throw(ArgumentError("Monoid does not have assigned generators.")) end diff --git a/src/rand.jl b/src/rand.jl index 1c866b5..5978a5e 100644 --- a/src/rand.jl +++ b/src/rand.jl @@ -13,36 +13,38 @@ end # constants taken from GAP function PRASampler( - G, - n::Integer = 2ngens(G) + 10, + M, + n::Integer = 2ngens(M) + 10, scramble_time::Integer = 10max(n, 10), ) - return PRASampler(Random.default_rng(), G, n, scramble_time) + return PRASampler(Random.default_rng(), M, n, scramble_time) end function Random.Sampler( RNG::Type{<:Random.AbstractRNG}, - G::Group, + M::Monoid, repetition::Random.Repetition = Val(Inf), ) - return PRASampler(RNG(), G) + return PRASampler(RNG(), M) end function PRASampler( rng::Random.AbstractRNG, - G::Group, - n::Integer = 2ngens(G) + 10, + M::Monoid, + n::Integer = 2ngens(M) + 10, scramble_time::Integer = 10max(n, 10), ) - if istrivial(G) - return PRASampler(fill(one(G), n), one(G), one(G)) + if istrivial(M) + return PRASampler(fill(one(M), n), one(M), one(M)) end - @assert hasgens(G) - l = max(n, 2ngens(G), 2) - sampler = let S = gens(G) - S = union!(inv.(S), S) + @assert hasgens(M) + l = max(n, 2ngens(M), 2) + sampler = let S = collect(gens(M)) + if M isa Group + S = union!(S, inv.(S)) + end append!(S, rand(rng, S, l - length(S))) - PRASampler(S, one(G), one(G)) + PRASampler(S, one(M), one(M)) end for _ in 1:scramble_time _ = rand(rng, sampler) @@ -51,15 +53,11 @@ function PRASampler( end function Random.rand(rng::Random.AbstractRNG, pra::PRASampler) - range = 1:length(pra.gentuple) + i = rand(rng, 1:length(pra.gentuple)) pra.right = pra.right * rand(rng, pra.gentuple) - - i = rand(rng, range) @inbounds pra.gentuple[i] = pra.gentuple[i] * pra.right - - j = rand(rng, range) - @inbounds pra.left = pra.left * pra.gentuple[j] + pra.left = rand(rng, pra.gentuple) * pra.left return pra.left end From 9e850ea099706a70cbef82c1f084bd877fbbdb4d Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Mon, 18 Nov 2024 03:26:09 +0100 Subject: [PATCH 2/6] update tests for Monoids --- test/conformance_test.jl | 278 +++++++++++++++++++++------------------ test/cyclic.jl | 2 - test/cyclic_monoid.jl | 76 +++++++++++ test/runtests.jl | 15 ++- 4 files changed, 237 insertions(+), 134 deletions(-) create mode 100644 test/cyclic_monoid.jl diff --git a/test/conformance_test.jl b/test/conformance_test.jl index 0c92c49..276004d 100644 --- a/test/conformance_test.jl +++ b/test/conformance_test.jl @@ -1,90 +1,106 @@ using GroupsCore using Test -function test_Group_interface(G::Group) - @testset "Group interface" begin +# these are for backwards compatibility: +test_Group_interface(G::Group) = test_GroupsCore_interface(G) +function test_GroupElement_interface(g::El, h::El) where {El<:GroupElement} + return test_GroupsCore_interface(g, h) +end + +function test_GroupsCore_interface(M::Monoid) + @testset "Monoid interface" begin @testset "Iteration protocol" begin - IS = Base.IteratorSize(typeof(G)) + IS = Base.IteratorSize(typeof(M)) if IS isa Base.IsInfinite - @test isfinite(G) == false + @test isfinite(M) == false else - isfiniteG = false + isfiniteM = false if IS isa Base.HasLength || IS isa Base.HasShape - @test isfinite(G) == true - isfiniteG = true + @test isfinite(M) == true + isfiniteM = true else @test IS isa Base.SizeUnknown try - @test isfinite(G) isa Bool - isfiniteG = isfinite(G) + @test isfinite(M) isa Bool + isfiniteM = isfinite(M) catch e @test e isa GroupsCore.InfiniteOrder - isfiniteG = false + isfiniteM = false end end - if isfiniteG - @test length(G) isa Int - @test length(G) > 0 + if isfiniteM + @test length(M) isa Int + @test length(M) > 0 - @test eltype(G) <: GroupElement - @test one(G) isa eltype(G) + @test eltype(M) <: MonoidElement + if M isa Group + @test eltype(M) <: GroupElement + end + @test one(M) isa eltype(M) - if GroupsCore.hasgens(G) - @test first(iterate(G)) isa eltype(G) - _, s = iterate(G) - if GroupsCore.istrivial(G) == 1 - @test isnothing(iterate(G, s)) + if GroupsCore.hasgens(M) + @test first(iterate(M)) isa eltype(M) + _, s = iterate(M) + if GroupsCore.istrivial(M) == 1 + @test isnothing(iterate(M, s)) else - @test first(iterate(G, s)) isa eltype(G) + @test first(iterate(M, s)) isa eltype(M) end - @test isone(first(G)) + @test isone(first(M)) end end end end - @testset "Group generators" begin - @test GroupsCore.hasgens(G) isa Bool + @testset "Monoid generators" begin + @test GroupsCore.hasgens(M) isa Bool - if GroupsCore.hasgens(G) - @test ngens(G) isa Int - @test gens(G) isa AbstractVector{eltype(G)} - @test length(gens(G)) == ngens(G) - if ngens(G) > 0 - @test first(gens(G)) == gens(G, 1) - @test last(gens(G)) == gens(G, ngens(G)) + if GroupsCore.hasgens(M) + @test ngens(M) isa Int + @test collect(gens(M)) isa AbstractVector{eltype(M)} + @test length(gens(M)) == ngens(M) + if ngens(M) > 0 + @test first(gens(M)) == gens(M, 1) + @test last(gens(M)) == gens(M, ngens(M)) end else # TODO: throw something more specific - @test_throws ErrorException gens(G) - @test_throws ErrorException ngens(G) + @test_throws ErrorException gens(M) + @test_throws ErrorException ngens(M) end end @testset "order, rand" begin - if isfinite(G) - @test order(Int16, G) isa Int16 - @test order(BigInt, G) isa BigInt - @test order(G) >= 1 - @test istrivial(G) == (order(G) == 1) + if isfinite(M) + @test order(Int16, M) isa Int16 + @test order(BigInt, M) isa BigInt + @test order(M) >= 1 + @test istrivial(M) == (order(M) == 1) else - @test_throws GroupsCore.InfiniteOrder order(G) - @test !istrivial(G) + @test_throws GroupsCore.InfiniteOrder order(M) + @test !istrivial(M) end - @test rand(G) isa GroupElement - @test rand(G, 2) isa AbstractVector{eltype(G)} - g, h = rand(G, 2) - @test parent(g) === parent(h) === G + @test rand(M) isa eltype(M) + @test rand(M, 2) isa AbstractVector{eltype(M)} + g, h = rand(M, 2) + @test parent(g) === parent(h) === M end + + test_GroupsCore_interface(rand(M, 2)...) + end end -function test_GroupElement_interface(g::GEl, h::GEl) where {GEl<:GroupElement} - @testset "GroupElement interface" begin +function test_GroupsCore_interface(g::El, h::El) where {El<:MonoidElement} + isgroup = El <: GroupElement + @testset "MonoidElement interface" begin @testset "Parent methods & deepcopy" begin - @test parent(g) isa Group + @test parent(g) isa Monoid + if isgroup + @test parent(g) isa Group + end @test parent(g) === parent(h) G = parent(g) @@ -111,51 +127,57 @@ function test_GroupElement_interface(g::GEl, h::GEl) where {GEl<:GroupElement} end end - @testset "Group operations" begin + @testset "Group/Monoid operations" begin old_g, old_h = deepcopy(g), deepcopy(h) # check that the default operations don't mutate their arguments - @test inv(g) isa typeof(g) - @test (g, h) == (old_g, old_h) - @test g * h isa typeof(g) @test (g, h) == (old_g, old_h) @test g^2 == g * g @test (g, h) == (old_g, old_h) - @test g^-3 == inv(g) * inv(g) * inv(g) - @test (g, h) == (old_g, old_h) - pow(g, n) = g^n - @test pow(g, 6) isa GroupElement - @test pow(g, 1) isa GroupElement + @test pow(g, 6) isa El + @test pow(g, 1) isa El @test (g, h) == (old_g, old_h) - @test (g * h)^-1 == inv(h) * inv(g) + @test (g * h) * g == g * (h * g) @test (g, h) == (old_g, old_h) - @test conj(g, h) == inv(h) * g * h - @test (g, h) == (old_g, old_h) + if isgroup + @test inv(g) isa typeof(g) + @test (g, h) == (old_g, old_h) - @test ^(g, h) == inv(h) * g * h - @test (g, h) == (old_g, old_h) + @test g^-3 == inv(g) * inv(g) * inv(g) + @test (g, h) == (old_g, old_h) - @test commutator(g, h) == g^-1 * h^-1 * g * h - @test (g, h) == (old_g, old_h) + @test (g * h)^-1 == inv(h) * inv(g) + @test (g, h) == (old_g, old_h) - @test commutator(g, h, g) == conj(inv(g), h) * conj(conj(g, h), g) - @test (g, h) == (old_g, old_h) + @test conj(g, h) == inv(h) * g * h + @test (g, h) == (old_g, old_h) - @test isone(g * inv(g)) && isone(inv(g) * g) - @test (g, h) == (old_g, old_h) + @test ^(g, h) == inv(h) * g * h + @test (g, h) == (old_g, old_h) - @test g / h == g * inv(h) - @test (g, h) == (old_g, old_h) + @test commutator(g, h) == g^-1 * h^-1 * g * h + @test (g, h) == (old_g, old_h) + + @test commutator(g, h, g) == + conj(inv(g), h) * conj(conj(g, h), g) + @test (g, h) == (old_g, old_h) + + @test isone(g * inv(g)) && isone(inv(g) * g) + @test (g, h) == (old_g, old_h) + + @test g / h == g * inv(h) + @test (g, h) == (old_g, old_h) + end end - @testset "Misc GroupElement methods" begin + @testset "Misc Element methods" begin @test one(g) isa typeof(g) @test isone(g) isa Bool @test isone(one(g)) @@ -166,13 +188,13 @@ function test_GroupElement_interface(g::GEl, h::GEl) where {GEl<:GroupElement} @test order(Int16, g) isa Int16 @test order(BigInt, g) isa BigInt @test order(g) >= 1 - if isfinite(parent(g)) + if isfinite(parent(g)) && isgroup @test iszero(order(parent(g)) % order(g)) end if !isone(g) && !isone(g^2) - @test order(g) > 2 + @test order(g) ≥ 2 end - @test order(inv(g)) == order(g) + isgroup && @test order(inv(g)) == order(g) @test order(one(g)) == 1 else @test_throws GroupsCore.InfiniteOrder order(g) @@ -198,10 +220,12 @@ function test_GroupElement_interface(g::GEl, h::GEl) where {GEl<:GroupElement} @test isone(one!(g)) g = deepcopy(old_g) - @test inv!(out, g) == inv(old_g) - @test g == old_g - @test inv!(out, g) == inv(old_g) - g = deepcopy(old_g) + if isgroup + @test inv!(out, g) == inv(old_g) + @test g == old_g + @test inv!(out, g) == inv(old_g) + g = deepcopy(old_g) + end @testset "mul!" begin @test mul!(out, g, h) == old_g * old_h @@ -222,71 +246,73 @@ function test_GroupElement_interface(g::GEl, h::GEl) where {GEl<:GroupElement} g = deepcopy(old_g) end - @testset "conj!" begin - res = old_h^-1 * old_g * old_h - @test conj!(out, g, h) == res - @test (g, h) == (old_g, old_h) + if isgroup + @testset "conj!" begin + res = old_h^-1 * old_g * old_h + @test conj!(out, g, h) == res + @test (g, h) == (old_g, old_h) - @test conj!(g, g, h) == res - @test h == old_h - g = deepcopy(old_g) + @test conj!(g, g, h) == res + @test h == old_h + g = deepcopy(old_g) - @test conj!(h, g, h) == res - @test g == old_g - h = deepcopy(old_h) + @test conj!(h, g, h) == res + @test g == old_g + h = deepcopy(old_h) - @test conj!(g, g, g) == old_g - g = deepcopy(old_g) - end + @test conj!(g, g, g) == old_g + g = deepcopy(old_g) + end - @testset "commutator!" begin - res = old_g^-1 * old_h^-1 * old_g * old_h + @testset "commutator!" begin + res = old_g^-1 * old_h^-1 * old_g * old_h - @test commutator!(out, g, h) == res - @test (g, h) == (old_g, old_h) + @test commutator!(out, g, h) == res + @test (g, h) == (old_g, old_h) - @test commutator!(out, g, h) == res - @test (g, h) == (old_g, old_h) + @test commutator!(out, g, h) == res + @test (g, h) == (old_g, old_h) - @test commutator!(g, g, h) == res - @test h == old_h - g = deepcopy(old_g) + @test commutator!(g, g, h) == res + @test h == old_h + g = deepcopy(old_g) - @test commutator!(h, g, h) == res - @test g == old_g - h = deepcopy(old_h) - end + @test commutator!(h, g, h) == res + @test g == old_g + h = deepcopy(old_h) + end - @testset "div_[left|right]!" begin - res = g * h^-1 - @test div_right!(out, g, h) == res - @test (g, h) == (old_g, old_h) + @testset "div_[left|right]!" begin + res = g * h^-1 + @test div_right!(out, g, h) == res + @test (g, h) == (old_g, old_h) - @test div_right!(g, g, h) == res - @test h == old_h - g = deepcopy(old_g) + @test div_right!(g, g, h) == res + @test h == old_h + g = deepcopy(old_g) - @test div_right!(h, g, h) == res - @test g == old_g - h = deepcopy(old_h) + @test div_right!(h, g, h) == res + @test g == old_g + h = deepcopy(old_h) - @test div_right!(g, g, g) == one(g) - g = deepcopy(old_g) + @test div_right!(g, g, g) == one(g) + g = deepcopy(old_g) - res = h^-1 * g - @test div_left!(out, g, h) == res - @test (g, h) == (old_g, old_h) + res = h^-1 * g + @test div_left!(out, g, h) == res + @test (g, h) == (old_g, old_h) - @test div_left!(g, g, h) == res - @test h == old_h - g = deepcopy(old_g) + @test div_left!(g, g, h) == res + @test h == old_h + g = deepcopy(old_g) - @test div_left!(h, g, h) == res - @test g == old_g - h = deepcopy(old_h) + @test div_left!(h, g, h) == res + @test g == old_g + h = deepcopy(old_h) - @test div_left!(g, g, g) == one(g) - g = deepcopy(old_g) + @test div_left!(g, g, g) == one(g) + g = deepcopy(old_g) + end end end end diff --git a/test/cyclic.jl b/test/cyclic.jl index 00433cd..826793c 100644 --- a/test/cyclic.jl +++ b/test/cyclic.jl @@ -1,5 +1,3 @@ -using Random - struct CyclicGroup <: GroupsCore.Group order::UInt end diff --git a/test/cyclic_monoid.jl b/test/cyclic_monoid.jl new file mode 100644 index 0000000..03346c7 --- /dev/null +++ b/test/cyclic_monoid.jl @@ -0,0 +1,76 @@ +struct CyclicMonoid <: GroupsCore.Monoid + f::Vector{UInt} + function CyclicMonoid(n::Integer) + @assert n ≥ 1 + return new([2:n; 2]) + end +end + +struct CyclicMonoidElement <: GroupsCore.MonoidElement + images::Vector{UInt} + parent::CyclicMonoid + function CyclicMonoidElement(vec::AbstractVector, C::CyclicMonoid) + l = length(C.f) + @assert length(vec) == l + @assert all(i -> 0 ≤ i ≤ l, vec) + return new(vec, C) + end +end + +Base.one(C::CyclicMonoid) = CyclicMonoidElement(1:length(C.f), C) + +Base.eltype(::Type{CyclicMonoid}) = CyclicMonoidElement +Base.iterate(C::CyclicMonoid) = one(C), gens(C, 1) +function Base.iterate(C::CyclicMonoid, x) + f = gens(C, 1) + xf = x * f + return xf == f ? (x, nothing) : (x, xf) +end +Base.iterate(::CyclicMonoid, ::Nothing) = nothing +Base.IteratorSize(::Type{CyclicMonoid}) = Base.HasLength() + +GroupsCore.order(::Type{T}, C::CyclicMonoid) where {T<:Integer} = T(length(C.f)) +GroupsCore.gens(C::CyclicMonoid) = (CyclicMonoidElement(C.f, C),) + +GroupsCore.parent(c::CyclicMonoidElement) = c.parent +function Base.:(==)(g::CyclicMonoidElement, h::CyclicMonoidElement) + return parent(g) === parent(h) && g.images == h.images +end + +function Base.:(*)(g::CyclicMonoidElement, h::CyclicMonoidElement) + @assert parent(g) === parent(h) + return CyclicMonoidElement(h.images[g.images], parent(g)) +end + +function Base.deepcopy_internal(g::CyclicMonoidElement, stack_dict::IdDict) + if !haskey(stack_dict, g) + stack_dict[g] = CyclicMonoidElement( + Base.deepcopy_internal(g.images, stack_dict), + parent(g), + ) + end + return stack_dict[g] +end + +################## Implementing GroupsCore Interface Done! + +#= +### Possible performance modifications: + +NOTE: Since CyclicMonoidElement is immutable there is no need to implement in-place mutable arithmetic. +Base.isone(m::CyclicMonoidElement) = m.images == 1:length(m.images) + +Base.hash(m::CyclicMonoidElement, h) = hash(m.images, hash(parent(m), h)) +=# + +### end of Monoid[Element] methods + +# Some eye-candy if you please +function Base.show(io::IO, C::CyclicMonoid) + return print(io, "Cyclic monoid on $(1:length(C.f))") +end +function Base.show(io::IO, c::CyclicMonoidElement) + print(io, 1:length(c.images), " → ") + join(io, c.images, ", ") + return nothing +end diff --git a/test/runtests.jl b/test/runtests.jl index 5baa3db..0667f16 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,26 +5,29 @@ include("conformance_test.jl") include("cyclic.jl") include("infinite_cyclic.jl") +include("cyclic_monoid.jl") @testset "GroupsCore.jl" begin include("test_notsatisfied.jl") @testset "Cyclic(1)" begin G = CyclicGroup(1) - test_Group_interface(G) - test_GroupElement_interface(rand(G, 2)...) + test_GroupsCore_interface(G) end @testset "Cyclic(12)" begin G = CyclicGroup(12) - test_Group_interface(G) - test_GroupElement_interface(rand(G, 2)...) + test_GroupsCore_interface(G) + end + + @testset "CyclicMonoid(5)" begin + G = CyclicGroup(12) + test_GroupsCore_interface(G) end @testset "InfCyclic" begin G = InfCyclicGroup() - test_Group_interface(G) - test_GroupElement_interface(rand(G, 2)...) + test_GroupsCore_interface(G) end include("extensions.jl") From d4a8da2d0feb2df4b5aed713d061c3c1c6e2bbd0 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Mon, 18 Nov 2024 03:26:56 +0100 Subject: [PATCH 3/6] document Monoids --- README.md | 6 ++-- docs/src/group_elements.md | 34 ++++++++++---------- docs/src/groups.md | 64 ++++++++++++++++++++------------------ docs/src/index.md | 23 ++++++++------ 4 files changed, 66 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 03d712f..544ae7c 100644 --- a/README.md +++ b/README.md @@ -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 ``` diff --git a/docs/src/group_elements.md b/docs/src/group_elements.md index 13ea1a4..bb36558 100644 --- a/docs/src/group_elements.md +++ b/docs/src/group_elements.md @@ -1,21 +1,20 @@ # [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` @@ -23,14 +22,14 @@ Base.:(*)(::GEl, ::GEl) where {GEl <: GroupElement} 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 @@ -38,26 +37,25 @@ 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 diff --git a/docs/src/groups.md b/docs/src/groups.md index 2e39b2f..6a41cc3 100644 --- a/docs/src/groups.md +++ b/docs/src/groups.md @@ -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, @@ -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. @@ -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 diff --git a/docs/src/index.md b/docs/src/index.md index 16f9ecf..01dcf6f 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -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 @@ -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 ``` From b200a8b7b3a969261348cc964c47e0d77ee1a1ba Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Mon, 18 Nov 2024 03:37:59 +0100 Subject: [PATCH 4/6] update docs/Project.toml --- docs/Manifest.toml | 93 -------------------------------------- docs/Project.toml | 3 -- docs/src/group_elements.md | 2 +- 3 files changed, 1 insertion(+), 97 deletions(-) delete mode 100644 docs/Manifest.toml diff --git a/docs/Manifest.toml b/docs/Manifest.toml deleted file mode 100644 index b9a263d..0000000 --- a/docs/Manifest.toml +++ /dev/null @@ -1,93 +0,0 @@ -# This file is machine-generated - editing it directly is not advised - -[[Base64]] -uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" - -[[Dates]] -deps = ["Printf"] -uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" - -[[DocStringExtensions]] -deps = ["LibGit2"] -git-tree-sha1 = "a32185f5428d3986f47c2ab78b1f216d5e6cc96f" -uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.8.5" - -[[Documenter]] -deps = ["Base64", "Dates", "DocStringExtensions", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] -git-tree-sha1 = "3ebb967819b284dc1e3c0422229b58a40a255649" -uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -version = "0.26.3" - -[[GroupsCore]] -deps = ["Markdown", "Random"] -path = ".." -uuid = "d5909c97-4eac-4ecc-a3dc-fdd0858a4120" -version = "0.4.0" - -[[IOCapture]] -deps = ["Logging"] -git-tree-sha1 = "377252859f740c217b936cebcd918a44f9b53b59" -uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" -version = "0.1.1" - -[[InteractiveUtils]] -deps = ["Markdown"] -uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" - -[[JSON]] -deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "81690084b6198a2e1da36fcfda16eeca9f9f24e4" -uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.1" - -[[LibGit2]] -deps = ["Base64", "NetworkOptions", "Printf", "SHA"] -uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" - -[[Logging]] -uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" - -[[Markdown]] -deps = ["Base64"] -uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" - -[[Mmap]] -uuid = "a63ad114-7e13-5084-954f-fe012c677804" - -[[NetworkOptions]] -uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" - -[[Parsers]] -deps = ["Dates"] -git-tree-sha1 = "c8abc88faa3f7a3950832ac5d6e690881590d6dc" -uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "1.1.0" - -[[Printf]] -deps = ["Unicode"] -uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" - -[[REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] -uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" - -[[Random]] -deps = ["Serialization"] -uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" - -[[SHA]] -uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" - -[[Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" - -[[Sockets]] -uuid = "6462fe0b-24de-5631-8697-dd941f90decc" - -[[Test]] -deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] -uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[[Unicode]] -uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" diff --git a/docs/Project.toml b/docs/Project.toml index 0bbe978..278841f 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,6 +1,3 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" GroupsCore = "d5909c97-4eac-4ecc-a3dc-fdd0858a4120" - -[compat] -Documenter = "0.26" diff --git a/docs/src/group_elements.md b/docs/src/group_elements.md index bb36558..05aa8fb 100644 --- a/docs/src/group_elements.md +++ b/docs/src/group_elements.md @@ -5,7 +5,7 @@ ## Obligatory methods ```@docs -parent(::Monoid) +parent(::MonoidElement) :(==)(::El, ::El) where {El <: MonoidElement} isfiniteorder(::MonoidElement) ``` From a3185c62df0e3e74a589658502f92dd96bd281bf Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Mon, 18 Nov 2024 03:43:38 +0100 Subject: [PATCH 5/6] modernize CI workflow --- .github/workflows/CI.yml | 46 ++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a6c7071..0a19982 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -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 From 24799c22c3194017ae8eff56202f45f3434cfcad Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Mon, 18 Nov 2024 04:11:25 +0100 Subject: [PATCH 6/6] bump version to 0.5.1 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 8bc214e..5ab2a15 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "GroupsCore" uuid = "d5909c97-4eac-4ecc-a3dc-fdd0858a4120" authors = ["Marek Kaluba "] -version = "0.5.0" +version = "0.5.1" [deps] Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"