diff --git a/Makefile.am b/Makefile.am
index bff551728..e11ec9622 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -124,6 +124,7 @@ detailinclude_HEADERS += include/libsemigroups/detail/kambites-nf.hpp
detailinclude_HEADERS += include/libsemigroups/detail/kbe.hpp
detailinclude_HEADERS += include/libsemigroups/detail/kbe.tpp
detailinclude_HEADERS += include/libsemigroups/detail/ke.hpp
+detailinclude_HEADERS += include/libsemigroups/detail/knuth-bendix-nf.hpp
detailinclude_HEADERS += include/libsemigroups/detail/multi-string-view.hpp
detailinclude_HEADERS += include/libsemigroups/detail/node-managed-graph.hpp
detailinclude_HEADERS += include/libsemigroups/detail/node-managed-graph.tpp
diff --git a/docs/DoxygenLayout.xml b/docs/DoxygenLayout.xml
index 5ff21d7f8..5f8957c64 100644
--- a/docs/DoxygenLayout.xml
+++ b/docs/DoxygenLayout.xml
@@ -141,17 +141,8 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
diff --git a/include/libsemigroups/cong-intf.hpp b/include/libsemigroups/cong-intf.hpp
index a123942c5..8ed2259c8 100644
--- a/include/libsemigroups/cong-intf.hpp
+++ b/include/libsemigroups/cong-intf.hpp
@@ -198,6 +198,7 @@ namespace libsemigroups {
Iterator2 last1,
Iterator3 first2,
Iterator4 last2) {
+ LIBSEMIGROUPS_ASSERT(!started());
_generating_pairs.emplace_back(first1, last1);
_generating_pairs.emplace_back(first2, last2);
return static_cast(*this);
@@ -1127,7 +1128,7 @@ namespace libsemigroups {
typename Range,
typename OutputWord = std::decay_t,
typename = std::enable_if_t>>
- [[nodiscard]] std::vector> partition(Subclass& kb,
+ [[nodiscard]] std::vector> partition(Subclass& ci,
Range r) {
// Congruence + ToddCoxeter have their own overloads for this
static_assert(!std::is_same_v
@@ -1145,8 +1146,8 @@ namespace libsemigroups {
while (!r.at_end()) {
auto next = r.get();
- if (kb.presentation().contains_empty_word() || !next.empty()) {
- auto next_nf = congruence_interface::reduce(kb, next);
+ if (ci.presentation().contains_empty_word() || !next.empty()) {
+ auto next_nf = congruence_interface::reduce(ci, next);
auto [it, inserted] = map.emplace(next_nf, index);
if (inserted) {
result.emplace_back();
diff --git a/include/libsemigroups/detail/knuth-bendix-nf.hpp b/include/libsemigroups/detail/knuth-bendix-nf.hpp
new file mode 100644
index 000000000..94cf8cbdc
--- /dev/null
+++ b/include/libsemigroups/detail/knuth-bendix-nf.hpp
@@ -0,0 +1,82 @@
+//
+// libsemigroups - C++ library for semigroups and monoids
+// Copyright (C) 2024 James D. Mitchell
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+
+// This file contains an implementation of a range object for producing normal
+// forms for a KnuthBendix object.
+
+#ifndef LIBSEMIGROUPS_DETAIL_KNUTH_BENDIX_NF_HPP_
+#define LIBSEMIGROUPS_DETAIL_KNUTH_BENDIX_NF_HPP_
+
+namespace detail {
+
+ template
+ class KnuthBendixNormalFormRange : public Paths {
+ using Paths_ = Paths;
+
+ mutable Word _current;
+ KnuthBendix* _kb;
+
+ public:
+ using size_type = typename Paths_::size_type;
+ using output_type = Word const&;
+
+ explicit KnuthBendixNormalFormRange(
+ KnuthBendix& kb)
+ : Paths(kb.gilman_graph()), _current(), _kb(&kb) {
+ // It's possible that the gilman graph is empty, so the call to
+ // source_no_checks(0) is technically invalid, but nothing goes wrong,
+ // so we just go with it. This is slightly smelly.
+ Paths_::source_no_checks(0);
+ if (!kb.presentation().contains_empty_word()) {
+ Paths_::next();
+ }
+ }
+
+ output_type get() const {
+ word_type const& w = Paths_::get();
+ _current.clear();
+ for (auto c : w) {
+ _current.push_back(_kb->presentation().letter_no_checks(c));
+ }
+ return _current;
+ }
+
+ KnuthBendixNormalFormRange& min(size_type val) noexcept {
+ // TODO(0) should not allow 0 unless contains_empty_word
+ Paths_::min(val);
+ return *this;
+ }
+
+ KnuthBendixNormalFormRange& max(size_type val) noexcept {
+ Paths_::max(val);
+ return *this;
+ }
+
+ using Paths_::at_end;
+ using Paths_::count;
+ using Paths_::max;
+ using Paths_::min;
+ using Paths_::next;
+ using Paths_::size_hint;
+
+ static constexpr bool is_finite = true; // this isn't always true!
+ static constexpr bool is_idempotent = true;
+ }; // class KnuthBendixNormalFormRange
+
+} // namespace detail
+#endif // LIBSEMIGROUPS_DETAIL_KNUTH_BENDIX_NF_HPP_
diff --git a/include/libsemigroups/knuth-bendix.hpp b/include/libsemigroups/knuth-bendix.hpp
index 2b5e7bd5e..74e4ee48f 100644
--- a/include/libsemigroups/knuth-bendix.hpp
+++ b/include/libsemigroups/knuth-bendix.hpp
@@ -22,8 +22,6 @@
// TODO(1)
// * noexcept
// * separate rule container from Rules
-// TODO(0)
-// * fix doc
// * nodiscard
#ifndef LIBSEMIGROUPS_KNUTH_BENDIX_HPP_
@@ -73,21 +71,24 @@ namespace libsemigroups {
class KBE;
} // namespace detail
- //! \defgroup knuth_bendix_class_group The Knuth-Bendix class
+ //! \defgroup knuth_bendix_group Knuth-Bendix
//!
- //! This page TODO
+ //! This page contains links to the documentation related to the
+ //! implementation of the Knuth-Bendix algorithm in ``libsemigroups``.
- // TODO(0) update the description
+ //! \ingroup knuth_bendix_group
+ //!
+ //! \brief Class containing an implementation of the Knuth-Bendix Algorithm.
+ //!
//! Defined in \c knuth-bendix.hpp.
//!
//! On this page we describe the functionality relating to the Knuth-Bendix
- //! algorithm for semigroups and monoids that is available in
- //! \c libsemigroups. This page contains details of the member functions
- //! of the class KnuthBendix.
+ //! algorithm for semigroups and monoids in \c libsemigroups. This page
+ //! contains details of the member functions of the class KnuthBendix.
//!
- //! This class is used to represent a
- //! [string rewriting system](https://w.wiki/9Re)
- //! defining a finitely presented monoid or semigroup.
+ //! This class is used to represent a [string rewriting
+ //! system](https://w.wiki/9Re) defining a 1- or 2-sided congruence on a
+ //! finitely presented monoid or semigroup.
//!
//! \par Example
//! \code
@@ -114,15 +115,6 @@ namespace libsemigroups {
// defined in detail/kbe.hpp
friend class ::libsemigroups::detail::KBE;
- ////////////////////////////////////////////////////////////////////////
- // KnuthBendix - typedefs/aliases - private
- ////////////////////////////////////////////////////////////////////////
-
- // using external_string_type = std::string;
- // using internal_string_type = std::string;
- // using external_char_type = char;
- // using internal_char_type = char;
-
////////////////////////////////////////////////////////////////////////
// KnuthBendix - nested subclasses - private
////////////////////////////////////////////////////////////////////////
@@ -146,18 +138,43 @@ namespace libsemigroups {
// Interface requirements - native-types
////////////////////////////////////////////////////////////////////////
- using native_letter_type = char;
- using native_word_type = std::string;
+ //! Type of the rules in the system.
+ using rule_type = std::pair;
+
+ //! \brief Type of the letters in the relations of the presentation stored
+ //! in a \ref KnuthBendix instance.
+ //!
+ //! A \ref KnuthBendix instance can be constructed or initialised from a
+ //! presentation of arbitrary types of letters and words. Internally the
+ //! letters are converted to \ref native_letter_type.
+ using native_letter_type = char;
+
+ //! \brief Type of the words in the relations of the presentation stored in
+ //! a \ref KnuthBendix instance.
+ //!
+ //! A \ref KnuthBendix instance can be constructed or initialised from a
+ //! presentation with arbitrary types of letters and words. Internally the
+ //! words are converted to \ref native_word_type.
+ using native_word_type = std::string;
+
+ //! \brief Type of the presentation stored in a \ref KnuthBendix instance.
+ //!
+ //! A \ref KnuthBendix instance can be constructed or initialised from a
+ //! presentation of arbitrary types of letters and words. Internally the
+ //! presentation is stored as a \ref native_presentation_type.
using native_presentation_type = Presentation;
//////////////////////////////////////////////////////////////////////////
// KnuthBendix - types - public
//////////////////////////////////////////////////////////////////////////
- //! This type contains various enums for specifying certain options to a
- //! KnuthBendix instance.
+ //! \brief Struct containing various options that can be used to control the
+ //! behaviour of Knuth-Bendix.
+ //!
+ //! This struct containing various options that can be used to control the
+ //! behaviour of Knuth-Bendix.
struct options {
- //! Values for specifying how to measure the length of an overlap.
+ //! \brief Values for specifying how to measure the length of an overlap.
//!
//! The values in this enum determine how a KnuthBendix instance
//! measures the length \f$d(AB, BC)\f$ of the overlap of two words
@@ -184,7 +201,7 @@ namespace libsemigroups {
Settings& operator=(Settings const&) noexcept = default;
Settings& operator=(Settings&&) noexcept = default;
- size_t batch_size;
+ size_t max_pending_rules;
size_t check_confluence_interval;
size_t max_overlap;
size_t max_rules;
@@ -232,21 +249,15 @@ namespace libsemigroups {
//! Constructs a KnuthBendix instance with no rules, and the short-lex
//! reduction ordering.
//!
- //! \param knd The kind of congruence to be used. Either \c left, \c right
- //! or \c twosided.
- //!
- //! \complexity
- //! Constant.
+ //! This function default constructs an uninitialised \ref
+ //! KnuthBendix instance.
explicit KnuthBendix();
//! \brief Remove the presentation and rewriter data
//!
//! This function clears the rewriter, presentation, settings and stats from
//! the KnuthBendix object, putting it back into the state it would be in if
- //! it was newly constructed.
- //!
- //! \param knd The kind of congruence to be used. Either \c left, \c right
- //! or \c twosided.
+ //! it was newly default constructed.
//!
//! \returns
//! A reference to \c this.
@@ -256,52 +267,114 @@ namespace libsemigroups {
//!
//! Copy constructor.
//!
- //! \param copy the KnuthBendix instance to copy.
+ //! \param that the KnuthBendix instance to copy.
//!
//! \complexity
//! \f$O(n)\f$ where \f$n\f$ is the sum of the lengths of the words in
- //! rules of \p copy.
+ //! rules of \p that.
KnuthBendix(KnuthBendix const& that);
- // TODO(0) doc
+ //! \brief Move constructor.
+ //!
+ //! Move constructor.
KnuthBendix(KnuthBendix&&);
- // TODO(0) doc
+ //! \brief Copy assignment operator.
+ //!
+ //! Copy assignment operator.
KnuthBendix& operator=(KnuthBendix const&);
- // TODO(0) doc
+ //! \brief Move assignment operator.
+ //!
+ //! Move assignment operator.
KnuthBendix& operator=(KnuthBendix&&);
~KnuthBendix();
- // TODO(0) doc
+ //! \brief Construct from \ref congruence_kind and Presentation.
+ //!
+ //! This function constructs a \ref KnuthBendix instance representing a
+ //! congruence of kind \p knd over the semigroup or monoid defined by the
+ //! presentation \p p.
+ //!
+ //! \param knd the kind (onesided or twosided) of the congruence.
+ //! \param p the presentation.
+ //!
+ //! \throws LibsemigroupsException if \p p is not valid.
KnuthBendix(congruence_kind knd, Presentation const& p);
- // TODO(0) doc
+ //! \brief Re-initialize a \ref KnuthBendix instance.
+ //!
+ //! This function puts a \ref KnuthBendix instance back into the state that
+ //! it would have been in if it had just been newly constructed from \p knd
+ //! and \p p.
+ //!
+ //! \param knd the kind (onesided or twosided) of the congruence.
+ //! \param p the presentation.
+ //!
+ //! \returns A reference to `*this`.
+ //!
+ //! \throws LibsemigroupsException if \p p is not valid.
KnuthBendix& init(congruence_kind knd, Presentation const& p);
- // TODO(0) doc
+ //! \copydoc KnuthBendix(congruence_kind, Presentation const&)
KnuthBendix(congruence_kind knd, Presentation&& p);
- // TODO(0) doc
+ //! \copydoc init(congruence_kind, Presentation const&)
KnuthBendix& init(congruence_kind knd, Presentation&& p);
- // TODO(0) doc
+ //! \brief Construct from \ref congruence_kind and Presentation.
+ //!
+ //! This function constructs a \ref KnuthBendix instance representing a
+ //! congruence of kind \p knd over the semigroup or monoid defined by the
+ //! presentation \p p. The type of the words in \p p can be anything, but
+ //! will be converted in to \ref native_word_type. This means that if the
+ //! input presentation uses \ref word_type, for example, as the word type,
+ //! then this presentation is converted into a \ref
+ //! native_presentation_type. This converted presentation can be recovered
+ //! using \ref presentation.
+ //!
+ //! \tparam Word the type of the words in the presentation \p p.
+ //! \param knd the kind (onesided or twosided) of the congruence.
+ //! \param p the presentation.
+ //!
+ //! \throws LibsemigroupsException if \p p is not valid.
// No rvalue ref version because we can't use it.
template
explicit KnuthBendix(congruence_kind knd, Presentation const& p)
+ // to_presentation throws in the next line if p isn't valid.
+ // The next line looks weird but we are usually taking in letter_type's
+ // and returning chars
: KnuthBendix(knd, to_presentation(p, [](auto const& x) {
return x;
})) {}
- // TODO(0) doc
+ //! \brief Re-initialize a \ref KnuthBendix
+ //! instance.
+ //!
+ //! This function re-initializes a \ref KnuthBendix instance as if it had
+ //! been newly constructed from \p knd and \p p.
+ //!
+ //! \tparam Word the type of the words in the presentation \p p.
+ //! \param knd the kind (onesided or twosided) of the congruence.
+ //! \param p the presentation.
+ //!
+ //! \returns A reference to `*this`.
+ //!
+ //! \throws LibsemigroupsException if \p p is not valid.
template
KnuthBendix& init(congruence_kind knd, Presentation const& p) {
+ // to_presentation throws in the next line if p isn't valid.
+ // The next line looks weird but we are usually taking in letter_type's
+ // and returning chars
init(knd,
to_presentation(p, [](auto const& x) { return x; }));
return *this;
}
+ // TODO(1) construct/init from kind and KnuthBendix const&, for consistency
+ // with ToddCoxeter
+
private:
void init_from_generating_pairs();
void init_from_presentation();
@@ -311,6 +384,20 @@ namespace libsemigroups {
//////////////////////////////////////////////////////////////////////////
public:
+ //! \brief Add generating pair via iterators.
+ //!
+ //! This function adds a generating pair to the congruence represented by a
+ //! \ref KnuthBendix instance.
+ //!
+ //! \cong_intf_params_contains
+ //!
+ //! \returns A reference to `*this`.
+ //!
+ //! \cong_intf_warn_assume_letters_in_bounds
+ //!
+ //! \warning It is assumed that \ref started returns \c false. Adding
+ //! generating pairs after \ref started is not permitted (but also not
+ //! checked by this function).
// NOTE THAT this is not the same as in ToddCoxeter, because the generating
// pairs contained in CongruenceInterface are word_types, and so we don't
// require any conversion here (since chars can be converted implicitly to
@@ -327,6 +414,18 @@ namespace libsemigroups {
first1, last1, first2, last2);
}
+ //! \brief Add generating pair via iterators.
+ //!
+ //! This function adds a generating pair to the congruence represented by a
+ //! \ref KnuthBendix instance.
+ //!
+ //! \cong_intf_params_contains
+ //!
+ //! \returns A reference to `*this`.
+ //!
+ //! \cong_intf_throws_if_letters_out_of_bounds
+ //!
+ //! \cong_intf_throws_if_started
template
OutputIterator reduce_no_run_no_checks(OutputIterator d_first,
InputIterator1 first,
- InputIterator2 last) const {
- // TODO(1) improve this to not require _tmp_element1
- if constexpr (std::is_same_v) {
- static_assert(std::is_same_v);
- _tmp_element1.assign(first, std::distance(first, last));
- } else {
- _tmp_element1.assign(first, last);
- }
- const_cast&>(*this).rewrite_inplace(
- _tmp_element1);
- return std::copy(
- std::begin(_tmp_element1), std::end(_tmp_element1), d_first);
- }
+ InputIterator2 last) const;
- // TODO(0) should be const
+ //! \brief Reduce a word with no enumeration.
+ //!
+ //! This function writes a reduced word equivalent to the input word
+ //! described by the iterator \p first and \p last to the output iterator \p
+ //! d_first. This function triggers no enumeration. The word output by this
+ //! function is equivalent to the input word in the congruence defined by a
+ //! \ref KnuthBendix instance. If the \ref KnuthBendix instance is \ref
+ //! finished, then the output word is a normal form for the input word. If
+ //! the \ref KnuthBendix instance is not \ref finished, then it might be
+ //! that equivalent input words produce different output words.
+ //!
+ //! \cong_intf_params_reduce
+ //!
+ //! \returns An \p OutputIterator pointing one beyond the last letter
+ //! inserted into \p d_first.
+ //!
+ //! \cong_intf_throws_if_letters_out_of_bounds
template
@@ -453,6 +627,24 @@ namespace libsemigroups {
return reduce_no_run_no_checks(d_first, first, last);
}
+ //! \brief Reduce a word with no checks.
+ //!
+ //! This function triggers a full enumeration and then writes a reduced
+ //! word equivalent to the input word described by the iterator \p first and
+ //! \p last to the output iterator \p d_first. The word output by this
+ //! function is equivalent to the input word in the congruence defined by a
+ //! \ref KnuthBendix instance. In other words, the output word is a normal
+ //! form for the input word or equivalently a canconical representative of
+ //! its congruence class.
+ //!
+ //! \cong_intf_params_reduce
+ //!
+ //! \returns An \p OutputIterator pointing one beyond the last letter
+ //! inserted into \p d_first.
+ //!
+ //! \cong_intf_warn_assume_letters_in_bounds
+ //!
+ //! \cong_intf_warn_undecidable{Knuth-Bendix}
template
@@ -463,6 +655,24 @@ namespace libsemigroups {
return reduce_no_run_no_checks(d_first, first, last);
}
+ //! \brief Reduce a word.
+ //!
+ //! This function triggers a full enumeration and then writes a reduced
+ //! word equivalent to the input word described by the iterator \p first and
+ //! \p last to the output iterator \p d_first. The word output by this
+ //! function is equivalent to the input word in the congruence defined by a
+ //! \ref KnuthBendix instance. In other words, the output word is a normal
+ //! form for the input word or equivalently a canconical representative of
+ //! its congruence class.
+ //!
+ //! \cong_intf_params_reduce
+ //!
+ //! \returns An \p OutputIterator pointing one beyond the last letter
+ //! inserted into \p d_first.
+ //!
+ //! \cong_intf_throws_if_letters_out_of_bounds
+ //!
+ //! \cong_intf_warn_undecidable{Knuth-Bendix}
template
@@ -496,14 +706,12 @@ namespace libsemigroups {
//!
//! \complexity
//! Constant.
- //!
- //! \sa \ref run and \ref process_pending_rules.
- KnuthBendix& batch_size(size_t val) {
- _settings.batch_size = val;
+ KnuthBendix& max_pending_rules(size_t val) {
+ _settings.max_pending_rules = val;
return *this;
}
- //! \brief Return the number of rules to accumulate before processing.
+ //! \brief Get the current number of rules to accumulate before processing.
//!
//! This function can be used to return the number of pending rules that
//! must accumulate before they are reduced, processed, and added to the
@@ -519,24 +727,20 @@ namespace libsemigroups {
//!
//! \complexity
//! Constant.
- //!
- //! \sa \ref run and \ref process_pending_rules.
- [[nodiscard]] size_t batch_size() const noexcept {
- return _settings.batch_size;
+ [[nodiscard]] size_t max_pending_rules() const noexcept {
+ return _settings.max_pending_rules;
}
//! \brief Set the interval at which confluence is checked.
//!
- //! The function \ref run periodically checks if
- //! the system is already confluent. This function can be used to
- //! set how frequently this happens, it is the number of new overlaps
- //! that should be considered before checking confluence. Setting this
- //! value too low can adversely affect the performance of
- //! \ref run.
+ //! The function \ref run periodically checks if the system is already
+ //! confluent. This function can be used to set how frequently this happens,
+ //! it is the number of new overlaps that should be considered before
+ //! checking confluence. Setting this value too low can adversely affect the
+ //! performance of \ref run.
//!
- //! The default value is \c 4096, and should be set to
- //! \ref LIMIT_MAX if \ref run should never
- //! check if the system is already confluent.
+ //! The default value is \c 4096, and should be set to \ref LIMIT_MAX if
+ //! \ref run should never check if the system is already confluent.
//!
//! \param val the new value of the interval.
//!
@@ -552,7 +756,7 @@ namespace libsemigroups {
return *this;
}
- //! \brief Return the interval at which confluence is checked.
+ //! \brief Get the current interval at which confluence is checked.
//!
//! The function \ref run periodically checks if
//! the system is already confluent. This function can be used to
@@ -597,7 +801,7 @@ namespace libsemigroups {
return *this;
}
- //! \brief Return the maximum length of overlaps to be considered.
+ //! \brief Get the current maximum length of overlaps to be considered.
//!
//! This function returns the maximum length of the overlap of two left hand
//! sides of rules that should be considered in \ref run.
@@ -619,11 +823,10 @@ namespace libsemigroups {
//! \brief Set the maximum number of rules.
//!
- //! This member function sets the (approximate) maximum number of rules
- //! that the system should contain. If this is number is exceeded in
- //! calls to \ref run or
- //! knuth_bendix_by_overlap_length, then they
- //! will terminate and the system may not be confluent.
+ //! This function sets the (approximate) maximum number of rules
+ //! that the system should contain. If this is number is exceeded in calls
+ //! to \ref run or knuth_bendix_by_overlap_length, then they will terminate
+ //! and the system may not be confluent.
//!
//! By default this value is \ref POSITIVE_INFINITY.
//!
@@ -641,14 +844,13 @@ namespace libsemigroups {
return *this;
}
- //! \brief Return the maximum number of rules.
+ //! \brief Get the current maximum number of rules.
//!
- //! This member function returns the (approximate) maximum number of rules
+ //! This function returns the (approximate) maximum number of rules
//! that the system should contain. If this is number is exceeded in
//! calls to \ref run or \ref knuth_bendix::by_overlap_length, then they
//! will terminate and the system may not be confluent.
//!
- //!
//! \returns
//! The maximum number of rules the system should contain, a value of type
//! \c size_t.
@@ -680,7 +882,7 @@ namespace libsemigroups {
//! \sa options::overlap.
KnuthBendix& overlap_policy(typename options::overlap val);
- //! \brief Return the overlap policy.
+ //! \brief Get the current overlap policy.
//!
//! This function returns the way that the length of an overlap of two words
//! in the system is measured.
@@ -703,37 +905,34 @@ namespace libsemigroups {
// KnuthBendix - member functions for rules and rewriting - public
//////////////////////////////////////////////////////////////////////////
- //! \brief Check if every letter of a word is in the presentation's
- //! alphabet.
+ //! \brief Throws if any letter in a range is out of bounds.
//!
- //! Check if every letter of a word is in the presentation's
- //! alphabet.
+ //! This function throws a LibsemigroupsException if any value pointed
+ //! at by an iterator in the range \p first to \p last is out of bounds
+ //! (i.e. does not belong to the alphabet of the \ref presentation used
+ //! to construct the \ref Kambites instance).
//!
- //! \throws LibsemigroupsException if there is a letter of \p w not in the
- //! presentations alphabet.
+ //! \tparam Iterator1 the type of first argument \p first.
+ //! \tparam Iterator2 the type of second argument \p last.
//!
- //! \param w word to validate.
+ //! \param first iterator pointing at the first letter of the word.
+ //! \param last iterator pointing one beyond the last letter of the
+ //! word.
//!
- //! \sa Presentation::validate_word.
- // TODO(0) remove
- void validate_word(word_type const& w) const {
- ToString to_string(presentation().alphabet());
- std::string s = to_string(w);
- presentation().validate_word(s.cbegin(), s.cend());
- }
-
+ //! \throw LibsemigroupsException if any letter in the range from \p
+ //! first to \p last is out of bounds.
template
void throw_if_letter_out_of_bounds(Iterator1 first, Iterator2 last) const {
presentation().validate_word(first, last);
}
- //! \brief Return the presentation defined by the rewriting system
+ //! \brief Return the presentation defined by the rewriting system.
//!
- //! Return the presentation defined by the rewriting system
+ //! This function returns the presentation defined by the rewriting system.
//!
//! \returns
//! A const reference to the presentation, a value of type
- //! Presentation const&.
+ //! ``Presentation const&``.
//!
//! \exceptions
//! \noexcept
@@ -745,58 +944,53 @@ namespace libsemigroups {
return _presentation;
}
- // TODO(0) add note about empty active rules after init and non-const-ness
- //! \brief Return the current number of active
- //! rules in the KnuthBendix instance.
+ //! \brief Return the current number of active rules in the KnuthBendix
+ //! instance.
//!
- //! Return the current number of active rules in
- //! the KnuthBendix instance.
+ //! This function returns the current number of active rules in the
+ //! KnuthBendix instance.
//!
//! \returns
- //! The current number of active rules, a value
- //! of type \c size_t.
+ //! The current number of active rules, a value of type \c size_t.
//!
//! \exceptions
//! \noexcept
//!
//! \complexity
//! Constant.
+ // TODO(1) this should be const
+ // TODO(1) add note about empty active rules after init and non-const-ness
+ // (this only applies if this becomes const)
[[nodiscard]] size_t number_of_active_rules() noexcept;
- //! \brief Return the current number of inactive
- //! rules in the KnuthBendix instance.
+ //! \brief Return the current number of inactive rules in the KnuthBendix
+ //! instance.
//!
- //! Return the current number of inactive rules
- //! in the KnuthBendix instance.
+ //! This function returns the current number of inactive rules in the
+ //! KnuthBendix instance.
//!
//! \returns
- //! The current number of inactive rules, a
- //! value of type \c size_t.
+ //! The current number of inactive rules, a value of type \c size_t.
//!
//! \exceptions
//! \noexcept
//!
//! \complexity
//! Constant.
- //!
[[nodiscard]] size_t number_of_inactive_rules() const noexcept {
return _rewriter.number_of_inactive_rules();
}
- //! \brief Return the number of rules that
- //! KnuthBendix has created
+ //! \brief Return the number of rules that KnuthBendix has created
//!
- //! Return the total number of Rule instances
- //! that have been created whilst whilst the
- //! Knuth-Bendix algorithm has been running.
- //! Note that this is not the sum of \ref
- //! number_of_active_rules and \ref
- //! number_of_inactive_rules, due to the
- //! re-initialisation of rules where possible.
+ //! This function returns the total number of Rule instances that have been
+ //! created whilst whilst the Knuth-Bendix algorithm has been running. Note
+ //! that this is not the sum of \ref number_of_active_rules and \ref
+ //! number_of_inactive_rules, due to the re-initialisation of rules where
+ //! possible.
//!
//! \returns
- //! The total number of rules, a value of type
- //! \c size_t.
+ //! The total number of rules, a value of type \c size_t.
//!
//! \exceptions
//! \noexcept
@@ -807,81 +1001,27 @@ namespace libsemigroups {
return _rewriter.stats().total_rules;
}
- // TODO(0) What do we do about doc-ing this?
- //! Type of the rules in the system.
- using rule_type = std::pair;
-
- // TODO(0) update the doc, now returns a Range
- // TODO(0) add note about empty active rules after
- // init
- //! \brief Return a copy of the active rules.
+ //! \brief Return a range object containing the active rules.
//!
- //! This member function returns a vector consisting of the pairs of
- //! strings which represent the rules of the KnuthBendix instance. The \c
+ //! This function returns a range object containing the pairs of
+ //! strings which represent the rules of a KnuthBendix instance. The \c
//! first entry in every such pair is greater than the \c second according
- //! to the reduction ordering of the KnuthBendix instance. The rules are
- //! sorted according to the reduction ordering used by the rewriting system,
- //! on the first entry.
+ //! to the reduction ordering of the KnuthBendix instance.
//!
//! \returns
- //! A copy of the currently active rules, a
- //! value of type
- //! \c std::vector.
- //!
- //! \complexity
- //! \f$O(n)\f$ where \f$n\f$ is the sum of the
- //! lengths of the words in rules of \p copy.
- [[nodiscard]] auto active_rules() {
- using rx::iterator_range;
- using rx::transform;
- if (_rewriter.number_of_active_rules() == 0
- && _rewriter.number_of_pending_rules() != 0) {
- _rewriter.process_pending_rules();
- }
- return iterator_range(_rewriter.begin(), _rewriter.end())
- | transform([this](auto const& rule) {
- // TODO(1) remove allocation
- detail::internal_string_type lhs
- = detail::internal_string_type(*rule->lhs());
- detail::internal_string_type rhs
- = detail::internal_string_type(*rule->rhs());
- internal_to_external_string(lhs);
- internal_to_external_string(rhs);
- return std::make_pair(lhs, rhs);
- });
- }
+ //! A range object containing the current active rules.
+ // TODO(1) should be const
+ // TODO(1) add note about empty active rules after, or better discuss that
+ // there are three kinds of rules in the system: active, inactive, and
+ // pending.
+ // TODO(1) allow type of words output to be specified
+ [[nodiscard]] auto active_rules();
private:
- // TODO(0) recycle this doc or delete
- // TODO add note about empty active rules after
- // init and non-const-ness
- //! \brief Rewrite a word in-place.
- //!
- //! The word \p w is rewritten in-place
- //! according to the current active rules in the
- //! KnuthBendix instance.
- //!
- //! \param w the word to rewrite.
- //!
- //! \returns
- //! The argument \p w after it has been
- //! rewritten.
- // TODO update doc
+ // TODO(1) remove this ...
void rewrite_inplace(std::string& w);
- // TODO(0) recycle this doc or delete
- // TODO add note about empty active rules after
- // init and non-const-ness
- //! \brief Rewrite a word.
- //!
- //! Rewrites a copy of the word \p w rewritten according to the current
- //! rules in the KnuthBendix instance.
- //!
- //! \param w the word to rewrite.
- //!
- //! \returns
- //! A copy of the argument \p w after it has
- //! been rewritten.
+ // TODO(1) remove this ...
[[nodiscard]] std::string rewrite(std::string w) {
rewrite_inplace(w);
return w;
@@ -910,11 +1050,9 @@ namespace libsemigroups {
//! instance is known, and \c false if it is not.
[[nodiscard]] bool confluent_known() const noexcept;
- // REVIEW None of these \sa's exist anymore. Should it be \ref
- // number_of_classes and \ref normal_forms?
//! \brief Return the Gilman \ref WordGraph.
//!
- //! Return the Gilman WordGraph of the system.
+ //! This function returns the Gilman WordGraph of the system.
//!
//! The Gilman WordGraph is a digraph where the labels of the paths from
//! the initial node (corresponding to the empty word) correspond to the
@@ -931,19 +1069,17 @@ namespace libsemigroups {
//! \warning This will terminate when the KnuthBendix instance is reduced
//! and confluent, which might be never.
//!
- //! \sa \ref number_of_normal_forms, \ref cbegin_normal_forms, and \ref
- //! cend_normal_forms.
+ //! \sa \ref number_of_classes, and \ref knuth_bendix::normal_forms.
WordGraph const& gilman_graph();
- //! \brief Return the node labels of the Gilman
- //! \ref WordGraph
+ //! \brief Return the node labels of the Gilman \ref WordGraph
//!
- //! Return the node labels of the Gilman \ref WordGraph, corresponding to
- //! the unique prefixes of the left-hand sides of the rules of the rewriting
- //! system.
+ //! This function returns the node labels of the Gilman \ref WordGraph,
+ //! corresponding to the unique prefixes of the left-hand sides of the rules
+ //! of the rewriting system.
//!
- //! \return The node labels of the Gilman \ref WordGraph, a value of type
- //! \c std::vector.
+ //! \return The node labels of the Gilman \ref WordGraph, a const reference
+ //! to a ``std::vector``.
//!
//! \sa \ref gilman_graph.
[[nodiscard]] std::vector const& gilman_graph_node_labels() {
@@ -951,55 +1087,16 @@ namespace libsemigroups {
return _gilman_graph_node_labels;
}
+ private:
//////////////////////////////////////////////////////////////////////////
- // KnuthBendix - attributes - public
+ // KnuthBendix - private member functions
//////////////////////////////////////////////////////////////////////////
- // TODO(0) recycle or delete
- //! \brief Check if two inputs are equivalent with respect to the system
- //!
- //! By first testing \c string equivalence, then by rewriting the inputs,
- //! then by running the Knuth-Bendix algorithm and rewriting the inputs with
- //! respect to the updated system again, check if \p u and \p v are
- //! equivalent.
- //!
- //! \param u, v the words to test the
- //! equivalence of.
- //!
- //! \returns \c true if \p u is equivalent to \p v, and \c false otherwise.
- //!
- //! \warning If the inputs don't rewrite to equivalent words with the
- //! initial rewriting rules, then the Knuth-Bendix algorithm is run. This
- //! terminates when the rewriting system is confluent, which may be never.
- //!
- //! \sa run.
-
- // TODO(0) recycle or delete
- //! \brief Check containment
- //!
- //! Check if the pair of words \p u and \p v is
- //! contained in within the congruence
- //! corresponding to rewriting system.
- //!
- //! \param u, v the words to check containment
- //! of.
- //!
- //! \returns \c true if the the pair consisting
- //! of \p u and \p v is contained within the
- //! congruence, and \c false otherwise.
- //!
- //! \sa \ref equal_to.
- // No in-place version just use rewrite instead,
- // this only exists so that run is called.
-
- private:
void report_presentation(Presentation const&) const;
void report_before_run();
void report_progress_from_thread(std::atomic_bool const&);
void report_after_run();
- // TODO(0) remove this use the version in CongruenceInterface
- void throw_if_started() const;
void stats_check_point();
[[nodiscard]] static detail::internal_char_type
@@ -1032,99 +1129,54 @@ namespace libsemigroups {
return _rewriter.max_active_word_length();
}
- //////////////////////////////////////////////////////////////////////////
- // Runner - pure virtual member functions - private
- //////////////////////////////////////////////////////////////////////////
-
- void run_impl() override;
void run_real(std::atomic_bool&);
- bool finished_impl() const override;
[[nodiscard]] bool stop_running() const;
- };
- //! This friend function allows a KnuthBendix object to be left shifted
- //! into a std::ostream, such as std::cout. The currently active rules
- //! of the system are represented in the output.
- template
- std::ostream& operator<<(std::ostream&,
- KnuthBendix const&);
+ //////////////////////////////////////////////////////////////////////////
+ // Runner - pure virtual member functions - private
+ //////////////////////////////////////////////////////////////////////////
- namespace detail {
- // TODO put in separate file
- template
- class KnuthBendixNormalFormRange : public Paths {
- using Paths_ = Paths;
-
- mutable Word _current;
- KnuthBendix* _kb;
-
- public:
- using size_type = typename Paths_::size_type;
- using output_type = Word const&;
-
- explicit KnuthBendixNormalFormRange(
- KnuthBendix& kb)
- : Paths(kb.gilman_graph()), _current(), _kb(&kb) {
- // It's possible that the gilman graph is empty, so the call to
- // source_no_checks(0) is technically invalid, but nothing goes wrong,
- // so we just go with it. This is slightly smelly.
- Paths_::source_no_checks(0);
- if (!kb.presentation().contains_empty_word()) {
- Paths_::next();
- }
- }
-
- output_type get() const {
- word_type const& w = Paths_::get();
- _current.clear();
- for (auto c : w) {
- _current.push_back(_kb->presentation().letter_no_checks(c));
- }
- return _current;
- }
-
- KnuthBendixNormalFormRange& min(size_type val) noexcept {
- Paths_::min(val);
- return *this;
- }
-
- KnuthBendixNormalFormRange& max(size_type val) noexcept {
- Paths_::max(val);
- return *this;
- }
-
- using Paths_::at_end;
- using Paths_::count;
- using Paths_::max;
- using Paths_::min;
- using Paths_::next;
- using Paths_::size_hint;
-
- static constexpr bool is_finite = true; // this isn't always true!
- static constexpr bool is_idempotent = true;
- }; // class KnuthBendixNormalFormRange
+ void run_impl() override;
+ bool finished_impl() const override;
+ }; // class KnuthBendix
- } // namespace detail
+// Special include for the KnuthBendixNormalFormRange, just to avoid cluttering
+// this file.
+#include "detail/knuth-bendix-nf.hpp"
+ //! \ingroup knuth_bendix_group
+ //!
+ //! \brief Helper functions for the \ref KnuthBendix class.
+ //!
+ //! Defined in \c knuth-bendix.hpp.
+ //!
+ //! This page contains documentation for some helper functions for the \ref
+ //! KnuthBendix class. In particular, these functions include versions of
+ //! several of the member functions of \ref KnuthBendix (that accept
+ //! iterators) whose parameters are not iterators, but objects instead. The
+ //! helpers documented on this page all belong to the namespace
+ //! ``knuth_bendix``.
+ //!
+ //! \sa \ref cong_intf_helpers_group
namespace knuth_bendix {
- //! \defgroup knuth_bendix_helpers_group Knuth-Bendix specific helpers
- //!
- //! TODO
- //! @{
-
////////////////////////////////////////////////////////////////////////
// KnuthBendix specific helpers
////////////////////////////////////////////////////////////////////////
- // TODO Should this have a param?
//! \brief Run the Knuth-Bendix algorithm by considering all overlaps of
//! a given length.
//!
//! This function runs the Knuth-Bendix algorithm on the rewriting
//! system represented by a KnuthBendix instance by considering all
//! overlaps of a given length \f$n\f$ (according to the \ref
- //! options::overlap) before those overlaps of length \f$n + 1\f$.
+ //! KnuthBendix::options::overlap) before those overlaps of length \f$n +
+ //! 1\f$.
+ //!
+ //! \tparam Rewriter the first template parameter for KnuthBendix.
+ //! \tparam ReductionOrder the second template parameter for KnuthBendix.
+ //!
+ //! \param kb the KnuthBendix instance.
//!
//! \complexity
//! See warning.
@@ -1132,41 +1184,25 @@ namespace libsemigroups {
//! \warning This will terminate when the KnuthBendix instance is
//! confluent, which might be never.
//!
- //! \sa \ref run.
+ //! \sa \ref KnuthBendix::run.
template
- void by_overlap_length(KnuthBendix&);
+ void by_overlap_length(KnuthBendix& kb);
//! \brief Check if the all rules are reduced with respect to each other.
//!
- //! This function is defined in \c knuth-bendix.hpp.
+ //! Defined in \c knuth-bendix.hpp.
//!
- //! \returns \c true if for each pair \f$(A, B)\f$ and \f$(C, D)\f$ of rules
- //! stored within the KnuthBendix instance, \f$C\f$ is neither a subword of
- //! \f$A\f$ nor \f$B\f$. Returns \c false otherwise.
+ //! \tparam Rewriter the first template parameter for KnuthBendix.
+ //! \tparam ReductionOrder the second template parameter for KnuthBendix.
//!
- //! \tparam Rewriter type of the rewriting system to be used in the
- //! Knuth-Bendix algorithm.
- //! \tparam ReductionOrder type of the reduction ordering used by the
- //! Knuth-Bendix algorithm.
//! \param kb the KnuthBendix instance defining the rules that are to be
//! checked for being reduced.
+ //!
+ //! \returns \c true if for each pair \f$(A, B)\f$ and \f$(C, D)\f$ of rules
+ //! stored within the KnuthBendix instance, \f$C\f$ is neither a subword of
+ //! \f$A\f$ nor \f$B\f$. Returns \c false otherwise.
template
- [[nodiscard]] bool is_reduced(KnuthBendix& kb) {
- for (auto const& test_rule : kb.active_rules()) {
- auto const lhs = test_rule.first;
- for (auto const& rule : kb.active_rules()) {
- if (test_rule == rule) {
- continue;
- }
-
- if (rule.first.find(lhs) != detail::internal_string_type::npos
- || rule.second.find(lhs) != detail::internal_string_type::npos) {
- return false;
- }
- }
- }
- return true;
- }
+ [[nodiscard]] bool is_reduced(KnuthBendix& kb);
////////////////////////////////////////////////////////////////////////
// Interface helpers - add_generating_pair
@@ -1197,36 +1233,29 @@ namespace libsemigroups {
// Interface helpers - normal_forms
////////////////////////////////////////////////////////////////////////
- //! \brief Return a forward iterator pointing at the first normal form with
- //! length in a given range.
+ //! \brief Returns a range object containing the normal forms.
//!
- //! If incremented, the iterator will point to the next least short-lex
- //! normal form (if it's less than \p max in length). Iterators of the
- //! type returned by this function should only be compared with other
- //! iterators created from the same KnuthBendix instance.
+ //! Defined in \c knuth-bendix.hpp.
//!
- //! \param lphbt the alphabet to use for the normal forms
- //! \param min the minimum length of a normal form
- //! \param max one larger than the maximum length of a normal form.
+ //! This function returns a range object containing normal forms of the
+ //! classes of the congruence represented by an instance of KnuthBendix. The
+ //! order of the classes, and the normal form that is returned, are
+ //! controlled by the reduction order used to construct \p kb. This function
+ //! triggers a full enumeration of \p kb.
//!
- //! \returns
- //! A value of type \ref const_normal_form_iterator.
+ //! \tparam Word the type of the words contained in the output range
+ //! (default: std::string).
+ //! \tparam Rewriter the first template parameter for KnuthBendix.
+ //! \tparam ReductionOrder the second template parameter for KnuthBendix.
//!
- //! \exceptions
- //! \no_libsemigroups_except
+ //! \param kb the \ref KnuthBendix instance.
//!
- //! \warning
- //! Copying iterators of this type is relatively expensive. As a
- //! consequence, prefix incrementing \c ++it the iterator \c it returned
- //! by \c cbegin_normal_forms is significantly cheaper than postfix
- //! incrementing \c it++.
+ //! \returns A range object.
//!
- //! \warning
- //! If the finitely presented semigroup represented by \c this is
- //! infinite, then \p max should be chosen with some care.
+ //! \exceptions
+ //! \no_libsemigroups_except
//!
- //! \sa \ref cend_normal_forms.
- // TODO update doc
+ //! \cong_intf_warn_undecidable{Knuth-Bendix}.
template
@@ -1247,57 +1276,49 @@ namespace libsemigroups {
using congruence_interface::non_trivial_classes;
- // Compute non-trivial classes in kb1!
- template
+ //! \brief Find the non-trivial classes of the quotient of one KnuthBendix
+ //! instance in another.
+ //!
+ //! This function returns the classes with size at least \f$2\f$ in the
+ //! normal forms of \p kb2 in \p kb1 (the greater congruence, with fewer
+ //! classes). This function triggers a full enumeration of both \p kb2 and
+ //! \p kb1.
+ //!
+ //! Note that this function does **not** compute the normal forms of \p kb2
+ //! and try to compute the partition of these induced by \p kb1, before
+ //! filtering out the classes of size \f$1\f$. In particular, it is possible
+ //! to compute the non-trivial classes of \p kb1 in \p kb2 if there are only
+ //! finitely many finite such classes, regardless of whether or not \p kb2
+ //! or \p kb1 has infinitely many classes.
+ //!
+ //! \tparam Word the type of the words contained in the output range
+ //! (default: std::string).
+ //! \tparam Rewriter the first template parameter for KnuthBendix.
+ //! \tparam ReductionOrder the second template parameter for KnuthBendix.
+ //!
+ //! \param kb1 the first \ref KnuthBendix instance.
+ //! \param kb2 the second \ref KnuthBendix instance.
+ //!
+ //! \returns The non-trivial classes of \p kb1 in \p kb2.
+ //!
+ //! \throws LibsemigroupsException if \p kb1 has infinitely many classes
+ //! and \p kb2 has finitely many classes (so that there is at least one
+ //! infinite non-trivial class).
+ //!
+ //! \throws LibsemigroupsException if the alphabets of the
+ //! presentations of \p kb1 and \p kb2 are not equal.
+ //!
+ //! \throws LibsemigroupsException if the \ref KnuthBendix::gilman_graph of
+ //! \p kb1 has fewer nodes than that of \p kb2.
+ //!
+ //! \cong_intf_warn_undecidable{Knuth-Bendix}.
+ template
[[nodiscard]] std::vector>
non_trivial_classes(KnuthBendix& kb1,
KnuthBendix& kb2);
- // TODO(0) move all of the functions from here to the end of the namespace
- // knuth_bendix to before the interface helpers when they are out of lined.
-
- ////////////////////////////////////////////////////////////////////////
- // Possible future interface helpers - try_equal_to
- ////////////////////////////////////////////////////////////////////////
-
- // TODO Doc
- template
- inline tril try_equal_to(Presentation& p,
- std::string const& lhs,
- std::string const& rhs,
- T t = std::chrono::seconds(1)) {
- constexpr static congruence_kind twosided = congruence_kind::twosided;
-
- // TODO validate lhs and rhs
- KnuthBendix kb(twosided, p);
- std::string lphbt = p.alphabet();
- std::vector perm(lphbt.size(), 0);
- std::iota(perm.begin(), perm.end(), 0);
-
- do {
- detail::apply_permutation(lphbt, perm);
-
- p.alphabet(lphbt);
- p.validate();
-
- kb.init(twosided, p);
- // TODO(0) no checks
- if (reduce_no_run(kb, lhs) == reduce_no_run(kb, rhs)) {
- return tril::TRUE;
- }
- kb.run_for(t);
- // TODO(0) no checks
- if (reduce_no_run(kb, lhs) == reduce_no_run(kb, rhs)) {
- return tril::TRUE;
- } else if (kb.finished()) {
- return tril::FALSE;
- }
- } while (std::next_permutation(perm.begin(), perm.end()));
- return tril::unknown;
- }
-
////////////////////////////////////////////////////////////////////////
// Possible future interface helpers - redundant_rule
////////////////////////////////////////////////////////////////////////
@@ -1305,7 +1326,7 @@ namespace libsemigroups {
//! \brief Return an iterator pointing at the left hand side of a redundant
//! rule.
//!
- //! This function is defined in \c knuth-bendix.hpp.
+ //! Defined in \c knuth-bendix.hpp.
//!
//! Starting with the last rule in the presentation, this function
//! attempts to run the Knuth-Bendix algorithm on the rules of the
@@ -1320,44 +1341,28 @@ namespace libsemigroups {
//! iterator pointing to its left hand side is returned.
//!
//! If no rule can be shown to be redundant in this way, then an iterator
- //! pointing to \c p.cend() is returned.
+ //! pointing to \c p.rules.cend() is returned.
//!
- //! \tparam T type of the 2nd parameter (time to try running
- //! Knuth-Bendix). \param p the presentation \param t time to run
- //! KnuthBendix for every omitted rule
+ //! \tparam Time type of the 2nd parameter (time to try running
+ //! Knuth-Bendix).
+ //! \param p the presentation.
+ //! \param t time to run KnuthBendix for every omitted rule.
+ //!
+ //! \returns An iterator pointing at the left-hand side of a redundant rule
+ //! of \c p.rules.cend().
//!
//! \warning The progress of the Knuth-Bendix algorithm may differ between
//! different calls to this function even if the parameters are identical.
//! As such this is non-deterministic, and may produce different results
//! with the same input.
- template
- [[nodiscard]] auto redundant_rule(Presentation const& p, T t) {
- constexpr static congruence_kind twosided = congruence_kind::twosided;
-
- p.validate();
- Presentation q;
- q.alphabet(p.alphabet());
- q.contains_empty_word(p.contains_empty_word());
- KnuthBendix kb;
-
- for (auto omit = p.rules.crbegin(); omit != p.rules.crend(); omit += 2) {
- q.rules.clear();
- q.rules.insert(q.rules.end(), p.rules.crbegin(), omit);
- q.rules.insert(q.rules.end(), omit + 2, p.rules.crend());
- kb.init(twosided, q);
- kb.run_for(t);
- // TODO no_checks
- if (reduce_no_run(kb, *omit) == reduce_no_run(kb, *(omit + 1))) {
- return (omit + 1).base() - 1;
- }
- }
- return p.rules.cend();
- }
+ template
+ [[nodiscard]] std::vector::const_iterator
+ redundant_rule(Presentation const& p, Time t);
//! \brief Return an iterator pointing at the left hand side of a redundant
//! rule.
//!
- //! This function is defined in \c knuth-bendix.hpp.
+ //! Defined in \c knuth-bendix.hpp.
//!
//! Starting with the last rule in the presentation, this function
//! attempts to run the Knuth-Bendix algorithm on the rules of the
@@ -1374,75 +1379,99 @@ namespace libsemigroups {
//! If no rule can be shown to be redundant in this way, then an iterator
//! pointing to \c p.cend() is returned.
//!
- //! \tparam W type of words in the Presentation
- //! \tparam T type of the 2nd parameter (time to try running
- //! Knuth-Bendix). \param p the presentation \param t time to run
- //! KnuthBendix for every omitted rule
+ //! \tparam Word type of words in the Presentation
+ //! \tparam Time type of the 2nd parameter (time to try running
+ //! Knuth-Bendix).
+ //! \param p the presentation.
+ //! \param t time to run KnuthBendix for every omitted rule.
//!
//! \warning The progress of the Knuth-Bendix algorithm may differ between
//! different calls to this function even if the parameters are identical.
//! As such this is non-deterministic, and may produce different results
//! with the same input.
- template
- [[nodiscard]] auto redundant_rule(Presentation const& p, T t) {
+ template
+ [[nodiscard]] auto redundant_rule(Presentation const& p, Time t) {
auto pp = to_presentation(p);
return p.rules.cbegin()
+ std::distance(pp.rules.cbegin(), redundant_rule(pp, t));
}
- //! @}
- } // namespace knuth_bendix
- // TODO to tpp file
- // TODO to to_presentation file
- template
- Presentation
- to_presentation(KnuthBendix& kb) {
- if constexpr (std::is_same_v) {
- auto const& p_orig = kb.presentation();
- Presentation p;
- p.alphabet(p_orig.alphabet())
- .contains_empty_word(p_orig.contains_empty_word());
-
- for (auto const& rule : kb.active_rules()) {
- presentation::add_rule(p, rule.first, rule.second);
- }
- return p;
- } else {
- return to_presentation(to_presentation(kb));
- }
- }
+ ////////////////////////////////////////////////////////////////////////
+ // Possible future interface helpers - try_equal_to
+ ////////////////////////////////////////////////////////////////////////
+
+ // TODO(1) Doc
+ // TODO(1) template std::string
+ // TODO(1) re-include later
+ // template
+ // inline tril try_equal_to(Presentation& p,
+ // std::string const& lhs,
+ // std::string const& rhs,
+ // T t = std::chrono::seconds(1));
+ } // namespace knuth_bendix
////////////////////////////////////////////////////////////////////////
// global functions - to_human_readable_repr
////////////////////////////////////////////////////////////////////////
- // TODO(0) What should the \param be?
+ //! \ingroup knuth_bendix_group
+ //!
+ //! \brief Insert into std::ostream.
+ //!
+ //! This function allows a KnuthBendix object to be left shifted
+ //! into a std::ostream, such as std::cout. The currently active rules
+ //! of the system are represented in the output.
+ //!
+ //! \param os the output stream to insert into.
+ //! \param kb the KnuthBendix object.
+ //!
+ //! \returns A reference to the first argument.
+ template
+ std::ostream& operator<<(std::ostream& os,
+ KnuthBendix const& kb);
+
+ //! \ingroup knuth_bendix_group
+ //!
//! \brief Return a string representation of a KnuthBendix instance
//!
- //! Return a string representation of a KnuthBendix instance, specifying the
- //! size of the underlying alphabet and the number of active rules.
+ //! This function returns a string representation of a KnuthBendix instance,
+ //! specifying the size of the underlying alphabet and the number of active
+ //! rules.
+ //!
+ //! \tparam Rewriter the first template parameter for KnuthBendix.
+ //! \tparam ReductionOrder the second template parameter for KnuthBendix.
+ //!
+ //! \param kb the KnuthBendix instance.
//!
//! \returns The representation, a value of type \c std::string
- // TODO(0) rename to_human_readable_repr
+ // TODO(1) preferably kb would be a const&
template
- std::string repr(KnuthBendix& kb) {
- using str = std::string;
-
- str conf;
- if (!kb.confluent_known()) {
- conf = "KnuthBendix";
- } else if (kb.confluent()) {
- conf = "confluent KnuthBendix";
- } else {
- conf = "non-confluent KnuthBendix";
- }
- str alphabet_size = std::to_string(kb.presentation().alphabet().size());
- str n_rules = std::to_string(kb.number_of_active_rules());
+ std::string to_human_readable_repr(KnuthBendix& kb);
- // TODO(JE) use fmt::format instead
- return str("<") + conf + " on " + alphabet_size + " letters with " + n_rules
- + " active rules>";
- }
+ //! \ingroup to_presentation_group
+ //!
+ //! \brief Make a presentation from a KnuthBendix object.
+ //!
+ //! This function constructs and returns a Presentation object using the
+ //! currently active rules of \p kb.
+ //!
+ //! No enumeration of the argument \p kb is performed, so it might be the
+ //! case that the resulting presentation does not define the same
+ //! semigroup/monoid as \p kb. To ensure that the resulting presentation
+ //! defines the same semigroup as \p kb, run KnuthBendix::run (or any other
+ //! function that fully enumerates \p kb) prior to calling this function.
+ //!
+ //! \tparam Word the type of the rules in the presentation being constructed.
+ //!
+ //! \param kb the KnuthBendix object from which to obtain the rules.
+ //!
+ //! \returns An object of type \c Presentation.
+ //!
+ //! \exceptions
+ //! \no_libsemigroups_except
+ // This cannot go into to-presentation.hpp since we include that here
+ template
+ Presentation to_presentation(KnuthBendix& kb);
} // namespace libsemigroups
#include "knuth-bendix.tpp"
diff --git a/include/libsemigroups/knuth-bendix.tpp b/include/libsemigroups/knuth-bendix.tpp
index 1cb1005bd..00b675aa7 100644
--- a/include/libsemigroups/knuth-bendix.tpp
+++ b/include/libsemigroups/knuth-bendix.tpp
@@ -91,8 +91,8 @@ namespace libsemigroups {
template
typename KnuthBendix::Settings&
KnuthBendix::Settings::init() noexcept {
- // TODO experiment with starting size to optimise speed.
- batch_size = 128;
+ // TODO(1) experiment with starting size to optimise speed.
+ max_pending_rules = 128;
check_confluence_interval = 4'096;
max_overlap = POSITIVE_INFINITY;
max_rules = POSITIVE_INFINITY;
@@ -185,7 +185,7 @@ namespace libsemigroups {
template
KnuthBendix&
KnuthBendix::operator=(KnuthBendix&& that) {
- CongruenceInterface::operator=(std::move(that)); // TODO correct?
+ CongruenceInterface::operator=(std::move(that));
_gen_pairs_initted = std::move(that._gen_pairs_initted);
_gilman_graph = std::move(that._gilman_graph);
_gilman_graph_node_labels = std::move(that._gilman_graph_node_labels);
@@ -277,7 +277,6 @@ namespace libsemigroups {
template
uint64_t KnuthBendix::number_of_classes() {
- // TODO uncomment
if (is_obviously_infinite(*this)) {
return POSITIVE_INFINITY;
}
@@ -291,13 +290,73 @@ namespace libsemigroups {
}
}
- // template
- // std::string
- // KnuthBendix::normal_form(std::string const& w) {
- // presentation().validate_word(w.cbegin(), w.cend());
- // run();
- // return rewrite(w);
- // }
+ template
+ template
+ [[nodiscard]] tril
+ KnuthBendix::currently_contains_no_checks(
+ Iterator1 first1,
+ Iterator2 last1,
+ Iterator3 first2,
+ Iterator4 last2) const {
+ if (std::equal(first1, last1, first2, last2)) {
+ return tril::TRUE;
+ }
+ // TODO(1) remove allocations here
+ std::string w1, w2;
+ reduce_no_run_no_checks(std::back_inserter(w1), first1, last1);
+ reduce_no_run_no_checks(std::back_inserter(w2), first2, last2);
+ if (w1 == w2) {
+ return tril::TRUE;
+ } else if (finished()) {
+ return tril::FALSE;
+ }
+ return tril::unknown;
+ }
+
+ template
+ template
+ OutputIterator KnuthBendix::reduce_no_run_no_checks(
+ OutputIterator d_first,
+ InputIterator1 first,
+ InputIterator2 last) const {
+ // TODO(1) improve this to not require _tmp_element1
+ if constexpr (std::is_same_v) {
+ static_assert(std::is_same_v);
+ _tmp_element1.assign(first, std::distance(first, last));
+ } else {
+ _tmp_element1.assign(first, last);
+ }
+ const_cast&>(*this).rewrite_inplace(
+ _tmp_element1);
+ return std::copy(
+ std::begin(_tmp_element1), std::end(_tmp_element1), d_first);
+ }
+
+ template
+ [[nodiscard]] auto KnuthBendix::active_rules() {
+ using rx::iterator_range;
+ using rx::transform;
+ if (_rewriter.number_of_active_rules() == 0
+ && _rewriter.number_of_pending_rules() != 0) {
+ _rewriter.process_pending_rules();
+ }
+ return iterator_range(_rewriter.begin(), _rewriter.end())
+ | transform([this](auto const& rule) {
+ // TODO(1) remove allocation
+ detail::internal_string_type lhs
+ = detail::internal_string_type(*rule->lhs());
+ detail::internal_string_type rhs
+ = detail::internal_string_type(*rule->rhs());
+ internal_to_external_string(lhs);
+ internal_to_external_string(rhs);
+ return std::make_pair(lhs, rhs);
+ });
+ }
template
void KnuthBendix::report_presentation(
@@ -389,8 +448,6 @@ namespace libsemigroups {
rc.min_width(12); // .divider("{:-<95}\n");
rc("KnuthBendix: RUN STATISTICS\n");
// rc.divider();
- // FIXME these are mostly 0, and should be obtained from the rewriter
- // probably
rc("KnuthBendix: max stack depth {}\n",
group_digits(_rewriter.max_stack_depth()));
rc("KnuthBendix: max rule length {}\n",
@@ -444,15 +501,6 @@ namespace libsemigroups {
// KnuthBendix - other methods - private
//////////////////////////////////////////////////////////////////////////
- template
- void KnuthBendix::throw_if_started() const {
- if (started()) {
- LIBSEMIGROUPS_EXCEPTION(
- "the presentation cannot be changed after Knuth-Bendix has "
- "started, maybe try `init` instead?");
- }
- }
-
template
void KnuthBendix::stats_check_point() {
_stats.prev_active_rules = number_of_active_rules();
@@ -469,7 +517,6 @@ namespace libsemigroups {
return _rewriter.confluence_known();
}
- // TODO should this check for 0 active rules?
template
bool KnuthBendix::confluent() const {
if (_rewriter.number_of_active_rules() == 0
@@ -517,8 +564,8 @@ namespace libsemigroups {
}
}
- // TODO (When the rewriters have a pointer to the KB instance) move this into
- // the rewriter
+ // TODO(1) (When the rewriters have a pointer to the KB instance) move this
+ // into the rewriter
template
void
KnuthBendix::run_real(std::atomic_bool& pause) {
@@ -552,14 +599,14 @@ namespace libsemigroups {
if (nr > _settings.check_confluence_interval) {
pause = true;
- // Checking confluence requires there to be no pending rules which, in
- // general, isn't the case at this point in the loop (other than when
- // nr is a common multiple of batch_size and
+ // Checking confluence requires there to be no pending rules which,
+ // in general, isn't the case at this point in the loop (other than
+ // when nr is a common multiple of max_pending_rules and
// confluence_check_interval). Therefore, it might make sense to
// process any remaining rules before checking confluence. However,
// this seems to worsen performance on the test cases, so it remains
// to see what the best option is for default behaviour.
- // TODO should we process rules here too?
+ // TODO(1) should we process rules here too?
// _rewriter.process_pending_rules();
if (confluent()) {
pause = false;
@@ -753,7 +800,7 @@ namespace libsemigroups {
KnuthBendix::uint_to_internal_char(size_t a) {
// Ensure that the input value doesn't overflow the internal char type,
// seems legit to me
- // TODO should this be
+ // TODO(1) should this be
// std::numeric_limits::max() -
// std::numeric_limits::min()?
LIBSEMIGROUPS_ASSERT(
@@ -771,8 +818,8 @@ namespace libsemigroups {
template
typename detail::internal_string_type
KnuthBendix::uint_to_internal_string(size_t i) {
- // TODO What is this check for?
- // TODO should this be
+ // TODO(1) What is this check for?
+ // TODO(1) should this be
// std::numeric_limits::max() -
// std::numeric_limits::min()?
LIBSEMIGROUPS_ASSERT(
@@ -852,7 +899,7 @@ namespace libsemigroups {
// KnuthBendixImpl - methods for rules - private
//////////////////////////////////////////////////////////////////////////
- // TODO move this to the single call site
+ // TODO(1) move this to the single call site
template
void KnuthBendix::init_from_presentation() {
auto const& p = _presentation;
@@ -911,7 +958,8 @@ namespace libsemigroups {
vlhs.cend()); // rule = AQ_j -> Q_iC
_rewriter.add_pending_rule(x, y);
- if (_rewriter.number_of_pending_rules() >= _settings.batch_size) {
+ if (_rewriter.number_of_pending_rules()
+ >= _settings.max_pending_rules) {
_rewriter.process_pending_rules();
}
// It can be that the iterator `it` is invalidated by the call to
@@ -922,95 +970,96 @@ namespace libsemigroups {
// will be considered later, because when the rule `u` is reactivated it
// is added to the end of the active rules list.
- // TODO remove some of the above checks, since now rules don't get
+ // TODO(1) remove some of the above checks, since now rules don't get
// processed after being added.
}
}
}
namespace knuth_bendix {
+
// We are computing non_trivial_classes with respect to kb2 (the greater
// congruence, with fewer classes)
//
// This should work ok if kb1 and kb2 represent different kinds of
// congruence.
- template
+ template
std::vector>
non_trivial_classes(KnuthBendix& kb1,
KnuthBendix& kb2) {
using rx::operator|;
- // It is intended that kb1 is defined using the same presentation as kb2
+ // It is intended that kb2 is defined using the same presentation as kb1
// and some additional rules. The output might still be meaningful if
// this is not the case.
- if (kb2.number_of_classes() == POSITIVE_INFINITY
- && kb1.number_of_classes() != POSITIVE_INFINITY) {
+ if (kb1.number_of_classes() == POSITIVE_INFINITY
+ && kb2.number_of_classes() != POSITIVE_INFINITY) {
LIBSEMIGROUPS_EXCEPTION(
"the 1st argument defines an infinite semigroup, and the 2nd "
"argument defines a finite semigroup, so there is at least one "
"infinite non-trivial class!");
- } else if (kb2.presentation().alphabet()
- != kb1.presentation().alphabet()) {
+ } else if (kb1.presentation().alphabet()
+ != kb2.presentation().alphabet()) {
// It might be possible to handle this case too,
// but doesn't seem worth it at present
LIBSEMIGROUPS_EXCEPTION("the arguments must have presentations with "
"the same alphabets, found {} and {}",
- kb2.presentation().alphabet(),
- kb1.presentation().alphabet());
+ kb1.presentation().alphabet(),
+ kb2.presentation().alphabet());
}
// We construct the WordGraph `wg` obtained by subtracting all of the
- // edges from the Gilman graph of kb1 from the Gilman graph of kb2. The
+ // edges from the Gilman graph of kb2 from the Gilman graph of kb1. The
// non-trivial classes are finite if and only if `wg` is acyclic. It
// would be possible to do this without actually constructing `wg` but
// constructing `wg` is simpler, and so we do that for now.
- auto g2 = kb2.gilman_graph();
auto g1 = kb1.gilman_graph();
+ auto g2 = kb2.gilman_graph();
- LIBSEMIGROUPS_ASSERT(g2.number_of_nodes() > 0);
LIBSEMIGROUPS_ASSERT(g1.number_of_nodes() > 0);
+ LIBSEMIGROUPS_ASSERT(g2.number_of_nodes() > 0);
- if (g2.number_of_nodes() < g1.number_of_nodes()) {
+ if (g1.number_of_nodes() < g2.number_of_nodes()) {
LIBSEMIGROUPS_EXCEPTION(
- "the Gilman digraph of the 1st argument must have at least as many "
- "nodes as the Gilman digraph of the 2nd argument, found {} nodes "
+ "the Gilman graph of the 1st argument must have strictly fewer "
+ "nodes than the Gilman graph of the 2nd argument, found {} nodes "
"and {} nodes",
- g2.number_of_nodes(),
- g1.number_of_nodes());
+ g1.number_of_nodes(),
+ g2.number_of_nodes());
}
// We need to obtain a mappings from the nodes of
- // g2 to g1 and vice versa.
+ // g1 to g2 and vice versa.
- using node_type = typename decltype(g2)::node_type;
+ using node_type = typename decltype(g1)::node_type;
- std::vector to_g1(g2.number_of_nodes(),
- static_cast(UNDEFINED));
- to_g1[0] = 0;
std::vector to_g2(g1.number_of_nodes(),
static_cast(UNDEFINED));
to_g2[0] = 0;
- for (auto v : g2.nodes()) {
- for (auto e : g2.labels()) {
- auto ve2 = g2.target_no_checks(v, e);
- if (to_g1[v] != UNDEFINED && ve2 != UNDEFINED) {
- auto ve1 = g1.target_no_checks(to_g1[v], e);
- if (ve1 != UNDEFINED && to_g1[ve2] == UNDEFINED) {
- to_g1[ve2] = ve1;
+ std::vector to_g1(g2.number_of_nodes(),
+ static_cast(UNDEFINED));
+ to_g1[0] = 0;
+ for (auto v : g1.nodes()) {
+ for (auto e : g1.labels()) {
+ auto ve1 = g1.target_no_checks(v, e);
+ if (to_g2[v] != UNDEFINED && ve1 != UNDEFINED) {
+ auto ve2 = g2.target_no_checks(to_g2[v], e);
+ if (ve2 != UNDEFINED && to_g2[ve1] == UNDEFINED) {
to_g2[ve1] = ve2;
+ to_g1[ve2] = ve1;
}
}
}
}
// We do a depth first search simultaneously for cycles, and edges E in
- // g2 not in g1. Pre order for cycle detection, post order for "can we
+ // g1 not in g2. Pre order for cycle detection, post order for "can we
// reach a node incident to an edge in E" and "number of paths through a
// node is infinite"
- size_t const N = g2.number_of_nodes();
+ size_t const N = g1.number_of_nodes();
// can_reach[v] == true if there is a path from v to a node incident to
- // an edge in g2 that's not in g1.
+ // an edge in g1 that's not in g2.
std::vector can_reach(N, false);
std::vector inf_paths(N, false);
std::vector seen(N, false);
@@ -1024,8 +1073,8 @@ namespace libsemigroups {
if (v >= N) {
// post order
v -= N;
- for (auto e : g2.labels()) {
- auto ve = g2.target_no_checks(v, e);
+ for (auto e : g1.labels()) {
+ auto ve = g1.target_no_checks(v, e);
if (ve != UNDEFINED) {
can_reach[v] = (can_reach[v] || can_reach[ve]);
if (can_reach[ve]) {
@@ -1042,49 +1091,49 @@ namespace libsemigroups {
// so we can tell when all of the descendants of v have been
// processed out of the stack
stck.push(v + N);
- if (to_g1[v] == UNDEFINED) {
+ if (to_g2[v] == UNDEFINED) {
can_reach[v] = true;
}
- for (auto e : g2.labels()) {
- auto ve2 = g2.target_no_checks(v, e);
- if (ve2 != UNDEFINED) {
- // Check if (v, e, ve2) corresponds to an edge in g1
+ for (auto e : g1.labels()) {
+ auto ve1 = g1.target_no_checks(v, e);
+ if (ve1 != UNDEFINED) {
+ // Check if (v, e, ve1) corresponds to an edge in g2
if (!can_reach[v]) {
- auto ve1 = g1.target_no_checks(to_g1[v], e);
- if (ve1 != UNDEFINED) {
- // edges (v, e, ve2) and (to_g1[v], e, ve1) exist, so
- // there's an edge in g2 not in g1 if the targets of these
+ auto ve2 = g2.target_no_checks(to_g2[v], e);
+ if (ve2 != UNDEFINED) {
+ // edges (v, e, ve1) and (to_g2[v], e, ve2) exist, so
+ // there's an edge in g1 not in g2 if the targets of these
// edges do not correspond to each other.
- can_reach[v] = (ve2 != to_g2[ve1]);
+ can_reach[v] = (ve1 != to_g1[ve2]);
} else {
// There's no edge labelled by e incident to the node
- // corresponding to v in g1, but there is such an edge in g2
- // and so (v, e, ve2) is in g2 but not g1.
+ // corresponding to v in g2, but there is such an edge in g1
+ // and so (v, e, ve1) is in g1 but not g2.
can_reach[v] = true;
}
}
- if (seen[ve2]) {
+ if (seen[ve1]) {
// cycle detected
inf_paths[v] = true;
} else {
- stck.push(ve2);
+ stck.push(ve1);
}
}
}
}
}
- // If we reach here, then the appropriate portion of g2 is acyclic, and
+ // If we reach here, then the appropriate portion of g1 is acyclic, and
// so all we do is enumerate the paths in that graph
- // Construct the "can_reach" subgraph of g2, could use a WordGraphView
- // here instead (but these don't yet exist) TODO(later)
- WordGraph wg(g2.number_of_nodes(), g2.out_degree());
+ // Construct the "can_reach" subgraph of g1, could use a WordGraphView
+ // here instead (but these don't yet exist) TODO(1)
+ WordGraph wg(g1.number_of_nodes(), g1.out_degree());
for (auto v : wg.nodes()) {
if (can_reach[v]) {
for (auto e : wg.labels()) {
- auto ve = g2.target_no_checks(v, e);
+ auto ve = g1.target_no_checks(v, e);
if (ve != UNDEFINED && can_reach[ve]) {
wg.target_no_checks(v, e, ve);
}
@@ -1094,44 +1143,64 @@ namespace libsemigroups {
Paths paths(wg);
// We only want those paths that pass through at least one of the edges
- // in g2 but not g1. Hence we require the `filter` in the next
+ // in g1 but not g2. Hence we require the `filter` in the next
// expression.
- auto words = (paths.source(0) | rx::filter([&g1](word_type const& path) {
+ auto words = (paths.source(0) | rx::filter([&g2](word_type const& path) {
return word_graph::last_node_on_path(
- g1, 0, path.cbegin(), path.cend())
+ g2, 0, path.cbegin(), path.cend())
.second
!= path.cend();
}));
// The check in the next loop could be put into the lambda passed to
// filter above, but then we'd have to convert `path` to a string, and
// then discard the string, so better to do it here. Note that the
- // normal forms in `kb1` never contain an edge in g2 \ g1 and so we must
+ // normal forms in `kb2` never contain an edge in g1 \ g2 and so we must
// add in every normal form.
if constexpr (std::is_same_v) {
auto ntc
- = partition(kb1, words | ToString(kb2.presentation().alphabet()));
- for (auto& klass : ntc) {
- klass.push_back(reduce_no_checks(kb1, klass[0]));
- }
- return ntc;
- } else if (!std::is_same_v) {
- auto ntc
- = partition(kb1, (words | rx::transform([](word_type const& w) {
- return Word(w.begin(), w.end());
- })));
+ = partition(kb2, words | ToString(kb1.presentation().alphabet()));
for (auto& klass : ntc) {
- klass.push_back(reduce_no_checks(kb1, klass[0]));
+ klass.push_back(reduce_no_checks(kb2, klass[0]));
}
return ntc;
} else {
- auto ntc = partition(kb1, words);
+ auto ntc = partition(kb2,
+ words | ToString(kb1.presentation().alphabet())
+ | rx::transform([](auto const& w) {
+ return Word(w.begin(), w.end());
+ }));
for (auto& klass : ntc) {
- klass.push_back(reduce_no_checks(kb1, klass[0]));
+ klass.push_back(reduce_no_checks(kb2, klass[0]));
}
return ntc;
}
}
+ template
+ [[nodiscard]] std::vector::const_iterator
+ redundant_rule(Presentation const& p, T t) {
+ constexpr static congruence_kind twosided = congruence_kind::twosided;
+
+ p.validate();
+ Presentation q;
+ q.alphabet(p.alphabet());
+ q.contains_empty_word(p.contains_empty_word());
+ KnuthBendix kb;
+
+ for (auto omit = p.rules.crbegin(); omit != p.rules.crend(); omit += 2) {
+ q.rules.clear();
+ q.rules.insert(q.rules.end(), p.rules.crbegin(), omit);
+ q.rules.insert(q.rules.end(), omit + 2, p.rules.crend());
+ kb.init(twosided, q);
+ kb.run_for(t);
+ if (reduce_no_run_no_checks(kb, *omit)
+ == reduce_no_run_no_checks(kb, *(omit + 1))) {
+ return (omit + 1).base() - 1;
+ }
+ }
+ return p.rules.cend();
+ }
+
template
void by_overlap_length(KnuthBendix& kb) {
size_t prev_max_overlap = kb.max_overlap();
@@ -1147,6 +1216,59 @@ namespace libsemigroups {
kb.check_confluence_interval(prev_check_confluence_interval);
}
+ template
+ [[nodiscard]] bool is_reduced(KnuthBendix& kb) {
+ for (auto const& test_rule : kb.active_rules()) {
+ auto const lhs = test_rule.first;
+ for (auto const& rule : kb.active_rules()) {
+ if (test_rule == rule) {
+ continue;
+ }
+
+ if (rule.first.find(lhs) != detail::internal_string_type::npos
+ || rule.second.find(lhs) != detail::internal_string_type::npos) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ // template
+ // tril try_equal_to(Presentation& p,
+ // std::string const& lhs,
+ // std::string const& rhs,
+ // T t) {
+ // constexpr static congruence_kind twosided = congruence_kind::twosided;
+
+ // // TODO(1) validate lhs and rhs
+ // KnuthBendix kb(twosided, p);
+ // std::string lphbt = p.alphabet();
+ // std::vector perm(lphbt.size(), 0);
+ // std::iota(perm.begin(), perm.end(), 0);
+
+ // do {
+ // detail::apply_permutation(lphbt, perm);
+
+ // p.alphabet(lphbt);
+ // p.validate();
+
+ // kb.init(twosided, p);
+ // // TODO(1) no checks
+ // if (reduce_no_run(kb, lhs) == reduce_no_run(kb, rhs)) {
+ // return tril::TRUE;
+ // }
+ // kb.run_for(t);
+ // // TODO(1) no checks
+ // if (reduce_no_run(kb, lhs) == reduce_no_run(kb, rhs)) {
+ // return tril::TRUE;
+ // } else if (kb.finished()) {
+ // return tril::FALSE;
+ // }
+ // } while (std::next_permutation(perm.begin(), perm.end()));
+ // return tril::unknown;
+ // }
+
} // namespace knuth_bendix
template
@@ -1155,4 +1277,47 @@ namespace libsemigroups {
os << kb.active_rules();
return os;
}
+
+ template
+ std::string
+ to_human_readable_repr(KnuthBendix& kb) {
+ std::string conf, genpairs;
+ if (kb.confluent_known()) {
+ conf = "confluent";
+ if (!kb.confluent()) {
+ conf = "non-" + conf;
+ }
+ }
+ if (kb.number_of_generating_pairs() != 0) {
+ genpairs = fmt::format("{} generating pairs + ",
+ kb.number_of_generating_pairs());
+ }
+
+ return fmt::format(
+ "<{}{} KnuthBendix over {} with {}{}/{} active/inactive rules>",
+ conf,
+ kb.kind() == congruence_kind::twosided ? " 2-sided" : " 1-sided",
+ to_human_readable_repr(kb.presentation()),
+ genpairs,
+ kb.number_of_active_rules(),
+ kb.number_of_inactive_rules());
+ }
+
+ template
+ Presentation
+ to_presentation(KnuthBendix& kb) {
+ if constexpr (std::is_same_v) {
+ auto const& p_orig = kb.presentation();
+ Presentation p;
+ p.alphabet(p_orig.alphabet())
+ .contains_empty_word(p_orig.contains_empty_word());
+
+ for (auto const& rule : kb.active_rules()) {
+ presentation::add_rule(p, rule.first, rule.second);
+ }
+ return p;
+ } else {
+ return to_presentation(to_presentation(kb));
+ }
+ }
} // namespace libsemigroups
diff --git a/include/libsemigroups/to-presentation.hpp b/include/libsemigroups/to-presentation.hpp
index 0292bbfb4..7b31678b8 100644
--- a/include/libsemigroups/to-presentation.hpp
+++ b/include/libsemigroups/to-presentation.hpp
@@ -47,17 +47,16 @@ namespace libsemigroups {
//! semigroup as \p fp, run FroidurePin::run (or any other function that
//! fully enumerates \p fp) prior to calling this function.
//!
- //! \tparam WordOutput the type of the presentation to construct (must be a
- //! type of Presentation).
+ //! \tparam Word the type of the rules in the presentation being constructing.
//!
//! \param fp the FroidurePin object from which to obtain the rules.
//!
- //! \returns An object of type \c Presentation.
+ //! \returns An object of type \c Presentation.
//!
//! \exceptions
//! \no_libsemigroups_except
- template
- Presentation to_presentation(FroidurePinBase& fp);
+ template
+ Presentation to_presentation(FroidurePinBase& fp);
#ifdef PARSED_BY_DOXYGEN
//! \ingroup to_presentation_group
diff --git a/include/libsemigroups/todd-coxeter.hpp b/include/libsemigroups/todd-coxeter.hpp
index 8c72526e1..8b7139493 100644
--- a/include/libsemigroups/todd-coxeter.hpp
+++ b/include/libsemigroups/todd-coxeter.hpp
@@ -1093,6 +1093,36 @@ namespace libsemigroups {
make_citow(last2));
}
+ //! \brief Check containment of a pair of words via iterators.
+ //!
+ //! This function checks whether or not the words represented by the ranges
+ //! \p first1 to \p last1 and \p first2 to \p last2 are already known to be
+ //! contained in the congruence represented by a \ref
+ //! todd_coxeter_class_group "ToddCoxeter" instance. This function performs
+ //! no enumeration, so it is possible for the words to be contained in the
+ //! congruence, but that this is not currently known.
+ //!
+ //! \cong_intf_params_contains
+ //!
+ //! \returns
+ //! * tril::TRUE if the words are known to belong to the congruence;
+ //! * tril::FALSE if the words are known to not belong to the congruence;
+ //! * tril::unknown otherwise.
+ //!
+ //! \cong_intf_throws_if_letters_out_of_bounds
+ template
+ tril currently_contains(Iterator1 first1,
+ Iterator2 last1,
+ Iterator3 first2,
+ Iterator4 last2) const {
+ throw_if_letter_out_of_bounds(first1, last1);
+ throw_if_letter_out_of_bounds(first2, last2);
+ return currently_contains_no_checks(first1, last1, first2, last2);
+ }
+
//! \brief Check containment of a pair of words via iterators.
//!
//! This function checks whether or not the words represented by the ranges
@@ -1108,6 +1138,7 @@ namespace libsemigroups {
//! \cong_intf_warn_undecidable{Todd-Coxeter}
//!
//! \cong_intf_warn_assume_letters_in_bounds
+ // TODO(0) out of line this
template
- tril currently_contains(Iterator1 first1,
- Iterator2 last1,
- Iterator3 first2,
- Iterator4 last2) const {
- throw_if_letter_out_of_bounds(first1, last1);
- throw_if_letter_out_of_bounds(first2, last2);
- return currently_contains_no_checks(first1, last1, first2, last2);
- }
-
//! \brief Check containment of a pair of words via iterators.
//!
//! This function checks whether or not the words represented by the ranges
@@ -3433,7 +3434,7 @@ namespace libsemigroups {
//! iterator pointing to its left hand side is returned.
//!
//! If no rule can be shown to be redundant in this way, then an iterator
- //! pointing to \c p.cend() is returned.
+ //! pointing to \c p.rules.cend() is returned.
//!
//! \tparam Word type of words in the Presentation.
//! \tparam Time type of the 2nd parameter (time to try running
@@ -3442,7 +3443,8 @@ namespace libsemigroups {
//! \param p the presentation.
//! \param t time to run Todd-Coxeter for every omitted rule.
//!
- //! \returns An iterator.
+ //! \returns An iterator pointing at the left-hand side of a redundant rule
+ //! of \c p.rules.cend().
//!
//! \warning The progress of the Todd-Coxeter algorithm may differ between
//! different calls to this function even if the parameters are identical.
@@ -3552,7 +3554,7 @@ namespace libsemigroups {
//!
//! This function returns a range object containing normal forms of the
//! classes of the congruence represented by an instance of ToddCoxeter. The
- //! order of the classes, and the normal form, that is returned are
+ //! order of the classes, and the normal form that is returned, are
//! controlled by ToddCoxeter::standardize(Order). This function triggers a
//! full enumeration of \p tc.
//!
@@ -3711,7 +3713,7 @@ namespace libsemigroups {
//! libsemigroups::word_type "word_type").
//!
//! \param tc1 the \ref todd_coxeter_class_group "ToddCoxeter" instance to
- //! use for partition.
+ //! use for partitioning.
//! \param tc2 the \ref todd_coxeter_class_group "ToddCoxeter" instance to
//! be partitioned.
//!
diff --git a/src/cong.cpp b/src/cong.cpp
index 74d41685f..917c8f847 100644
--- a/src/cong.cpp
+++ b/src/cong.cpp
@@ -139,7 +139,7 @@ namespace libsemigroups {
&& cong.get>()->finished()) {
KnuthBendix<> kb(cong.kind(), p);
auto strings = ::libsemigroups::knuth_bendix::non_trivial_classes(
- *cong.get>(), kb);
+ kb, *cong.get>());
std::vector> result;
for (auto const& klass : strings) {
result.push_back(rx::iterator_range(klass.begin(), klass.end())
@@ -162,7 +162,7 @@ namespace libsemigroups {
if (cong.has>() && cong.get>()->finished()) {
KnuthBendix<> kb(cong.kind(), p);
return ::libsemigroups::knuth_bendix::non_trivial_classes(
- *cong.get>(), kb);
+ kb, *cong.get>());
}
if (cong.has() && cong.get()->finished()) {
ToddCoxeter tc(cong.kind(), p);
diff --git a/tests/test-cong.cpp b/tests/test-cong.cpp
index 2e4ed5157..bb8379371 100644
--- a/tests/test-cong.cpp
+++ b/tests/test-cong.cpp
@@ -210,7 +210,7 @@ namespace libsemigroups {
REQUIRE(cong.has>());
KnuthBendix<> kb(twosided, p);
- REQUIRE(knuth_bendix::non_trivial_classes(*cong.get>(), kb)
+ REQUIRE(knuth_bendix::non_trivial_classes(kb, *cong.get>())
== std::vector>(
{{{1}, {0, 1}, {1, 1}, {0, 1, 1}, {0}}}));
@@ -361,7 +361,7 @@ namespace libsemigroups {
if (cong.has>()) {
KnuthBendix<> kb(twosided, p);
REQUIRE_THROWS_AS(
- knuth_bendix::non_trivial_classes(*cong.get>(), kb),
+ knuth_bendix::non_trivial_classes(kb, *cong.get>()),
LibsemigroupsException);
} else if (cong.has()) {
ToddCoxeter tc(twosided, p);
@@ -468,7 +468,7 @@ namespace libsemigroups {
KnuthBendix<> kb(twosided, p);
auto ntc
- = knuth_bendix::non_trivial_classes(*cong.get>(), kb);
+ = knuth_bendix::non_trivial_classes(kb, *cong.get>());
REQUIRE(ntc.size() == 1);
REQUIRE(ntc[0].size() == 5);
REQUIRE(ntc[0]
@@ -580,7 +580,7 @@ namespace libsemigroups {
KnuthBendix<> kb(twosided, p);
REQUIRE(kb.number_of_classes() == 78);
auto ntc
- = knuth_bendix::non_trivial_classes(*cong.get>(), kb);
+ = knuth_bendix::non_trivial_classes(kb, *cong.get>());
REQUIRE(ntc.size() == 1);
REQUIRE(ntc[0].size() == 78);
}
diff --git a/tests/test-knuth-bendix-1.cpp b/tests/test-knuth-bendix-1.cpp
index 3ff3591fe..4b9a1f081 100644
--- a/tests/test-knuth-bendix-1.cpp
+++ b/tests/test-knuth-bendix-1.cpp
@@ -89,12 +89,13 @@ namespace libsemigroups {
}
} // namespace
-#define KNUTH_BENDIX_TYPES \
- KnuthBendix, KnuthBendix
+ using RewriteTrie = detail::RewriteTrie;
+ using RewriteFromLeft = detail::RewriteFromLeft;
- TEMPLATE_TEST_CASE("confluent fp semigroup 1 (infinite)",
- "[000][quick][knuth-bendix][" __FILE__
- "][" STR(__LINE__) "]",
+#define KNUTH_BENDIX_TYPES RewriteTrie, RewriteFromLeft
+
+ TEMPLATE_TEST_CASE("KnuthBendix: confluent fp semigroup 1 (infinite)",
+ "[000][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
Presentation p;
@@ -102,7 +103,7 @@ namespace libsemigroups {
p.rules = {"ab", "ba", "ac", "ca", "aa", "a", "ac", "a", "ca", "a", "bb",
"bb", "bc", "cb", "bbb", "b", "bc", "b", "cb", "b", "a", "b"};
- TestType kb(twosided, p);
+ KnuthBendix kb(twosided, p);
// kb.process_pending_rules();
@@ -122,7 +123,7 @@ namespace libsemigroups {
// REQUIRE(knuth_bendix::is_reduced(kb));
}
- TEMPLATE_TEST_CASE("confluent fp semigroup 2 (infinite)",
+ TEMPLATE_TEST_CASE("KnuthBendix: confluent fp semigroup 2 (infinite)",
"[001][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -140,7 +141,7 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "cb", "b");
presentation::add_rule_no_checks(p, "a", "b");
- TestType kb(twosided, p);
+ KnuthBendix kb(twosided, p);
// kb.process_pending_rules();
@@ -154,7 +155,7 @@ namespace libsemigroups {
REQUIRE(kb.number_of_classes() == POSITIVE_INFINITY);
}
- TEMPLATE_TEST_CASE("confluent fp semigroup 3 (infinite)",
+ TEMPLATE_TEST_CASE("KnuthBendix: confluent fp semigroup 3 (infinite)",
"[002][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -173,7 +174,7 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "21", "1");
presentation::add_rule_no_checks(p, "0", "1");
- TestType kb(twosided, p);
+ KnuthBendix kb(twosided, p);
// kb.process_pending_rules();
@@ -201,9 +202,10 @@ namespace libsemigroups {
"22222222222"}));
}
- TEMPLATE_TEST_CASE("non-confluent fp semigroup from wikipedia (infinite)",
- "[003][quick][knuth-bendix]",
- KNUTH_BENDIX_TYPES) {
+ TEMPLATE_TEST_CASE(
+ "KnuthBendix: non-confluent fp semigroup from wikipedia (infinite)",
+ "[003][quick][knuth-bendix]",
+ KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
Presentation p;
@@ -213,7 +215,7 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "111", "");
presentation::add_rule_no_checks(p, "010101", "");
- TestType kb(twosided, p);
+ KnuthBendix kb(twosided, p);
REQUIRE(kb.presentation().alphabet() == "01");
REQUIRE(!kb.confluent());
kb.run();
@@ -233,7 +235,7 @@ namespace libsemigroups {
})));
}
- TEMPLATE_TEST_CASE("Example 5.1 in Sims (infinite)",
+ TEMPLATE_TEST_CASE("KnuthBendix: Example 5.1 in Sims (infinite)",
"[004][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -247,7 +249,7 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "dc", "");
presentation::add_rule_no_checks(p, "ca", "ac");
- TestType kb(twosided, p);
+ KnuthBendix kb(twosided, p);
REQUIRE(!kb.confluent());
kb.run();
@@ -262,14 +264,15 @@ namespace libsemigroups {
"ad", "bb", "bc", "bd", "cc", "dd", "aaa",
"aac", "aad", "acc", "add", "bbb", "bbc", "bbd",
"bcc", "bdd", "ccc", "ddd", "aaaa", "aaac", "aaad",
- "aacc", "aadd", "accc", "addd", "bbbb", "bbbc", "bbbd",
- "bbcc", "bbdd", "bccc", "bddd", "cccc", "dddd"}));
+ "aacc", "aadd", "accc", "addd", // codespell:ignore
+ "bbbb", "bbbc", "bbbd", "bbcc", "bbdd", "bccc", "bddd",
+ "cccc", "dddd"}));
REQUIRE((nf.min(0).max(6) | all_of([&kb](auto const& w) {
return knuth_bendix::reduce(kb, w) == w;
})));
}
- TEMPLATE_TEST_CASE("Example 5.1 in Sims (infinite) x 2",
+ TEMPLATE_TEST_CASE("KnuthBendix: Example 5.1 in Sims (infinite) x 2",
"[005][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -280,7 +283,7 @@ namespace libsemigroups {
presentation::add_inverse_rules(p, "AaBb");
presentation::add_rule_no_checks(p, "ba", "ab");
- TestType kb(twosided, p);
+ KnuthBendix kb(twosided, p);
REQUIRE(!kb.confluent());
kb.run();
@@ -302,7 +305,7 @@ namespace libsemigroups {
})));
}
- TEMPLATE_TEST_CASE("Example 5.3 in Sims",
+ TEMPLATE_TEST_CASE("KnuthBendix: Example 5.3 in Sims",
"[006][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -314,7 +317,7 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "bbb", "");
presentation::add_rule_no_checks(p, "ababab", "");
- TestType kb(twosided, p);
+ KnuthBendix kb(twosided, p);
REQUIRE(!kb.confluent());
kb.run();
@@ -343,7 +346,7 @@ namespace libsemigroups {
})));
}
- TEMPLATE_TEST_CASE("Example 5.4 in Sims",
+ TEMPLATE_TEST_CASE("KnuthBendix: Example 5.4 in Sims",
"[007][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -356,7 +359,7 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "bbb", "");
presentation::add_rule_no_checks(p, "ababab", "");
- TestType kb(twosided, p);
+ KnuthBendix kb(twosided, p);
REQUIRE(!kb.confluent());
kb.run();
@@ -380,7 +383,7 @@ namespace libsemigroups {
"baB"}));
}
- TEMPLATE_TEST_CASE("Example 6.4 in Sims (size 168)",
+ TEMPLATE_TEST_CASE("KnuthBendix: Example 6.4 in Sims (size 168)",
"[008][quick][knuth-bendix][no-valgrind]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -395,7 +398,7 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "ababababababab", "");
presentation::add_rule_no_checks(p, "abacabacabacabac", "");
- TestType kb(twosided, p);
+ KnuthBendix kb(twosided, p);
REQUIRE(!kb.confluent());
REQUIRE(!is_obviously_infinite(kb));
@@ -418,7 +421,7 @@ namespace libsemigroups {
REQUIRE(S.generator(2).string(kb) == "c");
}
- TEMPLATE_TEST_CASE("random example",
+ TEMPLATE_TEST_CASE("KnuthBendix: random example",
"[009][quick][knuth-bendix][no-valgrind]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -431,24 +434,24 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "010101", "2");
presentation::add_identity_rules(p, '2');
- TestType kb(twosided, p);
+ KnuthBendix kb(twosided, p);
REQUIRE(!kb.confluent());
kb.run();
REQUIRE(kb.number_of_active_rules() == 9);
REQUIRE(kb.confluent());
- auto& ad = kb.gilman_graph();
- REQUIRE(ad.number_of_nodes() == 9);
- REQUIRE(ad.number_of_edges() == 13);
- REQUIRE(!word_graph::is_acyclic(ad));
+ auto& wg = kb.gilman_graph();
+ REQUIRE(wg.number_of_nodes() == 9);
+ REQUIRE(wg.number_of_edges() == 13);
+ REQUIRE(!word_graph::is_acyclic(wg));
auto fp = to_froidure_pin(kb);
fp.enumerate(100);
auto expected = froidure_pin::current_normal_forms(fp);
- Paths paths(ad);
+ Paths paths(wg);
paths.source(0).min(1).max(fp.current_max_word_length() + 1);
REQUIRE(equal(expected, paths));
@@ -462,7 +465,7 @@ namespace libsemigroups {
}
TEMPLATE_TEST_CASE(
- "SL(2, 7) from Chapter 3, Proposition 1.5 in NR (size 336)",
+ "KnuthBendix: SL(2, 7) from Chapter 3, Proposition 1.5 in NR (size 336)",
"[010][quick][knuth-bendix][no-valgrind]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -479,7 +482,7 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "bB", "");
presentation::add_rule_no_checks(p, "Bb", "");
- TestType kb(twosided, p);
+ KnuthBendix kb(twosided, p);
REQUIRE(!kb.confluent());
@@ -497,18 +500,19 @@ namespace libsemigroups {
// monoid presentation
REQUIRE(S.number_of_generators() == 5);
- auto& ad = kb.gilman_graph();
- REQUIRE(ad.number_of_nodes() == 232);
- REQUIRE(ad.number_of_edges() == 265);
- REQUIRE(word_graph::is_acyclic(ad));
- Paths paths(ad);
+ auto& wg = kb.gilman_graph();
+ REQUIRE(wg.number_of_nodes() == 232);
+ REQUIRE(wg.number_of_edges() == 265);
+ REQUIRE(word_graph::is_acyclic(wg));
+ Paths paths(wg);
paths.source(0).min(0).max(13);
REQUIRE(paths.count() == 336);
}
- TEMPLATE_TEST_CASE("F(2, 5) - Chapter 9, Section 1 in NR (size 11)",
- "[011][knuth-bendix][quick]",
- KNUTH_BENDIX_TYPES) {
+ TEMPLATE_TEST_CASE(
+ "KnuthBendix: F(2, 5) - Chapter 9, Section 1 in NR (size 11)",
+ "[011][knuth-bendix][quick]",
+ KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
Presentation p;
p.alphabet("abcde");
@@ -518,7 +522,7 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "cd", "e");
presentation::add_rule_no_checks(p, "de", "a");
presentation::add_rule_no_checks(p, "ea", "b");
- TestType kb(twosided, p);
+ KnuthBendix kb(twosided, p);
REQUIRE(!kb.confluent());
kb.run();
@@ -526,16 +530,16 @@ namespace libsemigroups {
REQUIRE(kb.confluent());
REQUIRE(kb.number_of_classes() == 11);
- auto& ad = kb.gilman_graph();
- REQUIRE(ad.number_of_nodes() == 8);
- REQUIRE(ad.number_of_edges() == 11);
- REQUIRE(word_graph::is_acyclic(ad));
- Paths paths(ad);
+ auto& wg = kb.gilman_graph();
+ REQUIRE(wg.number_of_nodes() == 8);
+ REQUIRE(wg.number_of_edges() == 11);
+ REQUIRE(word_graph::is_acyclic(wg));
+ Paths paths(wg);
paths.source(0).min(0).max(5);
REQUIRE(paths.count() == 12);
}
- TEMPLATE_TEST_CASE("Reinis example 1",
+ TEMPLATE_TEST_CASE("KnuthBendix: Reinis example 1",
"[012][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -544,22 +548,22 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "a", "abb");
presentation::add_rule_no_checks(p, "b", "baa");
- TestType kb(twosided, p);
+ KnuthBendix kb(twosided, p);
REQUIRE(!kb.confluent());
kb.run();
REQUIRE(kb.number_of_active_rules() == 4);
- auto& ad = kb.gilman_graph();
- REQUIRE(ad.number_of_nodes() == 7);
- REQUIRE(ad.number_of_edges() == 17);
- REQUIRE(!word_graph::is_acyclic(ad));
- Paths paths(ad);
+ auto& wg = kb.gilman_graph();
+ REQUIRE(wg.number_of_nodes() == 7);
+ REQUIRE(wg.number_of_edges() == 17);
+ REQUIRE(!word_graph::is_acyclic(wg));
+ Paths paths(wg);
paths.source(0).min(0).max(10);
REQUIRE(paths.count() == 13'044);
}
- TEMPLATE_TEST_CASE("redundant_rule (std::string)",
+ TEMPLATE_TEST_CASE("KnuthBendix: redundant_rule (std::string)",
"[013][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -579,7 +583,7 @@ namespace libsemigroups {
REQUIRE(*(it + 1) == "baa");
}
- TEMPLATE_TEST_CASE("redundant_rule (word_type)",
+ TEMPLATE_TEST_CASE("KnuthBendix: redundant_rule (word_type)",
"[014][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
using literals::operator""_w;
@@ -601,7 +605,7 @@ namespace libsemigroups {
REQUIRE(*(it + 1) == 100_w);
}
- TEMPLATE_TEST_CASE("constructors/init for finished",
+ TEMPLATE_TEST_CASE("KnuthBendix: constructors/init for finished",
"[015][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -622,7 +626,7 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p2, "111", "");
presentation::add_rule_no_checks(p2, "010101", "");
- TestType kb1(twosided, p1);
+ KnuthBendix kb1(twosided, p1);
REQUIRE(!kb1.confluent());
REQUIRE(!kb1.finished());
kb1.run();
@@ -648,7 +652,7 @@ namespace libsemigroups {
REQUIRE(kb1.confluent_known());
REQUIRE(knuth_bendix::reduce(kb1, "abababbdbcbdbabdbdb") == "bbbbbbddd");
- TestType kb2(std::move(kb1));
+ KnuthBendix kb2(std::move(kb1));
REQUIRE(kb2.confluent());
REQUIRE(kb2.confluent_known());
REQUIRE(kb2.finished());
@@ -670,7 +674,7 @@ namespace libsemigroups {
REQUIRE(knuth_bendix::reduce(kb1, "abababbdbcbdbabdbdb") == "bbbbbbddd");
}
- TEMPLATE_TEST_CASE("constructors/init for partially run",
+ TEMPLATE_TEST_CASE("KnuthBendix: constructors/init for partially run",
"[016][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
using literals::operator""_w;
@@ -687,7 +691,7 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "ababababababab", "");
presentation::add_rule_no_checks(p, "abacabacabacabacabacabacabacabac", "");
- TestType kb1(twosided, p);
+ KnuthBendix kb1(twosided, p);
REQUIRE(!kb1.confluent());
REQUIRE(!kb1.finished());
kb1.run_for(std::chrono::milliseconds(10));
@@ -702,7 +706,7 @@ namespace libsemigroups {
REQUIRE(!kb1.confluent());
REQUIRE(!kb1.finished());
- TestType kb2(kb1);
+ KnuthBendix kb2(kb1);
REQUIRE(!kb2.confluent());
REQUIRE(!kb2.finished());
REQUIRE(kb2.presentation() == p);
@@ -717,7 +721,7 @@ namespace libsemigroups {
REQUIRE(!kb1.finished());
}
- TEMPLATE_TEST_CASE("non-trivial classes",
+ TEMPLATE_TEST_CASE("KnuthBendix: non-trivial classes",
"[017][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -733,36 +737,26 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "bc", "b");
presentation::add_rule_no_checks(p, "cb", "b");
- TestType kb1(twosided, p);
+ KnuthBendix kb1(twosided, p);
presentation::add_rule_no_checks(p, "a", "b");
- TestType kb2(twosided, p);
-
- // TODO uncomment
-
- // REQUIRE(kb1.gilman_graph()
- // == to_word_graph(5,
- // {{3, 1, 2},
- // {UNDEFINED, 4},
- // {UNDEFINED, UNDEFINED, 2},
- // {UNDEFINED, 1}}));
-
- // REQUIRE(kb2.gilman_graph()
- // == to_word_graph(
- // 3, {{2, UNDEFINED, 1}, {UNDEFINED, UNDEFINED, 1}}));
+ KnuthBendix kb2(twosided, p);
REQUIRE(knuth_bendix::contains(kb2, "a", "b"));
REQUIRE(knuth_bendix::contains(kb2, "a", "ba"));
REQUIRE(knuth_bendix::contains(kb2, "a", "bb"));
REQUIRE(knuth_bendix::contains(kb2, "a", "bab"));
- REQUIRE(knuth_bendix::non_trivial_classes(kb2, kb1)
+ REQUIRE(knuth_bendix::non_trivial_classes(kb1, kb2)
== std::vector>(
{{"b", "ab", "bb", "abb", "a"}}));
+ REQUIRE(knuth_bendix::non_trivial_classes(kb1, kb2)
+ == std::vector>(
+ {{{98}, {97, 98}, {98, 98}, {97, 98, 98}, {97}}}));
}
- TEMPLATE_TEST_CASE("non-trivial classes x 2",
+ TEMPLATE_TEST_CASE("KnuthBendix: non-trivial classes x 2",
"[018][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -778,19 +772,19 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "bc", "b");
presentation::add_rule_no_checks(p, "cb", "b");
- TestType kb1(twosided, p);
+ KnuthBendix kb1(twosided, p);
REQUIRE(kb1.number_of_classes() == POSITIVE_INFINITY);
presentation::add_rule_no_checks(p, "b", "c");
- TestType kb2(twosided, p);
+ KnuthBendix kb2(twosided, p);
REQUIRE(kb2.number_of_classes() == 2);
- REQUIRE_THROWS_AS(knuth_bendix::non_trivial_classes(kb2, kb1),
+ REQUIRE_THROWS_AS(knuth_bendix::non_trivial_classes(kb1, kb2),
LibsemigroupsException);
}
- TEMPLATE_TEST_CASE("non-trivial classes x 3",
+ TEMPLATE_TEST_CASE("KnuthBendix: non-trivial classes x 3",
"[019][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -806,18 +800,18 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, "bc", "b");
presentation::add_rule_no_checks(p, "cb", "b");
- TestType kb1(twosided, p);
+ KnuthBendix kb1(twosided, p);
presentation::add_rule_no_checks(p, "bb", "a");
- TestType kb2(twosided, p);
+ KnuthBendix kb2(twosided, p);
- REQUIRE(knuth_bendix::non_trivial_classes(kb2, kb1)
+ REQUIRE(knuth_bendix::non_trivial_classes(kb1, kb2)
== std::vector>(
{{"ab", "b"}, {"bb", "abb", "a"}}));
}
- TEMPLATE_TEST_CASE("non-trivial classes x 4",
+ TEMPLATE_TEST_CASE("KnuthBendix: non-trivial classes x 4",
"[020][quick][knuth-bendix]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -839,19 +833,20 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, 23_w, 2_w);
presentation::add_rule_no_checks(p, 32_w, 2_w);
- TestType kb1(twosided, p);
+ KnuthBendix kb1(twosided, p);
presentation::add_rule_no_checks(p, 0_w, 1_w);
- TestType kb2(twosided, p);
- REQUIRE(knuth_bendix::non_trivial_classes(kb2, kb1)
+ KnuthBendix kb2(twosided, p);
+ REQUIRE(knuth_bendix::non_trivial_classes(kb1, kb2)
== std::vector>(
{{{1}, {0, 1}, {1, 1}, {0, 1, 1}, {0}}}));
}
- TEMPLATE_TEST_CASE("non-trivial congruence on an infinite fp semigroup ",
- "[021][quick][knuth-bendix][no-valgrind]",
- KNUTH_BENDIX_TYPES) {
+ TEMPLATE_TEST_CASE(
+ "KnuthBendix: non-trivial congruence on an infinite fp semigroup ",
+ "[021][quick][knuth-bendix][no-valgrind]",
+ KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
Presentation p;
p.alphabet(5);
@@ -880,7 +875,7 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, 24_w, 2_w);
presentation::add_rule_no_checks(p, 34_w, 3_w);
- TestType kb1(twosided, p);
+ KnuthBendix kb1(twosided, p);
WordGraph test_wg1 = to_word_graph(
6,
@@ -897,7 +892,7 @@ namespace libsemigroups {
(normal_forms_from_word_graph(kb1, test_wg1) | rx::take(1000))));
presentation::add_rule_no_checks(p, 1_w, 2_w);
- TestType kb2(twosided, p);
+ KnuthBendix kb2(twosided, p);
WordGraph test_wg2 = to_word_graph(
5,
@@ -914,15 +909,16 @@ namespace libsemigroups {
REQUIRE(knuth_bendix::contains(kb2, 1_w, 2_w));
- auto ntc = knuth_bendix::non_trivial_classes(kb2, kb1);
+ auto ntc = knuth_bendix::non_trivial_classes(kb1, kb2);
REQUIRE(ntc.size() == 1);
REQUIRE(ntc[0].size() == 2);
REQUIRE(ntc == decltype(ntc)({{{2}, {1}}}));
}
- TEMPLATE_TEST_CASE("non-trivial congruence on an infinite fp semigroup",
- "[022][quick][kbp]",
- KNUTH_BENDIX_TYPES) {
+ TEMPLATE_TEST_CASE(
+ "KnuthBendix: non-trivial congruence on an infinite fp semigroup",
+ "[022][quick][kbp]",
+ KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
Presentation p;
p.alphabet(5);
@@ -951,20 +947,18 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, 24_w, 3_w);
presentation::add_rule_no_checks(p, 34_w, 1_w);
- TestType kb1(twosided, p);
+ KnuthBendix kb1(twosided, p);
presentation::add_rule_no_checks(p, 2_w, 3_w);
- TestType kb2(twosided, p);
- // TODO(0) be good to be able to specify the output type of
- // non_trivial_classes
- auto ntc = knuth_bendix::non_trivial_classes(kb2, kb1);
+ KnuthBendix kb2(twosided, p);
+ auto ntc = knuth_bendix::non_trivial_classes(kb1, kb2);
REQUIRE(ntc.size() == 1);
REQUIRE(ntc[0].size() == 3);
REQUIRE(ntc == decltype(ntc)({{{2}, {3}, {1}}}));
}
- TEMPLATE_TEST_CASE("trivial congruence on a finite fp semigroup",
+ TEMPLATE_TEST_CASE("KnuthBendix: trivial congruence on a finite fp semigroup",
"[023][quick][kbp]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -980,19 +974,20 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, 01010_w, 0100_w);
presentation::add_rule_no_checks(p, 01011_w, 0101_w);
- TestType kb1(twosided, p);
- TestType kb2(twosided, p);
+ KnuthBendix kb1(twosided, p);
+ KnuthBendix kb2(twosided, p);
REQUIRE(!p.contains_empty_word());
REQUIRE(kb1.number_of_classes() == 27);
REQUIRE(kb2.number_of_classes() == 27);
- auto ntc = knuth_bendix::non_trivial_classes(kb2, kb1);
+ auto ntc = knuth_bendix::non_trivial_classes(kb1, kb2);
REQUIRE(ntc.empty());
}
- TEMPLATE_TEST_CASE("universal congruence on a finite fp semigroup",
- "[024][quick][kbp]",
- KNUTH_BENDIX_TYPES) {
+ TEMPLATE_TEST_CASE(
+ "KnuthBendix: universal congruence on a finite fp semigroup",
+ "[024][quick][kbp]",
+ KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
Presentation p;
@@ -1007,16 +1002,16 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, 01010_w, 0100_w);
presentation::add_rule_no_checks(p, 01011_w, 0101_w);
- TestType kb1(twosided, p);
+ KnuthBendix kb1(twosided, p);
presentation::add_rule_no_checks(p, 0_w, 1_w);
presentation::add_rule_no_checks(p, 00_w, 0_w);
- TestType kb2(twosided, p);
+ KnuthBendix kb2(twosided, p);
REQUIRE(kb2.number_of_classes() == 1);
- auto ntc = knuth_bendix::non_trivial_classes(kb2, kb1);
+ auto ntc = knuth_bendix::non_trivial_classes(kb1, kb2);
REQUIRE(ntc.size() == 1);
REQUIRE(ntc[0].size() == 27);
@@ -1052,7 +1047,7 @@ namespace libsemigroups {
REQUIRE(ntc[0] == expected);
}
- TEMPLATE_TEST_CASE("finite fp semigroup, size 16",
+ TEMPLATE_TEST_CASE("KnuthBendix: finite fp semigroup, size 16",
"[025][quick][kbp]",
KNUTH_BENDIX_TYPES) {
auto rg = ReportGuard(false);
@@ -1110,7 +1105,7 @@ namespace libsemigroups {
presentation::add_rule_no_checks(p, {3, 0, 1, 0}, {3, 0, 1});
presentation::add_rule_no_checks(p, {3, 0, 3, 0}, {3, 0, 3});
- TestType kb1(twosided, p);
+ KnuthBendix kb1(twosided, p);
REQUIRE(kb1.gilman_graph().number_of_nodes() == 16);
WordGraph test_wg1 = to_word_graph(16,
@@ -1142,7 +1137,7 @@ namespace libsemigroups {
normal_forms_from_word_graph(kb1, test_wg1)));
presentation::add_rule_no_checks(p, {1}, {3});
- TestType kb2(twosided, p);
+ KnuthBendix