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 implementations of the member functions for the
20 // FroidurePin class.
21 
22 #include "libsemigroups-debug.hpp"      // for LIBSEMIGROUPS_ASSERT
23 #include "libsemigroups-exception.hpp"  // for LIBSEMIGROUPS_EXCEPTION
24 #include "report.hpp"                   // for REPORT
25 #include "timer.hpp"                    // for detail::Timer
26 
27 #ifndef LIBSEMIGROUPS_INCLUDE_FROIDURE_PIN_IMPL_HPP_
28 #define LIBSEMIGROUPS_INCLUDE_FROIDURE_PIN_IMPL_HPP_
29 
30 #define TEMPLATE template <typename TElementType, typename TTraits>
31 #define FROIDURE_PIN FroidurePin<TElementType, TTraits>
32 
33 #define VOID TEMPLATE void
34 #define INLINE_VOID TEMPLATE inline void
35 #define SIZE_T TEMPLATE size_t
36 #define BOOL TEMPLATE bool
37 #define TRIL TEMPLATE tril
38 #define ELEMENT_INDEX_TYPE TEMPLATE element_index_type
39 #define LETTER_TYPE TEMPLATE letter_type
40 #define CONST_REFERENCE TEMPLATE typename FROIDURE_PIN::const_reference
41 #define ELEMENT_TYPE TEMPLATE typename FROIDURE_PIN::element_type
42 #define CAYLEY_GRAPH_TYPE TEMPLATE typename FROIDURE_PIN::cayley_graph_type
43 #define WORD_TYPE TEMPLATE word_type
44 
45 namespace libsemigroups {
46   // using enumerate_index_type = FroidurePinBase::size_type;
47   using element_index_type = FroidurePinBase::element_index_type;
48 
49   ////////////////////////////////////////////////////////////////////////
50   // FroidurePin - constructors + destructor - public
51   ////////////////////////////////////////////////////////////////////////
52 
53   TEMPLATE
FroidurePin(std::vector<element_type> const * gens)54   FROIDURE_PIN::FroidurePin(std::vector<element_type> const* gens)
55       : detail::BruidhinnTraits<TElementType>(),
56         FroidurePinBase(),
57         _degree(UNDEFINED),
58         _duplicate_gens(),
59         _elements(),
60         _enumerate_order(),
61         _final(),
62         _first(),
63         _found_one(false),
64         _gens(),
65         _id(),
66         _idempotents(),
67         _idempotents_found(false),
68         _is_idempotent(),
69         _left(gens->size()),
70         _length(),
71         _lenindex(),
72         _letter_to_pos(),
73         _map(),
74         _mtx(),
75         _nr(0),
76         _nrgens(gens->size()),
77         _nr_rules(0),
78         _pos(0),
79         _pos_one(0),
80         _prefix(),
81         _reduced(gens->size()),
82         _relation_gen(0),
83         _relation_pos(UNDEFINED),
84         _right(gens->size()),
85         _sorted(),
86         _suffix(),
87         _tmp_product(),
88         _wordlen(0) {  // (length of the current word) - 1
89     if (_nrgens == 0) {
90       LIBSEMIGROUPS_EXCEPTION("no generators given");
91     }
92 #ifdef LIBSEMIGROUPS_VERBOSE
93     _nr_products = 0;
94 #endif
95     _right.set_default_value(UNDEFINED);
96     // FIXME(later) inclusion of the next line makes test FroidurePin of BMats
97     // 01 extremely slow (~50ms to ~10s!!!!)
98     // reserve(_nrgens);
99     _degree = Degree()((*gens)[0]);
100 
101     for (size_t i = 0; i < _nrgens; ++i) {
102       size_t degree = Degree()((*gens)[i]);
103       if (degree != _degree) {
104         LIBSEMIGROUPS_EXCEPTION(
105             "generator %d has degree %d but should have degree %d",
106             i,
107             degree,
108             _degree);
109       }
110     }
111     for (const_reference x : *gens) {
112       _gens.push_back(this->internal_copy(this->to_internal_const(x)));
113     }
114 
115     _tmp_product = this->to_internal(One()(this->to_external_const(_gens[0])));
116     _id          = this->to_internal(One()(this->to_external_const(_gens[0])));
117     _lenindex.push_back(0);
118 
119     // #ifdef LIBSEMIGROUPS_DENSEHASHMAP
120     //     _map.set_empty_key(this->empty_key(_id));
121     // #endif
122 
123     // add the generators
124     for (letter_type i = 0; i < _nrgens; i++) {
125       auto it = _map.find(_gens[i]);
126       if (it != _map.end()) {  // duplicate generator
127         _letter_to_pos.push_back(it->second);
128         _nr_rules++;
129         _duplicate_gens.emplace_back(i, _first[it->second]);
130         // i.e. _gens[i] = _gens[_first[it->second]]
131         // _first maps from element_index_type -> letter_type :)
132       } else {
133         is_one(_gens[i], _nr);
134         _elements.push_back(_gens[i]);
135         // Note that every non-duplicate generator is *really* stored in
136         // _elements, and so must be deleted from _elements but not _gens.
137         _first.push_back(i);
138         _final.push_back(i);
139         _enumerate_order.push_back(_nr);
140         _letter_to_pos.push_back(_nr);
141         _length.push_back(1);
142         _map.emplace(_elements.back(), _nr);
143         _prefix.push_back(UNDEFINED);
144         // TODO(later) _prefix.push_back(_nr) and get rid of _letter_to_pos, and
145         // the extra clause in the run member function!
146         _suffix.push_back(UNDEFINED);
147         _nr++;
148       }
149     }
150     expand(_nr);
151     _lenindex.push_back(_enumerate_order.size());
152   }
153 
154   TEMPLATE
FroidurePin(std::vector<element_type> const & gens)155   FROIDURE_PIN::FroidurePin(std::vector<element_type> const& gens)
156       : FroidurePin(&gens) {}
157 
158   TEMPLATE
FroidurePin(std::initializer_list<element_type> gens)159   FROIDURE_PIN::FroidurePin(std::initializer_list<element_type> gens)
160       : FroidurePin(std::vector<element_type>(gens)) {}
161 
162   TEMPLATE
FroidurePin(FroidurePin const & S)163   FROIDURE_PIN::FroidurePin(FroidurePin const& S)
164       : detail::BruidhinnTraits<TElementType>(),
165         FroidurePinBase(S),
166         _degree(S._degree),
167         _duplicate_gens(S._duplicate_gens),
168         _elements(),
169         _enumerate_order(S._enumerate_order),
170         _final(S._final),
171         _first(S._first),
172         _found_one(S._found_one),
173         _gens(),
174         _id(this->internal_copy(S._id)),
175         _idempotents(S._idempotents),
176         _idempotents_found(S._idempotents_found),
177         _is_idempotent(S._is_idempotent),
178         _left(S._left),
179         _length(S._length),
180         _lenindex(S._lenindex),
181         _letter_to_pos(S._letter_to_pos),
182         _nr(S._nr),
183         _nrgens(S._nrgens),
184         _nr_rules(S._nr_rules),
185         _pos(S._pos),
186         _pos_one(S._pos_one),
187         _prefix(S._prefix),
188         _reduced(S._reduced),
189         _relation_gen(S._relation_gen),
190         _relation_pos(S._relation_pos),
191         _right(S._right),
192         _sorted(),  // TODO(later) S this if set
193         _suffix(S._suffix),
194         _wordlen(S._wordlen) {
195 #ifdef LIBSEMIGROUPS_VERBOSE
196     _nr_products = 0;
197 #endif
198     _elements.reserve(_nr);
199 
200     // #ifdef LIBSEMIGROUPS_DENSEHASHMAP
201     //     _map.set_empty_key(this->empty_key(_id));
202     //     _map.resize(_nr);
203     // #else
204     //     // _map.reserve(_nr); // FIXME(later) uncommenting this line causes
205     //     huge
206     //     // slow down in FroidurePin 000
207     // #endif
208     _tmp_product = this->internal_copy(S._id);
209 
210     element_index_type i = 0;
211     for (internal_const_reference x : S._elements) {
212       auto y = this->internal_copy(x);
213       _elements.push_back(y);
214       _map.emplace(y, i++);
215     }
216     copy_gens();
217   }
218 
219   TEMPLATE
~FroidurePin()220   FROIDURE_PIN::~FroidurePin() {
221     this->internal_free(_tmp_product);
222     this->internal_free(_id);
223 
224     // delete those generators not in _elements, i.e. the duplicate ones
225     for (auto& x : _duplicate_gens) {
226       this->internal_free(_gens[x.first]);
227     }
228     for (auto& x : _elements) {
229       this->internal_free(x);
230     }
231   }
232 
233   ////////////////////////////////////////////////////////////////////////
234   // FroidurePin - constructor - private
235   ////////////////////////////////////////////////////////////////////////
236 
237   // Partial copy.
238   // \p copy a semigroup
239   // \p coll a collection of additional generators
240   //
241   // This is a constructor for a semigroup generated by the generators of the
242   // FroidurePin copy and the (possibly) additional generators coll.
243   //
244   // The relevant parts of the data structure of copy are copied and
245   // \c this will be corrupt unless add_generators or closure is called
246   // subsequently. This is why this member function is private.
247   //
248   // The same effect can be obtained by copying copy using the copy
249   // constructor and then calling add_generators or closure. However,
250   // this constructor avoids copying those parts of the data structure of
251   // copy that add_generators invalidates anyway. If copy has not been
252   // enumerated at all, then these two routes for adding more generators are
253   // equivalent.
254   //
255   // <add_generators> or <closure> should usually be called after this.
256   TEMPLATE
FroidurePin(FroidurePin const & S,std::vector<element_type> const * coll)257   FROIDURE_PIN::FroidurePin(FroidurePin const&               S,
258                             std::vector<element_type> const* coll)
259       : _degree(S._degree),  // copy for comparison in add_generators
260         _duplicate_gens(S._duplicate_gens),
261         _elements(),
262         _found_one(S._found_one),  // copy in case degree doesn't change in
263                                    // add_generators
264         _gens(),
265         _idempotents(S._idempotents),
266         _idempotents_found(S._idempotents_found),
267         _is_idempotent(S._is_idempotent),
268         _left(S._left),
269         _letter_to_pos(S._letter_to_pos),
270         _nr(S._nr),
271         _nrgens(S._nrgens),
272         _nr_rules(0),
273         _pos(S._pos),
274         _pos_one(S._pos_one),  // copy in case degree doesn't change in
275                                // add_generators
276         _reduced(S._reduced),
277         _relation_gen(0),
278         _relation_pos(UNDEFINED),
279         _right(S._right),
280         _sorted(),
281         _wordlen(0) {
282     LIBSEMIGROUPS_ASSERT(!coll->empty());
283     LIBSEMIGROUPS_ASSERT(Degree()(coll->at(0)) >= S.degree());
284 
285 #ifdef LIBSEMIGROUPS_DEBUG
286     for (const_reference x : *coll) {
287       LIBSEMIGROUPS_ASSERT(Degree()(x) == Degree()((*coll)[0]));
288     }
289 #endif
290 #ifdef LIBSEMIGROUPS_VERBOSE
291     _nr_products = 0;
292 #endif
293     _elements.reserve(S._nr);
294 
295     // the following are required for assignment to specific positions in
296     // add_generators
297     _final.resize(S._nr, 0);
298     _first.resize(S._nr, 0);
299     _length.resize(S._nr, 0);
300     _prefix.resize(S._nr, 0);
301     _suffix.resize(S._nr, 0);
302 
303     size_t deg_plus = Degree()(coll->at(0)) - S.degree();
304 
305     if (deg_plus != 0) {
306       _degree += deg_plus;
307       _found_one = false;
308       _pos_one   = 0;
309     }
310 
311     _lenindex.push_back(0);
312     _lenindex.push_back(S._lenindex[1]);
313     _enumerate_order.reserve(S._nr);
314 
315     // add the distinct old generators to new _enumerate_order
316     for (enumerate_index_type i = 0; i < S._lenindex[1]; i++) {
317       _enumerate_order.push_back(S._enumerate_order[i]);
318       _final[_enumerate_order[i]]  = S._final[S._enumerate_order[i]];
319       _first[_enumerate_order[i]]  = S._first[S._enumerate_order[i]];
320       _prefix[_enumerate_order[i]] = UNDEFINED;
321       _suffix[_enumerate_order[i]] = UNDEFINED;
322       _length[_enumerate_order[i]] = 1;
323     }
324 
325     _id          = One()(this->to_internal(coll->at(0)));
326     _tmp_product = this->internal_copy(_id);
327 
328     // #ifdef LIBSEMIGROUPS_DENSEHASHMAP
329     //     _map.set_empty_key(this->empty_key(_id));
330     //     _map.resize(S._nr);
331     // #else
332     _map.reserve(S._nr);
333     // #endif
334 
335     element_index_type i = 0;
336     for (internal_const_reference x : S._elements) {
337       auto y = this->internal_copy(x);
338       IncreaseDegree()(y, deg_plus);
339       _elements.push_back(y);
340       _map.emplace(y, i);
341       is_one(y, i++);
342     }
343     copy_gens();  // copy the old generators
344     // Now this is ready to have add_generators or closure called on it
345   }
346 
347   ////////////////////////////////////////////////////////////////////////
348   // FroidurePin - member functions - public
349   ////////////////////////////////////////////////////////////////////////
350 
word_to_pos(word_type const & w) const351   ELEMENT_INDEX_TYPE FROIDURE_PIN::word_to_pos(word_type const& w) const {
352     // w is a word in the generators (i.e. a vector of letter_type's)
353     if (w.size() == 0) {
354       LIBSEMIGROUPS_EXCEPTION("the given word has length 0");
355     }
356     for (auto x : w) {
357       validate_letter_index(x);
358     }
359     element_index_type out = _letter_to_pos[w[0]];
360     for (auto it = w.cbegin() + 1; it < w.cend() && out != UNDEFINED; ++it) {
361       out = _right.get(out, _letter_to_pos[*it]);
362     }
363     return out;
364   }
365 
word_to_element(word_type const & w) const366   ELEMENT_TYPE FROIDURE_PIN::word_to_element(word_type const& w) const {
367     element_index_type pos = word_to_pos(w);
368     if (pos != UNDEFINED) {
369       // Return a copy
370       return this->external_copy(this->to_external_const(_elements[pos]));
371     }
372     // word_to_pos is always known for generators (i.e. when w.size() == 1),
373     // and word_to_pos verifies that w is valid.
374     LIBSEMIGROUPS_ASSERT(w.size() > 1);
375     LIBSEMIGROUPS_ASSERT(w[0] < nr_generators() && w[1] < nr_generators());
376     element_type prod
377         = this->external_copy(this->to_external_const(_tmp_product));
378     Product()(prod,
379               this->to_external_const(_gens[w[0]]),
380               this->to_external_const(_gens[w[1]]));
381     for (auto it = w.begin() + 2; it < w.end(); ++it) {
382       LIBSEMIGROUPS_ASSERT(*it < nr_generators());
383       Swap()(this->to_external(_tmp_product), prod);
384       Product()(prod,
385                 this->to_external_const(_tmp_product),
386                 this->to_external_const(_gens[*it]));
387     }
388     return prod;
389   }
390 
equal_to(word_type const & u,word_type const & v) const391   BOOL FROIDURE_PIN::equal_to(word_type const& u, word_type const& v) const {
392     element_index_type u_pos = word_to_pos(u);  // validates u
393     element_index_type v_pos = word_to_pos(v);  // validates v
394     if (finished() || (u_pos != UNDEFINED && v_pos != UNDEFINED)) {
395       LIBSEMIGROUPS_ASSERT(u_pos != UNDEFINED);
396       LIBSEMIGROUPS_ASSERT(v_pos != UNDEFINED);
397       return u_pos == v_pos;
398     } else {
399       element_type uu  = word_to_element(u);
400       element_type vv  = word_to_element(v);
401       auto         res = (uu == vv);
402       this->external_free(uu);
403       this->external_free(vv);
404       return res;
405     }
406   }
407 
current_max_word_length() const408   SIZE_T FROIDURE_PIN::current_max_word_length() const noexcept {
409     return _length[_enumerate_order.back()];
410   }
411 
degree() const412   SIZE_T FROIDURE_PIN::degree() const noexcept {
413     return _degree;
414   }
415 
nr_generators() const416   SIZE_T FROIDURE_PIN::nr_generators() const noexcept {
417     return _gens.size();
418   }
419 
generator(letter_type pos) const420   CONST_REFERENCE FROIDURE_PIN::generator(letter_type pos) const {
421     validate_letter_index(pos);
422     return this->to_external_const(_gens[pos]);
423   }
424 
current_position(const_reference x) const425   ELEMENT_INDEX_TYPE FROIDURE_PIN::current_position(const_reference x) const {
426     if (Degree()(this->to_internal_const(x)) != _degree) {
427       return UNDEFINED;
428     }
429 
430     auto it = _map.find(this->to_internal_const(x));
431     return (it == _map.end() ? UNDEFINED : it->second);
432   }
433 
current_size() const434   SIZE_T FROIDURE_PIN::current_size() const noexcept {
435     return _elements.size();
436   }
437 
current_nr_rules() const438   SIZE_T FROIDURE_PIN::current_nr_rules() const noexcept {
439     return _nr_rules;
440   }
441 
442   ELEMENT_INDEX_TYPE
prefix(element_index_type pos) const443   FROIDURE_PIN::prefix(element_index_type pos) const {
444     validate_element_index(pos);
445     return _prefix[pos];
446   }
447 
448   ELEMENT_INDEX_TYPE
suffix(element_index_type pos) const449   FROIDURE_PIN::suffix(element_index_type pos) const {
450     validate_element_index(pos);
451     return _suffix[pos];
452   }
453 
454   LETTER_TYPE
first_letter(element_index_type pos) const455   FROIDURE_PIN::first_letter(element_index_type pos) const {
456     validate_element_index(pos);
457     return _first[pos];
458   }
459 
460   LETTER_TYPE
final_letter(element_index_type pos) const461   FROIDURE_PIN::final_letter(element_index_type pos) const {
462     validate_element_index(pos);
463     return _final[pos];
464   }
465 
length_const(element_index_type pos) const466   SIZE_T FROIDURE_PIN::length_const(element_index_type pos) const {
467     validate_element_index(pos);
468     return _length[pos];
469   }
470 
length_non_const(element_index_type pos)471   SIZE_T FROIDURE_PIN::length_non_const(element_index_type pos) {
472     if (pos >= _nr) {
473       run();
474     }
475     return length_const(pos);
476   }
477 
478   ELEMENT_INDEX_TYPE
product_by_reduction(element_index_type i,element_index_type j) const479   FROIDURE_PIN::product_by_reduction(element_index_type i,
480                                      element_index_type j) const {
481     validate_element_index(i);
482     validate_element_index(j);
483 
484     if (length_const(i) <= length_const(j)) {
485       while (i != UNDEFINED) {
486         j = _left.get(j, _final[i]);
487         i = _prefix[i];
488       }
489       return j;
490     } else {
491       while (j != UNDEFINED) {
492         i = _right.get(i, _first[j]);
493         j = _suffix[j];
494       }
495       return i;
496     }
497   }
498 
499   ELEMENT_INDEX_TYPE
fast_product(element_index_type i,element_index_type j) const500   FROIDURE_PIN::fast_product(element_index_type i, element_index_type j) const {
501     validate_element_index(i);
502     validate_element_index(j);
503     if (length_const(i)
504             < 2 * Complexity()(this->to_external_const(_tmp_product))
505         || length_const(j)
506                < 2 * Complexity()(this->to_external_const(_tmp_product))) {
507       return product_by_reduction(i, j);
508     } else {
509       Product()(this->to_external(_tmp_product),
510                 this->to_external_const(_elements[i]),
511                 this->to_external_const(_elements[j]));
512       return _map.find(_tmp_product)->second;
513     }
514   }
515 
letter_to_pos(letter_type i) const516   ELEMENT_INDEX_TYPE FROIDURE_PIN::letter_to_pos(letter_type i) const {
517     validate_letter_index(i);
518     return _letter_to_pos[i];
519   }
520 
nr_idempotents()521   SIZE_T FROIDURE_PIN::nr_idempotents() {
522     init_idempotents();
523     return _idempotents.size();
524   }
525 
is_idempotent(element_index_type pos)526   BOOL FROIDURE_PIN::is_idempotent(element_index_type pos) {
527     validate_element_index(pos);
528     init_idempotents();
529     return _is_idempotent[pos];
530   }
531 
nr_rules()532   SIZE_T FROIDURE_PIN::nr_rules() {
533     run();
534     return _nr_rules;
535   }
536 
reserve(size_t n)537   VOID FROIDURE_PIN::reserve(size_t n) {
538     // Since the FroidurePin we are enumerating is bounded in size by the
539     // maximum value of an element_index_t, we cast the argument here to this
540     // integer type.
541     element_index_type nn = static_cast<element_index_type>(n);
542     _elements.reserve(nn);
543     _final.reserve(nn);
544     _first.reserve(nn);
545     _enumerate_order.reserve(nn);
546     _left.reserve(nn);
547     _length.reserve(nn);
548 
549     // #ifdef LIBSEMIGROUPS_DENSEHASHMAP
550     //    _map.resize(nn);
551     // #else
552     _map.reserve(nn);
553     // #endif
554 
555     _prefix.reserve(nn);
556     _reduced.reserve(nn);
557     _right.reserve(nn);
558     _suffix.reserve(nn);
559   }
560 
size()561   SIZE_T FROIDURE_PIN::size() {
562     run();
563     return _elements.size();
564   }
565 
contains(const_reference x)566   BOOL FROIDURE_PIN::contains(const_reference x) {
567     return (position(x) != UNDEFINED);
568   }
569 
position(const_reference x)570   ELEMENT_INDEX_TYPE FROIDURE_PIN::position(const_reference x) {
571     if (Degree()(x) != _degree) {
572       return UNDEFINED;
573     }
574 
575     while (true) {
576       auto it = _map.find(this->to_internal_const(x));
577       if (it != _map.end()) {
578         return it->second;
579       }
580       if (finished()) {
581         return UNDEFINED;
582       }
583       enumerate(_nr + 1);
584       // _nr + 1 means we run batch_size() more elements
585     }
586   }
587 
sorted_position(const_reference x)588   ELEMENT_INDEX_TYPE FROIDURE_PIN::sorted_position(const_reference x) {
589     return position_to_sorted_position(position(x));
590   }
591 
592   ELEMENT_INDEX_TYPE
position_to_sorted_position(element_index_type pos)593   FROIDURE_PIN::position_to_sorted_position(element_index_type pos) {
594     run();
595     if (pos >= _nr) {
596       return UNDEFINED;
597     }
598     init_sorted();
599     return _sorted[pos].second;
600   }
601 
at(element_index_type pos)602   CONST_REFERENCE FROIDURE_PIN::at(element_index_type pos) {
603     enumerate(pos + 1);
604     return this->to_external_const(_elements.at(pos));
605   }
606 
operator [](element_index_type pos) const607   CONST_REFERENCE FROIDURE_PIN::operator[](element_index_type pos) const {
608     LIBSEMIGROUPS_ASSERT(pos < _elements.size());
609     return this->to_external_const(_elements[pos]);
610   }
611 
sorted_at(element_index_type pos)612   CONST_REFERENCE FROIDURE_PIN::sorted_at(element_index_type pos) {
613     init_sorted();
614     return this->to_external_const(_sorted.at(pos).first);
615   }
616 
right(element_index_type i,letter_type j)617   ELEMENT_INDEX_TYPE FROIDURE_PIN::right(element_index_type i, letter_type j) {
618     run();
619     return _right.get(i, j);
620   }
621 
right_cayley_graph()622   CAYLEY_GRAPH_TYPE const& FROIDURE_PIN::right_cayley_graph() {
623     run();
624     _right.shrink_rows_to(size());
625     return _right;
626   }
627 
left(element_index_type i,letter_type j)628   ELEMENT_INDEX_TYPE FROIDURE_PIN::left(element_index_type i, letter_type j) {
629     run();
630     return _left.get(i, j);
631   }
632 
left_cayley_graph()633   CAYLEY_GRAPH_TYPE const& FROIDURE_PIN::left_cayley_graph() {
634     run();
635     _left.shrink_rows_to(size());
636     return _left;
637   }
638 
minimal_factorisation(word_type & word,element_index_type pos)639   VOID FROIDURE_PIN::minimal_factorisation(word_type&         word,
640                                            element_index_type pos) {
641     if (pos >= _nr && !finished()) {
642       enumerate(pos + 1);
643     }
644     validate_element_index(pos);
645     word.clear();
646     while (pos != UNDEFINED) {
647       word.push_back(_first[pos]);
648       pos = _suffix[pos];
649     }
650   }
651 
652   WORD_TYPE
minimal_factorisation(element_index_type pos)653   FROIDURE_PIN::minimal_factorisation(element_index_type pos) {
654     word_type word;
655     minimal_factorisation(word, pos);
656     return word;
657   }
658 
minimal_factorisation(const_reference x)659   WORD_TYPE FROIDURE_PIN::minimal_factorisation(const_reference x) {
660     element_index_type pos = this->position(x);
661     if (pos == UNDEFINED) {
662       LIBSEMIGROUPS_EXCEPTION(
663           "the argument is not an element of the semigroup");
664     }
665     return minimal_factorisation(pos);
666   }
667 
factorisation(word_type & word,element_index_type pos)668   VOID FROIDURE_PIN::factorisation(word_type& word, element_index_type pos) {
669     minimal_factorisation(word, pos);
670   }
671 
factorisation(element_index_type pos)672   WORD_TYPE FROIDURE_PIN::factorisation(element_index_type pos) {
673     return minimal_factorisation(pos);
674   }
675 
factorisation(const_reference x)676   WORD_TYPE FROIDURE_PIN::factorisation(const_reference x) {
677     return minimal_factorisation(x);
678   }
679 
reset_next_relation()680   VOID FROIDURE_PIN::reset_next_relation() noexcept {
681     _relation_pos = UNDEFINED;
682     _relation_gen = 0;
683   }
684 
next_relation(word_type & relation)685   VOID FROIDURE_PIN::next_relation(word_type& relation) {
686     if (!finished()) {
687       run();
688     }
689 
690     relation.clear();
691 
692     if (_relation_pos == _nr) {  // no more relations
693       return;
694     }
695 
696     if (_relation_pos != UNDEFINED) {
697       while (_relation_pos < _nr) {
698         while (_relation_gen < _nrgens) {
699           if (!_reduced.get(_enumerate_order[_relation_pos], _relation_gen)
700               && (_relation_pos < _lenindex[1]
701                   || _reduced.get(_suffix[_enumerate_order[_relation_pos]],
702                                   _relation_gen))) {
703             relation.push_back(_enumerate_order[_relation_pos]);
704             relation.push_back(_relation_gen);
705             relation.push_back(
706                 _right.get(_enumerate_order[_relation_pos], _relation_gen));
707             break;
708           }
709           _relation_gen++;
710         }
711         if (_relation_gen == _nrgens) {  // then relation is empty
712           _relation_gen = 0;
713           _relation_pos++;
714         } else {
715           break;
716         }
717       }
718       _relation_gen++;
719     } else {
720       // duplicate generators
721       if (_relation_gen < _duplicate_gens.size()) {
722         relation.push_back(_duplicate_gens[_relation_gen].first);
723         relation.push_back(_duplicate_gens[_relation_gen].second);
724         _relation_gen++;
725       } else {
726         _relation_gen = 0;
727         _relation_pos++;
728         next_relation(relation);
729       }
730     }
731   }
732 
enumerate(size_t limit)733   VOID FROIDURE_PIN::enumerate(size_t limit) {
734     if (finished() || limit <= current_size()) {
735       return;
736     } else if (LIMIT_MAX - batch_size() > current_size()) {
737       limit = std::max(limit, current_size() + batch_size());
738     } else {  // batch_size() is very big for some reason
739       limit = batch_size();
740     }
741     REPORT_DEFAULT("limit = %llu (%s)\n", limit, __func__);
742     run_until([this, &limit]() -> bool { return current_size() >= limit; });
743   }
744 
run_impl()745   VOID FROIDURE_PIN::run_impl() {
746     std::lock_guard<std::mutex> lg(_mtx);
747     if (_pos >= _nr) {
748       return;
749     }
750 
751     detail::Timer timer;
752     size_t        tid = THREAD_ID_MANAGER.tid(std::this_thread::get_id());
753 
754     // product the generators by every generator
755     if (_pos < _lenindex[1]) {
756       size_type nr_shorter_elements = _nr;
757       while (_pos < _lenindex[1]) {
758         element_index_type i = _enumerate_order[_pos];
759         for (letter_type j = 0; j != _nrgens; ++j) {
760           Product()(this->to_external(_tmp_product),
761                     this->to_external_const(_elements[i]),
762                     this->to_external_const(_gens[j]),
763                     tid);
764 
765 #ifdef LIBSEMIGROUPS_VERBOSE
766           _nr_products++;
767 #endif
768           auto it = _map.find(_tmp_product);
769 
770           if (it != _map.end()) {
771             _right.set(i, j, it->second);
772             _nr_rules++;
773           } else {
774             is_one(_tmp_product, _nr);
775             _elements.push_back(this->internal_copy(_tmp_product));
776             _first.push_back(_first[i]);
777             _final.push_back(j);
778             _enumerate_order.push_back(_nr);
779             _length.push_back(2);
780             _map.emplace(_elements.back(), _nr);
781             _prefix.push_back(i);
782             _reduced.set(i, j, true);
783             _right.set(i, j, _nr);
784             _suffix.push_back(_letter_to_pos[j]);
785             _nr++;
786           }
787         }
788         _pos++;
789       }
790       for (enumerate_index_type i = 0; i != _pos; ++i) {
791         letter_type b = _final[_enumerate_order[i]];
792         for (letter_type j = 0; j != _nrgens; ++j) {
793           _left.set(_enumerate_order[i], j, _right.get(_letter_to_pos[j], b));
794         }
795       }
796       _wordlen++;
797       expand(_nr - nr_shorter_elements);
798       _lenindex.push_back(_enumerate_order.size());
799     }
800 
801     // Multiply the words of length > 1 by every generator
802     while (_pos != _nr && !stopped()) {
803       size_type nr_shorter_elements = _nr;
804       while (_pos != _lenindex[_wordlen + 1] && !stopped()) {
805         element_index_type i = _enumerate_order[_pos];
806         letter_type        b = _first[i];
807         element_index_type s = _suffix[i];
808         for (letter_type j = 0; j != _nrgens; ++j) {
809           if (!_reduced.get(s, j)) {
810             element_index_type r = _right.get(s, j);
811             if (_found_one && r == _pos_one) {
812               _right.set(i, j, _letter_to_pos[b]);
813             } else if (_prefix[r] != UNDEFINED) {  // r is not a generator
814               _right.set(i, j, _right.get(_left.get(_prefix[r], b), _final[r]));
815             } else {
816               _right.set(i, j, _right.get(_letter_to_pos[b], _final[r]));
817             }
818           } else {
819             Product()(this->to_external(_tmp_product),
820                       this->to_external_const(_elements[i]),
821                       this->to_external_const(_gens[j]),
822                       tid);
823 #ifdef LIBSEMIGROUPS_VERBOSE
824             _nr_products++;
825 #endif
826             auto it = _map.find(_tmp_product);
827 
828             if (it != _map.end()) {
829               _right.set(i, j, it->second);
830               _nr_rules++;
831             } else {
832               is_one(_tmp_product, _nr);
833               _elements.push_back(this->internal_copy(_tmp_product));
834               _first.push_back(b);
835               _final.push_back(j);
836               _length.push_back(_wordlen + 2);
837               _map.emplace(_elements.back(), _nr);
838               _prefix.push_back(i);
839               _reduced.set(i, j, true);
840               _right.set(i, j, _nr);
841               _suffix.push_back(_right.get(s, j));
842               _enumerate_order.push_back(_nr);
843               _nr++;
844             }
845           }
846         }  // finished applying gens to <_elements.at(_pos)>
847         _pos++;
848       }  // finished words of length <wordlen> + 1
849       expand(_nr - nr_shorter_elements);
850 
851       if (_pos > _nr || _pos == _lenindex[_wordlen + 1]) {
852         for (enumerate_index_type i = _lenindex[_wordlen]; i != _pos; ++i) {
853           element_index_type p = _prefix[_enumerate_order[i]];
854           letter_type        b = _final[_enumerate_order[i]];
855           for (letter_type j = 0; j != _nrgens; ++j) {
856             _left.set(_enumerate_order[i], j, _right.get(_left.get(p, j), b));
857           }
858         }
859         _wordlen++;
860         _lenindex.push_back(_enumerate_order.size());
861       }
862       REPORT_DEFAULT("found %d elements, %d rules, %d max word length\n",
863                      _nr,
864                      _nr_rules,
865                      current_max_word_length());
866     }
867     REPORT_TIME(timer);
868     report_why_we_stopped();
869 #ifdef LIBSEMIGROUPS_VERBOSE
870     REPORT_DEFAULT("number of products = %llu\n", _nr_products);
871 #endif
872   }
873 
finished_impl() const874   BOOL FROIDURE_PIN::finished_impl() const {
875     return _pos >= _nr;
876   }
877 
add_generator(element_type const & x)878   VOID FROIDURE_PIN::add_generator(element_type const& x) {
879     add_generators({x});
880   }
881 
882   TEMPLATE
883   template <typename TCollection>
add_generators(TCollection const & coll)884   void FROIDURE_PIN::add_generators(TCollection const& coll) {
885     static_assert(!std::is_pointer<TCollection>::value,
886                   "TCollection should not be a pointer");
887     if (immutable()) {
888       LIBSEMIGROUPS_EXCEPTION("cannot add generators, the FroidurePin instance "
889                               "has been set to immutable");
890     } else if (coll.size() == 0) {
891       return;
892     }
893     for (auto it = coll.begin(); it < coll.end(); ++it) {
894       element_index_type degree = Degree()(*it);
895       if (degree != _degree) {
896         LIBSEMIGROUPS_EXCEPTION(
897             "new generator %llu has degree %llu but should have degree %llu",
898             it - coll.begin(),
899             degree,
900             _degree);
901       }
902     }
903     detail::Timer timer;
904     size_t        tid = THREAD_ID_MANAGER.tid(std::this_thread::get_id());
905 
906     // get some parameters from the old semigroup
907     letter_type old_nrgens  = _nrgens;
908     size_type   old_nr      = _nr;
909     size_type   nr_old_left = _pos;
910 
911     // erase the old index
912     _enumerate_order.erase(_enumerate_order.begin() + _lenindex[1],
913                            _enumerate_order.end());
914 
915     // old_new[i] indicates if we have seen _elements.at(i) yet in new.
916     std::vector<bool> old_new;
917     old_new.clear();
918     old_new.resize(old_nr, false);
919     for (letter_type i = 0; i < _letter_to_pos.size(); i++) {
920       old_new[_letter_to_pos[i]] = true;
921     }
922 
923     // add the new generators to new _gens, _elements, and _enumerate_order
924     for (const_reference x : coll) {
925       auto it = _map.find(this->to_internal_const(x));
926       if (it == _map.end()) {  // new generator
927         _gens.push_back(this->internal_copy(this->to_internal_const(x)));
928         _elements.push_back(_gens.back());
929         _map.emplace(_gens.back(), _nr);
930 
931         _first.push_back(_gens.size() - 1);
932         _final.push_back(_gens.size() - 1);
933 
934         _letter_to_pos.push_back(_nr);
935         _enumerate_order.push_back(_nr);
936 
937         is_one(this->to_internal_const(x), _nr);
938         _prefix.push_back(UNDEFINED);
939         _suffix.push_back(UNDEFINED);
940         _length.push_back(1);
941         _nr++;
942       } else if (_letter_to_pos[_first[it->second]] == it->second) {
943         _gens.push_back(this->internal_copy(this->to_internal_const(x)));
944         // x is one of the existing generators
945         _duplicate_gens.push_back(
946             std::make_pair(_gens.size() - 1, _first[it->second]));
947         // _gens[_gens.size() - 1] = _gens[_first[it->second])]
948         // since _first maps element_index_type -> letter_type
949         _letter_to_pos.push_back(it->second);
950       } else {
951         // x is an old element that will now be a generator
952         _gens.push_back(_elements[it->second]);
953         _letter_to_pos.push_back(it->second);
954         _enumerate_order.push_back(it->second);
955 
956         _first[it->second]  = _gens.size() - 1;
957         _final[it->second]  = _gens.size() - 1;
958         _prefix[it->second] = UNDEFINED;
959         _suffix[it->second] = UNDEFINED;
960         _length[it->second] = UNDEFINED;
961 
962         old_new[it->second] = true;
963       }
964     }
965 
966     // reset the data structure
967     _idempotents_found = false;
968     _nr_rules          = _duplicate_gens.size();
969     _pos               = 0;
970     _wordlen           = 0;
971     _nrgens            = _gens.size();
972     _lenindex.clear();
973     _lenindex.push_back(0);
974     _lenindex.push_back(_nrgens - _duplicate_gens.size());
975 
976     // Add columns for new generators
977     _reduced = detail::DynamicArray2<bool>(
978         _nrgens, _reduced.nr_rows() + _nrgens - old_nrgens);
979     _left.add_cols(_nrgens - _left.nr_cols());
980     _right.add_cols(_nrgens - _right.nr_cols());
981 
982     // Add rows in for newly added generators
983     _left.add_rows(_nrgens - old_nrgens);
984     _right.add_rows(_nrgens - old_nrgens);
985 
986     size_type nr_shorter_elements;
987 
988     // Repeat until we have multiplied all of the elements of <old> up to the
989     // old value of _pos by all of the (new and old) generators
990 
991     while (nr_old_left > 0) {
992       nr_shorter_elements = _nr;
993       while (_pos < _lenindex[_wordlen + 1] && nr_old_left > 0) {
994         element_index_type i = _enumerate_order[_pos];  // position in _elements
995         letter_type        b = _first[i];
996         element_index_type s = _suffix[i];
997         if (_right.get(i, 0) != UNDEFINED) {
998           nr_old_left--;
999           // _elements[i] is in old semigroup, and its descendants are
1000           // known
1001           for (letter_type j = 0; j < old_nrgens; j++) {
1002             element_index_type k = _right.get(i, j);
1003             if (!old_new[k]) {  // it's new!
1004               is_one(_elements[k], k);
1005               _first[k]  = _first[i];
1006               _final[k]  = j;
1007               _length[k] = _wordlen + 2;
1008               _prefix[k] = i;
1009               _reduced.set(i, j, true);
1010               if (_wordlen == 0) {
1011                 _suffix[k] = _letter_to_pos[j];
1012               } else {
1013                 _suffix[k] = _right.get(s, j);
1014               }
1015               _enumerate_order.push_back(k);
1016               old_new[k] = true;
1017             } else if (s == UNDEFINED || _reduced.get(s, j)) {
1018               // this clause could be removed if _nr_rules wasn't necessary
1019               _nr_rules++;
1020             }
1021           }
1022           for (letter_type j = old_nrgens; j < _nrgens; j++) {
1023             closure_update(i, j, b, s, old_nr, tid, old_new);
1024           }
1025         } else {
1026           // _elements[i] is either not in old, or it is in old but its
1027           // descendants are not known
1028           for (letter_type j = 0; j < _nrgens; j++) {
1029             closure_update(i, j, b, s, old_nr, tid, old_new);
1030           }
1031         }
1032         _pos++;
1033       }  // finished words of length <wordlen> + 1
1034 
1035       expand(_nr - nr_shorter_elements);
1036       if (_pos > _nr || _pos == _lenindex[_wordlen + 1]) {
1037         if (_wordlen == 0) {
1038           for (enumerate_index_type i = 0; i < _pos; i++) {
1039             size_t b = _final[_enumerate_order[i]];
1040             for (letter_type j = 0; j < _nrgens; j++) {
1041               // TODO(JDM) reuse old info here!
1042               _left.set(
1043                   _enumerate_order[i], j, _right.get(_letter_to_pos[j], b));
1044             }
1045           }
1046         } else {
1047           for (enumerate_index_type i = _lenindex[_wordlen]; i < _pos; i++) {
1048             element_index_type p = _prefix[_enumerate_order[i]];
1049             letter_type        b = _final[_enumerate_order[i]];
1050             for (letter_type j = 0; j < _nrgens; j++) {
1051               // TODO(JDM) reuse old info here!
1052               _left.set(_enumerate_order[i], j, _right.get(_left.get(p, j), b));
1053             }
1054           }
1055         }
1056         _lenindex.push_back(_enumerate_order.size());
1057         _wordlen++;
1058       }
1059       REPORT_DEFAULT("found %d elements, %d rules, %d max word length\n",
1060                      _nr,
1061                      _nr_rules,
1062                      current_max_word_length());
1063     }
1064     if (started()) {
1065       REPORT_TIME(timer);
1066     }
1067     report_why_we_stopped();
1068   }
1069 
1070   VOID
add_generators(std::initializer_list<const_element_type> coll)1071   FROIDURE_PIN::add_generators(std::initializer_list<const_element_type> coll) {
1072     add_generators<std::initializer_list<const_element_type>>(coll);
1073   }
1074 
1075   TEMPLATE
1076   template <typename TCollection>
1077   FROIDURE_PIN*
copy_add_generators(TCollection const & coll) const1078   FROIDURE_PIN::copy_add_generators(TCollection const& coll) const {
1079     static_assert(!std::is_pointer<TCollection>::value,
1080                   "TCollection should not be a pointer");
1081     if (coll.size() == 0) {
1082       return new FroidurePin(*this);
1083     } else {
1084       // Partially copy
1085       FroidurePin* out = new FroidurePin(*this, &coll);
1086       out->add_generators(coll);
1087       return out;
1088     }
1089   }
1090 
1091   TEMPLATE
1092   template <typename TCollection>
closure(TCollection const & coll)1093   void FROIDURE_PIN::closure(TCollection const& coll) {
1094     static_assert(!std::is_pointer<TCollection>::value,
1095                   "TCollection should not be a pointer");
1096     if (coll.size() == 0) {
1097       return;
1098     } else {
1099       for (const_reference x : coll) {
1100         if (!contains(x)) {
1101           add_generators({x});
1102         }
1103       }
1104     }
1105   }
1106 
closure(std::initializer_list<const_element_type> coll)1107   VOID FROIDURE_PIN::closure(std::initializer_list<const_element_type> coll) {
1108     closure<std::initializer_list<const_element_type>>(coll);
1109   }
1110 
1111   TEMPLATE
1112   template <typename TCollection>
copy_closure(TCollection const & coll)1113   FROIDURE_PIN* FROIDURE_PIN::copy_closure(TCollection const& coll) {
1114     static_assert(!std::is_pointer<TCollection>::value,
1115                   "TCollection should not be a pointer");
1116     if (coll.size() == 0) {
1117       return new FroidurePin(*this);
1118     } else {
1119       // The next line is required so that when we call the closure member
1120       // function on out, the partial copy contains enough information to all
1121       // membership testing without a call to run (which will fail because
1122       // the partial copy does not contain enough data to run run).
1123       this->run();
1124       // Partially copy
1125       FroidurePin* out = new FroidurePin(*this, &coll);
1126       out->closure(coll);
1127       return out;
1128     }
1129   }
1130 
is_monoid()1131   BOOL FROIDURE_PIN::is_monoid() {
1132     run();
1133     return _found_one;
1134   }
1135 
1136   // More specialisations below in kbe.*pp and element.*pp
is_finite()1137   TRIL FROIDURE_PIN::is_finite() {
1138     return tril::TRUE;
1139   }
1140 
1141   ////////////////////////////////////////////////////////////////////////
1142   // FroidurePin - validation member functions - private
1143   ////////////////////////////////////////////////////////////////////////
1144 
validate_element_index(element_index_type i) const1145   VOID FROIDURE_PIN::validate_element_index(element_index_type i) const {
1146     if (i >= _nr) {
1147       LIBSEMIGROUPS_EXCEPTION(
1148           "element index out of bounds, expected value in [0, %d), got %d",
1149           _nr,
1150           i);
1151     }
1152   }
1153 
validate_letter_index(letter_type i) const1154   VOID FROIDURE_PIN::validate_letter_index(letter_type i) const {
1155     if (i >= nr_generators()) {
1156       LIBSEMIGROUPS_EXCEPTION(
1157           "generator index out of bounds, expected value in [0, %d), got %d",
1158           nr_generators(),
1159           i);
1160     }
1161   }
1162 
1163   ////////////////////////////////////////////////////////////////////////
1164   // FroidurePin - enumeration member functions - private
1165   ////////////////////////////////////////////////////////////////////////
1166 
1167   // Expand the data structures in the semigroup with space for nr elements
expand(size_type nr)1168   INLINE_VOID FROIDURE_PIN::expand(size_type nr) {
1169     _left.add_rows(nr);
1170     _reduced.add_rows(nr);
1171     _right.add_rows(nr);
1172   }
1173 
1174   // Check if an element is the identity, x should be in the position pos
1175   // of _elements.
is_one(internal_const_element_type x,element_index_type pos)1176   INLINE_VOID FROIDURE_PIN::is_one(
1177       internal_const_element_type x,
1178       element_index_type
1179           pos) noexcept(std::is_nothrow_default_constructible<InternalEqualTo>::
1180                             value&& noexcept(
1181                                 std::declval<InternalEqualTo>()(x, x))) {
1182     if (!_found_one && InternalEqualTo()(x, _id)) {
1183       _pos_one   = pos;
1184       _found_one = true;
1185     }
1186   }
1187 
1188   // _nrgens, _duplicates_gens, _letter_to_pos, and _elements must all be
1189   // initialised for this to work, and _gens must point to an empty vector.
copy_gens()1190   VOID FROIDURE_PIN::copy_gens() {
1191     LIBSEMIGROUPS_ASSERT(_gens.empty());
1192     _gens.resize(_nrgens);
1193     std::vector<bool> seen(_nrgens, false);
1194     // really copy duplicate gens from _elements
1195     for (std::pair<letter_type, letter_type> const& x : _duplicate_gens) {
1196       // The degree of everything in _elements has already been increased (if
1197       // it needs to be at all), and so we do not need to increase the degree
1198       // in the copy below.
1199       _gens[x.first] = this->internal_copy(_elements[_letter_to_pos[x.second]]);
1200       seen[x.first]  = true;
1201     }
1202     // the non-duplicate gens are already in _elements, so don't really copy
1203     for (letter_type i = 0; i < _nrgens; i++) {
1204       if (!seen[i]) {
1205         _gens[i] = _elements[_letter_to_pos[i]];
1206       }
1207     }
1208   }
1209 
closure_update(element_index_type i,letter_type j,letter_type b,element_index_type s,size_type old_nr,size_t const & tid,std::vector<bool> & old_new)1210   VOID FROIDURE_PIN::closure_update(element_index_type i,
1211                                     letter_type        j,
1212                                     letter_type        b,
1213                                     element_index_type s,
1214                                     size_type          old_nr,
1215                                     size_t const&      tid,
1216                                     std::vector<bool>& old_new) {
1217     if (_wordlen != 0 && !_reduced.get(s, j)) {
1218       element_index_type r = _right.get(s, j);
1219       if (_found_one && r == _pos_one) {
1220         _right.set(i, j, _letter_to_pos[b]);
1221       } else if (_prefix[r] != UNDEFINED) {
1222         _right.set(i, j, _right.get(_left.get(_prefix[r], b), _final[r]));
1223       } else {
1224         _right.set(i, j, _right.get(_letter_to_pos[b], _final[r]));
1225       }
1226     } else {
1227       Product()(this->to_external(_tmp_product),
1228                 this->to_external_const(_elements[i]),
1229                 this->to_external_const(_gens[j]),
1230                 tid);
1231       auto it = _map.find(_tmp_product);
1232       if (it == _map.end()) {  // it's new!
1233         is_one(_tmp_product, _nr);
1234         _elements.push_back(this->internal_copy(_tmp_product));
1235         _first.push_back(b);
1236         _final.push_back(j);
1237         _length.push_back(_wordlen + 2);
1238         _map.emplace(_elements.back(), _nr);
1239         _prefix.push_back(i);
1240         _reduced.set(i, j, true);
1241         _right.set(i, j, _nr);
1242         if (_wordlen == 0) {
1243           _suffix.push_back(_letter_to_pos[j]);
1244         } else {
1245           _suffix.push_back(_right.get(s, j));
1246         }
1247         _enumerate_order.push_back(_nr);
1248         _nr++;
1249       } else if (it->second < old_nr && !old_new[it->second]) {
1250         // we didn't process it yet!
1251         is_one(_tmp_product, it->second);
1252         _first[it->second]  = b;
1253         _final[it->second]  = j;
1254         _length[it->second] = _wordlen + 2;
1255         _prefix[it->second] = i;
1256         _reduced.set(i, j, true);
1257         _right.set(i, j, it->second);
1258         if (_wordlen == 0) {
1259           _suffix[it->second] = _letter_to_pos[j];
1260         } else {
1261           _suffix[it->second] = _right.get(s, j);
1262         }
1263         _enumerate_order.push_back(it->second);
1264         old_new[it->second] = true;
1265       } else {  // it->second >= old->_nr || old_new[it->second]
1266         // it's old
1267         _right.set(i, j, it->second);
1268         _nr_rules++;
1269       }
1270     }
1271   }
1272 
1273   ////////////////////////////////////////////////////////////////////////
1274   // FroidurePin - initialisation member functions - private
1275   ////////////////////////////////////////////////////////////////////////
1276 
1277   // Initialise the data member _sorted. We store a list of pairs consisting
1278   // of an internal_element_type and element_index_type which is sorted on the
1279   // first entry using the operator< of the Element class. The second
1280   // component is then inverted (as a permutation) so that we can then find
1281   // the position of an element in the sorted list of elements.
init_sorted()1282   VOID FROIDURE_PIN::init_sorted() {
1283     if (_sorted.size() == size()) {
1284       return;
1285     }
1286     size_t n = size();
1287     _sorted.reserve(n);
1288     for (element_index_type i = 0; i < n; i++) {
1289       _sorted.emplace_back(_elements[i], i);
1290     }
1291     std::sort(
1292         _sorted.begin(),
1293         _sorted.end(),
1294         [this](std::pair<internal_element_type, element_index_type> const& x,
1295                std::pair<internal_element_type, element_index_type> const& y)
1296             -> bool {
1297           return Less()(this->to_external_const(x.first),
1298                         this->to_external_const(y.first));
1299         });
1300 
1301     // Invert the permutation in _sorted[*].second
1302     std::vector<element_index_type> tmp_inverter;
1303     tmp_inverter.resize(n);
1304     for (element_index_type i = 0; i < n; i++) {
1305       tmp_inverter[_sorted[i].second] = i;
1306     }
1307     for (element_index_type i = 0; i < n; i++) {
1308       _sorted[i].second = tmp_inverter[i];
1309     }
1310   }
1311 
1312   // Find the idempotents and store their pointers and positions in a
1313   // std::pair of type internal_idempotent_pair.
init_idempotents()1314   VOID FROIDURE_PIN::init_idempotents() {
1315     if (_idempotents_found) {
1316       return;
1317     }
1318     _idempotents_found = true;
1319     run();
1320     _is_idempotent.resize(_nr, false);
1321 
1322     detail::Timer timer;
1323 
1324     // Find the threshold beyond which it is quicker to simply product
1325     // elements rather than follow a path in the Cayley graph. This is the
1326     // enumerate_index_t i for which length(i) >= complexity.
1327     size_t cmplxty = std::max(
1328         size_t{Complexity()(this->to_external_const(_tmp_product)) / 2},
1329         size_t{1});
1330     LIBSEMIGROUPS_ASSERT(_lenindex.size() > 1);
1331     // threshold_length = the min. length of a word which is >= complexity.
1332     // if a word has length strictly greater than threshold_length, then we
1333     // multiply, otherwise we trace in the Cayley graph.
1334     size_t threshold_length = std::min(cmplxty, current_max_word_length());
1335     LIBSEMIGROUPS_ASSERT(threshold_length < _lenindex.size());
1336 
1337     enumerate_index_type threshold_index = _lenindex.at(threshold_length);
1338 
1339     size_t total_load = 0;
1340     for (size_t i = 1; i <= threshold_length; ++i) {
1341       // _lenindex[i - 1] is the element_index_t where words of length i begin
1342       // so _lenindex[i] - _lenindex[i - 1]) is the number of words of length
1343       // i.
1344       total_load += i * (_lenindex[i] - _lenindex[i - 1]);
1345     }
1346 
1347     REPORT_VERBOSE_DEFAULT("When finding the number of idempotents . . .");
1348     REPORT_VERBOSE_DEFAULT(
1349         "complexity of multiplication %*s = %llu\n", 11, " ", cmplxty);
1350     REPORT_VERBOSE_DEFAULT(
1351         "multiple words longer than %*s = %llu\n", 13, " ", threshold_length);
1352     REPORT_VERBOSE_DEFAULT(
1353         "number of paths traced in Cayley graph %*s = %llu\n",
1354         0,
1355         " ",
1356         threshold_index);
1357     REPORT_VERBOSE_DEFAULT(
1358         "mean path length %*s = %llu\n", 23, " ", total_load / threshold_index);
1359     REPORT_VERBOSE_DEFAULT(
1360         "number of products %*s = %llu\n", 21, " ", _nr - threshold_index);
1361 
1362     // _lenindex.at(threshold_length) is the element_index_type where words of
1363     // length (threshold_length + 1) begin
1364     LIBSEMIGROUPS_ASSERT(_nr >= _lenindex.at(threshold_length));
1365     total_load += cmplxty * (_nr - _lenindex.at(threshold_length));
1366     size_t const N = max_threads();
1367     LIBSEMIGROUPS_ASSERT(N != 0);
1368 
1369     if (N == 1 || size() < concurrency_threshold()) {
1370       // Use only 1 thread
1371       idempotents(0, _nr, threshold_index, _idempotents);
1372     } else {
1373       // Use > 1 threads
1374       size_t                            mean_load = total_load / N;
1375       size_t                            len       = 1;
1376       std::vector<enumerate_index_type> first(N, 0);
1377       std::vector<enumerate_index_type> last(N, _nr);
1378       std::vector<std::vector<internal_idempotent_pair>> tmp(
1379           N, std::vector<internal_idempotent_pair>());
1380       std::vector<std::thread> threads;
1381       THREAD_ID_MANAGER.reset();
1382 
1383       for (size_t i = 0; i < N - 1; i++) {
1384         size_t thread_load = 0;
1385         last[i]            = first[i];
1386         while (thread_load < mean_load && last[i] < threshold_index) {
1387           if (last[i] >= _lenindex[len]) {
1388             ++len;
1389           }
1390           thread_load += len;
1391           ++last[i];
1392         }
1393         while (thread_load < mean_load) {
1394           thread_load += cmplxty;
1395           ++last[i];
1396         }
1397         total_load -= thread_load;
1398         REPORT_DEFAULT("thread %d has load %d\n", i + 1, thread_load);
1399         first[i + 1] = last[i];
1400 
1401         threads.emplace_back(&FroidurePin::idempotents,
1402                              this,
1403                              first[i],
1404                              last[i],
1405                              threshold_index,
1406                              std::ref(tmp[i]));
1407       }
1408 
1409       REPORT_DEFAULT("thread %d has load %d\n", N, total_load);
1410       threads.emplace_back(&FroidurePin::idempotents,
1411                            this,
1412                            first[N - 1],
1413                            last[N - 1],
1414                            threshold_index,
1415                            std::ref(tmp[N - 1]));
1416 
1417       size_t nr_idempotents = 0;
1418       for (size_t i = 0; i < N; i++) {
1419         threads[i].join();
1420         nr_idempotents += tmp[i].size();
1421       }
1422       _idempotents.reserve(nr_idempotents);
1423       for (size_t i = 0; i < N; i++) {
1424         std::copy(
1425             tmp[i].begin(), tmp[i].end(), std::back_inserter(_idempotents));
1426       }
1427     }
1428     REPORT_TIME(timer);
1429   }
1430 
1431   // Find the idempotents in the range [first, last) and store
1432   // the corresponding std::pair of type internal_idempotent_pair in the 4th
1433   // parameter. The parameter threshold is the point, calculated in
1434   // init_idempotents, at which it is better to simply product elements
1435   // rather than trace in the left/right Cayley graph.
idempotents(enumerate_index_type const first,enumerate_index_type const last,enumerate_index_type const threshold,std::vector<internal_idempotent_pair> & idempotents)1436   VOID FROIDURE_PIN::idempotents(
1437       enumerate_index_type const             first,
1438       enumerate_index_type const             last,
1439       enumerate_index_type const             threshold,
1440       std::vector<internal_idempotent_pair>& idempotents) {
1441     REPORT_DEFAULT(
1442         "first = %d, last = %d, diff = %d\n", first, last, last - first);
1443     detail::Timer timer;
1444 
1445     enumerate_index_type pos = first;
1446 
1447     for (; pos < std::min(threshold, last); pos++) {
1448       element_index_type k = _enumerate_order[pos];
1449       if (!_is_idempotent[k]) {
1450         // The following is product_by_reduction, don't have to consider
1451         // lengths because they are equal!!
1452         element_index_type i = k, j = k;
1453         while (j != UNDEFINED) {
1454           i = _right.get(i, _first[j]);
1455           // TODO(later) improve this if R/L-classes are known to stop
1456           // performing the product if we fall out of the R/L-class of the
1457           // initial element.
1458           j = _suffix[j];
1459         }
1460         if (i == k) {
1461           idempotents.emplace_back(_elements[k], k);
1462           _is_idempotent[k] = true;
1463         }
1464       }
1465     }
1466 
1467     if (pos >= last) {
1468       REPORT_TIME(timer);
1469       return;
1470     }
1471 
1472     // Cannot use _tmp_product itself since there are multiple threads here!
1473     internal_element_type tmp_product = this->internal_copy(_tmp_product);
1474     size_t tid = THREAD_ID_MANAGER.tid(std::this_thread::get_id());
1475 
1476     for (; pos < last; pos++) {
1477       element_index_type k = _enumerate_order[pos];
1478       if (!_is_idempotent[k]) {
1479         Product()(this->to_external(tmp_product),
1480                   this->to_external(_elements[k]),
1481                   this->to_external(_elements[k]),
1482                   tid);
1483         if (InternalEqualTo()(tmp_product, _elements[k])) {
1484           idempotents.emplace_back(_elements[k], k);
1485           _is_idempotent[k] = true;
1486         }
1487       }
1488     }
1489     this->internal_free(tmp_product);
1490     REPORT_TIME(timer);
1491   }
1492 
1493   ////////////////////////////////////////////////////////////////////////
1494   // FroidurePin - iterators - public
1495   ////////////////////////////////////////////////////////////////////////
1496 
1497   TEMPLATE
cbegin() const1498   typename FROIDURE_PIN::const_iterator FROIDURE_PIN::cbegin() const {
1499     return const_iterator(_elements.cbegin());
1500   }
1501 
1502   TEMPLATE
begin() const1503   typename FROIDURE_PIN::const_iterator FROIDURE_PIN::begin() const {
1504     return cbegin();
1505   }
1506 
1507   TEMPLATE
cend() const1508   typename FROIDURE_PIN::const_iterator FROIDURE_PIN::cend() const {
1509     return const_iterator(_elements.cend());
1510   }
1511 
1512   TEMPLATE
end() const1513   typename FROIDURE_PIN::const_iterator FROIDURE_PIN::end() const {
1514     return cend();
1515   }
1516 
1517   TEMPLATE
crbegin() const1518   typename FROIDURE_PIN::const_reverse_iterator FROIDURE_PIN::crbegin() const {
1519     return const_reverse_iterator(cend());
1520   }
1521 
1522   TEMPLATE
crend() const1523   typename FROIDURE_PIN::const_reverse_iterator FROIDURE_PIN::crend() const {
1524     return const_reverse_iterator(cbegin());
1525   }
1526 
1527   TEMPLATE
cbegin_sorted()1528   typename FROIDURE_PIN::const_iterator_sorted FROIDURE_PIN::cbegin_sorted() {
1529     init_sorted();
1530     return const_iterator_pair_first(_sorted.cbegin());
1531   }
1532 
1533   TEMPLATE
cend_sorted()1534   typename FROIDURE_PIN::const_iterator_sorted FROIDURE_PIN::cend_sorted() {
1535     init_sorted();
1536     return const_iterator_pair_first(_sorted.cend());
1537   }
1538 
1539   TEMPLATE
1540   typename FROIDURE_PIN::const_reverse_iterator_sorted
crbegin_sorted()1541   FROIDURE_PIN::crbegin_sorted() {
1542     init_sorted();
1543     return const_reverse_iterator_pair_first(cend_sorted());
1544   }
1545 
1546   TEMPLATE
1547   typename FROIDURE_PIN::const_reverse_iterator_sorted
crend_sorted()1548   FROIDURE_PIN::crend_sorted() {
1549     init_sorted();
1550     return const_reverse_iterator_pair_first(cbegin_sorted());
1551   }
1552 
1553   TEMPLATE
1554   typename FROIDURE_PIN::const_iterator_idempotents
cbegin_idempotents()1555   FROIDURE_PIN::cbegin_idempotents() {
1556     init_idempotents();
1557     return const_iterator_pair_first(_idempotents.cbegin());
1558   }
1559 
1560   TEMPLATE
1561   typename FROIDURE_PIN::const_iterator_idempotents
cend_idempotents()1562   FROIDURE_PIN::cend_idempotents() {
1563     init_idempotents();
1564     return const_iterator_pair_first(_idempotents.cend());
1565   }
1566 
1567   ////////////////////////////////////////////////////////////////////////
1568   // FroidurePin - iterators - private
1569   ////////////////////////////////////////////////////////////////////////
1570 
1571   TEMPLATE
1572   struct FROIDURE_PIN::DerefPairFirst : detail::BruidhinnTraits<TElementType> {
1573     const_reference
operator ()libsemigroups::FROIDURE_PIN::DerefPairFirst1574     operator()(typename std::vector<
1575                std::pair<internal_element_type,
1576                          element_index_type>>::const_iterator const& it) const {
1577       return this->to_external_const((*it).first);
1578     }
1579   };
1580 
1581   TEMPLATE
1582   struct FROIDURE_PIN::AddressOfPairFirst
1583       : detail::BruidhinnTraits<TElementType> {
1584     const_pointer
operator ()libsemigroups::FROIDURE_PIN::AddressOfPairFirst1585     operator()(typename std::vector<
1586                std::pair<internal_element_type,
1587                          element_index_type>>::const_iterator const& it) const {
1588       return &(this->to_external_const((*it).first));
1589     }
1590   };
1591 
1592   TEMPLATE
1593   struct FROIDURE_PIN::IteratorPairFirstTraits
1594       : detail::ConstIteratorTraits<
1595             std::vector<std::pair<internal_element_type, element_index_type>>> {
1596     using value_type =
1597         typename FroidurePin<TElementType, TTraits>::element_type;
1598     using const_reference =
1599         typename FroidurePin<TElementType, TTraits>::const_reference;
1600     using const_pointer =
1601         typename FroidurePin<TElementType, TTraits>::const_pointer;
1602 
1603     using Deref     = DerefPairFirst;
1604     using AddressOf = AddressOfPairFirst;
1605   };
1606 
1607 }  // namespace libsemigroups
1608 #endif  // LIBSEMIGROUPS_INCLUDE_FROIDURE_PIN_IMPL_HPP_
1609