1 // 2 // libsemigroups - C++ library for semigroups and monoids 3 // Copyright (C) 2019 James D. Mitchell 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 // 18 19 // This file contains an implementation of the Todd-Coxeter algorithm for 20 // semigroups. 21 22 #include "libsemigroups/todd-coxeter.hpp" 23 24 #include <algorithm> // for reverse 25 #include <chrono> // for nanoseconds etc 26 #include <cstddef> // for size_t 27 #include <memory> // for shared_ptr 28 #include <numeric> // for iota 29 #include <random> // for mt19937 30 #include <string> // for operator+, basic_string 31 #include <utility> // for pair 32 33 #ifdef LIBSEMIGROUPS_DEBUG 34 #include <set> // for set 35 #endif 36 37 #include "libsemigroups/cong-intf.hpp" // for CongruenceInterface 38 #include "libsemigroups/coset.hpp" // for CosetManager 39 #include "libsemigroups/froidure-pin-base.hpp" // for FroidurePinBase 40 #include "libsemigroups/froidure-pin.hpp" // for FroidurePin 41 #include "libsemigroups/knuth-bendix.hpp" // for fpsemigroup::KnuthBendix 42 #include "libsemigroups/libsemigroups-config.hpp" // for LIBSEMIGROUPS_DEBUG 43 #include "libsemigroups/libsemigroups-debug.hpp" // for LIBSEMIGROUPS_ASSERT 44 #include "libsemigroups/libsemigroups-exception.hpp" // for LIBSEMIGROUPS_EXCEPTION 45 #include "libsemigroups/obvinf.hpp" // for IsObviouslyInfinite 46 #include "libsemigroups/report.hpp" // for REPORT 47 #include "libsemigroups/stl.hpp" // for apply_permutation 48 #include "libsemigroups/tce.hpp" // for TCE 49 #include "libsemigroups/timer.hpp" // for detail::Timer 50 #include "libsemigroups/types.hpp" // for letter_type 51 52 // TODO(later) 53 // 54 // 1. Explore whether row-filling is useful when performing HLT. I think the 55 // just means making sure that there are no undefined values in the row of 56 // the current coset, this is an option from ACE. 57 // 58 // 2. Allow there to be a limit to the number of deductions that are stacked. 59 // this is an option from ACE. There are 4 options as described: 60 // 61 // https://magma.maths.usyd.edu.au/magma/handbook/text/833 62 // 63 // 3. Explore whether deductions can be useful in HLT. 64 // 65 // 4. Make make_deductions_dfs non-recursive, this will likely only be an issue 66 // for presentations with extremely long relations. 67 // 68 // 5. Use path compression in _ident, or other techniques from union-find, see: 69 // 70 // https://www.boost.org/doc/libs/1_70_0/boost/pending/disjoint_sets.hpp 71 // 72 // 6. Wreath product standardize mem fn. 73 // 74 // 7. ACE stacks deductions when processing coincidences, we don't 75 76 //////////////////////////////////////////////////////////////////////////////// 77 // COSET TABLES: 78 // 79 // We use these three tables to store all a coset's images and preimages. 80 // _table[c][i] is coset c's image under generator i. 81 // _preim_init[c][i] is ONE of coset c's preimages under generator i. 82 // _preim_next[c][i] is a coset that has THE SAME IMAGE as coset c 83 // (under i) 84 // 85 // Hence to find all the preimages of c under i: 86 // - Let u = _preim_init[c][i] ONCE. 87 // - Let u = _preim_next[u][i] REPEATEDLY until it becomes UNDEFINED. 88 // Each u is one preimage. 89 // 90 // To add v, a new preimage of c under i: 91 // - Set _preim_next[v][i] to point to the current _preim_init[c][i]. 92 // - Then change _preim_init[c][i] to point to v. 93 // Now the new preimage and all the old preimages are stored. 94 //////////////////////////////////////////////////////////////////////////////// 95 96 //////////////////////////////////////////////////////////////////////// 97 // Reporting macros 98 //////////////////////////////////////////////////////////////////////// 99 100 #define TODD_COXETER_REPORT_COSETS() \ 101 REPORT_DEFAULT("%d defined, %d max, %d active, %d killed (%s)\n", \ 102 nr_cosets_defined(), \ 103 coset_capacity(), \ 104 nr_cosets_active(), \ 105 nr_cosets_killed(), \ 106 __func__); 107 108 #define TODD_COXETER_REPORT_SWITCH(t, r) \ 109 REPORT_VERBOSE_DEFAULT("switching %*d %s and %*d %s\n", \ 110 detail::to_string(nr_cosets_active()).length() \ 111 - detail::to_string(t).length() + 1, \ 112 t, \ 113 (is_active_coset(t) ? "(active)" : "(free) "), \ 114 detail::to_string(nr_cosets_active()).length() \ 115 - detail::to_string(r).length() + 1, \ 116 r, \ 117 (is_active_coset(r) ? "(active)" : "(free) ")); 118 119 #ifdef LIBSEMIGROUPS_DEBUG 120 #define TODD_COXETER_REPORT_OK() REPORT_DEBUG(" ok\n").flush_right().flush(); 121 #else 122 #define TODD_COXETER_REPORT_OK() 123 #endif 124 125 // Helper functions 126 namespace { 127 using class_index_type = libsemigroups::CongruenceInterface::class_index_type; 128 using word_type = libsemigroups::word_type; 129 sort_generating_pairs(std::vector<class_index_type> & perm,std::vector<word_type> & vec)130 void sort_generating_pairs(std::vector<class_index_type>& perm, 131 std::vector<word_type>& vec) { 132 // Apply the permutation (adapted from 133 // stl.hpp:apply_permutation) 134 size_t const n = perm.size(); 135 for (size_t i = 0; i < n; ++i) { 136 size_t current = i; 137 while (i != perm[current]) { 138 size_t next = perm[current]; 139 std::swap(vec[2 * current], vec[2 * next]); 140 std::swap(vec[2 * current + 1], vec[2 * next + 1]); 141 perm[current] = current; 142 current = next; 143 } 144 perm[current] = current; 145 } 146 } 147 sort_generating_pairs(std::function<bool (word_type const &,word_type const &)> func,std::vector<class_index_type> & perm,std::vector<word_type> & vec)148 void sort_generating_pairs( 149 std::function<bool(word_type const&, word_type const&)> func, 150 std::vector<class_index_type>& perm, 151 std::vector<word_type>& vec) { 152 // Sort each relation so that the lhs is greater than the rhs according 153 // to func. 154 for (auto it = vec.begin(); it < vec.end(); it += 2) { 155 if (func(*it, *(it + 1))) { 156 std::swap(*it, *(it + 1)); 157 } 158 } 159 160 // Create a permutation of the even indexed entries in vec 161 perm.resize(vec.size() / 2); 162 std::iota(perm.begin(), perm.end(), 0); 163 std::sort(perm.begin(), 164 perm.end(), 165 [&func, &vec](class_index_type x, class_index_type y) -> bool { 166 return func(vec[2 * x], vec[2 * y]); 167 }); 168 sort_generating_pairs(perm, vec); 169 } 170 } // namespace 171 172 namespace libsemigroups { 173 namespace congruence { 174 //////////////////////////////////////////////////////////////////////// 175 // Aliases 176 //////////////////////////////////////////////////////////////////////// 177 178 using coset_type = congruence::ToddCoxeter::coset_type; 179 using Coincidence = std::pair<coset_type, coset_type>; 180 using Deduction = std::pair<coset_type, letter_type>; 181 182 //////////////////////////////////////////////////////////////////////// 183 // Helper structs 184 //////////////////////////////////////////////////////////////////////// 185 186 struct StackDeductions { operator ()libsemigroups::congruence::StackDeductions187 inline void operator()(std::stack<Deduction>& stck, 188 coset_type c, 189 letter_type a) const noexcept { 190 stck.emplace(c, a); 191 } 192 }; 193 194 struct DoNotStackDeductions { operator ()libsemigroups::congruence::DoNotStackDeductions195 inline void operator()(std::stack<Deduction>&, 196 coset_type, 197 letter_type) const noexcept {} 198 }; 199 200 struct ProcessCoincidences { 201 template <typename TStackDeductions> operator ()libsemigroups::congruence::ProcessCoincidences202 void operator()(congruence::ToddCoxeter* tc) const noexcept { 203 tc->process_coincidences<TStackDeductions>(); 204 } 205 }; 206 207 struct DoNotProcessCoincidences { 208 template <typename TStackDeductions> operator ()libsemigroups::congruence::DoNotProcessCoincidences209 void operator()(congruence::ToddCoxeter*) const noexcept {} 210 }; 211 212 //////////////////////////////////////////////////////////////////////// 213 // Helper free functions 214 //////////////////////////////////////////////////////////////////////// 215 ff(coset_type c,coset_type d,coset_type r)216 static inline coset_type ff(coset_type c, coset_type d, coset_type r) { 217 return (r == c ? d : (r == d ? c : r)); 218 } 219 220 //////////////////////////////////////////////////////////////////////// 221 // ToddCoxeter - inner classes - private 222 //////////////////////////////////////////////////////////////////////// 223 224 struct ToddCoxeter::Settings { Settingslibsemigroups::congruence::ToddCoxeter::Settings225 Settings() 226 : 227 #ifdef LIBSEMIGROUPS_DEBUG 228 enable_debug_verify_no_missing_deductions(true), 229 #endif 230 lookahead(policy::lookahead::partial), 231 lower_bound(UNDEFINED), 232 next_lookahead(5000000), 233 froidure_pin(policy::froidure_pin::none), 234 random_interval(200000000), 235 save(false), 236 standardize(false), 237 strategy(policy::strategy::hlt) { 238 } 239 240 Settings(Settings const& copy) = default; 241 242 #ifdef LIBSEMIGROUPS_DEBUG 243 bool enable_debug_verify_no_missing_deductions; 244 #endif 245 policy::lookahead lookahead; 246 size_t lower_bound; 247 size_t next_lookahead; 248 policy::froidure_pin froidure_pin; 249 std::chrono::nanoseconds random_interval; 250 bool save; 251 bool standardize; 252 policy::strategy strategy; 253 }; 254 255 class ToddCoxeter::FelschTree { 256 public: 257 using index_type = size_t; 258 using state_type = size_t; 259 using const_iterator = std::vector<index_type>::const_iterator; 260 static constexpr state_type initial_state = 0; 261 static constexpr state_type final_state = UNDEFINED; 262 FelschTree(ToddCoxeter const * tc)263 explicit FelschTree(ToddCoxeter const* tc) 264 : _automata(tc->nr_generators(), 1, final_state), 265 _index(1, std::vector<index_type>({})), 266 _parent(1, state_type(UNDEFINED)) {} 267 268 FelschTree(FelschTree const&) = default; 269 add_relations(std::vector<word_type> const & rels)270 void add_relations(std::vector<word_type> const& rels) { 271 size_t nr_words = 0; 272 LIBSEMIGROUPS_ASSERT(rels.size() % 2 == 0); 273 for (auto const& w : rels) { 274 // For every prefix [w.cbegin(), last) 275 for (auto last = w.cend(); last > w.cbegin(); --last) { 276 // For every suffix [first, last) of the prefix [w.cbegin(), last) 277 for (auto first = w.cbegin(); first < last; ++first) { 278 // Find the maximal suffix of [first, last) that corresponds to 279 // an existing state . . . 280 auto it = last - 1; 281 state_type s = initial_state; 282 while (_automata.get(s, *it) != final_state && it > first) { 283 s = _automata.get(s, *it); 284 --it; 285 } 286 if (_automata.get(s, *it) == final_state) { 287 // [it + 1, last) is the maximal suffix of [first, last) that 288 // corresponds to the existing state s 289 size_t nr_states = _automata.nr_rows(); 290 _automata.add_rows((it + 1) - first); 291 _index.resize(_index.size() + ((it + 1) - first), {}); 292 _parent.resize(_parent.size() + ((it + 1) - first), UNDEFINED); 293 while (it >= first) { 294 // Add [it, last) as a new state 295 _automata.set(s, *it, nr_states); 296 _parent[nr_states] = s; 297 s = nr_states; 298 nr_states++; 299 it--; 300 } 301 } 302 } 303 // Find the state corresponding to the prefix [w.cbegin(), last) 304 auto it = last - 1; 305 state_type s = initial_state; 306 while (it >= w.cbegin()) { 307 s = _automata.get(s, *it); 308 LIBSEMIGROUPS_ASSERT(s != final_state); 309 --it; 310 } 311 index_type m = ((nr_words % 2) == 0 ? nr_words : nr_words - 1); 312 if (!std::binary_search(_index[s].cbegin(), _index[s].cend(), m)) { 313 _index[s].push_back(m); 314 } 315 } 316 nr_words++; 317 } 318 } 319 push_back(letter_type x)320 void push_back(letter_type x) { 321 _current_state = _automata.get(initial_state, x); 322 } 323 push_front(letter_type x)324 bool push_front(letter_type x) { 325 LIBSEMIGROUPS_ASSERT(x < _automata.nr_cols()); 326 if (_automata.get(_current_state, x) != final_state) { 327 _current_state = _automata.get(_current_state, x); 328 return true; 329 } else { 330 return false; 331 } 332 } 333 pop_front()334 void pop_front() { 335 _current_state = _parent[_current_state]; 336 } 337 cbegin() const338 const_iterator cbegin() const { 339 LIBSEMIGROUPS_ASSERT(_current_state != final_state); 340 return _index[_current_state].cbegin(); 341 } 342 cend() const343 const_iterator cend() const { 344 LIBSEMIGROUPS_ASSERT(_current_state != final_state); 345 return _index[_current_state].cend(); 346 } 347 348 private: 349 using StateTable = detail::DynamicArray2<state_type>; 350 StateTable _automata; 351 state_type _current_state; 352 std::vector<std::vector<index_type>> _index; 353 std::vector<state_type> _parent; 354 }; 355 356 struct ToddCoxeter::TreeNode { TreeNodelibsemigroups::congruence::ToddCoxeter::TreeNode357 TreeNode() : parent(UNDEFINED), gen(UNDEFINED) {} TreeNodelibsemigroups::congruence::ToddCoxeter::TreeNode358 TreeNode(coset_type p, letter_type g) : parent(p), gen(g) {} 359 coset_type parent; 360 letter_type gen; 361 }; 362 363 //////////////////////////////////////////////////////////////////////// 364 // ToddCoxeter - constructors and destructor - public 365 //////////////////////////////////////////////////////////////////////// 366 ToddCoxeter(congruence_type type)367 ToddCoxeter::ToddCoxeter(congruence_type type) 368 : CongruenceInterface(type), 369 CosetManager(), 370 _coinc(), 371 _deduct(), 372 _extra(), 373 _felsch_tree(nullptr), 374 _nr_pairs_added_earlier(0), 375 _prefilled(false), 376 _preim_init(0, 0, UNDEFINED), 377 _preim_next(0, 0, UNDEFINED), 378 _relations(), 379 _settings(new Settings()), 380 _standardized(order::none), 381 _state(state::constructed), 382 _table(0, 0, UNDEFINED), 383 _tree(nullptr) {} 384 ToddCoxeter(ToddCoxeter const & copy)385 ToddCoxeter::ToddCoxeter(ToddCoxeter const& copy) 386 : CongruenceInterface(copy), 387 CosetManager(copy), 388 _coinc(copy._coinc), 389 _deduct(copy._deduct), 390 _extra(copy._extra), 391 _felsch_tree(nullptr), 392 _nr_pairs_added_earlier(copy._nr_pairs_added_earlier), 393 _prefilled(copy._prefilled), 394 _preim_init(copy._preim_init), 395 _preim_next(copy._preim_next), 396 _relations(copy._relations), 397 _settings(detail::make_unique<Settings>(*copy._settings)), 398 _standardized(copy._standardized), 399 _state(copy._state), 400 _table(copy._table), 401 _tree(nullptr) { 402 if (copy._felsch_tree != nullptr) { 403 _felsch_tree = detail::make_unique<FelschTree>(*copy._felsch_tree); 404 } 405 if (copy._tree != nullptr) { 406 _tree = detail::make_unique<Tree>(*copy._tree); 407 } 408 } 409 ToddCoxeter(congruence_type type,std::shared_ptr<FroidurePinBase> S,policy::froidure_pin p)410 ToddCoxeter::ToddCoxeter(congruence_type type, 411 std::shared_ptr<FroidurePinBase> S, 412 policy::froidure_pin p) 413 : ToddCoxeter(type) { 414 _settings->froidure_pin = p; 415 set_parent_froidure_pin(S); 416 set_nr_generators(S->nr_generators()); 417 } 418 419 // Construct a ToddCoxeter object representing a congruence over the 420 // semigroup defined by copy (the quotient that is). ToddCoxeter(congruence_type typ,ToddCoxeter & copy)421 ToddCoxeter::ToddCoxeter(congruence_type typ, ToddCoxeter& copy) 422 : ToddCoxeter(typ) { 423 if (copy.kind() != congruence_type::twosided && typ != copy.kind()) { 424 LIBSEMIGROUPS_EXCEPTION("incompatible types of congruence, found (" 425 + congruence_type_to_string(copy.kind()) + " / " 426 + congruence_type_to_string(typ) 427 + ") but only (left / left), (right / " 428 "right), (two-sided / *) are valid"); 429 } 430 copy_relations_for_quotient(copy); 431 } 432 ToddCoxeter(congruence_type typ,fpsemigroup::ToddCoxeter & copy)433 ToddCoxeter::ToddCoxeter(congruence_type typ, 434 fpsemigroup::ToddCoxeter& copy) 435 : ToddCoxeter(typ) { 436 set_parent_froidure_pin(copy); 437 if (copy.finished()) { 438 set_nr_generators(copy.froidure_pin()->nr_generators()); 439 _settings->froidure_pin = policy::froidure_pin::use_cayley_graph; 440 } else { 441 copy_relations_for_quotient(copy.congruence()); 442 _settings->froidure_pin = policy::froidure_pin::use_relations; 443 } 444 } 445 ToddCoxeter(congruence_type typ,fpsemigroup::KnuthBendix & kb)446 ToddCoxeter::ToddCoxeter(congruence_type typ, fpsemigroup::KnuthBendix& kb) 447 : ToddCoxeter(typ) { 448 set_nr_generators(kb.alphabet().size()); 449 // TODO(later) use active rules when available 450 for (auto it = kb.cbegin_rules(); it < kb.cend_rules(); ++it) { 451 add_pair(kb.string_to_word(it->first), kb.string_to_word(it->second)); 452 } 453 // Note that something goes horribly wrong if the next line is above the 454 // for loop above. 455 set_parent_froidure_pin(kb); 456 if (kb.finished() && kb.is_obviously_finite()) { 457 LIBSEMIGROUPS_ASSERT(_settings->froidure_pin 458 == policy::froidure_pin::none); 459 _settings->froidure_pin = policy::froidure_pin::use_cayley_graph; 460 } 461 } 462 463 ToddCoxeter::~ToddCoxeter() = default; 464 465 //////////////////////////////////////////////////////////////////////// 466 // CongruenceInterface - non-pure virtual member functions - public 467 //////////////////////////////////////////////////////////////////////// 468 contains(word_type const & lhs,word_type const & rhs)469 bool ToddCoxeter::contains(word_type const& lhs, word_type const& rhs) { 470 validate_word(lhs); 471 validate_word(rhs); 472 init(); 473 if (empty()) { 474 // Note that it's possible to be not _prefilled, and have _relations, 475 // and _extra empty, because shrink_to_fit clears _relations and 476 // _extra. That's why we use empty() here instead of checking 477 // _prefilled && _relations.empty() && _extra.empty(), as used to be the 478 // case. 479 // This defines the free semigroup 480 return lhs == rhs; 481 } 482 return CongruenceInterface::contains(lhs, rhs); 483 } 484 485 //////////////////////////////////////////////////////////////////////// 486 // ToddCoxeter - member functions (init + settings) - public 487 //////////////////////////////////////////////////////////////////////// 488 489 // Init prefill(table_type const & table)490 void ToddCoxeter::prefill(table_type const& table) { 491 prefill_and_validate(table, true); 492 init_preimages_from_table(); 493 } 494 495 // Settings 496 ToddCoxeter& froidure_pin_policy(policy::froidure_pin x)497 ToddCoxeter::froidure_pin_policy(policy::froidure_pin x) noexcept { 498 _settings->froidure_pin = x; 499 return *this; 500 } 501 502 ToddCoxeter::policy::froidure_pin froidure_pin_policy() const503 ToddCoxeter::froidure_pin_policy() const noexcept { 504 return _settings->froidure_pin; 505 } 506 lookahead(policy::lookahead x)507 ToddCoxeter& ToddCoxeter::lookahead(policy::lookahead x) noexcept { 508 _settings->lookahead = x; 509 return *this; 510 } 511 lower_bound(size_t n)512 ToddCoxeter& ToddCoxeter::lower_bound(size_t n) noexcept { 513 _settings->lower_bound = n; 514 return *this; 515 } 516 next_lookahead(size_t n)517 ToddCoxeter& ToddCoxeter::next_lookahead(size_t n) noexcept { 518 _settings->next_lookahead = n; 519 return *this; 520 } 521 standardize(bool x)522 ToddCoxeter& ToddCoxeter::standardize(bool x) noexcept { 523 _settings->standardize = x; 524 return *this; 525 } 526 save(bool x)527 ToddCoxeter& ToddCoxeter::save(bool x) { 528 if ((_prefilled 529 || (has_parent_froidure_pin() 530 && parent_froidure_pin()->is_finite() == tril::TRUE 531 && (_settings->froidure_pin == policy::froidure_pin::none 532 || _settings->froidure_pin 533 == policy::froidure_pin::use_cayley_graph))) 534 && x) { 535 LIBSEMIGROUPS_EXCEPTION("cannot use the save setting with a " 536 "prefilled ToddCoxeter instance"); 537 } 538 _settings->save = x; 539 return *this; 540 } 541 strategy(policy::strategy x)542 ToddCoxeter& ToddCoxeter::strategy(policy::strategy x) { 543 if ((_prefilled 544 || (has_parent_froidure_pin() 545 && parent_froidure_pin()->is_finite() == tril::TRUE 546 && (_settings->froidure_pin == policy::froidure_pin::none 547 || _settings->froidure_pin 548 == policy::froidure_pin::use_cayley_graph))) 549 && x == policy::strategy::felsch) { 550 LIBSEMIGROUPS_EXCEPTION("cannot use the Felsch strategy with a " 551 "prefilled ToddCoxeter instance"); 552 } 553 _settings->strategy = x; 554 return *this; 555 } 556 strategy() const557 ToddCoxeter::policy::strategy ToddCoxeter::strategy() const noexcept { 558 return _settings->strategy; 559 } 560 561 ToddCoxeter& random_interval(std::chrono::nanoseconds x)562 ToddCoxeter::random_interval(std::chrono::nanoseconds x) noexcept { 563 _settings->random_interval = x; 564 return *this; 565 } 566 sort_generating_pairs(std::function<bool (word_type const &,word_type const &)> func)567 ToddCoxeter& ToddCoxeter::sort_generating_pairs( 568 std::function<bool(word_type const&, word_type const&)> func) { 569 if (started()) { 570 LIBSEMIGROUPS_EXCEPTION( 571 "Cannot sort relations, the coset enumeration has started!") 572 } 573 init(); 574 std::vector<class_index_type> perm; 575 ::sort_generating_pairs(func, perm, _relations); 576 ::sort_generating_pairs(func, perm, _extra); 577 return *this; 578 } 579 random_shuffle_generating_pairs()580 ToddCoxeter& ToddCoxeter::random_shuffle_generating_pairs() { 581 if (started()) { 582 LIBSEMIGROUPS_EXCEPTION( 583 "Cannot shuffle relations, the coset enumeration has started!") 584 } 585 init(); 586 std::vector<class_index_type> perm(0, _relations.size()); 587 std::iota(perm.begin(), perm.end(), 0); 588 std::random_device rd; 589 std::mt19937 g(rd()); 590 std::shuffle(perm.begin(), perm.end(), g); 591 ::sort_generating_pairs(perm, _relations); 592 ::sort_generating_pairs(perm, _extra); 593 return *this; 594 } 595 596 //////////////////////////////////////////////////////////////////////// 597 // ToddCoxeter - member functions (container-like) - public 598 //////////////////////////////////////////////////////////////////////// 599 empty() const600 bool ToddCoxeter::empty() const { 601 return _relations.empty() && _extra.empty() && nr_cosets_active() == 1; 602 } 603 reserve(size_t n)604 void ToddCoxeter::reserve(size_t n) { 605 size_t m = coset_capacity(); 606 if (n > m) { 607 m = n - m; 608 _table.add_rows(m); 609 _preim_init.add_rows(m); 610 _preim_next.add_rows(m); 611 add_free_cosets(m); 612 } 613 } 614 shrink_to_fit()615 void ToddCoxeter::shrink_to_fit() { 616 if (!finished()) { 617 return; 618 } 619 if (!is_standardized()) { 620 standardize(order::shortlex); 621 } 622 623 _table.shrink_rows_to(nr_cosets_active()); 624 // Cannot delete _preim_init or _preim_next because they are required by 625 // standardize 626 _preim_init.shrink_rows_to(nr_cosets_active()); 627 _preim_next.shrink_rows_to(nr_cosets_active()); 628 _relations.clear(); 629 _relations.shrink_to_fit(); 630 _extra.clear(); 631 _extra.shrink_to_fit(); 632 erase_free_cosets(); 633 } 634 635 //////////////////////////////////////////////////////////////////////// 636 // ToddCoxeter - member functions (state) - public 637 //////////////////////////////////////////////////////////////////////// 638 complete() const639 bool ToddCoxeter::complete() const noexcept { 640 size_t const n = nr_generators(); 641 coset_type c = _id_coset; 642 while (c != first_free_coset()) { 643 for (size_t a = 0; a < n; ++a) { 644 if (_table.get(c, a) == UNDEFINED) { 645 return false; 646 } 647 } 648 c = next_active_coset(c); 649 } 650 return true; 651 } 652 compatible() const653 bool ToddCoxeter::compatible() const noexcept { 654 coset_type c = _id_coset; 655 while (c != first_free_coset()) { 656 for (auto it = _relations.cbegin(); it < _relations.cend(); it += 2) { 657 coset_type x = tau(c, (*it).cbegin(), (*it).cend()); 658 LIBSEMIGROUPS_ASSERT(is_active_coset(x) || x == UNDEFINED); 659 coset_type y = tau(c, (*(it + 1)).cbegin(), (*(it + 1)).cend()); 660 LIBSEMIGROUPS_ASSERT(is_active_coset(y) || y == UNDEFINED); 661 if (x != y) { 662 return false; 663 } 664 } 665 c = next_active_coset(c); 666 } 667 return true; 668 } 669 670 //////////////////////////////////////////////////////////////////////// 671 // ToddCoxeter - member functions (standardization) - public 672 //////////////////////////////////////////////////////////////////////// 673 is_standardized() const674 bool ToddCoxeter::is_standardized() const noexcept { 675 return _standardized != order::none; 676 } 677 standardize(order rdr)678 void ToddCoxeter::standardize(order rdr) { 679 if (_standardized == rdr || empty()) { 680 return; 681 } 682 switch (rdr) { 683 case order::shortlex: 684 init_standardize(); 685 shortlex_standardize(); 686 break; 687 case order::lex: 688 init_standardize(); 689 lex_standardize(); 690 break; 691 case order::recursive: 692 init_standardize(); 693 recursive_standardize(); 694 break; 695 case order::none: { 696 } 697 default: { 698 } 699 } 700 if (finished()) { 701 _standardized = rdr; 702 } 703 } 704 705 //////////////////////////////////////////////////////////////////////// 706 // CongruenceInterface - pure virtual member functions - private 707 //////////////////////////////////////////////////////////////////////// 708 class_index_to_word_impl(coset_type i)709 word_type ToddCoxeter::class_index_to_word_impl(coset_type i) { 710 run(); 711 if (!is_standardized()) { 712 standardize(order::shortlex); 713 } 714 LIBSEMIGROUPS_ASSERT(finished()); 715 word_type w; 716 TreeNode tn = (*_tree)[i + 1]; 717 while (tn.parent != UNDEFINED) { 718 w.push_back(tn.gen); 719 tn = (*_tree)[tn.parent]; 720 } 721 if (kind() != congruence_type::left) { 722 std::reverse(w.begin(), w.end()); 723 } 724 return w; 725 } 726 nr_classes_impl()727 size_t ToddCoxeter::nr_classes_impl() { 728 run(); 729 LIBSEMIGROUPS_ASSERT(finished()); 730 return nr_cosets_active() - 1; 731 } 732 quotient_impl()733 std::shared_ptr<FroidurePinBase> ToddCoxeter::quotient_impl() { 734 using detail::TCE; 735 run(); 736 standardize(order::shortlex); 737 shrink_to_fit(); 738 // Ensure class indices and letters are equal! 739 auto table = std::make_shared<table_type>(_table); 740 size_t n = nr_generators(); 741 for (letter_type a = 0; a < n;) { 742 if (table->get(0, a) != a + 1) { 743 table->erase_column(a); 744 n--; 745 } else { 746 ++a; 747 } 748 } 749 auto ptr = std::make_shared< 750 FroidurePin<TCE, FroidurePinTraits<TCE, table_type>>>(table); 751 for (size_t i = 0; i < nr_generators(); ++i) { 752 // We use table.get(0, i) instead of just i, because there might be 753 // more generators than cosets. 754 ptr->add_generator(TCE(_table.get(0, i))); 755 } 756 return ptr; 757 } 758 run_impl()759 void ToddCoxeter::run_impl() { 760 if (is_quotient_obviously_infinite()) { 761 LIBSEMIGROUPS_EXCEPTION( 762 "there are infinitely many classes in the congruence and " 763 "Todd-Coxeter will never terminate"); 764 } 765 if (_settings->lower_bound != UNDEFINED) { 766 size_t const bound = _settings->lower_bound; 767 _settings->lower_bound = UNDEFINED; 768 run_until([this, &bound]() -> bool { 769 return (nr_cosets_active() == bound) && complete(); 770 }); 771 } else if (_settings->strategy == policy::strategy::felsch) { 772 felsch(); 773 } else if (_settings->strategy == policy::strategy::hlt) { 774 hlt(); 775 } else if (_settings->strategy == policy::strategy::random) { 776 sims(); 777 } 778 } 779 finished_impl() const780 bool ToddCoxeter::finished_impl() const { 781 return _state == state::finished; 782 } 783 word_to_class_index_impl(word_type const & w)784 coset_type ToddCoxeter::word_to_class_index_impl(word_type const& w) { 785 run(); 786 LIBSEMIGROUPS_ASSERT(finished()); 787 if (!is_standardized()) { 788 standardize(order::shortlex); 789 } 790 coset_type c = const_word_to_class_index(w); 791 // c is in the range 1, ..., nr_cosets_active() because 0 represents the 792 // identity coset, and does not correspond to an element. 793 return c; 794 } 795 796 //////////////////////////////////////////////////////////////////////// 797 // CongruenceInterface - non-pure virtual member functions - private 798 //////////////////////////////////////////////////////////////////////// 799 800 coset_type const_word_to_class_index(word_type const & w) const801 ToddCoxeter::const_word_to_class_index(word_type const& w) const { 802 validate_word(w); 803 coset_type c = _id_coset; 804 805 if (kind() == congruence_type::left) { 806 c = tau(c, w.crbegin(), w.crend()); 807 } else { 808 c = tau(c, w.cbegin(), w.cend()); 809 } 810 return (c == UNDEFINED ? c : c - 1); 811 } 812 is_quotient_obviously_finite_impl()813 bool ToddCoxeter::is_quotient_obviously_finite_impl() { 814 if (finished()) { 815 return true; 816 } 817 init(); 818 return _prefilled; 819 // _prefilled means that either we were created from a FroidurePinBase* 820 // with _settings->froidure_pin = use_cayley_graph and we successfully 821 // prefilled the table, or we manually prefilled the table. In this case 822 // the semigroup defined by _relations must be finite. 823 } 824 is_quotient_obviously_infinite_impl()825 bool ToddCoxeter::is_quotient_obviously_infinite_impl() { 826 if (finished()) { 827 return false; 828 } 829 init(); 830 if (_prefilled) { 831 return false; 832 } else if (nr_generators() > _relations.size() + _extra.size()) { 833 return true; 834 } 835 detail::IsObviouslyInfinite ioi(nr_generators()); 836 ioi.add_rules(_relations.cbegin(), _relations.cend()); 837 ioi.add_rules(_extra.cbegin(), _extra.cend()); 838 return ioi.result(); 839 } 840 set_nr_generators_impl(size_t n)841 void ToddCoxeter::set_nr_generators_impl(size_t n) { 842 // TODO(later) add columns to make it up to n? 843 _preim_init = table_type(n, 1, UNDEFINED); 844 _preim_next = table_type(n, 1, UNDEFINED); 845 _table = table_type(n, 1, UNDEFINED); 846 } 847 848 //////////////////////////////////////////////////////////////////////// 849 // ToddCoxeter - member functions (validation) - private 850 //////////////////////////////////////////////////////////////////////// 851 validate_table(table_type const & table,size_t const first,size_t const last) const852 void ToddCoxeter::validate_table(table_type const& table, 853 size_t const first, 854 size_t const last) const { 855 REPORT_DEBUG_DEFAULT("validating coset table...\n"); 856 if (nr_generators() == UNDEFINED) { 857 LIBSEMIGROUPS_EXCEPTION("no generators have been defined"); 858 } else if (table.nr_cols() != nr_generators()) { 859 LIBSEMIGROUPS_EXCEPTION("invalid table, expected %d columns, found %d", 860 nr_generators(), 861 table.nr_cols()); 862 } 863 if (last - first <= 0) { 864 LIBSEMIGROUPS_EXCEPTION( 865 "invalid table, expected at least 1 rows, found %d", 866 table.nr_rows()); 867 } 868 for (size_t i = first; i < last; ++i) { 869 for (size_t j = 0; j < table.nr_cols(); ++j) { 870 coset_type c = table.get(i, j); 871 if (c < first || c >= last) { 872 LIBSEMIGROUPS_EXCEPTION( 873 "invalid table, expected entries in the range [%d, %d), found " 874 "%d in row %d, column %d", 875 i, 876 j, 877 first, 878 last, 879 c); 880 } 881 } 882 } 883 REPORT_DEBUG_DEFAULT("...coset table ok\n"); 884 } 885 886 //////////////////////////////////////////////////////////////////////// 887 // ToddCoxeter - member functions (initialisation) - private 888 //////////////////////////////////////////////////////////////////////// 889 890 //! Copy all _relations and _extra from copy into _relations of this copy_relations_for_quotient(ToddCoxeter & copy)891 void ToddCoxeter::copy_relations_for_quotient(ToddCoxeter& copy) { 892 REPORT_DEBUG_DEFAULT("copying relations for quotient...\n"); 893 LIBSEMIGROUPS_ASSERT(empty()); 894 copy.init(); 895 set_nr_generators(copy.nr_generators()); 896 _state = state::initialized; 897 _relations = copy._relations; 898 _relations.insert( 899 _relations.end(), copy._extra.cbegin(), copy._extra.cend()); 900 if (kind() == congruence_type::left 901 && copy.kind() != congruence_type::left) { 902 for (auto& w : _relations) { 903 std::reverse(w.begin(), w.end()); 904 } 905 } 906 _nr_pairs_added_earlier = 0; 907 } 908 init()909 void ToddCoxeter::init() { 910 if (_state == state::constructed) { 911 REPORT_DEBUG_DEFAULT("initializing...\n"); 912 // Add the relations/Cayley graph from parent() if any. 913 if (has_parent_froidure_pin() 914 && parent_froidure_pin()->is_finite() == tril::TRUE) { 915 if (_settings->froidure_pin == policy::froidure_pin::use_cayley_graph 916 || _settings->froidure_pin == policy::froidure_pin::none) { 917 REPORT_DEBUG_DEFAULT("using Cayley graph...\n"); 918 LIBSEMIGROUPS_ASSERT(_relations.empty()); 919 prefill(*parent_froidure_pin()); 920 #ifdef LIBSEMIGROUPS_DEBUG 921 // This is a check of program logic, since we use parent() to fill 922 // the table, so we only validate in debug mode. 923 validate_table(_table, 1, parent_froidure_pin()->size() + 1); 924 #endif 925 } else { 926 REPORT_DEBUG_DEFAULT("using presentation...\n"); 927 LIBSEMIGROUPS_ASSERT(_settings->froidure_pin 928 == policy::froidure_pin::use_relations); 929 auto fp = parent_froidure_pin(); 930 fp->run(); 931 for (auto it = fp->cbegin_rules(); it != fp->cend_rules(); ++it) { 932 reverse_if_necessary_and_push_back(it->first, _relations); 933 reverse_if_necessary_and_push_back(it->second, _relations); 934 } 935 #ifdef LIBSEMIGROUPS_DEBUG 936 // This is a check of program logic, since we use parent() to 937 // obtain the relations, so we only validate in debug mode. 938 for (auto const& rel : _relations) { 939 validate_word(rel); 940 } 941 // We don't add anything to _extra here so no need to check. 942 #endif 943 } 944 } 945 _state = state::initialized; 946 } 947 948 // Get new generating pairs (if any) from CongruenceInterface (if any) 949 auto it = cbegin_generating_pairs() + _nr_pairs_added_earlier; 950 if (kind() != congruence_type::twosided) { 951 for (; it < cend_generating_pairs(); ++it) { 952 reverse_if_necessary_and_push_back(it->first, _extra); 953 reverse_if_necessary_and_push_back(it->second, _extra); 954 } 955 } else { 956 for (; it < cend_generating_pairs(); ++it) { 957 reverse_if_necessary_and_push_back(it->first, _relations); 958 reverse_if_necessary_and_push_back(it->second, _relations); 959 } 960 } 961 _nr_pairs_added_earlier 962 = cend_generating_pairs() - cbegin_generating_pairs(); 963 } 964 init_felsch_tree()965 void ToddCoxeter::init_felsch_tree() { 966 LIBSEMIGROUPS_ASSERT(_state >= state::initialized); 967 if (_felsch_tree == nullptr) { 968 REPORT_DEFAULT("initializing the Felsch tree...\n"); 969 detail::Timer tmr; 970 _felsch_tree = detail::make_unique<FelschTree>(this); 971 _felsch_tree->add_relations(_relations); 972 REPORT_TIME(tmr); 973 } 974 } 975 init_preimages_from_table()976 void ToddCoxeter::init_preimages_from_table() { 977 REPORT_DEBUG("initializing preimages...\n"); 978 LIBSEMIGROUPS_ASSERT(_table.nr_cols() == nr_generators()); 979 LIBSEMIGROUPS_ASSERT(_table.nr_rows() >= nr_cosets_active()); 980 LIBSEMIGROUPS_ASSERT(_prefilled); 981 LIBSEMIGROUPS_ASSERT(_state == state::constructed); 982 LIBSEMIGROUPS_ASSERT(_relations.empty()); 983 984 for (coset_type c = 0; c < nr_cosets_active(); c++) { 985 for (size_t i = 0; i < nr_generators(); i++) { 986 coset_type b = _table.get(c, i); 987 _preim_next.set(c, i, _preim_init.get(b, i)); 988 _preim_init.set(b, i, c); 989 } 990 } 991 } 992 prefill(FroidurePinBase & S)993 void ToddCoxeter::prefill(FroidurePinBase& S) { 994 REPORT_DEBUG_DEFAULT("prefilling the coset table from FroidurePin...\n"); 995 LIBSEMIGROUPS_ASSERT(_state == state::constructed); 996 LIBSEMIGROUPS_ASSERT( 997 _settings->froidure_pin == policy::froidure_pin::use_cayley_graph 998 || _settings->froidure_pin == policy::froidure_pin::none); 999 LIBSEMIGROUPS_ASSERT(S.nr_generators() == nr_generators()); 1000 if (kind() == congruence_type::left) { 1001 prefill_and_validate(S.left_cayley_graph(), false); 1002 } else { 1003 prefill_and_validate(S.right_cayley_graph(), false); 1004 } 1005 for (size_t i = 0; i < nr_generators(); i++) { 1006 _table.set(0, i, S.letter_to_pos(i) + 1); 1007 } 1008 init_preimages_from_table(); 1009 } 1010 prefill_and_validate(table_type const & table,bool validate)1011 void ToddCoxeter::prefill_and_validate(table_type const& table, 1012 bool validate) { 1013 if (_settings->strategy == policy::strategy::felsch) { 1014 LIBSEMIGROUPS_EXCEPTION( 1015 "it is not possible to prefill when using the Felsch strategy"); 1016 } 1017 if (!empty()) { 1018 LIBSEMIGROUPS_EXCEPTION("cannot prefill a non-empty instance") 1019 } 1020 if (validate) { 1021 validate_table(table, 0, table.nr_rows()); 1022 } 1023 1024 REPORT_DEBUG("prefilling the coset table...\n"); 1025 _prefilled = true; 1026 size_t m = table.nr_rows() + 1; 1027 _table.add_rows(m - _table.nr_rows()); 1028 for (size_t i = 0; i < _table.nr_cols(); i++) { 1029 _table.set(0, i, i + 1); 1030 } 1031 for (size_t row = 0; row < _table.nr_rows() - 1; ++row) { 1032 for (size_t col = 0; col < _table.nr_cols(); ++col) { 1033 _table.set(row + 1, col, table.get(row, col) + 1); 1034 } 1035 } 1036 add_active_cosets(m - nr_cosets_active()); 1037 _preim_init.add_rows(m - _preim_init.nr_rows()); 1038 _preim_next.add_rows(m - _preim_next.nr_rows()); 1039 } 1040 1041 void reverse_if_necessary_and_push_back(word_type w,std::vector<word_type> & v)1042 ToddCoxeter::reverse_if_necessary_and_push_back(word_type w, 1043 std::vector<word_type>& v) { 1044 if (kind() == congruence_type::left) { 1045 std::reverse(w.begin(), w.end()); 1046 } 1047 v.push_back(std::move(w)); 1048 } 1049 1050 //////////////////////////////////////////////////////////////////////// 1051 // ToddCoxeter - member functions (cosets) - private 1052 //////////////////////////////////////////////////////////////////////// 1053 new_coset()1054 coset_type ToddCoxeter::new_coset() { 1055 if (!has_free_cosets()) { 1056 reserve(2 * coset_capacity()); 1057 return new_active_coset(); 1058 } else { 1059 coset_type const c = new_active_coset(); 1060 // Clear the new coset's row in each table 1061 for (letter_type i = 0; i < nr_generators(); i++) { 1062 _table.set(c, i, UNDEFINED); 1063 _preim_init.set(c, i, UNDEFINED); 1064 } 1065 return c; 1066 } 1067 } 1068 remove_preimage(coset_type const cx,letter_type const x,coset_type const d)1069 void ToddCoxeter::remove_preimage(coset_type const cx, 1070 letter_type const x, 1071 coset_type const d) { 1072 LIBSEMIGROUPS_ASSERT(is_active_coset(cx)); 1073 LIBSEMIGROUPS_ASSERT(is_valid_coset(d)); 1074 coset_type e = _preim_init.get(cx, x); 1075 if (e == d) { 1076 _preim_init.set(cx, x, _preim_next.get(d, x)); 1077 } else { 1078 while (_preim_next.get(e, x) != d) { 1079 e = _preim_next.get(e, x); 1080 } 1081 LIBSEMIGROUPS_ASSERT(_preim_next.get(e, x) == d); 1082 _preim_next.set(e, x, _preim_next.get(d, x)); 1083 } 1084 } 1085 1086 // Perform a DFS in _felsch_tree make_deductions_dfs(coset_type const c)1087 void ToddCoxeter::make_deductions_dfs(coset_type const c) { 1088 for (auto it = _felsch_tree->cbegin(); it < _felsch_tree->cend(); ++it) { 1089 push_definition_felsch<StackDeductions, DoNotProcessCoincidences>( 1090 c, _relations[*it], _relations[*it + 1]); 1091 } 1092 1093 size_t const n = nr_generators(); 1094 for (size_t x = 0; x < n; ++x) { 1095 if (_felsch_tree->push_front(x)) { 1096 coset_type e = _preim_init.get(c, x); 1097 while (e != UNDEFINED) { 1098 make_deductions_dfs(e); 1099 e = _preim_next.get(e, x); 1100 } 1101 _felsch_tree->pop_front(); 1102 } 1103 } 1104 } 1105 process_deductions()1106 void ToddCoxeter::process_deductions() { 1107 LIBSEMIGROUPS_ASSERT(!_prefilled); 1108 #ifdef LIBSEMIGROUPS_VERBOSE 1109 if (!_deduct.empty()) { 1110 REPORT_VERBOSE_DEFAULT("processing %llu deductions . . .\n", 1111 static_cast<uint64_t>(_deduct.size())); 1112 } 1113 #endif 1114 while (!_deduct.empty()) { 1115 auto d = _deduct.top(); 1116 _deduct.pop(); 1117 if (is_active_coset(d.first)) { 1118 _felsch_tree->push_back(d.second); 1119 make_deductions_dfs(d.first); 1120 process_coincidences<StackDeductions>(); 1121 } 1122 } 1123 process_coincidences<StackDeductions>(); 1124 if (!_deduct.empty()) { 1125 process_deductions(); 1126 } 1127 } 1128 1129 //////////////////////////////////////////////////////////////////////// 1130 // ToddCoxeter - member functions (main strategies) - private 1131 //////////////////////////////////////////////////////////////////////// 1132 felsch()1133 void ToddCoxeter::felsch() { 1134 REPORT_DEFAULT("performing Felsch %s standardization...\n", 1135 _settings->standardize ? "with" : "without"); 1136 detail::Timer tmr; 1137 init(); 1138 coset_type t = 0; 1139 size_t const n = nr_generators(); 1140 // Can only initialise _felsch_tree here because we require _relations 1141 // to be complete. 1142 init_felsch_tree(); 1143 if (_state == state::initialized) { 1144 LIBSEMIGROUPS_ASSERT(_settings->strategy == policy::strategy::felsch); 1145 for (auto it = _extra.cbegin(); it < _extra.cend(); it += 2) { 1146 push_definition_hlt<StackDeductions, ProcessCoincidences>( 1147 _id_coset, *it, *(it + 1)); 1148 } 1149 if (_settings->standardize) { 1150 for (letter_type x = 0; x < n; ++x) { 1151 standardize_immediate(_id_coset, t, x); 1152 } 1153 } 1154 if (!_prefilled) { 1155 if (_relations.empty()) { 1156 _felsch_tree->add_relations(_extra); 1157 _extra.swap(_relations); 1158 } 1159 process_deductions(); 1160 // process_deductions doesn't work if the table is prefilled 1161 } 1162 } else if (_state == state::hlt) { 1163 _current = _id_coset; 1164 } 1165 _state = state::felsch; 1166 while (_current != first_free_coset() && !stopped()) { 1167 for (letter_type a = 0; a < n; ++a) { 1168 if (_table.get(_current, a) == UNDEFINED) { 1169 define<StackDeductions>(_current, a, new_coset()); 1170 process_deductions(); 1171 #ifdef LIBSEMIGROUPS_DEBUG 1172 if (_settings->enable_debug_verify_no_missing_deductions) { 1173 debug_verify_no_missing_deductions(); 1174 } 1175 #endif 1176 } 1177 if (_settings->standardize) { 1178 standardize_immediate(_current, t, a); 1179 } 1180 } 1181 if (report()) { 1182 TODD_COXETER_REPORT_COSETS() 1183 } 1184 _current = next_active_coset(_current); 1185 } 1186 LIBSEMIGROUPS_ASSERT(_coinc.empty()); 1187 LIBSEMIGROUPS_ASSERT(_deduct.empty()); 1188 if (!stopped()) { 1189 LIBSEMIGROUPS_ASSERT(_current == first_free_coset()); 1190 _state = state::finished; 1191 } 1192 TODD_COXETER_REPORT_COSETS() 1193 REPORT_TIME(tmr); 1194 report_why_we_stopped(); 1195 } 1196 1197 // Walker's Strategy 1 = HLT = ACE style-R hlt()1198 void ToddCoxeter::hlt() { 1199 REPORT_DEFAULT("performing HLT %s standardization, %s lookahead, and%s" 1200 "deduction processing...\n", 1201 _settings->standardize ? "with" : "without", 1202 _settings->lookahead == policy::lookahead::partial 1203 ? "partial" 1204 : "full", 1205 _settings->save ? " " : " no "); 1206 detail::Timer tmr; 1207 init(); 1208 1209 coset_type t = 0; 1210 if (_state == state::initialized) { 1211 for (auto it = _extra.cbegin(); it < _extra.cend(); it += 2) { 1212 push_definition_hlt<DoNotStackDeductions, ProcessCoincidences>( 1213 _id_coset, *it, *(it + 1)); 1214 } 1215 if (_settings->standardize) { 1216 size_t const n = nr_generators(); 1217 for (letter_type x = 0; x < n; ++x) { 1218 standardize_immediate(_id_coset, t, x); 1219 } 1220 } 1221 if (!_prefilled) { 1222 if (_relations.empty()) { 1223 _extra.swap(_relations); 1224 } 1225 } 1226 } else if (_state == state::felsch) { 1227 _current = _id_coset; 1228 } 1229 _state = state::hlt; 1230 1231 if (_settings->save) { 1232 init_felsch_tree(); 1233 } 1234 // size_t const n = nr_generators(); 1235 while (_current != first_free_coset() && !stopped()) { 1236 if (!_settings->save) { 1237 for (auto it = _relations.cbegin(); it < _relations.cend(); it += 2) { 1238 push_definition_hlt<DoNotStackDeductions, ProcessCoincidences>( 1239 _current, *it, *(it + 1)); 1240 } 1241 // Row filling 1242 // for (letter_type x = 0; x < n; ++x) { 1243 // if (tau(_current, x) == UNDEFINED) { 1244 // define<DoNotStackDeductions>(_current, x, new_coset()); 1245 // } 1246 // } 1247 } else { 1248 for (auto it = _relations.cbegin(); it < _relations.cend(); it += 2) { 1249 push_definition_hlt<StackDeductions, DoNotProcessCoincidences>( 1250 _current, *it, *(it + 1)); 1251 process_deductions(); 1252 } 1253 // Row filling 1254 // for (letter_type x = 0; x < n; ++x) { 1255 // if (tau(_current, x) == UNDEFINED) { 1256 // define<StackDeductions>(_current, x, new_coset()); 1257 // } 1258 // } 1259 } 1260 if (nr_cosets_active() > _settings->next_lookahead) { 1261 perform_lookahead(); 1262 } 1263 if (_settings->standardize) { 1264 size_t const n = nr_generators(); 1265 for (letter_type x = 0; x < n; ++x) { 1266 standardize_immediate(_current, t, x); 1267 } 1268 } 1269 if (report()) { 1270 TODD_COXETER_REPORT_COSETS() 1271 } 1272 _current = next_active_coset(_current); 1273 } 1274 LIBSEMIGROUPS_ASSERT(_coinc.empty()); 1275 LIBSEMIGROUPS_ASSERT(_deduct.empty()); 1276 if (!stopped()) { 1277 LIBSEMIGROUPS_ASSERT(_current == first_free_coset()); 1278 _state = state::finished; 1279 } 1280 TODD_COXETER_REPORT_COSETS(); 1281 REPORT_TIME(tmr); 1282 report_why_we_stopped(); 1283 } 1284 1285 // This is not exactly Sim's TEN_CE, since all of the variants of 1286 // Todd-Coxeter represented in TEN_CE (that apply to semigroups/monoids) 1287 // are already accounted for in the above. sims()1288 void ToddCoxeter::sims() { 1289 REPORT_DEFAULT("performing random Sims' TEN_CE strategy...\n"); 1290 using int_dist_type 1291 = std::uniform_int_distribution<std::mt19937::result_type>; 1292 static int_dist_type dist(0, 9); 1293 static std::mt19937 mt; 1294 1295 static constexpr std::array<bool, 8> full 1296 = {true, true, true, true, false, false, false, false}; 1297 static constexpr std::array<bool, 10> stand 1298 = {true, true, false, false, true, true, false, false, true, false}; 1299 static constexpr std::array<bool, 8> save_it 1300 = {true, false, true, false, true, false, true, false}; 1301 1302 static const std::string line = std::string(79, '#') + '\n'; 1303 #ifdef LIBSEMIGROUPS_DEBUG 1304 // Don't check for missing deductions, because when changing from HLT to 1305 // Felsch and back repeatedly, there can be missing deductions after 1306 // deduction processing in Felsch (caused by not pushing all relations 1307 // through all cosets in HLT). 1308 _settings->enable_debug_verify_no_missing_deductions = false; 1309 #endif 1310 while (!finished()) { 1311 size_t m = dist(mt); 1312 if (m < 8) { 1313 strategy(policy::strategy::hlt); 1314 lookahead( 1315 (full[m] ? policy::lookahead::full : policy::lookahead::partial)); 1316 try { 1317 save(save_it[m]); 1318 } catch (...) { 1319 // It isn't always possible to use the save option (when this is 1320 // created from a Cayley table, for instance), and 1321 // ToddCoxeter::save throws if this is the case. 1322 } 1323 } else { 1324 try { 1325 strategy(policy::strategy::felsch); 1326 } catch (...) { 1327 // It isn't always possible to use the Felsch strategy (when this 1328 // is created from a Cayley table, for instance), and 1329 // ToddCoxeter::save throws if this is the case. 1330 } 1331 } 1332 standardize(stand[m]); 1333 1334 REPORT(line).prefix().flush(); 1335 run_for(_settings->random_interval); 1336 } 1337 LIBSEMIGROUPS_ASSERT(_coinc.empty()); 1338 LIBSEMIGROUPS_ASSERT(_deduct.empty()); 1339 // The next 2 lines are necessary because if we are changing from HLT to 1340 // Felsch repeatedly, we can be in the situation where the table is 1341 // complete but not compatible. Test [ToddCoxeter][048] is a good example 1342 // of this. 1343 lookahead(policy::lookahead::full); 1344 perform_lookahead(); 1345 } 1346 1347 // TODO(later) we could use deduction processing here instead of this, 1348 // where appropriate? perform_lookahead()1349 void ToddCoxeter::perform_lookahead() { 1350 state const old_state = _state; 1351 _state = state::lookahead; 1352 if (_settings->lookahead == policy::lookahead::partial) { 1353 REPORT_DEFAULT("performing partial lookahead...\n"); 1354 // Start lookahead from the coset after _current 1355 _current_la = next_active_coset(_current); 1356 } else { 1357 LIBSEMIGROUPS_ASSERT(_settings->lookahead == policy::lookahead::full); 1358 REPORT_DEFAULT("performing full lookahead...\n"); 1359 // Start from the first coset 1360 _current_la = _id_coset; 1361 } 1362 TODD_COXETER_REPORT_COSETS() 1363 1364 size_t nr_killed = nr_cosets_killed(); 1365 while (_current_la != first_free_coset() 1366 // when running the random sims method the state is finished at 1367 // this point, and so stopped() == true, but we anyway want to 1368 // perform a full lookahead, which is why "_state == 1369 // state::finished" is in the next line. 1370 && (old_state == state::finished || !stopped())) { 1371 for (auto it = _relations.cbegin(); it < _relations.cend(); it += 2) { 1372 push_definition_felsch<DoNotStackDeductions, ProcessCoincidences>( 1373 _current_la, *it, *(it + 1)); 1374 } 1375 _current_la = next_active_coset(_current_la); 1376 if (report()) { 1377 TODD_COXETER_REPORT_COSETS() 1378 } 1379 } 1380 nr_killed = nr_cosets_killed() - nr_killed; 1381 if (nr_cosets_active() > _settings->next_lookahead 1382 || nr_killed < (nr_cosets_active() / 4)) { 1383 _settings->next_lookahead *= 2; 1384 } 1385 REPORT_DEFAULT("%2d cosets killed\n", nr_killed); 1386 _state = old_state; 1387 } 1388 1389 //////////////////////////////////////////////////////////////////////// 1390 // ToddCoxeter - member functions (standardize) - private 1391 //////////////////////////////////////////////////////////////////////// 1392 init_standardize()1393 void ToddCoxeter::init_standardize() { 1394 if (_tree == nullptr) { 1395 _tree = detail::make_unique<std::vector<TreeNode>>(nr_cosets_active(), 1396 TreeNode()); 1397 } else { 1398 _tree->resize(nr_cosets_active()); 1399 } 1400 LIBSEMIGROUPS_ASSERT(_coinc.empty()); 1401 } 1402 1403 // Returns true if t is incremented (i.e. it's the first time we are 1404 // seeing and t as a coset in a standardization) and false otherwise. standardize_immediate(coset_type const s,coset_type & t,letter_type const x)1405 bool ToddCoxeter::standardize_immediate(coset_type const s, 1406 coset_type& t, 1407 letter_type const x) { 1408 LIBSEMIGROUPS_ASSERT(!finished()); 1409 coset_type const r = _table.get(s, x); 1410 if (r != UNDEFINED) { 1411 if (r > t) { 1412 t++; 1413 if (r > t) { 1414 swap(t, r); 1415 } 1416 return true; 1417 } 1418 } 1419 return false; 1420 } 1421 standardize_deferred(std::vector<coset_type> & p,std::vector<coset_type> & q,coset_type const s,coset_type & t,letter_type const x)1422 bool ToddCoxeter::standardize_deferred(std::vector<coset_type>& p, 1423 std::vector<coset_type>& q, 1424 coset_type const s, 1425 coset_type& t, 1426 letter_type const x) { 1427 // p : new -> old and q : old -> new 1428 coset_type r = _table.get(p[s], x); 1429 if (r != UNDEFINED) { 1430 r = q[r]; // new 1431 if (r > t) { 1432 t++; 1433 if (r > t) { 1434 TODD_COXETER_REPORT_SWITCH(r, t); 1435 std::swap(p[t], p[r]); 1436 std::swap(q[p[t]], q[p[r]]); 1437 } 1438 (*_tree)[t].parent = (s == t ? r : s); 1439 (*_tree)[t].gen = x; 1440 return true; 1441 } 1442 } 1443 return false; 1444 } 1445 lex_standardize()1446 void ToddCoxeter::lex_standardize() { 1447 REPORT_DEFAULT("standardizing (lex)... "); 1448 detail::Timer tmr; 1449 1450 coset_type s = 0; 1451 coset_type t = 0; 1452 letter_type x = 0; 1453 size_t const n = nr_generators(); 1454 std::vector<coset_type> p(coset_capacity(), 0); 1455 std::iota(p.begin(), p.end(), 0); 1456 std::vector<coset_type> q(coset_capacity(), 0); 1457 std::iota(q.begin(), q.end(), 0); 1458 1459 // Perform a DFS through the _table 1460 while (s <= t) { 1461 if (standardize_deferred(p, q, s, t, x)) { 1462 s = t; 1463 x = 0; 1464 continue; 1465 } 1466 x++; 1467 if (x == n) { // backtrack 1468 x = (*_tree)[s].gen; 1469 s = (*_tree)[s].parent; 1470 } 1471 } 1472 apply_permutation(p, q); 1473 1474 REPORT("%s\n", tmr.string().c_str()).prefix().flush_right().flush(); 1475 #ifdef LIBSEMIGROUPS_DEBUG 1476 debug_validate_forwd_bckwd(); 1477 debug_validate_table(); 1478 // debug_validate_preimages(); 1479 #endif 1480 } 1481 shortlex_standardize()1482 void ToddCoxeter::shortlex_standardize() { 1483 REPORT_DEFAULT("standardizing (shortlex)... "); 1484 detail::Timer tmr; 1485 coset_type t = 0; 1486 size_t const n = nr_generators(); 1487 std::vector<coset_type> p(coset_capacity(), 0); 1488 std::iota(p.begin(), p.end(), 0); 1489 std::vector<coset_type> q(coset_capacity(), 0); 1490 std::iota(q.begin(), q.end(), 0); 1491 1492 for (coset_type s = 0; s <= t; ++s) { 1493 for (letter_type x = 0; x < n; ++x) { 1494 standardize_deferred(p, q, s, t, x); 1495 } 1496 } 1497 apply_permutation(p, q); 1498 REPORT("%s\n", tmr.string().c_str()).prefix().flush_right().flush(); 1499 #ifdef LIBSEMIGROUPS_DEBUG 1500 debug_validate_forwd_bckwd(); 1501 debug_validate_table(); 1502 // debug_validate_preimages(); 1503 #endif 1504 } 1505 1506 // This is how the recursive words up to a given length M, and on an 1507 // arbitrary finite alphabet are generated. On a single letter alphabet, 1508 // this order is just increasing powers of the only generator: 1509 // 1510 // a < aa < aaa < aaaa < ... < aa...a (M times) 1511 // 1512 // With an n-letter alphabet A = {a_1, a_2, ..., a_n}, suppose we have 1513 // already obtained all of the words W_{n - 1} containing {a_1, ..., a_{n - 1514 // 1}}. Every word in W_{n - 1} is less than any word containing a_n, and 1515 // the least word greater than every word in W_{n - 1} is a_n. Words greater 1516 // than a_n are obtain in the follow way, where: 1517 // 1518 // x: is the maximum word in W_{n - 1}, this is constant, in the description 1519 // that follows. 1520 // u: the first word obtained in point (1), the first time it is applied 1521 // after (2) has been applied, starting with u = a_{n - 1}. 1522 // v: a word with one fewer letters than u, starting with the empty word. 1523 // w: a word such that w < u, also starting with the empty word. 1524 // 1525 // 1. If v < x, then v is replaced by the next word in the order. If |uv| <= 1526 // M, then the next word is uv. Otherwise, goto 1. 1527 // 1528 // 2. If v = x, then and there exists a word w' in the set of words obtained 1529 // so far such that w' > w and |w'| <= M - 1, then replace w with w', 1530 // replace u by wa_n, replace v by the empty word, and the next word is 1531 // wa_n. 1532 // 1533 // If no such word w' exists, then we have enumerated all the required 1534 // words, and we can stop. 1535 // 1536 // For example, if A = {a, b} and M = 4, then the initial elements in the 1537 // order are: 1538 // 1539 // e < a < aa < aaa < aaaa (e is the empty word) 1540 // 1541 // Set b > aaaa. At this point, x = aaaa, u = b, v = e, w = e, and so 1542 // (1) applies, v <- a, and since |uv| = ba <= 4 = M, the next word is 1543 // ba. Repeatedly applying (1), until it fails to hold, we obtain the 1544 // following: 1545 // 1546 // aaaa < b < ba < baa < baaa 1547 // 1548 // After defining baa < baaa, x = aaaa, u = b, v = aaaa, and w = e. Hence v 1549 // = x, and so (2) applies. The next w' in the set of words so far 1550 // enumerated is a, and |a| = 1 <= 3 = M - 1, and so w <- a, u <- ab, v <- 1551 // e, and the next word is ab. We repeatedly apply (1), until it fails, to 1552 // obtain 1553 // 1554 // baaa < ab < aba < abaa 1555 // 1556 // At which point u = b, v = aaaa = x, and w = a. Hence (2) applies, w <- 1557 // aa, v <- e, u <- aab, and the next word is: aab. And so on ... 1558 1559 // TODO(later): improve this, it is currently very slow. recursive_standardize()1560 void ToddCoxeter::recursive_standardize() { 1561 REPORT_DEFAULT("standardizing (recursive)... "); 1562 detail::Timer tmr; 1563 std::vector<word_type> out; 1564 size_t const n = nr_generators(); 1565 letter_type a = 0; 1566 coset_type s = 0; 1567 coset_type t = 0; 1568 1569 std::vector<coset_type> p(coset_capacity(), 0); 1570 std::iota(p.begin(), p.end(), 0); 1571 std::vector<coset_type> q(coset_capacity(), 0); 1572 std::iota(q.begin(), q.end(), 0); 1573 1574 while (s <= t) { 1575 if (standardize_deferred(p, q, s, t, 0)) { 1576 out.push_back(word_type(t, a)); 1577 } 1578 s++; 1579 } 1580 a++; 1581 bool new_generator = true; 1582 int x, u, w; 1583 while (a < n && t < nr_cosets_active() - 1) { 1584 if (new_generator) { 1585 w = -1; // -1 is the empty word 1586 if (standardize_deferred(p, q, 0, t, a)) { 1587 out.push_back({a}); 1588 } 1589 x = out.size() - 1; 1590 u = out.size() - 1; 1591 new_generator = false; 1592 } 1593 1594 coset_type const uu = tau(0, out[u].begin(), out[u].end()); 1595 if (uu != UNDEFINED) { 1596 for (int v = 0; v < x; v++) { 1597 coset_type const uuv = tau(uu, out[v].begin(), out[v].end() - 1); 1598 if (uuv != UNDEFINED) { 1599 s = q[uuv]; 1600 if (standardize_deferred(p, q, s, t, out[v].back())) { 1601 word_type nxt = out[u]; 1602 nxt.insert(nxt.end(), out[v].begin(), out[v].end()); 1603 out.push_back(std::move(nxt)); 1604 } 1605 } 1606 } 1607 } 1608 w++; 1609 if (static_cast<size_t>(w) < out.size()) { 1610 coset_type const ww = tau(0, out[w].begin(), out[w].end()); 1611 if (ww != UNDEFINED) { 1612 s = q[ww]; 1613 if (standardize_deferred(p, q, s, t, a)) { 1614 u = out.size(); 1615 word_type nxt = out[w]; 1616 nxt.push_back(a); 1617 out.push_back(std::move(nxt)); 1618 } 1619 } 1620 } else { 1621 a++; 1622 new_generator = true; 1623 } 1624 } 1625 apply_permutation(p, q); 1626 REPORT("%s\n", tmr.string().c_str()).prefix().flush_right().flush(); 1627 #ifdef LIBSEMIGROUPS_DEBUG 1628 debug_validate_forwd_bckwd(); 1629 debug_validate_table(); 1630 debug_validate_preimages(); 1631 #endif 1632 } 1633 1634 // The permutation q must map the active cosets to the [0, .. 1635 // , nr_cosets_active()) apply_permutation(std::vector<coset_type> & p,std::vector<coset_type> & q)1636 void ToddCoxeter::apply_permutation(std::vector<coset_type>& p, 1637 std::vector<coset_type>& q) { 1638 // p : new -> old, q = p ^ -1 1639 #ifdef LIBSEMIGROUPS_DEBUG 1640 for (size_t c = 0; c < q.size(); ++c) { 1641 LIBSEMIGROUPS_ASSERT( 1642 (is_active_coset(c) && q[c] < nr_cosets_active()) 1643 || (!is_active_coset(c) && q[c] >= nr_cosets_active())); 1644 LIBSEMIGROUPS_ASSERT(p[q[c]] == c); 1645 LIBSEMIGROUPS_ASSERT(q[p[c]] == c); 1646 } 1647 #endif 1648 { 1649 coset_type c = _id_coset; 1650 size_t const n = nr_generators(); 1651 // Permute all the values in the _table, and pre-images, that relate 1652 // to active cosets 1653 while (c < nr_cosets_active()) { 1654 for (letter_type x = 0; x < n; ++x) { 1655 coset_type i = _table.get(p[c], x); 1656 _table.set(p[c], x, (i == UNDEFINED ? i : q[i])); 1657 i = _preim_init.get(p[c], x); 1658 _preim_init.set(p[c], x, (i == UNDEFINED ? i : q[i])); 1659 i = _preim_next.get(p[c], x); 1660 _preim_next.set(p[c], x, (i == UNDEFINED ? i : q[i])); 1661 } 1662 c++; 1663 } 1664 // Permute the rows themselves 1665 _table.apply_row_permutation(p); 1666 _preim_init.apply_row_permutation(p); 1667 _preim_next.apply_row_permutation(p); 1668 } 1669 { 1670 // Permute the cosets in the CosetManager using p . . . 1671 size_t const n = p.size(); 1672 for (coset_type i = 0; i < n; ++i) { 1673 coset_type current = i; 1674 while (i != p[current]) { 1675 size_t next = p[current]; 1676 switch_cosets(current, next); 1677 p[current] = current; 1678 current = next; 1679 } 1680 p[current] = current; 1681 } 1682 } 1683 } 1684 1685 // Based on the procedure SWITCH in Sims' book, p193 1686 // Swaps an active coset and another coset in the table. swap(coset_type const c,coset_type const d)1687 void ToddCoxeter::swap(coset_type const c, coset_type const d) { 1688 TODD_COXETER_REPORT_SWITCH(c, d) 1689 1690 LIBSEMIGROUPS_ASSERT(_coinc.empty()); 1691 LIBSEMIGROUPS_ASSERT(c != _id_coset); 1692 LIBSEMIGROUPS_ASSERT(d != _id_coset); 1693 LIBSEMIGROUPS_ASSERT(c != d); 1694 LIBSEMIGROUPS_ASSERT(is_valid_coset(c)); 1695 LIBSEMIGROUPS_ASSERT(is_valid_coset(d)); 1696 LIBSEMIGROUPS_ASSERT(is_active_coset(c) || is_active_coset(d)); 1697 1698 size_t const n = nr_generators(); 1699 for (letter_type x = 0; x < n; ++x) { 1700 coset_type cx = _table.get(c, x); 1701 coset_type dx = _table.get(d, x); 1702 1703 if (is_active_coset(c)) { 1704 // Replace c <-- d in the coset table _table 1705 coset_type e = _preim_init.get(c, x); 1706 while (e != UNDEFINED) { 1707 LIBSEMIGROUPS_ASSERT(_table.get(e, x) == c); 1708 _table.set(e, x, d); 1709 e = _preim_next.get(e, x); 1710 } 1711 _table.set(c, x, ff(c, d, cx)); 1712 } 1713 if (is_active_coset(d)) { 1714 // Replace d <-- c in the coset table _table 1715 coset_type e = _preim_init.get(d, x); 1716 while (e != UNDEFINED) { 1717 _table.set(e, x, c); 1718 e = _preim_next.get(e, x); 1719 } 1720 _table.set(d, x, ff(c, d, dx)); 1721 } 1722 if (is_active_coset(c) && is_active_coset(d) && cx == dx 1723 && cx != UNDEFINED) { 1724 // Swap c <--> d in preimages of cx = dx 1725 size_t found = 0; 1726 coset_type e = _preim_init.get(cx, x); 1727 if (e == c) { 1728 _preim_init.set(cx, x, d); 1729 found++; 1730 } else if (e == d) { 1731 _preim_init.set(cx, x, c); 1732 found++; 1733 } 1734 while (e != UNDEFINED && found < 2) { 1735 LIBSEMIGROUPS_ASSERT(ff(c, d, _table.get(e, x)) == cx); 1736 coset_type f = _preim_next.get(e, x); 1737 if (f == c) { 1738 _preim_next.set(e, x, d); 1739 found++; 1740 } else if (f == d) { 1741 _preim_next.set(e, x, c); 1742 found++; 1743 } 1744 e = f; 1745 } 1746 } else { 1747 if (is_active_coset(c) && cx != UNDEFINED) { 1748 // Replace c <-- d in preimages of cx, and d is not a preimage of 1749 // cx under x 1750 coset_type e = _preim_init.get(cx, x); 1751 if (e == c) { 1752 _preim_init.set(cx, x, d); 1753 e = UNDEFINED; // To prevent going into the next loop 1754 } 1755 while (e != UNDEFINED) { 1756 LIBSEMIGROUPS_ASSERT(ff(c, d, _table.get(e, x)) == cx); 1757 coset_type f = _preim_next.get(e, x); 1758 if (f == c) { 1759 _preim_next.set(e, x, d); 1760 break; 1761 } 1762 e = f; 1763 } 1764 } 1765 if (is_active_coset(d) && dx != UNDEFINED) { 1766 // Replace d <-- c in preimages of dx, and c is not a preimage of 1767 // dx under x 1768 coset_type e = _preim_init.get(dx, x); 1769 if (e == d) { 1770 _preim_init.set(dx, x, c); 1771 e = UNDEFINED; // To prevent going into the next loop 1772 } 1773 while (e != UNDEFINED) { 1774 LIBSEMIGROUPS_ASSERT(ff(c, d, _table.get(e, x)) == dx); 1775 coset_type f = _preim_next.get(e, x); 1776 if (f == d) { 1777 _preim_next.set(e, x, c); 1778 break; 1779 } 1780 e = f; 1781 } 1782 } 1783 } 1784 _table.swap(c, x, d, x); 1785 _preim_init.swap(c, x, d, x); 1786 _preim_next.swap(c, x, d, x); 1787 } 1788 switch_cosets(c, d); 1789 } 1790 1791 //////////////////////////////////////////////////////////////////////// 1792 // ToddCoxeter - member functions (debug) - private 1793 //////////////////////////////////////////////////////////////////////// 1794 1795 #ifdef LIBSEMIGROUPS_DEBUG 1796 // Validates the coset table. debug_validate_table() const1797 void ToddCoxeter::debug_validate_table() const { 1798 REPORT_DEBUG_DEFAULT("validating the coset table... "); 1799 size_t const n = nr_generators(); 1800 coset_type c = _id_coset; 1801 while (c != first_free_coset()) { 1802 if (!is_active_coset(c)) { 1803 LIBSEMIGROUPS_EXCEPTION( 1804 "invalid table, coset %d is both free and active!", c); 1805 } 1806 for (letter_type x = 0; x < n; ++x) { 1807 if (_table.get(c, x) != UNDEFINED 1808 && !is_active_coset(_table.get(c, x))) { 1809 LIBSEMIGROUPS_EXCEPTION("invalid table, _table.get(%d, %d) = %d" 1810 " is not an active coset or UNDEFINED!", 1811 c, 1812 x, 1813 _table.get(c, x)); 1814 } 1815 } 1816 c = next_active_coset(c); 1817 } 1818 TODD_COXETER_REPORT_OK(); 1819 } 1820 1821 // Validates the preimages, this is very expensive. debug_validate_preimages() const1822 void ToddCoxeter::debug_validate_preimages() const { 1823 REPORT_DEBUG_DEFAULT("validating preimages... "); 1824 size_t const n = nr_generators(); 1825 coset_type c = _id_coset; 1826 while (c != first_free_coset()) { 1827 for (letter_type x = 0; x < n; ++x) { 1828 coset_type e = _preim_init.get(c, x); 1829 std::set<coset_type> stored_preimages; 1830 while (e != UNDEFINED) { 1831 if (!is_active_coset(e)) { 1832 LIBSEMIGROUPS_EXCEPTION("invalid preimage e = %d of c = %d, e = " 1833 "%d is not an active coset or UNDEFINED!", 1834 e, 1835 c, 1836 e); 1837 } 1838 if (!stored_preimages.insert(e).second) { 1839 LIBSEMIGROUPS_EXCEPTION( 1840 "duplicate preimage e = %d of c = %d under x = %d!", e, c, x); 1841 } 1842 if (_table.get(e, x) != c) { 1843 LIBSEMIGROUPS_EXCEPTION( 1844 "invalid preimage, _table.get(%d, %d) != %d but found e = %d " 1845 "in the preimages of %d under x = %d", 1846 e, 1847 x, 1848 c, 1849 e, 1850 c, 1851 x); 1852 } 1853 e = _preim_next.get(e, x); 1854 } 1855 // Check there are no missing preimages! 1856 coset_type d = 0; 1857 while (d != first_free_coset()) { 1858 if (d != c && _table.get(d, x) == c 1859 && stored_preimages.insert(d).second) { 1860 LIBSEMIGROUPS_EXCEPTION( 1861 "missing preimage, _table.get(%d, %d) == %d but d = %d " 1862 "wasn't found in the stored preimages", 1863 d, 1864 x, 1865 c, 1866 d); 1867 } 1868 d = next_active_coset(d); 1869 } 1870 } 1871 c = next_active_coset(c); 1872 } 1873 TODD_COXETER_REPORT_OK(); 1874 } 1875 debug_verify_no_missing_deductions() const1876 void ToddCoxeter::debug_verify_no_missing_deductions() const { 1877 REPORT_DEBUG_DEFAULT("verifying no missing deductions or " 1878 "coincidences..."); 1879 if (!_deduct.empty()) { 1880 LIBSEMIGROUPS_EXCEPTION("the deduction stack is not empty"); 1881 } 1882 if (!_coinc.empty()) { 1883 LIBSEMIGROUPS_EXCEPTION("the coincidence stack is not empty"); 1884 } 1885 for (coset_type c = _id_coset; c != first_free_coset(); 1886 c = next_active_coset(c)) { 1887 for (auto it = _relations.cbegin(); it < _relations.cend(); it += 2) { 1888 word_type const& u = *it; 1889 word_type const& v = *(it + 1); 1890 LIBSEMIGROUPS_ASSERT(is_valid_coset(c)); 1891 LIBSEMIGROUPS_ASSERT(!u.empty()); 1892 LIBSEMIGROUPS_ASSERT(!v.empty()); 1893 coset_type x = tau(c, u.cbegin(), u.cend() - 1); 1894 if (x == UNDEFINED) { 1895 goto end; 1896 } 1897 LIBSEMIGROUPS_ASSERT(is_valid_coset(x)); 1898 coset_type y = tau(c, v.cbegin(), v.cend() - 1); 1899 if (y == UNDEFINED) { 1900 goto end; 1901 } 1902 LIBSEMIGROUPS_ASSERT(is_valid_coset(y)); 1903 letter_type a = u.back(); 1904 letter_type b = v.back(); 1905 coset_type xx = _table.get(x, a); 1906 coset_type yy = _table.get(y, b); 1907 if (xx == UNDEFINED && yy != UNDEFINED) { 1908 LIBSEMIGROUPS_EXCEPTION("missing deduction tau(%d, %d) = %d when " 1909 "pushing coset %d through %s = %s", 1910 x, 1911 a, 1912 yy, 1913 c, 1914 detail::to_string(u).c_str(), 1915 detail::to_string(v).c_str()); 1916 } else if (xx != UNDEFINED && yy == UNDEFINED) { 1917 // tau(y, b) <- xx 1918 LIBSEMIGROUPS_EXCEPTION("missing deduction tau(%d, %d) = %d when " 1919 "pushing coset %d through %s = %s", 1920 y, 1921 b, 1922 xx, 1923 c, 1924 detail::to_string(u).c_str(), 1925 detail::to_string(v).c_str()); 1926 } else if (xx != UNDEFINED && yy != UNDEFINED) { 1927 // tau(x, a) and tau(y, b) are defined 1928 if (xx != yy) { 1929 LIBSEMIGROUPS_EXCEPTION("missing coincidence %d = %d when " 1930 "pushing coset %d through %s = %s", 1931 xx, 1932 yy, 1933 c, 1934 detail::to_string(u).c_str(), 1935 detail::to_string(v).c_str()); 1936 } 1937 } 1938 } 1939 } 1940 end: 1941 TODD_COXETER_REPORT_OK(); 1942 } 1943 1944 #endif 1945 } // namespace congruence 1946 } // namespace libsemigroups 1947