libsemigroups  v3.3.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 "types.hpp" // for tril, congruence_kind
49#include "ukkonen.hpp" // for maximal_piece_prefix_no...
50#include "word-range.hpp" // for operator+=, operator+
51
52#include "detail/cong-common-class.hpp" // for detail::CongruenceCommon
53#include "detail/fmt.hpp" // for format
54#include "detail/multi-view.hpp" // for MultiView, is_prefix
55#include "detail/string.hpp" // for is_prefix, maximum_comm...
56#include "detail/uf.hpp" // for Duf
57
58namespace libsemigroups {
59
74
83
99
110
124
148 // TODO(1) example
149 template <typename Word = detail::MultiView<std::string>>
150 class Kambites : public detail::CongruenceCommon {
151 public:
153 // Kambites - aliases - public
155
163 using native_word_type = std::conditional_t<
164 std::is_same_v<Word, detail::MultiView<std::string>>,
166 Word>;
167
168 private:
169 using internal_type = Word;
170
172 // Kambites - inner classes - private
174
175 // Data structure for caching the regularly accessed parts of the
176 // relation words.
177 struct RelationWords;
178
179 // Data structure for caching the complements of each relation word.
180 //
181 // We say that a relation word u' is a *complement* of a relation word u
182 // if there are relation words u = r_1,r_2, ..., r_n = u'$ such that
183 // either (r_i,r_{i+1}) \in R or (r_{i+1},r_i) \in R for $1 \leq i \leq
184 // n$. We say that $u'$ is a *proper complement* of the relation word u
185 // if it is a complement of u and u is not equal to u'.
186 class Complements;
187
189 // Kambites - data members - private
191
192 mutable size_t _class;
193 mutable Complements _complements;
194 mutable bool _have_class;
195 mutable std::vector<RelationWords> _XYZ_data;
196 mutable native_word_type _tmp_value1, _tmp_value2;
197
198 std::vector<native_word_type> _generating_pairs;
199 Presentation<native_word_type> _presentation;
200 Ukkonen _suffix_tree;
201
202 using internal_type_iterator = typename internal_type::const_iterator;
203
204 void throw_if_1_sided(congruence_kind knd) const;
205
206 public:
208 // Kambites - Constructors, destructor, initialisation - public
210
218
231
238
245
252
259
260 ~Kambites();
261
283 // call the rval ref constructor
285
310 // call the rval ref init
311 return init(knd, Presentation<native_word_type>(p));
312 }
313
322
328
342 [[nodiscard]] Presentation<native_word_type> const&
343 presentation() const noexcept {
344 return _presentation;
345 }
346
360 [[nodiscard]] std::vector<native_word_type> const&
361 generating_pairs() const noexcept {
362 return _generating_pairs;
363 }
364
366 // Interface requirements - add_pairs
368
381 template <typename Iterator1,
382 typename Iterator2,
383 typename Iterator3,
384 typename Iterator4>
386 Iterator2 last1,
387 Iterator3 first2,
388 Iterator4 last2);
401 template <typename Iterator1,
402 typename Iterator2,
403 typename Iterator3,
404 typename Iterator4>
405 Kambites& add_generating_pair(Iterator1 first1,
406 Iterator2 last1,
407 Iterator3 first2,
408 Iterator4 last2) {
409 return detail::CongruenceCommon::add_generating_pair<Kambites>(
410 first1, last1, first2, last2);
411 }
412
414 // Interface requirements - number_of_classes
416
434 // Not noexcept, throws
435 [[nodiscard]] uint64_t number_of_classes() {
437 return POSITIVE_INFINITY;
438 }
439
441 // Interface requirements - contains
443
473 template <typename Iterator1,
474 typename Iterator2,
475 typename Iterator3,
476 typename Iterator4>
477 [[nodiscard]] tril currently_contains_no_checks(Iterator1 first1,
478 Iterator2 last1,
479 Iterator3 first2,
480 Iterator4 last2) const;
481
506 template <typename Iterator1,
507 typename Iterator2,
508 typename Iterator3,
509 typename Iterator4>
510 [[nodiscard]] tril currently_contains(Iterator1 first1,
511 Iterator2 last1,
512 Iterator3 first2,
513 Iterator4 last2) const;
530 template <typename Iterator1,
531 typename Iterator2,
532 typename Iterator3,
533 typename Iterator4>
534 [[nodiscard]] bool contains_no_checks(Iterator1 first1,
535 Iterator2 last1,
536 Iterator3 first2,
537 Iterator4 last2) {
538 return detail::CongruenceCommon::contains_no_checks<Kambites>(
539 first1, last1, first2, last2);
540 }
541
558 template <typename Iterator1,
559 typename Iterator2,
560 typename Iterator3,
561 typename Iterator4>
562 [[nodiscard]] bool contains(Iterator1 first1,
563 Iterator2 last1,
564 Iterator3 first2,
565 Iterator4 last2);
566
568 // Interface requirements - reduce
570
571 private:
572 void normal_form_no_checks(native_word_type& result,
573 native_word_type const& w) const;
574
575 public:
598 template <typename OutputIterator, typename Iterator1, typename Iterator2>
599 OutputIterator reduce_no_run_no_checks(OutputIterator d_first,
600 Iterator1 first,
601 Iterator2 last) const;
602
625 template <typename OutputIterator, typename Iterator1, typename Iterator2>
626 OutputIterator reduce_no_run(OutputIterator d_first,
627 Iterator1 first,
628 Iterator2 last) const {
629 return detail::CongruenceCommon::reduce_no_run<Kambites>(
630 d_first, first, last);
631 }
632
654 template <typename OutputIterator,
655 typename InputIterator1,
656 typename InputIterator2>
657 OutputIterator reduce_no_checks(OutputIterator d_first,
658 InputIterator1 first,
659 InputIterator2 last) {
660 return detail::CongruenceCommon::reduce_no_checks<Kambites>(
661 d_first, first, last);
662 }
663
685 template <typename OutputIterator,
686 typename InputIterator1,
687 typename InputIterator2>
688 OutputIterator reduce(OutputIterator d_first,
689 InputIterator1 first,
690 InputIterator2 last) {
691 return detail::CongruenceCommon::reduce<Kambites>(d_first, first, last);
692 }
693
695 // Kambites - member functions - public
697
730 // not noexcept because number_of_pieces_no_checks isn't
731 [[nodiscard]] size_t small_overlap_class();
732
744 [[nodiscard]] size_t small_overlap_class() const noexcept;
745
759 [[nodiscard]] auto const& ukkonen() noexcept {
760 run();
761 return _suffix_tree;
762 }
763
765 // Kambites - validation functions - public
767
786 template <typename Iterator1, typename Iterator2>
788 Iterator2 last) const {
789 _presentation.throw_if_letter_not_in_alphabet(first, last);
790 }
791
802
813 void throw_if_not_C4() const;
814
827 [[nodiscard]] bool success() const override {
828 return finished() && _class >= 4;
829 }
830
831#ifdef LIBSEMIGROUPS_PARSED_BY_DOXYGEN
846 [[nodiscard]] congruence_kind kind() const noexcept;
847
863 [[nodiscard]] size_t number_of_generating_pairs() const noexcept;
864#endif
865
866 private:
868 // Kambites - XYZ functions - private
870
871 void really_init_XYZ_data(size_t i) const;
872
873 // init_XYZ_data is split into the function that really does the work
874 // (really_init_XYZ_data) which is called once, and init_XYZ_data which
875 // can be called very often.
876 // Intentionally inlined, do not move to tpp file.
877#if defined(__GNUC__) && !defined(__clang__)
878#pragma GCC diagnostic push
879#pragma GCC diagnostic ignored "-Winline"
880#endif
881 inline void init_XYZ_data(size_t i) const {
882 LIBSEMIGROUPS_ASSERT(i < _presentation.rules.size());
883 if (_XYZ_data.empty()) {
884 _XYZ_data.resize(_presentation.rules.size());
885 }
886 if (!_XYZ_data[i].is_initialized) {
887 really_init_XYZ_data(i);
888 }
889 }
890#if defined(__GNUC__) && !defined(__clang__)
891#pragma GCC diagnostic pop
892#endif
893 [[nodiscard]] internal_type const& X(size_t i) const;
894 [[nodiscard]] internal_type const& Y(size_t i) const;
895 [[nodiscard]] internal_type const& Z(size_t i) const;
896 [[nodiscard]] internal_type const& XY(size_t i) const;
897 [[nodiscard]] internal_type const& YZ(size_t i) const;
898 [[nodiscard]] internal_type const& XYZ(size_t i) const;
899
901 // Kambites - helpers - private
903
904 // Returns the index of the relation word r_i = X_iY_iZ_i if [first,
905 // last) = X_iY_iw for some w. If no such exists, then UNDEFINED is
906 // returned.
907 // Not noexcept because is_prefix isn't
908 [[nodiscard]] size_t
909 relation_prefix(internal_type_iterator const& first,
910 internal_type_iterator const& last) const;
911
912 // Returns the index of the relation word r_i = X_iY_iZ_i such that
913 // X_iY_i is a clean overlap prefix of <s>, i.e. <s> = X_iY_iw for some
914 // w, and there's no factor of <s> of the form X_jY_j starting before
915 // the beginning of Y_i. If no such exists, UNDEFINED is returned. Not
916 // noexcept because relation_prefix isn't
917 [[nodiscard]] inline size_t
918 clean_overlap_prefix(internal_type const& s) const {
919 return clean_overlap_prefix(s.cbegin(), s.cend());
920 }
921
922 [[nodiscard]] size_t
923 clean_overlap_prefix(internal_type_iterator const& first,
924 internal_type_iterator const& last) const;
925
926 // Calls clean_overlap_prefix on every suffix of <s> starting within the
927 // range [0, n), and returns a std::pair p where:
928 //
929 // * <p.first> is the starting index of the suffix of <s> that contains
930 // a clean_overlap_prefix.
931 //
932 // * <p.second> is the index of the relation word that's a clear overlap
933 // prefix of a suffix of <s>
934 [[nodiscard]] std::pair<size_t, size_t>
935 clean_overlap_prefix_mod(internal_type const& s, size_t n) const;
936
937 // If x + [first, last) = aX_sY_sw words a, w and for some index s,
938 // where X_s = X_s'X_s'', x = aX_s', and [first, last) = X_s''Y_sw, then
939 // this function returns a tuple <Word> where:
940 //
941 // 1. std::get<0>(Word) is the index <s>
942 //
943 // 2. std::get<1>(Word) is an iterator <it1> into <x>, such that
944 // [it1, x.cend()) = X_s'
945 //
946 // 3. std::get<2>(Word) is an iterator <it2> into [first, last), such
947 // that [it2, last) = w
948 //
949 // If no such relation word exists, then
950 //
951 // (UNDEFINED, first.cend(), second.cend())
952 //
953 // is returned.
954 //
955 // Not noexcept because relation_prefix isn't
956 [[nodiscard]] std::
957 tuple<size_t, internal_type_iterator, internal_type_iterator>
958 p_active(internal_type const& x,
959 internal_type_iterator const& first,
960 internal_type_iterator const& last) const;
961
962 // Returns a word equal to w in this, starting with the piece p, no
963 // checks are performed. Used in the normal_form function. Not noexcept
964 // because detail::is_prefix isn't
965 void replace_prefix(internal_type& w, internal_type const& p) const;
966
968 // Kambites - complement helpers - private
970
971 // Returns j among the complements of i such that [first,
972 // last) is a prefix of X_jY_jZ_j, and UNDEFINED otherwise.
973 [[nodiscard]] size_t
974 prefix_of_complement(size_t i,
975 internal_type_iterator const& first,
976 internal_type_iterator const& last) const;
977
978 // Helper for the above
979 [[nodiscard]] size_t prefix_of_complement(size_t i,
980 internal_type const& w) const {
981 return prefix_of_complement(i, w.cbegin(), w.cend());
982 }
983
984 // Returns the index j of a complement of X_iY_iZ_i such that X_jY_j is
985 // a prefix of w. Otherwise, UNDEFINED is returned.
986 [[nodiscard]] size_t complementary_XY_prefix(size_t i,
987 internal_type const& w) const;
988
989 // Returns j such that w is Z_j-active for some Z_j in the
990 // complements of Z_i. Otherwise it returns UNDEFINED.
991 //
992 // See also: p_active.
993 [[nodiscard]] size_t Z_active_complement(size_t i,
994 internal_type const& w) const;
995
996 // Returns j such that w is Z_j-active for some Z_j in the
997 // proper (j != i) complements of Z_i. Otherwise it returns UNDEFINED.
998 [[nodiscard]] size_t
999 Z_active_proper_complement(size_t i, internal_type const& w) const;
1000
1001 [[nodiscard]] size_t
1002 Z_active_proper_complement(size_t i,
1003 internal_type_iterator const& first,
1004 internal_type_iterator const& last) const;
1005
1007 // Kambites - static functions - private
1009
1010 [[nodiscard]] static size_t complementary_relation_word(size_t i) {
1011 return (i % 2 == 0 ? i + 1 : i - 1);
1012 }
1013
1014 template <typename native_word_type>
1015 static void pop_front(native_word_type& x) {
1016 x.erase(x.begin());
1017 }
1018
1019 static void pop_front(detail::MultiView<std::string>& x) {
1020 x.pop_front();
1021 }
1022
1023 template <typename Iterator>
1024 static void append(std::string& w, Iterator first, Iterator last) {
1025 w.append(first, last);
1026 }
1027
1028 template <typename Iterator>
1029 static void append(detail::MultiView<std::string>& w,
1030 Iterator first,
1031 Iterator last) {
1032 w.append(first, last);
1033 }
1034
1035 template <typename Iterator>
1036 static void append(word_type& w, Iterator first, Iterator last) {
1037 w.insert(w.end(), first, last);
1038 }
1039
1041 // Kambites - main functions - private
1043
1044 // copies u, v, and/or p if they are an rrvalue ref or a const ref.
1045
1046 // Implementation of the function of the same name in: Kambites, M.
1047 // (2009). Small overlap monoids. I. The word problem. J. Algebra,
1048 // 321(8), 2187–2205.
1049 //
1050 // Returns true if u and v represent the same element of the fp
1051 // semigroup represented by this, and p is a possible prefix of u, and
1052 // v.
1053 //
1054 // Parameters are given by value because they are modified by wp_prefix,
1055 // and it was too difficult to untangle the different cases (when u, v
1056 // are equal, not equal, references, rvalue references etc). It's
1057 // possible that it could be modified to only copy when necessary, but
1058 // this doesn't seem worth it at present.
1059 [[nodiscard]] bool wp_prefix(internal_type u,
1060 internal_type v,
1061 internal_type p) const;
1062
1063 // Implementational detail
1064 // Not noexcept because nothing else is and lots allocations
1065 void normal_form_inner(size_t& r, internal_type& v, internal_type& w) const;
1066
1068 // Runner - pure virtual member functions - private
1070
1071 void run_impl() override;
1072
1073 bool finished_impl() const override {
1074 return _have_class;
1075 }
1076 }; // class Kambites
1077
1079 //
1086 template <typename Word>
1087 Kambites(congruence_kind, Presentation<Word> const&) -> Kambites<Word>;
1088
1090 //
1097 template <typename Word>
1099
1101 //
1108 template <typename Word>
1109 Kambites(Kambites<Word> const&) -> Kambites<Word>;
1110
1112 //
1119 template <typename Word>
1120 Kambites(Kambites<Word>&&) -> Kambites<Word>;
1121
1137 template <typename Word>
1138 std::string to_human_readable_repr(Kambites<Word> const& k);
1139
1140} // namespace libsemigroups
1141
1142#include "kambites-class.tpp"
1143
1144#endif // LIBSEMIGROUPS_KAMBITES_CLASS_HPP_
T append(T... args)
For an implementation of presentations for semigroups or monoids.
Definition presentation.hpp:103
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:90
std::string to_human_readable_repr(Action< Element, Point, Func, Traits, LeftOrRight > const &action)
Return a human readable representation of an Action object.
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::MultiView< std::string > >, std::string, Word > native_word_type
Definition kambites-class.hpp:163
std::vector< letter_type > word_type
Type for a word over the generators of a semigroup.
Definition types.hpp:99
tril
Enum to indicate true, false or not currently knowable.
Definition types.hpp:54
congruence_kind
Enum to indicate the sided-ness of a congruence.
Definition types.hpp:67
T move(T... args)
Namespace for everything in the libsemigroups library.
Definition action.hpp:44