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