diff --git a/deps/src/presentation.cpp b/deps/src/presentation.cpp index 71ac3fb..fe01c35 100644 --- a/deps/src/presentation.cpp +++ b/deps/src/presentation.cpp @@ -28,11 +28,19 @@ #include #include +//////////////////////////////////////////////////////////////////////// +// CxxWrap type traits IsMirroredType and SuperType +//////////////////////////////////////////////////////////////////////// + namespace jlcxx { template <> struct IsMirroredType> : std::false_type {}; + template <> + struct IsMirroredType> + : std::false_type {}; + template <> struct IsMirroredType< libsemigroups::InversePresentation> @@ -47,290 +55,252 @@ namespace jlcxx { namespace libsemigroups_julia { + namespace { + + //////////////////////////////////////////////////////////////////////// + // WordT — bridges jlcxx argument types for word_type vs std::string + //////////////////////////////////////////////////////////////////////// + + template + struct WordT; + + template <> + struct WordT { + using ArgType = jlcxx::ArrayRef; + static libsemigroups::word_type convert(ArgType arr) { + return libsemigroups::word_type(arr.begin(), arr.end()); + } + }; + + template <> + struct WordT { + using ArgType = std::string const&; + static std::string const& convert(ArgType str) { + return str; + } + }; + + //////////////////////////////////////////////////////////////////////// + // bind_presentation — template helper that registers all + // word-type methods for a single Presentation instantiation. + //////////////////////////////////////////////////////////////////////// + + template + void bind_presentation(jl::Module& m, std::string const& name) { + using P = libsemigroups::Presentation; + using Letter = typename P::letter_type; + using Traits = WordT; + using Arg = typename Traits::ArgType; + + auto type = m.add_type

(name); + + //////////////////////////////////////////////////////////////////// + // Constructors & Common Methods + //////////////////////////////////////////////////////////////////// + + type.template constructor<>(); + type.template constructor

(); + + type.method("init!", [](P& self) { self.init(); }); + type.method("alphabet", + [](P const& self) -> Word { return self.alphabet(); }); + type.method("set_alphabet_size!", + [](P& self, size_t n) { self.alphabet(n); }); + type.method("alphabet_from_rules!", + [](P& self) { self.alphabet_from_rules(); }); + type.method("letter", [](P const& self, size_t i) -> Letter { + return self.letter(i); + }); + type.method("index_of", [](P const& self, Letter x) -> size_t { + return self.index(x); + }); + type.method("in_alphabet", [](P const& self, Letter x) -> bool { + return self.in_alphabet(x); + }); + type.method("contains_empty_word", [](P const& self) -> bool { + return self.contains_empty_word(); + }); + type.method("set_contains_empty_word!", + [](P& self, bool v) { self.contains_empty_word(v); }); + + type.method("add_generator_no_arg!", + [](P& self) -> Letter { return self.add_generator(); }); + type.method("add_generator!", + [](P& self, Letter x) { self.add_generator(x); }); + type.method("remove_generator!", + [](P& self, Letter x) { self.remove_generator(x); }); + + type.method("number_of_rules", [](P const& self) -> size_t { + return self.rules.size() / 2; + }); + type.method("rule_lhs", [](P const& self, size_t i) -> Word { + return self.rules.at(2 * i); + }); + type.method("rule_rhs", [](P const& self, size_t i) -> Word { + return self.rules.at(2 * i + 1); + }); + type.method("clear_rules!", [](P& self) { self.rules.clear(); }); + + type.method("throw_if_alphabet_has_duplicates", [](P const& self) { + self.throw_if_alphabet_has_duplicates(); + }); + type.method("throw_if_letter_not_in_alphabet", + [](P const& self, Letter x) { + self.throw_if_letter_not_in_alphabet(x); + }); + type.method("throw_if_bad_rules", + [](P const& self) { self.throw_if_bad_rules(); }); + type.method("throw_if_bad_alphabet_or_rules", + [](P const& self) { self.throw_if_bad_alphabet_or_rules(); }); + + m.method("length_of", [](P const& p) -> size_t { + return libsemigroups::presentation::length(p); + }); + m.method("longest_rule_length", [](P const& p) -> size_t { + return libsemigroups::presentation::longest_rule_length(p); + }); + m.method("shortest_rule_length", [](P const& p) -> size_t { + return libsemigroups::presentation::shortest_rule_length(p); + }); + m.method("is_normalized", [](P const& p) -> bool { + return libsemigroups::presentation::is_normalized(p); + }); + m.method("are_rules_sorted", [](P const& p) -> bool { + return libsemigroups::presentation::are_rules_sorted(p); + }); + m.method("throw_if_odd_number_of_rules", [](P const& p) { + libsemigroups::presentation::throw_if_odd_number_of_rules(p); + }); + m.method("normalize_alphabet!", [](P& p) { + libsemigroups::presentation::normalize_alphabet(p); + }); + m.method("reverse_rules!", + [](P& p) { libsemigroups::presentation::reverse(p); }); + m.method("sort_rules!", + [](P& p) { libsemigroups::presentation::sort_rules(p); }); + m.method("sort_each_rule!", [](P& p) -> bool { + return libsemigroups::presentation::sort_each_rule(p); + }); + m.method("add_identity_rules!", [](P& p, Letter e) { + libsemigroups::presentation::add_identity_rules(p, e); + }); + m.method("add_zero_rules!", [](P& p, Letter z) { + libsemigroups::presentation::add_zero_rules(p, z); + }); + m.method("remove_duplicate_rules!", [](P& p) { + libsemigroups::presentation::remove_duplicate_rules(p); + }); + m.method("remove_trivial_rules!", [](P& p) { + libsemigroups::presentation::remove_trivial_rules(p); + }); + m.method("add_rules!", [](P& p, P const& q) { + libsemigroups::presentation::add_rules(p, q); + }); + m.method("first_unused_letter", [](P const& p) -> Letter { + return libsemigroups::presentation::first_unused_letter(p); + }); + m.method("longest_rule_index", [](P 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", [](P const& p) -> size_t { + auto it = libsemigroups::presentation::shortest_rule(p); + return static_cast(std::distance(p.rules.cbegin(), it)); + }); + m.method("to_gap_string", + [](P const& p, std::string const& var_name) -> std::string { + return libsemigroups::presentation::to_gap_string(p, var_name); + }); + m.method("rules_vector", [](P const& p) -> std::vector { + return std::vector(p.rules.cbegin(), p.rules.cend()); + }); + type.method("is_equal", + [](P const& a, P const& b) -> bool { return a == b; }); + type.method("to_human_readable_repr", [](P const& p) -> std::string { + return libsemigroups::to_human_readable_repr(p); + }); + + //////////////////////////////////////////////////////////////////// + // 2. Word-argument specific methods + //////////////////////////////////////////////////////////////////// + + type.method("set_alphabet!", + [](P& self, Arg a) { self.alphabet(Traits::convert(a)); }); + + m.method("add_rule_no_checks!", [](P& p, Arg lhs, Arg rhs) { + libsemigroups::presentation::add_rule_no_checks( + p, Traits::convert(lhs), Traits::convert(rhs)); + }); + + m.method("add_rule!", [](P& p, Arg lhs, Arg rhs) { + libsemigroups::presentation::add_rule( + p, Traits::convert(lhs), Traits::convert(rhs)); + }); + + m.method("contains_rule", [](P& p, Arg lhs, Arg rhs) -> bool { + return libsemigroups::presentation::contains_rule( + p, Traits::convert(lhs), Traits::convert(rhs)); + }); + + m.method("change_alphabet!", [](P& p, Arg a) { + libsemigroups::presentation::change_alphabet(p, Traits::convert(a)); + }); + + m.method("add_inverse_rules!", [](P& p, Arg inverses) { + libsemigroups::presentation::add_inverse_rules( + p, Traits::convert(inverses)); + }); + + m.method("add_inverse_rules_with_identity!", + [](P& p, Arg inverses, Letter e) { + libsemigroups::presentation::add_inverse_rules( + p, Traits::convert(inverses), e); + }); + + m.method("replace_subword!", [](P& p, Arg existing, Arg replacement) { + libsemigroups::presentation::replace_subword( + p, Traits::convert(existing), Traits::convert(replacement)); + }); + + m.method("replace_word!", [](P& p, Arg existing, Arg replacement) { + libsemigroups::presentation::replace_word( + p, Traits::convert(existing), Traits::convert(replacement)); + }); + + m.method("replace_word_with_new_generator!", [](P& p, Arg w) -> Letter { + return libsemigroups::presentation::replace_word_with_new_generator( + p, Traits::convert(w)); + }); + + m.method("index_rule", [](P const& p, Arg lhs, Arg rhs) -> size_t { + return libsemigroups::presentation::index_rule( + p, Traits::convert(lhs), Traits::convert(rhs)); + }); + + m.method("is_rule", [](P const& p, Arg lhs, Arg rhs) -> bool { + return libsemigroups::presentation::is_rule( + p, Traits::convert(lhs), Traits::convert(rhs)); + }); + + m.method("throw_if_bad_inverses", [](P const& p, Arg inverses) { + libsemigroups::presentation::throw_if_bad_inverses( + p, Traits::convert(inverses)); + }); + } + + } // namespace + + //////////////////////////////////////////////////////////////////////// + // define_presentation — register Presentation & InversePresentation + //////////////////////////////////////////////////////////////////////// + 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); - }); + // Bind Presentation and Presentation + bind_presentation(m, "PresentationWord"); + bind_presentation(m, "PresentationString"); // ----------------------------------------------------------------------- // InversePresentation @@ -342,7 +312,7 @@ namespace libsemigroups_julia { jlcxx::julia_base_type>()); itype.constructor const&>(); - itype.constructor const&>(); // copy ctor + itype.constructor const&>(); itype.method( "set_inverses!", diff --git a/src/cong-common.jl b/src/cong-common.jl index 93c13fb..6475b3b 100644 --- a/src/cong-common.jl +++ b/src/cong-common.jl @@ -24,6 +24,15 @@ const CongruenceCommon = LibSemigroups.CongruenceCommon const Congruence = LibSemigroups.CongruenceWord +# The C++ binding takes the congruence kind as an Integer and a raw +# `PresentationWord`; adapt the public Julia types to that. +function Congruence(kind::congruence_kind, p::Presentation) + return @wrap_libsemigroups_call LibSemigroups.CongruenceWord( + _kind_to_cpp(kind), + _cxx_word(p), + ) +end + """ number_of_classes(cong::CongruenceCommon) -> UInt64 @@ -38,7 +47,7 @@ number_of_classes(cong::CongruenceCommon) = Return the presentation associated with `cong`. """ -presentation(cong::CongruenceCommon) = LibSemigroups.presentation(cong) +presentation(cong::CongruenceCommon) = _wrap_presentation(LibSemigroups.presentation(cong)) """ kind(cong::CongruenceCommon) -> congruence_kind diff --git a/src/kambites.jl b/src/kambites.jl index 1d5e794..08c4b4b 100644 --- a/src/kambites.jl +++ b/src/kambites.jl @@ -149,7 +149,7 @@ function init!(k::Kambites) end function init!(k::Kambites, kind::congruence_kind, p::Presentation) - @wrap_libsemigroups_call LibSemigroups.init!(k, kind, p) + @wrap_libsemigroups_call LibSemigroups.init!(k, kind, _cxx_word(p)) return k end @@ -168,7 +168,7 @@ presentation is returned. The Julia binding returns by value (an independent copy) rather than by reference; mutating the returned object does not affect `k`. """ -presentation(k::Kambites) = LibSemigroups.presentation(k) +presentation(k::Kambites) = _wrap_presentation(LibSemigroups.presentation(k)) """ generating_pairs(k::Kambites) -> Vector{Tuple{Vector{Int}, Vector{Int}}} diff --git a/src/knuth-bendix.jl b/src/knuth-bendix.jl index 5f57f2e..15812c4 100644 --- a/src/knuth-bendix.jl +++ b/src/knuth-bendix.jl @@ -75,6 +75,17 @@ number_of_classes(kb) # POSITIVE_INFINITY """ const KnuthBendix = LibSemigroups.KnuthBendixRewriteTrie +# The C++ binding takes the congruence kind as an Integer and a raw +# `PresentationWord`; adapt the public Julia types to that. +_kind_to_cpp(kind) = Int(kind) + +function KnuthBendix(kind::congruence_kind, p::Presentation) + return @wrap_libsemigroups_call LibSemigroups.KnuthBendixRewriteTrie( + _kind_to_cpp(kind), + _cxx_word(p), + ) +end + """ overlap_ABC @@ -141,8 +152,8 @@ function init!(kb::KnuthBendix) return kb end -function init!(kb::KnuthBendix, kind::congruence_kind, p::Presentation) - @wrap_libsemigroups_call LibSemigroups.init!(kb, kind, p) +function init!(kb::KnuthBendix, kind::congruence_kind, p::Presentation{Vector{Int}}) + @wrap_libsemigroups_call LibSemigroups.init!(kb, _kind_to_cpp(kind), p.cxx_obj) return kb end @@ -499,7 +510,7 @@ Return a copy of the presentation used by `kb`. required by the algorithm. This extra letter also appears in the output of [`active_rules`](@ref Semigroups.active_rules). """ -presentation(kb::KnuthBendix) = LibSemigroups.presentation(kb) +presentation(kb::KnuthBendix) = _wrap_presentation(LibSemigroups.presentation(kb)) # ============================================================================ # Rules access @@ -657,8 +668,10 @@ no redundant rule is identified within the timeout. """ function redundant_rule(p::Presentation, timeout::TimePeriod) ns = convert(Nanosecond, timeout) - idx = - @wrap_libsemigroups_call LibSemigroups.kb_redundant_rule(p, Int64(Dates.value(ns))) + idx = @wrap_libsemigroups_call LibSemigroups.kb_redundant_rule( + _cxx_word(p), + Int64(Dates.value(ns)), + ) n_flat = 2 * number_of_rules(p) # If idx == n_flat, no redundant rule was found if idx >= n_flat diff --git a/src/presentation-examples.jl b/src/presentation-examples.jl index 0d22ca9..4190b31 100644 --- a/src/presentation-examples.jl +++ b/src/presentation-examples.jl @@ -40,7 +40,8 @@ symmetric group of degree `n`. """ function symmetric_group(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_symmetric_group(m) + p = @wrap_libsemigroups_call LibSemigroups.example_symmetric_group(m) + Presentation{Vector{Int}}(p) end """ @@ -60,7 +61,9 @@ group of degree `n`. """ function alternating_group(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_alternating_group(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_alternating_group(m) + ) end """ @@ -80,7 +83,7 @@ This function returns a monoid presentation defining the braid group with """ function braid_group(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_braid_group(m) + Presentation{Vector{Int}}(@wrap_libsemigroups_call LibSemigroups.example_braid_group(m)) end """ @@ -101,7 +104,9 @@ the symmetric group of degree `n`, but does not. """ function not_symmetric_group(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_not_symmetric_group(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_not_symmetric_group(m) + ) end """ @@ -121,7 +126,9 @@ transformation monoid of degree `n`. """ function full_transformation_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_full_transformation_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_full_transformation_monoid(m) + ) end """ @@ -141,7 +148,9 @@ partial transformation monoid of degree `n`. """ function partial_transformation_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_partial_transformation_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_partial_transformation_monoid(m) + ) end """ @@ -161,7 +170,9 @@ symmetric inverse monoid of degree `n`. """ function symmetric_inverse_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_symmetric_inverse_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_symmetric_inverse_monoid(m) + ) end """ @@ -181,7 +192,9 @@ cyclic inverse monoid of degree `n`. """ function cyclic_inverse_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_cyclic_inverse_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_cyclic_inverse_monoid(m) + ) end """ @@ -201,7 +214,9 @@ transformations of degree `n`. """ function order_preserving_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_order_preserving_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_order_preserving_monoid(m) + ) end """ @@ -222,7 +237,11 @@ cyclic inverse monoid of degree `n`. """ function order_preserving_cyclic_inverse_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_order_preserving_cyclic_inverse_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_order_preserving_cyclic_inverse_monoid( + m, + ) + ) end """ @@ -243,7 +262,9 @@ transformation monoid of degree `n`. """ function orientation_preserving_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_orientation_preserving_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_orientation_preserving_monoid(m) + ) end """ @@ -264,8 +285,10 @@ orientation-reversing transformation monoid of degree `n`. """ function orientation_preserving_reversing_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_orientation_preserving_reversing_monoid( - m, + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_orientation_preserving_reversing_monoid( + m, + ) ) end @@ -286,7 +309,9 @@ partition monoid of degree `n`. """ function partition_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_partition_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_partition_monoid(m) + ) end """ @@ -306,7 +331,9 @@ partial Brauer monoid of degree `n`. """ function partial_brauer_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_partial_brauer_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_partial_brauer_monoid(m) + ) end """ @@ -326,7 +353,9 @@ Brauer monoid of degree `n`. """ function brauer_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_brauer_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_brauer_monoid(m) + ) end """ @@ -346,7 +375,9 @@ Brauer monoid of degree `n`. """ function singular_brauer_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_singular_brauer_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_singular_brauer_monoid(m) + ) end """ @@ -366,7 +397,9 @@ monoid with `n - 1` generators. """ function temperley_lieb_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_temperley_lieb_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_temperley_lieb_monoid(m) + ) end """ @@ -386,7 +419,9 @@ degree `n`. """ function motzkin_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_motzkin_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_motzkin_monoid(m) + ) end """ @@ -406,7 +441,11 @@ monoid of partial isometries of an `n`-cycle graph. """ function partial_isometries_cycle_graph_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_partial_isometries_cycle_graph_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_partial_isometries_cycle_graph_monoid( + m, + ) + ) end """ @@ -426,7 +465,9 @@ uniform block bijection monoid of degree `n`. """ function uniform_block_bijection_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_uniform_block_bijection_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_uniform_block_bijection_monoid(m) + ) end """ @@ -446,7 +487,9 @@ dual symmetric inverse monoid of degree `n`. """ function dual_symmetric_inverse_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_dual_symmetric_inverse_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_dual_symmetric_inverse_monoid(m) + ) end """ @@ -466,7 +509,9 @@ with `l` generators. """ function stellar_monoid(l::Integer) ll = UInt(l) - @wrap_libsemigroups_call LibSemigroups.example_stellar_monoid(ll) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_stellar_monoid(ll) + ) end """ @@ -486,7 +531,9 @@ This function returns the default libsemigroups presentation of the """ function zero_rook_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_zero_rook_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_zero_rook_monoid(m) + ) end """ @@ -510,7 +557,9 @@ monoid of degree `n`, where each arc carries at most `d - 1` beads. function abacus_jones_monoid(n::Integer, d::Integer) nn = UInt(n) dd = UInt(d) - @wrap_libsemigroups_call LibSemigroups.example_abacus_jones_monoid(nn, dd) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_abacus_jones_monoid(nn, dd) + ) end # ---- batch C: plactic / misc ---- @@ -532,7 +581,9 @@ with `n` generators. """ function plactic_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_plactic_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_plactic_monoid(m) + ) end """ @@ -552,7 +603,9 @@ with `n` generators. """ function chinese_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_chinese_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_chinese_monoid(m) + ) end """ @@ -572,7 +625,9 @@ generators. It is a quotient monoid of the plactic monoid. """ function hypo_plactic_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_hypo_plactic_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_hypo_plactic_monoid(m) + ) end """ @@ -592,7 +647,9 @@ with `n` generators. """ function stylic_monoid(n::Integer) m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_stylic_monoid(m) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_stylic_monoid(m) + ) end """ @@ -613,7 +670,9 @@ presentation to define the claimed group. """ function special_linear_group_2(q::Integer) qq = UInt(q) - @wrap_libsemigroups_call LibSemigroups.example_special_linear_group_2(qq) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_special_linear_group_2(qq) + ) end """ @@ -636,7 +695,9 @@ semigroup `F(r, n)`. function fibonacci_semigroup(r::Integer, n::Integer) rr = UInt(r) nn = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_fibonacci_semigroup(rr, nn) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_fibonacci_semigroup(rr, nn) + ) end """ @@ -659,7 +720,9 @@ semigroup presentation. function monogenic_semigroup(m::Integer, r::Integer) mm = UInt(m) rr = UInt(r) - @wrap_libsemigroups_call LibSemigroups.example_monogenic_semigroup(mm, rr) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_monogenic_semigroup(mm, rr) + ) end """ @@ -682,7 +745,9 @@ rectangular band. function rectangular_band(m::Integer, n::Integer) mm = UInt(m) nn = UInt(n) - @wrap_libsemigroups_call LibSemigroups.example_rectangular_band(mm, nn) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_rectangular_band(mm, nn) + ) end """ @@ -710,7 +775,9 @@ congruence containing the relations ``a^{\\sigma(a)} = a``. """ function sigma_plactic_monoid(sigma::AbstractVector{<:Integer}) s = UInt[UInt(x) for x in sigma] - @wrap_libsemigroups_call LibSemigroups.example_sigma_plactic_monoid(s) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_sigma_plactic_monoid(s) + ) end """ @@ -733,7 +800,9 @@ rank `l` and Iwahori-Hecke deformation `q`. 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) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_renner_type_B_monoid(ll, qq) + ) end """ @@ -756,7 +825,9 @@ rank `l` and Iwahori-Hecke deformation `q`. 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) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_renner_type_D_monoid(ll, qq) + ) end """ @@ -781,7 +852,9 @@ the Renner monoid of type B with rank `l` and Iwahori-Hecke deformation 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) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_not_renner_type_B_monoid(ll, qq) + ) end """ @@ -806,5 +879,7 @@ the Renner monoid of type D with rank `l` and Iwahori-Hecke deformation 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) + Presentation{Vector{Int}}( + @wrap_libsemigroups_call LibSemigroups.example_not_renner_type_D_monoid(ll, qq) + ) end diff --git a/src/presentation.jl b/src/presentation.jl index 432b817..fbb87ea 100644 --- a/src/presentation.jl +++ b/src/presentation.jl @@ -3,12 +3,33 @@ # Distributed under the terms of the GPL license version 3. """ -presentation.jl - Presentation + InversePresentation wrappers +presentation.jl - Presentation{W} + InversePresentation wrappers + +Provides a parametric `Presentation{W}` type wrapping the CxxWrap-bound +C++ `Presentation` template instantiations (`Vector{Int}` or `String`). +Follows the same three-layer pattern as `froidure-pin.jl`: CxxWrap type aliases, +union type, private helpers, mutable struct, constructors, and method wrappers. """ +# ============================================================================ +# CxxWrap type aliases — concrete Presentation instantiations +# ============================================================================ + +const PresentationWord = LibSemigroups.PresentationWord +const PresentationString = LibSemigroups.PresentationString + +# ============================================================================ +# Union type for cxx_obj field +# ============================================================================ + +const _PresentationCxx = Union{PresentationWord,PresentationString} + +# ============================================================================ +# Presentation{W} mutable struct +# ============================================================================ + """ - Presentation() -> Presentation - Presentation(other::Presentation) -> Presentation + Presentation{W} Type for semigroup or monoid presentations. @@ -27,1183 +48,700 @@ 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`. +""" +mutable struct Presentation{W} + cxx_obj::_PresentationCxx +end + +# ============================================================================ +# Private helpers +# ============================================================================ -!!! 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 + _cxx_presentation_type(::Type{W}) -> Type +Map a high-level Julia word type to its CxxWrap Presentation constructor type. """ - init!(p::Presentation) -> Presentation +_cxx_presentation_type(::Type{Vector{Int}}) = PresentationWord +_cxx_presentation_type(::Type{String}) = PresentationString -Remove the alphabet and all rules from `p`. +_presentation_word_to_cpp(::Type{Vector{Int}}, w::AbstractVector{<:Integer}) = + _word_to_cpp(w) +_presentation_word_to_cpp(::Type{String}, w::AbstractString) = String(w) +_presentation_word_to_cpp(::Type{String}, w::AbstractVector{<:AbstractString}) = join(w) +_presentation_word_to_cpp(::Type{String}, w::AbstractVector{Char}) = String(w) -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) +_presentation_word_from_cpp(::Type{Vector{Int}}, w) = _word_from_cpp(w) +_presentation_word_from_cpp(::Type{String}, w) = String(w) -""" - alphabet(p::Presentation) -> Vector{Int} +_presentation_letter_to_cpp(::Type{Vector{Int}}, x::Integer) = _letter_to_cpp(x) +# In src/presentation.jl: +_presentation_letter_to_cpp(::Type{String}, x::Char) = UInt(x) +_presentation_letter_from_cpp(::Type{Vector{Int}}, x) = _letter_from_cpp(x) +_presentation_letter_from_cpp(::Type{String}, x) = Char(x) -Return the alphabet of `p`. +# ============================================================================ +# Wrap / unwrap helpers +# ============================================================================ -Returns the alphabet of `p` as a `Vector{Int}` of 1-based letter indices. +const _CxxRefs{T} = + Union{T,CxxWrap.CxxWrapCore.ConstCxxRef{<:T},CxxWrap.CxxWrapCore.CxxRef{<:T}} -# Complexity -Constant. -""" -alphabet(p::Presentation) = _word_from_cpp(LibSemigroups.alphabet(p)) +# Always copy via the C++ copy constructor: presentation(kb) and friends +# return a reference into kb, which must not be aliased by a Julia object. +_wrap_presentation(x::PresentationWord) = Presentation{Vector{Int}}(x) +_wrap_presentation(x::PresentationString) = Presentation{String}(x) +_wrap_presentation(x::Presentation) = x -# 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) +_cxx(p::Presentation) = p.cxx_obj -""" - set_alphabet!(p::Presentation, n::Integer) -> Presentation +# Presentations accepted by algorithms that are only bound for word_type. +function _cxx_word(p::Presentation{W}) where {W} + W === Vector{Int} || throw( + ArgumentError( + "this algorithm is only available for Presentation{Vector{Int}}; " * + "got Presentation{$W}.", + ), + ) + return p.cxx_obj +end -Set the alphabet of `p` by size. +# ============================================================================ +# Constructors +# ============================================================================ -Sets the alphabet of `p` to be the first `n` positive integers -`[1, 2, ..., n]`. +function Presentation{W}() where {W} + T = _cxx_presentation_type(W) + return Presentation{W}(@wrap_libsemigroups_call T()) +end -# Arguments -- `p::Presentation`: the presentation to modify. -- `n::Integer`: the size of the alphabet. +Presentation() = Presentation{Vector{Int}}() -# Throws -- `LibsemigroupsError`: if `n` is greater than the maximum number of - letters supported. +function Presentation(p::Presentation{W}) where {W} + T = _cxx_presentation_type(W) + return Presentation{W}(@wrap_libsemigroups_call T(p.cxx_obj)) +end -# Warning -This function does not verify that the rules in `p` (if any) consist of -letters belonging to the new alphabet. +""" + Presentation(rules::AbstractVector{<:Union{Tuple{W,W}, Pair{W,W}}}) where {W<:Union{Vector{Int}, String}} -# See also -- [`throw_if_alphabet_has_duplicates`](@ref) -- [`throw_if_bad_rules`](@ref) -- [`throw_if_bad_alphabet_or_rules`](@ref) +Construct a [`Presentation{W}`](@ref Semigroups.Presentation) from a vector +of rules (given as `(lhs, rhs)` tuples or `lhs => rhs` pairs), inferring the alphabet +from the letters present in the rules. """ -function set_alphabet!(p::Presentation, n::Integer) - m = UInt(n) - @wrap_libsemigroups_call LibSemigroups.set_alphabet_size!(p, m) +function Presentation( + rules::AbstractVector{<:Union{Tuple{W,W},Pair{W,W}}}, +) where {W<:Union{Vector{Int},String}} + p = Presentation{W}() + for (lhs, rhs) in rules + add_rule_no_checks!(p, lhs, rhs) + end + alphabet_from_rules!(p) return p end """ - set_alphabet!(p::Presentation, a::AbstractVector{<:Integer}) -> Presentation - -Set the alphabet of `p` to the letters in `a`. + Presentation(alphabet) -Sets the alphabet of `p` to be the (1-based) letters in `a`. +Construct an empty [`Presentation{W}`](@ref Semigroups.Presentation) with an initial +alphabet defined by `alphabet`. +""" +function Presentation(alphabet::Union{Vector{Int},String,Vector{String},Vector{Char}}) + if alphabet isa Vector{String} + p = Presentation{String}() + set_alphabet!(p, join(alphabet)) + return p + end + W = (alphabet isa String || eltype(alphabet) === Char) ? String : Vector{Int} + p = Presentation{W}() + set_alphabet!(p, alphabet) + return p +end -# Arguments -- `p::Presentation`: the presentation to modify. -- `a::AbstractVector{<:Integer}`: the alphabet. +# ============================================================================ +# Core presentation methods +# ============================================================================ -# Throws -- `LibsemigroupsError`: if `a` contains duplicate letters. +""" + init!(p::Presentation{W}) -> Presentation -# Warning -This function does not verify that the rules in `p` (if any) consist of -letters belonging to the new alphabet. +Remove the alphabet and all rules from `p`. -# See also -- [`throw_if_bad_rules`](@ref) -- [`throw_if_bad_alphabet_or_rules`](@ref) +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. """ -function set_alphabet!(p::Presentation, a::AbstractVector{<:Integer}) - w = _word_to_cpp(a) - @wrap_libsemigroups_call LibSemigroups.set_alphabet!(p, w) - return p -end +init!(p::Presentation{W}) where {W} = (LibSemigroups.init!(p.cxx_obj); p) """ - alphabet_from_rules!(p::Presentation) -> Presentation + alphabet(p::Presentation{W}) -> Vector{Int} -Set the alphabet of `p` to be the letters that appear in its rules. +Return the alphabet of `p`. # 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) +Constant. """ -alphabet_from_rules!(p::Presentation) = (LibSemigroups.alphabet_from_rules!(p); p) +alphabet(p::Presentation{W}) where {W} = + _presentation_word_from_cpp(W, LibSemigroups.alphabet(p.cxx_obj)) -""" - letter(p::Presentation, i::Integer) -> Int +Base.deepcopy_internal(p::Presentation{W}, ::IdDict) where {W} = Presentation(p) -Return a letter in the alphabet of `p` by index. +""" + set_alphabet!(p::Presentation{W}, n::Integer) -> Presentation -Returns the letter of the alphabet in position `i` (1-based). +Set the alphabet of `p` by size. +""" +function set_alphabet!(p::Presentation{W}, n::Integer) where {W} + m = UInt(n) + @wrap_libsemigroups_call LibSemigroups.set_alphabet_size!(p.cxx_obj, m) + return p +end -# Arguments -- `p::Presentation`: the presentation. -- `i::Integer`: the 1-based index of the letter to return. +""" + set_alphabet!(p::Presentation{W}, a) -> Presentation -# Throws -- `LibsemigroupsError`: if `i` is not in the range ``[1, n]``, where - ``n`` is the length of the alphabet of `p`. +Set the alphabet of `p` to the letters in `a`. """ -function letter(p::Presentation, i::Integer) - idx = UInt(i - 1) - _letter_from_cpp(@wrap_libsemigroups_call LibSemigroups.letter(p, idx)) +function set_alphabet!(p::Presentation{W}, a) where {W} + w = _presentation_word_to_cpp(W, a) + @wrap_libsemigroups_call LibSemigroups.set_alphabet!(p.cxx_obj, w) + return p end """ - index_of(p::Presentation, x::Integer) -> Int - -Return the index of a letter in the alphabet of `p`. + alphabet_from_rules!(p::Presentation{W}) -> Presentation -Returns the 1-based position of the letter `x` in the alphabet of `p`. +Set the alphabet of `p` to be the letters that appear in its rules. +""" +alphabet_from_rules!(p::Presentation{W}) where {W} = + (LibSemigroups.alphabet_from_rules!(p.cxx_obj); p) -# Arguments -- `p::Presentation`: the presentation. -- `x::Integer`: the letter whose index is sought. +""" + letter(p::Presentation{W}, i::Integer) -# Throws -- `LibsemigroupsError`: if `x` does not belong to the alphabet of `p`. +Return a letter in the alphabet of `p` by index. +""" +function letter(p::Presentation{W}, i::Integer) where {W} + idx = UInt(i - 1) + _presentation_letter_from_cpp( + W, + @wrap_libsemigroups_call LibSemigroups.letter(p.cxx_obj, idx) + ) +end -# Complexity -Constant. +""" + index_of(p::Presentation{W}, x) -> Int -# Note -This function mirrors `Presentation::index` in libsemigroups, renamed to -`index_of` to avoid clashing with `Base.index`-style conventions in Julia. +Return the 1-based index of a letter in the alphabet of `p`. """ -function index_of(p::Presentation, x::Integer) - y = _letter_to_cpp(x) - Int(@wrap_libsemigroups_call LibSemigroups.index_of(p, y)) + 1 +function index_of(p::Presentation{W}, x) where {W} + y = _presentation_letter_to_cpp(W, x) + Int(@wrap_libsemigroups_call LibSemigroups.index_of(p.cxx_obj, y)) + 1 end """ - in_alphabet(p::Presentation, x::Integer) -> Bool + in_alphabet(p::Presentation{W}, x) -> 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)) +in_alphabet(p::Presentation{W}, x) where {W} = + LibSemigroups.in_alphabet(p.cxx_obj, _presentation_letter_to_cpp(W, x)) """ - contains_empty_word(p::Presentation) -> Bool + contains_empty_word(p::Presentation{W}) -> 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) +contains_empty_word(p::Presentation{W}) where {W} = + LibSemigroups.contains_empty_word(p.cxx_obj) """ - set_contains_empty_word!(p::Presentation, val::Bool) -> Presentation + set_contains_empty_word!(p::Presentation{W}, 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) +set_contains_empty_word!(p::Presentation{W}, val::Bool) where {W} = + (LibSemigroups.set_contains_empty_word!(p.cxx_obj, val); p) """ - add_generator!(p::Presentation) -> Int - add_generator!(p::Presentation, x::Integer) -> Presentation + add_generator!(p::Presentation{W}) + add_generator!(p::Presentation{W}, x) -> 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) +function add_generator!(p::Presentation{W}) where {W} + x = @wrap_libsemigroups_call LibSemigroups.add_generator_no_arg!(p.cxx_obj) + return _presentation_letter_from_cpp(W, x) end -function add_generator!(p::Presentation, x::Integer) - y = _letter_to_cpp(x) - @wrap_libsemigroups_call LibSemigroups.add_generator!(p, y) +function add_generator!(p::Presentation{W}, x) where {W} + y = _presentation_letter_to_cpp(W, x) + @wrap_libsemigroups_call LibSemigroups.add_generator!(p.cxx_obj, y) return p end """ - remove_generator!(p::Presentation, x::Integer) -> Presentation + remove_generator!(p::Presentation{W}, x) -> 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) +function remove_generator!(p::Presentation{W}, x) where {W} + y = _presentation_letter_to_cpp(W, x) + @wrap_libsemigroups_call LibSemigroups.remove_generator!(p.cxx_obj, 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`. + add_rule!(p::Presentation{W}, lhs, rhs) -> Presentation -# 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) +Add the rule `lhs = rhs` to `p`, after validation checks. """ -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) +function add_rule!(p::Presentation{W}, lhs, rhs) where {W} + l = _presentation_word_to_cpp(W, lhs) + r = _presentation_word_to_cpp(W, rhs) + @wrap_libsemigroups_call LibSemigroups.add_rule!(p.cxx_obj, l, r) return p end """ - add_rule_no_checks!(p::Presentation, lhs::AbstractVector{<:Integer}, rhs::AbstractVector{<:Integer}) -> Presentation + add_rule_no_checks!(p::Presentation{W}, lhs, rhs) -> 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) +function add_rule_no_checks!(p::Presentation{W}, lhs, rhs) where {W} + l = _presentation_word_to_cpp(W, lhs) + r = _presentation_word_to_cpp(W, rhs) + LibSemigroups.add_rule_no_checks!(p.cxx_obj, l, r) return p end """ - number_of_rules(p::Presentation) -> Int + number_of_rules(p::Presentation{W}) -> 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)) +number_of_rules(p::Presentation{W}) where {W} = + Int(LibSemigroups.number_of_rules(p.cxx_obj)) """ - rule_lhs(p::Presentation, i::Integer) -> Vector{Int} + rule_lhs(p::Presentation{W}, i::Integer) 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) +function rule_lhs(p::Presentation{W}, i::Integer) where {W} idx = UInt(i - 1) - _word_from_cpp(@wrap_libsemigroups_call LibSemigroups.rule_lhs(p, idx)) + _presentation_word_from_cpp( + W, + @wrap_libsemigroups_call LibSemigroups.rule_lhs(p.cxx_obj, idx) + ) end """ - rule_rhs(p::Presentation, i::Integer) -> Vector{Int} + rule_rhs(p::Presentation{W}, i::Integer) 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) +function rule_rhs(p::Presentation{W}, i::Integer) where {W} idx = UInt(i - 1) - _word_from_cpp(@wrap_libsemigroups_call LibSemigroups.rule_rhs(p, idx)) + _presentation_word_from_cpp( + W, + @wrap_libsemigroups_call LibSemigroups.rule_rhs(p.cxx_obj, idx) + ) end """ - rule(p::Presentation, i::Integer) -> Tuple{Vector{Int},Vector{Int}} + rule(p::Presentation{W}, i::Integer) 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)) +rule(p::Presentation{W}, i::Integer) where {W} = (rule_lhs(p, i), rule_rhs(p, i)) """ - rules(p::Presentation) -> Vector{Tuple{Vector{Int},Vector{Int}}} + rules(p::Presentation{W}) 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) +function rules(p::Presentation{W}) where {W} + flat = LibSemigroups.rules_vector(p.cxx_obj) n = length(flat) - return [(_word_from_cpp(flat[i]), _word_from_cpp(flat[i+1])) for i = 1:2:n] + return [ + ( + _presentation_word_from_cpp(W, flat[i]), + _presentation_word_from_cpp(W, flat[i+1]), + ) for i = 1:2:n + ] end """ - clear_rules!(p::Presentation) -> Presentation + clear_rules!(p::Presentation{W}) -> Presentation Remove all rules from `p`. - -The alphabet of `p` is left untouched. """ -clear_rules!(p::Presentation) = (LibSemigroups.clear_rules!(p); p) +clear_rules!(p::Presentation{W}) where {W} = (LibSemigroups.clear_rules!(p.cxx_obj); p) + +# ============================================================================ +# Validation guards +# ============================================================================ """ - throw_if_alphabet_has_duplicates(p::Presentation) + throw_if_alphabet_has_duplicates(p::Presentation{W}) 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_alphabet_has_duplicates(p::Presentation{W}) where {W} = ( + @wrap_libsemigroups_call LibSemigroups.throw_if_alphabet_has_duplicates(p.cxx_obj); + nothing +) """ - throw_if_letter_not_in_alphabet(p::Presentation, x::Integer) + throw_if_letter_not_in_alphabet(p::Presentation{W}, x) 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) +function throw_if_letter_not_in_alphabet(p::Presentation{W}, x) where {W} + y = _presentation_letter_to_cpp(W, x) + @wrap_libsemigroups_call LibSemigroups.throw_if_letter_not_in_alphabet(p.cxx_obj, 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). + throw_if_bad_rules(p::Presentation{W}) -# 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. +Check if every word in every rule of `p` consists only of letters belonging to the alphabet. """ -throw_if_bad_rules(p::Presentation) = - (@wrap_libsemigroups_call LibSemigroups.throw_if_bad_rules(p); nothing) +throw_if_bad_rules(p::Presentation{W}) where {W} = + (@wrap_libsemigroups_call LibSemigroups.throw_if_bad_rules(p.cxx_obj); nothing) """ - throw_if_bad_alphabet_or_rules(p::Presentation) + throw_if_bad_alphabet_or_rules(p::Presentation{W}) 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) +throw_if_bad_alphabet_or_rules(p::Presentation{W}) where {W} = ( + @wrap_libsemigroups_call LibSemigroups.throw_if_bad_alphabet_or_rules(p.cxx_obj); + nothing +) + +# ============================================================================ +# Algorithms & standalone helpers +# ============================================================================ """ - length_of(p::Presentation) -> Int + length_of(p::Presentation{W}) -> 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)) +length_of(p::Presentation{W}) where {W} = Int(LibSemigroups.length_of(p.cxx_obj)) """ - longest_rule_length(p::Presentation) -> Int + longest_rule_length(p::Presentation{W}) -> 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)) +longest_rule_length(p::Presentation{W}) where {W} = + Int(LibSemigroups.longest_rule_length(p.cxx_obj)) """ - shortest_rule_length(p::Presentation) -> Int + shortest_rule_length(p::Presentation{W}) -> 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)) +shortest_rule_length(p::Presentation{W}) where {W} = + Int(LibSemigroups.shortest_rule_length(p.cxx_obj)) """ - is_normalized(p::Presentation) -> Bool + is_normalized(p::Presentation{W}) -> 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) +is_normalized(p::Presentation{W}) where {W} = LibSemigroups.is_normalized(p.cxx_obj) """ - are_rules_sorted(p::Presentation) -> Bool + are_rules_sorted(p::Presentation{W}) -> 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) +are_rules_sorted(p::Presentation{W}) where {W} = LibSemigroups.are_rules_sorted(p.cxx_obj) """ - contains_rule(p::Presentation, lhs::AbstractVector{<:Integer}, rhs::AbstractVector{<:Integer}) -> Bool + contains_rule(p::Presentation{W}, lhs, rhs) -> 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) +function contains_rule(p::Presentation{W}, lhs, rhs) where {W} + l = _presentation_word_to_cpp(W, lhs) + r = _presentation_word_to_cpp(W, rhs) + LibSemigroups.contains_rule(p.cxx_obj, l, r) end """ - throw_if_odd_number_of_rules(p::Presentation) + throw_if_odd_number_of_rules(p::Presentation{W}) 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) +throw_if_odd_number_of_rules(p::Presentation{W}) where {W} = ( + @wrap_libsemigroups_call LibSemigroups.throw_if_odd_number_of_rules(p.cxx_obj); + nothing +) """ - normalize_alphabet!(p::Presentation) -> Presentation + normalize_alphabet!(p::Presentation{W}) -> 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 the alphabet of `p`. """ -normalize_alphabet!(p::Presentation) = - (@wrap_libsemigroups_call LibSemigroups.normalize_alphabet!(p); p) +normalize_alphabet!(p::Presentation{W}) where {W} = + (@wrap_libsemigroups_call LibSemigroups.normalize_alphabet!(p.cxx_obj); p) """ - change_alphabet!(p::Presentation, new_alphabet::AbstractVector{<:Integer}) -> Presentation + change_alphabet!(p::Presentation{W}, new_alphabet) -> 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) +function change_alphabet!(p::Presentation{W}, new_alphabet) where {W} + w = _presentation_word_to_cpp(W, new_alphabet) + @wrap_libsemigroups_call LibSemigroups.change_alphabet!(p.cxx_obj, 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) +Base.reverse!(p::Presentation{W}) where {W} = (LibSemigroups.reverse_rules!(p.cxx_obj); p) """ - sort_rules!(p::Presentation) -> Presentation + sort_rules!(p::Presentation{W}) -> 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_rules!(p::Presentation{W}) where {W} = (LibSemigroups.sort_rules!(p.cxx_obj); p) """ - sort_each_rule!(p::Presentation) -> Bool + sort_each_rule!(p::Presentation{W}) -> 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) +sort_each_rule!(p::Presentation{W}) where {W} = LibSemigroups.sort_each_rule!(p.cxx_obj) """ - add_identity_rules!(p::Presentation, e::Integer) -> Presentation + add_identity_rules!(p::Presentation{W}, e) -> 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) +function add_identity_rules!(p::Presentation{W}, e) where {W} + y = _presentation_letter_to_cpp(W, e) + @wrap_libsemigroups_call LibSemigroups.add_identity_rules!(p.cxx_obj, y) return p end """ - add_zero_rules!(p::Presentation, z::Integer) -> Presentation + add_zero_rules!(p::Presentation{W}, z) -> 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) +function add_zero_rules!(p::Presentation{W}, z) where {W} + y = _presentation_letter_to_cpp(W, z) + @wrap_libsemigroups_call LibSemigroups.add_zero_rules!(p.cxx_obj, y) return p end """ - remove_duplicate_rules!(p::Presentation) -> Presentation + remove_duplicate_rules!(p::Presentation{W}) -> 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_duplicate_rules!(p::Presentation{W}) where {W} = + (LibSemigroups.remove_duplicate_rules!(p.cxx_obj); p) """ - remove_trivial_rules!(p::Presentation) -> Presentation + remove_trivial_rules!(p::Presentation{W}) -> 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) +remove_trivial_rules!(p::Presentation{W}) where {W} = + (LibSemigroups.remove_trivial_rules!(p.cxx_obj); p) """ - add_rules!(p::Presentation, q::Presentation) -> Presentation + add_rules!(p::Presentation{W}, q::Presentation{W}) -> 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) +function add_rules!(p::Presentation{W}, q::Presentation{W}) where {W} + @wrap_libsemigroups_call LibSemigroups.add_rules!(p.cxx_obj, q.cxx_obj) return p end """ - add_inverse_rules!(p::Presentation, inverses::AbstractVector{<:Integer}) -> Presentation - add_inverse_rules!(p::Presentation, inverses::AbstractVector{<:Integer}, e::Integer) -> Presentation + add_inverse_rules!(p::Presentation{W}, inverses) -> Presentation + add_inverse_rules!(p::Presentation{W}, inverses, e) -> 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) +""" +function add_inverse_rules!(p::Presentation{W}, inverses) where {W} + v = _presentation_word_to_cpp(W, inverses) + @wrap_libsemigroups_call LibSemigroups.add_inverse_rules!(p.cxx_obj, 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) +function add_inverse_rules!(p::Presentation{W}, inverses, e) where {W} + v = _presentation_word_to_cpp(W, inverses) + y = _presentation_letter_to_cpp(W, e) + @wrap_libsemigroups_call LibSemigroups.add_inverse_rules_with_identity!(p.cxx_obj, 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. + replace_subword!(p::Presentation{W}, existing, replacement) -> Presentation -# See also -- [`replace_word!`](@ref) -- [`replace_word_with_new_generator!`](@ref) +Replace every non-overlapping occurrence of `existing` with `replacement`. """ -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) +function replace_subword!(p::Presentation{W}, existing, replacement) where {W} + e = _presentation_word_to_cpp(W, existing) + r = _presentation_word_to_cpp(W, replacement) + @wrap_libsemigroups_call LibSemigroups.replace_subword!(p.cxx_obj, 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`. + replace_word!(p::Presentation{W}, existing, replacement) -> Presentation -# 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) +Replace full-side instances of `existing` with `replacement`. """ -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) +function replace_word!(p::Presentation{W}, existing, replacement) where {W} + e = _presentation_word_to_cpp(W, existing) + r = _presentation_word_to_cpp(W, replacement) + @wrap_libsemigroups_call LibSemigroups.replace_word!(p.cxx_obj, 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`. + replace_word_with_new_generator!(p::Presentation{W}, w) -# Arguments -- `p::Presentation`: the presentation to modify. -- `w::AbstractVector{<:Integer}`: the word to replace. - -# Throws -- `LibsemigroupsError`: if `w` is empty. +Replace occurrences of `w` with a new generator `z` and add rule `w = z`. """ -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) +function replace_word_with_new_generator!(p::Presentation{W}, w) where {W} + v = _presentation_word_to_cpp(W, w) + z = @wrap_libsemigroups_call LibSemigroups.replace_word_with_new_generator!( + p.cxx_obj, + v, + ) + return _presentation_letter_from_cpp(W, z) end """ - first_unused_letter(p::Presentation) -> Int + first_unused_letter(p::Presentation{W}) 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)) +function first_unused_letter(p::Presentation{W}) where {W} + return _presentation_letter_from_cpp( + W, + @wrap_libsemigroups_call LibSemigroups.first_unused_letter(p.cxx_obj) + ) 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). + index_rule(p::Presentation{W}, lhs, rhs) -> Union{Int, UndefinedType} -# 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) +Return the 1-based index of the first rule equal to `lhs = rhs`. """ -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)) +function index_rule(p::Presentation{W}, lhs, rhs) where {W} + l = _presentation_word_to_cpp(W, lhs) + r = _presentation_word_to_cpp(W, rhs) + i = UInt(@wrap_libsemigroups_call LibSemigroups.index_rule(p.cxx_obj, 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. + is_rule(p::Presentation{W}, lhs, rhs) -> Bool -Mirrors `libsemigroups::presentation::is_rule`. - -# Throws -- `LibsemigroupsError`: if [`throw_if_bad_alphabet_or_rules`](@ref) throws - on `p`. - -# See also -- [`index_rule`](@ref) +Return `true` if `lhs = rhs` is a rule of `p`. """ -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) +function is_rule(p::Presentation{W}, lhs, rhs) where {W} + l = _presentation_word_to_cpp(W, lhs) + r = _presentation_word_to_cpp(W, rhs) + return @wrap_libsemigroups_call LibSemigroups.is_rule(p.cxx_obj, l, r) end """ - longest_rule_index(p::Presentation) -> Int - -Return the 1-based index of the first rule of `p` of maximal length. + longest_rule_index(p::Presentation{W}) -> Int -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) +Return the 1-based index of the first rule of maximal length. """ -function longest_rule_index(p::Presentation) - flat = UInt(@wrap_libsemigroups_call LibSemigroups.longest_rule_index(p)) +function longest_rule_index(p::Presentation{W}) where {W} + flat = UInt(@wrap_libsemigroups_call LibSemigroups.longest_rule_index(p.cxx_obj)) 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. + shortest_rule_index(p::Presentation{W}) -> Int -# 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) +Return the 1-based index of the first rule of minimal length. """ -function shortest_rule_index(p::Presentation) - flat = UInt(@wrap_libsemigroups_call LibSemigroups.shortest_rule_index(p)) +function shortest_rule_index(p::Presentation{W}) where {W} + flat = UInt(@wrap_libsemigroups_call LibSemigroups.shortest_rule_index(p.cxx_obj)) 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. + throw_if_bad_inverses(p::Presentation{W}, inverses) -# Arguments -- `p::Presentation`: the presentation. -- `inverses::AbstractVector{<:Integer}`: the proposed inverses. - -# Throws -- `LibsemigroupsError`: if any of the above conditions does not hold. +Throw if `inverses` does not define a valid list of semigroup inverses. """ -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) +function throw_if_bad_inverses(p::Presentation{W}, inverses) where {W} + v = _presentation_word_to_cpp(W, inverses) + @wrap_libsemigroups_call LibSemigroups.throw_if_bad_inverses(p.cxx_obj, 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. + to_gap_string(p::Presentation{W}, var_name::AbstractString = "p") -> String -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). +Return GAP source code that constructs `p`. """ -function to_gap_string(p::Presentation, var_name::AbstractString = "p") - s = @wrap_libsemigroups_call LibSemigroups.to_gap_string(p, String(var_name)) +function to_gap_string(p::Presentation{W}, var_name::AbstractString = "p") where {W} + s = @wrap_libsemigroups_call LibSemigroups.to_gap_string(p.cxx_obj, String(var_name)) return String(s) end -Base.:(==)(a::Presentation, b::Presentation) = LibSemigroups.is_equal(a, b) +Base.:(==)(a::Presentation{W}, b::Presentation{W}) where {W} = + LibSemigroups.is_equal(a.cxx_obj, b.cxx_obj) -""" - Base.isempty(p::Presentation) -> Bool +Base.isempty(p::Presentation{W}) where {W} = isempty(alphabet(p)) && number_of_rules(p) == 0 -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) +function Base.hash(p::Presentation{W}, h::UInt) where {W} return hash( ( alphabet(p), - [_word_from_cpp(w) for w in LibSemigroups.rules_vector(p)], + [ + _presentation_word_from_cpp(W, w) for + w in LibSemigroups.rules_vector(p.cxx_obj) + ], contains_empty_word(p), ), h, ) end -function Base.show(io::IO, p::Presentation) - print(io, LibSemigroups.to_human_readable_repr(p)) +function Base.show(io::IO, p::Presentation{W}) where {W} + print(io, LibSemigroups.to_human_readable_repr(p.cxx_obj)) 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 +InversePresentation(p::Presentation{Vector{Int}}) = + LibSemigroups.InversePresentation(p.cxx_obj) + """ 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) @@ -1215,9 +753,6 @@ 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)) @@ -1225,18 +760,6 @@ 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) @@ -1247,27 +770,12 @@ 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) @@ -1275,5 +783,74 @@ 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) + +# ============================================================================ +# InversePresentation Delegation +# ============================================================================ + +_presentation_view(ip::InversePresentation) = Presentation{Vector{Int}}(ip) + +function _forward_to_presentation(f, ip::InversePresentation, args...) + v = _presentation_view(ip) + r = f(v, args...) + return r === v ? ip : r +end + +for _f in ( + :add_generator!, + :add_identity_rules!, + :add_inverse_rules!, + :add_rule!, + :add_rule_no_checks!, + :add_rules!, + :add_zero_rules!, + :alphabet, + :alphabet_from_rules!, + :are_rules_sorted, + :change_alphabet!, + :clear_rules!, + :contains_empty_word, + :contains_rule, + :first_unused_letter, + :in_alphabet, + :index_of, + :index_rule, + :init!, + :is_normalized, + :is_rule, + :length_of, + :letter, + :longest_rule_index, + :longest_rule_length, + :normalize_alphabet!, + :number_of_rules, + :remove_duplicate_rules!, + :remove_generator!, + :remove_trivial_rules!, + :replace_subword!, + :replace_word!, + :replace_word_with_new_generator!, + :rule, + :rule_lhs, + :rule_rhs, + :rules, + :set_alphabet!, + :set_contains_empty_word!, + :shortest_rule_index, + :shortest_rule_length, + :sort_each_rule!, + :sort_rules!, + :throw_if_alphabet_has_duplicates, + :throw_if_bad_alphabet_or_rules, + :throw_if_bad_inverses, + :throw_if_bad_rules, + :throw_if_letter_not_in_alphabet, + :throw_if_odd_number_of_rules, + :to_gap_string, +) + @eval $_f(ip::InversePresentation, args...) = _forward_to_presentation($_f, ip, args...) +end + +Base.isempty(ip::InversePresentation) = isempty(_presentation_view(ip)) +Base.reverse!(ip::InversePresentation) = (reverse!(_presentation_view(ip)); ip) diff --git a/src/to.jl b/src/to.jl index ca2c0ea..0a0a82a 100644 --- a/src/to.jl +++ b/src/to.jl @@ -104,7 +104,8 @@ This conversion returns the presentation used to construct or initialise `c`. Congruence-generating pairs are not added to the returned presentation by running `c`. """ -to(::Type{Presentation}, c::Congruence) = LibSemigroups.to_presentation_word(c) +to(::Type{Presentation}, c::Congruence) = + _wrap_presentation(LibSemigroups.to_presentation_word(c)) """ to(::Type{Presentation}, k::Kambites) -> Presentation @@ -115,7 +116,8 @@ initialise `k`. This conversion does not enumerate `k`. """ -to(::Type{Presentation}, k::Kambites) = LibSemigroups.to_presentation_word(k) +to(::Type{Presentation}, k::Kambites) = + _wrap_presentation(LibSemigroups.to_presentation_word(k)) """ to(::Type{Presentation}, tc::ToddCoxeter) -> Presentation @@ -125,7 +127,8 @@ Return the presentation used to construct or initialise `tc`. This conversion does not enumerate `tc`. """ -to(::Type{Presentation}, tc::ToddCoxeter) = LibSemigroups.to_presentation_word(tc) +to(::Type{Presentation}, tc::ToddCoxeter) = + _wrap_presentation(LibSemigroups.to_presentation_word(tc)) """ to(::Type{Presentation}, kb::KnuthBendix) -> Presentation @@ -137,7 +140,8 @@ This conversion does not enumerate `kb`, so the returned presentation may not yet describe the completed semigroup or monoid. Run `kb` first if a fully processed presentation is required. """ -to(::Type{Presentation}, kb::KnuthBendix) = LibSemigroups.to_presentation_word(kb) +to(::Type{Presentation}, kb::KnuthBendix) = + _wrap_presentation(LibSemigroups.to_presentation_word(kb)) """ to(::Type{Presentation}, fp::FroidurePin) -> Presentation @@ -147,7 +151,8 @@ Return a presentation using the currently known rules of `fp`. This conversion enumerates `fp` before obtaining its presentation. """ -to(::Type{Presentation}, fp::FroidurePin) = LibSemigroups.to_presentation_word(fp.cxx_obj) +to(::Type{Presentation}, fp::FroidurePin) = + _wrap_presentation(LibSemigroups.to_presentation_word(fp.cxx_obj)) # ============================================================================ # Conversions to KnuthBendix @@ -217,7 +222,7 @@ those of `p`, over a normalised alphabet with a corresponding inverse letter for each letter of `p`. """ to(::Type{InversePresentation}, p::Presentation) = - @wrap_libsemigroups_call LibSemigroups.to_inverse_presentation_word(p) + @wrap_libsemigroups_call LibSemigroups.to_inverse_presentation_word(_cxx_word(p)) """ to(::Type{InversePresentation}, ip::InversePresentation) -> InversePresentation diff --git a/src/todd-coxeter.jl b/src/todd-coxeter.jl index c0fc9c7..e2d9e8b 100644 --- a/src/todd-coxeter.jl +++ b/src/todd-coxeter.jl @@ -51,6 +51,9 @@ objects. """ const ToddCoxeter = LibSemigroups.ToddCoxeterWord +ToddCoxeter(kind::congruence_kind, p::Presentation) = + LibSemigroups.ToddCoxeterWord(kind, _cxx_word(p)) + # --- strategy --------------------------------------------------------------- """ @@ -273,11 +276,13 @@ function init!(tc::ToddCoxeter) return tc end + function init!(tc::ToddCoxeter, kind::congruence_kind, p::Presentation) - @wrap_libsemigroups_call LibSemigroups.init!(tc, kind, p) + @wrap_libsemigroups_call LibSemigroups.init!(tc, kind, _cxx_word(p)) return tc end + function init!(tc::ToddCoxeter, kind::congruence_kind, other::ToddCoxeter) @wrap_libsemigroups_call LibSemigroups.init!(tc, kind, other) return tc @@ -887,7 +892,7 @@ If `tc` was constructed or initialised from a [`Presentation`](@ref Semigroups.Presentation), the returned presentation is empty. """ -presentation(tc::ToddCoxeter) = LibSemigroups.presentation(tc) +presentation(tc::ToddCoxeter) = _wrap_presentation(LibSemigroups.presentation(tc)) # ============================================================================ # Base.* overloads @@ -1002,7 +1007,7 @@ Knuth-Bendix-based helper. function tc_redundant_rule(p::Presentation, timeout::TimePeriod) # See note on is_non_trivial: keep coercions out of the macro arglist. ns = Int64(Dates.value(convert(Nanosecond, timeout))) - idx = @wrap_libsemigroups_call LibSemigroups.tc_redundant_rule(p, ns) + idx = @wrap_libsemigroups_call LibSemigroups.tc_redundant_rule(_cxx_word(p), ns) n_flat = 2 * number_of_rules(p) if idx >= n_flat return nothing diff --git a/test/test_presentation.jl b/test/test_presentation.jl index fc3a929..02deb59 100644 --- a/test/test_presentation.jl +++ b/test/test_presentation.jl @@ -7,6 +7,24 @@ using Semigroups @test hasmethod(Presentation, Tuple{}) end + @testset "parametric types and string support" begin + # Default constructor resolves to Vector{Int} + p_default = Presentation() + @test p_default isa Presentation{Vector{Int}} + + p_vec = Presentation([([1, 2], [2, 1])]) + + p_str = Presentation([("a", "b")]) + # String constructor inference + p_str_inferred = Presentation(["a", "b"]) + @test p_str_inferred isa Presentation{String} + + if isdefined(Semigroups, :_cxx_word) + @test_throws ArgumentError Semigroups._cxx_word(p_str) + @test Semigroups._cxx_word(p_default) === p_default.cxx_obj + end + end + @testset "constructors + init! + deepcopy + alphabet getter" begin p = Presentation() @test p isa Presentation @@ -22,6 +40,51 @@ using Semigroups @test r !== p # distinct wrapper end +@testset "Alphabet Invariants & Custom Alphabets" begin + p_char = Presentation(['a', 'b', 'c']) + @test p_char isa Presentation{String} + @test alphabet(p_char) == "abc" + @test letter(p_char, 1) == 'a' + @test index_of(p_char, 'b') == 2 + @test in_alphabet(p_char, 'c') + @test !in_alphabet(p_char, 'z') + + p_dup = Presentation() + @test_throws LibsemigroupsError set_alphabet!(p_dup, [1, 1, 2]) +end + @testset "Rule Searching and Index Invariants" begin + p = Presentation() + set_alphabet!(p, [1, 2]) + add_rule!(p, [1, 2], [2, 1]) + + # Verified present rule + @test is_rule(p, [1, 2], [2, 1]) + @test index_rule(p, [1, 2], [2, 1]) == 1 + + # Verified absent rule returns UNDEFINED sentinel + @test !is_rule(p, [1], [2]) + @test index_rule(p, [1], [2]) == Semigroups.UNDEFINED + @test !contains_rule(p, [1], [2]) + end + +@testset "Rule Validation Guards" begin + p = Presentation() + set_alphabet!(p, [1, 2]) + + @test_throws LibsemigroupsError throw_if_letter_not_in_alphabet(p, 3) + + add_rule_no_checks!(p, [1, 3], [2]) + @test_throws LibsemigroupsError throw_if_bad_rules(p) + @test_throws LibsemigroupsError throw_if_bad_alphabet_or_rules(p) +end + +@testset "Algorithm Type Guard Constraints" begin + p_str = Presentation{String}() + set_alphabet!(p_str, "ab") + + # Assert type guard raises ArgumentError explaining type incompatibility + @test_throws ArgumentError Semigroups._cxx_word(p_str) +end @testset "alphabet setters" begin p = Presentation() set_alphabet!(p, 3) @@ -247,7 +310,7 @@ using Semigroups set_alphabet!(p, [1, 2]) ip = InversePresentation(p) @test ip isa InversePresentation - @test ip isa Presentation # CxxWrap inheritance chain + @test ip isa Semigroups.LibSemigroups.PresentationWord # CxxWrap inheritance chain @test alphabet(ip) == [1, 2] # Copy ctor @@ -475,7 +538,7 @@ using Semigroups @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) + flat = Semigroups.LibSemigroups.rules_vector(p.cxx_obj) @test length(flat) == 2 * number_of_rules(p) end @@ -519,10 +582,10 @@ using Semigroups @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}) + @test hasmethod(LS.first_unused_letter, Tuple{LS.PresentationWord}) + @test hasmethod(LS.longest_rule_index, Tuple{LS.PresentationWord}) + @test hasmethod(LS.shortest_rule_index, Tuple{LS.PresentationWord}) + @test hasmethod(LS.rules_vector, Tuple{LS.PresentationWord}) # Vector-input methods: CxxWrap's ArrayRef maps to a concrete # Julia method signature that is tricky to spell as a `Tuple{...}`, so diff --git a/test/test_todd_coxeter.jl b/test/test_todd_coxeter.jl index 77f7107..880d8d8 100644 --- a/test/test_todd_coxeter.jl +++ b/test/test_todd_coxeter.jl @@ -239,6 +239,35 @@ end @test def_policy(tc) == def_policy_purge_all end +@testset "TC init! overloads coverage" begin + p1 = Presentation() + set_alphabet!(p1, 2) + add_rule_no_checks!(p1, _tc_word(0, 0, 0), _tc_word(0)) + add_rule_no_checks!(p1, _tc_word(0), _tc_word(1, 1)) + + p2 = Presentation() + set_alphabet!(p2, 1) + add_rule_no_checks!(p2, _tc_word(0, 0), _tc_word(0)) + + tc = ToddCoxeter(twosided, p1) + run!(tc) + @test number_of_classes(tc) == 5 + + # Re-initialize with presentation + init!(tc, twosided, p2) + run!(tc) + @test number_of_classes(tc) == 1 + + # Re-initialize with another ToddCoxeter + tc_other = ToddCoxeter(twosided, p1) + init!(tc, onesided, tc_other) + @test tc isa ToddCoxeter + + # Re-initialize with WordGraph + wg = WordGraph(1, 2) + init!(tc, twosided, wg) + @test tc isa ToddCoxeter +end @testset "TC - current_word_graph after run!" begin # After run!, current_word_graph may include inactive allocation slots, # so it has at least number_of_classes(tc) + 1 nodes (the +1 accounts for