1 //
2 // Semigroups package for GAP
3 // Copyright (C) 2016 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 // TODO(JDM) 1) if we use clear before resize maybe don't need fill
20 //           2) in-place product
21 
22 #include "bipart.h"
23 
24 #include <algorithm>
25 #include <mutex>
26 #include <string>
27 #include <thread>
28 #include <utility>
29 #include <vector>
30 
31 #include "src/compiled.h"
32 
33 #include "libsemigroups/blocks.hpp"
34 #include "libsemigroups/froidure-pin.hpp"
35 
36 using libsemigroups::Blocks;
37 using libsemigroups::Element;
38 using libsemigroups::REPORTER;
39 using libsemigroups::detail::Timer;
40 
41 // Global variables
42 
43 static std::vector<size_t> _BUFFER_size_t;
44 static std::vector<bool>   _BUFFER_bool;
45 
46 // A T_BIPART Obj in GAP is of the form:
47 //
48 //   [pointer to C++ bipartition, left blocks Obj, right blocks Obj]
49 
50 // Create a new GAP bipartition Obj from a C++ Bipartition pointer.
51 
bipart_new_obj(Bipartition * x)52 Obj bipart_new_obj(Bipartition* x) {
53   size_t deg = x->degree() + 1;
54   if (deg > static_cast<size_t>(LEN_PLIST(TYPES_BIPART))
55       || ELM_PLIST(TYPES_BIPART, deg) == 0) {
56     CALL_1ARGS(TYPE_BIPART, INTOBJ_INT(deg - 1));
57   }
58 
59   Obj o          = NewBag(T_BIPART, 3 * sizeof(Obj));
60   ADDR_OBJ(o)[0] = reinterpret_cast<Obj>(x);
61   return o;
62 }
63 
64 // A T_BLOCKS Obj in GAP is of the form:
65 //
66 //   [pointer to C++ blocks]
67 
68 // Returns the pointer to the C++ blocks object from the GAP bipartition
69 // object.
70 
71 // Create a new GAP blocks Obj from a C++ Blocks pointer.
72 
blocks_new_obj(Blocks * x)73 inline Obj blocks_new_obj(Blocks* x) {
74   Obj o          = NewBag(T_BLOCKS, 1 * sizeof(Obj));
75   ADDR_OBJ(o)[0] = reinterpret_cast<Obj>(x);
76   return o;
77 }
78 
79 ////////////////////////////////////////////////////////////////////////////////
80 ////////////////////////////////////////////////////////////////////////////////
81 // Bipartitions
82 ////////////////////////////////////////////////////////////////////////////////
83 ////////////////////////////////////////////////////////////////////////////////
84 
85 ////////////////////////////////////////////////////////////////////////////////
86 // GAP level functions
87 ////////////////////////////////////////////////////////////////////////////////
88 
89 // Create a bipartition from the list gap_blocks. The argument gap_blocks must
90 // be a list which either represents:
91 //
92 //    * the internal rep of a bipartition (list of positive integers such that
93 //      gap_blocks[i] is the index of the class containing i); or
94 //
95 //    * the external rep of a bipartition (a list of lists of ints that
96 //    partition [-n ... -1] union [1 .. n].
97 //
98 // BIPART_NC does only minimal checks, and doesn't fully check if its argument
99 // is valid.
100 
BIPART_NC(Obj self,Obj gap_blocks)101 Obj BIPART_NC(Obj self, Obj gap_blocks) {
102   SEMIGROUPS_ASSERT(IS_LIST(gap_blocks));
103   std::vector<u_int32_t> blocks;
104 
105   size_t degree         = 0;
106   size_t nr_left_blocks = 0;
107   size_t nr_blocks      = 0;
108 
109   if (LEN_LIST(gap_blocks) != 0) {
110     if (IS_LIST(ELM_LIST(gap_blocks, 1))) {  // gap_blocks is a list of lists
111       nr_blocks = LEN_LIST(gap_blocks);
112       for (size_t i = 1; i <= nr_blocks; i++) {
113         SEMIGROUPS_ASSERT(IS_LIST(ELM_LIST(gap_blocks, i)));
114         degree += LEN_LIST(ELM_LIST(gap_blocks, i));
115       }
116       blocks.resize(degree);
117 
118       degree /= 2;
119 
120       for (size_t i = 1; i <= nr_blocks; i++) {
121         Obj block = ELM_LIST(gap_blocks, i);
122         for (size_t j = 1; j <= static_cast<size_t>(LEN_LIST(block)); j++) {
123           SEMIGROUPS_ASSERT(IS_INTOBJ(ELM_LIST(block, j)));
124           int jj = INT_INTOBJ(ELM_LIST(block, j));
125           if (jj < 0) {
126             blocks[-jj + degree - 1] = i - 1;
127           } else {
128             nr_left_blocks = i;
129             blocks[jj - 1] = i - 1;
130           }
131         }
132       }
133     } else {  // gap_blocks is the internal rep of a bipartition
134       blocks.reserve(LEN_LIST(gap_blocks));
135       for (size_t i = 1; i <= static_cast<size_t>(LEN_LIST(gap_blocks)) / 2;
136            i++) {
137         SEMIGROUPS_ASSERT(IS_INTOBJ(ELM_LIST(gap_blocks, i))
138                           && INT_INTOBJ(ELM_LIST(gap_blocks, i)) > 0);
139         u_int32_t index = INT_INTOBJ(ELM_LIST(gap_blocks, i)) - 1;
140         blocks.push_back(index);
141         nr_blocks = (index > nr_blocks ? index : nr_blocks);
142       }
143       nr_left_blocks = nr_blocks + 1;
144       for (size_t i = (static_cast<size_t>(LEN_LIST(gap_blocks)) / 2) + 1;
145            i <= static_cast<size_t>(LEN_LIST(gap_blocks));
146            i++) {
147         SEMIGROUPS_ASSERT(IS_INTOBJ(ELM_LIST(gap_blocks, i))
148                           && INT_INTOBJ(ELM_LIST(gap_blocks, i)) > 0);
149         u_int32_t index = INT_INTOBJ(ELM_LIST(gap_blocks, i)) - 1;
150         blocks.push_back(index);
151         nr_blocks = (index > nr_blocks ? index : nr_blocks);
152       }
153       nr_blocks++;
154     }
155   }
156 
157   // construct C++ object
158   Bipartition* x = new Bipartition(blocks);
159   x->set_nr_left_blocks(nr_left_blocks);
160   x->set_nr_blocks(nr_blocks);
161 
162   return bipart_new_obj(x);
163 }
164 
165 // Returns the external rep of a GAP bipartition, see description before
166 // BIPART_NC for more details.
167 
BIPART_EXT_REP(Obj self,Obj x)168 Obj BIPART_EXT_REP(Obj self, Obj x) {
169   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
170 
171   Bipartition* xx = bipart_get_cpp(x);
172   size_t       n  = xx->degree();
173 
174   Obj ext_rep
175       = NEW_PLIST(n == 0 ? T_PLIST_EMPTY : T_PLIST_TAB, xx->nr_blocks());
176   SET_LEN_PLIST(ext_rep, (Int) xx->nr_blocks());
177 
178   for (size_t i = 0; i < 2 * n; i++) {
179     Obj entry = INTOBJ_INT((i < n ? i + 1 : -(i - n) - 1));
180     if (ELM_PLIST(ext_rep, xx->at(i) + 1) == 0) {
181       Obj block = NEW_PLIST(T_PLIST_CYC, 1);
182       SET_LEN_PLIST(block, 1);
183       SET_ELM_PLIST(block, 1, entry);
184       SET_ELM_PLIST(ext_rep, xx->at(i) + 1, block);
185       CHANGED_BAG(ext_rep);
186     } else {
187       Obj block = ELM_PLIST(ext_rep, xx->at(i) + 1);
188       AssPlist(block, LEN_PLIST(block) + 1, entry);
189     }
190   }
191   MakeImmutable(ext_rep);
192   return ext_rep;
193 }
194 
195 // Returns the internal rep of a GAP bipartition, see description before
196 // BIPART_NC for more details.
197 
BIPART_INT_REP(Obj self,Obj x)198 Obj BIPART_INT_REP(Obj self, Obj x) {
199   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
200 
201   Bipartition* xx = bipart_get_cpp(x);  // get C++ bipartition pointer
202   size_t       n  = xx->degree();
203 
204   Obj int_rep = NEW_PLIST_IMM(n == 0 ? T_PLIST_EMPTY : T_PLIST_CYC, 2 * n);
205   SET_LEN_PLIST(int_rep, (Int) 2 * n);
206 
207   for (size_t i = 0; i < 2 * n; i++) {
208     SET_ELM_PLIST(int_rep, i + 1, INTOBJ_INT(xx->at(i) + 1));
209   }
210   return int_rep;
211 }
212 
213 // Returns the hash value for a GAP bipartition from the C++ object.
214 
BIPART_HASH(Obj self,Obj x,Obj data)215 Obj BIPART_HASH(Obj self, Obj x, Obj data) {
216   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
217   SEMIGROUPS_ASSERT(IS_INTOBJ(data));
218 
219   return INTOBJ_INT((bipart_get_cpp(x)->hash_value() % INT_INTOBJ(data)) + 1);
220 }
221 
222 // Returns the degree of a GAP bipartition from the C++ object. A bipartition
223 // is of degree n if it is defined on [-n .. -1] union [1 .. n].
224 
BIPART_DEGREE(Obj self,Obj x)225 Obj BIPART_DEGREE(Obj self, Obj x) {
226   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
227 
228   return INTOBJ_INT(bipart_get_cpp(x)->degree());
229 }
230 
231 // Returns the number of blocks in the bipartition from the C++ object.
232 
BIPART_NR_BLOCKS(Obj self,Obj x)233 Obj BIPART_NR_BLOCKS(Obj self, Obj x) {
234   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
235 
236   return INTOBJ_INT(bipart_get_cpp(x)->nr_blocks());
237 }
238 
239 // Returns the number of blocks containing positive integers in the GAP
240 // bipartition from the C++ object.
241 
BIPART_NR_LEFT_BLOCKS(Obj self,Obj x)242 Obj BIPART_NR_LEFT_BLOCKS(Obj self, Obj x) {
243   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
244 
245   return INTOBJ_INT(bipart_get_cpp(x)->nr_left_blocks());
246 }
247 
248 // Returns the number of blocks both positive and negative integers in the GAP
249 // bipartition from the C++ object.
250 
BIPART_RANK(Obj self,Obj x,Obj dummy)251 Obj BIPART_RANK(Obj self, Obj x, Obj dummy) {
252   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
253 
254   return INTOBJ_INT(bipart_get_cpp(x)->rank());
255 }
256 
257 // Returns the product of the GAP bipartitions x and y as a new GAP
258 // bipartition.
259 
BIPART_PROD(Obj x,Obj y)260 Obj BIPART_PROD(Obj x, Obj y) {
261   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
262   SEMIGROUPS_ASSERT(TNUM_OBJ(y) == T_BIPART);
263 
264   Bipartition* xx = bipart_get_cpp(x);
265   Bipartition* yy = bipart_get_cpp(y);
266 
267   Element* z = new Bipartition(xx->degree());
268   z->redefine(xx, yy);
269 
270   return bipart_new_obj(static_cast<Bipartition*>(z));
271 }
272 
273 // Check if the GAP bipartitions x and y are equal.
274 
BIPART_EQ(Obj x,Obj y)275 Int BIPART_EQ(Obj x, Obj y) {
276   return (*bipart_get_cpp(x) == *bipart_get_cpp(y) ? 1L : 0L);
277 }
278 
279 // Check if x < y for the GAP bipartitions x and y.
280 
BIPART_LT(Obj x,Obj y)281 Int BIPART_LT(Obj x, Obj y) {
282   return (*bipart_get_cpp(x) < *bipart_get_cpp(y) ? 1L : 0L);
283 }
284 
285 // Returns the permutation of the indices of the transverse blocks of (x ^ * y)
286 // induced by (x ^ * y). Assumes that the left and right blocks of x and y are
287 // equal.
288 
BIPART_PERM_LEFT_QUO(Obj self,Obj x,Obj y)289 Obj BIPART_PERM_LEFT_QUO(Obj self, Obj x, Obj y) {
290   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
291   SEMIGROUPS_ASSERT(TNUM_OBJ(y) == T_BIPART);
292 
293 // The following is done to avoid leaking memory
294 #ifdef SEMIGROUPS_KERNEL_DEBUG
295   Blocks* xb = bipart_get_cpp(x)->left_blocks();
296   Blocks* yb = bipart_get_cpp(y)->left_blocks();
297   SEMIGROUPS_ASSERT(*xb == *yb);
298   delete xb;
299   delete yb;
300   xb = bipart_get_cpp(x)->right_blocks();
301   yb = bipart_get_cpp(y)->right_blocks();
302   SEMIGROUPS_ASSERT(*xb == *yb);
303   delete xb;
304   delete yb;
305 #endif
306 
307   size_t deg  = bipart_get_cpp(x)->degree();
308   Obj    p    = NEW_PERM4(deg);
309   UInt4* ptrp = ADDR_PERM4(p);
310 
311   Bipartition* xx = bipart_get_cpp(x);
312   Bipartition* yy = bipart_get_cpp(y);
313 
314   SEMIGROUPS_ASSERT(xx->degree() == yy->degree());
315 
316   // find indices of right blocks of <x>
317   size_t index = 0;
318   _BUFFER_size_t.clear();
319   _BUFFER_size_t.resize(2 * deg, -1);
320 
321   for (size_t i = deg; i < 2 * deg; i++) {
322     if (_BUFFER_size_t[xx->at(i)] == static_cast<size_t>(-1)) {
323       _BUFFER_size_t[xx->at(i)] = index;
324       index++;
325     }
326     ptrp[i - deg] = i - deg;
327   }
328 
329   for (size_t i = deg; i < 2 * deg; i++) {
330     if (yy->at(i) < xx->nr_left_blocks()) {
331       ptrp[_BUFFER_size_t[yy->at(i)]] = _BUFFER_size_t[xx->at(i)];
332     }
333   }
334   return p;
335 }
336 
337 // Returns the GAP bipartition xx ^ *.
338 
BIPART_LEFT_PROJ(Obj self,Obj x)339 Obj BIPART_LEFT_PROJ(Obj self, Obj x) {
340   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
341 
342   Bipartition* xx = bipart_get_cpp(x);
343 
344   size_t deg  = xx->degree();
345   size_t next = xx->nr_left_blocks();
346 
347   std::fill(_BUFFER_size_t.begin(),
348             std::min(_BUFFER_size_t.end(), _BUFFER_size_t.begin() + 2 * deg),
349             -1);
350   _BUFFER_size_t.resize(2 * deg, -1);
351 
352   std::vector<u_int32_t> blocks(2 * deg, -1);
353 
354   for (size_t i = 0; i < deg; i++) {
355     blocks[i] = xx->at(i);
356     if (xx->is_transverse_block(xx->at(i))) {
357       blocks[i + deg] = xx->at(i);
358     } else if (_BUFFER_size_t[xx->at(i)] != static_cast<size_t>(-1)) {
359       blocks[i + deg] = _BUFFER_size_t[xx->at(i)];
360     } else {
361       _BUFFER_size_t[xx->at(i)] = next;
362       blocks[i + deg]           = next;
363       next++;
364     }
365   }
366 
367   Bipartition* out = new Bipartition(blocks);
368   out->set_nr_blocks(next);
369   return bipart_new_obj(out);
370 }
371 
372 // Returns the GAP bipartition x ^ *x.
373 
BIPART_RIGHT_PROJ(Obj self,Obj x)374 Obj BIPART_RIGHT_PROJ(Obj self, Obj x) {
375   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
376 
377   Bipartition* xx = bipart_get_cpp(x);
378 
379   size_t deg     = xx->degree();
380   size_t l_block = 0;
381   size_t r_block = xx->nr_right_blocks();
382 
383   _BUFFER_size_t.clear();
384   _BUFFER_size_t.resize(4 * deg, -1);
385   auto buf1 = _BUFFER_size_t.begin();
386   auto buf2 = _BUFFER_size_t.begin() + 2 * deg;
387 
388   std::vector<u_int32_t> blocks(2 * deg, -1);
389 
390   for (size_t i = deg; i < 2 * deg; i++) {
391     if (buf2[xx->at(i)] == static_cast<size_t>(-1)) {
392       if (xx->is_transverse_block(xx->at(i))) {
393         buf2[xx->at(i)] = buf1[xx->at(i)] = l_block++;
394       } else {
395         buf2[xx->at(i)] = r_block++;
396         buf1[xx->at(i)] = l_block++;
397       }
398     }
399     blocks[i - deg] = buf1[xx->at(i)];
400     blocks[i]       = buf2[xx->at(i)];
401   }
402 
403   Bipartition* out = new Bipartition(blocks);
404   out->set_nr_blocks(r_block);
405   return bipart_new_obj(out);
406 }
407 
408 // Returns the GAP bipartition x ^ *.
409 
BIPART_STAR(Obj self,Obj x)410 Obj BIPART_STAR(Obj self, Obj x) {
411   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
412 
413   Bipartition* xx  = bipart_get_cpp(x);
414   size_t       deg = xx->degree();
415 
416   std::fill(_BUFFER_size_t.begin(),
417             std::min(_BUFFER_size_t.end(), _BUFFER_size_t.begin() + 2 * deg),
418             -1);
419   _BUFFER_size_t.resize(2 * deg, -1);
420 
421   std::vector<u_int32_t> blocks(2 * deg, -1);
422 
423   size_t next = 0;
424 
425   for (size_t i = 0; i < deg; i++) {
426     if (_BUFFER_size_t[xx->at(i + deg)] != static_cast<size_t>(-1)) {
427       blocks[i] = _BUFFER_size_t[xx->at(i + deg)];
428     } else {
429       _BUFFER_size_t[xx->at(i + deg)] = next;
430       blocks[i]                       = next;
431       next++;
432     }
433   }
434 
435   size_t nr_left = next;
436 
437   for (size_t i = 0; i < deg; i++) {
438     if (_BUFFER_size_t[xx->at(i)] != static_cast<size_t>(-1)) {
439       blocks[i + deg] = _BUFFER_size_t[xx->at(i)];
440     } else {
441       _BUFFER_size_t[xx->at(i)] = next;
442       blocks[i + deg]           = next;
443       next++;
444     }
445   }
446 
447   Bipartition* out = new Bipartition(blocks);
448   out->set_nr_blocks(next);
449   out->set_nr_left_blocks(nr_left);
450 
451   return bipart_new_obj(out);
452 }
453 
454 // Returns a permutation mapping the indices of the right transverse blocks of
455 // x and to the right transverse blocks of y. Assumes that x and y have equal
456 // left blocks.
457 
BIPART_LAMBDA_CONJ(Obj self,Obj x,Obj y)458 Obj BIPART_LAMBDA_CONJ(Obj self, Obj x, Obj y) {
459   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
460   SEMIGROUPS_ASSERT(TNUM_OBJ(y) == T_BIPART);
461 
462   Bipartition* xx = bipart_get_cpp(x);
463   Bipartition* yy = bipart_get_cpp(y);
464 
465 #ifdef SEMIGROUPS_KERNEL_DEBUG
466   Blocks* xb = xx->left_blocks();
467   Blocks* yb = yy->left_blocks();
468   SEMIGROUPS_ASSERT(*xb == *yb);
469   delete xb;
470   delete yb;
471 #endif
472 
473   size_t deg            = xx->degree();
474   size_t nr_left_blocks = xx->nr_left_blocks();
475   size_t nr_blocks      = std::max(xx->nr_blocks(), yy->nr_blocks());
476 
477   _BUFFER_bool.clear();
478   _BUFFER_bool.resize(3 * nr_blocks);
479   auto seen = _BUFFER_bool.begin();
480   auto src  = seen + nr_blocks;
481   auto dst  = src + nr_blocks;
482 
483   _BUFFER_size_t.clear();
484   _BUFFER_size_t.resize(nr_left_blocks);
485   auto   lookup = _BUFFER_size_t.begin();
486   size_t next   = 0;
487 
488   for (size_t i = deg; i < 2 * deg; i++) {
489     if (!seen[yy->at(i)]) {
490       seen[yy->at(i)] = true;
491       if (yy->at(i) < nr_left_blocks) {  // connected block
492         lookup[yy->at(i)] = next;
493       }
494       next++;
495     }
496   }
497 
498   std::fill(_BUFFER_bool.begin(), _BUFFER_bool.begin() + nr_blocks, false);
499 
500   Obj    p    = NEW_PERM4(nr_blocks);
501   UInt4* ptrp = ADDR_PERM4(p);
502   next        = 0;
503 
504   for (size_t i = deg; i < 2 * deg; i++) {
505     if (!seen[xx->at(i)]) {
506       seen[xx->at(i)] = true;
507       if (xx->at(i) < nr_left_blocks) {  // connected block
508         ptrp[next]             = lookup[xx->at(i)];
509         src[next]              = true;
510         dst[lookup[xx->at(i)]] = true;
511       }
512       next++;
513     }
514   }
515 
516   size_t j = 0;
517   for (size_t i = 0; i < nr_blocks; i++) {
518     if (!src[i]) {
519       while (dst[j]) {
520         j++;
521       }
522       ptrp[i] = j;
523       j++;
524     }
525   }
526   return p;
527 }
528 
529 // Returns a GAP bipartition y such that the right blocks of y are the right
530 // blocks of x permuted by p.
531 
BIPART_STAB_ACTION(Obj self,Obj x,Obj p)532 Obj BIPART_STAB_ACTION(Obj self, Obj x, Obj p) {
533   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
534 
535   // find the largest moved point of the permutation p
536   size_t pdeg;
537 
538   if (TNUM_OBJ(p) == T_PERM2) {
539     UInt2* ptr = ADDR_PERM2(p);
540     for (pdeg = DEG_PERM2(p); 1 <= pdeg; pdeg--) {
541       if (ptr[pdeg - 1] != pdeg - 1) {
542         break;
543       }
544     }
545   } else if (TNUM_OBJ(p) == T_PERM4) {
546     UInt4* ptr = ADDR_PERM4(p);
547     for (pdeg = DEG_PERM4(p); 1 <= pdeg; pdeg--) {
548       if (ptr[pdeg - 1] != pdeg - 1) {
549         break;
550       }
551     }
552   } else {
553     ErrorQuit("usage: <p> must be a perm (not a %s)", (Int) TNAM_OBJ(p), 0L);
554     return 0L;  // to keep the compiler happy
555   }
556 
557   if (pdeg == 0) {
558     return x;
559   }
560   Bipartition* xx = bipart_get_cpp(x);
561 
562   size_t deg       = xx->degree();
563   size_t nr_blocks = xx->nr_blocks();
564 
565   std::vector<u_int32_t> blocks(2 * deg);
566 
567   _BUFFER_size_t.clear();
568   _BUFFER_size_t.resize(2 * nr_blocks + std::max(deg, pdeg), -1);
569 
570   auto tab1 = _BUFFER_size_t.begin();
571   auto tab2 = _BUFFER_size_t.begin() + nr_blocks;
572   auto q    = tab2 + nr_blocks;  // the inverse of p
573 
574   if (TNUM_OBJ(p) == T_PERM2) {
575     UInt2* ptr = ADDR_PERM2(p);
576     UInt2  i;
577     for (i = 0; i < pdeg; i++) {
578       q[ptr[i]] = static_cast<size_t>(i);
579     }
580     for (; i < deg; i++) {
581       q[i] = static_cast<size_t>(i);
582     }
583   } else if (TNUM_OBJ(p) == T_PERM4) {
584     UInt4* ptr = ADDR_PERM4(p);
585     UInt4  i;
586     for (i = 0; i < pdeg; i++) {
587       q[ptr[i]] = static_cast<size_t>(i);
588     }
589     for (; i < deg; i++) {
590       q[i] = static_cast<size_t>(i);
591     }
592   }
593 
594   size_t next = 0;
595 
596   for (size_t i = deg; i < 2 * deg; i++) {
597     if (tab1[xx->at(i)] == static_cast<size_t>(-1)) {
598       tab1[xx->at(i)] = q[next];
599       tab2[next]      = xx->at(i);
600       next++;
601     }
602   }
603 
604   for (size_t i = 0; i < deg; i++) {
605     blocks[i]       = xx->at(i);
606     blocks[i + deg] = tab2[tab1[xx->at(i + deg)]];
607   }
608 
609   return bipart_new_obj(new Bipartition(blocks));
610 }
611 
612 // Returns the GAP Obj left block of the bipartition x. The left blocks are
613 // simply the subpartition of [1 .. n] induced by x.
614 
BIPART_LEFT_BLOCKS(Obj self,Obj x)615 Obj BIPART_LEFT_BLOCKS(Obj self, Obj x) {
616   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
617   if (ADDR_OBJ(x)[1] == NULL) {
618     Obj o          = blocks_new_obj(bipart_get_cpp(x)->left_blocks());
619     ADDR_OBJ(x)[1] = o;
620     CHANGED_BAG(x);
621   }
622   SEMIGROUPS_ASSERT(ADDR_OBJ(x)[1] != NULL);
623   SEMIGROUPS_ASSERT(TNUM_OBJ(ADDR_OBJ(x)[1]) == T_BLOCKS);
624   return ADDR_OBJ(x)[1];
625 }
626 
627 // Returns the GAP Obj right block of the bipartition x. The right blocks are
628 // simply the subpartition of [-n .. -1] induced by x.
629 
BIPART_RIGHT_BLOCKS(Obj self,Obj x)630 Obj BIPART_RIGHT_BLOCKS(Obj self, Obj x) {
631   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BIPART);
632   if (ADDR_OBJ(x)[2] == NULL) {
633     Obj o          = blocks_new_obj(bipart_get_cpp(x)->right_blocks());
634     ADDR_OBJ(x)[2] = o;
635     CHANGED_BAG(x);
636   }
637   SEMIGROUPS_ASSERT(ADDR_OBJ(x)[2] != NULL);
638   SEMIGROUPS_ASSERT(TNUM_OBJ(ADDR_OBJ(x)[2]) == T_BLOCKS);
639   return ADDR_OBJ(x)[2];
640 }
641 
642 ////////////////////////////////////////////////////////////////////////////////
643 ////////////////////////////////////////////////////////////////////////////////
644 // Blocks
645 ////////////////////////////////////////////////////////////////////////////////
646 ////////////////////////////////////////////////////////////////////////////////
647 
648 // Forward declarations, see below for the definition of these functions.
649 
650 static inline size_t fuse_it(size_t);
651 
652 static void fuse(u_int32_t,
653                  typename std::vector<u_int32_t>::const_iterator,
654                  u_int32_t,
655                  typename std::vector<u_int32_t>::const_iterator,
656                  u_int32_t,
657                  bool);
658 
659 ////////////////////////////////////////////////////////////////////////////////
660 // GAP-level functions
661 ////////////////////////////////////////////////////////////////////////////////
662 
663 // Create a GAP blocks object from the list gap_blocks. The argument gap_blocks
664 // must be a list which is the external rep of a GAP blocks object (a list of
665 // lists of integers such that the absolute values of these lists partition
666 // [1 .. n], transverse blocks are indicated by positive integers and
667 // non-transverse by negative integers).
668 //
669 // BLOCKS_NC does only minimal checks, and doesn't fully check if its argument
670 // is valid.
671 
BLOCKS_NC(Obj self,Obj gap_blocks)672 Obj BLOCKS_NC(Obj self, Obj gap_blocks) {
673   SEMIGROUPS_ASSERT(IS_LIST(gap_blocks));
674 
675   if (LEN_LIST(gap_blocks) == 0) {
676     return blocks_new_obj(new Blocks());
677   }
678 
679   SEMIGROUPS_ASSERT(IS_LIST(ELM_LIST(gap_blocks, 1)));
680 
681   size_t degree    = 0;
682   size_t nr_blocks = LEN_LIST(gap_blocks);
683 
684   for (size_t i = 1; i <= nr_blocks; i++) {
685     SEMIGROUPS_ASSERT(IS_LIST(ELM_LIST(gap_blocks, i)));
686     degree += LEN_LIST(ELM_LIST(gap_blocks, i));
687   }
688 
689   std::vector<u_int32_t>* blocks = new std::vector<u_int32_t>();
690   blocks->resize(degree);
691 
692   std::vector<bool>* lookup = new std::vector<bool>();
693   lookup->resize(nr_blocks);
694 
695   for (size_t i = 1; i <= nr_blocks; i++) {
696     Obj block = ELM_LIST(gap_blocks, i);
697     for (size_t j = 1; j <= static_cast<size_t>(LEN_LIST(block)); j++) {
698       SEMIGROUPS_ASSERT(IS_INTOBJ(ELM_LIST(block, j)));
699       int jj = INT_INTOBJ(ELM_LIST(block, j));
700       if (jj < 0) {
701         (*blocks)[-jj - 1] = i - 1;
702       } else {
703         (*blocks)[jj - 1] = i - 1;
704         (*lookup)[i - 1]  = true;
705       }
706     }
707   }
708 
709   return blocks_new_obj(new Blocks(blocks, lookup, nr_blocks));
710 }
711 
712 // Returns the external representation of a GAP blocks Obj, see the description
713 // before BLOCKS_NC for more details.
714 
BLOCKS_EXT_REP(Obj self,Obj x)715 Obj BLOCKS_EXT_REP(Obj self, Obj x) {
716   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BLOCKS);
717 
718   initRNams();
719 
720   Blocks* xx = blocks_get_cpp(x);
721   size_t  n  = xx->degree();
722 
723   Obj ext_rep
724       = NEW_PLIST(n == 0 ? T_PLIST_EMPTY : T_PLIST_TAB, xx->nr_blocks());
725   SET_LEN_PLIST(ext_rep, (Int) xx->nr_blocks());
726 
727   for (size_t i = 0; i < n; i++) {
728     Obj block;
729     Obj entry = (xx->is_transverse_block(xx->block(i)) ? INTOBJ_INT(i + 1)
730                                                        : INTOBJ_INT(-i - 1));
731     if (ELM_PLIST(ext_rep, xx->block(i) + 1) == 0) {
732       block = NEW_PLIST(T_PLIST_CYC, 1);
733       SET_LEN_PLIST(block, 1);
734       SET_ELM_PLIST(block, 1, entry);
735       SET_ELM_PLIST(ext_rep, xx->block(i) + 1, block);
736       CHANGED_BAG(ext_rep);
737     } else {
738       block = ELM_PLIST(ext_rep, xx->block(i) + 1);
739       AssPlist(block, LEN_PLIST(block) + 1, entry);
740     }
741   }
742   MakeImmutable(ext_rep);
743   return ext_rep;
744 }
745 
746 // Returns the hash value for a GAP bipartition from the C++ object.
747 
BLOCKS_HASH(Obj self,Obj x,Obj data)748 Obj BLOCKS_HASH(Obj self, Obj x, Obj data) {
749   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BLOCKS);
750 
751   return INTOBJ_INT((blocks_get_cpp(x)->hash_value() % INT_INTOBJ(data)) + 1);
752 }
753 
754 // Returns the degree of a GAP blocks from the C++ object. A blocks object
755 // is of degree n if the union of the sets of absolute values of its blocks is
756 // [1 .. n].
757 
BLOCKS_DEGREE(Obj self,Obj x)758 Obj BLOCKS_DEGREE(Obj self, Obj x) {
759   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BLOCKS);
760 
761   return INTOBJ_INT(blocks_get_cpp(x)->degree());
762 }
763 
764 // Returns the rank of a GAP blocks from the C++ object. The rank of a blocks
765 // object is the number of transverse blocks (equivalently the number of blocks
766 // consisting of positive integers).
767 
BLOCKS_RANK(Obj self,Obj x)768 Obj BLOCKS_RANK(Obj self, Obj x) {
769   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BLOCKS);
770 
771   return INTOBJ_INT(blocks_get_cpp(x)->rank());
772 }
773 
774 // Returns the number of blocks in the partition represented by the GAP blocks
775 // object.
776 
BLOCKS_NR_BLOCKS(Obj self,Obj x)777 Obj BLOCKS_NR_BLOCKS(Obj self, Obj x) {
778   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BLOCKS);
779 
780   return INTOBJ_INT(blocks_get_cpp(x)->nr_blocks());
781 }
782 
783 // Returns an idempotent GAP bipartition whose left and right blocks equal the
784 // GAP blocks object x.
785 
BLOCKS_PROJ(Obj self,Obj x)786 Obj BLOCKS_PROJ(Obj self, Obj x) {
787   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BLOCKS);
788 
789   Blocks* blocks = blocks_get_cpp(x);
790 
791   _BUFFER_size_t.clear();
792   _BUFFER_size_t.resize(blocks->nr_blocks(), -1);
793 
794   std::vector<u_int32_t> out(2 * blocks->degree());
795   u_int32_t              nr_blocks = blocks->nr_blocks();
796 
797   for (u_int32_t i = 0; i < blocks->degree(); i++) {
798     u_int32_t index = blocks->block(i);
799     out[i]          = index;
800     if (blocks->is_transverse_block(index)) {
801       out[i + blocks->degree()] = index;
802     } else {
803       if (_BUFFER_size_t[index] == static_cast<size_t>(-1)) {
804         _BUFFER_size_t[index] = nr_blocks;
805         nr_blocks++;
806       }
807       out[i + blocks->degree()] = _BUFFER_size_t[index];
808     }
809   }
810   return bipart_new_obj(new Bipartition(out));
811 }
812 
813 // Check if two GAP blocks objects are equal.
814 
BLOCKS_EQ(Obj x,Obj y)815 Int BLOCKS_EQ(Obj x, Obj y) {
816   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BLOCKS);
817   SEMIGROUPS_ASSERT(TNUM_OBJ(y) == T_BLOCKS);
818 
819   return (*blocks_get_cpp(x) == *blocks_get_cpp(y) ? 1L : 0L);
820 }
821 
822 // Check if x < y, when x and y are GAP blocks objects.
823 
BLOCKS_LT(Obj x,Obj y)824 Int BLOCKS_LT(Obj x, Obj y) {
825   SEMIGROUPS_ASSERT(TNUM_OBJ(x) == T_BLOCKS);
826   SEMIGROUPS_ASSERT(TNUM_OBJ(y) == T_BLOCKS);
827 
828   return (*blocks_get_cpp(x) < *blocks_get_cpp(y) ? 1L : 0L);
829 }
830 
831 // Returns True if there is an idempotent bipartition with left blocks equal to
832 // left_gap and right blocks equal to right_gap.
833 
BLOCKS_E_TESTER(Obj self,Obj left_gap,Obj right_gap)834 Obj BLOCKS_E_TESTER(Obj self, Obj left_gap, Obj right_gap) {
835   SEMIGROUPS_ASSERT(TNUM_OBJ(left_gap) == T_BLOCKS);
836   SEMIGROUPS_ASSERT(TNUM_OBJ(right_gap) == T_BLOCKS);
837 
838   Blocks* left  = blocks_get_cpp(left_gap);
839   Blocks* right = blocks_get_cpp(right_gap);
840 
841   if (left->rank() != right->rank()) {
842     return False;
843   } else if (left->rank() == 0) {
844     return True;
845   }
846 
847   // prepare the _BUFFER_bool for detecting transverse fused blocks
848   _BUFFER_bool.clear();
849   _BUFFER_bool.resize(right->nr_blocks() + 2 * left->nr_blocks());
850   std::copy(right->lookup()->begin(),
851             right->lookup()->end(),
852             _BUFFER_bool.begin() + left->nr_blocks());
853   auto seen = _BUFFER_bool.begin() + right->nr_blocks() + left->nr_blocks();
854 
855   // after the following line:
856   //
857   // 1) [_BUFFER_size_t.begin() .. _BUFFER_size_t.begin() + left_nr_blocks +
858   //    right_nr_blocks - 1] is the fuse table for left and right
859   //
860   // 2) _BUFFER_bool is a lookup for the transverse blocks of the fused left
861   //     and right
862 
863   fuse(left->degree(),
864        left->cbegin(),
865        left->nr_blocks(),
866        right->cbegin(),
867        right->nr_blocks(),
868        true);
869 
870   // check we are injective on transverse blocks of <left> and that the fused
871   // blocks are also transverse.
872 
873   for (u_int32_t i = 0; i < left->nr_blocks(); i++) {
874     if (left->is_transverse_block(i)) {
875       size_t j = fuse_it(i);
876       if (!_BUFFER_bool[j] || seen[j]) {
877         return False;
878       }
879       seen[j] = true;
880     }
881   }
882   return True;
883 }
884 
885 // Returns the idempotent bipartition with left blocks equal to
886 // left_gap and right blocks equal to right_gap, assuming that this exists.
887 
BLOCKS_E_CREATOR(Obj self,Obj left_gap,Obj right_gap)888 Obj BLOCKS_E_CREATOR(Obj self, Obj left_gap, Obj right_gap) {
889   SEMIGROUPS_ASSERT(TNUM_OBJ(left_gap) == T_BLOCKS);
890   SEMIGROUPS_ASSERT(TNUM_OBJ(right_gap) == T_BLOCKS);
891   SEMIGROUPS_ASSERT(BLOCKS_E_TESTER(self, left_gap, right_gap) == True);
892 
893   Blocks* left  = blocks_get_cpp(left_gap);
894   Blocks* right = blocks_get_cpp(right_gap);
895 
896   fuse(left->degree(),
897        left->cbegin(),
898        left->nr_blocks(),
899        right->cbegin(),
900        right->nr_blocks(),
901        false);
902 
903   _BUFFER_size_t.resize(3 * (left->nr_blocks() + right->nr_blocks()), 0);
904   std::fill(
905       _BUFFER_size_t.begin() + 2 * (left->nr_blocks() + right->nr_blocks()),
906       _BUFFER_size_t.begin() + 3 * (left->nr_blocks() + right->nr_blocks()),
907       -1);
908 
909   auto tab1 = _BUFFER_size_t.begin() + left->nr_blocks() + right->nr_blocks();
910   auto tab2
911       = _BUFFER_size_t.begin() + 2 * (left->nr_blocks() + right->nr_blocks());
912 
913   // find new names for the signed blocks of right
914   for (size_t i = 0; i < right->nr_blocks(); i++) {
915     if (right->is_transverse_block(i)) {
916       tab1[fuse_it(i + left->nr_blocks())] = i;
917     }
918   }
919 
920   std::vector<u_int32_t> blocks(2 * left->degree());
921 
922   size_t next = right->nr_blocks();
923 
924   for (size_t i = 0; i < left->degree(); i++) {
925     blocks[i] = right->block(i);
926     size_t j  = left->block(i);
927     if (left->is_transverse_block(j)) {
928       blocks[i + left->degree()] = tab1[fuse_it(j)];
929     } else {
930       if (tab2[j] == static_cast<size_t>(-1)) {
931         tab2[j] = next;
932         next++;
933       }
934       blocks[i + left->degree()] = tab2[j];
935     }
936   }
937 
938   Bipartition* out = new Bipartition(blocks);
939   out->set_nr_blocks(next);
940   out->set_nr_left_blocks(right->nr_blocks());
941 
942   return bipart_new_obj(out);
943 }
944 
945 // Returns the left blocks of BLOCKS_PROJ(blocks_gap) * x_gap where the latter
946 // is a GAP bipartition.
947 
BLOCKS_LEFT_ACT(Obj self,Obj blocks_gap,Obj x_gap)948 Obj BLOCKS_LEFT_ACT(Obj self, Obj blocks_gap, Obj x_gap) {
949   SEMIGROUPS_ASSERT(TNUM_OBJ(x_gap) == T_BIPART);
950   SEMIGROUPS_ASSERT(TNUM_OBJ(blocks_gap) == T_BLOCKS);
951 
952   Bipartition* x      = bipart_get_cpp(x_gap);
953   Blocks*      blocks = blocks_get_cpp(blocks_gap);
954 
955   if (blocks->degree() != x->degree()) {
956     // hack to allow Lambda/RhoOrbSeed
957     return blocks_new_obj(x->left_blocks());
958   } else if (blocks->degree() == 0) {
959     return blocks_gap;
960   }
961 
962   // prepare the _BUFFER_bool for detecting transverse fused blocks
963   _BUFFER_bool.clear();
964   _BUFFER_bool.resize(x->nr_blocks() + blocks->nr_blocks());
965   std::copy(blocks->lookup()->begin(),
966             blocks->lookup()->end(),
967             _BUFFER_bool.begin() + x->nr_blocks());
968 
969   fuse(x->degree(),
970        x->begin() + x->degree(),
971        x->nr_blocks(),
972        blocks->cbegin(),
973        blocks->nr_blocks(),
974        true);
975 
976   _BUFFER_size_t.resize(2 * (x->nr_blocks() + blocks->nr_blocks()), -1);
977   auto tab = _BUFFER_size_t.begin() + x->nr_blocks() + blocks->nr_blocks();
978 
979   std::vector<u_int32_t>* out_blocks = new std::vector<u_int32_t>();
980   out_blocks->reserve(x->degree());
981   std::vector<bool>* out_lookup = new std::vector<bool>();
982   out_lookup->resize(x->degree());
983 
984   u_int32_t next = 0;
985 
986   for (u_int32_t i = 0; i < x->degree(); i++) {
987     u_int32_t j = fuse_it(x->at(i));
988     if (tab[j] == static_cast<size_t>(-1)) {
989       tab[j] = next;
990       next++;
991     }
992     out_blocks->push_back(tab[j]);
993     (*out_lookup)[tab[j]] = _BUFFER_bool[j];
994   }
995   out_lookup->resize(next);
996   return blocks_new_obj(new Blocks(out_blocks, out_lookup));
997 }
998 
999 // Returns the right blocks of x_gap * BLOCKS_PROJ(blocks_gap) where the former
1000 // is a GAP bipartition.
1001 
BLOCKS_RIGHT_ACT(Obj self,Obj blocks_gap,Obj x_gap)1002 Obj BLOCKS_RIGHT_ACT(Obj self, Obj blocks_gap, Obj x_gap) {
1003   SEMIGROUPS_ASSERT(TNUM_OBJ(x_gap) == T_BIPART);
1004   SEMIGROUPS_ASSERT(TNUM_OBJ(blocks_gap) == T_BLOCKS);
1005 
1006   Bipartition* x      = bipart_get_cpp(x_gap);
1007   Blocks*      blocks = blocks_get_cpp(blocks_gap);
1008 
1009   if (blocks->degree() != x->degree()) {
1010     // hack to allow Lambda/RhoOrbSeed
1011     return blocks_new_obj(x->right_blocks());
1012   } else if (blocks->degree() == 0) {
1013     return blocks_gap;
1014   }
1015 
1016   // prepare the _BUFFER_bool for detecting transverse fused blocks
1017   _BUFFER_bool.clear();
1018   _BUFFER_bool.resize(x->nr_blocks() + blocks->nr_blocks());
1019   std::copy(
1020       blocks->lookup()->begin(), blocks->lookup()->end(), _BUFFER_bool.begin());
1021 
1022   fuse(x->degree(),
1023        blocks->cbegin(),
1024        blocks->nr_blocks(),
1025        x->begin(),
1026        x->nr_blocks(),
1027        true);
1028 
1029   _BUFFER_size_t.resize(2 * (x->nr_blocks() + blocks->nr_blocks()), -1);
1030   auto tab = _BUFFER_size_t.begin() + x->nr_blocks() + blocks->nr_blocks();
1031 
1032   std::vector<u_int32_t>* out_blocks = new std::vector<u_int32_t>();
1033   out_blocks->reserve(x->degree());
1034   std::vector<bool>* out_lookup = new std::vector<bool>();
1035   out_lookup->resize(x->degree());
1036 
1037   u_int32_t next = 0;
1038 
1039   for (u_int32_t i = x->degree(); i < 2 * x->degree(); i++) {
1040     u_int32_t j = fuse_it(x->at(i) + blocks->nr_blocks());
1041     if (tab[j] == static_cast<size_t>(-1)) {
1042       tab[j] = next;
1043       next++;
1044     }
1045     out_blocks->push_back(tab[j]);
1046     (*out_lookup)[tab[j]] = _BUFFER_bool[j];
1047   }
1048   out_lookup->resize(next);
1049   return blocks_new_obj(new Blocks(out_blocks, out_lookup));
1050 }
1051 
1052 // Returns a GAP bipartition y such that if BLOCKS_LEFT_ACT(blocks_gap, x_gap)
1053 // = Y, then BLOCKS_LEFT_ACT(Y, y) = blocks_gap, and y acts on Y as the inverse
1054 // of x_gap on Y.
1055 
BLOCKS_INV_LEFT(Obj self,Obj blocks_gap,Obj x_gap)1056 Obj BLOCKS_INV_LEFT(Obj self, Obj blocks_gap, Obj x_gap) {
1057   SEMIGROUPS_ASSERT(TNUM_OBJ(blocks_gap) == T_BLOCKS);
1058   SEMIGROUPS_ASSERT(TNUM_OBJ(x_gap) == T_BIPART);
1059   Blocks*      blocks = blocks_get_cpp(blocks_gap);
1060   Bipartition* x      = bipart_get_cpp(x_gap);
1061   SEMIGROUPS_ASSERT(x->degree() == blocks->degree());
1062 
1063   fuse(x->degree(),
1064        blocks->cbegin(),
1065        blocks->nr_blocks(),
1066        x->begin() + x->degree(),
1067        x->nr_blocks(),
1068        false);
1069   SEMIGROUPS_ASSERT(_BUFFER_size_t.size()
1070                     == blocks->nr_blocks() + x->nr_blocks());
1071 
1072   std::vector<u_int32_t> out_blocks(2 * x->degree());
1073 
1074   _BUFFER_size_t.resize(2 * blocks->nr_blocks() + x->nr_blocks(), -1);
1075   SEMIGROUPS_ASSERT(_BUFFER_size_t.size()
1076                     == 2 * blocks->nr_blocks() + x->nr_blocks());
1077   SEMIGROUPS_ASSERT(std::all_of(
1078       _BUFFER_size_t.cbegin() + blocks->nr_blocks() + x->nr_blocks(),
1079       _BUFFER_size_t.cend(),
1080       [](size_t i) -> bool { return i == static_cast<size_t>(-1); }));
1081   auto tab = _BUFFER_size_t.begin() + blocks->nr_blocks() + x->nr_blocks();
1082   SEMIGROUPS_ASSERT(_BUFFER_size_t.end() - tab == blocks->nr_blocks());
1083 
1084   for (u_int32_t i = 0; i < blocks->nr_blocks(); i++) {
1085     if (blocks->is_transverse_block(i)) {
1086       SEMIGROUPS_ASSERT(fuse_it(i) < blocks->nr_blocks());
1087       SEMIGROUPS_ASSERT(tab + fuse_it(i) < _BUFFER_size_t.end());
1088       tab[fuse_it(i)] = i;
1089     }
1090   }
1091 
1092   // find the left blocks of the output
1093   for (u_int32_t i = 0; i < blocks->degree(); i++) {
1094     out_blocks[i] = blocks->block(i);
1095     u_int32_t j   = fuse_it(x->at(i) + blocks->nr_blocks());
1096     if (j >= blocks->nr_blocks() || tab[j] == static_cast<size_t>(-1)) {
1097       out_blocks[i + x->degree()] = blocks->nr_blocks();  // junk
1098     } else {
1099       out_blocks[i + x->degree()] = tab[j];
1100     }
1101   }
1102 
1103   Bipartition* out = new Bipartition(out_blocks);
1104   out->set_nr_left_blocks(blocks->nr_blocks());
1105 
1106   return bipart_new_obj(out);
1107 }
1108 
1109 // Returns a GAP bipartition y such that if BLOCKS_RIGHT_ACT(blocks_gap, x_gap)
1110 // = Y, then BLOCKS_RIGHT_ACT(Y, y) = blocks_gap, and y acts on Y as the inverse
1111 // of x_gap on Y.
1112 //
1113 // We fuse <blocks_gap> with the left blocks of <x_gap> keeping track of
1114 // the signs of the fused classes.
1115 //
1116 // The left blocks of the output are then:
1117 //
1118 // 1) disconnected right blocks of <x_gap> (before fusing)
1119 //
1120 // 2) disconnected right blocks of <x_gap> (after fusing)
1121 //
1122 // 3) connected right blocks of <x_gap> (after fusing)
1123 //
1124 // both types 1+2 of the disconnected blocks are unioned into one left block of
1125 // the output with index <junk>. The connected blocks 3 of <x_gap> are given the
1126 // next
1127 // available index, if they have not been seen before. The table <tab1> keeps
1128 // track of which connected right blocks of <x_gap> have been seen before and
1129 // the
1130 // corresponding index in the output, i.e. <tab1[x]> is the index in <out> of
1131 // the
1132 // fused block with index <x>.
1133 //
1134 // The right blocks of the output are:
1135 //
1136 // 1) disconnected blocks of <blocks_gap>; or
1137 //
1138 // 2) connected blocks of <blocks_gap>.
1139 //
1140 // The disconnected blocks 1 are given the next available index, if they have
1141 // not
1142 // been seen before. The table <tab2> keeps track of which disconnected blocks
1143 // of
1144 // <blocks_gap> have been seen before and the corresponding index in the output,
1145 // i.e.
1146 // <tab2[x]> is the index in <out> of the disconnected block of <blocks_gap>
1147 // with
1148 // index <x>. The connected blocks 2 of <blocks_gap> is given the index
1149 // <tab1[x]>
1150 // where <x> is the fused index of the block.
1151 
BLOCKS_INV_RIGHT(Obj self,Obj blocks_gap,Obj x_gap)1152 Obj BLOCKS_INV_RIGHT(Obj self, Obj blocks_gap, Obj x_gap) {
1153   Blocks*      blocks = blocks_get_cpp(blocks_gap);
1154   Bipartition* x      = bipart_get_cpp(x_gap);
1155 
1156   // prepare _BUFFER_bool for fusing
1157 
1158   _BUFFER_bool.clear();
1159   _BUFFER_bool.resize(blocks->nr_blocks() + x->nr_blocks());
1160   std::copy(
1161       blocks->lookup()->begin(), blocks->lookup()->end(), _BUFFER_bool.begin());
1162 
1163   fuse(x->degree(),
1164        blocks->cbegin(),
1165        blocks->nr_blocks(),
1166        x->begin(),
1167        x->nr_blocks(),
1168        true);
1169 
1170   u_int32_t junk = -1;
1171   u_int32_t next = 0;
1172 
1173   std::vector<u_int32_t> out_blocks(2 * x->degree());
1174 
1175   _BUFFER_size_t.resize(3 * blocks->nr_blocks() + 2 * x->nr_blocks(), -1);
1176   auto tab1 = _BUFFER_size_t.begin() + blocks->nr_blocks() + x->nr_blocks();
1177   auto tab2
1178       = _BUFFER_size_t.begin() + 2 * (blocks->nr_blocks() + x->nr_blocks());
1179 
1180   // find the left blocks of the output
1181   for (u_int32_t i = 0; i < blocks->degree(); i++) {
1182     if (x->at(i + x->degree()) < x->nr_left_blocks()) {
1183       u_int32_t j = fuse_it(x->at(i + x->degree()) + blocks->nr_blocks());
1184       if (_BUFFER_bool[j]) {
1185         if (tab1[j] == static_cast<size_t>(-1)) {
1186           tab1[j] = next;
1187           next++;
1188         }
1189         out_blocks[i] = tab1[j];
1190         continue;
1191       }
1192     }
1193     if (junk == (u_int32_t) -1) {
1194       junk = next;
1195       next++;
1196     }
1197     out_blocks[i] = junk;
1198   }
1199 
1200   u_int32_t out_nr_left_blocks = next;
1201 
1202   // find the right blocks of the output
1203 
1204   for (u_int32_t i = blocks->degree(); i < 2 * blocks->degree(); i++) {
1205     u_int32_t j = blocks->block(i - blocks->degree());
1206     if (blocks->is_transverse_block(j)) {
1207       out_blocks[i] = tab1[fuse_it(j)];
1208     } else {
1209       if (tab2[j] == static_cast<size_t>(-1)) {
1210         tab2[j] = next;
1211         next++;
1212       }
1213       out_blocks[i] = tab2[j];
1214     }
1215   }
1216 
1217   Bipartition* out = new Bipartition(out_blocks);
1218   out->set_nr_left_blocks(out_nr_left_blocks);
1219   out->set_nr_blocks(next);
1220   return bipart_new_obj(out);
1221 }
1222 
1223 ////////////////////////////////////////////////////////////////////////////////
1224 // Non-GAP functions
1225 ////////////////////////////////////////////////////////////////////////////////
1226 
1227 // The functions below do the Union-Find algorithm for finding the least
1228 // equivalence relation containing two other equivalence relations. These are
1229 // used by many of the functions above for blocks.
1230 
1231 // Returns the class containing the number i (i.e. this is the Find part of
1232 // Union-Find). This strongly relies on everything being set up correctly.
1233 
fuse_it(size_t i)1234 static inline size_t fuse_it(size_t i) {
1235   while (_BUFFER_size_t[i] < i) {
1236     i = _BUFFER_size_t[i];
1237   }
1238   return i;
1239 }
1240 
1241 // This function performs Union-Find to find the least equivalence relation
1242 // containing the equivalence relations starting at left_begin, and
1243 // right_begin (named left and right below).
1244 //
1245 // If it = left_begin or right_begin, then it must be an iterator such that:
1246 //
1247 //   * it.end() >= it.begin() + deg
1248 //
1249 //   * *(it.begin() + i) is the index of the block containing i in the
1250 //   equivalence relation represented by it
1251 //
1252 //   * left_nr_blocks/right_nr_blocks is the number of blocks in the
1253 //   equivalence relation represented by left_begin/right_begin.
1254 //
1255 //   * if we want to keep track of transverse blocks, then sign should be true,
1256 //   otherwise it is false.
1257 //
1258 // After running fuse:
1259 //
1260 // 1) [_BUFFER_size_t.begin() .. _BUFFER_size_t.begin() + left_nr_blocks +
1261 //    right_nr_blocks - 1] is the fuse table for left and right
1262 //
1263 // 2) If sign == true, then _BUFFER_bool is a lookup for the transverse blocks
1264 //    of the fused left and right
1265 //
1266 // Note that _BUFFER_bool has to be pre-assigned with the correct values, i.e.
1267 // it must be at least initialized (and have the appropriate length).
1268 
fuse(u_int32_t deg,typename std::vector<u_int32_t>::const_iterator left_begin,u_int32_t left_nr_blocks,typename std::vector<u_int32_t>::const_iterator right_begin,u_int32_t right_nr_blocks,bool sign)1269 static void fuse(u_int32_t                                       deg,
1270                  typename std::vector<u_int32_t>::const_iterator left_begin,
1271                  u_int32_t                                       left_nr_blocks,
1272                  typename std::vector<u_int32_t>::const_iterator right_begin,
1273                  u_int32_t right_nr_blocks,
1274                  bool      sign) {
1275   _BUFFER_size_t.clear();
1276   _BUFFER_size_t.reserve(left_nr_blocks + right_nr_blocks);
1277 
1278   for (size_t i = 0; i < left_nr_blocks + right_nr_blocks; i++) {
1279     _BUFFER_size_t.push_back(i);
1280   }
1281 
1282   for (auto left_it = left_begin, right_it = right_begin;
1283        left_it < left_begin + deg;
1284        left_it++, right_it++) {
1285     size_t j = fuse_it(*left_it);
1286     size_t k = fuse_it(*right_it + left_nr_blocks);
1287 
1288     if (j != k) {
1289       if (j < k) {
1290         _BUFFER_size_t[k] = j;
1291         if (sign && _BUFFER_bool[k]) {
1292           _BUFFER_bool[j] = true;
1293         }
1294       } else {
1295         _BUFFER_size_t[j] = k;
1296         if (sign && _BUFFER_bool[j]) {
1297           _BUFFER_bool[k] = true;
1298         }
1299       }
1300     }
1301   }
1302 }
1303 
1304 ////////////////////////////////////////////////////////////////////////////////
1305 // Counting idempotents in regular *-semigroups of bipartitions
1306 ////////////////////////////////////////////////////////////////////////////////
1307 
1308 // The class and functions below implement a parallel idempotent counting
1309 // method for regular *-semigroups of bipartitions.
1310 
1311 // IdempotentCounter is a class for containing the data required for the
1312 // search for idempotents.
1313 
1314 class IdempotentCounter {
1315   typedef std::vector<std::vector<size_t>> thrds_size_t;
1316   typedef std::vector<std::vector<bool>>   thrds_bool_t;
1317   typedef std::pair<size_t, size_t>        unpr_t;
1318   typedef std::vector<std::vector<unpr_t>> thrds_unpr_t;
1319 
1320  public:
IdempotentCounter(Obj orbit,Obj scc,Obj lookup,unsigned int nr_threads,Obj report)1321   IdempotentCounter(Obj          orbit,
1322                     Obj          scc,
1323                     Obj          lookup,
1324                     unsigned int nr_threads,
1325                     Obj          report)
1326       : _nr_threads(std::min(nr_threads, std::thread::hardware_concurrency())),
1327         _fuse_tab(thrds_size_t(_nr_threads, std::vector<size_t>())),
1328         _lookup(thrds_bool_t(_nr_threads, std::vector<bool>())),
1329         _min_scc(),
1330         _orbit(),
1331         _report(report == True),
1332         _scc(),
1333         _scc_pos(std::vector<size_t>(LEN_LIST(orbit), 0)),
1334         _seen(thrds_bool_t(_nr_threads, std::vector<bool>())),
1335         _threads(),
1336         _unprocessed(thrds_unpr_t(_nr_threads, std::vector<unpr_t>())),
1337         _vals(thrds_size_t(_nr_threads,
1338                            std::vector<size_t>(LEN_PLIST(scc) - 1, 0))) {
1339     // copy the GAP blocks from the orbit into _orbit
1340     _orbit.reserve(LEN_LIST(orbit));
1341     for (Int i = 2; i <= LEN_LIST(orbit); i++) {
1342       _orbit.push_back(blocks_get_cpp(ELM_LIST(orbit, i)));
1343     }
1344 
1345     size_t total_load = 0;
1346     size_t min_rank   = -1;
1347     // copy the scc from GAP to C++
1348     for (Int i = 2; i <= LEN_PLIST(scc); i++) {
1349       Obj comp = ELM_PLIST(scc, i);
1350 
1351       _scc.push_back(std::vector<size_t>());
1352       _scc.back().reserve(LEN_PLIST(comp));
1353 
1354       for (Int j = 1; j <= LEN_PLIST(comp); j++) {
1355         _scc.back().push_back(INT_INTOBJ(ELM_PLIST(comp, j)) - 2);
1356         _scc_pos[INT_INTOBJ(ELM_PLIST(comp, j)) - 2] = j - 1;
1357       }
1358       _ranks.push_back(_orbit[_scc.back()[0]]->rank());
1359       if (_ranks.back() < min_rank) {
1360         min_rank = _ranks.back();
1361         _min_scc = i - 2;
1362       }
1363       total_load += _scc.back().size() * (_scc.back().size() - 1) / 2;
1364     }
1365 
1366     total_load -= (_scc[_min_scc].size() * (_scc[_min_scc].size() - 1) / 2);
1367 
1368     // queue pairs for each thread
1369     size_t mean_load   = total_load / _nr_threads;
1370     size_t thread_id   = 0;
1371     size_t thread_load = 0;
1372 
1373     for (size_t i = 0; i < _orbit.size(); i++) {
1374       size_t comp = INT_INTOBJ(ELM_PLIST(lookup, i + 2)) - 2;
1375       if (comp != _min_scc) {
1376         _unprocessed[thread_id].push_back(std::make_pair(i, comp));
1377         thread_load += _scc[comp].size() - _scc_pos[i];
1378         if (thread_load >= mean_load && thread_id != _nr_threads - 1) {
1379           thread_id++;
1380           thread_load = 0;
1381         }
1382       }
1383     }
1384   }
1385 
count()1386   std::vector<size_t> count() {
1387     libsemigroups::THREAD_ID_MANAGER.reset();
1388     auto rg = libsemigroups::ReportGuard(_report);
1389     REPORT_DEFAULT("using %llu / %llu additional threads",
1390                    _nr_threads,
1391                    std::thread::hardware_concurrency());
1392     Timer timer;
1393 
1394     for (size_t i = 0; i < _nr_threads; i++) {
1395       _threads.push_back(
1396           std::thread(&IdempotentCounter::thread_counter, this, i));
1397     }
1398 
1399     for (size_t i = 0; i < _nr_threads; i++) {
1400       _threads[i].join();
1401     }
1402 
1403     REPORT_TIME(timer);
1404 
1405     size_t              max = *max_element(_ranks.begin(), _ranks.end()) + 1;
1406     std::vector<size_t> out = std::vector<size_t>(max, 0);
1407 
1408     for (size_t j = 0; j < _scc.size(); j++) {
1409       size_t rank = _ranks[j];
1410       if (j != _min_scc) {
1411         for (size_t i = 0; i < _nr_threads; i++) {
1412           out[rank] += _vals[i][j];
1413         }
1414 
1415       } else {
1416         out[rank] += _scc[_min_scc].size() * _scc[_min_scc].size();
1417       }
1418     }
1419 
1420     return out;
1421   }
1422 
1423  private:
thread_counter(size_t thread_id)1424   void thread_counter(size_t thread_id) {
1425     Timer timer;
1426 
1427     for (unpr_t index : _unprocessed[thread_id]) {
1428       if (tester(thread_id, index.first, index.first)) {
1429         _vals[thread_id][index.second]++;
1430       }
1431       std::vector<size_t> comp  = _scc[index.second];
1432       auto                begin = comp.begin() + _scc_pos[index.first] + 1;
1433       for (auto it = begin; it < comp.end(); it++) {
1434         if (tester(thread_id, index.first, *it)) {
1435           _vals[thread_id][index.second] += 2;
1436         }
1437       }
1438     }
1439     REPORT_DEFAULT("finished in %llu", timer.string());
1440   }
1441 
1442   // This is basically the same as BLOCKS_E_TESTER, but is required because we
1443   // must have different temporary storage for every thread.
tester(size_t thread_id,size_t i,size_t j)1444   bool tester(size_t thread_id, size_t i, size_t j) {
1445     Blocks* left  = _orbit[i];
1446     Blocks* right = _orbit[j];
1447 
1448     // prepare the _lookup for detecting transverse fused blocks
1449     _lookup[thread_id].clear();
1450     _lookup[thread_id].resize(right->nr_blocks() + left->nr_blocks());
1451     std::copy(right->lookup()->begin(),
1452               right->lookup()->end(),
1453               _lookup[thread_id].begin() + left->nr_blocks());
1454 
1455     _seen[thread_id].clear();
1456     _seen[thread_id].resize(left->nr_blocks());
1457 
1458     // prepare the _fuse_tab
1459     _fuse_tab[thread_id].clear();
1460     _fuse_tab[thread_id].reserve(left->nr_blocks() + right->nr_blocks());
1461 
1462     for (size_t k = 0; k < left->nr_blocks() + right->nr_blocks(); k++) {
1463       _fuse_tab[thread_id].push_back(k);
1464     }
1465 
1466     for (auto left_it = left->cbegin(), right_it = right->cbegin();
1467          left_it < left->cbegin() + left->degree();
1468          left_it++, right_it++) {
1469       size_t k = fuse_it(thread_id, *left_it);
1470       size_t l = fuse_it(thread_id, *right_it + left->nr_blocks());
1471 
1472       if (k != l) {
1473         if (k < l) {
1474           _fuse_tab[thread_id][l] = k;
1475           if (_lookup[thread_id][l]) {
1476             _lookup[thread_id][k] = true;
1477           }
1478         } else {
1479           _fuse_tab[thread_id][k] = l;
1480           if (_lookup[thread_id][k]) {
1481             _lookup[thread_id][l] = true;
1482           }
1483         }
1484       }
1485     }
1486 
1487     for (u_int32_t k = 0; k < left->nr_blocks(); k++) {
1488       if (left->is_transverse_block(k)) {
1489         size_t l = fuse_it(thread_id, k);
1490         if (!_lookup[thread_id][l] || _seen[thread_id][l]) {
1491           return false;
1492         }
1493         _seen[thread_id][l] = true;
1494       }
1495     }
1496     return true;
1497   }
1498 
fuse_it(size_t thread_id,size_t i)1499   inline size_t fuse_it(size_t thread_id, size_t i) {
1500     while (_fuse_tab[thread_id][i] < i) {
1501       i = _fuse_tab[thread_id][i];
1502     }
1503     return i;
1504   }
1505 
1506   size_t               _nr_threads;
1507   thrds_size_t         _fuse_tab;
1508   thrds_bool_t         _lookup;
1509   size_t               _min_scc;
1510   std::vector<Blocks*> _orbit;
1511   bool                 _report;
1512   std::vector<size_t>  _ranks;
1513   thrds_size_t         _scc;
1514   std::vector<size_t>  _scc_pos;
1515   // _scc_pos[i] is the position of _orbit[i] in its scc
1516   thrds_bool_t             _seen;
1517   std::vector<std::thread> _threads;
1518   thrds_unpr_t             _unprocessed;
1519   thrds_size_t             _vals;
1520   // map from the scc indices to the rank of elements in that scc
1521 };
1522 
1523 // GAP-level function
1524 
BIPART_NR_IDEMPOTENTS(Obj self,Obj o,Obj scc,Obj lookup,Obj nr_threads,Obj report)1525 Obj BIPART_NR_IDEMPOTENTS(Obj self,
1526                           Obj o,
1527                           Obj scc,
1528                           Obj lookup,
1529                           Obj nr_threads,
1530                           Obj report) {
1531   IdempotentCounter   finder(o, scc, lookup, INT_INTOBJ(nr_threads), report);
1532   std::vector<size_t> vals = finder.count();
1533 
1534   Obj out = NEW_PLIST(T_PLIST_CYC, vals.size());
1535   SET_LEN_PLIST(out, vals.size());
1536 
1537   for (size_t i = 1; i <= vals.size(); i++) {
1538     SET_ELM_PLIST(out, i, INTOBJ_INT(vals[i - 1]));
1539   }
1540 
1541   return out;
1542 }
1543