//
// 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
////////////////////////////////////////////////////////////////////////
// 1. Helper functions, types etc in anonymous namespace
////////////////////////////////////////////////////////////////////////
using coset_type = libsemigroups::detail::CosetManager::coset_type;
namespace {
using class_index_type = libsemigroups::CongruenceInterface::class_index_type;
using word_type = libsemigroups::word_type;
using Perm =
typename libsemigroups::detail::CosetManager::Perm;
template <
typename T>
using EqualTo =
typename libsemigroups::EqualTo<T>;
template <
typename T>
using Hash =
typename libsemigroups::Hash<T>;
void reverse_if_necessary_and_push_back(
libsemigroups::congruence::ToddCoxeter
const* tc,
word_type w,
std::vector<word_type>& v) {
if (tc->kind() == libsemigroups::congruence_kind::left) {
std::reverse(w.begin(), w.end());
}
v.push_back(std::move(w));
}
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);
}
auto const& 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;
}
bool trivial()
const {
return (_duf.number_of_blocks() == _map.size());
}
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 (
auto const& 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);
auto const& 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) {
auto const& word = it->first;
auto const& index = it->second;
auto const& 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);
}
private:
libsemigroups::detail::Duf<> _duf;
std::unordered_map<word_type, size_t, Hash<word_type>, EqualTo<word_type>>
_map;
};
struct Noop {
template <
typename... Args>
void operator()(Args...)
const noexcept {}
template <
typename T>
void operator()(libsemigroups::congruence::ToddCoxeter*)
const noexcept {}
};
using DoNotProcessCoincidences = Noop;
using NoPreferredDefs = Noop;
using DoNotStackDeductions = Noop;
}
// namespace
namespace libsemigroups {
using detail::FelschTree;
namespace congruence {
////////////////////////////////////////////////////////////////////////
// 2. ToddCoxeter - inner classes - private
////////////////////////////////////////////////////////////////////////
// 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();
}
}
else if (_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);
}
else if (_tc->deduction_policy() & opt::discard_all_if_no_space) {
clear();
}
}
}
bool any_skipped()
const noexcept {
return _any_skipped;
}
// noexcept correct?
bool empty()
const noexcept {
return _deduct.empty();
}
size_t size()
const noexcept {
return _deduct.size();
}
void clear() {
if (!_deduct.empty()) {
_any_skipped =
true;
std::stack<Deduction> copy;
std::swap(copy, _deduct);
}
}
private:
bool _any_skipped;
std::stack<Deduction> _deduct;
ToddCoxeter* _tc;
};
struct ToddCoxeter::Settings {
bool use_relations_in_extra =
false;
size_t deduct_max = 2
'000;
options::deductions deduct_policy
= options::deductions::no_stack_if_no_space | options::deductions::v2;
size_t f_defs = 100
'000;
options::froidure_pin froidure_pin = options::froidure_pin::none;
size_t hlt_defs = 200
'000;
size_t large_collapse = 100
'000;
options::lookahead lookahead
= options::lookahead::partial | options::lookahead::hlt;
float lookahead_growth_factor = 2.0;
size_t lookahead_growth_threshold = 4;
size_t lower_bound = UNDEFINED;
size_t max_preferred_defs = 256;
size_t min_lookahead = 10
'000;
size_t next_lookahead = 5
'000'000;
options::preferred_defs preferred_defs
= options::preferred_defs::deferred;
std::chrono::nanoseconds random_interval = std::chrono::milliseconds(200);
bool restandardize =
false;
bool save =
false;
bool standardize =
false;
options::strategy strategy = options::strategy::hlt;
};
class ToddCoxeter::PreferredDefs {
public:
explicit PreferredDefs(ToddCoxeter* tc) : _defs(), _tc(tc) {}
bool empty()
const noexcept {
return _defs.empty();
}
Deduction pop() {
Deduction d = _defs.front();
_defs.pop();
return d;
}
void emplace(coset_type c, letter_type x) {
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_tc->_stats.total_preferred_defs++;
#endif
_defs.emplace(c, x);
if (_defs.size() > _tc->max_preferred_defs()) {
_defs.pop();
}
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_tc->_stats.max_preferred_defs
= std::max(_tc->_stats.max_preferred_defs, uint64_t(_defs.size()));
#endif
}
void purge_from_top() {
while (!_defs.empty()
&& (!_tc->is_active_coset(_defs.front().first)
|| _tc->_word_graph.unsafe_neighbor(_defs.front().first,
_defs.front().second)
!= UNDEFINED)) {
_defs.pop();
}
}
private:
std::queue<Deduction> _defs;
ToddCoxeter* _tc;
};
struct ToddCoxeter::TreeNode {
TreeNode() : parent(UNDEFINED), gen(UNDEFINED) {}
TreeNode(coset_type p, letter_type g) : parent(p), gen(g) {}
coset_type parent;
letter_type gen;
};
struct ToddCoxeter::QueuePreferredDefs {
void operator()(ToddCoxeter* tc,
coset_type x,
letter_type a,
coset_type y,
letter_type b) {
tc->_preferred_defs->emplace(x, a);
tc->_preferred_defs->emplace(y, b);
}
};
struct ToddCoxeter::StackDeductions {
inline void operator()(ToddCoxeter::Deductions* stck,
coset_type c,
letter_type a)
const noexcept {
stck->emplace(c, a);
}
};
template <
typename TStackDeduct>
struct ToddCoxeter::ImmediateDef {
void operator()(ToddCoxeter* tc,
coset_type x,
letter_type a,
coset_type y,
letter_type b)
const {
coset_type d = tc->new_coset();
tc->def_edge<TStackDeduct>(x, a, d);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
tc->_stats.tc2_good_appl++;
if (tc->strategy() == options::strategy::hlt) {
tc->_stats.tc1_hlt_appl++;
}
else {
tc->_stats.tc1_f_appl++;
}
#endif
if (a != b || x != y) {
tc->def_edge<TStackDeduct>(y, b, d);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
tc->_stats.tc2_good_appl++;
#endif
}
}
};
////////////////////////////////////////////////////////////////////////
// 3. ToddCoxeter - constructors and destructor - public
////////////////////////////////////////////////////////////////////////
ToddCoxeter::ToddCoxeter(congruence_kind type)
: CongruenceInterface(type),
CosetManager(),
_coinc(),
_deduct(
new Deductions(
this)),
_extra(),
_felsch_tree(nullptr),
_nr_pairs_added_earlier(0),
_prefilled(
false),
_preferred_defs(
new PreferredDefs(
this)),
_relations(),
_settings(
new Settings()),
_standard_max(0),
_standardized(order::none),
_state(state::constructed),
_tree(nullptr),
_word_graph() {}
ToddCoxeter::ToddCoxeter(ToddCoxeter
const& copy)
: CongruenceInterface(copy),
CosetManager(copy),
_coinc(copy._coinc),
_deduct(std::make_unique<Deductions>(*copy._deduct)),
_extra(copy._extra),
_felsch_tree(nullptr),
_nr_pairs_added_earlier(copy._nr_pairs_added_earlier),
_prefilled(copy._prefilled),
_preferred_defs(
std::make_unique<PreferredDefs>(*copy._preferred_defs)),
_relations(copy._relations),
_settings(std::make_unique<Settings>(*copy._settings)),
_standard_max(copy._standard_max),
_standardized(copy._standardized),
_state(copy._state),
_tree(nullptr),
_word_graph(copy._word_graph) {
if (copy._felsch_tree != nullptr) {
_felsch_tree = std::make_unique<FelschTree>(*copy._felsch_tree);
}
if (copy._tree != nullptr) {
_tree = std::make_unique<Tree>(*copy._tree);
}
}
ToddCoxeter::ToddCoxeter(congruence_kind type,
std::shared_ptr<FroidurePinBase> S,
options::froidure_pin p)
: ToddCoxeter(type) {
froidure_pin_policy(p);
set_parent_froidure_pin(S);
set_number_of_generators(S->number_of_generators());
}
// 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::ToddCoxeter& copy)
: ToddCoxeter(typ) {
set_parent_froidure_pin(copy);
if (copy.finished()) {
set_number_of_generators(copy.froidure_pin()->number_of_generators());
froidure_pin_policy(options::froidure_pin::use_cayley_graph);
}
else {
copy_relations_for_quotient(copy.congruence());
froidure_pin_policy(options::froidure_pin::use_relations);
}
}
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);
}
////////////////////////////////////////////////////////////////////////
// 5. CongruenceInterface - non-pure virtual member functions - private
////////////////////////////////////////////////////////////////////////
class_index_type
ToddCoxeter::const_word_to_class_index(word_type
const& w)
const {
validate_word(w);
coset_type c = _id_coset;
if (kind() == congruence_kind::left) {
c = action_digraph_helper::follow_path_nc(
_word_graph, c, w.crbegin(), w.crend());
}
else {
c = action_digraph_helper::follow_path_nc(
_word_graph, c, w.cbegin(), w.cend());
}
return (c == UNDEFINED ? UNDEFINED
:
static_cast<class_index_type>(c - 1));
}
bool ToddCoxeter::is_quotient_obviously_finite_impl() {
if (finished()) {
return true;
}
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.
}
bool ToddCoxeter::is_quotient_obviously_infinite_impl() {
if (finished()) {
return false;
}
init_generating_pairs();
if (_prefilled) {
return false;
}
else if (number_of_generators() > _relations.size() + _extra.size()) {
return true;
}
detail::IsObviouslyInfinite ioi(number_of_generators());
ioi.add_rules(_relations.cbegin(), _relations.cend());
ioi.add_rules(_extra.cbegin(), _extra.cend());
return ioi.result();
}
void ToddCoxeter::set_number_of_generators_impl(size_t n) {
LIBSEMIGROUPS_ASSERT(_word_graph.out_degree() == 0);
LIBSEMIGROUPS_ASSERT(_word_graph.number_of_nodes() == 0);
_word_graph.add_nodes(1);
_word_graph.add_to_out_degree(n);
}
void ToddCoxeter::add_generators_impl(size_t n) {
LIBSEMIGROUPS_ASSERT(_word_graph.out_degree() != 0);
LIBSEMIGROUPS_ASSERT(_word_graph.number_of_nodes() == 1);
_word_graph.add_to_out_degree(n);
}
////////////////////////////////////////////////////////////////////////
// 5. CongruenceInterface - pure virtual member functions - private
////////////////////////////////////////////////////////////////////////
word_type ToddCoxeter::class_index_to_word_impl(class_index_type i) {
run();
if (!is_standardized()) {
standardize(order::shortlex);
}
LIBSEMIGROUPS_ASSERT(finished());
word_type w;
TreeNode tn = (*_tree)[i + 1];
while (tn.parent != UNDEFINED) {
w.push_back(tn.gen);
tn = (*_tree)[tn.parent];
}
if (kind() != congruence_kind::left) {
std::reverse(w.begin(), w.end());
}
return w;
}
size_t ToddCoxeter::number_of_classes_impl() {
run();
LIBSEMIGROUPS_ASSERT(finished());
return number_of_cosets_active() - 1;
}
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();
}
else if (strategy() == options::strategy::hlt) {
hlt();
}
else if (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());
}
if (strategy() == options::strategy::CR) {
CR_style();
}
else if (strategy() == options::strategy::R_over_C) {
R_over_C_style();
}
else if (strategy() == options::strategy::Cr) {
Cr_style();
}
else if (strategy() == options::strategy::Rc) {
Rc_style();
}
}
}
bool ToddCoxeter::finished_impl()
const {
return _state == state::finished;
}
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
////////////////////////////////////////////////////////////////////////
// Settings
ToddCoxeter&
ToddCoxeter::froidure_pin_policy(options::froidure_pin x) noexcept {
_settings->froidure_pin = x;
return *
this;
}
ToddCoxeter::options::froidure_pin
ToddCoxeter::froidure_pin_policy()
const noexcept {
return _settings->froidure_pin;
}
ToddCoxeter& ToddCoxeter::lookahead(options::lookahead x) noexcept {
if (!(x & options::lookahead::felsch) && !(x & options::lookahead::hlt)) {
x = x | options::lookahead::hlt;
}
_settings->lookahead = x;
return *
this;
}
ToddCoxeter::options::lookahead ToddCoxeter::lookahead()
const noexcept {
return _settings->lookahead;
}
ToddCoxeter& ToddCoxeter::lower_bound(size_t n) noexcept {
_settings->lower_bound = n;
return *
this;
}
size_t ToddCoxeter::lower_bound()
const noexcept {
return _settings->lower_bound;
}
ToddCoxeter& ToddCoxeter::next_lookahead(size_t n) noexcept {
_settings->next_lookahead = n;
return *
this;
}
size_t ToddCoxeter::next_lookahead()
const noexcept {
return _settings->next_lookahead;
}
ToddCoxeter& ToddCoxeter::min_lookahead(size_t n) noexcept {
_settings->min_lookahead = n;
return *
this;
}
size_t ToddCoxeter::min_lookahead()
const noexcept {
return _settings->min_lookahead;
}
ToddCoxeter& ToddCoxeter::standardize(
bool x) noexcept {
_settings->standardize = x;
return *
this;
}
bool ToddCoxeter::standardize()
const noexcept {
return _settings->standardize;
}
ToddCoxeter& ToddCoxeter::save(
bool x) {
if ((_prefilled
|| (has_parent_froidure_pin()
&& parent_froidure_pin()->is_finite() == tril::
TRUE
&& (_settings->froidure_pin == options::froidure_pin::none
|| _settings->froidure_pin
== options::froidure_pin::use_cayley_graph)))
&& x) {
LIBSEMIGROUPS_EXCEPTION(
"cannot use the save setting with a "
"prefilled ToddCoxeter instance");
}
_settings->save = x;
return *
this;
}
bool ToddCoxeter::save()
const noexcept {
return _settings->save;
}
ToddCoxeter& ToddCoxeter::strategy(options::strategy x) {
if ((_prefilled
|| (has_parent_froidure_pin()
&& parent_froidure_pin()->is_finite() == tril::
TRUE
&& (_settings->froidure_pin == options::froidure_pin::none
|| _settings->froidure_pin
== options::froidure_pin::use_cayley_graph)))
&& x == options::strategy::felsch) {
LIBSEMIGROUPS_EXCEPTION(
"cannot use the Felsch strategy with a "
"prefilled ToddCoxeter instance");
}
_settings->strategy = x;
return *
this;
}
ToddCoxeter::options::strategy ToddCoxeter::strategy()
const noexcept {
return _settings->strategy;
}
ToddCoxeter&
ToddCoxeter::random_interval(std::chrono::nanoseconds x) noexcept {
_settings->random_interval = x;
return *
this;
}
std::chrono::nanoseconds ToddCoxeter::random_interval()
const noexcept {
return _settings->random_interval;
}
ToddCoxeter& ToddCoxeter::use_relations_in_extra(
bool val) noexcept {
_settings->use_relations_in_extra = val;
return *
this;
}
bool ToddCoxeter::use_relations_in_extra()
const noexcept {
return _settings->use_relations_in_extra;
}
ToddCoxeter& ToddCoxeter::deduction_policy(options::deductions val) {
using int_type = std::underlying_type_t<ToddCoxeter::options::deductions>;
if (
static_cast<int_type>(val) % 2 == 0
||
static_cast<int_type>(val) < 4) {
LIBSEMIGROUPS_EXCEPTION(
"invalid option %s!",
detail::to_string(val).c_str())
}
_settings->deduct_policy = val;
if (val & options::deductions::unlimited) {
_settings->deduct_max = POSITIVE_INFINITY;
}
return *
this;
}
ToddCoxeter::options::deductions
ToddCoxeter::deduction_policy()
const noexcept {
return _settings->deduct_policy;
}
ToddCoxeter& ToddCoxeter::max_deductions(size_t val) noexcept {
_settings->deduct_max = val;
return *
this;
}
size_t ToddCoxeter::max_deductions()
const noexcept {
return _settings->deduct_max;
}
ToddCoxeter&
ToddCoxeter::preferred_defs(options::preferred_defs val) noexcept {
if (val == options::preferred_defs::none) {
// Don't call preferred_defs() here because ends up in infinite loop.
_settings->max_preferred_defs = 0;
}
_settings->preferred_defs = val;
return *
this;
}
ToddCoxeter::options::preferred_defs
ToddCoxeter::preferred_defs()
const noexcept {
return _settings->preferred_defs;
}
ToddCoxeter& ToddCoxeter::restandardize(
bool val) noexcept {
_settings->restandardize = val;
return *
this;
}
bool ToddCoxeter::restandardize()
const noexcept {
return _settings->restandardize;
}
ToddCoxeter& ToddCoxeter::max_preferred_defs(size_t val) noexcept {
if (val == 0) {
// Don't call preferred_defs() here because ends up in infinite loop.
_settings->preferred_defs = options::preferred_defs::none;
}
_settings->max_preferred_defs = val;
return *
this;
}
size_t ToddCoxeter::max_preferred_defs()
const noexcept {
return _settings->max_preferred_defs;
}
ToddCoxeter& ToddCoxeter::hlt_defs(size_t val) {
if (val < length_of_generating_pairs()) {
LIBSEMIGROUPS_EXCEPTION(
"Expected a value >= %llu, found %llu!",
uint64_t(length_of_generating_pairs()),
uint64_t(val));
}
_settings->hlt_defs = val;
return *
this;
}
size_t ToddCoxeter::hlt_defs()
const noexcept {
return _settings->hlt_defs;
}
ToddCoxeter& ToddCoxeter::f_defs(size_t val) {
if (val == 0) {
LIBSEMIGROUPS_EXCEPTION(
"Expected a value != 0!");
}
_settings->f_defs = val;
return *
this;
}
size_t ToddCoxeter::f_defs()
const noexcept {
return _settings->f_defs;
}
ToddCoxeter& ToddCoxeter::lookahead_growth_factor(
float val) {
if (val < 1.0) {
LIBSEMIGROUPS_EXCEPTION(
"Expected a value >= 1.0, found %f", val);
}
_settings->lookahead_growth_factor = val;
return *
this;
}
float ToddCoxeter::lookahead_growth_factor()
const noexcept {
return _settings->lookahead_growth_factor;
}
ToddCoxeter& ToddCoxeter::lookahead_growth_threshold(size_t val) noexcept {
_settings->lookahead_growth_threshold = val;
return *
this;
}
size_t ToddCoxeter::lookahead_growth_threshold()
const noexcept {
return _settings->lookahead_growth_threshold;
}
ToddCoxeter& ToddCoxeter::large_collapse(size_t val) noexcept {
_settings->large_collapse = val;
return *
this;
}
size_t ToddCoxeter::large_collapse()
const noexcept {
return _settings->large_collapse;
}
// Private settings!
void ToddCoxeter::push_settings() {
Settings* prev_settings = _settings.get();
_settings.release();
_setting_stack.push(prev_settings);
_settings = std::make_unique<Settings>(*prev_settings);
}
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::sort_generating_pairs(
std::function<
bool(word_type
const&, word_type
const&)> func) {
if (started()) {
LIBSEMIGROUPS_EXCEPTION(
"Cannot sort relations, the enumeration has started!")
}
init_generating_pairs();
Perm perm;
::sort_generating_pairs(func, perm, _relations);
::sort_generating_pairs(func, perm, _extra);
return *
this;
}
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 (
auto const& 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 (
auto const& 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!");
}
else if (_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;
}
bool ToddCoxeter::reduce_length_once() {
LIBSEMIGROUPS_ASSERT(_felsch_tree == nullptr);
using const_iterator_word =
typename ukkonen::detail::GreedyReduceHelper::const_iterator;
if (_relations.empty() && _extra.empty()) {
return false;
}
Ukkonen u;
ukkonen::add_words(u, _relations);
ukkonen::add_words(u, _extra);
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) {
return false;
// 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);
return true;
}
////////////////////////////////////////////////////////////////////////
// 9. ToddCoxeter - member functions (container-like) - public
////////////////////////////////////////////////////////////////////////
bool ToddCoxeter::empty()
const {
return _relations.empty() && _extra.empty()
&& number_of_cosets_active() == 1;
}
void ToddCoxeter::reserve(size_t n) {
size_t m = coset_capacity();
if (n > m) {
m = n - m;
_word_graph.add_nodes(m);
add_free_cosets(m);
}
}
void ToddCoxeter::shrink_to_fit() {
if (!finished()) {
return;
}
if (!is_standardized()) {
standardize(order::shortlex);
}
_word_graph.shrink_to_fit(number_of_cosets_active());
_relations.clear();
_relations.shrink_to_fit();
_extra.clear();
_extra.shrink_to_fit();
erase_free_cosets();
}
////////////////////////////////////////////////////////////////////////
// 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) {
return false;
}
}
c = next_active_coset(c);
}
return true;
}
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) {
return false;
}
}
return true;
}
bool ToddCoxeter::compatible()
const noexcept {
coset_type c = _id_coset;
if (!compatible(c, _extra.cbegin(), _extra.cend())) {
return false;
}
while (c != first_free_coset()) {
if (!compatible(c, _relations.cbegin(), _relations.cend())) {
return false;
}
c = next_active_coset(c);
}
return true;
}
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;
}
size_t ToddCoxeter::felsch_tree_height() {
init_generating_pairs();
init_run();
init_felsch_tree();
return _felsch_tree->height();
}
size_t ToddCoxeter::felsch_tree_number_of_nodes() {
init_generating_pairs();
init_run();
init_felsch_tree();
return _felsch_tree->number_of_nodes();
}
////////////////////////////////////////////////////////////////////////
// 11. ToddCoxeter - member functions (standardization) - public
////////////////////////////////////////////////////////////////////////
bool ToddCoxeter::is_standardized()
const noexcept {
return _standardized != order::none;
}
ToddCoxeter::order ToddCoxeter::standardization_order()
const noexcept {
return _standardized;
}
bool ToddCoxeter::standardize(order rdr) {
if (_standardized == rdr || empty()) {
return false;
}
LIBSEMIGROUPS_ASSERT(_coinc.empty());
LIBSEMIGROUPS_ASSERT(_deduct->empty());
bool result =
false;
switch (rdr) {
case order::shortlex:
init_standardize();
result = shortlex_standardize();
break;
case order::lex:
init_standardize();
result = lex_standardize();
break;
case order::recursive:
init_standardize();
result = recursive_standardize();
break;
case order::none:
default:
break;
}
if (finished()) {
_standardized = rdr;
}
else {
_standard_max = number_of_cosets_active();
}
return result;
}
////////////////////////////////////////////////////////////////////////
// 12. ToddCoxeter - member functions (attributes) - public
////////////////////////////////////////////////////////////////////////
tril ToddCoxeter::is_non_trivial(size_t tries,
std::chrono::milliseconds try_for,
float threshold) {
if (is_quotient_obviously_infinite()) {
return tril::
TRUE;
}
else if (finished()) {
return number_of_classes() == 1 ? tril::
FALSE : tril::
TRUE;
}
detail::Timer tmr;
static std::random_device rd;
static std::mt19937 g(rd());
for (size_t try_ = 0; try_ < tries; ++try_) {
REPORT_DEFAULT(
"trying to show non-triviality: %d / %d\n", try_ + 1, tries);
ToddCoxeter tc(*
this);
tc.init_felsch_tree();
tc.standardize(
true);
tc.save(
true);
while (!tc.finished()) {
tc.run_for(try_for);
size_t limit = tc.number_of_cosets_active();
while (tc.number_of_cosets_active() >= threshold * limit
&& !tc.finished()) {
std::uniform_int_distribution<> d(0,
tc.number_of_cosets_active() - 1);
size_t N = d(g);
auto c1 = tc._id_coset;
for (size_t i = 0; i < N; ++i) {
c1 = tc.next_active_coset(c1);
}
N = d(g);
auto c2 = tc._id_coset;
for (size_t i = 0; i < N; ++i) {
c2 = tc.next_active_coset(c2);
}
tc._coinc.emplace(c1, c2);
tc.process_coincidences(stack_deductions::yes);
tc.process_deductions();
tc.run_for(try_for);
}
}
if (tc.number_of_classes() > 1) {
REPORT_DEFAULT(
"successfully showed non-triviality!\n");
report_time(__func__, tmr);
return tril::
TRUE;
}
}
REPORT_DEFAULT(
"failed to show non-triviality!\n");
report_time(__func__, tmr);
return tril::unknown;
}
////////////////////////////////////////////////////////////////////////
// 13. ToddCoxeter - member functions (reporting + stats) - public
////////////////////////////////////////////////////////////////////////
std::string ToddCoxeter::stats_string()
const {
detail::PrintTable pt;
pt.header(
"Summary of statistics");
pt(
"Number of generators:",
"%'14llu", uint64_t(number_of_generators()));
pt(
"Number of relations:",
"%'14llu", uint64_t(_relations.size() / 2));
pt(
"Number of generating pairs:",
"%'14llu", uint64_t(_extra.size() / 2));
pt(
"Total length of generating pairs:",
"%'14llu",
uint64_t(
const_cast<ToddCoxeter*>(
this)->length_of_generating_pairs()));
pt.divider();
pt(
"cosets defined (hlt)",
"%'14llu", uint64_t(_stats.tc1_hlt_appl));
pt(
"cosets defined (felsch)",
"%'14llu", uint64_t(_stats.tc1_f_appl));
pt(
"total cosets defined",
"%'14llu",
uint64_t(number_of_cosets_defined() - 2));
pt(
"coset capacity",
"%'14llu", uint64_t(coset_capacity()));
pt(
"cosets killed",
"%'14llu", uint64_t(number_of_cosets_killed()));
pt(
"killed / defined",
"%13f%%",
static_cast<
float>(100 * number_of_cosets_killed())
/ number_of_cosets_defined());
pt(
"defined / capacity",
"%13f%%",
static_cast<
float>(100 * number_of_cosets_defined())
/ coset_capacity());
#ifdef LIBSEMIGROUPS_ENABLE_STATS
pt.divider();
pt(
"relations pushed (good)",
"%'14llu", uint64_t(_stats.tc2_good_appl));
pt(
"relations pushed (total)",
"%'14llu", uint64_t(_stats.tc2_appl));
pt(
"relations pushed (% good)",
"%13f%%",
static_cast<
double>(100 * _stats.tc2_good_appl) / _stats.tc2_appl);
pt.divider();
pt(
"calls to process_coincidences",
"%'14llu", uint64_t(_stats.tc3_appl));
pt(
"maximum coincidences",
"%'14llu", uint64_t(_stats.max_coinc));
pt(
"total active coincidences",
"%'14llu",
uint64_t(_stats.nr_active_coinc));
pt(
"total coincidences",
"%'14llu", uint64_t(_stats.total_coinc));
pt.divider();
pt(
"calls to process_deductions",
"%'14llu",
uint64_t(_stats.total_deduct));
pt(
"maximum deductions",
"%'14llu", uint64_t(_stats.max_deduct));
pt(
"total active deductions",
"%'14llu",
uint64_t(_stats.nr_active_deduct));
pt(
"total deductions",
"%'14llu", uint64_t(_stats.total_deduct));
pt.divider();
pt(
"maximum preferred defs",
"%'14llu",
uint64_t(_stats.max_preferred_defs));
pt(
"total active preferred defs",
"%'14llu",
uint64_t(_stats.nr_active_preferred_defs));
pt(
"total preferred defs",
"%'14llu",
uint64_t(_stats.total_preferred_defs));
pt.divider();
pt(
"calls to lookahead (hlt)",
"%'14llu",
uint64_t(_stats.hlt_lookahead_calls));
pt(
"calls to lookahead (felsch)",
"%'14llu",
uint64_t(_stats.f_lookahead_calls));
#endif
pt.footer(
"End of summary");
return pt.emit();
}
std::string ToddCoxeter::settings_string()
const {
detail::PrintTable pt;
pt.header(
"Summary of settings (Todd-Coxeter algorithm)");
pt(
"deduction_policy:", deduction_policy());
pt(
"f_defs:",
"%'14llu", uint64_t(f_defs()));
pt(
"froidure_pin_policy:", froidure_pin_policy());
pt(
"hlt_defs:",
"%'14llu", uint64_t(hlt_defs()));
pt(
"lookahead:", lookahead());
pt(
"lookahead_growth_factor:",
"%14f", lookahead_growth_factor());
pt(
"lookahead_growth_threshold:",
"%14llu",
uint64_t(lookahead_growth_threshold()));
if (lower_bound() == UNDEFINED) {
pt(
"lower_bound:",
"\u221E");
}
else {
pt(
"lower_bound:",
"%'14llu", uint64_t(lower_bound()));
}
pt(
"max_deductions:",
"%'14llu", uint64_t(max_deductions()));
pt(
"max_preferred_defs:",
"%'14llu", uint64_t(max_preferred_defs()));
pt(
"next_lookahead:",
"%'14llu", uint64_t(next_lookahead()));
pt(
"preferred_defs:", preferred_defs());
pt(
"random_interval:", detail::Timer::string(random_interval()));
pt(
"save: ", save() ?
"true" :
"false");
pt(
"standardize:", standardize() ?
"true" :
"false");
pt(
"strategy: ", strategy());
pt(
"use_relations_in_extra:",
use_relations_in_extra() ?
"true" :
"false");
pt.footer(
"End of summary");
return pt.emit();
}
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;
}
////////////////////////////////////////////////////////////////////////
// 14. ToddCoxeter - member functions (reporting + stats) - private
////////////////////////////////////////////////////////////////////////
namespace {
std::string fmt_line() {
#if defined(LIBSEMIGROUPS_FMT_ENABLED) &&
defined(LIBSEMIGROUPS_ENABLE_STATS)
return "\t{:12L} {:+12L} ({})\n";
#elif defined(LIBSEMIGROUPS_FMT_ENABLED) && !
defined(LIBSEMIGROUPS_ENABLE_STATS)
return "\t{:12L} {:>12} ({})\n";
#elif !
defined(LIBSEMIGROUPS_FMT_ENABLED) &&
defined(LIBSEMIGROUPS_ENABLE_STATS)
return "\t%'12llu %+'12lld (%s)\n";
#elif !
defined(LIBSEMIGROUPS_FMT_ENABLED) \
&& !
defined(LIBSEMIGROUPS_ENABLE_STATS)
return "\t%'12llu %12s (%s)\n";
#endif
}
}
// namespace
#ifdef LIBSEMIGROUPS_ENABLE_STATS
void ToddCoxeter::report_coincidences(
char const* fnam) {
if (report::should_report()) {
REPORT_DEFAULT(FORMAT(
"coincidences:" + fmt_line(),
_coinc.size(),
int64_t(_coinc.size() - _stats.prev_coincidences),
fnam));
_stats.prev_coincidences = _coinc.size();
}
}
#else
void ToddCoxeter::report_coincidences(
char const* fnam) {
REPORT_DEFAULT(
FORMAT(
"coincidences:" + fmt_line(), _coinc.size(),
"", fnam));
}
#endif
#ifdef LIBSEMIGROUPS_ENABLE_STATS
void ToddCoxeter::report_active_cosets(
char const* fnam) {
if (report::should_report()) {
REPORT_DEFAULT(FORMAT(
"active cosets:" + fmt_line(),
number_of_cosets_active(),
int64_t(number_of_cosets_active() - _stats.prev_active_cosets),
fnam));
_stats.prev_active_cosets = number_of_cosets_active();
}
}
#else
void ToddCoxeter::report_active_cosets(
char const* fnam) {
REPORT_DEFAULT(FORMAT(
"active cosets:" + fmt_line(), number_of_cosets_active(),
"", fnam));
}
#endif
void ToddCoxeter::report_cosets_killed(
char const* fnam, int64_t N)
const {
if (report::should_report()) {
#ifdef LIBSEMIGROUPS_FMT_ENABLED
std::string fmt =
"\t{:>12} {:+12L} ({})\n";
#else
std::string fmt =
"\t%12s %+'12lld (%s)\n";
#endif
REPORT_DEFAULT(FORMAT(
"cosets killed:" + fmt,
"", -1 * N, fnam));
}
}
void ToddCoxeter::report_inc_lookahead(
char const* fnam,
size_t new_value)
const {
if (report::should_report()) {
#if defined(LIBSEMIGROUPS_FMT_ENABLED)
std::string fmt =
"\t{:12L} {:+12L} ({})\n";
#else
std::string fmt =
"\t%'12llu %+'12lld (%s)\n";
#endif
REPORT_DEFAULT(FORMAT(
"lookahead at:" + fmt,
new_value,
int64_t(new_value - next_lookahead()),
fnam));
}
}
void ToddCoxeter::report_time(
char const* fnam, detail::Timer& t)
const {
if (report::should_report()) {
auto tt = t.string();
size_t width = 12;
// Check if we contain a \mu
if (tt.find(
"\u03BC") != std::string::npos) {
width = 13;
}
#ifdef LIBSEMIGROUPS_FMT_ENABLED
std::string fmt =
"\t{:>" + std::to_string(width) +
"} {:>{}} ({})\n";
REPORT_DEFAULT(FORMAT(
"elapsed time:" + fmt, tt.c_str(),
"", 12, fnam));
#else
std::string fmt =
"\t%" + std::to_string(width) +
"s %*s (%s)\n";
REPORT_DEFAULT(FORMAT(
"elapsed time:" + fmt, tt.c_str(), 12,
"", fnam));
#endif
}
}
// Cannot test this
void ToddCoxeter::report_at_coset(
char const* fnam, size_t N)
const {
if (report::should_report()) {
#ifdef LIBSEMIGROUPS_FMT_ENABLED
std::string fmt =
"\t{:12L} {:12L} ({})\n";
#else
std::string fmt =
"\t%'12llu %'12lld (%s)\n";
#endif
REPORT_DEFAULT(
FORMAT(
"at coset:" + fmt, N, number_of_cosets_active(), fnam));
}
}
////////////////////////////////////////////////////////////////////////
// 15. ToddCoxeter - member functions (validation) - private
////////////////////////////////////////////////////////////////////////
void ToddCoxeter::validate_table(table_type
const& table,
size_t
const first,
size_t
const last)
const {
REPORT_DEBUG_DEFAULT(
"validating coset table...\n");
if (number_of_generators() == UNDEFINED) {
LIBSEMIGROUPS_EXCEPTION(
"no generators have been defined");
}
else if (table.number_of_cols() != number_of_generators()) {
LIBSEMIGROUPS_EXCEPTION(
"invalid table, expected %d columns, found %d",
number_of_generators(),
table.number_of_cols());
}
else if (last - first == 0) {
LIBSEMIGROUPS_EXCEPTION(
"invalid table, expected at least 1 rows, found 0!");
}
for (size_t i = first; i < last; ++i) {
for (size_t j = 0; j < table.number_of_cols(); ++j) {
coset_type c = table.get(i, j);
if (c < first || c >= last) {
LIBSEMIGROUPS_EXCEPTION(
"invalid table, expected entries in the "
"range [%d, %d), found "
"%d in row %d, column %d",
i,
j,
first,
last,
c);
}
}
}
REPORT_DEBUG_DEFAULT(
"...coset table ok\n");
}
////////////////////////////////////////////////////////////////////////
// 16. ToddCoxeter - member functions (initialisation) - private
////////////////////////////////////////////////////////////////////////
//! 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 (
auto const& 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();
}
void ToddCoxeter::init_felsch_tree() {
LIBSEMIGROUPS_ASSERT(_state >= state::relation_extra_initialized);
if (_felsch_tree == nullptr) {
REPORT_DEFAULT(
"initializing the Felsch tree...\n");
detail::Timer tmr;
_felsch_tree = std::make_unique<FelschTree>(number_of_generators());
_felsch_tree->add_relations(_relations.cbegin(), _relations.cend());
REPORT_DEFAULT(
"Felsch tree has %llu nodes + height %llu\n",
_felsch_tree->number_of_nodes(),
_felsch_tree->height());
report_time(__func__, tmr);
}
}
// Initialization
void ToddCoxeter::prefill(table_type
const& table,
std::function<size_t(size_t)> generator_to_row) {
prefill_and_validate(table,
true, generator_to_row);
}
void ToddCoxeter::prefill(FroidurePinBase& S) {
REPORT_DEBUG_DEFAULT(
"prefilling the coset table from FroidurePin...\n");
LIBSEMIGROUPS_ASSERT(_state == state::constructed);
LIBSEMIGROUPS_ASSERT(
froidure_pin_policy() == options::froidure_pin::use_cayley_graph
|| froidure_pin_policy() == options::froidure_pin::none);
LIBSEMIGROUPS_ASSERT(S.number_of_generators() == number_of_generators());
auto generator_to_row = [&S](size_t i) {
return S.current_position(i); };
if (kind() == congruence_kind::left) {
prefill_and_validate(S.left_cayley_graph(),
false, generator_to_row);
}
else {
prefill_and_validate(S.right_cayley_graph(),
false, generator_to_row);
}
}
void ToddCoxeter::prefill_and_validate(
table_type
const& table,
bool validate,
std::function<size_t(size_t)> generator_to_row) {
if (strategy() == options::strategy::felsch) {
LIBSEMIGROUPS_EXCEPTION(
"it is not possible to prefill when using the Felsch strategy");
}
else if (!empty()) {
LIBSEMIGROUPS_EXCEPTION(
"cannot prefill a non-empty instance")
}
if (validate) {
validate_table(table, 0, table.number_of_rows());
}
REPORT_DEBUG(
"prefilling the coset table...\n");
_prefilled =
true;
size_t m = table.number_of_rows() + 1;
add_active_cosets(m - number_of_cosets_active());
_word_graph.add_nodes(m - _word_graph.number_of_nodes());
for (size_t i = 0; i < _word_graph.out_degree(); i++) {
def_edge<DoNotStackDeductions>(0, i, generator_to_row(i) + 1);
}
for (size_t row = 0; row < _word_graph.number_of_nodes() - 1; ++row) {
for (size_t col = 0; col < _word_graph.out_degree(); ++col) {
def_edge<DoNotStackDeductions>(row + 1, col, table.get(row, col) + 1);
}
}
}
////////////////////////////////////////////////////////////////////////
// 17. ToddCoxeter - member functions (cosets) - private
////////////////////////////////////////////////////////////////////////
coset_type ToddCoxeter::new_coset() {
if (has_free_cosets()) {
coset_type
const c = new_active_coset();
// Clear the new coset's row in each table
_word_graph.clear_sources_and_targets(c);
return c;
}
else {
reserve(2 * coset_capacity());
return new_active_coset();
}
}
template <
typename TStackDeduct>
coset_type ToddCoxeter::def_edges(coset_type c,
word_type::const_iterator first,
word_type::const_iterator last) noexcept {
word_type::const_iterator it;
std::tie(c, it) = action_digraph_helper::last_node_on_path_nc(
_word_graph, c, first, last);
_stats.tc1_hlt_appl += std::distance(it, last);
for (; it < last; ++it) {
LIBSEMIGROUPS_ASSERT(_word_graph.unsafe_neighbor(c, *it) == UNDEFINED);
coset_type d = new_coset();
def_edge<TStackDeduct>(c, *it, d);
c = d;
}
return c;
}
template <
typename TStackDeduct,
typename TProcessCoincide>
void ToddCoxeter::push_definition_hlt(coset_type
const& c,
word_type
const& u,
word_type
const& v) noexcept {
LIBSEMIGROUPS_ASSERT(is_active_coset(c));
LIBSEMIGROUPS_ASSERT(!u.empty());
LIBSEMIGROUPS_ASSERT(!v.empty());
coset_type
const x = def_edges<TStackDeduct>(c, u.cbegin(), u.cend() - 1);
coset_type
const y = def_edges<TStackDeduct>(c, v.cbegin(), v.cend() - 1);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.tc2_appl += 2;
#endif
push_definition<TStackDeduct,
TProcessCoincide,
ImmediateDef<TStackDeduct>>(x, u.back(), y, v.back());
}
template <
typename TStackDeduct,
typename TProcessCoincide,
typename TPreferredDef>
void ToddCoxeter::push_definition_felsch(coset_type
const& c,
word_type
const& u,
word_type
const& v) noexcept {
LIBSEMIGROUPS_ASSERT(is_active_coset(c));
LIBSEMIGROUPS_ASSERT(!u.empty());
LIBSEMIGROUPS_ASSERT(!v.empty());
coset_type x = action_digraph_helper::follow_path_nc(
_word_graph, c, u.cbegin(), u.cend() - 1);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.tc2_appl++;
#endif
if (x == UNDEFINED) {
return;
}
coset_type y = action_digraph_helper::follow_path_nc(
_word_graph, c, v.cbegin(), v.cend() - 1);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.tc2_appl++;
#endif
if (y == UNDEFINED) {
return;
}
push_definition<TStackDeduct, TProcessCoincide, TPreferredDef>(
x, u.back(), y, v.back());
}
template <
typename TStackDeduct,
typename TProcessCoincide,
typename TPreferredDef>
void ToddCoxeter::push_definition(coset_type x,
letter_type a,
coset_type y,
letter_type b) {
LIBSEMIGROUPS_ASSERT(is_valid_coset(x));
LIBSEMIGROUPS_ASSERT(is_valid_coset(y));
LIBSEMIGROUPS_ASSERT(a < number_of_generators());
LIBSEMIGROUPS_ASSERT(b < number_of_generators());
coset_type
const xa = _word_graph.unsafe_neighbor(x, a);
coset_type
const yb = _word_graph.unsafe_neighbor(y, b);
if (xa == UNDEFINED && yb != UNDEFINED) {
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.tc2_good_appl++;
#endif
def_edge<TStackDeduct>(x, a, yb);
}
else if (xa != UNDEFINED && yb == UNDEFINED) {
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.tc2_good_appl++;
#endif
def_edge<TStackDeduct>(y, b, xa);
}
else if (xa != UNDEFINED && yb != UNDEFINED && xa != yb) {
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.tc2_good_appl++;
#endif
_coinc.emplace(xa, yb);
TProcessCoincide()(
this);
}
else if (xa == UNDEFINED && yb == UNDEFINED) {
// We discover that we are one letter away from being able
// to follow the paths labelled u and v from some node. I.e.
// u = u_1a and v = v_1b and u_1 and v_1 label (c, d)- and
// (c, e)-paths but u and v don't label any paths starting
// at c (i.e there are no edges labelled a incident to d nor
// labelled b incident to e. This would make the (d, a) and
// (e, b) "preferred" definitions, or make an immediate definition or
// do nothing.
TPreferredDef()(
this, x, a, y, b);
}
}
void ToddCoxeter::process_coincidences(stack_deductions val) {
if (_coinc.empty()) {
return;
}
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.tc3_appl++;
#endif
std::function<
void(coset_type, letter_type)> new_edge_func;
if (val == stack_deductions::no) {
new_edge_func = [](coset_type, letter_type) {};
}
else {
new_edge_func = [
this](coset_type c, letter_type x) {
this->_deduct->emplace(c, x);
};
}
auto incompat_func
= [
this](coset_type c, coset_type d) { this->_coinc.emplace(c, d); };
while (!_coinc.empty() && _coinc.size() < large_collapse()) {
Coincidence c = _coinc.top();
_coinc.pop();
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.total_coinc++;
#endif
coset_type min = find_coset(c.first);
coset_type max = find_coset(c.second);
if (min != max) {
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.nr_active_coinc++;
#endif
if (min > max) {
std::swap(min, max);
}
union_cosets(min, max);
_word_graph.merge_nodes(min, max, new_edge_func, incompat_func);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.max_coinc = std::max(_stats.max_coinc,
static_cast<uint64_t>(_coinc.size()));
#endif
}
}
if (report() || !_coinc.empty()) {
if (!_coinc.empty()) {
REPORT_DEFAULT(
"large collapse detected!\n");
report_coincidences(__func__);
}
report_active_cosets(__func__);
}
bool large_collapse = !_coinc.empty();
while (!_coinc.empty()) {
Coincidence c = _coinc.top();
_coinc.pop();
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.total_coinc++;
#endif
coset_type min = find_coset(c.first);
coset_type max = find_coset(c.second);
if (min != max) {
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.nr_active_coinc++;
#endif
if (min > max) {
std::swap(min, max);
}
union_cosets(min, max);
for (letter_type i = 0; i < number_of_generators(); ++i) {
coset_type
const v = _word_graph.unsafe_neighbor(max, i);
if (v != UNDEFINED) {
coset_type
const u = _word_graph.unsafe_neighbor(min, i);
if (u == UNDEFINED) {
_word_graph.ActionDigraph<coset_type>::add_edge_nc(min, v, i);
}
else if (u != v) {
// Add (u,v) to the stack of pairs to be identified
_coinc.emplace(u, v);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.max_coinc = std::max(
_stats.max_coinc,
static_cast<uint64_t>(_coinc.size()));
#endif
}
}
}
}
if (report()) {
report_coincidences(__func__);
report_active_cosets(__func__);
}
}
if (large_collapse) {
{
// Remove all sources of all remaining active cosets
auto c = _id_coset;
while (c != first_free_coset()) {
_word_graph.clear_sources(c);
c = next_active_coset(c);
}
}
{
REPORT_DEFAULT(
"Rebuilding table sources...\n");
auto c = _id_coset;
size_t m = 0;
while (c != first_free_coset()) {
m++;
for (letter_type x = 0; x < number_of_generators(); ++x) {
auto cx = _word_graph.unsafe_neighbor(c, x);
if (cx != UNDEFINED) {
auto d = find_coset(cx);
if (cx != d) {
new_edge_func(c, x);
_word_graph.ActionDigraph<coset_type>::add_edge_nc(c, d, x);
}
// Must re-add the source, even if we don't need to reset
// the target or stack the deduction
_word_graph.add_source(d, x, c);
LIBSEMIGROUPS_ASSERT(is_active_coset(d));
}
}
c = next_active_coset(c);
if (report()) {
report_at_coset(__func__, m);
}
}
}
}
if (report() || large_collapse) {
report_active_cosets(__func__);
}
}
////////////////////////////////////////////////////////////////////////
// 18. ToddCoxeter - member functions (main strategies) - private
////////////////////////////////////////////////////////////////////////
void ToddCoxeter::init_run() {
LIBSEMIGROUPS_ASSERT(_state >= state::relation_extra_initialized);
if (_state == state::relation_extra_initialized) {
if (save() || strategy() == options::strategy::felsch) {
for (
auto it = _extra.cbegin(); it < _extra.cend(); it += 2) {
push_definition_hlt<StackDeductions,
ProcessCoincidences<stack_deductions::yes>>(
_id_coset, *it, *(it + 1));
}
}
else {
for (
auto it = _extra.cbegin(); it < _extra.cend(); it += 2) {
push_definition_hlt<DoNotStackDeductions,
ProcessCoincidences<stack_deductions::no>>(
_id_coset, *it, *(it + 1));
}
}
if (strategy() == options::strategy::felsch
&& use_relations_in_extra()) {
for (
auto it = _relations.cbegin(); it < _relations.cend(); it += 2) {
push_definition_hlt<StackDeductions,
ProcessCoincidences<stack_deductions::yes>>(
_id_coset, *it, *(it + 1));
}
}
if (!_prefilled && _relations.empty()) {
_extra.swap(_relations);
}
if (save() || strategy() == options::strategy::felsch) {
init_felsch_tree();
process_deductions();
}
if (standardize()) {
// There are no deductions so we don't have to keep track of whether
// or not we have made any changes here.
for (letter_type x = 0; x < number_of_generators(); ++x) {
standardize_immediate(_id_coset, x);
}
}
}
else {
if (standardize() && restandardize()) {
// We have previously run HLT or Felsch, and now we are running with
// immediate standardization. So, in case we weren't standardizing
// previously, we call standardize.
if (standardize(order::shortlex)) {
_deduct->clear();
}
}
if (save() || strategy() == options::strategy::felsch) {
// If we previously ran hlt, and are now running (felsch or hlt +
// save), then the state will not be relation_extra_initialized and
// felsch_tree will not be initialized.
init_felsch_tree();
}
}
}
void ToddCoxeter::finalise_run(detail::Timer& tmr) {
LIBSEMIGROUPS_ASSERT(_state == state::hlt || _state == state::felsch);
LIBSEMIGROUPS_ASSERT(_coinc.empty());
if (!stopped()) {
if (_deduct->any_skipped()) {
if (number_of_cosets_active() != lower_bound() + 1 || !complete()) {
push_settings();
lookahead(options::lookahead::full | options::lookahead::hlt);
perform_lookahead();
pop_settings();
}
}
// LIBSEMIGROUPS_ASSERT(complete());
// LIBSEMIGROUPS_ASSERT(compatible());
_state = state::finished;
}
else {
report_active_cosets(__func__);
report_why_we_stopped();
}
report_time(__func__, tmr);
if (finished()) {
// Can't set standardize here, because we haven't initialised _tree,
// even if we are using standard_immediate. Also until we are finished
// it is probably a terrible idea to populate _tree, because it will
// be much larger than required in many cases, and we also have to
// manage its size etc. Also standardize_immediate doesn't currently
// guarantee that the table is actually standardized by the end of the
// run.
REPORT_DEFAULT(
"SUCCESS!\n");
}
}
void ToddCoxeter::felsch() {
REPORT_DEFAULT(
"performing Felsch...\n");
detail::Timer tmr;
init_generating_pairs();
init_run();
_state = state::felsch;
size_t
const n = number_of_generators();
while (_current != first_free_coset() && !stopped()) {
if (preferred_defs() == options::preferred_defs::deferred) {
_preferred_defs->purge_from_top();
while (!_preferred_defs->empty()) {
Deduction d = _preferred_defs->pop();
LIBSEMIGROUPS_ASSERT(is_active_coset(d.first));
LIBSEMIGROUPS_ASSERT(_word_graph.unsafe_neighbor(d.first, d.second)
== UNDEFINED);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.nr_active_preferred_defs++;
#endif
_stats.tc1_f_appl++;
def_edge<StackDeductions>(d.first, d.second, new_coset());
if (standardize()) {
standardize_immediate(d.first, d.second);
}
process_deductions();
// Note that process_deductions and process_coincidences both can
// made definitions, but don't do any standardization, so it is
// possible that the table is not actually standardized at this
// point. This is okay because we anyway don't record that the
// table is standardized, and so if standardize(order) is called
// later then it performs an actual standardization at that point.
// As such standardize_immediate is only really an attempt to keep
// the table somewhat standardized, not genuinely standardized.
_preferred_defs->purge_from_top();
}
}
for (letter_type a = 0; a < n; ++a) {
if (_word_graph.unsafe_neighbor(_current, a) == UNDEFINED) {
_stats.tc1_f_appl++;
def_edge<StackDeductions>(_current, a, new_coset());
if (standardize()) {
standardize_immediate(_current, a);
}
process_deductions();
}
}
if (report()) {
report_active_cosets(__func__);
}
_current = next_active_coset(_current);
}
finalise_run(tmr);
}
// Walker's Strategy 1 = HLT = ACE style-R
void ToddCoxeter::hlt() {
REPORT_DEFAULT(
"performing HLT...\n");
detail::Timer tmr;
init_generating_pairs();
init_run();
_state = state::hlt;
bool do_pop_settings =
false;
if (save() && preferred_defs() == options::preferred_defs::deferred) {
push_settings();
do_pop_settings =
true;
// The call to process_deductions in the main loop below could
// potentially accumulate large numbers of preferred definitions in
// the queue if the preferred_defs() setting is
// options::preferred_defs::deferred, so we change it.
preferred_defs(options::preferred_defs::none);
}
while (_current != first_free_coset() && !stopped()) {
if (!save()) {
for (
auto it = _relations.cbegin(); it < _relations.cend(); it += 2) {
push_definition_hlt<DoNotStackDeductions,
ProcessCoincidences<stack_deductions::no>>(
_current, *it, *(it + 1));
}
}
else {
for (
auto it = _relations.cbegin(); it < _relations.cend(); it += 2) {
push_definition_hlt<StackDeductions, DoNotProcessCoincidences>(
_current, *it, *(it + 1));
process_deductions();
// See the comments in ToddCoxeter::felsch about the meaning of
// standardize_immediate.
}
}
if (standardize()) {
bool any_changes =
false;
for (letter_type x = 0; x < number_of_generators(); ++x) {
any_changes |= standardize_immediate(_current, x);
}
if (any_changes) {
_deduct->clear();
}
}
if ((!save() || _deduct->any_skipped())
&& number_of_cosets_active() > next_lookahead()) {
// If save() == true and no deductions were skipped, then we have
// already run process_deductions, and so there's no point in doing
// a lookahead.
perform_lookahead();
}
if (report()) {
report_active_cosets(__func__);
}
_current = next_active_coset(_current);
}
finalise_run(tmr);
if (do_pop_settings) {
pop_settings();
}
}
void ToddCoxeter::random() {
push_settings();
REPORT_DEFAULT(
"performing random strategy...\n");
using int_dist_type
= std::uniform_int_distribution<std::mt19937::result_type>;
static int_dist_type dist(0, 9);
static std::mt19937 mt;
static constexpr std::array<
bool, 8> full
= {
true,
true,
true,
true,
false,
false,
false,
false};
static constexpr std::array<
bool, 10> stand
= {
true,
true,
false,
false,
true,
true,
false,
false,
true,
false};
static constexpr std::array<
bool, 8> save_it
= {
true,
false,
true,
false,
true,
false,
true,
false};
static const std::string line = std::string(79,
'#') +
'\n';
while (!finished()) {
size_t prev_num_cosets = number_of_cosets_active();
auto prev_strategy = strategy();
size_t m = dist(mt);
if (m < 8) {
strategy(options::strategy::hlt);
lookahead((full[m] ? options::lookahead::full
: options::lookahead::partial));
try {
save(save_it[m]);
}
catch (...) {
// It isn't always possible to use the save option (when this is
// created from a Cayley table, for instance), and
// ToddCoxeter::save throws if this is the case.
}
}
else {
try {
strategy(options::strategy::felsch);
}
catch (...) {
strategy(options::strategy::hlt);
// It isn't always possible to use the Felsch strategy (when this
// is created from a Cayley table, for instance), and
// ToddCoxeter::save throws if this is the case.
}
}
standardize(stand[m]);
REPORT(line).prefix().flush();
// Some tests show that this is better to do when using this strategy
if (prev_strategy != strategy()) {
_current = _id_coset;
}
run_for(_settings->random_interval);
if (prev_num_cosets == number_of_cosets_active()) {
lookahead(options::lookahead::full | options::lookahead::hlt);
perform_lookahead();
}
}
LIBSEMIGROUPS_ASSERT(_coinc.empty());
lookahead(options::lookahead::full | options::lookahead::hlt);
perform_lookahead();
pop_settings();
}
void ToddCoxeter::CR_style() {
size_t N = length_of_generating_pairs();
push_settings();
while (!finished()) {
strategy(options::strategy::felsch);
auto M = _stats.tc1_f_appl;
run_until([
this, &M]() ->
bool {
return this->_stats.tc1_f_appl >= (this->f_defs() + M);
});
if (finished()) {
break;
}
strategy(options::strategy::hlt);
M = _stats.tc1_hlt_appl;
run_until([
this, &M, &N]() ->
bool {
return this->_stats.tc1_hlt_appl >= ((this->hlt_defs() / N) + M);
});
}
lookahead(options::lookahead::full | options::lookahead::hlt);
perform_lookahead();
pop_settings();
}
void ToddCoxeter::R_over_C_style() {
push_settings();
strategy(options::strategy::hlt);
run_until([
this]() ->
bool {
return this->number_of_cosets_active() >= this->next_lookahead();
});
if (lookahead() & options::lookahead::hlt) {
lookahead(options::lookahead::full | options::lookahead::hlt);
}
else {
lookahead(options::lookahead::full | options::lookahead::felsch);
}
perform_lookahead();
CR_style();
pop_settings();
}
void ToddCoxeter::Cr_style() {
push_settings();
strategy(options::strategy::felsch);
auto M = _stats.tc1_f_appl;
run_until([
this, &M]() ->
bool {
return this->_stats.tc1_f_appl >= (this->f_defs() + M);
});
strategy(options::strategy::hlt);
M = _stats.tc1_hlt_appl;
size_t N = length_of_generating_pairs();
run_until([
this, &M, &N]() ->
bool {
return this->_stats.tc1_hlt_appl >= ((this->hlt_defs() / N) + M);
});
strategy(options::strategy::felsch);
run();
lookahead(options::lookahead::full | options::lookahead::hlt);
perform_lookahead();
pop_settings();
}
void ToddCoxeter::Rc_style() {
push_settings();
strategy(options::strategy::hlt);
auto M = _stats.tc1_hlt_appl;
size_t N = length_of_generating_pairs();
run_until([
this, &M, &N]() ->
bool {
return this->_stats.tc1_hlt_appl >= ((this->hlt_defs() / N) + M);
});
strategy(options::strategy::felsch);
M = _stats.tc1_f_appl;
run_until([
this, &M]() ->
bool {
return this->_stats.tc1_f_appl >= (this->f_defs() + M);
});
strategy(options::strategy::hlt);
run();
lookahead(options::lookahead::full | options::lookahead::hlt);
perform_lookahead();
pop_settings();
}
////////////////////////////////////////////////////////////////////////
// 19. ToddCoxeter - member functions (deduction processing) - private
////////////////////////////////////////////////////////////////////////
void ToddCoxeter::process_deductions() {
LIBSEMIGROUPS_ASSERT(!_prefilled);
if (deduction_policy() & options::deductions::v2) {
switch (preferred_defs()) {
case options::preferred_defs::none:
process_deductions_v2<NoPreferredDefs>();
break;
case options::preferred_defs::immediate_no_stack:
process_deductions_v2<ImmediateDef<DoNotStackDeductions>>();
break;
case options::preferred_defs::immediate_yes_stack:
process_deductions_v2<ImmediateDef<StackDeductions>>();
break;
case options::preferred_defs::deferred:
process_deductions_v2<QueuePreferredDefs>();
default:
break;
}
}
else {
LIBSEMIGROUPS_ASSERT(deduction_policy() & options::deductions::v1);
switch (preferred_defs()) {
case options::preferred_defs::none:
process_deductions_v1<NoPreferredDefs>();
break;
case options::preferred_defs::immediate_no_stack:
process_deductions_v1<ImmediateDef<DoNotStackDeductions>>();
break;
case options::preferred_defs::immediate_yes_stack:
process_deductions_v1<ImmediateDef<StackDeductions>>();
break;
case options::preferred_defs::deferred:
process_deductions_v1<QueuePreferredDefs>();
default:
break;
}
}
}
// Version 1
template <
typename TPreferredDefs>
void ToddCoxeter::process_deductions_v1() {
LIBSEMIGROUPS_ASSERT(_felsch_tree != nullptr);
LIBSEMIGROUPS_ASSERT(!_prefilled);
size_t report_check = 100
'000;
while (!_deduct->empty()) {
auto d = _deduct->pop();
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.total_deduct++;
#endif
if (is_active_coset(d.first)) {
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.nr_active_deduct++;
#endif
report_check--;
_felsch_tree->push_back(d.second);
process_deductions_dfs_v1<TPreferredDefs>(d.first);
process_coincidences(stack_deductions::yes);
// The report_check bit in the next line stops us from calling
// report() too often, which can bring a time penalty.
if (report_check == 0 && report()) {
report_check = 100
'000;
report_active_cosets(__func__);
}
}
if (_deduct->empty()) {
process_coincidences(stack_deductions::yes);
}
}
process_coincidences(stack_deductions::yes);
}
template <
typename TPreferredDefs>
void ToddCoxeter::process_deductions_dfs_v1(coset_type c) {
for (
auto it = _felsch_tree->cbegin(); it < _felsch_tree->cend(); ++it) {
push_definition_felsch<StackDeductions,
DoNotProcessCoincidences,
TPreferredDefs>(c, *it);
}
size_t
const n = number_of_generators();
for (size_t x = 0; x < n; ++x) {
if (_felsch_tree->push_front(x)) {
coset_type e = _word_graph.first_source(c, x);
while (e != UNDEFINED) {
process_deductions_dfs_v1<TPreferredDefs>(e);
e = _word_graph.next_source(e, x);
}
_felsch_tree->pop_front();
}
}
}
// Version 2
template <
typename TPreferredDefs>
void ToddCoxeter::process_deductions_v2() {
LIBSEMIGROUPS_ASSERT(_felsch_tree != nullptr);
LIBSEMIGROUPS_ASSERT(!_prefilled);
size_t report_check = 100
'000;
while (!_deduct->empty()) {
auto d = _deduct->pop();
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.total_deduct++;
#endif
if (is_active_coset(d.first)) {
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.nr_active_deduct++;
#endif
report_check--;
_felsch_tree->push_back(d.second);
for (
auto it = _felsch_tree->cbegin(); it < _felsch_tree->cend();
++it) {
// Using anything other than NoPreferredDefs here seems to be bad
// in test case "ACE --- perf602p5 - Felsch", maybe this is a good
// example where the fill factor would be useful?
push_definition_felsch<StackDeductions,
DoNotProcessCoincidences,
NoPreferredDefs>(d.first, *it);
}
process_deductions_dfs_v2<TPreferredDefs>(d.first, d.first);
process_coincidences(stack_deductions::yes);
// The report_check bit in the next line stops us from calling
// report() too often, which can bring a time penalty.
if (report_check == 0 && report()) {
report_check = 100
'000;
report_active_cosets(__func__);
}
}
if (_deduct->empty()) {
process_coincidences(stack_deductions::yes);
}
}
process_coincidences(stack_deductions::yes);
}
template <
typename TPreferredDefs>
void ToddCoxeter::process_deductions_dfs_v2(coset_type root, coset_type c) {
size_t
const n = number_of_generators();
for (size_t x = 0; x < n; ++x) {
coset_type e = _word_graph.first_source(c, x);
if (e != UNDEFINED && _felsch_tree->push_front(x)) {
// We only need to push the side (the good side) of the relation
// that corresponds to the prefix in the tree through one preimage,
// because pushing it through any preimage leads to the same place
// (this is how the preimages/tree works!). If that place is more
// than one away from the end of the relation, then we don't have to
// do anything further, i.e. no more pushes of any other preimage or
// any pushes involving the other side of the relation. Do the
// pushing of the good side once here and pass the result to the dfs
// function. Update the pushing above to only do the other (bad)
// side of the relations.
for (
auto it = _felsch_tree->cbegin(); it < _felsch_tree->cend();
++it) {
auto i = *it;
// good
auto j = (i % 2 == 0 ? *it + 1 : *it - 1);
auto const& u = _relations[i];
auto const& v = _relations[j];
LIBSEMIGROUPS_ASSERT(action_digraph_helper::follow_path_nc(
_word_graph,
_word_graph.first_source(c, x),
u.cbegin(),
u.cbegin() + _felsch_tree->length() - 1)
== root);
// Start the push through not at the preimage, but at the original
// node definition we are processing; again because we know that
// all paths lead to this node (again by the definition of the
// search).
coset_type y = action_digraph_helper::follow_path_nc(
_word_graph,
root,
u.cbegin() + _felsch_tree->length() - 1,
u.cend() - 1);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.tc2_appl++;
#endif
if (y == UNDEFINED) {
continue;
}
e = _word_graph.first_source(c, x);
while (e != UNDEFINED) {
coset_type z = action_digraph_helper::follow_path_nc(
_word_graph, e, v.cbegin(), v.cend() - 1);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.tc2_appl++;
#endif
if (z != UNDEFINED) {
push_definition<StackDeductions,
DoNotProcessCoincidences,
TPreferredDefs>(y, u.back(), z, v.back());
}
e = _word_graph.next_source(e, x);
}
}
e = _word_graph.first_source(c, x);
while (e != UNDEFINED) {
process_deductions_dfs_v2<TPreferredDefs>(root, e);
e = _word_graph.next_source(e, x);
}
_felsch_tree->pop_front();
}
}
}
////////////////////////////////////////////////////////////////////////
// 20. ToddCoxeter - member functions (lookahead) - private
////////////////////////////////////////////////////////////////////////
void ToddCoxeter::perform_lookahead() {
detail::Timer t;
state
const old_state = _state;
_state = state::lookahead;
if (lookahead() & options::lookahead::partial) {
REPORT_DEFAULT(
"performing partial lookahead...\n");
// Start lookahead from the coset after _current
_current_la = next_active_coset(_current);
}
else {
LIBSEMIGROUPS_ASSERT(_settings->lookahead & options::lookahead::full);
REPORT_DEFAULT(
"performing full lookahead...\n");
// Start from the first coset
_current_la = _id_coset;
}
size_t number_of_killed = 0;
if (lookahead() & options::lookahead::hlt) {
number_of_killed = hlt_lookahead(old_state);
}
else {
LIBSEMIGROUPS_ASSERT(lookahead() & options::lookahead::felsch);
number_of_killed = felsch_lookahead(old_state);
}
report_cosets_killed(__func__, number_of_killed);
if (number_of_cosets_active()
< (next_lookahead() / lookahead_growth_factor())
&& next_lookahead() > min_lookahead()) {
// If the next_lookahead is much bigger than the current number of
// cosets, then reduce the next lookahead.
report_inc_lookahead(
__func__, lookahead_growth_factor() * number_of_cosets_active());
next_lookahead(lookahead_growth_factor() * number_of_cosets_active());
}
else if (number_of_cosets_active() > next_lookahead()
|| number_of_killed < (number_of_cosets_active()
/ lookahead_growth_threshold())) {
// Otherwise, if we already exceed the next_lookahead or too few
// cosets were killed, then increase the next lookahead.
report_inc_lookahead(__func__,
next_lookahead() * lookahead_growth_factor());
_settings->next_lookahead *= lookahead_growth_factor();
}
report_time(__func__, t);
_state = old_state;
}
size_t ToddCoxeter::hlt_lookahead(state
const& old_state) {
report_active_cosets(__func__);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.hlt_lookahead_calls++;
#endif
size_t
const old_number_of_killed = number_of_cosets_killed();
while (_current_la != first_free_coset()
// when running certain strategies the state is finished at this
// point, and so stopped() == true, but we anyway want to perform
// a full lookahead, which is why "_state == state::finished" is
// in the next line.
&& (old_state == state::finished || !stopped())) {
for (
auto it = _relations.cbegin(); it < _relations.cend(); it += 2) {
push_definition_felsch<DoNotStackDeductions,
ProcessCoincidences<stack_deductions::no>,
NoPreferredDefs>(
// Using NoPreferredDefs is just a (more or less) arbitrary
// choice, could allow the other choices here too (which works,
// but didn't seem to be very useful).
_current_la,
*it,
*(it + 1));
}
_current_la = next_active_coset(_current_la);
if (report()) {
report_active_cosets(__func__);
}
}
return number_of_cosets_killed() - old_number_of_killed;
}
size_t ToddCoxeter::felsch_lookahead(state
const& old_state) {
report_active_cosets(__func__);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.f_lookahead_calls++;
#endif
size_t
const old_number_of_killed = number_of_cosets_killed();
init_felsch_tree();
while (_current_la != first_free_coset()
// when running certain strategies the state is finished at
// this point, and so stopped() == true, but we anyway want to
// perform a full lookahead, which is why "_state ==
// state::finished" is in the next line.
&& (old_state == state::finished || !stopped())) {
for (size_t a = 0; a < number_of_generators(); ++a) {
_deduct->emplace(_current_la, a);
}
process_deductions();
_current_la = next_active_coset(_current_la);
if (report()) {
report_active_cosets(__func__);
}
}
return number_of_cosets_killed() - old_number_of_killed;
}
////////////////////////////////////////////////////////////////////////
// 21. ToddCoxeter - member functions (standardize) - private
////////////////////////////////////////////////////////////////////////
void ToddCoxeter::init_standardize() {
if (_tree == nullptr) {
_tree = std::make_unique<std::vector<TreeNode>>(
number_of_cosets_active(), TreeNode());
}
else {
_tree->resize(number_of_cosets_active());
}
LIBSEMIGROUPS_ASSERT(_coinc.empty());
LIBSEMIGROUPS_ASSERT(_deduct->empty());
}
bool ToddCoxeter::standardize_immediate(coset_type s, letter_type x) {
LIBSEMIGROUPS_ASSERT(is_active_coset(s));
LIBSEMIGROUPS_ASSERT(!finished());
LIBSEMIGROUPS_ASSERT(_coinc.empty());
coset_type
const r = _word_graph.unsafe_neighbor(s, x);
LIBSEMIGROUPS_ASSERT(r == UNDEFINED || is_active_coset(r));
if (r != UNDEFINED) {
if (r > _standard_max) {
_standard_max++;
if (r > _standard_max) {
swap_cosets(_standard_max, r);
LIBSEMIGROUPS_ASSERT(is_active_coset(_standard_max));
return true;
}
}
}
return false;
}
bool ToddCoxeter::standardize_deferred(Perm& p,
Perm& q,
coset_type
const s,
coset_type& t,
letter_type
const x) {
// p : new -> old and q : old -> new
coset_type r = _word_graph.unsafe_neighbor(p[s], x);
if (r != UNDEFINED) {
r = q[r];
// new
if (r > t) {
t++;
if (r > t) {
std::swap(p[t], p[r]);
std::swap(q[p[t]], q[p[r]]);
}
(*_tree)[t].parent = (s == t ? r : s);
(*_tree)[t].gen = x;
return true;
}
}
return false;
}
bool ToddCoxeter::lex_standardize() {
REPORT_DEFAULT(
"standardizing:\t%*s(%s)\n", 26,
"", __func__);
detail::Timer tmr;
coset_type s = 0;
coset_type t = 0;
letter_type x = 0;
size_t
const n = number_of_generators();
Perm p(coset_capacity(), 0);
std::iota(p.begin(), p.end(), 0);
Perm q(coset_capacity(), 0);
std::iota(q.begin(), q.end(), 0);
bool result =
false;
// Perform a DFS through the _word_graph.table()
while (s <= t) {
if (standardize_deferred(p, q, s, t, x)) {
result =
true;
s = t;
x = 0;
continue;
}
x++;
if (x == n) {
// backtrack
x = (*_tree)[s].gen;
s = (*_tree)[s].parent;
}
}
apply_permutation(p, q);
report_time(__func__, tmr);
#ifdef LIBSEMIGROUPS_DEBUG
debug_validate_forwd_bckwd();
debug_validate_table();
// debug_validate_word_graph();
#endif
return result;
}
bool ToddCoxeter::shortlex_standardize() {
REPORT_DEFAULT(
"standardizing:\t%*s(%s)\n", 26,
"", __func__);
detail::Timer tmr;
coset_type t = 0;
size_t
const n = number_of_generators();
Perm p(coset_capacity(), 0);
std::iota(p.begin(), p.end(), 0);
Perm q(coset_capacity(), 0);
std::iota(q.begin(), q.end(), 0);
bool result =
false;
for (coset_type s = 0; s <= t; ++s) {
for (letter_type x = 0; x < n; ++x) {
result |= standardize_deferred(p, q, s, t, x);
}
}
apply_permutation(p, q);
report_time(__func__, tmr);
#ifdef LIBSEMIGROUPS_DEBUG
debug_validate_forwd_bckwd();
debug_validate_table();
// debug_validate_word_graph();
#endif
return result;
}
// This is how the recursive words up to a given length M, and on an
// arbitrary finite alphabet are generated. On a single letter alphabet,
// this order is just increasing powers of the only generator:
//
// a < aa < aaa < aaaa < ... < aa...a (M times)
//
// With an n-letter alphabet A = {a_1, a_2, ..., a_n}, suppose we have
// already obtained all of the words W_{n - 1} containing {a_1, ..., a_{n
// - 1}}. Every word in W_{n - 1} is less than any word containing a_n,
// and the least word greater than every word in W_{n - 1} is a_n. Words
// greater than a_n are obtain in the follow way, where:
//
// x: is the maximum word in W_{n - 1}, this is constant, in the
// description
// that follows.
// u: the first word obtained in point (1), the first time it is applied
// after (2) has been applied, starting with u = a_{n - 1}.
// v: a word with one fewer letters than u, starting with the empty
// word. w: a word such that w < u, also starting with the empty word.
//
// (a) If v < x, then v is replaced by the next word in the order. If |uv|
// <= M, then the next word is uv. Otherwise, goto a.
//
// (b) If v = x, then and there exists a word w' in the set of words
// obtained so far such that w' > w and |w'| <= M - 1, then replace
// w with w', replace u by wa_n, replace v by the empty word, and
// the next word is wa_n.
//
// If no such word w' exists, then we have enumerated all the
// required words, and we can stop.
//
// For example, if A = {a, b} and M = 4, then the initial elements in
// the order are:
//
// e < a < aa < aaa < aaaa (e is the empty word)
//
// Set b > aaaa. At this point, x = aaaa, u = b, v = e, w = e, and so
// (1) applies, v <- a, and since |uv| = ba <= 4 = M, the next word is
// ba. Repeatedly applying (1), until it fails to hold, we obtain the
// following:
//
// aaaa < b < ba < baa < baaa
//
// After defining baa < baaa, x = aaaa, u = b, v = aaaa, and w = e.
// Hence v = x, and so (2) applies. The next w' in the set of words so
// far enumerated is a, and |a| = 1 <= 3 = M - 1, and so w <- a, u <-
// ab, v <- e, and the next word is ab. We repeatedly apply (1), until
// it fails, to obtain
//
// baaa < ab < aba < abaa
//
// At which point u = b, v = aaaa = x, and w = a. Hence (2) applies, w
// <- aa, v <- e, u <- aab, and the next word is: aab. And so on ...
// TODO(later): improve this, it is currently very slow.
bool ToddCoxeter::recursive_standardize() {
REPORT_DEFAULT(
"standardizing (recursive)...\n");
detail::Timer tmr;
std::vector<word_type> out;
size_t
const n = number_of_generators();
letter_type a = 0;
coset_type s = 0;
coset_type t = 0;
Perm p(coset_capacity(), 0);
std::iota(p.begin(), p.end(), 0);
Perm q(coset_capacity(), 0);
std::iota(q.begin(), q.end(), 0);
bool result =
false;
while (s <= t) {
if (standardize_deferred(p, q, s, t, 0)) {
out.push_back(word_type(t, a));
result =
true;
}
s++;
}
a++;
bool new_generator =
true;
int x, u, w;
while (a < n && t < number_of_cosets_active() - 1) {
if (new_generator) {
w = -1;
// -1 is the empty word
if (standardize_deferred(p, q, 0, t, a)) {
result =
true;
out.push_back({a});
}
x = out.size() - 1;
u = out.size() - 1;
new_generator =
false;
}
coset_type
const uu = action_digraph_helper::follow_path_nc(
_word_graph, 0, out[u].begin(), out[u].end());
if (uu != UNDEFINED) {
for (
int v = 0; v < x; v++) {
coset_type
const uuv = action_digraph_helper::follow_path_nc(
_word_graph, uu, out[v].begin(), out[v].end() - 1);
if (uuv != UNDEFINED) {
s = q[uuv];
if (standardize_deferred(p, q, s, t, out[v].back())) {
result =
true;
word_type nxt = out[u];
nxt.insert(nxt.end(), out[v].begin(), out[v].end());
out.push_back(std::move(nxt));
}
}
}
}
w++;
if (
static_cast<size_t>(w) < out.size()) {
coset_type
const ww = action_digraph_helper::follow_path_nc(
_word_graph, 0, out[w].begin(), out[w].end());
if (ww != UNDEFINED) {
s = q[ww];
if (standardize_deferred(p, q, s, t, a)) {
result =
true;
u = out.size();
word_type nxt = out[w];
nxt.push_back(a);
out.push_back(std::move(nxt));
}
}
}
else {
a++;
new_generator =
true;
}
}
apply_permutation(p, q);
report_time(__func__, tmr);
#ifdef LIBSEMIGROUPS_DEBUG
debug_validate_forwd_bckwd();
debug_validate_table();
// debug_validate_word_graph();
#endif
return result;
}
// The permutation q must map the active cosets to the [0, ..
// , number_of_cosets_active())
void ToddCoxeter::apply_permutation(Perm& p, Perm& q) {
// p : new -> old, q = p ^ -1
#ifdef LIBSEMIGROUPS_DEBUG
for (size_t c = 0; c < q.size(); ++c) {
LIBSEMIGROUPS_ASSERT(
(is_active_coset(c) && q[c] < number_of_cosets_active())
|| (!is_active_coset(c) && q[c] >= number_of_cosets_active()));
LIBSEMIGROUPS_ASSERT(p[q[c]] == c);
LIBSEMIGROUPS_ASSERT(q[p[c]] == c);
}
#endif
_word_graph.permute_nodes_nc(p, q, number_of_cosets_active());
CosetManager::apply_permutation(p);
}
// Based on the procedure SWITCH in Sims' book, p193
// Swaps an active coset and another coset in the table.
void ToddCoxeter::swap_cosets(coset_type c, coset_type d) {
LIBSEMIGROUPS_ASSERT(_coinc.empty());
LIBSEMIGROUPS_ASSERT(c != _id_coset);
LIBSEMIGROUPS_ASSERT(d != _id_coset);
LIBSEMIGROUPS_ASSERT(c != d);
LIBSEMIGROUPS_ASSERT(is_valid_coset(c));
LIBSEMIGROUPS_ASSERT(is_valid_coset(d));
if (is_active_coset(c) && is_active_coset(d)) {
_word_graph.swap_nodes(c, d);
}
else if (is_active_coset(c)) {
_word_graph.rename_node(c, d);
}
else {
LIBSEMIGROUPS_ASSERT(is_active_coset(d));
_word_graph.rename_node(d, c);
}
switch_cosets(c, d);
}
////////////////////////////////////////////////////////////////////////
// 22. ToddCoxeter - member functions (debug) - private
////////////////////////////////////////////////////////////////////////
#ifdef LIBSEMIGROUPS_DEBUG
// Validates the coset table.
void ToddCoxeter::debug_validate_table()
const {
REPORT_DEBUG_DEFAULT(
"validating the coset table...\n");
size_t
const n = number_of_generators();
coset_type c = _id_coset;
while (c != first_free_coset()) {
if (!is_active_coset(c)) {
LIBSEMIGROUPS_EXCEPTION(
"invalid table, coset %d is both free and active!", c);
}
for (letter_type x = 0; x < n; ++x) {
if (_word_graph.unsafe_neighbor(c, x) != UNDEFINED
&& !is_active_coset(_word_graph.unsafe_neighbor(c, x))) {
LIBSEMIGROUPS_EXCEPTION(
"invalid table, _word_graph.unsafe_neighbor(%d, %d) = %d"
" is not an active coset or UNDEFINED!",
c,
x,
_word_graph.unsafe_neighbor(c, x));
}
}
c = next_active_coset(c);
}
}
// Validates _word_graph, this is very expensive.
void ToddCoxeter::debug_validate_word_graph()
const {
size_t
const n = number_of_generators();
coset_type c = _id_coset;
while (c != first_free_coset()) {
for (letter_type x = 0; x < n; ++x) {
coset_type cx = _word_graph.unsafe_neighbor(c, x);
if (cx != UNDEFINED && !is_active_coset(cx)) {
LIBSEMIGROUPS_EXCEPTION(
"invalid table, _word_graph.unsafe_neighbor(%d, %d) = %d"
" is not an active coset or UNDEFINED!",
c,
x,
cx);
}
}
c = next_active_coset(c);
}
c = _id_coset;
while (c != first_free_coset()) {
for (letter_type x = 0; x < n; ++x) {
coset_type e = _word_graph.first_source(c, x);
std::set<coset_type> stored_preimages;
while (e != UNDEFINED) {
if (!stored_preimages.insert(e).second) {
LIBSEMIGROUPS_EXCEPTION(
"duplicate preimage e = %llu of c = %llu "
"under generator x = %llu",
e,
c,
x);
}
if (!is_active_coset(e)) {
LIBSEMIGROUPS_EXCEPTION(
"dead coset preimage e = %llu of c = %llu "
"under generator x = %llu",
e,
c,
x);
}
if (_word_graph.unsafe_neighbor(e, x) != c) {
LIBSEMIGROUPS_EXCEPTION(
"invalid preimage, unsafe_neighbor(%d, %d) = %d != %d "
"butfound e = %d in the preimages of c = %d under "
"generator "
"x = %d",
e,
x,
_word_graph.unsafe_neighbor(e, x),
c,
e,
c,
x);
}
e = _word_graph.next_source(e, x);
}
// Check there are no missing preimages!
e = _id_coset;
while (e != first_free_coset()) {
if (_word_graph.unsafe_neighbor(e, x) == c
&& stored_preimages.insert(e).second) {
LIBSEMIGROUPS_EXCEPTION(
"missing preimage, _word_graph.unsafe_neighbor(%d, %d) == "
"%d but e = %d wasn't found in the stored preimages",
e,
x,
c,
e);
}
e = next_active_coset(e);
}
}
c = next_active_coset(c);
}
}
void ToddCoxeter::debug_verify_no_missing_deductions()
const {
if (!_deduct->empty()) {
LIBSEMIGROUPS_EXCEPTION(
"the deduction stack is not empty");
}
else if (!_coinc.empty()) {
LIBSEMIGROUPS_EXCEPTION(
"the coincidence stack is not empty");
}
for (coset_type c = _id_coset; c != first_free_coset();
c = next_active_coset(c)) {
for (
auto it = _relations.cbegin(); it < _relations.cend(); it += 2) {
word_type
const& u = *it;
word_type
const& v = *(it + 1);
LIBSEMIGROUPS_ASSERT(is_valid_coset(c));
LIBSEMIGROUPS_ASSERT(!u.empty());
LIBSEMIGROUPS_ASSERT(!v.empty());
coset_type x = action_digraph_helper::follow_path_nc(
_word_graph, c, u.cbegin(), u.cend() - 1);
if (x == UNDEFINED) {
return;
}
LIBSEMIGROUPS_ASSERT(is_valid_coset(x));
coset_type y = action_digraph_helper::follow_path_nc(
_word_graph, c, v.cbegin(), v.cend() - 1);
if (y == UNDEFINED) {
return;
}
LIBSEMIGROUPS_ASSERT(is_valid_coset(y));
letter_type a = u.back();
letter_type b = v.back();
coset_type xx = _word_graph.unsafe_neighbor(x, a);
coset_type yy = _word_graph.unsafe_neighbor(y, b);
if (xx == UNDEFINED && yy != UNDEFINED) {
LIBSEMIGROUPS_EXCEPTION(
"missing deduction tau(%d, %d) = %d when "
"pushing coset %d through %s = %s",
x,
a,
yy,
c,
detail::to_string(u).c_str(),
detail::to_string(v).c_str());
}
else if (xx != UNDEFINED && yy == UNDEFINED) {
// _table(y, b) <- xx
LIBSEMIGROUPS_EXCEPTION(
"missing deduction tau(%d, %d) = %d when "
"pushing coset %d through %s = %s",
y,
b,
xx,
c,
detail::to_string(u).c_str(),
detail::to_string(v).c_str());
}
else if (xx != UNDEFINED && yy != UNDEFINED) {
// _table(x, a) and _table(y, b) are defined
if (xx != yy) {
LIBSEMIGROUPS_EXCEPTION(
"missing coincidence %d = %d when "
"pushing coset %d through %s = %s",
xx,
yy,
c,
detail::to_string(u).c_str(),
detail::to_string(v).c_str());
}
}
}
}
}
#endif
////////////////////////////////////////////////////////////////////////
// Combining options
////////////////////////////////////////////////////////////////////////
ToddCoxeter::options::deductions
operator|(ToddCoxeter::options::deductions
const& opt1,
ToddCoxeter::options::deductions
const& opt2) {
using int_type = std::underlying_type_t<ToddCoxeter::options::deductions>;
auto int1 =
static_cast<int_type>(opt1);
auto int2 =
static_cast<int_type>(opt2);
if ((int1 < 4 && int2 < 4) || (int1 >= 4 && int2 >= 4)) {
LIBSEMIGROUPS_EXCEPTION(
"invalid operands %s and %s for operator|",
detail::to_string(opt1).c_str(),
detail::to_string(opt2).c_str());
}
return ToddCoxeter::options::deductions(int1 | int2);
}
bool operator&(ToddCoxeter::options::deductions
const& opt1,
ToddCoxeter::options::deductions
const& opt2) {
using int_type = std::underlying_type_t<ToddCoxeter::options::deductions>;
auto int1 =
static_cast<int_type>(opt1);
auto int2 =
static_cast<int_type>(opt2);
static int_type constexpr mask1 = 3;
static int_type constexpr mask2
= std::numeric_limits<int_type>::max() - 3;
if (int1 < 4 || int2 < 4) {
return (int1 & mask1) == (int2 & mask1);
}
else {
return (int1 & mask2) == (int2 & mask2);
}
}
ToddCoxeter::options::lookahead
operator|(ToddCoxeter::options::lookahead
const& opt1,
ToddCoxeter::options::lookahead
const& opt2) {
using int_type = std::underlying_type_t<ToddCoxeter::options::lookahead>;
auto int1 =
static_cast<int_type>(opt1);
auto int2 =
static_cast<int_type>(opt2);
if ((int1 <= 2 && int2 <= 2) || (int1 > 2 && int2 > 2)) {
LIBSEMIGROUPS_EXCEPTION(
"invalid operands %s and %s for operator|",
detail::to_string(opt1).c_str(),
detail::to_string(opt2).c_str());
}
return ToddCoxeter::options::lookahead(int1 | int2);
}
bool operator&(ToddCoxeter::options::lookahead
const& opt1,
ToddCoxeter::options::lookahead
const& opt2) {
using int_type = std::underlying_type_t<ToddCoxeter::options::lookahead>;
auto int1 =
static_cast<int_type>(opt1);
auto int2 =
static_cast<int_type>(opt2);
static int_type constexpr mask1 = 3;
static int_type constexpr mask2
= std::numeric_limits<int_type>::max() - 3;
if (int1 <= 2 || int2 <= 2) {
return (int1 & mask1) == (int2 & mask1);
}
else {
return (int1 & mask2) == (int2 & mask2);
}
}
// Silence compiler warnings in g++-11
std::ostringstream&
operator<<(std::ostringstream& os,
ToddCoxeter::options::strategy
const& val);
std::ostringstream&
operator<<(std::ostringstream& os,
ToddCoxeter::options::strategy
const& val) {
using strategy =
typename ToddCoxeter::options::strategy;
switch (val) {
case strategy::hlt:
os <<
"HLT";
break;
case strategy::felsch:
os <<
"Felsch";
break;
case strategy::random:
os <<
"random";
break;
case strategy::CR:
os <<
"CR";
break;
case strategy::R_over_C:
os <<
"R/C";
break;
case strategy::Cr:
os <<
"Cr";
break;
case strategy::Rc:
os <<
"Rc";
break;
default:
os <<
"unknown";
break;
}
return os;
}
// Silence compiler warnings in g++-11
std::ostringstream&
operator<<(std::ostringstream& os,
ToddCoxeter::options::deductions
const& val);
std::ostringstream&
operator<<(std::ostringstream& os,
ToddCoxeter::options::deductions
const& val) {
using deductions =
typename ToddCoxeter::options::deductions;
if (val & deductions::v1) {
os <<
"v1 + ";
}
else if (val & deductions::v2) {
os <<
"v2 + ";
}
else {
os <<
"not set + ";
}
if (val & deductions::no_stack_if_no_space) {
os <<
"no_stack_if_no_space";
}
else if (val & deductions::purge_from_top) {
os <<
"purge_from_top";
}
else if (val & deductions::purge_all) {
os <<
"purge_all";
}
else if (val & deductions::discard_all_if_no_space) {
os <<
"discard_all_if_no_space";
}
else if (val & deductions::unlimited) {
os <<
"unlimited";
}
else {
os <<
"not set";
}
return os;
}
// Silence compiler warnings in g++-11
std::ostringstream&
operator<<(std::ostringstream& os,
ToddCoxeter::options::lookahead
const& val);
std::ostringstream&
operator<<(std::ostringstream& os,
ToddCoxeter::options::lookahead
const& val) {
using lookahead =
typename ToddCoxeter::options::lookahead;
if (lookahead::partial & val) {
os <<
"partial ";
}
else if (lookahead::full & val) {
os <<
"full ";
}
else {
os <<
"not set + ";
}
if (lookahead::hlt & val) {
os <<
"HLT";
}
else if (lookahead::felsch & val) {
os <<
"Felsch";
}
else {
os <<
"not set + ";
}
return os;
}
// Silence compiler warnings in g++-11
std::ostringstream&
operator<<(std::ostringstream& os,
ToddCoxeter::options::froidure_pin
const& val);
std::ostringstream&
operator<<(std::ostringstream& os,
ToddCoxeter::options::froidure_pin
const& val) {
using froidure_pin =
typename ToddCoxeter::options::froidure_pin;
switch (val) {
case froidure_pin::none:
os <<
"none";
break;
case froidure_pin::use_relations:
os <<
"use_relations";
break;
case froidure_pin::use_cayley_graph:
os <<
"use_cayley_graph";
break;
default:
os <<
"unknown";
break;
}
return os;
}
// Silence compiler warnings in g++-11
std::ostringstream&
operator<<(std::ostringstream& os,
ToddCoxeter::options::preferred_defs
const& val);
std::ostringstream&
operator<<(std::ostringstream& os,
ToddCoxeter::options::preferred_defs
const& val) {
using preferred_defs =
typename ToddCoxeter::options::preferred_defs;
switch (val) {
case preferred_defs::none:
os <<
"none";
break;
case preferred_defs::immediate_no_stack:
os <<
"immediate + no deduction stacked";
break;
case preferred_defs::immediate_yes_stack:
os <<
"immediate + deduction stacked";
break;
case preferred_defs::deferred:
os <<
"deferred";
break;
default:
os <<
"unknown";
break;
}
return os;
}
}
// namespace congruence
}
// namespace libsemigroups