// // libsemigroups - C++ library for semigroups and monoids // Copyright (C) 2019-21 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 <http://www.gnu.org/licenses/>. //
// This file contains an implementation of the Todd-Coxeter algorithm for // semigroups.
//////////////////////////////////////////////////////////////////////////////// // TODO(later) // // * Make process_deductions_dfs non-recursive, this will likely only be an // issue for presentations with extremely long relations. // // * Wreath product standardize mem fn. // //////////////////////////////////////////////////////////////////////////////// // // Notes // // 1. It's suggested in Sims' book in the implementation appendix that using // the actual index of the coset referenced rather than the "row", to // avoid multiplication in get. I implemented this in 2021 and it made no // difference whatsoever, so I backed the changes. // // 2. In 2021, I couldn't figure out a low-cost way of implementing the "fill" // factor. The idea is that we should check that a certain percentage of the // entries in the table are filled before we use preferred definitions. // This doesn't seem to be necessary in any of the examples or tests, i.e. // the resulting enumerations are valid and terminate if we just always use // the preferred definitions and don't try to keep a particular percentage // of the entries in the table filled. It's possible that not using the // preferred definitions when the table is not filled enough, would make the // enumeration terminate faster, but it didn't seem worth the extra cost or // complexity of implementing the fill factor. // // 3. In 2021, I tried in felsch_lookahead to default to hlt_lookahead if there // are more paths (nodes) in the Felsch tree than the number of active // cosets times the number of relations, but found no examples where this // was invoked. So, I scrapped this too. // // 4. In 2021, I tried putting the options save() + standardize() outside the // main loop (+ duplicating a lot of the code) in ToddCoxeter::hlt and this // produced no measurable difference to the performance, so I backed these // changes out too.
#include"libsemigroups/todd-coxeter.hpp"
#include <algorithm> // for reverse #include <array> // for array #include <chrono> // for nanoseconds etc #include <cstddef> // for size_t #include <iterator> // for distance #include <limits> // for numeric_limits #include <memory> // for shared_ptr #include <numeric> // for iota, accumulate #include <ostream> // for operator<< #include <queue> // for queue #include <random> // for mt19937 #include <string> // for operator+, basic_string #include <tuple> // for tie #include <unordered_map> // for unordered_map #include <unordered_set> // for unordered_set #include <utility> // for pair
#ifdef LIBSEMIGROUPS_DEBUG #include <set> // for set #endif
#include"libsemigroups/config.hpp"// for LIBSEMIGROUPS_DEBUG
#include"libsemigroups/adapters.hpp"// for Hash #include"libsemigroups/cong-intf.hpp"// for CongruenceInterface #include"libsemigroups/coset.hpp"// for CosetManager #include"libsemigroups/debug.hpp"// for LIBSEMIGROUPS_ASSERT #include"libsemigroups/digraph-helper.hpp"// for follow_path_nc, last_... #include"libsemigroups/exception.hpp"// for LIBSEMIGROUPS_EXCEPTION #include"libsemigroups/felsch-tree.hpp"// for FelschTree #include"libsemigroups/froidure-pin-base.hpp"// for FroidurePinBase #include"libsemigroups/froidure-pin.hpp"// for FroidurePin #include"libsemigroups/knuth-bendix.hpp"// for fpsemigroup::KnuthBendix #include"libsemigroups/obvinf.hpp"// for IsObviouslyInfinite #include"libsemigroups/report.hpp"// for PrintTable, Reporter #include"libsemigroups/string.hpp"// for to_string #include"libsemigroups/tce.hpp"// for TCE #include"libsemigroups/timer.hpp"// for detail::Timer #include"libsemigroups/types.hpp"// for letter_type #include"libsemigroups/uf.hpp"// for Duf #include"libsemigroups/ukkonen.hpp"// for Ukkonen
// This file is organised as follows: // // 1. Helper functions, types etc in anonymous namespace // 2. ToddCoxeter - inner classes - private // 3. ToddCoxeter - constructors and destructor - public // 4. CongruenceInterface - non-pure virtual member functions - public // 5. CongruenceInterface - non-pure virtual member functions - private // 6. CongruenceInterface - pure virtual member functions - private // 7. ToddCoxeter - member functions (init + settings) - public // 8. ToddCoxeter - member functions (sorting gen. pairs etc) - public // 9. ToddCoxeter - member functions (container-like) - public // 10. ToddCoxeter - member functions (state) - public // 11. ToddCoxeter - member functions (standardization) - public // 12. ToddCoxeter - member functions (attributes) - public // 13. ToddCoxeter - member functions (reporting + stats) - public // 14. ToddCoxeter - member functions (reporting + stats) - private // 15. ToddCoxeter - member functions (validation) - private // 16. ToddCoxeter - member functions (initialisation + settings) - private // 17. ToddCoxeter - member functions (cosets) - private // 18. ToddCoxeter - member functions (main strategies) - private // 19. ToddCoxeter - member functions (deduction processing) - private // 20. ToddCoxeter - member functions (lookahead) - private // 21. ToddCoxeter - member functions (standardize) - private // 22. ToddCoxeter - member functions (debug) - private
void sort_generating_pairs(Perm& perm, std::vector<word_type>& vec) { // Apply the permutation (adapted from stl.hpp:apply_permutation)
size_t const n = perm.size(); for (size_t i = 0; i < n; ++i) {
size_t current = i; while (i != perm[current]) {
size_t next = perm[current];
std::swap(vec[2 * current], vec[2 * next]);
std::swap(vec[2 * current + 1], vec[2 * next + 1]);
perm[current] = current;
current = next;
}
perm[current] = current;
}
}
void sort_generating_pairs(
std::function<bool(word_type const&, word_type const&)> func,
Perm& perm,
std::vector<word_type>& vec) { // Sort each relation so that the lhs is greater than the rhs according // to func. for (auto it = vec.begin(); it < vec.end(); it += 2) { if (func(*it, *(it + 1))) {
std::swap(*it, *(it + 1));
}
}
// Create a permutation of the even indexed entries in vec
perm.resize(vec.size() / 2);
std::iota(perm.begin(), perm.end(), 0);
std::sort(perm.begin(),
perm.end(),
[&func, &vec](class_index_type x, class_index_type y) -> bool { return func(vec[2 * x], vec[2 * y]);
});
sort_generating_pairs(perm, vec);
}
// Attempts to reduce the length of the words in "words" by finding the // equivalence relation on the union of "wrt" and "words" generated by the // pairs in "wrt". If A = {u_1, u_2, ..., u_n} are the distinct words in an // equivalence class on "words" and u_1 is the short-lex minimum word in the // class, then the words in "words" involving the words in A are replaced by: // u_1 = u_2, u_1 = u_3, ..., u_1 = u_n. class PairsSimplifier { public:
PairsSimplifier() = default;
PairsSimplifier& add(std::vector<word_type> const& words) {
LIBSEMIGROUPS_ASSERT(words.size() % 2 == 0);
_duf.resize(_duf.size() + words.size()); // Create equivalence relation of the equal relations for (size_t i = 0; i < words.size(); ++i) { if (i % 2 == 0) {
_duf.unite(i, i + 1);
} autoconst& current_word = words[i];
decltype(_map)::iterator it; bool inserted;
std::tie(it, inserted) = _map.emplace(current_word, i); if (!inserted) {
_duf.unite(it->second, i);
}
} return *this;
}
void apply(std::vector<word_type>& words) { using libsemigroups::shortlex_compare;
LIBSEMIGROUPS_ASSERT(words.size() % 2 == 0); // Class index -> index of min. length words in wrt + words
std::unordered_map<size_t, word_type> mins; if (words.empty()) { return;
}
// Find index of minimum length word in every class for (autoconst& word : words) { auto i = _map.find(word)->second; auto j = _duf.find(i);
decltype(mins)::iterator it; bool inserted;
std::tie(it, inserted) = mins.emplace(j, word); autoconst& min_word = it->second; if (!inserted && shortlex_compare(word, min_word)) {
it->second = word;
}
}
std::vector<word_type> result; for (auto it = _map.cbegin(); it != _map.cend(); ++it) { autoconst& word = it->first; autoconst& index = it->second; autoconst& min_word = mins.find(_duf.find(index))->second; if (min_word != word) {
result.push_back(min_word);
result.push_back(word);
}
}
std::swap(words, result);
}
// A class for managing Deductions (recently changed or defined values of // the form std::pair(coset_type, letter_type). class ToddCoxeter::Deductions { public: explicit Deductions(ToddCoxeter* tc)
: _any_skipped(false), _deduct(), _tc(tc) {}
Deduction pop() { auto res = _deduct.top();
_deduct.pop(); return res;
}
void emplace(coset_type c, letter_type x) { using opt = options::deductions; if (_deduct.size() < _tc->max_deductions()) {
_deduct.emplace(c, x); #ifdef LIBSEMIGROUPS_ENABLE_STATS
_tc->_stats.max_deduct = std::max(
_tc->_stats.max_deduct, static_cast<uint64_t>(_deduct.size())); #endif
} else {
_any_skipped = true; if (_tc->deduction_policy() & opt::purge_from_top) { while (!_deduct.empty()
&& !_tc->is_active_coset(_deduct.top().first)) {
_deduct.pop();
}
} elseif (_tc->deduction_policy() & opt::purge_all) {
std::stack<Deduction> copy; while (!_deduct.empty()) { auto d = pop(); if (_tc->is_active_coset(d.first)) {
copy.push(d);
}
}
std::swap(copy, _deduct);
} elseif (_tc->deduction_policy() & opt::discard_all_if_no_space) {
clear();
}
}
}
// Construct a ToddCoxeter object representing a congruence over the // semigroup defined by copy (the quotient that is).
ToddCoxeter::ToddCoxeter(congruence_kind typ, ToddCoxeter& copy)
: ToddCoxeter(typ) { if (copy.kind() != congruence_kind::twosided && typ != copy.kind()) {
LIBSEMIGROUPS_EXCEPTION("incompatible types of congruence, found ("
+ congruence_kind_to_string(copy.kind()) + " / "
+ congruence_kind_to_string(typ)
+ ") but only (left / left), (right / " "right), (two-sided / *) are valid");
}
copy_relations_for_quotient(copy);
}
ToddCoxeter::ToddCoxeter(congruence_kind typ, fpsemigroup::KnuthBendix& kb)
: ToddCoxeter(typ) {
set_number_of_generators(kb.alphabet().size()); // TODO(later) use active rules when available for (auto it = kb.cbegin_rules(); it < kb.cend_rules(); ++it) {
add_pair(kb.string_to_word(it->first), kb.string_to_word(it->second));
} // Note that something goes horribly wrong if the next line is above the // for loop above.
set_parent_froidure_pin(kb); if (kb.finished() && kb.is_obviously_finite()) {
LIBSEMIGROUPS_ASSERT(froidure_pin_policy()
== options::froidure_pin::none);
froidure_pin_policy(options::froidure_pin::use_cayley_graph);
}
}
ToddCoxeter::~ToddCoxeter() { while (!_setting_stack.empty()) {
pop_settings();
}
}
//////////////////////////////////////////////////////////////////////// // 4. CongruenceInterface - non-pure virtual member functions - public ////////////////////////////////////////////////////////////////////////
bool ToddCoxeter::contains(word_type const& lhs, word_type const& rhs) {
validate_word(lhs);
validate_word(rhs);
init_generating_pairs(); if (empty()) { // Note that it's possible to be not _prefilled, and have _relations, // and _extra empty, because shrink_to_fit clears _relations and // _extra. That's why we use empty() here instead of checking // _prefilled && _relations.empty() && _extra.empty(), as used to be // the case. This defines the free semigroup return lhs == rhs;
} return CongruenceInterface::contains(lhs, rhs);
}
bool ToddCoxeter::is_quotient_obviously_finite_impl() { if (finished()) { returntrue;
}
init_generating_pairs(); return _prefilled; // _prefilled means that either we were created from a FroidurePinBase* // with froidure_pin_policy() == use_cayley_graph and we successfully // prefilled the table, or we manually prefilled the table. In this // case the semigroup defined by _relations must be finite.
}
std::shared_ptr<FroidurePinBase> ToddCoxeter::quotient_impl() { using detail::TCE;
run();
standardize(order::shortlex);
shrink_to_fit(); // Ensure class indices and letters are equal! auto table = std::make_shared<table_type>(_word_graph.table());
size_t n = number_of_generators(); for (letter_type a = 0; a < n;) { if (table->get(0, a) != a + 1) {
table->erase_column(a);
n--;
} else {
++a;
}
} auto ptr = std::make_shared<
FroidurePin<TCE, FroidurePinTraits<TCE, table_type>>>(table); for (size_t i = 0; i < number_of_generators(); ++i) { // We use table.get(0, i) instead of just i, because there might be // more generators than cosets.
ptr->add_generator(TCE(_word_graph.unsafe_neighbor(0, i)));
} return ptr;
}
void ToddCoxeter::run_impl() { if (is_quotient_obviously_infinite()) {
LIBSEMIGROUPS_EXCEPTION( "there are infinitely many classes in the congruence and " "Todd-Coxeter will never terminate");
}
if (strategy() == options::strategy::felsch) {
felsch();
} elseif (strategy() == options::strategy::hlt) {
hlt();
} elseif (strategy() == options::strategy::random) { if (running_for()) {
LIBSEMIGROUPS_EXCEPTION( "the strategy \"%s\" is incompatible with run_for!",
detail::to_string(strategy()).c_str());
}
random();
} else { if (running_until()) {
LIBSEMIGROUPS_EXCEPTION( "the strategy \"%s\" is incompatible with run_until!",
detail::to_string(strategy()).c_str());
}
class_index_type ToddCoxeter::word_to_class_index_impl(word_type const& w) {
run();
LIBSEMIGROUPS_ASSERT(finished()); if (!is_standardized()) {
standardize(order::shortlex);
} return const_word_to_class_index(w); // c is in the range 1, ..., number_of_cosets_active() because 0 // represents the identity coset, and does not correspond to an element.
}
//////////////////////////////////////////////////////////////////////// // 7. ToddCoxeter - member functions (init_generating_pairs + settings) - // public ////////////////////////////////////////////////////////////////////////
void ToddCoxeter::pop_settings() { if (!_setting_stack.empty()) {
_settings.reset(_setting_stack.top());
_setting_stack.pop();
}
}
//////////////////////////////////////////////////////////////////////// // 8. ToddCoxeter - member functions (sorting gen. pairs etc) - public ////////////////////////////////////////////////////////////////////////
ToddCoxeter& ToddCoxeter::random_shuffle_generating_pairs() { if (started()) {
LIBSEMIGROUPS_EXCEPTION("Cannot shuffle the generating pairs, the " "enumeration has started!")
}
init_generating_pairs();
Perm perm(0, _relations.size());
std::iota(perm.begin(), perm.end(), 0);
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(perm.begin(), perm.end(), g);
::sort_generating_pairs(perm, _relations);
::sort_generating_pairs(perm, _extra); return *this;
}
ToddCoxeter& ToddCoxeter::remove_duplicate_generating_pairs() { if (started()) {
LIBSEMIGROUPS_EXCEPTION("Cannot remove duplicate generating pairs, the " "enumeration has started!")
}
init_generating_pairs();
std::unordered_set<relation_type, Hash<relation_type>> relations_set; for (size_t i = 0; i < _relations.size(); i += 2) { if (shortlex_compare(_relations[i], _relations[i + 1])) {
relations_set.emplace(_relations[i], _relations[i + 1]);
} else {
relations_set.emplace(_relations[i + 1], _relations[i]);
}
}
_relations.clear(); for (autoconst& p : relations_set) {
_relations.push_back(p.first);
_relations.push_back(p.second);
}
std::unordered_set<relation_type, Hash<relation_type>> extra_set;
for (size_t i = 0; i < _extra.size(); i += 2) { if (shortlex_compare(_extra[i], _extra[i + 1])) { if (relations_set.emplace(_extra[i], _extra[i + 1]).second) {
extra_set.emplace(_extra[i], _extra[i + 1]);
}
} else { if (relations_set.emplace(_extra[i + 1], _extra[i]).second) {
extra_set.emplace(_extra[i + 1], _extra[i]);
}
}
}
_extra.clear(); for (autoconst& p : extra_set) {
_extra.push_back(p.first);
_extra.push_back(p.second);
} return *this;
}
ToddCoxeter& ToddCoxeter::simplify(size_t n) {
init_generating_pairs(); if (started()) {
LIBSEMIGROUPS_EXCEPTION("the enumeration has started, it is no longer " "possible to change the generating pairs!");
} elseif (_prefilled) { // There's no actual reason that we cannot do this here, just reducing // complexity of introducing this feature. But it would mean making a // fundamental change away from assuming that _relations is empty if // _prefilled is true. So, I am opting not to do this now.
LIBSEMIGROUPS_EXCEPTION("the table has been prefilled, it is no longer " "possible to change the generating pairs!");
} if (_felsch_tree != nullptr) {
_felsch_tree = nullptr;
}
PairsSimplifier p; // Replace words in _relations with min. equiv. word in _relations
p.add(_relations).apply(_relations);
p.add(_extra).apply(_extra);
remove_duplicate_generating_pairs(); // Reduce the length of the presentation by introducing new generators, // until either we reach n, or we cannot reduce the presentation any // further for (size_t i = 0; i < n; ++i) { if (!reduce_length_once()) { return *this;
}
} return *this;
}
ukkonen::detail::GreedyReduceHelper helper(u);
const_iterator_word first, last; // Get the best word [first, last) so that replacing every // non-overlapping occurrence of [first, last) in _relations and // _extra with a new generator "x", and adding "x = [first, last)" as // a relation reduces the length of the presentation as much as // possible.
std::tie(first, last) = ukkonen::dfs(u, helper); if (first == last) { returnfalse; // no change
}
letter_type const x = number_of_generators();
add_generators(1); auto replace_subword = [&first, &last, &x](word_type& word) { auto it = std::search(word.begin(), word.end(), first, last); while (it != word.end()) { // found [first, last)
*it = x; auto pos = it - word.begin();
word.erase(it + 1, it + (last - first)); // it not valid
it = std::search(word.begin() + pos + 1, word.end(), first, last);
}
};
std::for_each(_relations.begin(), _relations.end(), replace_subword);
_relations.emplace_back(word_type({x}));
_relations.emplace_back(first, last);
std::for_each(_extra.begin(), _extra.end(), replace_subword); returntrue;
}
//////////////////////////////////////////////////////////////////////// // 9. ToddCoxeter - member functions (container-like) - public ////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////// // 10. ToddCoxeter - member functions (state) - public ////////////////////////////////////////////////////////////////////////
bool ToddCoxeter::complete() const noexcept {
size_t const n = number_of_generators();
coset_type c = _id_coset; while (c != first_free_coset()) { for (size_t a = 0; a < n; ++a) { if (_word_graph.unsafe_neighbor(c, a) == UNDEFINED) { returnfalse;
}
}
c = next_active_coset(c);
} returntrue;
}
template <typename T> bool ToddCoxeter::compatible(coset_type c, T first, T last) const { for (auto it = first; it < last; it += 2) {
coset_type x = action_digraph_helper::follow_path_nc(
_word_graph, c, (*it).cbegin(), (*it).cend());
LIBSEMIGROUPS_ASSERT(is_active_coset(x) || x == UNDEFINED);
coset_type y = action_digraph_helper::follow_path_nc(
_word_graph, c, (*(it + 1)).cbegin(), (*(it + 1)).cend());
LIBSEMIGROUPS_ASSERT(is_active_coset(y) || y == UNDEFINED); if (x != y || x == UNDEFINED) { returnfalse;
}
} returntrue;
}
bool ToddCoxeter::compatible() const noexcept {
coset_type c = _id_coset; if (!compatible(c, _extra.cbegin(), _extra.cend())) { returnfalse;
} while (c != first_free_coset()) { if (!compatible(c, _relations.cbegin(), _relations.cend())) { returnfalse;
}
c = next_active_coset(c);
} returntrue;
}
size_t ToddCoxeter::length_of_generating_pairs() {
init_generating_pairs(); auto op = [](size_t val, word_type const& x) { return val + x.size(); };
size_t N = std::accumulate(
_relations.cbegin(), _relations.cend(), size_t(0), op);
N += std::accumulate(_extra.cbegin(), _extra.cend(), size_t(0), op); return N;
}
std::string ToddCoxeter::to_gap_string() {
std::string const alphabet
= "abcdefghijklmnopqrstuvwxyzABCDFGHIJKLMNOPQRSTUVWY"; if (kind() != congruence_kind::twosided) {
LIBSEMIGROUPS_EXCEPTION( "expected congruence kind to be twosided, found "
+ congruence_kind_to_string(kind()));
} if (number_of_generators() > 49) {
LIBSEMIGROUPS_EXCEPTION("expected at most 49 generators, found %llu!",
uint64_t(number_of_generators()));
}
init_generating_pairs();
auto to_gap_word = [&alphabet](word_type const& w) -> std::string {
std::string out; for (auto it = w.cbegin(); it < w.cend() - 1; ++it) {
out += alphabet[*it];
out += " * ";
}
out += alphabet[w.back()]; return out;
};
std::string out = "free := FreeSemigroup("; for (size_t i = 0; i < number_of_generators(); ++i) {
out += std::string("\"") + alphabet[i] + "\""; if (i != number_of_generators() - 1) {
out += ", ";
}
}
out += ");\n";
out += "DoAssignGenVars(GeneratorsOfSemigroup(free));\n";
out += "rules := [\n"; for (auto it = _relations.cbegin(); it < _relations.cend(); it += 2) {
out += " [";
out += to_gap_word(*it);
out += ", ";
out += to_gap_word(*(it + 1)); if (it != _relations.cend() - 2) {
out += "],\n";
} else {
out += "]\n";
}
} for (auto it = _extra.cbegin(); it < _extra.cend(); it += 2) {
out += " [";
out += to_gap_word(*it);
out += ", ";
out += to_gap_word(*(it + 1)); if (it != _extra.cend() - 2) {
out += "],\n";
} else {
out += "]\n";
}
}
out += " ];\n";
out += "S := free / rules;\n"; return out;
}
//! Copy all _relations and _extra from copy into _relations of this void ToddCoxeter::copy_relations_for_quotient(ToddCoxeter& copy) {
REPORT_DEBUG_DEFAULT("copying relations for quotient...\n");
LIBSEMIGROUPS_ASSERT(empty());
copy.init_generating_pairs(); if (copy.number_of_generators() == UNDEFINED) { return;
}
set_number_of_generators(copy.number_of_generators());
_state = state::relation_extra_initialized;
_relations = copy._relations;
_relations.insert(
_relations.end(), copy._extra.cbegin(), copy._extra.cend()); if (kind() == congruence_kind::left
&& copy.kind() != congruence_kind::left) { for (auto& w : _relations) {
std::reverse(w.begin(), w.end());
}
}
_nr_pairs_added_earlier = 0;
}
void ToddCoxeter::init_generating_pairs() { if (_state == state::constructed) {
REPORT_DEBUG_DEFAULT("initializing...\n"); // Add the relations/Cayley graph from parent() if any. if (has_parent_froidure_pin()
&& parent_froidure_pin()->is_finite() == tril::TRUE) { if (froidure_pin_policy() == options::froidure_pin::use_cayley_graph
|| froidure_pin_policy() == options::froidure_pin::none) {
REPORT_DEBUG_DEFAULT("using Cayley graph...\n");
LIBSEMIGROUPS_ASSERT(_relations.empty());
prefill(*parent_froidure_pin()); #ifdef LIBSEMIGROUPS_DEBUG // This is a check of program logic, since we use parent() to fill // the table, so we only validate in debug mode.
validate_table(
_word_graph.table(), 1, parent_froidure_pin()->size() + 1); #endif
} else {
REPORT_DEBUG_DEFAULT("using presentation...\n");
LIBSEMIGROUPS_ASSERT(froidure_pin_policy()
== options::froidure_pin::use_relations); auto fp = parent_froidure_pin();
fp->run(); for (auto it = fp->cbegin_rules(); it != fp->cend_rules(); ++it) {
reverse_if_necessary_and_push_back(this, it->first, _relations);
reverse_if_necessary_and_push_back(this, it->second, _relations);
} #ifdef LIBSEMIGROUPS_DEBUG // This is a check of program logic, since we use parent() to // obtain the relations, so we only validate in debug mode. for (autoconst& rel : _relations) {
validate_word(rel);
} // We don't add anything to _extra here so no need to check. #endif
}
}
_state = state::relation_extra_initialized;
}
// Get new generating pairs (if any) from CongruenceInterface (if any) auto it = cbegin_generating_pairs() + _nr_pairs_added_earlier; if (kind() != congruence_kind::twosided) { for (; it < cend_generating_pairs(); ++it) {
reverse_if_necessary_and_push_back(this, it->first, _extra);
reverse_if_necessary_and_push_back(this, it->second, _extra);
}
} else { for (; it < cend_generating_pairs(); ++it) {
reverse_if_necessary_and_push_back(this, it->first, _relations);
reverse_if_necessary_and_push_back(this, it->second, _relations);
}
}
_nr_pairs_added_earlier
= cend_generating_pairs() - cbegin_generating_pairs();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.