Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ compile_commands.json
/local/*
/Manifest.toml
/src/libraryfuncdictionary.jl
.DS_Store
9 changes: 5 additions & 4 deletions src/Semigroups.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ include("LibSemigroups.jl")
# Re-export the low-level module for advanced users
using .LibSemigroups

# Import error handling utilities
include("libsemigroups/errors.jl")
using .Errors: LibsemigroupsError, @wrap_libsemigroups_call

# Julia-side wrapper files
include("libsemigroups/constants.jl")
include("libsemigroups/errors.jl")
include("libsemigroups/transf.jl")

# Import error handling utilities
using .Errors: LibsemigroupsError, @wrap_libsemigroups_call

# High-level element types
include("elements/transf.jl")

Expand All @@ -83,6 +83,7 @@ export is_undefined, is_positive_infinity, is_negative_infinity, is_limit_max
# Transformation types and functions
export Transf, PPerm, Perm
export degree, rank, images, image_set, domain_set
export increase_degree_by!, swap!
export left_one, right_one

end # module Semigroups
130 changes: 94 additions & 36 deletions src/elements/transf.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2026, James W. Swent
# Copyright (c) 2026, James W. Swent, J. D. Mitchell
#
# Distributed under the terms of the GPL license version 3.
#
Expand All @@ -22,14 +22,16 @@ using CxxWrap.StdLib: StdVector
_scalar_type_from_degree(n::Integer) -> Type

Select appropriate unsigned integer type based on degree `n`.
Returns UInt8 for n < 256, UInt16 for n < 65536, UInt32 otherwise.
Returns UInt8 for n ≤ 255, UInt16 for n ≤ 65535, UInt32 otherwise.
"""
function _scalar_type_from_degree(n::Integer)
if n < 2^8
# Use <= for typemax: degree n stores 0-based indices 0..n-1, so max index n-1
# must fit in the scalar type. For n=255, max index=254 fits in UInt8.
if n <= typemax(UInt8)
return UInt8
elseif n < 2^16
elseif n <= typemax(UInt16)
return UInt16
elseif n < 2^32
elseif n <= typemax(UInt32)
return UInt32
else
error("Degree $n too large (maximum supported degree is 2^32-1)")
Expand All @@ -52,6 +54,11 @@ function _transf_type_from_degree(n::Integer)
end
end

function _transf_type_from_scalar_type(scalar::DataType)
lookup = Dict(UInt8 => Transf1, UInt16 => Transf2, UInt32 => Transf4)
return lookup[scalar]
end

"""
_pperm_type_from_degree(n::Integer) -> Type

Expand Down Expand Up @@ -103,7 +110,7 @@ t = Transf([1, 2, 1])
Note: The constructor accepts 1-based indexing (Julia convention) but
internally converts to 0-based indexing for the C++ library.
"""
mutable struct Transf
mutable struct Transf{T}
cxx_obj::Union{Transf1,Transf2,Transf4}

"""
Expand All @@ -117,32 +124,41 @@ mutable struct Transf
t = Transf([2, 1, 2, 3]) # degree 4, maps 1->2, 2->1, 3->2, 4->3
```
"""
function Transf(images::AbstractVector{<:Integer})
n = length(images)
if n == 0
error("Cannot create transformation of degree 0")
end
# Internal constructor from C++ object (used by operations)
# function Transf(cxx_obj::Union{Transf1,Transf2,Transf4})
#  return new{typeof(cxx_obj)}(cxx_obj)
# end

# Convert to 0-based indexing for C++
images_0based = [UInt(img - 1) for img in images]
end

# Select appropriate C++ type based on degree
CxxType = _transf_type_from_degree(n)
ScalarType = CxxType === Transf1 ? UInt8 : (CxxType === Transf2 ? UInt16 : UInt32)
Transf(t::Transf1) = Transf{UInt8}(t)
Transf(t::Transf2) = Transf{UInt16}(t)
Transf(t::Transf4) = Transf{UInt32}(t)

# Convert to correct scalar type
images_typed = convert(Vector{ScalarType}, images_0based)
function Transf(images::AbstractVector{<:Integer}, ::Type{T}) where {T}
n = length(images)
if n == 0 || n > typemax(T)
error("Cannot create transformation of degree $n")
end

# Construct C++ object - CxxWrap needs StdVector
cxx_obj = CxxType(StdVector(images_typed))
# Select the appropriate C++ type based on T
CxxType = _transf_type_from_scalar_type(T)

return new(cxx_obj)
end
# Convert to 0-based indexing for C++
images_0based = [UInt(img - 1) for img in images]

# Internal constructor from C++ object (used by operations)
function Transf(cxx_obj::Union{Transf1,Transf2,Transf4})
return new(cxx_obj)
end
# Convert to the desired type T
images_typed = convert(Vector{T}, images_0based)

# Construct the C++ object (StdVector wrapper)
cxx_obj = @wrap_libsemigroups_call CxxType(StdVector{T}(images_typed))

# Return the Transf{T} instance
return Transf{T}(cxx_obj)
end

function Transf(images::AbstractVector{<:Integer})
return Transf(images, _scalar_type_from_degree(length(images)))
end

# Degree and rank
Expand Down Expand Up @@ -213,7 +229,7 @@ Base.hash(t::Transf, h::UInt) = hash(hash_value(t.cxx_obj), h)

Create an independent copy of transformation `t`.
"""
Base.copy(t::Transf) = Transf(copy(t.cxx_obj))
Base.copy(t::Transf{T}) where {T} = Transf{T}(copy(t.cxx_obj))

# Multiplication
"""
Expand Down Expand Up @@ -241,7 +257,7 @@ function Base.:(*)(t1::Transf, t2::Transf)
ScalarType = CxxType === Transf1 ? UInt8 : (CxxType === Transf2 ? UInt16 : UInt32)
# Convert Julia Vector to CxxWrap StdVector
std_vec1 = StdVector{ScalarType}(convert(Vector{ScalarType}, imgs1))
t1_cxx = CxxType(std_vec1)
t1_cxx = @wrap_libsemigroups_call CxxType(std_vec1)
end

if typeof(t2_cxx) !== CxxType
Expand All @@ -252,7 +268,7 @@ function Base.:(*)(t1::Transf, t2::Transf)
ScalarType = CxxType === Transf1 ? UInt8 : (CxxType === Transf2 ? UInt16 : UInt32)
# Convert Julia Vector to CxxWrap StdVector
std_vec2 = StdVector{ScalarType}(convert(Vector{ScalarType}, imgs2))
t2_cxx = CxxType(std_vec2)
t2_cxx = @wrap_libsemigroups_call CxxType(std_vec2)
end

# Compute product
Expand Down Expand Up @@ -369,7 +385,7 @@ mutable struct PPerm
CxxType = _pperm_type_from_degree(n)

# Construct C++ object - CxxWrap needs StdVector
cxx_obj = CxxType(StdVector(images_0based))
cxx_obj = @wrap_libsemigroups_call CxxType(StdVector(images_0based))

return new(cxx_obj)
end
Expand Down Expand Up @@ -406,7 +422,11 @@ mutable struct PPerm
CxxType = _pperm_type_from_degree(deg)

# Construct C++ object - CxxWrap needs StdVector
cxx_obj = CxxType(StdVector(dom_0based), StdVector(img_0based), UInt(deg))
cxx_obj = @wrap_libsemigroups_call CxxType(
StdVector(dom_0based),
StdVector(img_0based),
UInt(deg),
)

return new(cxx_obj)
end
Expand Down Expand Up @@ -487,7 +507,7 @@ function Base.:(*)(p1::PPerm, p2::PPerm)
end
# Convert Julia Vector to CxxWrap StdVector
std_vec1 = StdVector{ScalarType}(convert(Vector{ScalarType}, imgs1))
p1_cxx = CxxType(std_vec1)
p1_cxx = @wrap_libsemigroups_call CxxType(std_vec1)
end

if typeof(p2_cxx) !== CxxType
Expand All @@ -498,7 +518,7 @@ function Base.:(*)(p1::PPerm, p2::PPerm)
end
# Convert Julia Vector to CxxWrap StdVector
std_vec2 = StdVector{ScalarType}(convert(Vector{ScalarType}, imgs2))
p2_cxx = CxxType(std_vec2)
p2_cxx = @wrap_libsemigroups_call CxxType(std_vec2)
end

product_inplace!(result_cxx, p1_cxx, p2_cxx)
Expand Down Expand Up @@ -633,7 +653,7 @@ mutable struct Perm
images_typed = convert(Vector{ScalarType}, images_0based)

# Construct C++ object - CxxWrap needs StdVector
cxx_obj = CxxType(StdVector(images_typed))
cxx_obj = @wrap_libsemigroups_call CxxType(StdVector(images_typed))

return new(cxx_obj)
end
Expand Down Expand Up @@ -714,7 +734,7 @@ function Base.:(*)(p1::Perm, p2::Perm)
ScalarType = CxxType === Perm1 ? UInt8 : (CxxType === Perm2 ? UInt16 : UInt32)
# Convert Julia Vector to CxxWrap StdVector
std_vec1 = StdVector{ScalarType}(convert(Vector{ScalarType}, imgs1))
p1_cxx = CxxType(std_vec1)
p1_cxx = @wrap_libsemigroups_call CxxType(std_vec1)
end

if typeof(p2_cxx) !== CxxType
Expand All @@ -725,7 +745,7 @@ function Base.:(*)(p1::Perm, p2::Perm)
ScalarType = CxxType === Perm1 ? UInt8 : (CxxType === Perm2 ? UInt16 : UInt32)
# Convert Julia Vector to CxxWrap StdVector
std_vec2 = StdVector{ScalarType}(convert(Vector{ScalarType}, imgs2))
p2_cxx = CxxType(std_vec2)
p2_cxx = @wrap_libsemigroups_call CxxType(std_vec2)
end

product_inplace!(result_cxx, p1_cxx, p2_cxx)
Expand Down Expand Up @@ -787,3 +807,41 @@ function image_set(p::Perm)
# Convert to 1-based
return sort([Int(x) + 1 for x in img_0based])
end

# ============================================================================
# Generic functions for all transformation types
# ============================================================================

"""
increase_degree_by!(t::Union{Transf,PPerm,Perm}, n::Integer)

Increase the degree of transformation `t` by `n` points.
Modifies `t` in place and returns it for method chaining.

# Example
```julia
t = Transf([1, 2])
increase_degree_by!(t, 3) # Now has degree 5
```
"""
function increase_degree_by!(t::Union{Transf,PPerm,Perm}, n::Integer)
increase_degree_by!(t.cxx_obj, n)
return t
end

"""
swap!(t1::T, t2::T) where T<:Union{Transf,PPerm,Perm}

Swap the contents of transformations `t1` and `t2`. Both objects are modified.

# Example
```julia
t1 = Transf([1, 2])
t2 = Transf([2, 1, 3])
swap!(t1, t2) # t1 and t2 have exchanged contents
```
"""
function swap!(t1::T, t2::T) where {T<:Union{Transf,PPerm,Perm}}
swap!(t1.cxx_obj, t2.cxx_obj)
return nothing
end
8 changes: 4 additions & 4 deletions src/libsemigroups/transf.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2026, James W. Swent
# Copyright (c) 2026, James W. Swent, J. D. Mitchell
#
# Distributed under the terms of the GPL license version 3.
#
Expand Down Expand Up @@ -151,7 +151,7 @@ product_inplace!(result, x, y)
"""
function product_inplace!(result::T, x::T, y::T) where {T<:_PTransfTypes}
GC.@preserve result x y begin
LibSemigroups.product_inplace!(result, x, y)
@wrap_libsemigroups_call LibSemigroups.product_inplace!(result, x, y)
end
return nothing
end
Expand All @@ -170,7 +170,7 @@ increase_degree_by!(t, 2) # Now has degree 4
"""
function increase_degree_by!(t::T, n::Integer) where {T<:_PTransfTypes}
GC.@preserve t begin
LibSemigroups.increase_degree_by!(t, UInt(n))
@wrap_libsemigroups_call LibSemigroups.increase_degree_by!(t, UInt(n))
end
return t
end
Expand All @@ -182,7 +182,7 @@ Swap the contents of `t` with `x`. Both objects are modified.
"""
function swap!(t::T, x::T) where {T<:_PTransfTypes}
GC.@preserve t x begin
LibSemigroups.swap(t, x)
@wrap_libsemigroups_call LibSemigroups.swap(t, x)
end
return nothing
end
Expand Down
67 changes: 67 additions & 0 deletions test/test_transf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -375,4 +375,71 @@ end
id2 = one(Transf, 3)
@test id == id2
end

function check_increase_degree_by!(T)
x = T([1])
@test degree(x) == 1
increase_degree_by!(x, 2)
@test degree(x) == 3
increase_degree_by!(x, 15)
@test degree(x) == 18
increase_degree_by!(x, 15)
@test degree(x) == 33
increase_degree_by!(x, 255)
@test degree(x) == 288
increase_degree_by!(x, 2^16)
@test degree(x) == 288 + 2^16
end

@testset "increase_degree_by! method" begin
check_increase_degree_by!(Transf)
check_increase_degree_by!(PPerm)
check_increase_degree_by!(Perm)
end


@testset "swap! method" begin
# Test swap for Transf
x = Transf([1])
y = Transf([1, 2])
swap!(x, y)
@test x == Transf([1, 2])
@test y == Transf([1])

# Test swap for PPerm
p1 = PPerm([1], [2], 3)
p2 = PPerm([1, 2], [3, 4], 5)
swap!(p1, p2)
@test p1 == PPerm([1, 2], [3, 4], 5)
@test p2 == PPerm([1], [2], 3)

# Test swap for Perm
perm1 = Perm([1])
perm2 = Perm([2, 1])
swap!(perm1, perm2)
@test perm1 == Perm([2, 1])
@test perm2 == Perm([1])
end

@testset "Return policy tests" begin
# Test that copy returns a new object
for TestType in (Transf, PPerm, Perm)
x = TestType([1])
y = copy(x)
@test x !== y # Different objects
@test x == y # But equal values
end

# Test that images returns a new vector each time
x = Transf([1, 2, 3])
imgs1 = images(x)
imgs2 = images(x)
@test imgs1 !== imgs2 # Different vectors

# Test that increase_degree_by! modifies in place and returns the same object
x = Transf([1])
result = increase_degree_by!(x, 5)
@test result === x # Same object
@test degree(x) == 6
end
end
Loading