diff --git a/Makefile b/Makefile index f8eba83..6ceeff8 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ docs: $(JULIA) --project=docs -e 'using Pkg; Pkg.develop(path="."); Pkg.instantiate()' $(JULIA) --project=docs docs/make.jl -docs-serve: docs +docs-serve: $(JULIA) --project=docs -e 'using Pkg; Pkg.add("LiveServer")' $(JULIA) --project=docs -e 'using LiveServer; servedocs(; include_dirs = ["src"])' diff --git a/deps/src/CMakeLists.txt b/deps/src/CMakeLists.txt index 114859f..6bc4c3a 100644 --- a/deps/src/CMakeLists.txt +++ b/deps/src/CMakeLists.txt @@ -52,6 +52,8 @@ add_library(libsemigroups_julia SHARED transf.cpp word-graph.cpp word-range.cpp + presentation.cpp + presentation-examples.cpp ) # Include directories diff --git a/deps/src/libsemigroups_julia.cpp b/deps/src/libsemigroups_julia.cpp index e59bb4c..ba5d9d7 100644 --- a/deps/src/libsemigroups_julia.cpp +++ b/deps/src/libsemigroups_julia.cpp @@ -40,6 +40,8 @@ namespace libsemigroups_julia { define_order(mod); define_word_range(mod); define_word_graph(mod); + define_presentation(mod); + define_presentation_examples(mod); } } // namespace libsemigroups_julia diff --git a/deps/src/libsemigroups_julia.hpp b/deps/src/libsemigroups_julia.hpp index 4e42d19..b582894 100644 --- a/deps/src/libsemigroups_julia.hpp +++ b/deps/src/libsemigroups_julia.hpp @@ -62,6 +62,8 @@ 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_presentation(jl::Module& mod); + void define_presentation_examples(jl::Module& mod); } // namespace libsemigroups_julia diff --git a/deps/src/presentation-examples.cpp b/deps/src/presentation-examples.cpp new file mode 100644 index 0000000..c8bf1c1 --- /dev/null +++ b/deps/src/presentation-examples.cpp @@ -0,0 +1,184 @@ +// +// 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 . +// + +#include "libsemigroups_julia.hpp" + +#include +#include +#include + +#include + +#include +#include + +namespace libsemigroups_julia { + + void define_presentation_examples(jl::Module& m) { + using libsemigroups::Presentation; + using libsemigroups::word_type; + namespace examples = libsemigroups::presentation::examples; + + m.method("example_symmetric_group", + [](size_t n) -> Presentation { + return examples::symmetric_group(n); + }); + m.method("example_alternating_group", + [](size_t n) -> Presentation { + return examples::alternating_group(n); + }); + m.method("example_braid_group", [](size_t n) -> Presentation { + return examples::braid_group(n); + }); + m.method("example_not_symmetric_group", + [](size_t n) -> Presentation { + return examples::not_symmetric_group(n); + }); + m.method("example_full_transformation_monoid", + [](size_t n) -> Presentation { + return examples::full_transformation_monoid(n); + }); + m.method("example_partial_transformation_monoid", + [](size_t n) -> Presentation { + return examples::partial_transformation_monoid(n); + }); + m.method("example_symmetric_inverse_monoid", + [](size_t n) -> Presentation { + return examples::symmetric_inverse_monoid(n); + }); + m.method("example_cyclic_inverse_monoid", + [](size_t n) -> Presentation { + return examples::cyclic_inverse_monoid(n); + }); + m.method("example_order_preserving_monoid", + [](size_t n) -> Presentation { + return examples::order_preserving_monoid(n); + }); + m.method("example_order_preserving_cyclic_inverse_monoid", + [](size_t n) -> Presentation { + return examples::order_preserving_cyclic_inverse_monoid(n); + }); + m.method("example_orientation_preserving_monoid", + [](size_t n) -> Presentation { + return examples::orientation_preserving_monoid(n); + }); + m.method("example_orientation_preserving_reversing_monoid", + [](size_t n) -> Presentation { + return examples::orientation_preserving_reversing_monoid(n); + }); + m.method("example_partition_monoid", + [](size_t n) -> Presentation { + return examples::partition_monoid(n); + }); + m.method("example_partial_brauer_monoid", + [](size_t n) -> Presentation { + return examples::partial_brauer_monoid(n); + }); + m.method("example_brauer_monoid", [](size_t n) -> Presentation { + return examples::brauer_monoid(n); + }); + m.method("example_singular_brauer_monoid", + [](size_t n) -> Presentation { + return examples::singular_brauer_monoid(n); + }); + m.method("example_temperley_lieb_monoid", + [](size_t n) -> Presentation { + return examples::temperley_lieb_monoid(n); + }); + m.method("example_motzkin_monoid", [](size_t n) -> Presentation { + return examples::motzkin_monoid(n); + }); + m.method("example_partial_isometries_cycle_graph_monoid", + [](size_t n) -> Presentation { + return examples::partial_isometries_cycle_graph_monoid(n); + }); + m.method("example_uniform_block_bijection_monoid", + [](size_t n) -> Presentation { + return examples::uniform_block_bijection_monoid(n); + }); + m.method("example_dual_symmetric_inverse_monoid", + [](size_t n) -> Presentation { + return examples::dual_symmetric_inverse_monoid(n); + }); + m.method("example_stellar_monoid", [](size_t l) -> Presentation { + return examples::stellar_monoid(l); + }); + m.method("example_zero_rook_monoid", + [](size_t n) -> Presentation { + return examples::zero_rook_monoid(n); + }); + m.method("example_abacus_jones_monoid", + [](size_t n, size_t d) -> Presentation { + return examples::abacus_jones_monoid(n, d); + }); + + // ---- batch C: plactic / misc ---- + + m.method("example_plactic_monoid", [](size_t n) -> Presentation { + return examples::plactic_monoid(n); + }); + m.method("example_chinese_monoid", [](size_t n) -> Presentation { + return examples::chinese_monoid(n); + }); + m.method("example_hypo_plactic_monoid", + [](size_t n) -> Presentation { + return examples::hypo_plactic_monoid(n); + }); + m.method("example_stylic_monoid", [](size_t n) -> Presentation { + return examples::stylic_monoid(n); + }); + m.method("example_special_linear_group_2", + [](size_t q) -> Presentation { + return examples::special_linear_group_2(q); + }); + m.method("example_fibonacci_semigroup", + [](size_t r, size_t n) -> Presentation { + return examples::fibonacci_semigroup(r, n); + }); + m.method("example_monogenic_semigroup", + [](size_t m, size_t r) -> Presentation { + return examples::monogenic_semigroup(m, r); + }); + m.method("example_rectangular_band", + [](size_t m, size_t n) -> Presentation { + return examples::rectangular_band(m, n); + }); + m.method("example_sigma_plactic_monoid", + [](jlcxx::ArrayRef sigma) -> Presentation { + return examples::sigma_plactic_monoid( + std::vector(sigma.begin(), sigma.end())); + }); + m.method("example_renner_type_B_monoid", + [](size_t l, int q) -> Presentation { + return examples::renner_type_B_monoid(l, q); + }); + m.method("example_renner_type_D_monoid", + [](size_t l, int q) -> Presentation { + return examples::renner_type_D_monoid(l, q); + }); + m.method("example_not_renner_type_B_monoid", + [](size_t l, int q) -> Presentation { + return examples::not_renner_type_B_monoid(l, q); + }); + m.method("example_not_renner_type_D_monoid", + [](size_t l, int q) -> Presentation { + return examples::not_renner_type_D_monoid(l, q); + }); + } + +} // namespace libsemigroups_julia diff --git a/deps/src/presentation.cpp b/deps/src/presentation.cpp new file mode 100644 index 0000000..71ac3fb --- /dev/null +++ b/deps/src/presentation.cpp @@ -0,0 +1,373 @@ +// +// 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 . +// + +#include "libsemigroups_julia.hpp" + +#include +#include + +#include + +#include +#include +#include +#include + +namespace jlcxx { + template <> + struct IsMirroredType> + : std::false_type {}; + + template <> + struct IsMirroredType< + libsemigroups::InversePresentation> + : std::false_type {}; + + template <> + struct SuperType< + libsemigroups::InversePresentation> { + using type = libsemigroups::Presentation; + }; +} // namespace jlcxx + +namespace libsemigroups_julia { + + void define_presentation(jl::Module& m) { + using libsemigroups::Presentation; + using libsemigroups::word_type; + + auto type = m.add_type>("Presentation"); + type.constructor<>(); + type.constructor const&>(); // copy ctor + + type.method("init!", [](Presentation& self) { self.init(); }); + + type.method("alphabet", + [](Presentation const& self) -> word_type { + return self.alphabet(); + }); + + type.method( + "set_alphabet_size!", + [](Presentation& self, size_t n) { self.alphabet(n); }); + + type.method("set_alphabet!", + [](Presentation& self, jlcxx::ArrayRef a) { + self.alphabet(word_type(a.begin(), a.end())); + }); + + type.method("alphabet_from_rules!", [](Presentation& self) { + self.alphabet_from_rules(); + }); + + type.method("letter", + [](Presentation const& self, size_t i) -> size_t { + return self.letter(i); + }); + + type.method("index_of", + [](Presentation const& self, size_t x) -> size_t { + return self.index(x); + }); + + type.method("in_alphabet", + [](Presentation const& self, size_t x) -> bool { + return self.in_alphabet(x); + }); + + type.method("contains_empty_word", + [](Presentation const& self) -> bool { + return self.contains_empty_word(); + }); + type.method("set_contains_empty_word!", + [](Presentation& self, bool v) { + self.contains_empty_word(v); + }); + + type.method("add_generator_no_arg!", + [](Presentation& self) -> size_t { + return self.add_generator(); + }); + type.method("add_generator!", [](Presentation& self, size_t x) { + self.add_generator(x); + }); + type.method("remove_generator!", + [](Presentation& self, size_t x) { + self.remove_generator(x); + }); + + m.method("add_rule_no_checks!", + [](Presentation& p, + jlcxx::ArrayRef lhs, + jlcxx::ArrayRef rhs) { + word_type l(lhs.begin(), lhs.end()); + word_type r(rhs.begin(), rhs.end()); + libsemigroups::presentation::add_rule_no_checks(p, l, r); + }); + + m.method("add_rule!", + [](Presentation& p, + jlcxx::ArrayRef lhs, + jlcxx::ArrayRef rhs) { + word_type l(lhs.begin(), lhs.end()); + word_type r(rhs.begin(), rhs.end()); + libsemigroups::presentation::add_rule(p, l, r); + }); + + type.method("number_of_rules", + [](Presentation const& self) -> size_t { + return self.rules.size() / 2; + }); + + type.method("rule_lhs", + [](Presentation const& self, size_t i) -> word_type { + return self.rules.at(2 * i); + }); + + type.method("rule_rhs", + [](Presentation const& self, size_t i) -> word_type { + return self.rules.at(2 * i + 1); + }); + + type.method("clear_rules!", + [](Presentation& self) { self.rules.clear(); }); + + type.method("throw_if_alphabet_has_duplicates", + [](Presentation const& self) { + self.throw_if_alphabet_has_duplicates(); + }); + type.method("throw_if_letter_not_in_alphabet", + [](Presentation const& self, size_t x) { + self.throw_if_letter_not_in_alphabet(x); + }); + type.method("throw_if_bad_rules", [](Presentation const& self) { + self.throw_if_bad_rules(); + }); + type.method("throw_if_bad_alphabet_or_rules", + [](Presentation const& self) { + self.throw_if_bad_alphabet_or_rules(); + }); + + m.method("length_of", [](Presentation const& p) -> size_t { + return libsemigroups::presentation::length(p); + }); + m.method("longest_rule_length", + [](Presentation const& p) -> size_t { + return libsemigroups::presentation::longest_rule_length(p); + }); + m.method("shortest_rule_length", + [](Presentation const& p) -> size_t { + return libsemigroups::presentation::shortest_rule_length(p); + }); + m.method("is_normalized", [](Presentation const& p) -> bool { + return libsemigroups::presentation::is_normalized(p); + }); + m.method("are_rules_sorted", [](Presentation const& p) -> bool { + return libsemigroups::presentation::are_rules_sorted(p); + }); + m.method("contains_rule", + [](Presentation& p, + jlcxx::ArrayRef lhs, + jlcxx::ArrayRef rhs) -> bool { + word_type l(lhs.begin(), lhs.end()); + word_type r(rhs.begin(), rhs.end()); + return libsemigroups::presentation::contains_rule(p, l, r); + }); + m.method("throw_if_odd_number_of_rules", + [](Presentation const& p) { + // Fully qualified: the iterator-pair overload is also in scope. + libsemigroups::presentation::throw_if_odd_number_of_rules(p); + }); + + m.method("normalize_alphabet!", [](Presentation& p) { + libsemigroups::presentation::normalize_alphabet(p); + }); + m.method("change_alphabet!", + [](Presentation& p, jlcxx::ArrayRef a) { + libsemigroups::presentation::change_alphabet( + p, word_type(a.begin(), a.end())); + }); + m.method("reverse_rules!", [](Presentation& p) { + libsemigroups::presentation::reverse(p); + }); + m.method("sort_rules!", [](Presentation& p) { + libsemigroups::presentation::sort_rules(p); + }); + m.method("sort_each_rule!", [](Presentation& p) -> bool { + return libsemigroups::presentation::sort_each_rule(p); + }); + + m.method("add_identity_rules!", [](Presentation& p, size_t e) { + libsemigroups::presentation::add_identity_rules(p, e); + }); + m.method("add_zero_rules!", [](Presentation& p, size_t z) { + libsemigroups::presentation::add_zero_rules(p, z); + }); + m.method("remove_duplicate_rules!", [](Presentation& p) { + libsemigroups::presentation::remove_duplicate_rules(p); + }); + m.method("remove_trivial_rules!", [](Presentation& p) { + libsemigroups::presentation::remove_trivial_rules(p); + }); + + m.method("add_rules!", + [](Presentation& p, Presentation const& q) { + libsemigroups::presentation::add_rules(p, q); + }); + + m.method("add_inverse_rules!", + [](Presentation& p, jlcxx::ArrayRef inverses) { + word_type v(inverses.begin(), inverses.end()); + libsemigroups::presentation::add_inverse_rules(p, v); + }); + + m.method("add_inverse_rules_with_identity!", + [](Presentation& p, + jlcxx::ArrayRef inverses, + size_t e) { + word_type v(inverses.begin(), inverses.end()); + libsemigroups::presentation::add_inverse_rules(p, v, e); + }); + + m.method("replace_subword!", + [](Presentation& p, + jlcxx::ArrayRef existing, + jlcxx::ArrayRef replacement) { + word_type e(existing.begin(), existing.end()); + word_type r(replacement.begin(), replacement.end()); + libsemigroups::presentation::replace_subword(p, e, r); + }); + + m.method("replace_word!", + [](Presentation& p, + jlcxx::ArrayRef existing, + jlcxx::ArrayRef replacement) { + word_type e(existing.begin(), existing.end()); + word_type r(replacement.begin(), replacement.end()); + libsemigroups::presentation::replace_word(p, e, r); + }); + + m.method( + "replace_word_with_new_generator!", + [](Presentation& p, jlcxx::ArrayRef w) -> size_t { + word_type v(w.begin(), w.end()); + return libsemigroups::presentation::replace_word_with_new_generator( + p, v); + }); + + m.method("first_unused_letter", + [](Presentation const& p) -> size_t { + return libsemigroups::presentation::first_unused_letter(p); + }); + + m.method("index_rule", + [](Presentation const& p, + jlcxx::ArrayRef lhs, + jlcxx::ArrayRef rhs) -> size_t { + word_type l(lhs.begin(), lhs.end()); + word_type r(rhs.begin(), rhs.end()); + return libsemigroups::presentation::index_rule(p, l, r); + }); + + m.method("is_rule", + [](Presentation const& p, + jlcxx::ArrayRef lhs, + jlcxx::ArrayRef rhs) -> bool { + word_type l(lhs.begin(), lhs.end()); + word_type r(rhs.begin(), rhs.end()); + return libsemigroups::presentation::is_rule(p, l, r); + }); + + m.method("longest_rule_index", + [](Presentation const& p) -> size_t { + auto it = libsemigroups::presentation::longest_rule(p); + return static_cast(std::distance(p.rules.cbegin(), it)); + }); + + m.method("shortest_rule_index", + [](Presentation const& p) -> size_t { + auto it = libsemigroups::presentation::shortest_rule(p); + return static_cast(std::distance(p.rules.cbegin(), it)); + }); + + m.method( + "throw_if_bad_inverses", + [](Presentation const& p, jlcxx::ArrayRef inverses) { + word_type v(inverses.begin(), inverses.end()); + libsemigroups::presentation::throw_if_bad_inverses(p, v); + }); + + m.method("to_gap_string", + [](Presentation const& p, + std::string const& var_name) -> std::string { + return libsemigroups::presentation::to_gap_string(p, var_name); + }); + + m.method("rules_vector", + [](Presentation const& p) -> std::vector { + return std::vector(p.rules.cbegin(), p.rules.cend()); + }); + + type.method( + "is_equal", + [](Presentation const& a, + Presentation const& b) -> bool { return a == b; }); + type.method("to_human_readable_repr", + [](Presentation const& p) -> std::string { + return libsemigroups::to_human_readable_repr(p); + }); + + // ----------------------------------------------------------------------- + // InversePresentation + // ----------------------------------------------------------------------- + using libsemigroups::InversePresentation; + + auto itype = m.add_type>( + "InversePresentation", + jlcxx::julia_base_type>()); + + itype.constructor const&>(); + itype.constructor const&>(); // copy ctor + + itype.method( + "set_inverses!", + [](InversePresentation& self, jlcxx::ArrayRef w) { + self.inverses(word_type(w.begin(), w.end())); + }); + itype.method("inverses", + [](InversePresentation const& self) -> word_type { + return self.inverses(); + }); + itype.method("inverse_of", + [](InversePresentation const& self, + size_t x) -> size_t { return self.inverse(x); }); + itype.method("throw_if_bad_alphabet_rules_or_inverses", + [](InversePresentation const& self) { + self.throw_if_bad_alphabet_rules_or_inverses(); + }); + itype.method( + "is_equal_inv", + [](InversePresentation const& a, + InversePresentation const& b) -> bool { return a == b; }); + itype.method("to_human_readable_repr", + [](InversePresentation const& self) -> std::string { + return libsemigroups::to_human_readable_repr(self); + }); + } + +} // namespace libsemigroups_julia diff --git a/docs/make.jl b/docs/make.jl index 704677b..d20887f 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -39,6 +39,13 @@ makedocs(; "Orders" => "data-structures/order.md", "Words" => "data-structures/word-range.md", "Word Graphs" => "data-structures/word-graph.md", + "Presentations" => [ + "Overview" => "data-structures/presentations/index.md", + "Presentation" => "data-structures/presentations/presentation.md", + "InversePresentation" => "data-structures/presentations/inverse-presentation.md", + "Helper functions" => "data-structures/presentations/helpers.md", + "Examples" => "data-structures/presentations/examples.md", + ], "Elements" => [ "Overview" => "data-structures/elements/index.md", "Transformations" => [ diff --git a/docs/src/data-structures/elements/transformations/index.md b/docs/src/data-structures/elements/transformations/index.md index b861e40..8168f45 100644 --- a/docs/src/data-structures/elements/transformations/index.md +++ b/docs/src/data-structures/elements/transformations/index.md @@ -52,7 +52,7 @@ All transformation types use **1-based indexing** (the standard Julia convention). Internally, indices are converted to 0-based for the underlying C++ library ([libsemigroups](https://libsemigroups.github.io/libsemigroups/)). -## Full API — shared functions +## Full API - shared functions ```@docs Base.copy(::Semigroups.Transf) diff --git a/docs/src/data-structures/elements/transformations/perm.md b/docs/src/data-structures/elements/transformations/perm.md index 6cf978b..c0b9de5 100644 --- a/docs/src/data-structures/elements/transformations/perm.md +++ b/docs/src/data-structures/elements/transformations/perm.md @@ -61,7 +61,7 @@ Permutations can be composed using `*`: ```julia p = Perm([2, 3, 1]) q = Perm([3, 1, 2]) -r = p * q # Perm([1, 2, 3]) — the identity +r = p * q # Perm([1, 2, 3]) - the identity ``` ### Comparison diff --git a/docs/src/data-structures/presentations/examples.md b/docs/src/data-structures/presentations/examples.md new file mode 100644 index 0000000..0882e48 --- /dev/null +++ b/docs/src/data-structures/presentations/examples.md @@ -0,0 +1,132 @@ +# Example presentations + +This page documents the catalogue of standard presentations exposed via +`libsemigroups::presentation::examples`. Each function returns a fresh +[`Presentation`](@ref Semigroups.Presentation) with the alphabet and rules +initialised to a well-known construction. + +!!! warning "v1 limitation" + Semigroups.jl v1 binds `Presentation` only. Alphabets and + rules use `Vector{Int}` with 1-based letter indices. + +## Groups and transformation monoids + +### Contents + +| Function | Description | +| --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | +| [`symmetric_group`](@ref Semigroups.symmetric_group(::Integer)) | Symmetric group `S_n`. | +| [`alternating_group`](@ref Semigroups.alternating_group(::Integer)) | Alternating group `A_n`. | +| [`braid_group`](@ref Semigroups.braid_group(::Integer)) | Braid group `B_n`. | +| [`not_symmetric_group`](@ref Semigroups.not_symmetric_group(::Integer)) | A presentation that does not define `S_n`. | +| [`full_transformation_monoid`](@ref Semigroups.full_transformation_monoid(::Integer)) | Full transformation monoid `T_n`. | +| [`partial_transformation_monoid`](@ref Semigroups.partial_transformation_monoid(::Integer)) | Partial transformation monoid `PT_n`. | +| [`symmetric_inverse_monoid`](@ref Semigroups.symmetric_inverse_monoid(::Integer)) | Symmetric inverse monoid `I_n`. | +| [`cyclic_inverse_monoid`](@ref Semigroups.cyclic_inverse_monoid(::Integer)) | Cyclic inverse monoid. | +| [`order_preserving_monoid`](@ref Semigroups.order_preserving_monoid(::Integer)) | Monoid of order-preserving transformations of `{1,...,n}`. | +| [`order_preserving_cyclic_inverse_monoid`](@ref Semigroups.order_preserving_cyclic_inverse_monoid(::Integer)) | Order-preserving cyclic inverse monoid. | +| [`orientation_preserving_monoid`](@ref Semigroups.orientation_preserving_monoid(::Integer)) | Orientation-preserving monoid. | +| [`orientation_preserving_reversing_monoid`](@ref Semigroups.orientation_preserving_reversing_monoid(::Integer)) | Orientation-preserving-or-reversing monoid. | + +### Full API + +```@docs +Semigroups.symmetric_group(::Integer) +Semigroups.alternating_group(::Integer) +Semigroups.braid_group(::Integer) +Semigroups.not_symmetric_group(::Integer) +Semigroups.full_transformation_monoid(::Integer) +Semigroups.partial_transformation_monoid(::Integer) +Semigroups.symmetric_inverse_monoid(::Integer) +Semigroups.cyclic_inverse_monoid(::Integer) +Semigroups.order_preserving_monoid(::Integer) +Semigroups.order_preserving_cyclic_inverse_monoid(::Integer) +Semigroups.orientation_preserving_monoid(::Integer) +Semigroups.orientation_preserving_reversing_monoid(::Integer) +``` + +## Diagram and partition monoids + +### Contents + +| Function | Description | +| ----------------------------------------------------------------------------------------------------------- | -------------------------------------- | +| [`partition_monoid`](@ref Semigroups.partition_monoid(::Integer)) | Partition monoid `P_n`. | +| [`partial_brauer_monoid`](@ref Semigroups.partial_brauer_monoid(::Integer)) | Partial Brauer monoid. | +| [`brauer_monoid`](@ref Semigroups.brauer_monoid(::Integer)) | Brauer monoid. | +| [`singular_brauer_monoid`](@ref Semigroups.singular_brauer_monoid(::Integer)) | Singular Brauer monoid. | +| [`temperley_lieb_monoid`](@ref Semigroups.temperley_lieb_monoid(::Integer)) | Temperley-Lieb monoid. | +| [`motzkin_monoid`](@ref Semigroups.motzkin_monoid(::Integer)) | Motzkin monoid. | +| [`partial_isometries_cycle_graph_monoid`](@ref Semigroups.partial_isometries_cycle_graph_monoid(::Integer)) | Partial isometries of the cycle graph. | +| [`uniform_block_bijection_monoid`](@ref Semigroups.uniform_block_bijection_monoid(::Integer)) | Uniform block bijection monoid. | +| [`dual_symmetric_inverse_monoid`](@ref Semigroups.dual_symmetric_inverse_monoid(::Integer)) | Dual symmetric inverse monoid. | +| [`stellar_monoid`](@ref Semigroups.stellar_monoid(::Integer)) | Stellar monoid. | +| [`zero_rook_monoid`](@ref Semigroups.zero_rook_monoid(::Integer)) | 0-rook monoid. | +| [`abacus_jones_monoid`](@ref Semigroups.abacus_jones_monoid(::Integer, ::Integer)) | Abacus Jones monoid. | + +### Full API + +```@docs +Semigroups.partition_monoid(::Integer) +Semigroups.partial_brauer_monoid(::Integer) +Semigroups.brauer_monoid(::Integer) +Semigroups.singular_brauer_monoid(::Integer) +Semigroups.temperley_lieb_monoid(::Integer) +Semigroups.motzkin_monoid(::Integer) +Semigroups.partial_isometries_cycle_graph_monoid(::Integer) +Semigroups.uniform_block_bijection_monoid(::Integer) +Semigroups.dual_symmetric_inverse_monoid(::Integer) +Semigroups.stellar_monoid(::Integer) +Semigroups.zero_rook_monoid(::Integer) +Semigroups.abacus_jones_monoid(::Integer, ::Integer) +``` + +## Plactic monoids + +### Contents + +| Function | Description | +| ------------------------------------------------------------------------------------------- | ---------------------------------- | +| [`plactic_monoid`](@ref Semigroups.plactic_monoid(::Integer)) | Plactic monoid on `n` letters. | +| [`chinese_monoid`](@ref Semigroups.chinese_monoid(::Integer)) | Chinese monoid on `n` letters. | +| [`hypo_plactic_monoid`](@ref Semigroups.hypo_plactic_monoid(::Integer)) | Hypoplactic monoid on `n` letters. | +| [`sigma_plactic_monoid`](@ref Semigroups.sigma_plactic_monoid(::AbstractVector{<:Integer})) | ``\sigma``-plactic monoid for a given `sigma`. | +| [`stylic_monoid`](@ref Semigroups.stylic_monoid(::Integer)) | Stylic monoid on `n` letters. | + +### Full API + +```@docs +Semigroups.plactic_monoid(::Integer) +Semigroups.chinese_monoid(::Integer) +Semigroups.hypo_plactic_monoid(::Integer) +Semigroups.sigma_plactic_monoid(::AbstractVector{<:Integer}) +Semigroups.stylic_monoid(::Integer) +``` + +## Other + +### Contents + +| Function | Description | +| -------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| [`fibonacci_semigroup`](@ref Semigroups.fibonacci_semigroup(::Integer, ::Integer)) | Fibonacci semigroup `F(r, n)`. | +| [`monogenic_semigroup`](@ref Semigroups.monogenic_semigroup(::Integer, ::Integer)) | Monogenic semigroup with index `m`, period `r`. | +| [`rectangular_band`](@ref Semigroups.rectangular_band(::Integer, ::Integer)) | ``m \times n`` rectangular band. | +| [`special_linear_group_2`](@ref Semigroups.special_linear_group_2(::Integer)) | `SL(2, q)`. | +| [`renner_type_B_monoid`](@ref Semigroups.renner_type_B_monoid(::Integer, ::Integer)) | Renner monoid of type B. | +| [`renner_type_D_monoid`](@ref Semigroups.renner_type_D_monoid(::Integer, ::Integer)) | Renner monoid of type D. | +| [`not_renner_type_B_monoid`](@ref Semigroups.not_renner_type_B_monoid(::Integer, ::Integer)) | A presentation that does _not_ define the type-B Renner monoid. | +| [`not_renner_type_D_monoid`](@ref Semigroups.not_renner_type_D_monoid(::Integer, ::Integer)) | A presentation that does _not_ define the type-D Renner monoid. | + +### Full API + +```@docs +Semigroups.fibonacci_semigroup(::Integer, ::Integer) +Semigroups.monogenic_semigroup(::Integer, ::Integer) +Semigroups.rectangular_band(::Integer, ::Integer) +Semigroups.special_linear_group_2(::Integer) +Semigroups.renner_type_B_monoid(::Integer, ::Integer) +Semigroups.renner_type_D_monoid(::Integer, ::Integer) +Semigroups.not_renner_type_B_monoid(::Integer, ::Integer) +Semigroups.not_renner_type_D_monoid(::Integer, ::Integer) +``` diff --git a/docs/src/data-structures/presentations/helpers.md b/docs/src/data-structures/presentations/helpers.md new file mode 100644 index 0000000..c03def1 --- /dev/null +++ b/docs/src/data-structures/presentations/helpers.md @@ -0,0 +1,135 @@ +# Helper functions + +This page collects the free functions that operate on a +[`Presentation`](@ref Semigroups.Presentation). They mirror the +`libsemigroups::presentation::*` namespace and are organised into three +groups: validation, scalar queries, and shape / rule-set mutators. + +!!! warning "v1 limitation" + Semigroups.jl v1 binds helpers for `Presentation` only. + Alphabets and rules use `Vector{Int}` with 1-based letter indices. + +## Validation + +These functions throw `LibsemigroupsError` if the presentation is +ill-formed in some way; otherwise they return `nothing`. + +### Contents + +| Function | Description | +| -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| [`throw_if_alphabet_has_duplicates`](@ref Semigroups.throw_if_alphabet_has_duplicates(::Presentation)) | Throw if the alphabet contains a repeated letter. | +| [`throw_if_letter_not_in_alphabet`](@ref Semigroups.throw_if_letter_not_in_alphabet(::Presentation, ::Integer)) | Throw if the given letter is not in the alphabet. | +| [`throw_if_bad_rules`](@ref Semigroups.throw_if_bad_rules(::Presentation)) | Throw if rules refer to letters outside the alphabet or have odd count. | +| [`throw_if_bad_alphabet_or_rules`](@ref Semigroups.throw_if_bad_alphabet_or_rules(::Presentation)) | Combined alphabet-and-rules check. | +| [`throw_if_odd_number_of_rules`](@ref Semigroups.throw_if_odd_number_of_rules(::Presentation)) | Throw if the underlying rule-word count is odd. | +| [`throw_if_bad_inverses`](@ref Semigroups.throw_if_bad_inverses(::Presentation, ::AbstractVector{<:Integer})) | Throw if a proposed inverses vector is not a valid involution. | + +### Full API + +```@docs +Semigroups.throw_if_alphabet_has_duplicates(::Presentation) +Semigroups.throw_if_letter_not_in_alphabet(::Presentation, ::Integer) +Semigroups.throw_if_bad_rules(::Presentation) +Semigroups.throw_if_bad_alphabet_or_rules(::Presentation) +Semigroups.throw_if_odd_number_of_rules(::Presentation) +Semigroups.throw_if_bad_inverses(::Presentation, ::AbstractVector{<:Integer}) +``` + +## Queries + +Scalar-valued, non-mutating queries. + +### Contents + +| Function | Description | +| ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| [`length_of`](@ref Semigroups.length_of(::Presentation)) | Total length of all rule words. | +| [`longest_rule_length`](@ref Semigroups.longest_rule_length(::Presentation)) | Length of the longest single rule word. | +| [`shortest_rule_length`](@ref Semigroups.shortest_rule_length(::Presentation)) | Length of the shortest single rule word. | +| [`is_normalized`](@ref Semigroups.is_normalized(::Presentation)) | `true` iff the alphabet is `[1, ..., n]`. | +| [`are_rules_sorted`](@ref Semigroups.are_rules_sorted(::Presentation)) | `true` iff the rule list is sorted. | +| [`contains_rule`](@ref Semigroups.contains_rule(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer})) | Whether a given `lhs = rhs` appears as a rule. | +| [`is_rule`](@ref Semigroups.is_rule(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer})) | Validated-form test for `lhs = rhs`. | +| [`index_rule`](@ref Semigroups.index_rule(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer})) | 1-based rule-pair index of `lhs = rhs`, or [`UNDEFINED`](@ref Semigroups.UNDEFINED). | +| [`first_unused_letter`](@ref Semigroups.first_unused_letter(::Presentation)) | Smallest letter not in the alphabet. | +| [`longest_rule_index`](@ref Semigroups.longest_rule_index(::Presentation)) | 1-based index of the first rule of maximal length. | +| [`shortest_rule_index`](@ref Semigroups.shortest_rule_index(::Presentation)) | 1-based index of the first rule of minimal length. | + +### Full API + +```@docs +Semigroups.length_of(::Presentation) +Semigroups.longest_rule_length(::Presentation) +Semigroups.shortest_rule_length(::Presentation) +Semigroups.is_normalized(::Presentation) +Semigroups.are_rules_sorted(::Presentation) +Semigroups.contains_rule(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer}) +Semigroups.is_rule(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer}) +Semigroups.index_rule(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer}) +Semigroups.first_unused_letter(::Presentation) +Semigroups.longest_rule_index(::Presentation) +Semigroups.shortest_rule_index(::Presentation) +``` + +## Mutators + +Shape mutators reorder or rewrite the alphabet / rule words; rule-set +mutators add or remove rules. + +### Shape mutators + +| Function | Description | +| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | +| [`normalize_alphabet!`](@ref Semigroups.normalize_alphabet!(::Presentation)) | Rewrite `p` so the alphabet is `[1, ..., n]`. | +| [`change_alphabet!`](@ref Semigroups.change_alphabet!(::Presentation, ::AbstractVector{<:Integer})) | Rewrite under `old[i] -> new[i]`. | +| [`Base.reverse!`](@ref Base.reverse!(::Presentation)) | Reverse each rule word in place (extends `Base.reverse!`; not exported). | +| [`sort_rules!`](@ref Semigroups.sort_rules!(::Presentation)) | Sort the rule list. | +| [`sort_each_rule!`](@ref Semigroups.sort_each_rule!(::Presentation)) | Order each rule so that `lhs >= rhs`. | + +### Rule-set mutators + +| Function | Description | +| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | +| [`add_identity_rules!`](@ref Semigroups.add_identity_rules!(::Presentation, ::Integer)) | Add ``e \cdot a = a \cdot e = a`` for every letter ``a``. | +| [`add_zero_rules!`](@ref Semigroups.add_zero_rules!(::Presentation, ::Integer)) | Add ``z \cdot a = a \cdot z = z`` for every letter ``a``. | +| [`add_rules!`](@ref Semigroups.add_rules!(::Presentation, ::Presentation)) | Copy every rule of `q` into `p`. | +| [`add_inverse_rules!`](@ref Semigroups.add_inverse_rules!(::Presentation, ::AbstractVector{<:Integer})) | Add rules ``a_i \cdot b_i = e`` for a matching list of inverses. | +| [`replace_subword!`](@ref Semigroups.replace_subword!(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer})) | Replace every non-overlapping occurrence of a subword. | +| [`replace_word!`](@ref Semigroups.replace_word!(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer})) | Replace a word wherever it appears as a full side of a rule. | +| [`replace_word_with_new_generator!`](@ref Semigroups.replace_word_with_new_generator!(::Presentation, ::AbstractVector{<:Integer})) | Replace non-overlapping occurrences with a fresh generator. | +| [`remove_duplicate_rules!`](@ref Semigroups.remove_duplicate_rules!(::Presentation)) | Drop duplicate rules, keeping the first occurrence. | +| [`remove_trivial_rules!`](@ref Semigroups.remove_trivial_rules!(::Presentation)) | Drop rules of the form `u = u`. | + +### Full API + +```@docs +Semigroups.normalize_alphabet!(::Presentation) +Semigroups.change_alphabet!(::Presentation, ::AbstractVector{<:Integer}) +Base.reverse!(::Presentation) +Semigroups.sort_rules!(::Presentation) +Semigroups.sort_each_rule!(::Presentation) +Semigroups.add_identity_rules!(::Presentation, ::Integer) +Semigroups.add_zero_rules!(::Presentation, ::Integer) +Semigroups.add_rules!(::Presentation, ::Presentation) +Semigroups.add_inverse_rules!(::Presentation, ::AbstractVector{<:Integer}) +Semigroups.replace_subword!(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer}) +Semigroups.replace_word!(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer}) +Semigroups.replace_word_with_new_generator!(::Presentation, ::AbstractVector{<:Integer}) +Semigroups.remove_duplicate_rules!(::Presentation) +Semigroups.remove_trivial_rules!(::Presentation) +``` + +## Export + +Helpers that serialize a presentation to another language's source code. + +| Function | Description | +| ----------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| [`to_gap_string`](@ref Semigroups.to_gap_string(::Presentation, ::AbstractString)) | GAP source code that would reconstruct the presentation. | + +### Full API + +```@docs +Semigroups.to_gap_string(::Presentation, ::AbstractString) +``` diff --git a/docs/src/data-structures/presentations/index.md b/docs/src/data-structures/presentations/index.md new file mode 100644 index 0000000..0bdeebc --- /dev/null +++ b/docs/src/data-structures/presentations/index.md @@ -0,0 +1,20 @@ +# Presentations + +This section documents the [`Presentation`](@ref Semigroups.Presentation) and +[`InversePresentation`](@ref Semigroups.InversePresentation) types, the +namespace of helper free functions that operate on them, and the catalog of +standard example presentations provided by +`libsemigroups::presentation::examples`. + +!!! warning "v1 limitation" + Semigroups.jl v1 binds `Presentation` only. Alphabets and + rules use `Vector{Int}` with 1-based letter indices. + +## Contents + +| Page | Description | +| ---------------------------------------------- | -------------------------------------------------------------- | +| [Presentation](presentation.md) | The main `Presentation{word_type}` type. | +| [InversePresentation](inverse-presentation.md) | `InversePresentation{word_type}` with per-generator inverses. | +| [Helper functions](helpers.md) | Free functions in `presentation::*`. | +| [Examples](examples.md) | Standard presentations (symmetric group, partition monoid, ...). | diff --git a/docs/src/data-structures/presentations/inverse-presentation.md b/docs/src/data-structures/presentations/inverse-presentation.md new file mode 100644 index 0000000..9f0c0c9 --- /dev/null +++ b/docs/src/data-structures/presentations/inverse-presentation.md @@ -0,0 +1,32 @@ +# The InversePresentation type + +This page documents the type +[`InversePresentation`](@ref Semigroups.InversePresentation), a +[`Presentation`](@ref Semigroups.Presentation) equipped with per-generator +inverses. + +!!! warning "v1 limitation" + Semigroups.jl v1 binds `InversePresentation` only. Alphabets, + rules, and inverses use `Vector{Int}` with 1-based letter indices. + +```@docs +Semigroups.InversePresentation +``` + +## Contents + +| Function | Description | +| -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | +| [`set_inverses!`](@ref Semigroups.set_inverses!(::InversePresentation, ::AbstractVector{<:Integer})) | Set the vector of inverses, one per generator. | +| [`inverses`](@ref Semigroups.inverses(::InversePresentation)) | Return the inverses as a `Vector{Int}`. | +| [`inverse_of`](@ref Semigroups.inverse_of(::InversePresentation, ::Integer)) | Return the inverse of a given letter. | +| [`throw_if_bad_alphabet_rules_or_inverses`](@ref Semigroups.throw_if_bad_alphabet_rules_or_inverses(::InversePresentation)) | Validate the alphabet, rules, and inverses jointly. | + +## Full API + +```@docs +Semigroups.set_inverses!(::InversePresentation, ::AbstractVector{<:Integer}) +Semigroups.inverses(::InversePresentation) +Semigroups.inverse_of(::InversePresentation, ::Integer) +Semigroups.throw_if_bad_alphabet_rules_or_inverses(::InversePresentation) +``` diff --git a/docs/src/data-structures/presentations/presentation.md b/docs/src/data-structures/presentations/presentation.md new file mode 100644 index 0000000..f3915e3 --- /dev/null +++ b/docs/src/data-structures/presentations/presentation.md @@ -0,0 +1,74 @@ +# The Presentation type + +This page documents the type [`Presentation`](@ref Semigroups.Presentation), +a finite semigroup or monoid presentation over `word_type` (Julia: +`Vector{Int}` with 1-based letter indices). + +!!! warning "v1 limitation" + Semigroups.jl v1 binds `Presentation` only. Alphabets and + rules use `Vector{Int}` with 1-based letter indices. + +```@docs +Semigroups.Presentation +``` + +## Contents + +| Function | Description | +| -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | +| [`init!`](@ref Semigroups.init!(::Presentation)) | Reset a presentation to the empty state. | +| [`alphabet`](@ref Semigroups.alphabet(::Presentation)) | Return the alphabet as a `Vector{Int}`. | +| [`set_alphabet!`](@ref Semigroups.set_alphabet!(::Presentation, ::Integer)) | Set the alphabet to `[1, ..., n]`. | +| [`set_alphabet!`](@ref Semigroups.set_alphabet!(::Presentation, ::AbstractVector{<:Integer})) | Set the alphabet to the given vector. | +| [`alphabet_from_rules!`](@ref Semigroups.alphabet_from_rules!(::Presentation)) | Infer the alphabet from the rules. | +| [`letter`](@ref Semigroups.letter(::Presentation, ::Integer)) | Return the `i`-th letter of the alphabet. | +| [`index_of`](@ref Semigroups.index_of(::Presentation, ::Integer)) | Return the 1-based index of a letter. | +| [`in_alphabet`](@ref Semigroups.in_alphabet(::Presentation, ::Integer)) | Test membership in the alphabet. | +| [`contains_empty_word`](@ref Semigroups.contains_empty_word(::Presentation)) | Query whether the empty word is allowed. | +| [`set_contains_empty_word!`](@ref Semigroups.set_contains_empty_word!(::Presentation, ::Bool)) | Set whether the empty word is allowed. | +| [`add_generator!`](@ref Semigroups.add_generator!(::Presentation)) | Append a generator (no-arg or by letter). | +| [`remove_generator!`](@ref Semigroups.remove_generator!(::Presentation, ::Integer)) | Remove a letter from the alphabet. | +| [`number_of_rules`](@ref Semigroups.number_of_rules(::Presentation)) | Number of rules in the presentation. | +| [`rule_lhs`](@ref Semigroups.rule_lhs(::Presentation, ::Integer)) | Left-hand side of the `i`-th rule. | +| [`rule_rhs`](@ref Semigroups.rule_rhs(::Presentation, ::Integer)) | Right-hand side of the `i`-th rule. | +| [`rule`](@ref Semigroups.rule(::Presentation, ::Integer)) | `(lhs, rhs)` tuple for the `i`-th rule. | +| [`rules`](@ref Semigroups.rules(::Presentation)) | All rules as `(lhs, rhs)` tuples. | +| [`clear_rules!`](@ref Semigroups.clear_rules!(::Presentation)) | Remove every rule. | +| [`add_rule!`](@ref Semigroups.add_rule!(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer})) | Append a checked rule. | +| [`add_rule_no_checks!`](@ref Semigroups.add_rule_no_checks!(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer})) | Append a rule without checks. | + +## Full API + +```@docs +Semigroups.init!(::Presentation) +Semigroups.alphabet(::Presentation) +Semigroups.set_alphabet!(::Presentation, ::Integer) +Semigroups.set_alphabet!(::Presentation, ::AbstractVector{<:Integer}) +Semigroups.alphabet_from_rules!(::Presentation) +Semigroups.letter(::Presentation, ::Integer) +Semigroups.index_of(::Presentation, ::Integer) +Semigroups.in_alphabet(::Presentation, ::Integer) +Semigroups.contains_empty_word(::Presentation) +Semigroups.set_contains_empty_word!(::Presentation, ::Bool) +Semigroups.add_generator!(::Presentation) +Semigroups.remove_generator!(::Presentation, ::Integer) +Semigroups.number_of_rules(::Presentation) +Semigroups.rule_lhs(::Presentation, ::Integer) +Semigroups.rule_rhs(::Presentation, ::Integer) +Semigroups.rule(::Presentation, ::Integer) +Semigroups.rules(::Presentation) +Semigroups.clear_rules!(::Presentation) +Semigroups.add_rule!(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer}) +Semigroups.add_rule_no_checks!(::Presentation, ::AbstractVector{<:Integer}, ::AbstractVector{<:Integer}) +``` + +## `Base` interface + +A [`Presentation`](@ref Semigroups.Presentation) extends the standard +value-semantics predicates from `Base`, making it usable as a dictionary +key and interoperable with generic Julia code. + +```@docs +Base.isempty(::Presentation) +Base.hash(::Presentation, ::UInt) +``` diff --git a/docs/src/data-structures/word-graph.md b/docs/src/data-structures/word-graph.md index a2e8dca..bda3ba4 100644 --- a/docs/src/data-structures/word-graph.md +++ b/docs/src/data-structures/word-graph.md @@ -4,6 +4,10 @@ This page contains the documentation of the type [`WordGraph`](@ref Semigroups.WordGraph), a representation of a word graph over an alphabet of fixed out-degree. +!!! warning "Minimal implementation" + Semigroups.jl v1 currently contains a minimal implementation of WordGraph + in order to support algorithm implementations. + ```@docs Semigroups.WordGraph ``` diff --git a/src/Semigroups.jl b/src/Semigroups.jl index 6788c47..f69ebba 100644 --- a/src/Semigroups.jl +++ b/src/Semigroups.jl @@ -67,6 +67,8 @@ include("runner.jl") include("order.jl") include("word-range.jl") include("word-graph.jl") +include("presentation.jl") +include("presentation-examples.jl") # High-level element types include("bmat8.jl") @@ -111,6 +113,47 @@ export next!, at_end, valid, init!, size_hint, upper_bound # WordGraph export WordGraph, number_of_nodes, out_degree, target, target!, add_nodes! +# Presentation +export Presentation, alphabet, set_alphabet!, alphabet_from_rules! +export letter, index_of, in_alphabet +export contains_empty_word, set_contains_empty_word! +export add_generator!, remove_generator! +export add_rule!, add_rule_no_checks!, add_rules! +export number_of_rules, rule, rule_lhs, rule_rhs, rules, clear_rules! +export throw_if_alphabet_has_duplicates, throw_if_letter_not_in_alphabet +export throw_if_bad_rules, throw_if_bad_alphabet_or_rules +export length_of, longest_rule_length, shortest_rule_length +export longest_rule_index, shortest_rule_index +export first_unused_letter, index_rule, is_rule +export is_normalized, are_rules_sorted, contains_rule +export throw_if_odd_number_of_rules, throw_if_bad_inverses +export normalize_alphabet!, change_alphabet!, sort_rules!, sort_each_rule! +export add_identity_rules!, add_zero_rules!, add_inverse_rules! +export replace_subword!, replace_word!, replace_word_with_new_generator! +export remove_duplicate_rules!, remove_trivial_rules! +export to_gap_string +export InversePresentation, set_inverses!, inverses, inverse_of +export throw_if_bad_alphabet_rules_or_inverses + +# presentation::examples +export symmetric_group +export alternating_group, braid_group, not_symmetric_group +export full_transformation_monoid, partial_transformation_monoid +export symmetric_inverse_monoid, cyclic_inverse_monoid +export order_preserving_monoid, order_preserving_cyclic_inverse_monoid +export orientation_preserving_monoid, orientation_preserving_reversing_monoid +export partition_monoid, partial_brauer_monoid, brauer_monoid +export singular_brauer_monoid, temperley_lieb_monoid, motzkin_monoid +export partial_isometries_cycle_graph_monoid, uniform_block_bijection_monoid +export dual_symmetric_inverse_monoid, stellar_monoid, zero_rook_monoid +export abacus_jones_monoid +export plactic_monoid, chinese_monoid, hypo_plactic_monoid, stylic_monoid +export special_linear_group_2 +export fibonacci_semigroup, monogenic_semigroup, rectangular_band +export sigma_plactic_monoid +export renner_type_B_monoid, renner_type_D_monoid +export not_renner_type_B_monoid, not_renner_type_D_monoid + # Transformation types and functions export Transf, PPerm, Perm export degree, rank, image, domain, inverse diff --git a/src/bmat8.jl b/src/bmat8.jl index f671e60..25ce908 100644 --- a/src/bmat8.jl +++ b/src/bmat8.jl @@ -171,7 +171,7 @@ Base.transpose(x::BMat8)::BMat8 = LibSemigroups.bmat8_transpose(x) # Semigroups.jl functions ######################################################################## -# TODO document the 0-arg constructor +# TODO document the 0-arg constructor """ BMat8(rows::Vector{Vector{T}})::BMat8 where {T<:Union{Bool,Int64}} -> BMat8 diff --git a/src/constants.jl b/src/constants.jl index 1cccb6b..ad3fd5a 100644 --- a/src/constants.jl +++ b/src/constants.jl @@ -72,7 +72,7 @@ const LIMIT_MAX = LimitMaxType() # Conversion functions to get the underlying integer values -# UNDEFINED conversions — libsemigroups uses typemax(T) natively +# UNDEFINED conversions - libsemigroups uses typemax(T) natively Base.convert(::Type{T}, ::UndefinedType) where {T<:Integer} = typemax(T) # POSITIVE_INFINITY conversions @@ -105,7 +105,7 @@ Base.convert(::Type{Int64}, ::LimitMaxType) = LibSemigroups.LIMIT_MAX_Int64() # Comparison operations -# UNDEFINED comparisons — UNDEFINED is a distinct sentinel, never equal to any integer +# UNDEFINED comparisons - UNDEFINED is a distinct sentinel, never equal to any integer Base.:(==)(::Integer, ::UndefinedType) = false Base.:(==)(::UndefinedType, ::Integer) = false Base.:(==)(::UndefinedType, ::UndefinedType) = true diff --git a/src/order.jl b/src/order.jl index 5662bb6..bbb291a 100644 --- a/src/order.jl +++ b/src/order.jl @@ -5,7 +5,7 @@ """ order.jl - Order enum and compare helpers -Exposes `Order` (an enum selecting a word ordering — shortlex, lex, recursive- +Exposes `Order` (an enum selecting a word ordering - shortlex, lex, recursive- path) along with comparator free functions instantiated for Julia `Vector{Int}` words. The Julia public API uses 1-based letter indices; the private `_word_to_cpp` helper converts to 0-based at the C++ boundary. @@ -25,10 +25,10 @@ The values of this enum can be passed as the argument to functions such as for congruence classes are given with respect to one of these orders. Values: -- [`ORDER_NONE`](@ref) — no ordering -- [`ORDER_SHORTLEX`](@ref) — short-lex: order by length, then lexicographically -- [`ORDER_LEX`](@ref) — pure lexicographic (not a well-order in general) -- [`ORDER_RECURSIVE`](@ref) — recursive-path ordering (Jantzen 2012, +- [`ORDER_NONE`](@ref) - no ordering +- [`ORDER_SHORTLEX`](@ref) - short-lex: order by length, then lexicographically +- [`ORDER_LEX`](@ref) - pure lexicographic (not a well-order in general) +- [`ORDER_RECURSIVE`](@ref) - recursive-path ordering (Jantzen 2012, Definition 1.2.14, page 24) """ const Order = LibSemigroups.Order @@ -71,7 +71,7 @@ const ORDER_RECURSIVE = LibSemigroups.order_recursive # ---------------------------------------------------------------------------- # Julia uses 1-based letter indices; libsemigroups stores 0-based in # `word_type = std::vector` (UInt64 on 64-bit systems). Conversion -# is applied at the C++ boundary — these helpers are the only place the +# is applied at the C++ boundary - these helpers are the only place the # shift happens. # ============================================================================ diff --git a/src/presentation-examples.jl b/src/presentation-examples.jl new file mode 100644 index 0000000..0d22ca9 --- /dev/null +++ b/src/presentation-examples.jl @@ -0,0 +1,810 @@ +# Copyright (c) 2026, James W. Swent +# +# Distributed under the terms of the GPL license version 3. + +""" +presentation-examples.jl - Julia wrappers for `libsemigroups::presentation::examples` + +Each binding returns a fresh [`Presentation`](@ref Semigroups.Presentation) +over `word_type`, exposed in Julia as `Vector{Int}` words with 1-based +letter indices. These wrappers mirror the default +`libsemigroups::presentation::examples` constructors. + +!!! note "Argument conversion" + Most integer parameters are converted to the corresponding C++ + `size_t` values with `UInt(...)` before the libsemigroups call, and + the Renner-family `q` parameters are converted with `Int32(...)`. + Negative or otherwise unrepresentable inputs therefore raise + `InexactError` in Julia before `libsemigroups` can throw + `LibsemigroupsError`. + +!!! warning "v1 limitation" + v1 of Semigroups.jl binds `Presentation` only. Alphabets and + rules use `Vector{Int}` with 1-based letter indices. +""" + +""" + symmetric_group(n::Integer) -> Presentation + +A presentation for the symmetric group of degree `n`. + +This function returns the default libsemigroups presentation of the +symmetric group of degree `n`. + +# Arguments +- `n::Integer`: the degree of the symmetric group. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 2`. +""" +function symmetric_group(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_symmetric_group(m) +end + +""" + alternating_group(n::Integer) -> Presentation + +A presentation for the alternating group of degree `n`. + +This function returns a monoid presentation defining the alternating +group of degree `n`. + +# Arguments +- `n::Integer`: the degree of the alternating group. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 4`. +""" +function alternating_group(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_alternating_group(m) +end + +""" + braid_group(n::Integer) -> Presentation + +A presentation for the braid group with `n - 1` generators. + +This function returns a monoid presentation defining the braid group with +`n - 1` generators. + +# Arguments +- `n::Integer`: the degree, or equivalently the number of generators plus `1`. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 3`. +""" +function braid_group(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_braid_group(m) +end + +""" + not_symmetric_group(n::Integer) -> Presentation + +A presentation that is not a symmetric group but has the same number of +generators and relations as the symmetric group of degree `n`. + +This function returns a monoid presentation which is claimed to define +the symmetric group of degree `n`, but does not. + +# Arguments +- `n::Integer`: the claimed degree of the symmetric group. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 4`. +""" +function not_symmetric_group(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_not_symmetric_group(m) +end + +""" + full_transformation_monoid(n::Integer) -> Presentation + +A presentation for the full transformation monoid of degree `n`. + +This function returns the default libsemigroups presentation of the full +transformation monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree of the full transformation monoid. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 2`. +""" +function full_transformation_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_full_transformation_monoid(m) +end + +""" + partial_transformation_monoid(n::Integer) -> Presentation + +A presentation for the partial transformation monoid of degree `n`. + +This function returns the default libsemigroups presentation of the +partial transformation monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree of the partial transformation monoid. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 2`. +""" +function partial_transformation_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_partial_transformation_monoid(m) +end + +""" + symmetric_inverse_monoid(n::Integer) -> Presentation + +A presentation for the symmetric inverse monoid of degree `n`. + +This function returns the default libsemigroups presentation of the +symmetric inverse monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree of the symmetric inverse monoid. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 4`. +""" +function symmetric_inverse_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_symmetric_inverse_monoid(m) +end + +""" + cyclic_inverse_monoid(n::Integer) -> Presentation + +A presentation for the cyclic inverse monoid of degree `n`. + +This function returns the default libsemigroups presentation of the +cyclic inverse monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree of the cyclic inverse monoid. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 3`. +""" +function cyclic_inverse_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_cyclic_inverse_monoid(m) +end + +""" + order_preserving_monoid(n::Integer) -> Presentation + +A presentation for the order-preserving transformation monoid of degree `n`. + +This function returns a presentation for the monoid of order-preserving +transformations of degree `n`. + +# Arguments +- `n::Integer`: the degree. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 3`. +""" +function order_preserving_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_order_preserving_monoid(m) +end + +""" + order_preserving_cyclic_inverse_monoid(n::Integer) -> Presentation + +A presentation for the order-preserving part of the cyclic inverse monoid of +degree `n`. + +This function returns a presentation for the order-preserving part of the +cyclic inverse monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 3`. +""" +function order_preserving_cyclic_inverse_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_order_preserving_cyclic_inverse_monoid(m) +end + +""" + orientation_preserving_monoid(n::Integer) -> Presentation + +A presentation for the orientation-preserving transformation monoid of degree +`n`. + +This function returns a presentation for the orientation-preserving +transformation monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 3`. +""" +function orientation_preserving_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_orientation_preserving_monoid(m) +end + +""" + orientation_preserving_reversing_monoid(n::Integer) -> Presentation + +A presentation for the orientation-preserving and -reversing transformation +monoid of degree `n`. + +This function returns a presentation for the orientation-preserving and +orientation-reversing transformation monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 3`. +""" +function orientation_preserving_reversing_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_orientation_preserving_reversing_monoid( + m, + ) +end + +""" + partition_monoid(n::Integer) -> Presentation + +A presentation for the partition monoid of degree `n`. + +This function returns the default libsemigroups presentation of the +partition monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree of the partition monoid. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 4`. +""" +function partition_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_partition_monoid(m) +end + +""" + partial_brauer_monoid(n::Integer) -> Presentation + +A presentation for the partial Brauer monoid of degree `n`. + +This function returns the default libsemigroups presentation of the +partial Brauer monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree of the partial Brauer monoid. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 1`. +""" +function partial_brauer_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_partial_brauer_monoid(m) +end + +""" + brauer_monoid(n::Integer) -> Presentation + +A presentation for the Brauer monoid of degree `n`. + +This function returns the default libsemigroups presentation of the +Brauer monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree of the Brauer monoid. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 1`. +""" +function brauer_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_brauer_monoid(m) +end + +""" + singular_brauer_monoid(n::Integer) -> Presentation + +A presentation for the singular part of the Brauer monoid of degree `n`. + +This function returns a monoid presentation for the singular part of the +Brauer monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 3`. +""" +function singular_brauer_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_singular_brauer_monoid(m) +end + +""" + temperley_lieb_monoid(n::Integer) -> Presentation + +A presentation for the Temperley-Lieb monoid with `n - 1` generators. + +This function returns a monoid presentation defining the Temperley-Lieb +monoid with `n - 1` generators. + +# Arguments +- `n::Integer`: the degree, or equivalently the number of generators plus `1`. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 3`. +""" +function temperley_lieb_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_temperley_lieb_monoid(m) +end + +""" + motzkin_monoid(n::Integer) -> Presentation + +A presentation for the Motzkin monoid of degree `n`. + +This function returns a monoid presentation defining the Motzkin monoid of +degree `n`. + +# Arguments +- `n::Integer`: the degree. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 1`. +""" +function motzkin_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_motzkin_monoid(m) +end + +""" + partial_isometries_cycle_graph_monoid(n::Integer) -> Presentation + +A presentation for the monoid of partial isometries of an `n`-cycle graph. + +This function returns the default libsemigroups presentation of the +monoid of partial isometries of an `n`-cycle graph. + +# Arguments +- `n::Integer`: the degree of the cycle graph. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 3`. +""" +function partial_isometries_cycle_graph_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_partial_isometries_cycle_graph_monoid(m) +end + +""" + uniform_block_bijection_monoid(n::Integer) -> Presentation + +A presentation for the uniform block bijection monoid of degree `n`. + +This function returns the default libsemigroups presentation of the +uniform block bijection monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree of the monoid. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 3`. +""" +function uniform_block_bijection_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_uniform_block_bijection_monoid(m) +end + +""" + dual_symmetric_inverse_monoid(n::Integer) -> Presentation + +A presentation for the dual symmetric inverse monoid of degree `n`. + +This function returns the default libsemigroups presentation of the +dual symmetric inverse monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree of the monoid. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 3`. +""" +function dual_symmetric_inverse_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_dual_symmetric_inverse_monoid(m) +end + +""" + stellar_monoid(l::Integer) -> Presentation + +A presentation for the stellar monoid with `l` generators. + +This function returns a monoid presentation defining the stellar monoid +with `l` generators. + +# Arguments +- `l::Integer`: the number of generators. + +# Throws +- `InexactError`: if `l` is negative. +- `LibsemigroupsError`: if `l < 2`. +""" +function stellar_monoid(l::Integer) + ll = UInt(l) + @wrap_libsemigroups_call LibSemigroups.example_stellar_monoid(ll) +end + +""" + zero_rook_monoid(n::Integer) -> Presentation + +A presentation for the 0-rook monoid of degree `n`. + +This function returns the default libsemigroups presentation of the +0-rook monoid of degree `n`. + +# Arguments +- `n::Integer`: the degree of the monoid. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 2`. +""" +function zero_rook_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_zero_rook_monoid(m) +end + +""" + abacus_jones_monoid(n::Integer, d::Integer) -> Presentation + +A presentation for the abacus Jones monoid of degree `n` with at most `d-1` +beads per arc. + +This function returns a monoid presentation defining the abacus Jones +monoid of degree `n`, where each arc carries at most `d - 1` beads. + +# Arguments +- `n::Integer`: the degree. +- `d::Integer`: one more than the maximum number of beads on each arc. + +# Throws +- `InexactError`: if `n` or `d` is negative. +- `LibsemigroupsError`: if `n < 3`. +- `LibsemigroupsError`: if `d == 0`. +""" +function abacus_jones_monoid(n::Integer, d::Integer) + nn = UInt(n) + dd = UInt(d) + @wrap_libsemigroups_call LibSemigroups.example_abacus_jones_monoid(nn, dd) +end + +# ---- batch C: plactic / misc ---- + +""" + plactic_monoid(n::Integer) -> Presentation + +A presentation for the plactic monoid with `n` generators. + +This function returns a monoid presentation defining the plactic monoid +with `n` generators. + +# Arguments +- `n::Integer`: the number of generators. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 1`. +""" +function plactic_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_plactic_monoid(m) +end + +""" + chinese_monoid(n::Integer) -> Presentation + +A presentation for the Chinese monoid with `n` generators. + +This function returns a monoid presentation defining the Chinese monoid +with `n` generators. + +# Arguments +- `n::Integer`: the number of generators. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 2`. +""" +function chinese_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_chinese_monoid(m) +end + +""" + hypo_plactic_monoid(n::Integer) -> Presentation + +A presentation for the hypo-plactic monoid with `n` generators. + +This function returns a presentation for the hypo-plactic monoid with `n` +generators. It is a quotient monoid of the plactic monoid. + +# Arguments +- `n::Integer`: the number of generators. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 1`. +""" +function hypo_plactic_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_hypo_plactic_monoid(m) +end + +""" + stylic_monoid(n::Integer) -> Presentation + +A presentation for the stylic monoid with `n` generators. + +This function returns a monoid presentation defining the stylic monoid +with `n` generators. + +# Arguments +- `n::Integer`: the number of generators. + +# Throws +- `InexactError`: if `n` is negative. +- `LibsemigroupsError`: if `n < 2`. +""" +function stylic_monoid(n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_stylic_monoid(m) +end + +""" + special_linear_group_2(q::Integer) -> Presentation + +A presentation for the special linear group SL(2, q). + +This function returns a presentation for the special linear group +`SL(2, q)`, where `q` should be an odd prime for the returned +presentation to define the claimed group. + +# Arguments +- `q::Integer`: the order of the finite field. + +# Throws +- `InexactError`: if `q` is negative. +- `LibsemigroupsError`: if `q < 3`. +""" +function special_linear_group_2(q::Integer) + qq = UInt(q) + @wrap_libsemigroups_call LibSemigroups.example_special_linear_group_2(qq) +end + +""" + fibonacci_semigroup(r::Integer, n::Integer) -> Presentation + +A presentation for the Fibonacci semigroup F(r, n). + +This function returns a semigroup presentation defining the Fibonacci +semigroup `F(r, n)`. + +# Arguments +- `r::Integer`: the length of the left-hand sides of the relations. +- `n::Integer`: the number of generators. + +# Throws +- `InexactError`: if `r` or `n` is negative. +- `LibsemigroupsError`: if `r < 1`. +- `LibsemigroupsError`: if `n < 1`. +""" +function fibonacci_semigroup(r::Integer, n::Integer) + rr = UInt(r) + nn = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_fibonacci_semigroup(rr, nn) +end + +""" + monogenic_semigroup(m::Integer, r::Integer) -> Presentation + +A presentation for the monogenic semigroup with index `m` and period `r`. + +This function returns the presentation ``. When +`m == 0` the result is a monoid presentation; otherwise it is a +semigroup presentation. + +# Arguments +- `m::Integer`: the index. +- `r::Integer`: the period. + +# Throws +- `InexactError`: if `m` or `r` is negative. +- `LibsemigroupsError`: if `r == 0`. +""" +function monogenic_semigroup(m::Integer, r::Integer) + mm = UInt(m) + rr = UInt(r) + @wrap_libsemigroups_call LibSemigroups.example_monogenic_semigroup(mm, rr) +end + +""" + rectangular_band(m::Integer, n::Integer) -> Presentation + +A presentation for the ``m \\times n`` rectangular band. + +This function returns a semigroup presentation defining the `m` by `n` +rectangular band. + +# Arguments +- `m::Integer`: the number of rows. +- `n::Integer`: the number of columns. + +# Throws +- `InexactError`: if `m` or `n` is negative. +- `LibsemigroupsError`: if `m == 0`. +- `LibsemigroupsError`: if `n == 0`. +""" +function rectangular_band(m::Integer, n::Integer) + mm = UInt(m) + nn = UInt(n) + @wrap_libsemigroups_call LibSemigroups.example_rectangular_band(mm, nn) +end + +""" + sigma_plactic_monoid(sigma::AbstractVector{<:Integer}) -> Presentation + +Presentation for the ``\\sigma``-plactic monoid with coefficient sequence `sigma`. + +This function returns a presentation for the ``\\sigma``-plactic monoid. +The image of ``\\sigma`` is given by the values in `sigma`, and the +resulting monoid is the quotient of the plactic monoid by the least +congruence containing the relations ``a^{\\sigma(a)} = a``. + +!!! note "0-based `sigma`" + Unlike alphabet letters (which are 1-based in the Julia API), the + `sigma` argument here is a vector of libsemigroups-native 0-based + coefficient indices - pass it through verbatim. + +# Arguments +- `sigma::AbstractVector{<:Integer}`: the image of ``\\sigma`` in + libsemigroups' native 0-based indexing. + +# Throws +- `InexactError`: if any entry of `sigma` is negative. +- `LibsemigroupsError`: if `sigma` is empty. +""" +function sigma_plactic_monoid(sigma::AbstractVector{<:Integer}) + s = UInt[UInt(x) for x in sigma] + @wrap_libsemigroups_call LibSemigroups.example_sigma_plactic_monoid(s) +end + +""" + renner_type_B_monoid(l::Integer, q::Integer) -> Presentation + +A presentation for the Renner type B monoid of rank `l` over a field of size `q`. + +This function returns a presentation for the Renner monoid of type B with +rank `l` and Iwahori-Hecke deformation `q`. + +# Arguments +- `l::Integer`: the rank. +- `q::Integer`: the Iwahori-Hecke deformation. + +# Throws +- `InexactError`: if `l` is negative, or if `q` is not representable as + `Int32`. +- `LibsemigroupsError`: if `q` is neither `0` nor `1`. +""" +function renner_type_B_monoid(l::Integer, q::Integer) + ll = UInt(l) + qq = Int32(q) + @wrap_libsemigroups_call LibSemigroups.example_renner_type_B_monoid(ll, qq) +end + +""" + renner_type_D_monoid(l::Integer, q::Integer) -> Presentation + +A presentation for the Renner type D monoid of rank `l` over a field of size `q`. + +This function returns a presentation for the Renner monoid of type D with +rank `l` and Iwahori-Hecke deformation `q`. + +# Arguments +- `l::Integer`: the rank. +- `q::Integer`: the Iwahori-Hecke deformation. + +# Throws +- `InexactError`: if `l` is negative, or if `q` is not representable as + `Int32`. +- `LibsemigroupsError`: if `q` is neither `0` nor `1`. +""" +function renner_type_D_monoid(l::Integer, q::Integer) + ll = UInt(l) + qq = Int32(q) + @wrap_libsemigroups_call LibSemigroups.example_renner_type_D_monoid(ll, qq) +end + +""" + not_renner_type_B_monoid(l::Integer, q::Integer) -> Presentation + +A presentation (not Renner type B) related to the Renner type B monoid of +rank `l` over a field of size `q`. + +This function returns a presentation that incorrectly claims to define +the Renner monoid of type B with rank `l` and Iwahori-Hecke deformation +`q`. + +# Arguments +- `l::Integer`: the rank. +- `q::Integer`: the Iwahori-Hecke deformation. + +# Throws +- `InexactError`: if `l` is negative, or if `q` is not representable as + `Int32`. +- `LibsemigroupsError`: if `q` is neither `0` nor `1`. +""" +function not_renner_type_B_monoid(l::Integer, q::Integer) + ll = UInt(l) + qq = Int32(q) + @wrap_libsemigroups_call LibSemigroups.example_not_renner_type_B_monoid(ll, qq) +end + +""" + not_renner_type_D_monoid(l::Integer, q::Integer) -> Presentation + +A presentation (not Renner type D) related to the Renner type D monoid of +rank `l` over a field of size `q`. + +This function returns a presentation that incorrectly claims to define +the Renner monoid of type D with rank `l` and Iwahori-Hecke deformation +`q`. + +# Arguments +- `l::Integer`: the rank. +- `q::Integer`: the Iwahori-Hecke deformation. + +# Throws +- `InexactError`: if `l` is negative, or if `q` is not representable as + `Int32`. +- `LibsemigroupsError`: if `q` is neither `0` nor `1`. +""" +function not_renner_type_D_monoid(l::Integer, q::Integer) + ll = UInt(l) + qq = Int32(q) + @wrap_libsemigroups_call LibSemigroups.example_not_renner_type_D_monoid(ll, qq) +end diff --git a/src/presentation.jl b/src/presentation.jl new file mode 100644 index 0000000..432b817 --- /dev/null +++ b/src/presentation.jl @@ -0,0 +1,1279 @@ +# Copyright (c) 2026, James W. Swent +# +# Distributed under the terms of the GPL license version 3. + +""" +presentation.jl - Presentation + InversePresentation wrappers +""" + +""" + Presentation() -> Presentation + Presentation(other::Presentation) -> Presentation + +Type for semigroup or monoid presentations. + +This type provides a shallow wrapper around a vector of words (the *rules* +of the presentation), together with an *alphabet*. It is intended to be +used as the input to other algorithms in `libsemigroups` (such as +the Knuth-Bendix, Todd-Coxeter, and Kambites algorithms). + +In a valid presentation, rules only consist of letters from within the +alphabet; however, for performance reasons, it is possible to update both +the rules and the alphabet independently of each other. For this reason, +it is possible for the alphabet and the rules to become out of sync. +[`Presentation`](@ref Semigroups.Presentation) provides some checks that +the rules define a valid presentation, and some related helper functions +live as module-level functions in `Semigroups`. + +The zero-argument form constructs an empty presentation with no rules and +no alphabet; the one-argument form copies `other`. + +!!! warning "v1 limitation" + v1 of Semigroups.jl binds `Presentation` only. Alphabets + and rules are expressed as `Vector{Int}` with 1-based letter indices. +""" +const Presentation = LibSemigroups.Presentation + +""" + init!(p::Presentation) -> Presentation + +Remove the alphabet and all rules from `p`. + +This function clears the alphabet and all rules of `p`, putting it back +into the state it would be in if it was newly constructed. +""" +init!(p::Presentation) = (LibSemigroups.init!(p); p) + +""" + alphabet(p::Presentation) -> Vector{Int} + +Return the alphabet of `p`. + +Returns the alphabet of `p` as a `Vector{Int}` of 1-based letter indices. + +# Complexity +Constant. +""" +alphabet(p::Presentation) = _word_from_cpp(LibSemigroups.alphabet(p)) + +# Route `Base.deepcopy` through the C++ copy constructor. Default +# deepcopy_internal for CxxWrap-wrapped types may shallow-copy the handle. +Base.deepcopy_internal(p::Presentation, ::IdDict) = Presentation(p) + +""" + set_alphabet!(p::Presentation, n::Integer) -> Presentation + +Set the alphabet of `p` by size. + +Sets the alphabet of `p` to be the first `n` positive integers +`[1, 2, ..., n]`. + +# Arguments +- `p::Presentation`: the presentation to modify. +- `n::Integer`: the size of the alphabet. + +# Throws +- `LibsemigroupsError`: if `n` is greater than the maximum number of + letters supported. + +# Warning +This function does not verify that the rules in `p` (if any) consist of +letters belonging to the new alphabet. + +# See also +- [`throw_if_alphabet_has_duplicates`](@ref) +- [`throw_if_bad_rules`](@ref) +- [`throw_if_bad_alphabet_or_rules`](@ref) +""" +function set_alphabet!(p::Presentation, n::Integer) + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.set_alphabet_size!(p, m) + return p +end + +""" + set_alphabet!(p::Presentation, a::AbstractVector{<:Integer}) -> Presentation + +Set the alphabet of `p` to the letters in `a`. + +Sets the alphabet of `p` to be the (1-based) letters in `a`. + +# Arguments +- `p::Presentation`: the presentation to modify. +- `a::AbstractVector{<:Integer}`: the alphabet. + +# Throws +- `LibsemigroupsError`: if `a` contains duplicate letters. + +# Warning +This function does not verify that the rules in `p` (if any) consist of +letters belonging to the new alphabet. + +# See also +- [`throw_if_bad_rules`](@ref) +- [`throw_if_bad_alphabet_or_rules`](@ref) +""" +function set_alphabet!(p::Presentation, a::AbstractVector{<:Integer}) + w = _word_to_cpp(a) + @wrap_libsemigroups_call LibSemigroups.set_alphabet!(p, w) + return p +end + +""" + alphabet_from_rules!(p::Presentation) -> Presentation + +Set the alphabet of `p` to be the letters that appear in its rules. + +# Complexity +At most ``O(mn)`` where ``m`` is the number of rules and ``n`` is the +length of the longest rule. + +# See also +- [`throw_if_bad_rules`](@ref) +- [`throw_if_bad_alphabet_or_rules`](@ref) +""" +alphabet_from_rules!(p::Presentation) = (LibSemigroups.alphabet_from_rules!(p); p) + +""" + letter(p::Presentation, i::Integer) -> Int + +Return a letter in the alphabet of `p` by index. + +Returns the letter of the alphabet in position `i` (1-based). + +# Arguments +- `p::Presentation`: the presentation. +- `i::Integer`: the 1-based index of the letter to return. + +# Throws +- `LibsemigroupsError`: if `i` is not in the range ``[1, n]``, where + ``n`` is the length of the alphabet of `p`. +""" +function letter(p::Presentation, i::Integer) + idx = UInt(i - 1) + _letter_from_cpp(@wrap_libsemigroups_call LibSemigroups.letter(p, idx)) +end + +""" + index_of(p::Presentation, x::Integer) -> Int + +Return the index of a letter in the alphabet of `p`. + +Returns the 1-based position of the letter `x` in the alphabet of `p`. + +# Arguments +- `p::Presentation`: the presentation. +- `x::Integer`: the letter whose index is sought. + +# Throws +- `LibsemigroupsError`: if `x` does not belong to the alphabet of `p`. + +# Complexity +Constant. + +# Note +This function mirrors `Presentation::index` in libsemigroups, renamed to +`index_of` to avoid clashing with `Base.index`-style conventions in Julia. +""" +function index_of(p::Presentation, x::Integer) + y = _letter_to_cpp(x) + Int(@wrap_libsemigroups_call LibSemigroups.index_of(p, y)) + 1 +end + +""" + in_alphabet(p::Presentation, x::Integer) -> Bool + +Check if a letter belongs to the alphabet of `p`. + +# Arguments +- `p::Presentation`: the presentation. +- `x::Integer`: the letter to check. + +# Complexity +Constant on average, worst case linear in the size of the alphabet. +""" +in_alphabet(p::Presentation, x::Integer) = LibSemigroups.in_alphabet(p, _letter_to_cpp(x)) + +""" + contains_empty_word(p::Presentation) -> Bool + +Return whether the empty word is a valid relation word in `p`. + +Returns `true` if the empty word is a valid relation word in `p`, and +`false` otherwise. + +If `p` is not allowed to contain the empty word (according to this +function), then `p` may still be isomorphic to a monoid, but is not given +as a quotient of a free monoid. + +# Complexity +Constant. +""" +contains_empty_word(p::Presentation) = LibSemigroups.contains_empty_word(p) + +""" + set_contains_empty_word!(p::Presentation, val::Bool) -> Presentation + +Set whether the empty word is a valid relation word in `p`. + +Specify whether the empty word should be a valid relation word +(corresponding to `val` being `true`), or not (corresponding to `val` +being `false`). + +If `p` is not allowed to contain the empty word (according to the value +specified here), then `p` may still be isomorphic to a monoid, but is not +given as a quotient of a free monoid. + +# Arguments +- `p::Presentation`: the presentation to modify. +- `val::Bool`: whether `p` can contain the empty word. + +# Complexity +Constant. +""" +set_contains_empty_word!(p::Presentation, val::Bool) = + (LibSemigroups.set_contains_empty_word!(p, val); p) + +""" + add_generator!(p::Presentation) -> Int + add_generator!(p::Presentation, x::Integer) -> Presentation + +Add a generator to `p`. + +The zero-argument form adds the first letter not already in the alphabet +of `p` as a generator and returns this letter (1-based). + +The one-argument form adds the letter `x` as a generator of `p`. + +# Arguments +- `p::Presentation`: the presentation to modify. +- `x::Integer`: (one-argument form) the letter to add as a generator. + +# Throws +- `LibsemigroupsError`: (zero-argument form) if the alphabet is already + of the maximum possible size supported by the letter type. +- `LibsemigroupsError`: (one-argument form) if `x` is already in the + alphabet of `p`. +""" +function add_generator!(p::Presentation) + x = @wrap_libsemigroups_call LibSemigroups.add_generator_no_arg!(p) + return _letter_from_cpp(x) +end + +function add_generator!(p::Presentation, x::Integer) + y = _letter_to_cpp(x) + @wrap_libsemigroups_call LibSemigroups.add_generator!(p, y) + return p +end + +""" + remove_generator!(p::Presentation, x::Integer) -> Presentation + +Remove the letter `x` as a generator of `p`. + +# Arguments +- `p::Presentation`: the presentation to modify. +- `x::Integer`: the letter to remove as a generator. + +# Throws +- `LibsemigroupsError`: if `x` is not in the alphabet of `p`. + +# Complexity +Average case: linear in the length of the alphabet; worst case: quadratic +in the length of the alphabet. +""" +function remove_generator!(p::Presentation, x::Integer) + y = _letter_to_cpp(x) + @wrap_libsemigroups_call LibSemigroups.remove_generator!(p, y) + return p +end + +""" + add_rule!(p::Presentation, lhs::AbstractVector{<:Integer}, rhs::AbstractVector{<:Integer}) -> Presentation + +Add the rule `lhs = rhs` to `p`, after checking that `lhs` and `rhs` +only contain letters in the alphabet of `p`. + +# Arguments +- `p::Presentation`: the presentation to modify. +- `lhs::AbstractVector{<:Integer}`: the left-hand side of the rule. +- `rhs::AbstractVector{<:Integer}`: the right-hand side of the rule. + +# Throws +- `LibsemigroupsError`: if `lhs` or `rhs` contains any letters not + belonging to the alphabet of `p`. +- `LibsemigroupsError`: if [`contains_empty_word`](@ref)`(p)` returns + `false` and either `lhs` or `rhs` is empty. + +# See also +- [`add_rule_no_checks!`](@ref) +""" +function add_rule!( + p::Presentation, + lhs::AbstractVector{<:Integer}, + rhs::AbstractVector{<:Integer}, +) + l = _word_to_cpp(lhs) + r = _word_to_cpp(rhs) + @wrap_libsemigroups_call LibSemigroups.add_rule!(p, l, r) + return p +end + +""" + add_rule_no_checks!(p::Presentation, lhs::AbstractVector{<:Integer}, rhs::AbstractVector{<:Integer}) -> Presentation + +Add the rule `lhs = rhs` to `p` without checking the arguments. + +# Arguments +- `p::Presentation`: the presentation to modify. +- `lhs::AbstractVector{<:Integer}`: the left-hand side of the rule. +- `rhs::AbstractVector{<:Integer}`: the right-hand side of the rule. + +# Complexity +Amortized constant. + +# Warning +No checks that the arguments describe words over the alphabet of `p` +are performed. + +# See also +- [`add_rule!`](@ref) +""" +function add_rule_no_checks!( + p::Presentation, + lhs::AbstractVector{<:Integer}, + rhs::AbstractVector{<:Integer}, +) + l = _word_to_cpp(lhs) + r = _word_to_cpp(rhs) + LibSemigroups.add_rule_no_checks!(p, l, r) + return p +end + +""" + number_of_rules(p::Presentation) -> Int + +Return the number of rules in `p`. + +The rules of a [`Presentation`](@ref Semigroups.Presentation) are stored +internally as a vector of words, with each rule occupying two consecutive +entries (its left-hand and right-hand sides); the number of rules is +therefore half the length of this vector. + +# Complexity +Constant. +""" +number_of_rules(p::Presentation) = Int(LibSemigroups.number_of_rules(p)) + +""" + rule_lhs(p::Presentation, i::Integer) -> Vector{Int} + +Return the left-hand side of the `i`-th rule of `p` (1-based rule index). + +# Arguments +- `p::Presentation`: the presentation. +- `i::Integer`: the 1-based index of the rule. + +# Throws +- `LibsemigroupsError`: if `i` is not in the range ``[1, n]``, where + ``n`` is [`number_of_rules`](@ref)`(p)`. + +# See also +- [`rule_rhs`](@ref) +- [`rules`](@ref) +""" +function rule_lhs(p::Presentation, i::Integer) + idx = UInt(i - 1) + _word_from_cpp(@wrap_libsemigroups_call LibSemigroups.rule_lhs(p, idx)) +end + +""" + rule_rhs(p::Presentation, i::Integer) -> Vector{Int} + +Return the right-hand side of the `i`-th rule of `p` (1-based rule index). + +# Arguments +- `p::Presentation`: the presentation. +- `i::Integer`: the 1-based index of the rule. + +# Throws +- `LibsemigroupsError`: if `i` is not in the range ``[1, n]``, where + ``n`` is [`number_of_rules`](@ref)`(p)`. + +# See also +- [`rule_lhs`](@ref) +- [`rules`](@ref) +""" +function rule_rhs(p::Presentation, i::Integer) + idx = UInt(i - 1) + _word_from_cpp(@wrap_libsemigroups_call LibSemigroups.rule_rhs(p, idx)) +end + +""" + rule(p::Presentation, i::Integer) -> Tuple{Vector{Int},Vector{Int}} + +Return the `i`-th rule of `p` as a `(lhs, rhs)` tuple (1-based rule index). + +Thin wrapper combining [`rule_lhs`](@ref) and [`rule_rhs`](@ref). For bulk +access prefer [`rules`](@ref), which makes a single C++ call. + +# Arguments +- `p::Presentation`: the presentation. +- `i::Integer`: the 1-based index of the rule. + +# Throws +- `LibsemigroupsError`: if `i` is not in the range ``[1, n]``, where ``n`` + is [`number_of_rules`](@ref)`(p)`. + +# See also +- [`rule_lhs`](@ref) +- [`rule_rhs`](@ref) +- [`rules`](@ref) +""" +rule(p::Presentation, i::Integer) = (rule_lhs(p, i), rule_rhs(p, i)) + +""" + rules(p::Presentation) -> Vector{Tuple{Vector{Int},Vector{Int}}} + +Return all rules of `p` as a vector of `(lhs, rhs)` tuples. + +Mirrors `p.rules` in libsemigroups. This is a single C++ call into +`LibSemigroups.rules_vector` followed by a Julia-side pairing, so is +appreciably faster than iterating [`rule_lhs`](@ref) / [`rule_rhs`](@ref) +for large presentations. +""" +function rules(p::Presentation) + flat = LibSemigroups.rules_vector(p) + n = length(flat) + return [(_word_from_cpp(flat[i]), _word_from_cpp(flat[i+1])) for i = 1:2:n] +end + +""" + clear_rules!(p::Presentation) -> Presentation + +Remove all rules from `p`. + +The alphabet of `p` is left untouched. +""" +clear_rules!(p::Presentation) = (LibSemigroups.clear_rules!(p); p) + +""" + throw_if_alphabet_has_duplicates(p::Presentation) + +Check if the alphabet of `p` is valid. + +# Throws +- `LibsemigroupsError`: if there are duplicate letters in the alphabet + of `p`. + +# Complexity +Linear in the length of the alphabet. +""" +throw_if_alphabet_has_duplicates(p::Presentation) = + (@wrap_libsemigroups_call LibSemigroups.throw_if_alphabet_has_duplicates(p); nothing) + +""" + throw_if_letter_not_in_alphabet(p::Presentation, x::Integer) + +Check if a letter belongs to the alphabet of `p`. + +# Arguments +- `p::Presentation`: the presentation. +- `x::Integer`: the letter to check. + +# Throws +- `LibsemigroupsError`: if `x` does not belong to the alphabet of `p`. + +# Complexity +Constant on average, worst case linear in the size of the alphabet. +""" +function throw_if_letter_not_in_alphabet(p::Presentation, x::Integer) + y = _letter_to_cpp(x) + @wrap_libsemigroups_call LibSemigroups.throw_if_letter_not_in_alphabet(p, y) + return nothing +end + +""" + throw_if_bad_rules(p::Presentation) + +Check if every word in every rule of `p` consists only of letters +belonging to the alphabet. + +Also checks that there are an even number of words in `p`'s rule list +(i.e. that every rule has both a left- and right-hand side). + +# Throws +- `LibsemigroupsError`: if any word contains a letter not in the + alphabet of `p`. +- `LibsemigroupsError`: if the number of words in `p`'s rule list is odd. + +# Complexity +Worst case ``O(mnt)`` where ``m`` is the length of the longest word, +``n`` is the size of the alphabet and ``t`` is the number of rules. +""" +throw_if_bad_rules(p::Presentation) = + (@wrap_libsemigroups_call LibSemigroups.throw_if_bad_rules(p); nothing) + +""" + throw_if_bad_alphabet_or_rules(p::Presentation) + +Check if the alphabet and rules of `p` are valid. + +# Throws +- `LibsemigroupsError`: if [`throw_if_alphabet_has_duplicates`](@ref) or + [`throw_if_bad_rules`](@ref) does. + +# Complexity +Worst case ``O(mnp)`` where ``m`` is the length of the longest word, +``n`` is the size of the alphabet, and ``p`` is the number of rules. +""" +throw_if_bad_alphabet_or_rules(p::Presentation) = + (@wrap_libsemigroups_call LibSemigroups.throw_if_bad_alphabet_or_rules(p); nothing) + +""" + length_of(p::Presentation) -> Int + +Return the sum of the lengths of all rule words in `p`. + +That is, ``\\sum (|u| + |v|)`` over all rules ``u = v`` of `p`. + +This function mirrors `presentation::length` in libsemigroups. It is +named `length_of` (rather than extending `Base.length`) to avoid ambiguity +with [`number_of_rules`](@ref), since neither choice is uniformly +natural. +""" +length_of(p::Presentation) = Int(LibSemigroups.length_of(p)) + +""" + longest_rule_length(p::Presentation) -> Int + +Return the maximum length of a rule in `p`. + +The *length* of a rule is defined to be the sum of the lengths of its +left-hand and right-hand sides. + +# Throws +- `LibsemigroupsError`: if the number of rule words in `p` is odd. +""" +longest_rule_length(p::Presentation) = Int(LibSemigroups.longest_rule_length(p)) + +""" + shortest_rule_length(p::Presentation) -> Int + +Return the minimum length of a rule in `p`. + +The *length* of a rule is defined to be the sum of the lengths of its +left-hand and right-hand sides. + +# Throws +- `LibsemigroupsError`: if the number of rule words in `p` is odd. +""" +shortest_rule_length(p::Presentation) = Int(LibSemigroups.shortest_rule_length(p)) + +""" + is_normalized(p::Presentation) -> Bool + +Check if the presentation `p` is normalized. + +Returns `true` if the alphabet of `p` is `[1, 2, ..., n]` (where ``n`` is +the size of the alphabet), and `false` otherwise. +""" +is_normalized(p::Presentation) = LibSemigroups.is_normalized(p) + +""" + are_rules_sorted(p::Presentation) -> Bool + +Check if the rules of `p` are sorted in short-lex order. + +Returns `true` if the rules ``u_1 = v_1, \\ldots, u_n = v_n`` of `p` +satisfy ``u_1 v_1 < \\cdots < u_n v_n`` where ``<`` is the short-lex +order. + +# Throws +- `LibsemigroupsError`: if the number of rule words in `p` is odd. + +# See also +- [`sort_rules!`](@ref) +""" +are_rules_sorted(p::Presentation) = LibSemigroups.are_rules_sorted(p) + +""" + contains_rule(p::Presentation, lhs::AbstractVector{<:Integer}, rhs::AbstractVector{<:Integer}) -> Bool + +Check if `p` contains the rule `lhs = rhs`. + +# Arguments +- `p::Presentation`: the presentation. +- `lhs::AbstractVector{<:Integer}`: the left-hand side of the rule. +- `rhs::AbstractVector{<:Integer}`: the right-hand side of the rule. + +# Complexity +Linear in [`number_of_rules`](@ref)`(p)`. +""" +function contains_rule( + p::Presentation, + lhs::AbstractVector{<:Integer}, + rhs::AbstractVector{<:Integer}, +) + l = _word_to_cpp(lhs) + r = _word_to_cpp(rhs) + LibSemigroups.contains_rule(p, l, r) +end + +""" + throw_if_odd_number_of_rules(p::Presentation) + +Throw if the number of words in the rule list of `p` is odd. + +# Throws +- `LibsemigroupsError`: if the number of words in the rule list of `p` + is odd (i.e. there is a dangling left-hand side with no matching + right-hand side). +""" +throw_if_odd_number_of_rules(p::Presentation) = + (@wrap_libsemigroups_call LibSemigroups.throw_if_odd_number_of_rules(p); nothing) + +""" + normalize_alphabet!(p::Presentation) -> Presentation + +Normalize the alphabet of `p` to `[1, ..., n]`. + +Modifies `p` in-place so that the alphabet is `[1, ..., n]` (or +equivalent), rewriting the rules to use this alphabet. If the alphabet is +already normalized, no changes are made. + +# Throws +- `LibsemigroupsError`: if [`throw_if_bad_alphabet_or_rules`](@ref) + throws on `p` before modification. +""" +normalize_alphabet!(p::Presentation) = + (@wrap_libsemigroups_call LibSemigroups.normalize_alphabet!(p); p) + +""" + change_alphabet!(p::Presentation, new_alphabet::AbstractVector{<:Integer}) -> Presentation + +Change or re-order the alphabet of `p`. + +Replaces the alphabet of `p` with `new_alphabet` where possible, and +re-writes the rules of `p` using the new alphabet. + +# Arguments +- `p::Presentation`: the presentation to modify. +- `new_alphabet::AbstractVector{<:Integer}`: the replacement alphabet. + +# Throws +- `LibsemigroupsError`: if the size of the alphabet of `p` does not + equal the length of `new_alphabet`. +""" +function change_alphabet!(p::Presentation, new_alphabet::AbstractVector{<:Integer}) + w = _word_to_cpp(new_alphabet) + @wrap_libsemigroups_call LibSemigroups.change_alphabet!(p, w) + return p +end + +""" + Base.reverse!(p::Presentation) -> Presentation + +Reverse every rule of `p`, in place. + +Extends `Base.reverse!` so that `reverse!(p)` reverses the left- and +right-hand sides of every rule of `p`. Binding-level extensions of +`Base` functions to presentation-like types are documented on the +`Presentation` doc page. +""" +Base.reverse!(p::Presentation) = (LibSemigroups.reverse_rules!(p); p) + +""" + sort_rules!(p::Presentation) -> Presentation + +Sort the rules of `p` in short-lex order. + +Sorts the rules ``u_1 = v_1, \\ldots, u_n = v_n`` so that +``u_1 v_1 < \\cdots < u_n v_n`` where ``<`` is the short-lex order. + +# Throws +- `LibsemigroupsError`: if the number of rule words in `p` is odd. + +# See also +- [`are_rules_sorted`](@ref) +""" +sort_rules!(p::Presentation) = (LibSemigroups.sort_rules!(p); p) + +""" + sort_each_rule!(p::Presentation) -> Bool + +Sort the two sides of each rule in short-lex order. + +Reorders each rule ``u = v`` of `p` so that the left-hand side is +short-lex greater than or equal to the right-hand side (i.e. the longer +/ lexicographically-larger side is on the left). + +Returns `true` if any rule was reordered, and `false` otherwise. + +# Throws +- `LibsemigroupsError`: if the number of rule words in `p` is odd. + +# Complexity +Linear in the number of rules. +""" +sort_each_rule!(p::Presentation) = LibSemigroups.sort_each_rule!(p) + +""" + add_identity_rules!(p::Presentation, e::Integer) -> Presentation + +Add rules for an identity element. + +Adds rules of the form ``ae = ea = a`` for every letter ``a`` in the +alphabet of `p`, where ``e`` is the letter given by the second argument. + +# Arguments +- `p::Presentation`: the presentation to modify. +- `e::Integer`: the identity element. + +# Throws +- `LibsemigroupsError`: if `e` is not a letter in the alphabet of `p`. + +# Complexity +Linear in the number of rules. +""" +function add_identity_rules!(p::Presentation, e::Integer) + y = _letter_to_cpp(e) + @wrap_libsemigroups_call LibSemigroups.add_identity_rules!(p, y) + return p +end + +""" + add_zero_rules!(p::Presentation, z::Integer) -> Presentation + +Add rules for a zero element. + +Adds rules of the form ``az = za = z`` for every letter ``a`` in the +alphabet of `p`, where ``z`` is the letter given by the second argument. + +# Arguments +- `p::Presentation`: the presentation to modify. +- `z::Integer`: the zero element. + +# Throws +- `LibsemigroupsError`: if `z` is not a letter in the alphabet of `p`. + +# Complexity +Linear in the number of rules. +""" +function add_zero_rules!(p::Presentation, z::Integer) + y = _letter_to_cpp(z) + @wrap_libsemigroups_call LibSemigroups.add_zero_rules!(p, y) + return p +end + +""" + remove_duplicate_rules!(p::Presentation) -> Presentation + +Remove duplicate rules from `p`. + +Removes all but one instance of any duplicate rules (if any). Note that +rules of the form ``u = v`` and ``v = u`` (if any) are considered +duplicates. The rules may be reordered by this function even if there +are no duplicate rules. + +# Throws +- `LibsemigroupsError`: if the number of rule words in `p` is odd. + +# Complexity +Linear in the number of rules. +""" +remove_duplicate_rules!(p::Presentation) = (LibSemigroups.remove_duplicate_rules!(p); p) + +""" + remove_trivial_rules!(p::Presentation) -> Presentation + +Remove rules consisting of identical words. + +Removes all instances of rules (if any) where the left-hand side and +right-hand side are identical (i.e. rules of the form ``u = u``). + +# Throws +- `LibsemigroupsError`: if the number of rule words in `p` is odd. + +# Complexity +Linear in the number of rules. +""" +remove_trivial_rules!(p::Presentation) = (LibSemigroups.remove_trivial_rules!(p); p) + +""" + add_rules!(p::Presentation, q::Presentation) -> Presentation + +Add the rules of `q` to `p`. + +Each rule of `q` is checked to contain only letters of `alphabet(p)` before +being added; if the ``n``-th rule would fail this check, the first ``n-1`` +rules are still added to `p`. + +Mirrors `libsemigroups::presentation::add_rules`. + +# Arguments +- `p::Presentation`: the presentation to add rules to. +- `q::Presentation`: the presentation whose rules should be copied into `p`. + +# Throws +- `LibsemigroupsError`: if any rule of `q` contains a letter not in + `alphabet(p)`. +""" +function add_rules!(p::Presentation, q::Presentation) + @wrap_libsemigroups_call LibSemigroups.add_rules!(p, q) + return p +end + +""" + add_inverse_rules!(p::Presentation, inverses::AbstractVector{<:Integer}) -> Presentation + add_inverse_rules!(p::Presentation, inverses::AbstractVector{<:Integer}, e::Integer) -> Presentation + +Add rules for inverses. + +The letter with index `i` in `inverses` is taken to be the inverse of the +letter `alphabet(p)[i]`. The rules added are ``a_i b_i = e`` where +``\\{a_1, \\ldots, a_n\\}`` is `alphabet(p)`, ``\\{b_1, \\ldots, b_n\\}`` +is `inverses`, and `e` is the identity letter. If `e` is omitted, the +identity is taken to be the empty word. + +Mirrors `libsemigroups::presentation::add_inverse_rules`. + +# Arguments +- `p::Presentation`: the presentation to add rules to. +- `inverses::AbstractVector{<:Integer}`: the inverses of the letters in + `alphabet(p)`. +- `e::Integer`: (3-arg form) the identity letter. + +# Throws +- `LibsemigroupsError`: if `length(inverses) != length(alphabet(p))`, if + `inverses` does not contain the same letters as `alphabet(p)`, if + ``(a_i^{-1})^{-1} = a_i`` fails for some `i`, or if + ``e^{-1} = e`` fails. +""" +function add_inverse_rules!(p::Presentation, inverses::AbstractVector{<:Integer}) + v = _word_to_cpp(inverses) + @wrap_libsemigroups_call LibSemigroups.add_inverse_rules!(p, v) + return p +end + +function add_inverse_rules!( + p::Presentation, + inverses::AbstractVector{<:Integer}, + e::Integer, +) + v = _word_to_cpp(inverses) + y = _letter_to_cpp(e) + @wrap_libsemigroups_call LibSemigroups.add_inverse_rules_with_identity!(p, v, y) + return p +end + +""" + replace_subword!(p::Presentation, existing::AbstractVector{<:Integer}, replacement::AbstractVector{<:Integer}) -> Presentation + +Replace every non-overlapping occurrence of the word `existing` in every +rule of `p` with the word `replacement`. `p` is modified in place. + +Mirrors `libsemigroups::presentation::replace_subword`. + +# Arguments +- `p::Presentation`: the presentation to modify. +- `existing::AbstractVector{<:Integer}`: the subword to replace. +- `replacement::AbstractVector{<:Integer}`: the replacement word. + +# Throws +- `LibsemigroupsError`: if `existing` is empty. + +# See also +- [`replace_word!`](@ref) +- [`replace_word_with_new_generator!`](@ref) +""" +function replace_subword!( + p::Presentation, + existing::AbstractVector{<:Integer}, + replacement::AbstractVector{<:Integer}, +) + e = _word_to_cpp(existing) + r = _word_to_cpp(replacement) + @wrap_libsemigroups_call LibSemigroups.replace_subword!(p, e, r) + return p +end + +""" + replace_word!(p::Presentation, existing::AbstractVector{<:Integer}, replacement::AbstractVector{<:Integer}) -> Presentation + +Replace every instance of the word `existing` that appears as a full side +of some rule with the word `replacement`. Specifically, every rule of the +form ``existing = w`` or ``w = existing`` has `existing` replaced by +`replacement`. `p` is modified in place. + +Differs from [`replace_subword!`](@ref), which replaces any non-overlapping +occurrence of `existing` anywhere inside any rule. + +Mirrors `libsemigroups::presentation::replace_word`. + +# Arguments +- `p::Presentation`: the presentation to modify. +- `existing::AbstractVector{<:Integer}`: the word to replace. +- `replacement::AbstractVector{<:Integer}`: the replacement word. + +# See also +- [`replace_subword!`](@ref) +""" +function replace_word!( + p::Presentation, + existing::AbstractVector{<:Integer}, + replacement::AbstractVector{<:Integer}, +) + e = _word_to_cpp(existing) + r = _word_to_cpp(replacement) + @wrap_libsemigroups_call LibSemigroups.replace_word!(p, e, r) + return p +end + +""" + replace_word_with_new_generator!(p::Presentation, w::AbstractVector{<:Integer}) -> Int + +Replace every non-overlapping (left-to-right) instance of the word `w` in +every rule of `p` with a new generator `z`, and add the rule ``w = z``. +The new generator and rule are added even if `w` is not a subword of any +rule. Returns the new generator `z` as a 1-based letter index. + +Mirrors `libsemigroups::presentation::replace_word_with_new_generator`. + +# Arguments +- `p::Presentation`: the presentation to modify. +- `w::AbstractVector{<:Integer}`: the word to replace. + +# Throws +- `LibsemigroupsError`: if `w` is empty. +""" +function replace_word_with_new_generator!(p::Presentation, w::AbstractVector{<:Integer}) + v = _word_to_cpp(w) + z = @wrap_libsemigroups_call LibSemigroups.replace_word_with_new_generator!(p, v) + return _letter_from_cpp(z) +end + +""" + first_unused_letter(p::Presentation) -> Int + +Return the smallest letter not already in the alphabet of `p`. + +Mirrors `libsemigroups::presentation::first_unused_letter`. + +# Throws +- `LibsemigroupsError`: if the alphabet of `p` is already of the maximum + possible size supported by the underlying letter type. +""" +function first_unused_letter(p::Presentation) + return _letter_from_cpp(@wrap_libsemigroups_call LibSemigroups.first_unused_letter(p)) +end + +""" + index_rule(p::Presentation, lhs::AbstractVector{<:Integer}, rhs::AbstractVector{<:Integer}) -> Union{Int, UndefinedType} + +Return the 1-based index of the first rule of `p` equal to `lhs = rhs`, +or [`UNDEFINED`](@ref Semigroups.UNDEFINED) if no such rule exists. + +Mirrors `libsemigroups::presentation::index_rule`. The returned index is +the rule-pair index - the same index accepted by [`rule_lhs`](@ref), +[`rule_rhs`](@ref), and [`rule`](@ref). + +# Arguments +- `p::Presentation`: the presentation. +- `lhs::AbstractVector{<:Integer}`: the left-hand side of the rule. +- `rhs::AbstractVector{<:Integer}`: the right-hand side of the rule. + +# Throws +- `LibsemigroupsError`: if [`throw_if_bad_alphabet_or_rules`](@ref) throws + on `p`. + +# See also +- [`is_rule`](@ref) +""" +function index_rule( + p::Presentation, + lhs::AbstractVector{<:Integer}, + rhs::AbstractVector{<:Integer}, +) + l = _word_to_cpp(lhs) + r = _word_to_cpp(rhs) + i = UInt(@wrap_libsemigroups_call LibSemigroups.index_rule(p, l, r)) + i == typemax(UInt) && return UNDEFINED + return div(Int(i), 2) + 1 +end + +""" + is_rule(p::Presentation, lhs::AbstractVector{<:Integer}, rhs::AbstractVector{<:Integer}) -> Bool + +Return `true` if `lhs = rhs` is a rule of `p`, and `false` otherwise. + +Mirrors `libsemigroups::presentation::is_rule`. + +# Throws +- `LibsemigroupsError`: if [`throw_if_bad_alphabet_or_rules`](@ref) throws + on `p`. + +# See also +- [`index_rule`](@ref) +""" +function is_rule( + p::Presentation, + lhs::AbstractVector{<:Integer}, + rhs::AbstractVector{<:Integer}, +) + l = _word_to_cpp(lhs) + r = _word_to_cpp(rhs) + return @wrap_libsemigroups_call LibSemigroups.is_rule(p, l, r) +end + +""" + longest_rule_index(p::Presentation) -> Int + +Return the 1-based index of the first rule of `p` of maximal length. + +The *length* of a rule is the sum of the lengths of its left- and +right-hand sides. Mirrors `libsemigroups::presentation::longest_rule`, +returning a rule-pair index instead of the C++ iterator - so the result is +suitable to pass to [`rule`](@ref), [`rule_lhs`](@ref), or +[`rule_rhs`](@ref). + +# Throws +- `LibsemigroupsError`: if the number of rule words in `p` is odd (which + includes the case of no rules). + +# See also +- [`shortest_rule_index`](@ref) +- [`longest_rule_length`](@ref) +""" +function longest_rule_index(p::Presentation) + flat = UInt(@wrap_libsemigroups_call LibSemigroups.longest_rule_index(p)) + return div(Int(flat), 2) + 1 +end + +""" + shortest_rule_index(p::Presentation) -> Int + +Return the 1-based index of the first rule of `p` of minimal length. + +The *length* of a rule is the sum of the lengths of its left- and +right-hand sides. Mirrors `libsemigroups::presentation::shortest_rule`, +returning a rule-pair index instead of the C++ iterator. + +# Throws +- `LibsemigroupsError`: if the number of rule words in `p` is odd (which + includes the case of no rules). + +# See also +- [`longest_rule_index`](@ref) +- [`shortest_rule_length`](@ref) +""" +function shortest_rule_index(p::Presentation) + flat = UInt(@wrap_libsemigroups_call LibSemigroups.shortest_rule_index(p)) + return div(Int(flat), 2) + 1 +end + +""" + throw_if_bad_inverses(p::Presentation, inverses::AbstractVector{<:Integer}) + +Throw a `LibsemigroupsError` if `inverses` does not define a valid list of +semigroup inverses for `alphabet(p)`. + +Mirrors `libsemigroups::presentation::throw_if_bad_inverses`. Specifically, +this function checks that `alphabet(p)` and `inverses` contain the same +letters, that `inverses` is duplicate-free, and that if `a_i = b_j` (where +`a` is the alphabet and `b` is `inverses`) then `a_j = b_i` - i.e. taking +an inverse is an involution on the letters. + +# Arguments +- `p::Presentation`: the presentation. +- `inverses::AbstractVector{<:Integer}`: the proposed inverses. + +# Throws +- `LibsemigroupsError`: if any of the above conditions does not hold. +""" +function throw_if_bad_inverses(p::Presentation, inverses::AbstractVector{<:Integer}) + v = _word_to_cpp(inverses) + @wrap_libsemigroups_call LibSemigroups.throw_if_bad_inverses(p, v) + return nothing +end + +""" + to_gap_string(p::Presentation, var_name::AbstractString = "p") -> String + +Return the GAP source code that would construct a presentation with the +same alphabet and rules as `p`. Presentations in GAP are created by taking +quotients of free semigroups or monoids. + +Mirrors `libsemigroups::presentation::to_gap_string`. + +# Arguments +- `p::Presentation`: the presentation. +- `var_name::AbstractString`: the name of the GAP variable to assign to + (defaults to `"p"`). + +# Throws +- `LibsemigroupsError`: if `p` has more than 49 generators (the cap on + GAP's default alphabet). +""" +function to_gap_string(p::Presentation, var_name::AbstractString = "p") + s = @wrap_libsemigroups_call LibSemigroups.to_gap_string(p, String(var_name)) + return String(s) +end + +Base.:(==)(a::Presentation, b::Presentation) = LibSemigroups.is_equal(a, b) + +""" + Base.isempty(p::Presentation) -> Bool + +Return `true` iff `p` has an empty alphabet and no rules - the state it +would be in immediately after [`Presentation`](@ref Semigroups.Presentation)`()` +or [`init!`](@ref). +""" +Base.isempty(p::Presentation) = isempty(alphabet(p)) && number_of_rules(p) == 0 + +""" + Base.hash(p::Presentation, h::UInt) -> UInt + +Stable hash suitable for dictionary keys: presentations equal under +`==` hash to the same value. The hash combines the +alphabet, the flat rules list, and the `contains_empty_word` flag. +""" +function Base.hash(p::Presentation, h::UInt) + return hash( + ( + alphabet(p), + [_word_from_cpp(w) for w in LibSemigroups.rules_vector(p)], + contains_empty_word(p), + ), + h, + ) +end + +function Base.show(io::IO, p::Presentation) + print(io, LibSemigroups.to_human_readable_repr(p)) +end + +# ---------------------------------------------------------------------------- +# InversePresentation +# ---------------------------------------------------------------------------- + +""" + InversePresentation(p::Presentation) -> InversePresentation + InversePresentation(ip::InversePresentation) -> InversePresentation + +Type for inverse-semigroup or inverse-monoid presentations. + +An [`InversePresentation`](@ref Semigroups.InversePresentation) is a +[`Presentation`](@ref Semigroups.Presentation) together with a word +recording a semigroup inverse for each letter of the alphabet. It is a +subtype of [`Presentation`](@ref Semigroups.Presentation) and is intended +to be used as the input to other algorithms in `libsemigroups`. + +Constructed from a [`Presentation`](@ref Semigroups.Presentation) (with +inverses initially empty), or as a copy of another +[`InversePresentation`](@ref Semigroups.InversePresentation). + +!!! warning "v1 limitation" + v1 of Semigroups.jl binds `InversePresentation` only. + Alphabets, rules, and inverses are expressed as `Vector{Int}` with + 1-based letter indices. +""" +const InversePresentation = LibSemigroups.InversePresentation + +""" + set_inverses!(ip::InversePresentation, w::AbstractVector{<:Integer}) -> InversePresentation + +Set the inverse of each letter in the alphabet of `ip`. + +The `i`-th entry of `w` is taken to be the inverse of the `i`-th letter +of `alphabet(ip)`. + +# Arguments +- `ip::InversePresentation`: the inverse presentation to modify. +- `w::AbstractVector{<:Integer}`: a word containing the inverses. + +# Throws +- `LibsemigroupsError`: if the alphabet contains duplicate letters, or + if `w` does not define a valid semigroup inverse for the alphabet. + +# Note +Although the alphabet is not an explicit argument to this function, the +alphabet must be checked here since a specification of inverses cannot +make sense if the alphabet contains duplicate letters. + +# See also +- [`throw_if_alphabet_has_duplicates`](@ref) +- [`throw_if_bad_alphabet_rules_or_inverses`](@ref) +""" +function set_inverses!(ip::InversePresentation, w::AbstractVector{<:Integer}) + v = _word_to_cpp(w) + @wrap_libsemigroups_call LibSemigroups.set_inverses!(ip, v) + return ip +end + +""" + inverses(ip::InversePresentation) -> Vector{Int} + +Return the inverse of each letter in the alphabet of `ip`. + +The `i`-th entry of the returned vector is the inverse of the `i`-th +letter of `alphabet(ip)`. +""" +inverses(ip::InversePresentation) = _word_from_cpp(LibSemigroups.inverses(ip)) + +""" + inverse_of(ip::InversePresentation, x::Integer) -> Int + +Return the inverse of the letter `x` in `ip`. + +# Arguments +- `ip::InversePresentation`: the inverse presentation. +- `x::Integer`: the letter whose inverse is sought. + +# Throws +- `LibsemigroupsError`: if no inverses have been set, or if `x` is not + in the alphabet of `ip`. + +# Note +This function mirrors `InversePresentation::inverse` in libsemigroups, +renamed to `inverse_of` to avoid shadowing Julia's `Base.inv`. +""" +function inverse_of(ip::InversePresentation, x::Integer) + y = _letter_to_cpp(x) + _letter_from_cpp(@wrap_libsemigroups_call LibSemigroups.inverse_of(ip, y)) +end + +""" + throw_if_bad_alphabet_rules_or_inverses(ip::InversePresentation) + +Check if `ip` is a valid inverse presentation. + +Specifically, checks that the alphabet of `ip` does not contain duplicate +letters, that all rules only contain letters defined in the alphabet, and +that the inverses act as semigroup inverses. + +# Throws +- `LibsemigroupsError`: if the alphabet contains duplicate letters. +- `LibsemigroupsError`: if the rules contain letters not defined in the + alphabet. +- `LibsemigroupsError`: if the inverses do not act as semigroup + inverses. + +# See also +- [`throw_if_bad_alphabet_or_rules`](@ref) +""" +throw_if_bad_alphabet_rules_or_inverses(ip::InversePresentation) = ( + @wrap_libsemigroups_call LibSemigroups.throw_if_bad_alphabet_rules_or_inverses(ip); + nothing +) + +# Equality that accounts for inverses (not the base `Presentation ==`). +Base.:(==)(a::InversePresentation, b::InversePresentation) = + LibSemigroups.is_equal_inv(a, b) + +function Base.show(io::IO, ip::InversePresentation) + print(io, LibSemigroups.to_human_readable_repr(ip)) +end + +# Extend deepcopy via the copy ctor. +Base.deepcopy_internal(ip::InversePresentation, ::IdDict) = InversePresentation(ip) diff --git a/src/transf.jl b/src/transf.jl index 251f233..0431c87 100644 --- a/src/transf.jl +++ b/src/transf.jl @@ -99,7 +99,7 @@ const _PermTypes = Union{Perm1,Perm2,Perm4} const _PTransfTypes = Union{_TransfTypes,_PPermTypes,_PermTypes} # ============================================================================ -# Index convention — helpers for crossing the Julia/C++ boundary +# Index convention - helpers for crossing the Julia/C++ boundary # ---------------------------------------------------------------------------- # Julia uses 1-based indexing with `UNDEFINED` for missing values. # C++ (libsemigroups) uses 0-based indexing with `typemax(T)` as the UNDEFINED @@ -178,7 +178,7 @@ right_one(p::_PPermTypes) = LibSemigroups.right_one(p) _scalar_type_from_degree(n::Integer) -> Type Select appropriate unsigned integer type based on degree `n`. -Returns UInt8 for n ≤ 255, UInt16 for n ≤ 65535, UInt32 otherwise. +Returns UInt8 for n <= 255, UInt16 for n <= 65535, UInt32 otherwise. """ function _scalar_type_from_degree(n::Integer) # Use <= for typemax: degree n stores 0-based indices 0..n-1, so max index n-1 @@ -475,7 +475,7 @@ Base.copy(t::Transf{T}) where {T} = Transf{T}(copy(t.cxx_obj)) """ *(t1::Transf, t2::Transf) -> Transf -Compose two transformations. Returns t1 ∘ t2, i.e., (t1*t2)[i] = t1[t2[i]]. +Compose two transformations. Returns ``t_1 \\circ t_2``, i.e., `(t1*t2)[i] = t1[t2[i]]`. Both operands must have the same scalar type (use the same underlying C++ type). # Example diff --git a/src/word-graph.jl b/src/word-graph.jl index 054353f..8ac2c76 100644 --- a/src/word-graph.jl +++ b/src/word-graph.jl @@ -51,7 +51,7 @@ const WordGraph = LibSemigroups.WordGraph # ============================================================================ # Index conversion (private, file-local) # ============================================================================ -# WordGraph — node_type = label_type = uint32_t. Conversion happens +# WordGraph - node_type = label_type = uint32_t. Conversion happens # here, not in C++: the C++ binding is pure pass-through per project convention. _to_cpp(x::Integer) = UInt32(x - 1) diff --git a/test/runtests.jl b/test/runtests.jl index f66df62..9682068 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -19,5 +19,7 @@ using Semigroups include("test_runner.jl") include("test_transf.jl") include("test_word_graph.jl") + include("test_presentation.jl") + include("test_presentation_examples.jl") include("test_word_range.jl") end diff --git a/test/test_constants.jl b/test/test_constants.jl index 857e282..b713101 100644 --- a/test/test_constants.jl +++ b/test/test_constants.jl @@ -9,7 +9,7 @@ test_constants.jl - Tests for libsemigroups constants """ @testset "Constants" begin - # Test UNDEFINED conversions — typemax(T) is the libsemigroups native sentinel + # Test UNDEFINED conversions - typemax(T) is the libsemigroups native sentinel @test convert(UInt8, UNDEFINED) === typemax(UInt8) @test convert(UInt16, UNDEFINED) === typemax(UInt16) @test convert(UInt32, UNDEFINED) === typemax(UInt32) diff --git a/test/test_presentation.jl b/test/test_presentation.jl new file mode 100644 index 0000000..fc3a929 --- /dev/null +++ b/test/test_presentation.jl @@ -0,0 +1,546 @@ +using Test +using Semigroups + +@testset verbose = true "Presentation" begin + @testset "scaffolding" begin + @test isdefined(Semigroups, :Presentation) + @test hasmethod(Presentation, Tuple{}) + end + + @testset "constructors + init! + deepcopy + alphabet getter" begin + p = Presentation() + @test p isa Presentation + + q = Presentation(p) # copy constructor + @test q isa Presentation + + init!(q) + @test alphabet(q) == Int[] + + r = deepcopy(p) # Base.deepcopy via copy ctor + @test r isa Presentation + @test r !== p # distinct wrapper + end + + @testset "alphabet setters" begin + p = Presentation() + set_alphabet!(p, 3) + @test alphabet(p) == [1, 2, 3] + + set_alphabet!(p, [1, 2, 4]) + @test alphabet(p) == [1, 2, 4] + + # Rejection of duplicates + @test_throws LibsemigroupsError set_alphabet!(p, [1, 1]) + + # Negative n must surface as InexactError (not wrapped by @wrap_libsemigroups_call) + @test_throws InexactError set_alphabet!(p, -1) + end + + @testset "alphabet queries" begin + p = Presentation() + set_alphabet!(p, [3, 1, 4]) + @test letter(p, 1) == 3 + @test letter(p, 2) == 1 + @test letter(p, 3) == 4 + @test_throws LibsemigroupsError letter(p, 4) + + @test index_of(p, 3) == 1 + @test index_of(p, 1) == 2 + @test index_of(p, 4) == 3 + @test_throws LibsemigroupsError index_of(p, 99) + + @test in_alphabet(p, 3) + @test !in_alphabet(p, 99) + end + + @testset "contains_empty_word" begin + p = Presentation() + @test contains_empty_word(p) == false + set_contains_empty_word!(p, true) + @test contains_empty_word(p) == true + set_contains_empty_word!(p, false) + @test contains_empty_word(p) == false + end + + @testset "generator management" begin + p = Presentation() + set_alphabet!(p, 2) # alphabet = [1, 2] + x = add_generator!(p) # first letter not in alphabet (1-based) + @test x == 3 + @test alphabet(p) == [1, 2, 3] + + add_generator!(p, 7) + @test 7 in alphabet(p) + @test_throws LibsemigroupsError add_generator!(p, 7) # duplicate + + remove_generator!(p, 7) + @test !(7 in alphabet(p)) + @test_throws LibsemigroupsError remove_generator!(p, 99) + end + + @testset "add_rule! / add_rule_no_checks!" begin + p = Presentation() + set_alphabet!(p, 3) + + add_rule_no_checks!(p, [1, 1, 1], [1]) + add_rule!(p, [2, 2], [2]) + + # Checked form rejects letter outside alphabet + @test_throws LibsemigroupsError add_rule!(p, [1, 99], [1]) + + # Checked form rejects empty rule when contains_empty_word == false + @test_throws LibsemigroupsError add_rule!(p, Int[], [1]) + + # With contains_empty_word = true, empty sides are accepted + set_contains_empty_word!(p, true) + add_rule!(p, Int[], [1]) + end + + @testset "rule access" begin + p = Presentation() + set_alphabet!(p, 3) + @test number_of_rules(p) == 0 + @test isempty(rules(p)) + + add_rule_no_checks!(p, [1, 1, 1], [1]) + @test number_of_rules(p) == 1 + @test rule_lhs(p, 1) == [1, 1, 1] + @test rule_rhs(p, 1) == [1] + @test rules(p) == [([1, 1, 1], [1])] + + clear_rules!(p) + @test number_of_rules(p) == 0 + end + + @testset "validation throws" begin + p = Presentation() + set_alphabet!(p, [1, 2, 3]) + throw_if_alphabet_has_duplicates(p) # no throw + throw_if_letter_not_in_alphabet(p, 2) # no throw + @test_throws LibsemigroupsError throw_if_letter_not_in_alphabet(p, 99) + throw_if_bad_alphabet_or_rules(p) + + q = Presentation() + set_alphabet!(q, 2) # [1, 2] + add_rule_no_checks!(q, [1, 99], [2]) # illegal letter 99 + @test_throws LibsemigroupsError throw_if_bad_rules(q) + @test_throws LibsemigroupsError throw_if_bad_alphabet_or_rules(q) + end + + @testset "helpers: scalar queries" begin + p = Presentation() + set_alphabet!(p, 2) + add_rule!(p, [1, 1], [1]) # lens 2 + 1 + add_rule!(p, [2, 2], [2]) # lens 2 + 1 + + @test length_of(p) == 6 # 2 + 1 + 2 + 1 + @test longest_rule_length(p) == 3 + @test shortest_rule_length(p) == 3 + @test contains_rule(p, [1, 1], [1]) + @test !contains_rule(p, [1, 2], [1]) + + @test are_rules_sorted(p) + @test is_normalized(p) + + throw_if_odd_number_of_rules(p) # no throw (even) + end + + @testset "helpers: shape mutators" begin + # normalize_alphabet! + p = Presentation() + set_alphabet!(p, [7, 3]) + add_rule!(p, [7, 3], [7]) + normalize_alphabet!(p) + @test alphabet(p) == [1, 2] + + # change_alphabet! + q = Presentation() + set_alphabet!(q, [1, 2]) + add_rule!(q, [1, 2], [1]) + change_alphabet!(q, [5, 6]) + @test alphabet(q) == [5, 6] + @test rule_lhs(q, 1) == [5, 6] + + # Base.reverse! (not exported; dispatches on ::Presentation) + r = Presentation() + set_alphabet!(r, 2) + add_rule!(r, [1, 2, 1], [2]) + reverse!(r) + @test rule_lhs(r, 1) == [1, 2, 1] # palindrome reverses to itself + # adjacent rule with distinct reverse: + add_rule!(r, [1, 2], [2, 1]) + reverse!(r) + reverse!(r) # double-reverse is identity + @test rule_lhs(r, 2) == [1, 2] + + # sort_rules!, sort_each_rule! + s = Presentation() + set_alphabet!(s, 2) + add_rule!(s, [2, 2], [2]) + add_rule!(s, [1, 1], [1]) + sort_rules!(s) + @test rule_lhs(s, 1) == [1, 1] # shortlex: smaller lhs first + + t = Presentation() + set_alphabet!(t, 2) + add_rule!(t, [2], [1, 1, 1]) # lhs < rhs by shortlex + sort_each_rule!(t) + # After sort_each_rule!: lhs > rhs under shortlex (larger side first). + # [1,1,1] has length 3 > [2] length 1, so [1,1,1] becomes lhs. + @test rule_lhs(t, 1) == [1, 1, 1] + @test rule_rhs(t, 1) == [2] + end + + @testset "helpers: rule-set mutators" begin + # add_identity_rules! + t = Presentation() + set_alphabet!(t, 2) + set_contains_empty_word!(t, true) + add_identity_rules!(t, 1) # make 1 the identity -> 1*a = a*1 = a + # 2-letter alphabet: rules are {0,0}={0}, {1,0}={1}, {0,1}={1} (0-indexed) + # i.e., 3 rules for 2 letters (2n-1 where n=2) + @test number_of_rules(t) == 3 + + # add_zero_rules! + z = Presentation() + set_alphabet!(z, 2) + set_contains_empty_word!(z, true) + add_zero_rules!(z, 1) # make 1 a zero -> 1*a = a*1 = 1 + @test number_of_rules(z) == 3 + + # remove_duplicate_rules! + u = Presentation() + set_alphabet!(u, 2) + add_rule!(u, [1, 1], [1]) + add_rule!(u, [1, 1], [1]) # duplicate + remove_duplicate_rules!(u) + @test number_of_rules(u) == 1 + + # remove_trivial_rules! + v = Presentation() + set_alphabet!(v, 1) + add_rule!(v, [1], [1]) # trivial + remove_trivial_rules!(v) + @test number_of_rules(v) == 0 + end + + @testset "equality + show" begin + p = Presentation() + set_alphabet!(p, 2) + add_rule!(p, [1, 1], [1]) + + q = Presentation() + set_alphabet!(q, 2) + add_rule!(q, [1, 1], [1]) + + @test p == q + add_rule!(q, [2], [2]) + @test p != q + + s = sprint(show, p) + @test occursin("presentation", s) + end + + @testset "InversePresentation" begin + p = Presentation() + set_alphabet!(p, [1, 2]) + ip = InversePresentation(p) + @test ip isa InversePresentation + @test ip isa Presentation # CxxWrap inheritance chain + @test alphabet(ip) == [1, 2] + + # Copy ctor + jp = InversePresentation(ip) + @test jp isa InversePresentation + + set_inverses!(ip, [2, 1]) + @test inverses(ip) == [2, 1] + @test inverse_of(ip, 1) == 2 + @test inverse_of(ip, 2) == 1 + + # Bad inverses rejected + @test_throws LibsemigroupsError set_inverses!(ip, [1, 1]) + + throw_if_bad_alphabet_rules_or_inverses(ip) + + # == takes inverses into account + kp = InversePresentation(p) + set_inverses!(kp, [2, 1]) + @test ip == kp + set_inverses!(kp, [1, 2]) # different inverse map + @test ip != kp + + # show produces something containing "presentation" (case-insensitive match) + s = sprint(show, ip) + @test occursin("presentation", lowercase(s)) + end + + @testset "end-to-end: construct, validate, render, compare" begin + p = Presentation() + set_alphabet!(p, 2) + add_rule!(p, [1, 1, 1], [1]) + add_rule!(p, [2, 2], [2]) + add_rule!(p, [1, 2, 1], [2, 1, 2]) + + throw_if_bad_alphabet_or_rules(p) + @test number_of_rules(p) == 3 + @test rules(p) == [([1, 1, 1], [1]), ([2, 2], [2]), ([1, 2, 1], [2, 1, 2])] + + # Round-trip through Base.reverse! and back + reverse!(p) + reverse!(p) + @test rules(p) == [([1, 1, 1], [1]), ([2, 2], [2]), ([1, 2, 1], [2, 1, 2])] + + # Equality against a freshly constructed twin + q = Presentation() + set_alphabet!(q, 2) + add_rule!(q, [1, 1, 1], [1]) + add_rule!(q, [2, 2], [2]) + add_rule!(q, [1, 2, 1], [2, 1, 2]) + @test p == q + + # Promote to InversePresentation + ip = InversePresentation(p) + set_inverses!(ip, [2, 1]) + @test inverse_of(ip, 1) == 2 + throw_if_bad_alphabet_rules_or_inverses(ip) + end + + @testset "add_rules!" begin + p = Presentation() + set_alphabet!(p, 2) + add_rule!(p, [1, 1], [1]) + + q = Presentation() + set_alphabet!(q, 2) + add_rule!(q, [2, 2], [2]) + add_rule!(q, [1, 2], [2, 1]) + + @test add_rules!(p, q) === p # chainable + @test rules(p) == [([1, 1], [1]), ([2, 2], [2]), ([1, 2], [2, 1])] + + # Bad letter in q's rules -> throws + bad = Presentation() + set_alphabet!(bad, 1) + add_rule_no_checks!(bad, [1, 99], [1]) # 99 not in p's alphabet + @test_throws LibsemigroupsError add_rules!(p, bad) + end + + @testset "add_inverse_rules!" begin + # 2-arg form (no identity): inverses of [1,2] are [2,1] + p = Presentation() + set_alphabet!(p, [1, 2]) + set_contains_empty_word!(p, true) # empty identity allowed + @test add_inverse_rules!(p, [2, 1]) === p # chainable + # The resulting rules should encode a*a^(-1) = empty for each letter. + @test number_of_rules(p) >= 2 + + # 3-arg form with explicit identity letter. + q = Presentation() + set_alphabet!(q, [1, 2, 3]) + @test add_inverse_rules!(q, [2, 1, 3], 3) === q # identity = letter 3 + @test number_of_rules(q) >= 1 + + # Invalid inverses rejected + r = Presentation() + set_alphabet!(r, [1, 2]) + @test_throws LibsemigroupsError add_inverse_rules!(r, [1, 1]) + end + + @testset "replace_subword!" begin + p = Presentation() + set_alphabet!(p, 2) + add_rule!(p, [1, 1, 1], [1]) # a^3 = a + + @test replace_subword!(p, [1, 1], [2]) === p + # Every non-overlapping "aa" in the rule "aaa = a" is replaced by "b": + # "aaa" -> "ba" (one a leftover); rhs "a" unchanged. + @test rules(p) == [([2, 1], [1])] + + @test_throws LibsemigroupsError replace_subword!(p, Int[], [1]) + end + + @testset "replace_word!" begin + p = Presentation() + set_alphabet!(p, 2) + add_rule!(p, [1, 1], [1, 1]) # a^2 = a^2 (trivial) + add_rule!(p, [1, 2], [1, 1]) # ab = a^2 + # Replace the full-side word [1,1] wherever it appears as a complete side. + replace_word!(p, [1, 1], [2]) + # After: [1,1] fully replaced on matching sides; [1,2]=[1,1] -> [1,2]=[2]. + @test rules(p) == [([2], [2]), ([1, 2], [2])] + end + + @testset "replace_word_with_new_generator!" begin + p = Presentation() + set_alphabet!(p, 2) + add_rule!(p, [1, 2, 1, 2], [1]) + + z = replace_word_with_new_generator!(p, [1, 2]) + @test z isa Int + @test z in alphabet(p) # new generator present + # The rule for the new generator (w = z) should be present: + @test contains_rule(p, [1, 2], [z]) + + @test_throws LibsemigroupsError replace_word_with_new_generator!(p, Int[]) + end + + @testset "first_unused_letter" begin + p = Presentation() + set_alphabet!(p, 3) + @test first_unused_letter(p) == 4 # 1-based + + q = Presentation() + set_alphabet!(q, [1, 3, 5]) + @test first_unused_letter(q) == 2 # first gap + end + + @testset "index_rule + is_rule + UNDEFINED" begin + p = Presentation() + set_alphabet!(p, 2) + add_rule!(p, [1, 1], [1]) + add_rule!(p, [2, 2], [2]) + add_rule!(p, [1, 2], [2, 1]) + + @test index_rule(p, [1, 1], [1]) == 1 + @test index_rule(p, [2, 2], [2]) == 2 + @test index_rule(p, [1, 2], [2, 1]) == 3 + + missing_idx = index_rule(p, [1], [2]) + @test missing_idx === UNDEFINED # singleton, not nothing + @test is_undefined(missing_idx) + + @test is_rule(p, [1, 1], [1]) + @test is_rule(p, [2, 2], [2]) + @test !is_rule(p, [1], [2]) + end + + @testset "longest_rule_index + shortest_rule_index" begin + p = Presentation() + set_alphabet!(p, 2) + add_rule!(p, [1], [1]) # len 2 + add_rule!(p, [1, 1, 1], [2, 2]) # len 5 + add_rule!(p, [1, 2], [2]) # len 3 + + @test longest_rule_index(p) == 2 + @test shortest_rule_index(p) == 1 + end + + @testset "throw_if_bad_inverses" begin + p = Presentation() + set_alphabet!(p, [1, 2]) + @test throw_if_bad_inverses(p, [2, 1]) === nothing # valid + + # Duplicate inverses -> throws + @test_throws LibsemigroupsError throw_if_bad_inverses(p, [1, 1]) + + # Wrong length -> throws + @test_throws LibsemigroupsError throw_if_bad_inverses(p, [2]) + end + + @testset "to_gap_string" begin + p = Presentation() + set_alphabet!(p, 2) + add_rule!(p, [1, 1], [1]) + + s = to_gap_string(p) # default var_name "p" + @test s isa String + @test !isempty(s) + @test occursin("p", s) + + s2 = to_gap_string(p, "S") + @test occursin("S", s2) + end + + @testset "rule(p, i) accessor" begin + p = Presentation() + set_alphabet!(p, 2) + add_rule!(p, [1, 1], [1]) + add_rule!(p, [2, 2], [2]) + add_rule!(p, [1, 2], [2, 1]) + + for i = 1:3 + @test rule(p, i) == (rule_lhs(p, i), rule_rhs(p, i)) + end + end + + @testset "rules(p) via rules_vector binding" begin + p = Presentation() + set_alphabet!(p, 2) + @test rules(p) == Tuple{Vector{Int},Vector{Int}}[] # empty + + add_rule!(p, [1, 1], [1]) + add_rule!(p, [2, 2], [2]) + @test rules(p) == [([1, 1], [1]), ([2, 2], [2])] + + # Binding-surface: rules_vector is a callable C++ method on Presentation. + flat = Semigroups.LibSemigroups.rules_vector(p) + @test length(flat) == 2 * number_of_rules(p) + end + + @testset "Base.isempty + Base.hash" begin + p = Presentation() + @test isempty(p) # just constructed + + set_alphabet!(p, 2) + @test !isempty(p) # has alphabet + + q = Presentation() + set_alphabet!(q, 2) + add_rule!(q, [1, 1], [1]) + @test !isempty(q) # has rules + + init!(q) + @test isempty(q) # cleared back + + # hash stability + equality + a = Presentation() + set_alphabet!(a, 2) + add_rule!(a, [1, 1], [1]) + + b = deepcopy(a) + @test hash(a) isa UInt + @test hash(a) == hash(b) + + # Two equal-but-separately-built presentations + c = Presentation() + set_alphabet!(c, 2) + add_rule!(c, [1, 1], [1]) + @test a == c + @test hash(a) == hash(c) + + # Differing presentations hash differently (overwhelmingly likely) + add_rule!(c, [2, 2], [2]) + @test a != c + @test hash(a) != hash(c) + end + + @testset "binding surface" begin + LS = Semigroups.LibSemigroups + # Scalar / reference signatures - hasmethod with concrete types works. + @test hasmethod(LS.first_unused_letter, Tuple{Presentation}) + @test hasmethod(LS.longest_rule_index, Tuple{Presentation}) + @test hasmethod(LS.shortest_rule_index, Tuple{Presentation}) + @test hasmethod(LS.rules_vector, Tuple{Presentation}) + + # Vector-input methods: CxxWrap's ArrayRef maps to a concrete + # Julia method signature that is tricky to spell as a `Tuple{...}`, so + # use `isdefined` to check the binding name is registered - enough to + # catch silent omissions. Call-through correctness is covered above. + for name in ( + :add_rules!, + :add_inverse_rules!, + :add_inverse_rules_with_identity!, + :replace_subword!, + :replace_word!, + :replace_word_with_new_generator!, + :index_rule, + :is_rule, + :throw_if_bad_inverses, + :to_gap_string, + ) + @test isdefined(LS, name) + end + end +end diff --git a/test/test_presentation_examples.jl b/test/test_presentation_examples.jl new file mode 100644 index 0000000..a9271b4 --- /dev/null +++ b/test/test_presentation_examples.jl @@ -0,0 +1,114 @@ +using Test +using Semigroups + +@testset verbose = true "presentation examples" begin + @testset "scaffolding" begin + p = symmetric_group(3) + @test p isa Presentation + @test length(alphabet(p)) == 2 + # Known from libsemigroups tests-presentation-examples-1.cpp: + # symmetric_group(3) has 4 rules. + @test number_of_rules(p) == 4 + throw_if_bad_alphabet_or_rules(p) + end + + @testset "batch A (group/transformation)" begin + # Smoke: every binding produces a valid presentation + for (fn, n) in [ + (alternating_group, 5), + (braid_group, 4), + (not_symmetric_group, 4), + (full_transformation_monoid, 4), + (partial_transformation_monoid, 4), + (symmetric_inverse_monoid, 4), + (cyclic_inverse_monoid, 4), + (order_preserving_monoid, 4), + (order_preserving_cyclic_inverse_monoid, 4), + (orientation_preserving_monoid, 4), + (orientation_preserving_reversing_monoid, 4), + ] + p = fn(n) + @test p isa Presentation + throw_if_bad_alphabet_or_rules(p) + end + + # Known-answer check: symmetric_group uses Car56 variant, which has n-1 generators + p4 = symmetric_group(4) + @test length(alphabet(p4)) == 3 # Car56: n-1 = 3 generators for n=4 + end + + @testset "batch B (diagram/partition)" begin + for (fn, n) in [ + (partition_monoid, 4), + (partial_brauer_monoid, 3), + (brauer_monoid, 3), + (singular_brauer_monoid, 3), + (temperley_lieb_monoid, 4), + (motzkin_monoid, 4), + (partial_isometries_cycle_graph_monoid, 3), + (uniform_block_bijection_monoid, 3), + (dual_symmetric_inverse_monoid, 3), + (stellar_monoid, 3), + (zero_rook_monoid, 3), + ] + p = fn(n) + @test p isa Presentation + throw_if_bad_alphabet_or_rules(p) + end + + # abacus_jones_monoid has two args + ajm = abacus_jones_monoid(4, 3) + @test ajm isa Presentation + throw_if_bad_alphabet_or_rules(ajm) + + # Known-answer: temperley_lieb_monoid(4) - n-1 generators + tlm = temperley_lieb_monoid(4) + @test length(alphabet(tlm)) == 3 # n-1 = 3 generators for n=4 + end + + @testset "batch C (plactic/misc)" begin + for (fn, n) in [ + (plactic_monoid, 4), + (chinese_monoid, 4), + (hypo_plactic_monoid, 4), + (stylic_monoid, 4), + (special_linear_group_2, 3), + ] + p = fn(n) + @test p isa Presentation + throw_if_bad_alphabet_or_rules(p) + end + + # 2-arg variants + for (fn, a, b) in [ + (fibonacci_semigroup, 2, 5), + (monogenic_semigroup, 3, 2), + (rectangular_band, 2, 3), + ] + p = fn(a, b) + @test p isa Presentation + throw_if_bad_alphabet_or_rules(p) + end + + # sigma_plactic_monoid - vector input (0-based per libsemigroups) + spm = sigma_plactic_monoid([2, 1]) + @test spm isa Presentation + throw_if_bad_alphabet_or_rules(spm) + + # Renner-family (int q) + for fn in ( + renner_type_B_monoid, + renner_type_D_monoid, + not_renner_type_B_monoid, + not_renner_type_D_monoid, + ) + p = fn(3, 1) + @test p isa Presentation + throw_if_bad_alphabet_or_rules(p) + end + + # Known-answer: monogenic_semigroup(m=3, r=2) has a single generator + ms = monogenic_semigroup(3, 2) + @test length(alphabet(ms)) == 1 + end +end diff --git a/test/test_transf.jl b/test/test_transf.jl index beb4df7..a0f02db 100644 --- a/test/test_transf.jl +++ b/test/test_transf.jl @@ -352,17 +352,17 @@ end # ======================================================================== function check_parametric_type(T) - # Auto-selection: small degree → UInt8 + # Auto-selection: small degree -> UInt8 x8 = T(collect(1:10)) @test x8 isa T{UInt8} @test degree(x8) == 10 - # Auto-selection: large degree → UInt16 + # Auto-selection: large degree -> UInt16 x16 = T(collect(1:300)) @test x16 isa T{UInt16} @test degree(x16) == 300 - # Boundary: 255 → UInt8, 256 → UInt16 + # Boundary: 255 -> UInt8, 256 -> UInt16 @test T(collect(1:255)) isa T{UInt8} @test T(collect(1:256)) isa T{UInt16} @@ -533,18 +533,18 @@ end @testset "Index conversion helpers" begin for T in (UInt8, UInt16, UInt32) - # Julia → C++ (integer) + # Julia -> C++ (integer) @test Semigroups._to_cpp(1, T) === T(0) @test Semigroups._to_cpp(5, T) === T(4) - # Julia → C++ (UNDEFINED) + # Julia -> C++ (UNDEFINED) @test Semigroups._to_cpp(UNDEFINED, T) === typemax(T) - # C++ → Julia (never undefined) + # C++ -> Julia (never undefined) @test Semigroups._from_cpp(T(0)) === 1 @test Semigroups._from_cpp(T(4)) === 5 - # C++ → Julia (PPerm; may be undefined) + # C++ -> Julia (PPerm; may be undefined) @test Semigroups._from_cpp_undef(T(0)) === 1 @test Semigroups._from_cpp_undef(T(4)) === 5 @test Semigroups._from_cpp_undef(typemax(T)) === UNDEFINED diff --git a/test/test_word_range.jl b/test/test_word_range.jl index 83a2d23..3afa449 100644 --- a/test/test_word_range.jl +++ b/test/test_word_range.jl @@ -47,7 +47,7 @@ using Semigroups set_alphabet_size!(r, 2) set_order!(r, ORDER_SHORTLEX) set_first!(r, [1]) # 0_w in C++ - set_last!(r, [1, 1, 1, 1]) # 0000_w in C++ (length 4 → stops before length 4) + set_last!(r, [1, 1, 1, 1]) # 0000_w in C++ (length 4 -> stops before length 4) # Shortlex over alphabet 2, words of length 1..3: 2 + 4 + 8 = 14. @test count(r) == 14 @test first_word(r) == [1]