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 #include "libsemigroups/cong-intf.hpp" 20 21 #include "libsemigroups/constants.hpp" // for UNDEFINED 22 #include "libsemigroups/froidure-pin-base.hpp" // for FroidurePinBase 23 #include "libsemigroups/libsemigroups-debug.hpp" // for LIBSEMIGROUPS_ASSERT 24 #include "libsemigroups/libsemigroups-exception.hpp" // for LIBSEMIGROUPS_EXCEPTION 25 #include "libsemigroups/report.hpp" // for REPORT_VERBOSE_DEFAULT 26 #include "libsemigroups/stl.hpp" // for detail::to_string 27 28 namespace libsemigroups { 29 30 class CongruenceInterface::LazyFroidurePin { 31 public: 32 LazyFroidurePin() = default; 33 set(std::shared_ptr<FroidurePinBase> ptr)34 void set(std::shared_ptr<FroidurePinBase> ptr) { 35 LIBSEMIGROUPS_ASSERT(_froidure_pin == nullptr); 36 LIBSEMIGROUPS_ASSERT(_fp_semigroup == nullptr); 37 _froidure_pin = ptr; 38 } 39 set(std::shared_ptr<FpSemigroupInterface> ptr)40 void set(std::shared_ptr<FpSemigroupInterface> ptr) { 41 LIBSEMIGROUPS_ASSERT(_froidure_pin == nullptr); 42 LIBSEMIGROUPS_ASSERT(_fp_semigroup == nullptr); 43 _fp_semigroup = ptr; 44 } 45 has_froidure_pin() const46 bool has_froidure_pin() const noexcept { 47 return _froidure_pin != nullptr; 48 } 49 has_fpsemigroup() const50 bool has_fpsemigroup() const noexcept { 51 return _fp_semigroup != nullptr; 52 } 53 can_compute_froidure_pin() const54 bool can_compute_froidure_pin() const noexcept { 55 return _froidure_pin != nullptr || _fp_semigroup != nullptr; 56 } 57 froidure_pin() const58 std::shared_ptr<FroidurePinBase> froidure_pin() const { 59 if (!has_froidure_pin()) { 60 if (_fp_semigroup != nullptr 61 && !_fp_semigroup->is_obviously_infinite()) { 62 _froidure_pin = _fp_semigroup->froidure_pin(); 63 } else { 64 // This can be the case if a CongruenceInterface is default 65 // constructed, i.e. constructed neither from a FroidurePinBase 66 // nor an FpSemigroupInterface, or the _fp_semigroup is obviously 67 // infinite 68 LIBSEMIGROUPS_EXCEPTION("no parent FroidurePin can be determined!"); 69 } 70 } 71 return _froidure_pin; 72 } 73 fpsemigroup() const74 std::shared_ptr<FpSemigroupInterface> fpsemigroup() const { 75 return _fp_semigroup; 76 } 77 78 private: 79 mutable std::shared_ptr<FroidurePinBase> _froidure_pin; 80 mutable std::shared_ptr<FpSemigroupInterface> _fp_semigroup; 81 }; 82 83 //////////////////////////////////////////////////////////////////////////// 84 // CongruenceInterface - constructors + destructor - public 85 //////////////////////////////////////////////////////////////////////////// 86 CongruenceInterface(congruence_type type)87 CongruenceInterface::CongruenceInterface(congruence_type type) 88 : Runner(), 89 // Non-mutable 90 _gen_pairs(), 91 _nr_gens(UNDEFINED), 92 _parent(std::make_shared<LazyFroidurePin>()), 93 _type(type), 94 // Mutable 95 _init_ntc_done(), 96 _is_obviously_finite(false), 97 _is_obviously_infinite(false), 98 _quotient(nullptr), 99 _non_trivial_classes() { 100 reset(); 101 } 102 103 CongruenceInterface::~CongruenceInterface() = default; 104 105 //////////////////////////////////////////////////////////////////////////// 106 // Runner - non-pure virtual overridden function - public 107 //////////////////////////////////////////////////////////////////////////// 108 before_run()109 void CongruenceInterface::before_run() { 110 if (nr_generators() == UNDEFINED) { 111 LIBSEMIGROUPS_EXCEPTION("no generators have been set!"); 112 } 113 } 114 115 //////////////////////////////////////////////////////////////////////////// 116 // CongruenceInterface - non-pure virtual methods - public 117 //////////////////////////////////////////////////////////////////////////// 118 const_contains(word_type const & u,word_type const & v) const119 tril CongruenceInterface::const_contains(word_type const& u, 120 word_type const& v) const { 121 validate_word(u); 122 validate_word(v); 123 if (u == v) { 124 return tril::TRUE; 125 } 126 class_index_type uu, vv; 127 try { 128 uu = const_word_to_class_index(u); 129 vv = const_word_to_class_index(v); 130 } catch (LibsemigroupsException const& e) { 131 REPORT_VERBOSE_DEFAULT("ignoring exception:\n%s", e.what()); 132 return tril::unknown; 133 } 134 if (uu == UNDEFINED || vv == UNDEFINED) { 135 return tril::unknown; 136 } else if (uu == vv) { 137 return tril::TRUE; 138 } else if (finished()) { 139 return tril::FALSE; 140 } else { 141 return tril::unknown; 142 } 143 } 144 set_nr_generators(size_t n)145 void CongruenceInterface::set_nr_generators(size_t n) { 146 if (nr_generators() != UNDEFINED) { 147 if (nr_generators() != n) { 148 LIBSEMIGROUPS_EXCEPTION("cannot change the number of generators"); 149 } else { 150 return; // do nothing 151 } 152 } else if (n == 0) { 153 LIBSEMIGROUPS_EXCEPTION("the number of generators must be non-zero!"); 154 } else if (started()) { 155 LIBSEMIGROUPS_EXCEPTION( 156 "cannot set the number of generator at this stage"); 157 } 158 _nr_gens = n; 159 set_nr_generators_impl(n); 160 reset(); 161 } 162 163 ///////////////////////////////////////////////////////////////////////// 164 // CongruenceInterface - non-virtual methods - public 165 ///////////////////////////////////////////////////////////////////////// 166 add_pair(word_type const & u,word_type const & v)167 void CongruenceInterface::add_pair(word_type const& u, word_type const& v) { 168 if (started()) { 169 LIBSEMIGROUPS_EXCEPTION( 170 "cannot add further generating pairs at this stage"); 171 } 172 validate_word(u); 173 validate_word(v); 174 if (u == v) { 175 return; 176 } else if (has_parent_froidure_pin() 177 && parent_froidure_pin()->equal_to(u, v)) { 178 return; 179 } 180 // Note that _gen_pairs might contain pairs of distinct words that 181 // represent the same element of the parent semigroup (if any). 182 _gen_pairs.emplace_back(u, v); 183 add_pair_impl(u, v); 184 reset(); 185 } 186 class_index_to_word(class_index_type i)187 word_type CongruenceInterface::class_index_to_word(class_index_type i) { 188 if (nr_generators() == UNDEFINED) { 189 LIBSEMIGROUPS_EXCEPTION("no generators have been defined"); 190 } else if (i >= nr_classes()) { 191 LIBSEMIGROUPS_EXCEPTION("invalid class index, expected a value in the " 192 "range [0, %d), found %d", 193 nr_classes(), 194 i); 195 } 196 return class_index_to_word_impl(i); 197 } 198 199 // Basic exception guarantee (since is_quotient_obviously_infinite() may 200 // change the object). 201 std::shared_ptr<FroidurePinBase> quotient_froidure_pin()202 CongruenceInterface::quotient_froidure_pin() { 203 if (_quotient != nullptr) { 204 LIBSEMIGROUPS_ASSERT(kind() == congruence_type::twosided); 205 return _quotient; 206 } else if (kind() != congruence_type::twosided) { 207 LIBSEMIGROUPS_EXCEPTION("the congruence must be two-sided"); 208 } else if (is_quotient_obviously_infinite()) { 209 LIBSEMIGROUPS_EXCEPTION( 210 "cannot find the quotient semigroup, it is infinite"); 211 } 212 _quotient = quotient_impl(); 213 _quotient->immutable(true); 214 return _quotient; 215 } 216 is_quotient_obviously_infinite()217 bool CongruenceInterface::is_quotient_obviously_infinite() { 218 // If has_parent_froidure_pin(), then that is either finite (and so this 219 // is not obviously infinite), or infinite, which is undecidable in 220 // general, so we leave the answer to this question to 221 // is_quotient_obviously_infinite_impl in the derived class. 222 if (nr_generators() == UNDEFINED) { 223 // If nr_generators() is undefined, then there is no quotient yet, 224 // and so it is not obviously infinite, or anything! 225 REPORT_VERBOSE("not obviously infinite (no generators yet defined)"); 226 return false; 227 } else if (has_quotient_froidure_pin() 228 && quotient_froidure_pin()->finished()) { 229 // If the quotient FroidurePin is fully enumerated, it must be 230 // finite, and hence this is not (obviously) infinite. 231 REPORT_VERBOSE("not obviously infinite (finite)"); 232 return false; 233 } else if (has_parent_froidure_pin() && parent_froidure_pin()->finished()) { 234 REPORT_VERBOSE("not obviously infinite (parent finite)"); 235 return false; 236 } else if (is_quotient_obviously_infinite_impl()) { 237 // The derived class of CongruenceInterface knows the quotient is 238 // infinite 239 return true; 240 } 241 REPORT_VERBOSE("the quotient is not obviously infinite . . ."); 242 return false; 243 } 244 is_quotient_obviously_finite()245 bool CongruenceInterface::is_quotient_obviously_finite() { 246 if ((has_quotient_froidure_pin() && quotient_froidure_pin()->finished()) 247 || (has_parent_froidure_pin() && parent_froidure_pin()->finished()) 248 || is_quotient_obviously_finite_impl()) { 249 return true; 250 } 251 return false; 252 } 253 nr_classes()254 size_t CongruenceInterface::nr_classes() { 255 if (nr_generators() == UNDEFINED) { 256 return UNDEFINED; 257 } else if (!finished() && is_quotient_obviously_infinite()) { 258 return POSITIVE_INFINITY; 259 } 260 return nr_classes_impl(); 261 } 262 263 CongruenceInterface::class_index_type word_to_class_index(word_type const & word)264 CongruenceInterface::word_to_class_index(word_type const& word) { 265 // validate_word throws if nr_generators is undefined. 266 validate_word(word); 267 return word_to_class_index_impl(word); 268 } 269 270 ///////////////////////////////////////////////////////////////////////// 271 // CongruenceInterface - non-virtual methods - protected 272 ///////////////////////////////////////////////////////////////////////// 273 set_parent_froidure_pin(std::shared_ptr<FroidurePinBase> prnt)274 void CongruenceInterface::set_parent_froidure_pin( 275 std::shared_ptr<FroidurePinBase> prnt) { 276 LIBSEMIGROUPS_ASSERT(!_parent->has_froidure_pin()); 277 LIBSEMIGROUPS_ASSERT(nr_generators() == UNDEFINED 278 || prnt->nr_generators() == nr_generators()); 279 LIBSEMIGROUPS_ASSERT(!finished()); 280 if (nr_generators() == UNDEFINED) { 281 set_nr_generators(prnt->nr_generators()); 282 } 283 _parent->set(prnt); 284 reset(); 285 } 286 set_parent_froidure_pin(std::shared_ptr<FpSemigroupInterface> prnt)287 void CongruenceInterface::set_parent_froidure_pin( 288 std::shared_ptr<FpSemigroupInterface> prnt) { 289 LIBSEMIGROUPS_ASSERT(!_parent->has_froidure_pin()); 290 LIBSEMIGROUPS_ASSERT(nr_generators() == UNDEFINED 291 || prnt->alphabet().size() == nr_generators()); 292 LIBSEMIGROUPS_ASSERT(!finished()); 293 if (nr_generators() == UNDEFINED && !prnt->alphabet().empty()) { 294 set_nr_generators(prnt->alphabet().size()); 295 } 296 _parent->set(prnt); 297 reset(); 298 } 299 300 ///////////////////////////////////////////////////////////////////////// 301 // CongruenceInterface - non-pure virtual methods - private 302 ///////////////////////////////////////////////////////////////////////// 303 add_pair_impl(word_type const &,word_type const &)304 void CongruenceInterface::add_pair_impl(word_type const&, word_type const&) {} 305 306 CongruenceInterface::class_index_type const_word_to_class_index(word_type const &) const307 CongruenceInterface::const_word_to_class_index(word_type const&) const { 308 return UNDEFINED; 309 } 310 set_nr_generators_impl(size_t)311 void CongruenceInterface::set_nr_generators_impl(size_t) { 312 // do nothing 313 } 314 315 std::shared_ptr<CongruenceInterface::non_trivial_classes_type const> non_trivial_classes_impl()316 CongruenceInterface::non_trivial_classes_impl() { 317 if (!_parent->can_compute_froidure_pin()) { 318 LIBSEMIGROUPS_EXCEPTION("Cannot determine the parent FroidurePin and so " 319 "cannot compute non-trivial classes!"); 320 } 321 // The next line may trigger an infinite computation 322 auto fp = _parent->froidure_pin(); 323 auto ntc = non_trivial_classes_type(nr_classes(), std::vector<word_type>()); 324 325 word_type w; 326 for (size_t pos = 0; pos < fp->size(); ++pos) { 327 fp->factorisation(w, pos); 328 LIBSEMIGROUPS_ASSERT(word_to_class_index(w) < ntc.size()); 329 ntc[word_to_class_index(w)].push_back(w); 330 } 331 ntc.erase(std::remove_if(ntc.begin(), 332 ntc.end(), 333 [](std::vector<word_type> const& klass) -> bool { 334 return klass.size() <= 1; 335 }), 336 ntc.end()); 337 return std::make_shared<non_trivial_classes_type>(ntc); 338 } 339 340 ///////////////////////////////////////////////////////////////////////// 341 // CongruenceInterface - non-virtual methods - private 342 ///////////////////////////////////////////////////////////////////////// 343 init_non_trivial_classes()344 void CongruenceInterface::init_non_trivial_classes() { 345 if (!_init_ntc_done) { 346 _non_trivial_classes = non_trivial_classes_impl(); 347 _init_ntc_done = true; 348 } 349 } 350 reset()351 void CongruenceInterface::reset() noexcept { 352 // set_finished(false); 353 _non_trivial_classes.reset(); 354 _init_ntc_done = false; 355 _quotient.reset(); 356 _is_obviously_finite = false; 357 _is_obviously_infinite = false; 358 } 359 360 std::shared_ptr<FroidurePinBase> parent_froidure_pin() const361 CongruenceInterface::parent_froidure_pin() const { 362 return _parent->froidure_pin(); 363 } 364 has_parent_froidure_pin() const365 bool CongruenceInterface::has_parent_froidure_pin() const noexcept { 366 return _parent->has_froidure_pin(); 367 } 368 369 std::shared_ptr<FpSemigroupInterface> parent_fpsemigroup() const370 CongruenceInterface::parent_fpsemigroup() const { 371 return _parent->fpsemigroup(); 372 } 373 has_parent_fpsemigroup() const374 bool CongruenceInterface::has_parent_fpsemigroup() const noexcept { 375 return _parent->has_fpsemigroup(); 376 } 377 378 ///////////////////////////////////////////////////////////////////////// 379 // CongruenceInterface - non-virtual methods - protected 380 ///////////////////////////////////////////////////////////////////////// 381 validate_letter(letter_type c) const382 bool CongruenceInterface::validate_letter(letter_type c) const { 383 if (nr_generators() == UNDEFINED) { 384 LIBSEMIGROUPS_EXCEPTION("no generators have been defined"); 385 } 386 return c < _nr_gens; 387 } 388 validate_word(word_type const & w) const389 void CongruenceInterface::validate_word(word_type const& w) const { 390 for (auto l : w) { 391 // validate_letter throws if no generators are defined 392 if (!validate_letter(l)) { 393 LIBSEMIGROUPS_EXCEPTION( 394 "letter index out of bounds in word %s, expected " 395 "value in [0, %d), got %d", 396 detail::to_string(w).c_str(), 397 l, 398 _nr_gens); 399 } 400 } 401 } 402 403 ///////////////////////////////////////////////////////////////////////// 404 // CongruenceInterface - non-virtual static methods - protected 405 ///////////////////////////////////////////////////////////////////////// 406 407 std::string const& congruence_type_to_string(congruence_type typ)408 CongruenceInterface::congruence_type_to_string(congruence_type typ) { 409 switch (typ) { 410 case congruence_type::twosided: 411 return STRING_TWOSIDED; 412 case congruence_type::left: 413 return STRING_LEFT; 414 case congruence_type::right: 415 return STRING_RIGHT; 416 default: 417 LIBSEMIGROUPS_EXCEPTION("incorrect type"); 418 } 419 } 420 421 ///////////////////////////////////////////////////////////////////////// 422 // CongruenceInterface - static data members - private 423 ///////////////////////////////////////////////////////////////////////// 424 425 const std::string CongruenceInterface::STRING_TWOSIDED = "two-sided"; 426 const std::string CongruenceInterface::STRING_LEFT = "left"; 427 const std::string CongruenceInterface::STRING_RIGHT = "right"; 428 } // namespace libsemigroups 429