Skip to content
Open
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 deps/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ message(STATUS "libsemigroups libraries: ${LIBSEMIGROUPS_LIBRARIES}")
add_library(libsemigroups_julia SHARED
libsemigroups_julia.cpp
constants.cpp
word-graph.cpp
runner.cpp
transf.cpp
)
Expand Down
3 changes: 3 additions & 0 deletions deps/src/libsemigroups_julia.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ JLCXX_MODULE define_julia_module(jl::Module & mod)
// Define base types (must be registered before derived types)
define_runner(mod);

// Define WordGraph (must be before FroidurePinBase later)
define_word_graph(mod);

// Define element types
define_transf(mod);
}
Expand Down
8 changes: 8 additions & 0 deletions deps/src/libsemigroups_julia.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,17 @@
// libsemigroups headers (these transitively include fmt)
#include <libsemigroups/constants.hpp>
#include <libsemigroups/types.hpp>
#include <libsemigroups/word-graph.hpp>

// Index conversion utilities (1-based ↔ 0-based)
#include "index_utils.hpp"

// CxxWrap type traits — non-Julia C++ types must opt out of mirroring
namespace jlcxx {
template <>
struct IsMirroredType<libsemigroups::WordGraph<uint32_t>> : std::false_type {};
} // namespace jlcxx

namespace libsemigroups_julia {

// Namespace aliases for convenience
Expand All @@ -59,6 +66,7 @@ namespace libsemigroups = ::libsemigroups;
// Forward declarations of binding functions
void define_constants(jl::Module & mod);
void define_runner(jl::Module & mod);
void define_word_graph(jl::Module & mod);
void define_transf(jl::Module & mod);

} // namespace libsemigroups_julia
Expand Down
127 changes: 127 additions & 0 deletions deps/src/word-graph.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//
// Semigroups.jl
// Copyright (C) 2026, James W. Swent
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#include "libsemigroups_julia.hpp"

#include <cstddef>
#include <cstdint>
#include <vector>

namespace libsemigroups_julia {

void define_word_graph(jl::Module & m)
{
using WG = libsemigroups::WordGraph<uint32_t>;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
using WG = libsemigroups::WordGraph<uint32_t>;
using WordGraph_ = libsemigroups::WordGraph<uint32_t>;


auto type = m.add_type<WG>("WordGraph");

//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////

m.method("WordGraph", [](size_t num_nodes, size_t out_deg) -> WG {
return WG(num_nodes, out_deg);
});

//////////////////////////////////////////////////////////////////////////
// Size / structure queries
//////////////////////////////////////////////////////////////////////////

// number_of_nodes - total number of nodes in the graph
type.method("number_of_nodes", &WG::number_of_nodes);

// out_degree - number of edge labels (same for all nodes)
type.method("out_degree", &WG::out_degree);

// number_of_edges - total number of defined edges (all nodes)
type.method("number_of_edges", [](WG const & self) -> size_t {
return self.number_of_edges();
});

// number_of_edges_node - number of defined edges from a specific node
// Julia passes 1-based node index
type.method("number_of_edges_node", [](WG const & self, uint32_t s) -> size_t {
return self.number_of_edges(to_0_based(s));
});

//////////////////////////////////////////////////////////////////////////
// Edge lookup
//////////////////////////////////////////////////////////////////////////

// target - get the target of edge (source, label).
// Julia passes 1-based source and label, returns 1-based target (0 = UNDEFINED)
type.method("target", [](WG const & self, uint32_t source, uint32_t label) -> uint32_t {
return to_1_based_undef(self.target(to_0_based(source), to_0_based(label)));
});

// next_label_and_target - find next defined edge from node s with label >= a.
// Returns a 2-element vector [label, target] since CxxWrap can't return std::pair.
// Julia passes 1-based s and a, returns 1-based label/target (0 = UNDEFINED)
type.method("next_label_and_target_vec",
[](WG const & self, uint32_t s, uint32_t a) -> std::vector<uint32_t> {
auto [label, target] = self.next_label_and_target(to_0_based(s), to_0_based(a));
return {to_1_based_undef(label), to_1_based_undef(target)};
});

//////////////////////////////////////////////////////////////////////////
// Iteration helpers (collect to vector for CxxWrap)
//////////////////////////////////////////////////////////////////////////

// targets_vector - all targets from a given source node as a vector.
// Julia passes 1-based source, returns 1-based targets (0 = UNDEFINED)
type.method(
"targets_vector", [](WG const & self, uint32_t source) -> std::vector<uint32_t> {
auto src = to_0_based(source);
std::vector<uint32_t> result;
result.reserve(self.out_degree());
for (auto it = self.cbegin_targets(src); it != self.cend_targets(src); ++it)
{
result.push_back(to_1_based_undef(*it));
}
return result;
});

//////////////////////////////////////////////////////////////////////////
// Comparison
//////////////////////////////////////////////////////////////////////////

type.method("is_equal", [](WG const & a, WG const & b) -> bool {
return a == b;
});
type.method("is_not_equal", [](WG const & a, WG const & b) -> bool {
return a != b;
});
type.method("is_less", [](WG const & a, WG const & b) -> bool {
return a < b;
});

//////////////////////////////////////////////////////////////////////////
// Copy and hash
//////////////////////////////////////////////////////////////////////////

// copy - returns a deep copy
type.method("copy", [](WG const & self) -> WG {
return WG(self);
});

// hash - for use in Julia Base.hash
type.method("hash", [](WG const & self) -> size_t {
return self.hash_value();
});
}

} // namespace libsemigroups_julia
6 changes: 6 additions & 0 deletions src/Semigroups.jl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ using .Errors: LibsemigroupsError, @wrap_libsemigroups_call
# Julia-side wrapper files
include("libsemigroups/constants.jl")
include("libsemigroups/runner.jl")
include("libsemigroups/word-graph.jl")
include("libsemigroups/transf.jl")

# High-level element types
Expand Down Expand Up @@ -91,6 +92,11 @@ export report_why_we_stopped, string_why_we_stopped
export tril, tril_FALSE, tril_TRUE, tril_unknown, tril_to_bool
export is_undefined, is_positive_infinity, is_negative_infinity, is_limit_max

# WordGraph
export WordGraph
export number_of_nodes, out_degree, number_of_edges
export target, next_label_and_target, targets

# Transformation types and functions
export Transf, PPerm, Perm
export degree, rank, images, image_set, domain_set
Expand Down
125 changes: 125 additions & 0 deletions src/libsemigroups/word-graph.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Copyright (c) 2026, James W. Swent
#
# Distributed under the terms of the GPL license version 3.
#
# The full license is in the file LICENSE, distributed with this software.

"""
word-graph.jl - Julia wrappers for libsemigroups WordGraph

This file provides low-level Julia wrappers for the C++ WordGraph<uint32_t>
class exposed via CxxWrap.

All indices are **1-based** (Julia convention). The C++ binding layer
handles conversion to 0-based internally. UNDEFINED is represented as `0`.
"""

# ============================================================================
# Type alias
# ============================================================================

"""
WordGraph

A word graph (deterministic automaton without initial or accept states).
Nodes are numbered `1` to `number_of_nodes(g)` and every node has the
same out-degree. Edge labels are `1` to `out_degree(g)`.

Construct with `WordGraph(m, n)` for `m` nodes and out-degree `n`.
"""
const WordGraph = LibSemigroups.WordGraph

# ============================================================================
# Size / structure queries
# ============================================================================

"""
number_of_nodes(g::WordGraph) -> Int

Return the number of nodes in the word graph.
"""
number_of_nodes(g::WordGraph) = Int(LibSemigroups.number_of_nodes(g))

"""
out_degree(g::WordGraph) -> Int

Return the out-degree (number of edge labels) of every node.
"""
out_degree(g::WordGraph) = Int(LibSemigroups.out_degree(g))

"""
number_of_edges(g::WordGraph) -> Int

Return the total number of defined edges in the word graph.
"""
number_of_edges(g::WordGraph) = Int(LibSemigroups.number_of_edges(g))

"""
number_of_edges(g::WordGraph, s::Integer) -> Int

Return the number of defined edges with source node `s` (1-based).
"""
number_of_edges(g::WordGraph, s::Integer) =
Int(LibSemigroups.number_of_edges_node(g, UInt32(s)))


# ============================================================================
# Edge lookup
# ============================================================================

"""
target(g::WordGraph, source::Integer, label::Integer) -> UInt32

Return the target of the edge from `source` with `label` (both 1-based).
Returns `0` if no such edge is defined (UNDEFINED).
"""
target(g::WordGraph, source::Integer, label::Integer) =
LibSemigroups.target(g, UInt32(source), UInt32(label))

"""
next_label_and_target(g::WordGraph, s::Integer, a::Integer)

Return the next defined edge from node `s` with label >= `a` (both 1-based).
Returns a `(label, target)` tuple; both are `0` (UNDEFINED) if none found.
"""
function next_label_and_target(g::WordGraph, s::Integer, a::Integer)
v = LibSemigroups.next_label_and_target_vec(g, UInt32(s), UInt32(a))
return (v[1], v[2])
end

# ============================================================================
# Iteration helpers
# ============================================================================

"""
targets(g::WordGraph, source::Integer) -> Vector{UInt32}

Return all edge targets from `source` (1-based) as a vector. Includes
`0` (UNDEFINED) entries for labels with no defined edge.
"""
targets(g::WordGraph, source::Integer) = LibSemigroups.targets_vector(g, UInt32(source))

# ============================================================================
# Comparison operators
# ============================================================================

Base.:(==)(a::WordGraph, b::WordGraph) = LibSemigroups.is_equal(a, b)
Base.:(<)(a::WordGraph, b::WordGraph) = LibSemigroups.is_less(a, b)

# ============================================================================
# Copy and hash
# ============================================================================

Base.copy(g::WordGraph) = LibSemigroups.copy(g)
Base.hash(g::WordGraph, h::UInt) = hash(LibSemigroups.hash(g), h)

# ============================================================================
# Display
# ============================================================================

function Base.show(io::IO, g::WordGraph)
n = number_of_nodes(g)
e = number_of_edges(g)
d = out_degree(g)
print(io, "WordGraph($n, $d) with $e edges")
end
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ using Semigroups
include("test_constants.jl")
include("test_errors.jl")
include("test_runner.jl")
include("test_word_graph.jl")
include("test_transf.jl")
end
Loading