libsemigroups  v3.0.0
C++ library for semigroups and monoids
Loading...
Searching...
No Matches
kambites-class.hpp
1//
2// libsemigroups - C++ library for semigroups and monoids
3// Copyright (C) 2021-2025 James D. Mitchell + Maria Tsalakou
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <http://www.gnu.org/licenses/>.
17//
18
19// This file contains the declaration of the Kambites class implementing the
20// algorithm described in:
21//
22// Kambites, M. (2009). Small overlap monoids. I. The word problem. J. Algebra,
23// 321(8), 2187–2205.
24//
25// for solving the word problem in small overlap monoids, and a novel algorithm
26// for computing normal forms in small overlap monoids, by Maria Tsalakou.
27
28#ifndef LIBSEMIGROUPS_KAMBITES_CLASS_HPP_
29#define LIBSEMIGROUPS_KAMBITES_CLASS_HPP_
30
31#include <algorithm> // for equal, move, copy, min
32#include <cstddef> // for size_t
33#include <cstdint> // for uint64_t
34#include <functional> // for swap
35#include <initializer_list> // for begin, end
36#include <string> // for basic_string, string, swap
37#include <tuple> // for get, tie, swap, tuple
38#include <type_traits> // for is_same_v, conditional_t
39#include <unordered_map> // for swap, unordered_map
40#include <utility> // for get, move, swap, make_pair
41#include <vector> // for vector, swap
42
43#include "constants.hpp" // for UNDEFINED, operator==
44#include "debug.hpp" // for LIBSEMIGROUPS_ASSERT
45#include "exception.hpp" // for LIBSEMIGROUPS_EXCEPTION
46#include "order.hpp" // for lexicographical_compare
47#include "presentation.hpp" // for operator!=, Presentation
48#include "to-presentation.hpp" // for to<Presentation>
49#include "types.hpp" // for tril, congruence_kind
50#include "ukkonen.hpp" // for maximal_piece_prefix_no...
51#include "word-range.hpp" // for operator+=, operator+
52
53#include "detail/cong-common-class.hpp" // for detail::CongruenceCommon
54#include "detail/fmt.hpp" // for format
55#include "detail/multi-string-view.hpp" // for MultiStringView, is_prefix
56#include "detail/string.hpp" // for is_prefix, maximum_comm...
57#include "detail/uf.hpp" // for Duf
58
59namespace libsemigroups {
60
75
84
100
111
125
149 // TODO(1) example
150 template <typename Word = detail::MultiStringView>
151 class Kambites : public detail::CongruenceCommon {
152 public:
154 // Kambites - aliases - public
156
165 = std::conditional_t<std::is_same_v<Word, detail::MultiStringView>,
167 Word>;
168
169 private:
170 using internal_type = Word;
171
173 // Kambites - inner classes - private
175
176 // Data structure for caching the regularly accessed parts of the
177 // relation words.
178 struct RelationWords;
179
180 // Data structure for caching the complements of each relation word.
181 //
182 // We say that a relation word u' is a *complement* of a relation word u
183 // if there are relation words u = r_1,r_2, ..., r_n = u'$ such that
184 // either (r_i,r_{i+1}) \in R or (r_{i+1},r_i) \in R for $1 \leq i \leq
185 // n$. We say that $u'$ is a *proper complement* of the relation word u
186 // if it is a complement of u and u is not equal to u'.
187 class Complements;
188
190 // Kambites - data members - private
192
193 mutable size_t _class;
194 mutable Complements _complements;
195 mutable bool _have_class;
196 mutable std::vector<RelationWords> _XYZ_data;
197 mutable native_word_type _tmp_value1, _tmp_value2;
198
199 std::vector<native_word_type> _generating_pairs;
200 Presentation<native_word_type> _presentation;
201 Ukkonen _suffix_tree;
202
203 using internal_type_iterator = typename internal_type::const_iterator;
204
205 void throw_if_1_sided(congruence_kind knd) const;
206
207 public:
209 // Kambites - Constructors, destructor, initialisation - public
211
218 Kambites();
219
231 Kambites& init();
232
238 Kambites(Kambites const&);
239
246
252 Kambites& operator=(Kambites const&);
253
260
261 ~Kambites();
262
282 //! \ref congruence_kind::twosided.
284 // call the rval ref constructor
286
308 //! \ref congruence_kind::twosided.
311 // call the rval ref init
312 return init(knd, Presentation<native_word_type>(p));
313 }
314
318 //! const&)
320 : Kambites() {
321 init(knd, std::move(p));
322 }
323
329
344 presentation() const noexcept {
345 return _presentation;
346 }
347
361 [[nodiscard]] std::vector<native_word_type> const&
362 generating_pairs() const noexcept {
363 return _generating_pairs;
364 }
365
367 // Interface requirements - add_pairs
369
382 template <typename Iterator1,
383 typename Iterator2,
384 typename Iterator3,
385 typename Iterator4>
386 Kambites& add_generating_pair_no_checks(Iterator1 first1,
387 Iterator2 last1,
388 Iterator3 first2,
389 Iterator4 last2);
402 template <typename Iterator1,
403 typename Iterator2,
404 typename Iterator3,
405 typename Iterator4>
406 Kambites& add_generating_pair(Iterator1 first1,
407 Iterator2 last1,
408 Iterator3 first2,
409 Iterator4 last2) {
410 return detail::CongruenceCommon::add_generating_pair<Kambites>(
411 first1, last1, first2, last2);
412 }
413
415 // Interface requirements - number_of_classes
417
435 // Not noexcept, throws
436 [[nodiscard]] uint64_t number_of_classes() {
438 return POSITIVE_INFINITY;
439 }
440
442 // Interface requirements - contains
444
474 template <typename Iterator1,
475 typename Iterator2,
476 typename Iterator3,
477 typename Iterator4>
478 [[nodiscard]] tril currently_contains_no_checks(Iterator1 first1,
479 Iterator2 last1,
480 Iterator3 first2,
481 Iterator4 last2) const;
482
507 template <typename Iterator1,
508 typename Iterator2,
509 typename Iterator3,
510 typename Iterator4>
511 [[nodiscard]] tril currently_contains(Iterator1 first1,
512 Iterator2 last1,
513 Iterator3 first2,
514 Iterator4 last2) const;
531 template <typename Iterator1,
532 typename Iterator2,
533 typename Iterator3,
534 typename Iterator4>
535 [[nodiscard]] bool contains_no_checks(Iterator1 first1,
536 Iterator2 last1,
537 Iterator3 first2,
538 Iterator4 last2) {
539 return detail::CongruenceCommon::contains_no_checks<Kambites>(
540 first1, last1, first2, last2);
541 }
542
559 template <typename Iterator1,
560 typename Iterator2,
561 typename Iterator3,
562 typename Iterator4>
563 [[nodiscard]] bool contains(Iterator1 first1,
564 Iterator2 last1,
565 Iterator3 first2,
566 Iterator4 last2);
567
569 // Interface requirements - reduce
571
572 private:
573 void normal_form_no_checks(native_word_type& result,
574 native_word_type const& w) const;
575
576 public:
599 template <typename OutputIterator, typename Iterator1, typename Iterator2>
600 OutputIterator reduce_no_run_no_checks(OutputIterator d_first,
601 Iterator1 first,
602 Iterator2 last) const;
603
626 template <typename OutputIterator, typename Iterator1, typename Iterator2>
627 OutputIterator reduce_no_run(OutputIterator d_first,
628 Iterator1 first,
629 Iterator2 last) const {
630 return detail::CongruenceCommon::reduce_no_run<Kambites>(
631 d_first, first, last);
632 }
633
655 template <typename OutputIterator,
656 typename InputIterator1,
657 typename InputIterator2>
658 OutputIterator reduce_no_checks(OutputIterator d_first,
659 InputIterator1 first,
660 InputIterator2 last) {
661 return detail::CongruenceCommon::reduce_no_checks<Kambites>(
662 d_first, first, last);
663 }
664
686 template <typename OutputIterator,
687 typename InputIterator1,
688 typename InputIterator2>
689 OutputIterator reduce(OutputIterator d_first,
690 InputIterator1 first,
691 InputIterator2 last) {
692 return detail::CongruenceCommon::reduce<Kambites>(d_first, first, last);
693 }
694
696 // Kambites - member functions - public
698
731 // not noexcept because number_of_pieces_no_checks isn't
732 [[nodiscard]] size_t small_overlap_class();
733
745 [[nodiscard]] size_t small_overlap_class() const noexcept;
746
759 //! \noexcept
760 [[nodiscard]] auto const& ukkonen() noexcept {
761 run();
762 return _suffix_tree;
763 }
764
766 // Kambites - validation functions - public
768
787 template <typename Iterator1, typename Iterator2>
788 void throw_if_letter_not_in_alphabet(Iterator1 first,
789 Iterator2 last) const {
790 _presentation.throw_if_letter_not_in_alphabet(first, last);
791 }
792
802 void throw_if_not_C4();
803
814 void throw_if_not_C4() const;
815
827 //! at least \f$4\f$.
828 [[nodiscard]] bool success() const override {
829 return finished() && _class >= 4;
830 }
831
832#ifdef LIBSEMIGROUPS_PARSED_BY_DOXYGEN
847 [[nodiscard]] congruence_kind kind() const noexcept;
848
864 [[nodiscard]] size_t number_of_generating_pairs() const noexcept;
865#endif
866
867 private:
869 // Kambites - XYZ functions - private
871
872 void really_init_XYZ_data(size_t i) const;
873
874 // init_XYZ_data is split into the function that really does the work
875 // (really_init_XYZ_data) which is called once, and init_XYZ_data which
876 // can be called very often.
877 // Intentionally inlined, do not move to tpp file.
878#if defined(__GNUC__) && !defined(__clang__)
879#pragma GCC diagnostic push
880#pragma GCC diagnostic ignored "-Winline"
881#endif
882 inline void init_XYZ_data(size_t i) const {
883 LIBSEMIGROUPS_ASSERT(i < _presentation.rules.size());
884 if (_XYZ_data.empty()) {
885 _XYZ_data.resize(_presentation.rules.size());
886 }
887 if (!_XYZ_data[i].is_initialized) {
888 really_init_XYZ_data(i);
889 }
890 }
891#if defined(__GNUC__) && !defined(__clang__)
892#pragma GCC diagnostic pop
893#endif
894 [[nodiscard]] internal_type const& X(size_t i) const;
895 [[nodiscard]] internal_type const& Y(size_t i) const;
896 [[nodiscard]] internal_type const& Z(size_t i) const;
897 [[nodiscard]] internal_type const& XY(size_t i) const;
898 [[nodiscard]] internal_type const& YZ(size_t i) const;
899 [[nodiscard]] internal_type const& XYZ(size_t i) const;
900
902 // Kambites - helpers - private
904
905 // Returns the index of the relation word r_i = X_iY_iZ_i if [first,
906 // last) = X_iY_iw for some w. If no such exists, then UNDEFINED is
907 // returned.
908 // Not noexcept because is_prefix isn't
909 [[nodiscard]] size_t
910 relation_prefix(internal_type_iterator const& first,
911 internal_type_iterator const& last) const;
912
913 // Returns the index of the relation word r_i = X_iY_iZ_i such that
914 // X_iY_i is a clean overlap prefix of <s>, i.e. <s> = X_iY_iw for some
915 // w, and there's no factor of <s> of the form X_jY_j starting before
916 // the beginning of Y_i. If no such exists, UNDEFINED is returned. Not
917 // noexcept because relation_prefix isn't
918 [[nodiscard]] inline size_t
919 clean_overlap_prefix(internal_type const& s) const {
920 return clean_overlap_prefix(s.cbegin(), s.cend());
921 }
922
923 [[nodiscard]] size_t
924 clean_overlap_prefix(internal_type_iterator const& first,
925 internal_type_iterator const& last) const;
926
927 // Calls clean_overlap_prefix on every suffix of <s> starting within the
928 // range [0, n), and returns a std::pair p where:
929 //
930 // * <p.first> is the starting index of the suffix of <s> that contains
931 // a clean_overlap_prefix.
932 //
933 // * <p.second> is the index of the relation word that's a clear overlap
934 // prefix of a suffix of <s>
935 [[nodiscard]] std::pair<size_t, size_t>
936 clean_overlap_prefix_mod(internal_type const& s, size_t n) const;
937
938 // If x + [first, last) = aX_sY_sw words a, w and for some index s,
939 // where X_s = X_s'X_s'', x = aX_s', and [first, last) = X_s''Y_sw, then
940 // this function returns a tuple <Word> where:
941 //
942 // 1. std::get<0>(Word) is the index <s>
943 //
944 // 2. std::get<1>(Word) is an iterator <it1> into <x>, such that
945 // [it1, x.cend()) = X_s'
946 //
947 // 3. std::get<2>(Word) is an iterator <it2> into [first, last), such
948 // that [it2, last) = w
949 //
950 // If no such relation word exists, then
951 //
952 // (UNDEFINED, first.cend(), second.cend())
953 //
954 // is returned.
955 //
956 // Not noexcept because relation_prefix isn't
957 [[nodiscard]] std::
958 tuple<size_t, internal_type_iterator, internal_type_iterator>
959 p_active(internal_type const& x,
960 internal_type_iterator const& first,
961 internal_type_iterator const& last) const;
962
963 // Returns a word equal to w in this, starting with the piece p, no
964 // checks are performed. Used in the normal_form function. Not noexcept
965 // because detail::is_prefix isn't
966 void replace_prefix(internal_type& w, internal_type const& p) const;
967
969 // Kambites - complement helpers - private
971
972 // Returns j among the complements of i such that [first,
973 // last) is a prefix of X_jY_jZ_j, and UNDEFINED otherwise.
974 [[nodiscard]] size_t
975 prefix_of_complement(size_t i,
976 internal_type_iterator const& first,
977 internal_type_iterator const& last) const;
978
979 // Helper for the above
980 [[nodiscard]] size_t prefix_of_complement(size_t i,
981 internal_type const& w) const {
982 return prefix_of_complement(i, w.cbegin(), w.cend());
983 }
984
985 // Returns the index j of a complement of X_iY_iZ_i such that X_jY_j is
986 // a prefix of w. Otherwise, UNDEFINED is returned.
987 [[nodiscard]] size_t complementary_XY_prefix(size_t i,
988 internal_type const& w) const;
989
990 // Returns j such that w is Z_j-active for some Z_j in the
991 // complements of Z_i. Otherwise it returns UNDEFINED.
992 //
993 // See also: p_active.
994 [[nodiscard]] size_t Z_active_complement(size_t i,
995 internal_type const& w) const;
996
997 // Returns j such that w is Z_j-active for some Z_j in the
998 // proper (j != i) complements of Z_i. Otherwise it returns UNDEFINED.
999 [[nodiscard]] size_t
1000 Z_active_proper_complement(size_t i, internal_type const& w) const;
1001
1002 [[nodiscard]] size_t
1003 Z_active_proper_complement(size_t i,
1004 internal_type_iterator const& first,
1005 internal_type_iterator const& last) const;
1006
1008 // Kambites - static functions - private
1010
1011 [[nodiscard]] static size_t complementary_relation_word(size_t i) {
1012 return (i % 2 == 0 ? i + 1 : i - 1);
1013 }
1014
1015 template <typename native_word_type>
1016 static void pop_front(native_word_type& x) {
1017 x.erase(x.begin());
1018 }
1019
1020 static void pop_front(detail::MultiStringView& x) {
1021 x.pop_front();
1022 }
1023
1024 template <typename Iterator>
1025 static void append(std::string& w, Iterator first, Iterator last) {
1026 w.append(first, last);
1027 }
1028
1029 template <typename Iterator>
1030 static void append(detail::MultiStringView& w,
1031 Iterator first,
1032 Iterator last) {
1033 w.append(first, last);
1034 }
1035
1036 template <typename Iterator>
1037 static void append(word_type& w, Iterator first, Iterator last) {
1038 w.insert(w.end(), first, last);
1039 }
1040
1042 // Kambites - main functions - private
1044
1045 // copies u, v, and/or p if they are an rrvalue ref or a const ref.
1046
1047 // Implementation of the function of the same name in: Kambites, M.
1048 // (2009). Small overlap monoids. I. The word problem. J. Algebra,
1049 // 321(8), 2187–2205.
1050 //
1051 // Returns true if u and v represent the same element of the fp
1052 // semigroup represented by this, and p is a possible prefix of u, and
1053 // v.
1054 //
1055 // Parameters are given by value because they are modified by wp_prefix,
1056 // and it was too difficult to untangle the different cases (when u, v
1057 // are equal, not equal, references, rvalue references etc). It's
1058 // possible that it could be modified to only copy when necessary, but
1059 // this doesn't seem worth it at present.
1060 [[nodiscard]] bool wp_prefix(internal_type u,
1061 internal_type v,
1062 internal_type p) const;
1063
1064 // Implementational detail
1065 // Not noexcept because nothing else is and lots allocations
1066 void normal_form_inner(size_t& r, internal_type& v, internal_type& w) const;
1067
1069 // Runner - pure virtual member functions - private
1071
1072 void run_impl() override;
1073
1074 bool finished_impl() const override {
1075 return _have_class;
1076 }
1077 }; // class Kambites
1078
1080 //
1087 template <typename Word>
1088 Kambites(congruence_kind, Presentation<Word> const&) -> Kambites<Word>;
1089
1091 //
1098 template <typename Word>
1100
1102 //
1109 template <typename Word>
1110 Kambites(Kambites<Word> const&) -> Kambites<Word>;
1111
1113 //
1120 template <typename Word>
1121 Kambites(Kambites<Word>&&) -> Kambites<Word>;
1122
1138 template <typename Word>
1139 std::string to_human_readable_repr(Kambites<Word> const& k);
1140
1141} // namespace libsemigroups
1142
1143#include "kambites-class.tpp"
1144
1145#endif // LIBSEMIGROUPS_KAMBITES_CLASS_HPP_
T append(T... args)
For an implementation of presentations for semigroups or monoids.
Definition presentation.hpp:102
void run()
Run until finished.
bool finished() const
Check if run has been run to completion or not.
For an implementation of Ukkonen's algorithm.
Definition ukkonen.hpp:81
std::string to_human_readable_repr(AhoCorasick const &ac)
Return a string representation.
PositiveInfinity const POSITIVE_INFINITY
Value for positive infinity.
size_t small_overlap_class()
Get the small overlap class.
auto const & ukkonen() noexcept
Definition kambites-class.hpp:759
Kambites(congruence_kind, Presentation< Word > const &) -> Kambites< Word >
Deduction guide.
Kambites & init()
Re-initialize a Kambites instance.
Kambites()
Default constructor.
Kambites & operator=(Kambites const &)
Default copy assignment operator.
void throw_if_letter_not_in_alphabet(Iterator1 first, Iterator2 last) const
Throws if any letter in a range is out of bounds.
Definition kambites-class.hpp:787
void throw_if_not_C4()
Throws if small_overlap_class isn't at least .
bool success() const override
Check if the small overlap class has been computed and is at least 4.
Definition kambites-class.hpp:827
OutputIterator reduce_no_checks(OutputIterator d_first, InputIterator1 first, InputIterator2 last)
Reduce a word with no checks.
Definition kambites-class.hpp:657
uint64_t number_of_classes()
Compute the number of classes in the congruence.
Definition kambites-class.hpp:435
bool contains_no_checks(Iterator1 first1, Iterator2 last1, Iterator3 first2, Iterator4 last2)
Check containment of a pair of words via iterators.
Definition kambites-class.hpp:534
Kambites & add_generating_pair_no_checks(Iterator1 first1, Iterator2 last1, Iterator3 first2, Iterator4 last2)
Add generating pair via iterators.
OutputIterator reduce_no_run_no_checks(OutputIterator d_first, Iterator1 first, Iterator2 last) const
Reduce a word with no computation of the small_overlap_class or checks.
tril currently_contains(Iterator1 first1, Iterator2 last1, Iterator3 first2, Iterator4 last2) const
Check containment of a pair of words via iterators.
OutputIterator reduce(OutputIterator d_first, InputIterator1 first, InputIterator2 last)
Reduce a word.
Definition kambites-class.hpp:688
size_t number_of_generating_pairs() const noexcept
std::vector< native_word_type > const & generating_pairs() const noexcept
Get the generating pairs of the congruence.
Definition kambites-class.hpp:361
tril currently_contains_no_checks(Iterator1 first1, Iterator2 last1, Iterator3 first2, Iterator4 last2) const
Check containment of a pair of words via iterators.
Presentation< native_word_type > const & presentation() const noexcept
Get the presentation used to define a Kambites instance (if any).
Definition kambites-class.hpp:343
bool contains(Iterator1 first1, Iterator2 last1, Iterator3 first2, Iterator4 last2)
Check containment of a pair of words via iterators.
congruence_kind kind() const noexcept
The kind of the congruence (1- or 2-sided).
OutputIterator reduce_no_run(OutputIterator d_first, Iterator1 first, Iterator2 last) const
Reduce a word with no computation of the small_overlap_class.
Definition kambites-class.hpp:626
Kambites & add_generating_pair(Iterator1 first1, Iterator2 last1, Iterator3 first2, Iterator4 last2)
Add generating pair via iterators.
Definition kambites-class.hpp:405
std::conditional_t< std::is_same_v< Word, detail::MultiStringView >, std::string, Word > native_word_type
Definition kambites-class.hpp:164
std::vector< letter_type > word_type
Type for a word over the generators of a semigroup.
Definition types.hpp:101
tril
Enum to indicate true, false or not currently knowable.
Definition types.hpp:56
congruence_kind
Enum to indicate the sided-ness of a congruence.
Definition types.hpp:69
T move(T... args)
Namespace for everything in the libsemigroups library.
Definition action.hpp:44