Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/groups/perm_gps/partn_ref/canonical_augmentation.pyx
8815 views
1
r"""
2
Canonical augmentation
3
4
This module implements a general algorithm for generating isomorphism classes
5
of objects. The class of objects in question must be some kind of structure
6
which can be built up out of smaller objects by a process of augmentation,
7
and for which an automorphism is a permutation in `S_n` for some `n`. This
8
process consists of starting with a finite number of "seed objects" and
9
building up to more complicated objects by a sequence of "augmentations." It
10
should be noted that the word "canonical" in the term canonical augmentation
11
is used loosely. Given an object `X`, one must define a canonical parent
12
`M(X)`, which is essentially an arbitrary choice.
13
14
The class of objects in question must satisfy the assumptions made in the
15
module ``automorphism_group_canonical_label``, in particular the three
16
custom functions mentioned there must be implemented:
17
18
A. ``refine_and_return_invariant``:
19
20
Signature:
21
22
``int refine_and_return_invariant(PartitionStack *PS, void *S, int *cells_to_refine_by, int ctrb_len)``
23
24
B. ``compare_structures``:
25
26
Signature:
27
28
``int compare_structures(int *gamma_1, int *gamma_2, void *S1, void *S2, int degree)``
29
30
C. ``all_children_are_equivalent``:
31
32
Signature:
33
34
``bint all_children_are_equivalent(PartitionStack *PS, void *S)``
35
36
37
In the following functions there is frequently a mem_err input. This is a
38
pointer to an integer which must be set to a nonzero value in case of an
39
allocation failure. Other functions have an int return value which serves the
40
same purpose. The idea is that if a memory error occurs, the canonical
41
generator should still be able to iterate over the objects already generated
42
before it terminates.
43
44
More details about these functions can be found in that module. In addition,
45
several other functions must be implemented, which will make use of the
46
following::
47
48
ctypedef struct iterator:
49
void *data
50
void *(*next)(void *data, int *degree, int *mem_err)
51
52
The following functions must be implemented for each specific type of object to
53
be generated. Each function following which takes a ``mem_err`` variable as
54
input should make use of this variable.
55
56
D. ``generate_children``:
57
58
Signature:
59
60
``int generate_children(void *S, aut_gp_and_can_lab *group, iterator *it)``
61
62
This function receives a pointer to an iterator ``it``. The iterator
63
has two fields: ``data`` and ``next``. The function ``generate_children``
64
should set these two fields, returning 1 to indicate a memory error, or 0
65
for no error.
66
67
The function that ``next`` points to takes ``data`` as an argument, and
68
should return a (``void *``) pointer to the next object to be iterated. It
69
also takes a pointer to an int, and must update that int to reflect the
70
degree of each generated object. The objects to be iterated over should
71
satisfy the property that if `\gamma` is an automorphism of the parent
72
object `S`, then for any two child objects `C_1, C_2` given by the iterator,
73
it is not the case that `\gamma(C_1) = C_2`, where in the latter `\gamma` is
74
appropriately extended if necessary to operate on `C_1` and `C_2`. It is
75
essential for this iterator to handle its own ``data``. If the ``next``
76
function is called and no suitable object is yielded, a NULL pointer
77
indicates a termination of the iteration. At this point, the data pointed to
78
by the ``data`` variable should be cleared by the ``next`` function, because
79
the iterator struct itself will be deallocated.
80
81
The ``next`` function must check ``mem_err[0]`` before proceeding. If it is
82
nonzero then the function should deallocate the iterator right away and
83
return NULL to end the iteration. This ensures that the canonical
84
augmenatation software will finish iterating over the objects found before
85
finishing, and the ``mem_err`` attribute of the ``canonical_generator_data``
86
will reflect this.
87
88
The objects which the iterator generates can be thought of as augmentations,
89
which the following function must turn into objects.
90
91
E. ``apply_augmentation``:
92
93
Signature:
94
95
``void *apply_augmentation(void *parent, void *aug, void *child, int *degree, bint *mem_err)``
96
97
This function takes the ``parent``, applies the augmentation ``aug`` and
98
returns a pointer to the corresponding child object (freeing aug if
99
necessary). Should also update degree[0] to be the degree of the new child.
100
101
F. ``free_object``:
102
103
Signature:
104
105
``void free_object(void *child)``
106
107
This function is a simple deallocation function for children which are not
108
canonically generated, and therefore rejected in the canonical augmentation
109
process. They should deallocate the contents of ``child``.
110
111
G. ``free_iter_data``:
112
113
Signature:
114
115
``void free_iter_data(void *data)``
116
117
This function deallocates the data part of the iterator which is set up by
118
``generate_children``.
119
120
H. ``free_aug``:
121
122
Signature:
123
124
``void free_aug(void *aug)``
125
126
This function frees an augmentation as generated by the iterator returned
127
by ``generate_children``.
128
129
I. ``canonical_parent``:
130
131
Signature:
132
133
``void *canonical_parent(void *child, void *parent, int *permutation, int *degree, bint *mem_err)``
134
135
Apply the ``permutation`` to the ``child``, determine an arbitrary but fixed
136
parent, apply the inverse of ``permutation`` to that parent, and return the
137
resulting object. Must also set the integer ``degree`` points to to the
138
degree of the returned object.
139
140
NOTE:
141
142
It is a good idea to try to implement an augmentation scheme where the
143
degree of objects on each level of the augmentation tree is constant. The
144
iteration will be more efficient in this case, as the relevant work spaces
145
will never need to be reallocated. Otherwise, one should at least strive to
146
iterate over augmentations in such a way that all children of the same degree
147
are given in the same segment of iteration.
148
149
DOCTEST:
150
sage: import sage.groups.perm_gps.partn_ref.canonical_augmentation
151
152
REFERENCE:
153
154
[1] McKay, Brendan D. Isomorph-free exhaustive generation. J Algorithms,
155
Vol. 26 (1998), pp. 306-324.
156
157
"""
158
159
#*****************************************************************************
160
# Copyright (C) 2010 - 2011 Robert L. Miller <[email protected]>
161
#
162
# Distributed under the terms of the GNU General Public License (GPL)
163
# http://www.gnu.org/licenses/
164
#*****************************************************************************
165
166
include 'data_structures_pyx.pxi' # includes bitsets
167
168
cdef void *canonical_generator_next(void *can_gen_data, int *degree, bint *mem_err):
169
r"""
170
This function is part of the iterator struct which will iterate over
171
objects. Return value of ``NULL`` indicates termination.
172
"""
173
# degree ignored!
174
cdef canonical_generator_data *cgd = <canonical_generator_data *> can_gen_data
175
cdef iterator *cur_iter
176
cdef void *next_candidate, *parent_cand, *aug
177
cdef int i, next_cand_deg, *isom, parent_cand_deg
178
cdef PartitionStack *part
179
cdef bint augmentation_is_canonical
180
cdef aut_gp_and_can_lab *output
181
cdef agcl_work_space *agcl_ws
182
cdef dc_work_space *dc_ws
183
184
if cgd.level == 0:
185
if cgd.mem_err:
186
mem_err[0] = 1
187
if cgd.dealloc:
188
deallocate_cgd(cgd)
189
return NULL
190
191
while cgd.level < cgd.max_level:
192
cur_iter = cgd.iterator_stack + cgd.level - 1
193
# getting next candidate at level cgd.level
194
aug = cur_iter.next( cur_iter.data, &cgd.degree_stack[cgd.level], &cgd.mem_err )
195
if aug is NULL:
196
# cur_iter has run out: backtrack by one, return the parent
197
cgd.level -= 1
198
return cgd.object_stack[cgd.level]
199
else:
200
next_candidate = cgd.apply_augmentation(cgd.object_stack[cgd.level-1],
201
aug, cgd.object_stack[cgd.level], &cgd.degree_stack[cgd.level],
202
&cgd.mem_err)
203
cgd.object_stack[cgd.level] = next_candidate
204
if cgd.mem_err: continue
205
next_cand_deg = cgd.degree_stack[cgd.level]
206
if cgd.agcl_work_spaces[cgd.level] is NULL:
207
# allocate a work space if it hasn't been allocated already
208
cgd.agcl_work_spaces[cgd.level] = allocate_agcl_work_space(next_cand_deg)
209
cgd.ps_stack[cgd.level] = PS_new(next_cand_deg, 0)
210
elif next_cand_deg != cgd.agcl_work_spaces[cgd.level].degree:
211
# in case the degree has changed, we need to reallocate the work
212
# space: this is why one should do augmentations in order of degree
213
deallocate_agcl_work_space(cgd.agcl_work_spaces[cgd.level])
214
deallocate_agcl_output(cgd.aut_gp_stack[cgd.level])
215
cgd.aut_gp_stack[cgd.level] = NULL
216
PS_dealloc(cgd.ps_stack[cgd.level])
217
cgd.agcl_work_spaces[cgd.level] = allocate_agcl_work_space(next_cand_deg)
218
cgd.ps_stack[cgd.level] = PS_new(next_cand_deg, 0)
219
if cgd.agcl_work_spaces[cgd.level] is NULL or cgd.ps_stack[cgd.level] is NULL:
220
cgd.mem_err = 1
221
continue
222
# see if next_candidate is canonically augmented
223
part = cgd.ps_stack[cgd.level]
224
PS_unit_partition(part)
225
try:
226
cgd.aut_gp_stack[cgd.level] = get_aut_gp_and_can_lab(next_candidate,
227
part, next_cand_deg, cgd.all_children_are_equivalent,
228
cgd.refine_and_return_invariant, cgd.compare_structures,
229
1, cgd.group, cgd.agcl_work_spaces[cgd.level], cgd.aut_gp_stack[cgd.level])
230
except MemoryError:
231
cgd.mem_err = 1
232
continue
233
parent_cand = cgd.canonical_parent(next_candidate, cgd.parent_stack[cgd.level-1],
234
cgd.aut_gp_stack[cgd.level].relabeling, &parent_cand_deg, &cgd.mem_err)
235
if cgd.mem_err:
236
continue
237
if parent_cand_deg != cgd.degree_stack[cgd.level-1]:
238
augmentation_is_canonical = 0
239
else:
240
if cgd.dc_work_spaces[cgd.level-1] is NULL:
241
# allocate a work space if it hasn't been allocated already
242
cgd.dc_work_spaces[cgd.level-1] = allocate_dc_work_space(next_cand_deg)
243
elif next_cand_deg != cgd.dc_work_spaces[cgd.level-1].degree:
244
# in case the degree has changed, we need to reallocate the work
245
# space: this is why one should do augmentations in order of degree
246
deallocate_dc_work_space(cgd.dc_work_spaces[cgd.level-1])
247
cgd.dc_work_spaces[cgd.level-1] = allocate_dc_work_space(next_cand_deg)
248
if cgd.dc_work_spaces[cgd.level-1] is NULL:
249
cgd.mem_err = 1
250
continue
251
part = cgd.ps_stack[cgd.level]
252
PS_unit_partition(part)
253
try:
254
augmentation_is_canonical = double_coset(cgd.object_stack[cgd.level-1],
255
parent_cand, part, NULL, next_cand_deg,
256
cgd.all_children_are_equivalent,
257
cgd.refine_and_return_invariant,
258
cgd.compare_structures,
259
cgd.aut_gp_stack[cgd.level].group, cgd.dc_work_spaces[cgd.level-1], NULL)
260
except MemoryError:
261
cgd.mem_err = 1
262
continue
263
if augmentation_is_canonical:
264
# the object is canonically augmented, so we add it to the chain
265
if cgd.level + 1 != cgd.max_level:
266
cgd.mem_err |= cgd.generate_children(next_candidate,
267
cgd.aut_gp_stack[cgd.level], cgd.iterator_stack+cgd.level)
268
if cgd.mem_err:
269
continue
270
cgd.level += 1
271
272
if cgd.level == cgd.max_level:
273
# we're at the end of the chain, so just give the object
274
cgd.level -= 1
275
return cgd.object_stack[cgd.level]
276
277
cdef canonical_generator_data *allocate_cgd(int max_depth, int degree):
278
r"""
279
Allocate the data part of the canonical generation iterator struct.
280
"""
281
cdef canonical_generator_data *cgd = <canonical_generator_data *> sage_malloc(sizeof(canonical_generator_data))
282
cdef PartitionStack *part
283
if cgd is NULL:
284
sage_free(cgd)
285
return NULL
286
cgd.object_stack = <void **> sage_malloc(max_depth * sizeof(void *))
287
cgd.degree_stack = <int *> sage_malloc(max_depth * sizeof(int))
288
cgd.iterator_stack = <iterator *> sage_malloc(max_depth * sizeof(iterator))
289
cgd.aut_gp_stack = <aut_gp_and_can_lab **> sage_malloc(max_depth * sizeof(aut_gp_and_can_lab *))
290
cgd.agcl_work_spaces = <agcl_work_space **> sage_malloc(max_depth * sizeof(agcl_work_space *))
291
cgd.dc_work_spaces = <dc_work_space **> sage_malloc(max_depth * sizeof(dc_work_space *))
292
cgd.ps_stack = <PartitionStack **> sage_malloc(max_depth * sizeof(PartitionStack *))
293
cgd.aug_stack = <void **> sage_malloc(max_depth * sizeof(void *))
294
cgd.parent_stack = <void **> sage_malloc(max_depth * sizeof(void *))
295
part = PS_new(degree, 1)
296
cdef agcl_work_space *agclws = allocate_agcl_work_space(degree)
297
cdef aut_gp_and_can_lab *output = allocate_agcl_output(degree)
298
if cgd.object_stack is NULL or cgd.degree_stack is NULL or \
299
cgd.iterator_stack is NULL or cgd.aut_gp_stack is NULL or \
300
cgd.agcl_work_spaces is NULL or cgd.dc_work_spaces is NULL or \
301
cgd.ps_stack is NULL or cgd.aug_stack is NULL or \
302
cgd.parent_stack is NULL or agclws is NULL or output is NULL:
303
sage_free(cgd.object_stack)
304
sage_free(cgd.degree_stack)
305
sage_free(cgd.iterator_stack)
306
sage_free(cgd.aut_gp_stack)
307
sage_free(cgd.agcl_work_spaces)
308
sage_free(cgd.dc_work_spaces)
309
sage_free(cgd.ps_stack)
310
sage_free(cgd.aug_stack)
311
sage_free(cgd.parent_stack)
312
sage_free(cgd)
313
PS_dealloc(part)
314
deallocate_agcl_work_space(agclws)
315
deallocate_agcl_output(output)
316
return NULL
317
318
cdef int i
319
cgd.allocd_levels = max_depth
320
for i from 0 <= i < max_depth:
321
cgd.agcl_work_spaces[i] = NULL
322
cgd.dc_work_spaces[i] = NULL
323
cgd.aut_gp_stack[i] = NULL
324
cgd.ps_stack[i] = NULL
325
cgd.aug_stack[i] = NULL
326
cgd.parent_stack[i] = NULL
327
cgd.object_stack[i] = NULL
328
cgd.iterator_stack[i].data = NULL
329
cgd.agcl_work_spaces[0] = agclws
330
cgd.aut_gp_stack[0] = output
331
cgd.ps_stack[0] = part
332
333
cgd.degree_stack[0] = degree
334
return cgd
335
336
cdef void deallocate_cgd(canonical_generator_data *cgd):
337
r"""
338
Deallocate the data part of the canonical generation iterator struct.
339
"""
340
if cgd is NULL:
341
return
342
cdef int i
343
cdef void *thingy
344
cdef void (*clearer)(void*)
345
for i from 0 <= i < cgd.allocd_levels:
346
if cgd.agcl_work_spaces[i] is not NULL:
347
deallocate_agcl_work_space(cgd.agcl_work_spaces[i])
348
if cgd.ps_stack[i] is not NULL:
349
PS_dealloc(cgd.ps_stack[i])
350
if cgd.dc_work_spaces[i] is not NULL:
351
deallocate_dc_work_space(cgd.dc_work_spaces[i])
352
if cgd.aut_gp_stack[i] is not NULL:
353
deallocate_agcl_output(cgd.aut_gp_stack[i])
354
if cgd.object_stack[i] is not NULL:
355
cgd.free_object(cgd.object_stack[i])
356
if cgd.parent_stack[i] is not NULL:
357
cgd.free_object(cgd.parent_stack[i])
358
if cgd.aug_stack[i] is not NULL:
359
cgd.free_aug(cgd.aug_stack[i])
360
if cgd.iterator_stack[i].data is not NULL:
361
cgd.free_iter_data(cgd.iterator_stack[i].data)
362
sage_free(cgd.object_stack)
363
sage_free(cgd.degree_stack)
364
sage_free(cgd.iterator_stack)
365
sage_free(cgd.aut_gp_stack)
366
sage_free(cgd.agcl_work_spaces)
367
sage_free(cgd.dc_work_spaces)
368
sage_free(cgd.ps_stack)
369
sage_free(cgd.aug_stack)
370
sage_free(cgd.parent_stack)
371
sage_free(cgd)
372
373
cdef iterator *setup_canonical_generator(int degree,
374
bint (*all_children_are_equivalent)(PartitionStack *PS, void *S),
375
int (*refine_and_return_invariant)\
376
(PartitionStack *PS, void *S, int *cells_to_refine_by, int ctrb_len),
377
int (*compare_structures)(int *gamma_1, int *gamma_2, void *S1, void *S2, int degree),
378
int (*generate_children)(void *, aut_gp_and_can_lab *, iterator *),
379
void *(*apply_augmentation)(void *, void *, void *, int *, bint *),
380
void (*free_object)(void *),
381
void (*free_iter_data)(void *),
382
void (*free_aug)(void *),
383
void *(*canonical_parent)(void *child, void *parent, int *permutation, int *degree, bint *mem_err),
384
int max_depth, bint reduce_children, iterator *cangen_prealloc) except NULL:
385
"""
386
Canonical generation of isomorphism classes of objects.
387
388
INPUT:
389
390
- ``S`` - pointer to the seed object
391
392
- ``degree`` - the degree of S
393
394
- ``all_children_are_equivalent`` - pointer to a function
395
INPUT:
396
PS -- pointer to a partition stack
397
S -- pointer to the structure
398
OUTPUT:
399
bint -- returns True if it can be determined that all refinements below
400
the current one will result in an equivalent discrete partition
401
402
- ``refine_and_return_invariant`` - pointer to a function
403
INPUT:
404
PS -- pointer to a partition stack
405
S -- pointer to the structure
406
alpha -- an array consisting of numbers, which indicate the starting
407
positions of the cells to refine against (will likely be modified)
408
OUTPUT:
409
int -- returns an invariant under application of arbitrary permutations
410
411
- ``compare_structures`` - pointer to a function
412
INPUT:
413
gamma_1, gamma_2 -- (list) permutations of the points of S1 and S2
414
S1, S2 -- pointers to the structures
415
degree -- degree of gamma_1 and 2
416
OUTPUT:
417
int -- 0 if gamma_1(S1) = gamma_2(S2), otherwise -1 or 1 (see docs for cmp),
418
such that the set of all structures is well-ordered
419
420
- ``generate_children`` - pointer to a function
421
INPUT:
422
S -- pointer to the structure
423
group -- pointer to an automorphism group (canonical relabeling is not guaranteed)
424
it -- preallocated iterator struct
425
OUTPUT:
426
iterator * -- pointer to an iterator over inequivalent augmentations of S
427
428
- ``apply_augmentation`` - pointer to a function
429
INPUT:
430
parent -- object to augment
431
aug -- the augmentation
432
child -- space to put the augmented object
433
degree -- pointer to an int, function should store the degree of the augmented object here
434
mem_err -- pointer where memory error can be reported
435
OUTPUT:
436
pointer to child
437
438
- ``free_object`` - pointer to a function
439
INPUT:
440
child -- object to be freed
441
442
- ``free_iter_data`` - pointer to a function
443
INPUT:
444
data -- data part of an iterator struct
445
446
- ``free_aug`` - pointer to a function
447
INPUT:
448
aug -- augmentation to be freed
449
450
- ``canonical_parent`` - pointer to a function
451
INPUT:
452
child -- pointer to the structure
453
parent -- space to store the canonical parent
454
permutation -- array representing a relabeling of the child
455
degree -- pointer to store the degree of the parent
456
mem_err -- pointer for indicating memory errors
457
OUTPUT:
458
pointer to the parent
459
460
- ``max_depth`` - maximum depth of augmentations to be made from the seed object S
461
462
OUTPUT:
463
464
pointer to an iterator of objects
465
466
"""
467
if max_depth <= 1:
468
raise ValueError("Maximum depth (%d) must be at least two."%max_depth)
469
if reduce_children:
470
raise NotImplementedError
471
472
# Allocate memory for the arrays and check for failures:
473
cdef iterator *canonical_generator
474
cdef canonical_generator_data *cgd
475
if cangen_prealloc is NULL:
476
canonical_generator = <iterator *> sage_malloc(sizeof(iterator))
477
cgd = allocate_cgd(max_depth, degree)
478
if canonical_generator is NULL or cgd is NULL:
479
sage_free(canonical_generator)
480
deallocate_cgd(cgd)
481
raise MemoryError
482
cgd.dealloc = 1
483
else:
484
canonical_generator = cangen_prealloc
485
cgd = <canonical_generator_data *> canonical_generator.data
486
if cgd.degree_stack[0] != degree or cgd.allocd_levels < max_depth:
487
deallocate_cgd(cgd)
488
cgd = allocate_cgd(max_depth, degree)
489
if cgd is NULL:
490
raise MemoryError
491
cgd.dealloc = 0
492
canonical_generator.data = <void *> cgd
493
canonical_generator.next = &canonical_generator_next
494
495
cgd.max_level = max_depth
496
cgd.reduce_children = reduce_children
497
cgd.mem_err = 0
498
499
cgd.all_children_are_equivalent = all_children_are_equivalent
500
cgd.refine_and_return_invariant = refine_and_return_invariant
501
cgd.compare_structures = compare_structures
502
503
cgd.generate_children = generate_children
504
cgd.apply_augmentation = apply_augmentation
505
cgd.free_object = free_object
506
cgd.free_iter_data = free_iter_data
507
cgd.free_aug = free_aug
508
cgd.canonical_parent = canonical_parent
509
510
return canonical_generator
511
512
cdef iterator *start_canonical_generator(StabilizerChain *group, void *obj, int degree, iterator *canonical_generator) except NULL:
513
r"""
514
Given the containing group ``group`` and the seed object ``obj`` of degree
515
``degree``, initiate the canonical generator stored (and already allocated)
516
at ``canonical_generator``.
517
"""
518
cdef canonical_generator_data *cgd = <canonical_generator_data *> canonical_generator.data
519
if obj is NULL:
520
obj = cgd.object_stack[0]
521
else:
522
cgd.object_stack[0] = obj
523
cgd.level = 1
524
cgd.group = group
525
PS_unit_partition(cgd.ps_stack[0])
526
try:
527
cgd.aut_gp_stack[0] = get_aut_gp_and_can_lab(obj, cgd.ps_stack[0], degree,
528
cgd.all_children_are_equivalent,
529
cgd.refine_and_return_invariant,
530
cgd.compare_structures,
531
0, group, cgd.agcl_work_spaces[0], cgd.aut_gp_stack[0])
532
except MemoryError:
533
cgd.mem_err = 1
534
else:
535
cgd.mem_err |= cgd.generate_children(obj, cgd.aut_gp_stack[0], cgd.iterator_stack)
536
if cgd.mem_err:
537
raise MemoryError
538
539
return canonical_generator
540
541
542
543
544
545
546
547
548