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 deps/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ add_library(libsemigroups_julia SHARED
transf.cpp
word-graph.cpp
word-range.cpp
paths.cpp
presentation.cpp
presentation-examples.cpp
knuth-bendix.cpp
Expand Down
6 changes: 3 additions & 3 deletions deps/src/libsemigroups_julia.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@
namespace libsemigroups_julia {

JLCXX_MODULE define_julia_module(jl::Module& mod) {
mod.method("libsemigroups_version", []() -> std::string {
return LIBSEMIGROUPS_VERSION;
});
mod.method("libsemigroups_version",
[]() -> std::string { return LIBSEMIGROUPS_VERSION; });

// Define constants first (UNDEFINED, POSITIVE_INFINITY, etc.)
define_constants(mod);
Expand All @@ -49,6 +48,7 @@ namespace libsemigroups_julia {
define_order(mod);
define_word_range(mod);
define_word_graph(mod);
define_paths(mod);
define_froidure_pin_base(mod);
define_froidure_pin(mod);
define_presentation(mod);
Expand Down
1 change: 1 addition & 0 deletions deps/src/libsemigroups_julia.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ namespace libsemigroups_julia {
void define_order(jl::Module& mod);
void define_word_range(jl::Module& mod);
void define_word_graph(jl::Module& mod);
void define_paths(jl::Module& mod);
void define_froidure_pin_base(jl::Module& mod);
void define_froidure_pin(jl::Module& mod);
void define_presentation(jl::Module& mod);
Expand Down
142 changes: 142 additions & 0 deletions deps/src/paths.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
//
// 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 <libsemigroups/order.hpp>
#include <libsemigroups/paths.hpp>
#include <libsemigroups/types.hpp>
#include <libsemigroups/word-graph.hpp>

#include <cstddef>
#include <cstdint>
#include <string>

namespace jlcxx {
template <>
struct IsMirroredType<libsemigroups::Paths<uint32_t>> : std::false_type {};
} // namespace jlcxx

namespace libsemigroups_julia {

void define_paths(jl::Module& m) {
using Paths_ = libsemigroups::Paths<uint32_t>;
using WordGraph_ = libsemigroups::WordGraph<uint32_t>;
using libsemigroups::Order;

// Registered as "PathsCxx" so the public Julia name `Paths` is free for
// the high-level wrapper struct in `src/paths.jl`.
auto type = m.add_type<Paths_>("PathsCxx");

// Constructor: Paths(WordGraph const&). The C++ object holds only a raw
// pointer to the WordGraph; lifetime is the Julia wrapper's responsibility
// (the wrapper struct holds a `g::WordGraph` field that pins it for GC).
type.constructor<WordGraph_ const&>();

// Reinitialize: rebind to a new WordGraph and reset all settings. The
// Julia wrapper's `init!` overwrites its `g` field after this call so the
// GC pin matches the new C++ pointer.
type.method("init!",
[](Paths_& self, WordGraph_ const& wg) { self.init(wg); });

// --- Intentionally not bound ---
// * `is_finite` / `is_idempotent`: compile-time `static constexpr bool`
// traits for rx::ranges integration; not user-callable methods.
// * `size_hint()`: numerically identical to `count()` but lives only for
// rx::ranges compatibility -- redundant on the Julia side.
// * `cbegin_pilo` / `cbegin_pislo` / `cbegin_pstilo` / `cbegin_pstislo`
// (and matching `cend_*`): low-level iterator factories subsumed by
// the high-level `Paths` range API (`get` / `next!` / `at_end`).

// --- Validation ---

type.method("throw_if_source_undefined",
[](Paths_ const& self) { self.throw_if_source_undefined(); });

// --- Range / iteration interface ---

// `get` returns `word_type const&` in C++ (reference to internal iterator
// storage that is invalidated by `next`). Copy at the boundary.
type.method("get", [](Paths_ const& self) -> libsemigroups::word_type {
return self.get();
});

type.method("next!", [](Paths_& self) { self.next(); });

type.method("at_end",
[](Paths_ const& self) -> bool { return self.at_end(); });

type.method("count",
[](Paths_ const& self) -> uint64_t { return self.count(); });

// --- Settings: getter / setter pairs ---
// Same-name different-arity overloads are unreliable in CxxWrap; split
// setters to use the `!` suffix per Julia convention.

// source
type.method("source",
[](Paths_ const& self) -> uint32_t { return self.source(); });
type.method("source!", [](Paths_& self, uint32_t n) { self.source(n); });

// target — single setter handles both regular and UNDEFINED cases. The
// underlying libsemigroups `target(n)` short-circuits on `n == UNDEFINED`
// (paths.hpp:968-973), accepting it as "any reachable target". The Julia
// wrapper's `target!(p, ::UndefinedType)` arm dispatches into this same
// call after converting UNDEFINED to typemax(uint32_t).
type.method("target",
[](Paths_ const& self) -> uint32_t { return self.target(); });
type.method("target!", [](Paths_& self, uint32_t n) { self.target(n); });

// min
type.method("min",
[](Paths_ const& self) -> std::size_t { return self.min(); });
type.method("min!", [](Paths_& self, std::size_t val) { self.min(val); });

// max
type.method("max",
[](Paths_ const& self) -> std::size_t { return self.max(); });
type.method("max!", [](Paths_& self, std::size_t val) { self.max(val); });

// order
type.method("order",
[](Paths_ const& self) -> Order { return self.order(); });
type.method("order!", [](Paths_& self, Order val) { self.order(val); });

// --- Read-only queries ---

type.method("current_target", [](Paths_ const& self) -> uint32_t {
return self.current_target();
});

// word_graph returns `WordGraph const&`; the Julia wrapper holds the
// original WordGraph in its `g` field, so this is rarely needed from
// Julia. Bound for parity / completeness. Returned as a reference (the
// caller gets a `CxxBaseRef{WordGraph}`).
type.method("word_graph", [](Paths_ const& self) -> WordGraph_ const& {
return self.word_graph();
});

// --- Display ---
// `to_human_readable_repr` is a free function template in libsemigroups,
// bound at module level (not as `type.method`).
m.method("to_human_readable_repr", [](Paths_ const& p) -> std::string {
return libsemigroups::to_human_readable_repr(p);
});
}

} // namespace libsemigroups_julia
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ makedocs(;
"Examples" => "data-structures/presentations/examples.md",
],
"Word Graphs" => "data-structures/word-graph.md",
"Paths" => "data-structures/paths.md",
"Words" => "data-structures/word-range.md",
],
"Main Algorithms" => [
Expand Down
90 changes: 90 additions & 0 deletions docs/src/data-structures/paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# The Paths type

This page documents the type [`Paths`](@ref Semigroups.Paths), a stateful
range over paths in a [`WordGraph`](@ref Semigroups.WordGraph). A `Paths`
object pins its source word graph for garbage collection and yields paths
as `Vector{Int}` of 1-based edge labels via the standard Julia iteration
protocol or the manual `get` / [`next!`](@ref Semigroups.next!) /
[`at_end`](@ref Semigroups.at_end) interface.

!!! warning "v1 limitation"
Paths is bound for `WordGraph<uint32_t>` only; other Node types follow
when consumers need them.

```@docs
Semigroups.Paths
```

## Usage

```jldoctest
julia> using Semigroups

julia> g = WordGraph(3, 2);

julia> target!(g, 1, 1, 2); target!(g, 1, 2, 3); target!(g, 2, 1, 3);

julia> p = paths(g; source = 1, max = 3);

julia> collect(p)
4-element Vector{Vector{Int64}}:
[]
[1]
[2]
[1, 1]
```

## Contents

| Function | Description |
| ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| [`Paths`](@ref Semigroups.Paths(::WordGraph)) | Construct a [`Paths`](@ref Semigroups.Paths) over a word graph. |
| [`paths`](@ref Semigroups.paths(::WordGraph)) | Keyword-argument factory for a [`Paths`](@ref Semigroups.Paths). |
| [`init!`](@ref Semigroups.init!(::Paths, ::WordGraph)) | Rebind to a new [`WordGraph`](@ref Semigroups.WordGraph) and reset settings. |
| [`source`](@ref Semigroups.source(::Paths)) | Get the current source node. |
| [`source!`](@ref Semigroups.source!(::Paths, ::Integer)) | Set the source node. |
| [`target`](@ref Semigroups.target(::Paths)) | Get the current target node (or [`UNDEFINED`](@ref Semigroups.UNDEFINED)). |
| [`target!`](@ref Semigroups.target!(::Paths, ::Integer)) | Set the target node, or clear it with [`UNDEFINED`](@ref Semigroups.UNDEFINED). |
| [`min!`](@ref Semigroups.min!(::Paths, ::Integer)) | Set the minimum path length. |
| [`max!`](@ref Semigroups.max!(::Paths, ::Integer)) | Set the maximum path length, or remove the bound with [`POSITIVE_INFINITY`](@ref Semigroups.POSITIVE_INFINITY). |
| [`order`](@ref Semigroups.order(::Paths)) | Get the current word [`Order`](@ref Semigroups.Order). |
| [`order!`](@ref Semigroups.order!(::Paths, ::Order)) | Set the word [`Order`](@ref Semigroups.Order). |
| [`current_target`](@ref Semigroups.current_target(::Paths)) | Target node of the path currently labelled by [`Base.get`](@ref Base.get(::Paths)). |
| [`word_graph`](@ref Semigroups.word_graph(::Paths)) | Return the underlying [`WordGraph`](@ref Semigroups.WordGraph). |
| [`next!`](@ref Semigroups.next!(::Paths)) | Advance the range to the next path. |
| [`at_end`](@ref Semigroups.at_end(::Paths)) | Test whether the range is exhausted. |
| [`throw_if_source_undefined`](@ref Semigroups.throw_if_source_undefined(::Paths)) | Throw if [`source`](@ref Semigroups.source(::Paths)) is undefined. |
| [`Base.min`](@ref Base.min(::Paths)) | Get the current minimum path length (qualified-only). |
| [`Base.max`](@ref Base.max(::Paths)) | Get the current maximum path length (qualified-only). |
| [`Base.get`](@ref Base.get(::Paths)) | Get the current path as a `Vector{Int}` (qualified-only). |
| [`Base.count`](@ref Base.count(::Paths)) | Get the number of paths in the range (qualified-only). |

[`Paths`](@ref Semigroups.Paths) also implements the standard Julia iteration
protocol — `for w in p`, `collect(p)`, etc. — and a `Base.show` method for
human-readable display. Iteration is *destructive*: it advances `p` itself,
leaving `at_end(p)` true on completion.

## Full API

```@docs
Semigroups.Paths(::WordGraph)
Semigroups.paths(::WordGraph)
Semigroups.init!(::Paths, ::WordGraph)
Semigroups.source(::Paths)
Semigroups.source!(::Paths, ::Integer)
Semigroups.target(::Paths)
Semigroups.target!(::Paths, ::Integer)
Semigroups.min!(::Paths, ::Integer)
Semigroups.max!(::Paths, ::Integer)
Semigroups.order(::Paths)
Semigroups.order!(::Paths, ::Order)
Semigroups.current_target(::Paths)
Semigroups.word_graph(::Paths)
Semigroups.next!(::Paths)
Semigroups.at_end(::Paths)
Semigroups.throw_if_source_undefined(::Paths)
Base.min(::Paths)
Base.max(::Paths)
Base.get(::Paths)
Base.count(::Paths)
```
Loading
Loading