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