1 //
2 // libsemigroups - C++ library for semigroups and monoids
3 // Copyright (C) 2020 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 the specializations of various adapters for
20 // derived classes of Element.
21 
22 #ifndef LIBSEMIGROUPS_ELEMENT_ADAPTERS_HPP_
23 #define LIBSEMIGROUPS_ELEMENT_ADAPTERS_HPP_
24 
25 #include "action.hpp"               // for RightAction
26 #include "adapters.hpp"             // for Complexity etc
27 #include "bitset.hpp"               // for BitSet
28 #include "constants.hpp"            // for UNDEFINED
29 #include "element.hpp"              // for Element
30 #include "konieczny.hpp"            // for Konieczny
31 #include "libsemigroups-debug.hpp"  // for LIBSEMIGROUPS_ASSERT
32 #include "stl.hpp"                  // for Hash, EqualTo, Less
33 
34 namespace libsemigroups {
35   ////////////////////////////////////////////////////////////////////////
36   // Complexity specializations
37   ////////////////////////////////////////////////////////////////////////
38 
39   // Specialization of templates from adapters.hpp for classes derived from
40   // the class Element, such as Transformation<size_t> and so on . . .
41   //! Specialization of the adapter Complexity for pointers to subclasses of
42   //! Element.
43   //!
44   //! \sa Complexity.
45   template <typename TSubclass>
46   struct Complexity<TSubclass*,
47                     typename std::enable_if<
48                         std::is_base_of<Element, TSubclass>::value>::type> {
49     //! Returns \p x->complexity().
operator ()libsemigroups::Complexity50     inline size_t operator()(TSubclass const* x) const {
51       return x->complexity();
52     }
53   };
54 
55   //! Specialization of the adapter Complexity for subclasses of
56   //! Element.
57   //!
58   //! \sa Complexity.
59   template <typename TSubclass>
60   struct Complexity<TSubclass,
61                     typename std::enable_if<
62                         std::is_base_of<Element, TSubclass>::value>::type> {
63     //! Returns \p x.complexity().
operator ()libsemigroups::Complexity64     inline size_t operator()(TSubclass const& x) const {
65       return x.complexity();
66     }
67   };
68 
69   ////////////////////////////////////////////////////////////////////////
70   // Degree specializations
71   ////////////////////////////////////////////////////////////////////////
72 
73   //! Specialization of the adapter Degree for pointers to subclasses of
74   //! Element.
75   //!
76   //! \sa Degree.
77   template <typename TSubclass>
78   struct Degree<TSubclass*,
79                 typename std::enable_if<
80                     std::is_base_of<Element, TSubclass>::value>::type> {
81     //! Returns \p x->degree().
operator ()libsemigroups::Degree82     inline size_t operator()(TSubclass const* x) const {
83       return x->degree();
84     }
85   };
86 
87   //! Specialization of the adapter Degree for subclasses of Element.
88   //!
89   //! \sa Degree.
90   template <typename TSubclass>
91   struct Degree<
92       TSubclass,
93       typename std::enable_if<std::is_base_of<Element, TSubclass>::value,
94                               void>::type> {
95     //! Returns \p x.degree().
operator ()libsemigroups::Degree96     inline size_t operator()(TSubclass const& x) const {
97       return x.degree();
98     }
99   };
100 
101   ////////////////////////////////////////////////////////////////////////
102   // IncreaseDegree specializations
103   ////////////////////////////////////////////////////////////////////////
104 
105   //! Specialization of the adapter IncreaseDegree for pointers to subclasses
106   //! of Element.
107   //!
108   //! \sa IncreaseDegree.
109   template <typename TSubclass>
110   struct IncreaseDegree<TSubclass*,
111                         typename std::enable_if<
112                             std::is_base_of<Element, TSubclass>::value>::type> {
113     //! Returns \p x->increase_degree_by(\p n).
operator ()libsemigroups::IncreaseDegree114     inline void operator()(TSubclass* x, size_t n) const {
115       return x->increase_degree_by(n);
116     }
117   };
118 
119   // TODO(later) why is there no specialisation for non-pointers for
120   // IncreaseDegree?
121 
122   ////////////////////////////////////////////////////////////////////////
123   // Less specializations
124   ////////////////////////////////////////////////////////////////////////
125 
126   //! Specialization of the adapter Less for pointers to subclasses
127   //! of Element.
128   //!
129   //! \sa Less.
130   template <typename TSubclass>
131   struct Less<TSubclass*,
132               typename std::enable_if<
133                   std::is_base_of<Element, TSubclass>::value>::type> {
134     //! Returns \c true if the object pointed to by \p x is less than the
135     //! object pointed to by \p y.
operator ()libsemigroups::Less136     inline bool operator()(TSubclass const* x, TSubclass const* y) const {
137       return *x < *y;
138     }
139   };
140 
141   // TODO(later) why is there no specialisation for non-pointers for
142   // Less?
143 
144   ////////////////////////////////////////////////////////////////////////
145   // One specializations
146   ////////////////////////////////////////////////////////////////////////
147 
148   //! Specialization of the adapter One for pointers to subclasses
149   //! of Element.
150   //!
151   //! \sa One.
152   template <typename TSubclass>
153   struct One<TSubclass*,
154              typename std::enable_if<
155                  std::is_base_of<Element, TSubclass>::value>::type> {
156     //! Returns a pointer to a new instance of the one of \p x.
operator ()libsemigroups::One157     TSubclass* operator()(Element const* x) const {
158       // return new TSubclass(std::move(x->identity<TSubclass>()));
159       return static_cast<TSubclass*>(x->heap_identity());
160     }
161 
162     //! Returns a pointer to a new instance of the one of \p x.
operator ()libsemigroups::One163     TSubclass* operator()(size_t n) {
164       return new TSubclass(std::move(TSubclass::one(n)));
165     }
166   };
167 
168   //! Specialization of the adapter One for subclasses of Element.
169   //!
170   //! \sa One.
171   template <typename TSubclass>
172   struct One<TSubclass,
173              typename std::enable_if<
174                  std::is_base_of<Element, TSubclass>::value>::type> {
175     //! Returns a new instance of the one of \p x.
operator ()libsemigroups::One176     TSubclass operator()(TSubclass const& x) const {
177       return static_cast<TSubclass>(x.identity());
178     }
179 
180     //! Returns a new instance of the one of \p x.
operator ()libsemigroups::One181     TSubclass operator()(size_t n) {
182       return TSubclass(std::move(TSubclass::identity(n)));
183     }
184   };
185 
186   ////////////////////////////////////////////////////////////////////////
187   // Product specializations
188   ////////////////////////////////////////////////////////////////////////
189 
190   //! Specialization of the adapter Product for pointers to subclasses of
191   //! Element.
192   //!
193   //! \sa Product.
194   template <typename TSubclass>
195   struct Product<TSubclass*,
196                  typename std::enable_if<
197                      std::is_base_of<Element, TSubclass>::value>::type> {
198     //! Changes \p xy in-place to hold the product of \p x and \p y using
199     //! Element::redefine.
operator ()libsemigroups::Product200     void operator()(TSubclass*       xy,
201                     TSubclass const* x,
202                     TSubclass const* y,
203                     size_t           tid = 0) {
204       xy->redefine(*x, *y, tid);
205     }
206   };
207 
208   //! Specialization of the adapter Product for subclasses of Element.
209   //!
210   //! \sa Product.
211   template <typename TSubclass>
212   struct Product<TSubclass,
213                  typename std::enable_if<
214                      std::is_base_of<Element, TSubclass>::value>::type> {
215     //! Changes \p xy in-place to hold the product of \p x and \p y using
216     //! Element::redefine.
operator ()libsemigroups::Product217     void operator()(TSubclass&       xy,
218                     TSubclass const& x,
219                     TSubclass const& y,
220                     size_t           tid = 0) {
221       xy.redefine(x, y, tid);
222     }
223   };
224 
225   ////////////////////////////////////////////////////////////////////////
226   // Swap specializations
227   ////////////////////////////////////////////////////////////////////////
228 
229   //! Specialization of the adapter Swap for subclasses of Element.
230   //!
231   //! \sa Swap.
232   template <typename TSubclass>
233   struct Swap<TSubclass*,
234               typename std::enable_if<
235                   std::is_base_of<Element, TSubclass>::value>::type> {
236     //! Swaps the object pointed to by \p x with the one pointed to by \p y.
operator ()libsemigroups::Swap237     void operator()(TSubclass* x, TSubclass* y) const {
238       x->swap(*y);
239     }
240   };
241 
242   // TODO(later) why is there no specialisation for non-pointers for
243   // Swap?
244 
245   ////////////////////////////////////////////////////////////////////////
246   // Inverse specializations
247   ////////////////////////////////////////////////////////////////////////
248 
249   //! Specialization of the adapter Inverse for pointers to
250   //! Permutation instances.
251   //!
252   //! \sa Inverse.
253   template <typename TValueType>
254   struct Inverse<Permutation<TValueType>*> {
255     //! Returns a pointer to a new instance of the inverse of \p x.
operator ()libsemigroups::Inverse256     Permutation<TValueType>* operator()(Permutation<TValueType>* x) {
257       return new Permutation<TValueType>(std::move(x->inverse()));
258     }
259   };
260 
261   //! Specialization of the adapter Inverse for Permutation instances.
262   //!
263   //! \sa Inverse.
264   template <typename TValueType>
265   struct Inverse<Permutation<TValueType>> {
266     //! Returns the inverse of \p x.
operator ()libsemigroups::Inverse267     Permutation<TValueType> operator()(Permutation<TValueType> const& x) {
268       return x.inverse();
269     }
270   };
271 
272   ////////////////////////////////////////////////////////////////////////
273   // Hash specializations
274   ////////////////////////////////////////////////////////////////////////
275 
276   //! Specialization of the adapter Hash for subclasses of Element.
277   //!
278   //! \sa Hash.
279   template <typename TSubclass>
280   struct Hash<TSubclass,
281               typename std::enable_if<
282                   std::is_base_of<Element, TSubclass>::value>::type> {
283     //! Returns \p x.hash_value()
operator ()libsemigroups::Hash284     size_t operator()(TSubclass const& x) const {
285       return x.hash_value();
286     }
287   };
288 
289   //! Specialization of the adapter Hash for pointers to subclasses of Element.
290   //!
291   //! \sa Hash.
292   template <typename TSubclass>
293   struct Hash<TSubclass*,
294               typename std::enable_if<
295                   std::is_base_of<Element, TSubclass>::value>::type> {
296     //! Returns \p x->hash_value()
operator ()libsemigroups::Hash297     size_t operator()(TSubclass const* x) const {
298       return x->hash_value();
299     }
300   };
301 
302   ////////////////////////////////////////////////////////////////////////
303   // EqualTo specializations
304   ////////////////////////////////////////////////////////////////////////
305 
306   //! Specialization of the adapter EqualTo for pointers to subclasses of
307   //! Element.
308   //!
309   //! \sa EqualTo.
310   template <typename TSubclass>
311   struct EqualTo<
312       TSubclass*,
313       typename std::enable_if<
314           std::is_base_of<libsemigroups::Element, TSubclass>::value>::type> {
315     //! Tests equality of two const Element pointers via equality of the
316     //! Elements they point to.
operator ()libsemigroups::EqualTo317     bool operator()(TSubclass const* x, TSubclass const* y) const {
318       return *x == *y;
319     }
320   };
321 
322   // TODO(later) why is there no specialisation for non-pointers for
323   // EqualTo?
324 
325   ////////////////////////////////////////////////////////////////////////
326   // ImageRight/LeftAction - PartialPerm
327   ////////////////////////////////////////////////////////////////////////
328 
329   // Slowest
330   //! Specialization of the adapter ImageRightAction for instances of
331   //! PartialPerm.
332   //!
333   //! \sa ImageRightAction
334   template <typename T>
335   struct ImageRightAction<PartialPerm<T>, PartialPerm<T>> {
336     //! Stores the idempotent \f$(xy) ^ {-1}xy\f$ in \p res.
operator ()libsemigroups::ImageRightAction337     void operator()(PartialPerm<T>&       res,
338                     PartialPerm<T> const& pt,
339                     PartialPerm<T> const& x) const noexcept {
340       res.redefine(pt, x);
341       res.swap(res.right_one());
342     }
343   };
344 
345   // Faster than the above, but slower than the below
346   // works for T = std::vector and T = StaticVector1
347   //! Specialization of the adapter ImageRightAction for instances of
348   //! PartialPerm and std::vector.
349   //!
350   //! \sa ImageRightAction
351   template <typename S, typename T>
352   struct ImageRightAction<PartialPerm<S>, T> {
353     //! Stores the image set of \c pt under \c x in \p res.
operator ()libsemigroups::ImageRightAction354     void operator()(T& res, T const& pt, PartialPerm<S> const& x) const {
355       res.clear();
356       for (auto i : pt) {
357         if (x[i] != UNDEFINED) {
358           res.push_back(x[i]);
359         }
360       }
361       std::sort(res.begin(), res.end());
362     }
363   };
364 
365   // Fastest, but limited to at most degree 64
366   //! Specialization of the adapter ImageRightAction for instances of
367   //! PartialPerm and BitSet<N> (\f$0 \leq N \leq 64\f$).
368   //!
369   //! \sa ImageRightAction
370   template <typename T, size_t N>
371   struct ImageRightAction<PartialPerm<T>, BitSet<N>> {
372     //! Stores the image set of \c pt under \c x in \p res.
operator ()libsemigroups::ImageRightAction373     void operator()(BitSet<N>&            res,
374                     BitSet<N> const&      pt,
375                     PartialPerm<T> const& x) const {
376       res.reset();
377       // Apply the lambda to every set bit in pt
378       pt.apply([&x, &res](size_t i) {
379         if (x[i] != UNDEFINED) {
380           res.set(x[i]);
381         }
382       });
383     }
384   };
385 
386   // Slowest
387   //! Specialization of the adapter ImageLeftAction for instances of
388   //! PartialPerm.
389   //!
390   //! \sa ImageLeftAction.
391   template <typename T>
392   struct ImageLeftAction<PartialPerm<T>, PartialPerm<T>> {
393     //! Stores the idempotent \f$xy(xy) ^ {-1}\f$ in \p res.
operator ()libsemigroups::ImageLeftAction394     void operator()(PartialPerm<T>&       res,
395                     PartialPerm<T> const& pt,
396                     PartialPerm<T> const& x) const noexcept {
397       res.redefine(x, pt);
398       res.swap(res.left_one());
399     }
400   };
401 
402   // Fastest when used with BitSet<N>.
403   // works for T = std::vector and T = BitSet<N>
404   // Using BitSet<N> limits this to size 64. However, if we are trying to
405   // compute a LeftAction object, then the max size of such is 2 ^ 64, which is
406   // probably not achievable. So, for higher degrees, we will only be able to
407   // compute relatively sparse LeftActions (i.e. not containing the majority of
408   // the 2 ^ n possible subsets), in which case using vectors or
409   // StaticVector1's might be not be appreciable slower anyway. All of this is
410   // to say that it probably isn't worthwhile trying to make BitSet's work for
411   // more than 64 bits.
412   //! Specialization of the adapter ImageLeftAction for instances of
413   //! PartialPerm and std::vector or BitSet<N> (\f$0 \leq N \leq 64\f$).
414   //!
415   //! \sa ImageLeftAction.
416   template <typename S, typename T>
417   struct ImageLeftAction<PartialPerm<S>, T> {
operator ()libsemigroups::ImageLeftAction418     void operator()(T& res, T const& pt, PartialPerm<S> const& x) const {
419       //! Stores the inverse image set of \c pt under \c x in \p res.
420       static PartialPerm<S> xx({});
421       x.inverse(xx);
422       ImageRightAction<PartialPerm<S>, T>()(res, pt, xx);
423     }
424   };
425 
426   ////////////////////////////////////////////////////////////////////////
427   // Lambda/Rho - PartialPerm
428   ////////////////////////////////////////////////////////////////////////
429 
430   // This currently limits the use of Konieczny to partial perms of degree at
431   // most 64 with the default traits class, since we cannot know the degree
432   // at compile time, only at run time.
433   //! Specialization of the adapter LambdaValue for instances of PartialPerm.
434   //! Note that the the type chosen here limits the Konieczny algorithm to
435   //! PartialPerms of degree at most 64 (or 32 on 32-bit systems).
436   //!
437   //! \sa LambdaValue.
438   template <typename T>
439   struct LambdaValue<PartialPerm<T>> {
440     static constexpr size_t N = BitSet<1>::max_size();
441     //! For PartialPerms, \c type is BitSet<N>, representing the image of the
442     //! PartialPerms.
443     using type = BitSet<N>;
444   };
445 
446   //! Specialization of the adapter RhoValue for instances of PartialPerm.
447   //! Note that the the type chosen here limits the Konieczny algorithm to
448   //! PartialPerms of degree at most 64 (or 32 on 32-bit systems).
449   //!
450   //! \sa RhoValue.
451   template <typename T>
452   struct RhoValue<PartialPerm<T>> {
453     //! For PartialPerms, \c type is BitSet<N>, representing the domain of the
454     //! PartialPerms.
455     using type = typename LambdaValue<PartialPerm<T>>::type;
456   };
457 
458   //! Specialization of the adapter Lambda for instances of PartialPerm and
459   //! BitSet<N>.
460   //!
461   //! \sa Lambda.
462   template <typename T, size_t N>
463   struct Lambda<PartialPerm<T>, BitSet<N>> {
464     //! Modifies \p res to contain the image set of \p x; that is, \p res[i]
465     //! will be \c true if and only if `x[j] = i` for some \f$j\f$.
operator ()libsemigroups::Lambda466     void operator()(BitSet<N>& res, PartialPerm<T> const& x) const {
467       if (x.degree() > N) {
468         LIBSEMIGROUPS_EXCEPTION(
469             "expected partial perm of degree at most %llu, found %llu",
470             static_cast<uint64_t>(N),
471             static_cast<uint64_t>(x.degree()));
472       }
473       res.reset();
474       for (size_t i = 0; i < x.degree(); ++i) {
475         if (x[i] != UNDEFINED) {
476           res.set(x[i]);
477         }
478       }
479     }
480   };
481 
482   //! Specialization of the adapter Rho for instances of PartialPerm and
483   //! BitSet<N>.
484   //!
485   //! \sa Rho.
486   template <typename T, size_t N>
487   struct Rho<PartialPerm<T>, BitSet<N>> {
488     //! Modifies \p res to contain the domain of \p x; that is, \p res[i]
489     //! will be \c true if and only if `x[i] != UNDEFINED`.
operator ()libsemigroups::Rho490     void operator()(BitSet<N>& res, PartialPerm<T> const& x) const {
491       if (x.degree() > N) {
492         LIBSEMIGROUPS_EXCEPTION(
493             "expected partial perm of degree at most %llu, found %llu",
494             static_cast<uint64_t>(N),
495             static_cast<uint64_t>(x.degree()));
496       }
497       static PartialPerm<T> xx({});
498       x.inverse(xx);
499       Lambda<PartialPerm<T>, BitSet<N>>()(res, xx);
500     }
501   };
502 
503   //! Specialization of the adapter Rank for instances of PartialPerm.
504   //!
505   //! \sa Rank and PartialPerm::crank.
506   template <typename T>
507   struct Rank<PartialPerm<T>> {
508     //! Returns the rank of \p x.
509     //!
510     //! The rank of a PartialPerm is the number of points in the image.
operator ()libsemigroups::Rank511     size_t operator()(PartialPerm<T> const& x) const {
512       return x.crank();
513     }
514   };
515 
516   ////////////////////////////////////////////////////////////////////////
517   // ImageRight/LeftAction - Transformation
518   ////////////////////////////////////////////////////////////////////////
519 
520   // Equivalent to OnSets in GAP
521   // Slowest
522   // works for T = std::vector and T = StaticVector1
523   //! Specialization of the adapter ImageRightAction for instances of
524   //! Transformation and std::vector.
525   //!
526   //! \sa ImageRightAction
527   template <typename S, typename T>
528   struct ImageRightAction<Transformation<S>, T> {
529     //! Stores the image set of \c pt under \c x in \p res.
operator ()libsemigroups::ImageRightAction530     void operator()(T& res, T const& pt, Transformation<S> const& x) const {
531       res.clear();
532       for (auto i : pt) {
533         res.push_back(x[i]);
534       }
535       std::sort(res.begin(), res.end());
536       res.erase(std::unique(res.begin(), res.end()), res.end());
537     }
538   };
539 
540   // Fastest, but limited to at most degree 64
541   //! Specialization of the adapter ImageRightAction for instances of
542   //! Transformation and BitSet<N> (\f$0 \leq N leq 64\f$).
543   //!
544   //! \sa ImageRightAction
545   template <typename TIntType, size_t N>
546   struct ImageRightAction<Transformation<TIntType>, BitSet<N>> {
547     //! Stores the image set of \c pt under \c x in \p res.
operator ()libsemigroups::ImageRightAction548     void operator()(BitSet<N>&                      res,
549                     BitSet<N> const&                pt,
550                     Transformation<TIntType> const& x) const {
551       res.reset();
552       // Apply the lambda to every set bit in pt
553       pt.apply([&x, &res](size_t i) { res.set(x[i]); });
554     }
555   };
556 
557   // OnKernelAntiAction
558   // T = StaticVector1<S> or std::vector<S>
559   //! Specialization of the adapter ImageLeftAction for instances of
560   //! Transformation and std::vector.
561   //!
562   //! \sa ImageLeftAction
563   template <typename S, typename T>
564   struct ImageLeftAction<Transformation<S>, T> {
565     //! Stores the image of \p pt under the left action of \p x in \p res.
operator ()libsemigroups::ImageLeftAction566     void operator()(T& res, T const& pt, Transformation<S> const& x) const {
567       res.clear();
568       res.resize(x.degree());
569       static thread_local std::vector<S> buf;
570       buf.clear();
571       buf.resize(x.degree(), S(UNDEFINED));
572       S next = 0;
573 
574       for (size_t i = 0; i < res.size(); ++i) {
575         if (buf[pt[x[i]]] == UNDEFINED) {
576           buf[pt[x[i]]] = next++;
577         }
578         res[i] = buf[pt[x[i]]];
579       }
580     }
581   };
582 
583   ////////////////////////////////////////////////////////////////////////
584   // Lambda/Rho - Transformation
585   ////////////////////////////////////////////////////////////////////////
586 
587   // This currently limits the use of Konieczny to transformation of degree at
588   // most 64 with the default traits class, since we cannot know the degree at
589   // compile time, only at run time.
590   //! Specialization of the adapter LambdaValue for instances of Transformation.
591   //! Note that the the type chosen here limits the Konieczny algorithm to
592   //! Transformations of degree at most 64 (or 32 on 32-bit systems).
593   //!
594   //! \sa LambdaValue.
595   template <typename T>
596   struct LambdaValue<Transformation<T>> {
597     //! For Transformations, \c type is the largest BitSet available,
598     //! representing the image set of the Transformations.
599     static constexpr size_t N = BitSet<1>::max_size();
600     using type                = BitSet<N>;
601   };
602 
603   // Benchmarks indicate that using std::vector yields similar performance to
604   // using StaticVector1's.
605   //! Specialization of the adapter RhoValue for instances of Transformation.
606   //!
607   //! \sa RhoValue.
608   template <typename T>
609   struct RhoValue<Transformation<T>> {
610     //! For Transformation<T>s, \c type is std::vector<T>, representing the
611     //! kernel of the Transformations.
612     using type = std::vector<T>;
613   };
614 
615   // T = std::vector or StaticVector1
616   //! Specialization of the adapter Lambda for instances of Transformation and
617   //! std::vector.
618   //!
619   //! \sa Lambda.
620   template <typename S, typename T>
621   struct Lambda<Transformation<S>, T> {
622     // not noexcept because std::vector::resize isn't (although
623     // StaticVector1::resize is).
624     //! Modifies \p res to contain the image set of \p x; that is, \p res[i]
625     //! will be \c true if and only if `x[j] = i` for some \f$j\f$.
operator ()libsemigroups::Lambda626     void operator()(T& res, Transformation<S> const& x) const {
627       res.clear();
628       res.resize(x.degree());
629       for (size_t i = 0; i < res.size(); ++i) {
630         res[i] = x[i];
631       }
632       std::sort(res.begin(), res.end());
633       res.erase(std::unique(res.begin(), res.end()), res.end());
634     }
635   };
636 
637   //! Specialization of the adapter Lambda for instances of Transformation and
638   //! BitSet<N>.
639   //!
640   //! \sa Lambda.
641   template <typename T, size_t N>
642   struct Lambda<Transformation<T>, BitSet<N>> {
643     // not noexcept because it can throw
644     //! Modifies \p res to contain the image set of \p x; that is, \p res[i]
645     //! will be \c true if and only if `x[j] = i` for some \f$j\f$.
operator ()libsemigroups::Lambda646     void operator()(BitSet<N>& res, Transformation<T> const& x) const {
647       if (x.degree() > N) {
648         LIBSEMIGROUPS_EXCEPTION(
649             "expected a transformation of degree at most %llu, found %llu",
650             static_cast<uint64_t>(N),
651             static_cast<uint64_t>(x.degree()));
652       }
653       res.reset();
654       for (size_t i = 0; i < x.degree(); ++i) {
655         res.set(x[i]);
656       }
657     }
658   };
659 
660   // T = std::vector<S> or StaticVector1<S, N>
661   //! Specialization of the adapter Rho for instances of Transformation and
662   //! std::vector.
663   //!
664   //! \sa Rho.
665   template <typename S, typename T>
666   struct Rho<Transformation<S>, T> {
667     // not noexcept because std::vector::resize isn't (although
668     // StaticVector1::resize is).
operator ()libsemigroups::Rho669     void operator()(T& res, Transformation<S> const& x) const {
670       res.clear();
671       res.resize(x.degree());
672       static thread_local std::vector<S> buf;
673       buf.clear();
674       buf.resize(x.degree(), S(UNDEFINED));
675       S next = 0;
676 
677       for (size_t i = 0; i < res.size(); ++i) {
678         if (buf[x[i]] == UNDEFINED) {
679           buf[x[i]] = next++;
680         }
681         res[i] = buf[x[i]];
682       }
683     }
684   };
685 
686   //! Specialization of the adapter Rank for instances of Transformation.
687   //!
688   //! \sa Rank.
689   template <typename T>
690   struct Rank<Transformation<T>> {
operator ()libsemigroups::Rank691     size_t operator()(Transformation<T> const& x) {
692       return x.crank();
693     }
694   };
695 
696   ////////////////////////////////////////////////////////////////////////
697   // ImageRightAction - Permutation specialisation
698   ////////////////////////////////////////////////////////////////////////
699 
700   //! Specialization of the adapter ImageRightAction for pointers to
701   //! Permutation instances.
702   //!
703   //! \sa ImageRightAction.
704   template <typename TValueType>
705   struct ImageRightAction<
706       Permutation<TValueType>*,
707       TValueType,
708       typename std::enable_if<std::is_integral<TValueType>::value>::type> {
709     //! Returns the image of \p pt under \p x.
operator ()libsemigroups::ImageRightAction710     TValueType operator()(Permutation<TValueType> const* x,
711                           TValueType const               pt) {
712       return (*x)[pt];
713     }
714   };
715 
716   //! Specialization of the adapter ImageRightAction for instances of
717   //! Permutation.
718   //!
719   //! \sa ImageRightAction.
720   template <typename TIntType>
721   struct ImageRightAction<
722       Permutation<TIntType>,
723       TIntType,
724       typename std::enable_if<std::is_integral<TIntType>::value>::type> {
725     //! Stores the image of \p pt under the action of \p p in \p res.
operator ()libsemigroups::ImageRightAction726     void operator()(TIntType&                    res,
727                     TIntType const&              pt,
728                     Permutation<TIntType> const& p) const noexcept {
729       LIBSEMIGROUPS_ASSERT(pt < p.degree());
730       res = p[pt];
731     }
732 
733     //! Returns the image of \p pt under the action of \p p.
operator ()libsemigroups::ImageRightAction734     TIntType operator()(TIntType const pt, Permutation<TIntType> const& x) {
735       return x[pt];
736     }
737   };
738 
739   ////////////////////////////////////////////////////////////////////////
740   // ImageRight/LeftAction - BooleanMat
741   ////////////////////////////////////////////////////////////////////////
742 
743   // T = StaticVector1<BitSet<N>, N> or std::vector<BitSet<N>>
744   // Possibly further container when value_type is BitSet.
745   //! Specialization of the adapter ImageRightAction for instances of
746   //! BooleanMat and vectors of BitSet<N> (\f$0 \leq N leq 64\f$).
747   //!
748   //! \sa ImageRightAction
749   template <typename T>
750   struct ImageRightAction<BooleanMat, T> {
751     // not noexcept because BitSet<N>::apply isn't
752     //! Stores the image of \p pt under the right action of \p p in \p res.
operator ()libsemigroups::ImageRightAction753     void operator()(T& res, T const& pt, BooleanMat const& x) const {
754       using value_type = typename T::value_type;
755       // TODO(later):
756       // static_assert(std::is_same<BitSet, value_type>::value(),
757       //              "the template paramater T must be a container of
758       //              BitSet's");
759       // The above doesn't work because BitSet requires template parameters
760       res.clear();
761 
762       for (auto const& v : pt) {
763         value_type cup;
764         cup.reset();
765         v.apply([&x, &cup](size_t i) {
766           for (size_t j = 0; j < x.degree(); ++j) {
767             cup.set(j, cup[j] || x[i * x.degree() + j]);
768           }
769         });
770         res.push_back(std::move(cup));
771       }
772       booleanmat_helpers::rows_basis<T>(res);
773     }
774   };
775 
776   //! Specialization of the adapter ImageRightAction for instances of
777   //! BooleanMat and std::vector<std::vector<bool>>.
778   //!
779   //! \sa ImageRightAction
780   template <>
781   struct ImageRightAction<BooleanMat, std::vector<std::vector<bool>>> {
782     // not noexcept because the constructor of std::vector isn't
783     //! Stores the image of \p pt under the right action of \p p in \p res.
operator ()libsemigroups::ImageRightAction784     void operator()(std::vector<std::vector<bool>>&       res,
785                     std::vector<std::vector<bool>> const& pt,
786                     BooleanMat const&                     x) const {
787       res.clear();
788 
789       for (auto it = pt.cbegin(); it < pt.cend(); ++it) {
790         std::vector<bool> cup(x.degree(), false);
791         for (size_t i = 0; i < x.degree(); ++i) {
792           if ((*it)[i]) {
793             for (size_t j = 0; j < x.degree(); ++j) {
794               cup[j] = cup[j] || x[i * x.degree() + j];
795             }
796           }
797         }
798         res.push_back(std::move(cup));
799       }
800       booleanmat_helpers::rows_basis(res);
801     }
802   };
803 
804   //! Specialization of the adapter ImageLeftAction for instances of
805   //! BooleanMat and std::vector<std::vector<bool>> or std::vector<BitSet<N>>
806   //! (\f$0 \leq N leq 64\f$).
807   //!
808   //! \sa ImageLeftAction
809   template <typename T>
810   struct ImageLeftAction<BooleanMat, T> {
811     //! Stores the image of \p pt under the left action of \p p in \p res.
operator ()libsemigroups::ImageLeftAction812     void operator()(T& res, T const& pt, BooleanMat const& x) const {
813       const_cast<BooleanMat*>(&x)->transpose_in_place();
814       // What if ImageRightAction throws?
815       ImageRightAction<BooleanMat, T>()(res, pt, x);
816       const_cast<BooleanMat*>(&x)->transpose_in_place();
817     }
818   };
819 
820   ////////////////////////////////////////////////////////////////////////
821   // Lambda/Rho - BooleanMat
822   ////////////////////////////////////////////////////////////////////////
823 
824   // This currently limits the use of Konieczny to matrices of dimension at
825   // most 64 with the default traits class, since we cannot know the dimension
826   // of the matrices at compile time, only at run time.
827   //! Specialization of the adapter LambdaValue for instances of BooleanMat.
828   //! Note that the the type chosen here limits the Konieczny algorithm to
829   //! BooleanMats of degree at most 64 (or 32 on 32-bit systems).
830   //!
831   //! \sa LambdaValue.
832   template <>
833   struct LambdaValue<BooleanMat> {
834     static constexpr size_t N = BitSet<1>::max_size();
835     //! For BooleanMats, \c type is StaticVector1<BitSet<N>, N>, where \c N is
836     //! the maximum width of BitSet on the system. This represents the row space
837     //! basis of the BooleanMats.
838     using type = detail::StaticVector1<BitSet<N>, N>;
839   };
840 
841   //! Specialization of the adapter RhoValue for instances of BooleanMat.
842   //! Note that the the type chosen here limits the Konieczny algorithm to
843   //! BooleanMats of degree at most 64 (or 32 on 32-bit systems).
844   //!
845   //! \sa RhoValue.
846   template <>
847   struct RhoValue<BooleanMat> {
848     //! For BooleanMats, \c type is StaticVector1<BitSet<N>, N>, where \c N is
849     //! the maximum width of BitSet on the system. This represents the column
850     //! space basis of the BooleanMats.
851     using type = typename LambdaValue<BooleanMat>::type;
852   };
853 
854   // Benchmarks show that the following is the fastest (i.e. duplicating the
855   // code from ImageRightAction, and using StaticVector1).
856   // T = StaticVector1<BitSet>, std::vector<BitSet>,
857   // StaticVector1<std::bitset>, std::vector<std::bitset>
858   //! Specialization of the adapter Lambda for instances of Transformation and
859   //! std::vector<BitSet<N>> or StaticVector1<BitSet<N>>.
860   //!
861   //! \sa Lambda.
862   template <typename T>
863   struct Lambda<BooleanMat, T> {
864     //! Modifies \p res to contain the row space basis of \p x.
operator ()libsemigroups::Lambda865     void operator()(T& res, BooleanMat const& x) const {
866       using S        = typename T::value_type;
867       size_t const N = S().size();
868       if (x.degree() > N) {
869         LIBSEMIGROUPS_EXCEPTION(
870             "expected matrix of dimension at most %llu, found %llu",
871             static_cast<uint64_t>(N),
872             static_cast<uint64_t>(x.degree()));
873       }
874       res.clear();
875       for (size_t i = 0; i < x.degree(); ++i) {
876         S cup;
877         cup.reset();
878         for (size_t j = 0; j < x.degree(); ++j) {
879           cup.set(j, x[i * x.degree() + j]);
880         }
881         res.push_back(std::move(cup));
882       }
883       booleanmat_helpers::rows_basis<T>(res);
884     }
885   };
886 
887   // T = StaticVector1<BitSet>, std::vector<BitSet>,
888   // StaticVector1<std::bitset>, std::vector<std::bitset>
889   //! Specialization of the adapter Rho for instances of Transformation and
890   //! std::vector<BitSet<N>> or StaticVector1<BitSet<N>>.
891   //!
892   //! \sa Lambda.
893   template <typename T>
894   struct Rho<BooleanMat, T> {
895     //! Modifies \p res to contain the column space basis of \p x.
operator ()libsemigroups::Rho896     void operator()(T& res, BooleanMat const& x) const noexcept {
897       const_cast<BooleanMat*>(&x)->transpose_in_place();
898       Lambda<BooleanMat, T>()(res, x);
899       const_cast<BooleanMat*>(&x)->transpose_in_place();
900     }
901   };
902 
903   ////////////////////////////////////////////////////////////////////////
904   // Rank - BooleanMat
905   ////////////////////////////////////////////////////////////////////////
906 
907   // Version of ImageRightAction where we convert the matrix to bitsets
908   //! Specialization of the adapter ImageRightAction for instances of
909   //! BooleanMat and BitSet<N>.
910   //!
911   //! \sa ImageRightAction.
912   template <size_t N>
913   struct ImageRightAction<BooleanMat, BitSet<N>> {
914     using result_type = BitSet<N>;
915     //! Stores the image of \p pt under the right action of \p p in \p res.
operator ()libsemigroups::ImageRightAction916     void operator()(result_type&       res,
917                     result_type const& pt,
918                     BooleanMat const&  x) const {
919       static thread_local detail::StaticVector1<BitSet<N>, N> x_rows;
920       x_rows.clear();
921       for (size_t i = 0; i < x.degree(); ++i) {
922         BitSet<N> row(0);
923         for (size_t j = 0; j < x.degree(); ++j) {
924           if (x[i * x.degree() + j]) {
925             row.set(j, true);
926           }
927         }
928         x_rows.push_back(std::move(row));
929       }
930       res.reset();
931       pt.apply([&res](size_t i) { res |= x_rows[i]; });
932     }
933   };
934 
935   //! Specialization of the adapter RankState for instances of BooleanMat.
936   //!
937   //! \sa RankState.
938   template <>
939   class RankState<BooleanMat> {
940     using MaxBitSet = BitSet<BitSet<1>::max_size()>;
941 
942    public:
943     //! The additional data used by Rank<BooleanMat> is the orbit of the rows of
944     //! the semigroup.
945     using type = RightAction<BooleanMat,
946                              MaxBitSet,
947                              ImageRightAction<BooleanMat, MaxBitSet>>;
948 
949     //! Default constructor.
RankState()950     RankState() : _orb() {}
951 
952     //! Construct from const iterators to the generators of the semigroup.
953     template <typename T>
RankState(T first,T last)954     RankState(T first, T last) : _orb() {
955       static thread_local std::vector<MaxBitSet> seeds;
956       LIBSEMIGROUPS_ASSERT(_orb.empty());
957       if (std::distance(first, last) == 0) {
958         LIBSEMIGROUPS_EXCEPTION(
959             "expected a positive number of generators in the second argument");
960       }
961       for (auto it = first; it < last; ++it) {
962         _orb.add_generator(*it);
963       }
964       for (size_t i = 0; i < first->degree(); ++i) {
965         MaxBitSet seed(0);
966         seed.set(i, true);
967         _orb.add_seed(seed);
968       }
969     }
970 
971     // Other constructors are deleted.
972     RankState(RankState const&) = delete;
973     RankState(RankState&&)      = delete;
974     RankState& operator=(RankState const&) = delete;
975     RankState& operator=(RankState&&) = delete;
976 
977     //! Returns the row orbit.
get() const978     type const& get() const {
979       _orb.run();
980       LIBSEMIGROUPS_ASSERT(_orb.finished());
981       return _orb;
982     }
983 
984    private:
985     mutable type _orb;
986   };
987 
988   //! Specialization of the adapter Rank for instances of BooleanMat
989   //!
990   //! \sa Rank.
991   template <>
992   struct Rank<BooleanMat> {
993     //! Returns the rank of \p x.
994     //!
995     //! The rank of a BooleanMat may be defined as the rank of the
996     //! Transformation obtained via the action of the BooleanMat on the row
997     //! orbit of the semigroup.
operator ()libsemigroups::Rank998     size_t operator()(RankState<BooleanMat> const& state,
999                       BooleanMat const&            x) const {
1000       using bitset_type = BitSet<BitSet<1>::max_size()>;
1001       using orb_type    = typename RankState<BooleanMat>::type;
1002       static thread_local std::vector<bool>        seen;
1003       static thread_local std::vector<bitset_type> x_rows;
1004       seen.clear();
1005       x_rows.clear();
1006       orb_type const& orb = state.get();
1007       LIBSEMIGROUPS_ASSERT(orb.finished());
1008       seen.resize(orb.current_size());
1009       for (size_t i = 0; i < x.degree(); ++i) {
1010         bitset_type row(0);
1011         for (size_t j = 0; j < x.degree(); ++j) {
1012           if (x[i * x.degree() + j]) {
1013             row.set(j, true);
1014           }
1015         }
1016         x_rows.push_back(std::move(row));
1017       }
1018       size_t rnk = 0;
1019       for (size_t i = 0; i < orb.current_size(); ++i) {
1020         bitset_type const& row = orb[i];
1021         bitset_type        cup;
1022         cup.reset();
1023         row.apply([&cup](size_t j) { cup |= x_rows[j]; });
1024         size_t pos = orb.position(cup);
1025         LIBSEMIGROUPS_ASSERT(pos != UNDEFINED);
1026         if (!seen[pos]) {
1027           rnk++;
1028           seen[pos] = true;
1029         }
1030       }
1031       return rnk;
1032     }
1033   };
1034 }  // namespace libsemigroups
1035 #endif  // LIBSEMIGROUPS_ELEMENT_ADAPTERS_HPP_
1036