Path: blob/master/src/sage/groups/perm_gps/partn_ref/canonical_augmentation.pyx
8815 views
r"""1Canonical augmentation23This module implements a general algorithm for generating isomorphism classes4of objects. The class of objects in question must be some kind of structure5which can be built up out of smaller objects by a process of augmentation,6and for which an automorphism is a permutation in `S_n` for some `n`. This7process consists of starting with a finite number of "seed objects" and8building up to more complicated objects by a sequence of "augmentations." It9should be noted that the word "canonical" in the term canonical augmentation10is used loosely. Given an object `X`, one must define a canonical parent11`M(X)`, which is essentially an arbitrary choice.1213The class of objects in question must satisfy the assumptions made in the14module ``automorphism_group_canonical_label``, in particular the three15custom functions mentioned there must be implemented:1617A. ``refine_and_return_invariant``:1819Signature:2021``int refine_and_return_invariant(PartitionStack *PS, void *S, int *cells_to_refine_by, int ctrb_len)``2223B. ``compare_structures``:2425Signature:2627``int compare_structures(int *gamma_1, int *gamma_2, void *S1, void *S2, int degree)``2829C. ``all_children_are_equivalent``:3031Signature:3233``bint all_children_are_equivalent(PartitionStack *PS, void *S)``343536In the following functions there is frequently a mem_err input. This is a37pointer to an integer which must be set to a nonzero value in case of an38allocation failure. Other functions have an int return value which serves the39same purpose. The idea is that if a memory error occurs, the canonical40generator should still be able to iterate over the objects already generated41before it terminates.4243More details about these functions can be found in that module. In addition,44several other functions must be implemented, which will make use of the45following::4647ctypedef struct iterator:48void *data49void *(*next)(void *data, int *degree, int *mem_err)5051The following functions must be implemented for each specific type of object to52be generated. Each function following which takes a ``mem_err`` variable as53input should make use of this variable.5455D. ``generate_children``:5657Signature:5859``int generate_children(void *S, aut_gp_and_can_lab *group, iterator *it)``6061This function receives a pointer to an iterator ``it``. The iterator62has two fields: ``data`` and ``next``. The function ``generate_children``63should set these two fields, returning 1 to indicate a memory error, or 064for no error.6566The function that ``next`` points to takes ``data`` as an argument, and67should return a (``void *``) pointer to the next object to be iterated. It68also takes a pointer to an int, and must update that int to reflect the69degree of each generated object. The objects to be iterated over should70satisfy the property that if `\gamma` is an automorphism of the parent71object `S`, then for any two child objects `C_1, C_2` given by the iterator,72it is not the case that `\gamma(C_1) = C_2`, where in the latter `\gamma` is73appropriately extended if necessary to operate on `C_1` and `C_2`. It is74essential for this iterator to handle its own ``data``. If the ``next``75function is called and no suitable object is yielded, a NULL pointer76indicates a termination of the iteration. At this point, the data pointed to77by the ``data`` variable should be cleared by the ``next`` function, because78the iterator struct itself will be deallocated.7980The ``next`` function must check ``mem_err[0]`` before proceeding. If it is81nonzero then the function should deallocate the iterator right away and82return NULL to end the iteration. This ensures that the canonical83augmenatation software will finish iterating over the objects found before84finishing, and the ``mem_err`` attribute of the ``canonical_generator_data``85will reflect this.8687The objects which the iterator generates can be thought of as augmentations,88which the following function must turn into objects.8990E. ``apply_augmentation``:9192Signature:9394``void *apply_augmentation(void *parent, void *aug, void *child, int *degree, bint *mem_err)``9596This function takes the ``parent``, applies the augmentation ``aug`` and97returns a pointer to the corresponding child object (freeing aug if98necessary). Should also update degree[0] to be the degree of the new child.99100F. ``free_object``:101102Signature:103104``void free_object(void *child)``105106This function is a simple deallocation function for children which are not107canonically generated, and therefore rejected in the canonical augmentation108process. They should deallocate the contents of ``child``.109110G. ``free_iter_data``:111112Signature:113114``void free_iter_data(void *data)``115116This function deallocates the data part of the iterator which is set up by117``generate_children``.118119H. ``free_aug``:120121Signature:122123``void free_aug(void *aug)``124125This function frees an augmentation as generated by the iterator returned126by ``generate_children``.127128I. ``canonical_parent``:129130Signature:131132``void *canonical_parent(void *child, void *parent, int *permutation, int *degree, bint *mem_err)``133134Apply the ``permutation`` to the ``child``, determine an arbitrary but fixed135parent, apply the inverse of ``permutation`` to that parent, and return the136resulting object. Must also set the integer ``degree`` points to to the137degree of the returned object.138139NOTE:140141It is a good idea to try to implement an augmentation scheme where the142degree of objects on each level of the augmentation tree is constant. The143iteration will be more efficient in this case, as the relevant work spaces144will never need to be reallocated. Otherwise, one should at least strive to145iterate over augmentations in such a way that all children of the same degree146are given in the same segment of iteration.147148DOCTEST:149sage: import sage.groups.perm_gps.partn_ref.canonical_augmentation150151REFERENCE:152153[1] McKay, Brendan D. Isomorph-free exhaustive generation. J Algorithms,154Vol. 26 (1998), pp. 306-324.155156"""157158#*****************************************************************************159# Copyright (C) 2010 - 2011 Robert L. Miller <[email protected]>160#161# Distributed under the terms of the GNU General Public License (GPL)162# http://www.gnu.org/licenses/163#*****************************************************************************164165include 'data_structures_pyx.pxi' # includes bitsets166167cdef void *canonical_generator_next(void *can_gen_data, int *degree, bint *mem_err):168r"""169This function is part of the iterator struct which will iterate over170objects. Return value of ``NULL`` indicates termination.171"""172# degree ignored!173cdef canonical_generator_data *cgd = <canonical_generator_data *> can_gen_data174cdef iterator *cur_iter175cdef void *next_candidate, *parent_cand, *aug176cdef int i, next_cand_deg, *isom, parent_cand_deg177cdef PartitionStack *part178cdef bint augmentation_is_canonical179cdef aut_gp_and_can_lab *output180cdef agcl_work_space *agcl_ws181cdef dc_work_space *dc_ws182183if cgd.level == 0:184if cgd.mem_err:185mem_err[0] = 1186if cgd.dealloc:187deallocate_cgd(cgd)188return NULL189190while cgd.level < cgd.max_level:191cur_iter = cgd.iterator_stack + cgd.level - 1192# getting next candidate at level cgd.level193aug = cur_iter.next( cur_iter.data, &cgd.degree_stack[cgd.level], &cgd.mem_err )194if aug is NULL:195# cur_iter has run out: backtrack by one, return the parent196cgd.level -= 1197return cgd.object_stack[cgd.level]198else:199next_candidate = cgd.apply_augmentation(cgd.object_stack[cgd.level-1],200aug, cgd.object_stack[cgd.level], &cgd.degree_stack[cgd.level],201&cgd.mem_err)202cgd.object_stack[cgd.level] = next_candidate203if cgd.mem_err: continue204next_cand_deg = cgd.degree_stack[cgd.level]205if cgd.agcl_work_spaces[cgd.level] is NULL:206# allocate a work space if it hasn't been allocated already207cgd.agcl_work_spaces[cgd.level] = allocate_agcl_work_space(next_cand_deg)208cgd.ps_stack[cgd.level] = PS_new(next_cand_deg, 0)209elif next_cand_deg != cgd.agcl_work_spaces[cgd.level].degree:210# in case the degree has changed, we need to reallocate the work211# space: this is why one should do augmentations in order of degree212deallocate_agcl_work_space(cgd.agcl_work_spaces[cgd.level])213deallocate_agcl_output(cgd.aut_gp_stack[cgd.level])214cgd.aut_gp_stack[cgd.level] = NULL215PS_dealloc(cgd.ps_stack[cgd.level])216cgd.agcl_work_spaces[cgd.level] = allocate_agcl_work_space(next_cand_deg)217cgd.ps_stack[cgd.level] = PS_new(next_cand_deg, 0)218if cgd.agcl_work_spaces[cgd.level] is NULL or cgd.ps_stack[cgd.level] is NULL:219cgd.mem_err = 1220continue221# see if next_candidate is canonically augmented222part = cgd.ps_stack[cgd.level]223PS_unit_partition(part)224try:225cgd.aut_gp_stack[cgd.level] = get_aut_gp_and_can_lab(next_candidate,226part, next_cand_deg, cgd.all_children_are_equivalent,227cgd.refine_and_return_invariant, cgd.compare_structures,2281, cgd.group, cgd.agcl_work_spaces[cgd.level], cgd.aut_gp_stack[cgd.level])229except MemoryError:230cgd.mem_err = 1231continue232parent_cand = cgd.canonical_parent(next_candidate, cgd.parent_stack[cgd.level-1],233cgd.aut_gp_stack[cgd.level].relabeling, &parent_cand_deg, &cgd.mem_err)234if cgd.mem_err:235continue236if parent_cand_deg != cgd.degree_stack[cgd.level-1]:237augmentation_is_canonical = 0238else:239if cgd.dc_work_spaces[cgd.level-1] is NULL:240# allocate a work space if it hasn't been allocated already241cgd.dc_work_spaces[cgd.level-1] = allocate_dc_work_space(next_cand_deg)242elif next_cand_deg != cgd.dc_work_spaces[cgd.level-1].degree:243# in case the degree has changed, we need to reallocate the work244# space: this is why one should do augmentations in order of degree245deallocate_dc_work_space(cgd.dc_work_spaces[cgd.level-1])246cgd.dc_work_spaces[cgd.level-1] = allocate_dc_work_space(next_cand_deg)247if cgd.dc_work_spaces[cgd.level-1] is NULL:248cgd.mem_err = 1249continue250part = cgd.ps_stack[cgd.level]251PS_unit_partition(part)252try:253augmentation_is_canonical = double_coset(cgd.object_stack[cgd.level-1],254parent_cand, part, NULL, next_cand_deg,255cgd.all_children_are_equivalent,256cgd.refine_and_return_invariant,257cgd.compare_structures,258cgd.aut_gp_stack[cgd.level].group, cgd.dc_work_spaces[cgd.level-1], NULL)259except MemoryError:260cgd.mem_err = 1261continue262if augmentation_is_canonical:263# the object is canonically augmented, so we add it to the chain264if cgd.level + 1 != cgd.max_level:265cgd.mem_err |= cgd.generate_children(next_candidate,266cgd.aut_gp_stack[cgd.level], cgd.iterator_stack+cgd.level)267if cgd.mem_err:268continue269cgd.level += 1270271if cgd.level == cgd.max_level:272# we're at the end of the chain, so just give the object273cgd.level -= 1274return cgd.object_stack[cgd.level]275276cdef canonical_generator_data *allocate_cgd(int max_depth, int degree):277r"""278Allocate the data part of the canonical generation iterator struct.279"""280cdef canonical_generator_data *cgd = <canonical_generator_data *> sage_malloc(sizeof(canonical_generator_data))281cdef PartitionStack *part282if cgd is NULL:283sage_free(cgd)284return NULL285cgd.object_stack = <void **> sage_malloc(max_depth * sizeof(void *))286cgd.degree_stack = <int *> sage_malloc(max_depth * sizeof(int))287cgd.iterator_stack = <iterator *> sage_malloc(max_depth * sizeof(iterator))288cgd.aut_gp_stack = <aut_gp_and_can_lab **> sage_malloc(max_depth * sizeof(aut_gp_and_can_lab *))289cgd.agcl_work_spaces = <agcl_work_space **> sage_malloc(max_depth * sizeof(agcl_work_space *))290cgd.dc_work_spaces = <dc_work_space **> sage_malloc(max_depth * sizeof(dc_work_space *))291cgd.ps_stack = <PartitionStack **> sage_malloc(max_depth * sizeof(PartitionStack *))292cgd.aug_stack = <void **> sage_malloc(max_depth * sizeof(void *))293cgd.parent_stack = <void **> sage_malloc(max_depth * sizeof(void *))294part = PS_new(degree, 1)295cdef agcl_work_space *agclws = allocate_agcl_work_space(degree)296cdef aut_gp_and_can_lab *output = allocate_agcl_output(degree)297if cgd.object_stack is NULL or cgd.degree_stack is NULL or \298cgd.iterator_stack is NULL or cgd.aut_gp_stack is NULL or \299cgd.agcl_work_spaces is NULL or cgd.dc_work_spaces is NULL or \300cgd.ps_stack is NULL or cgd.aug_stack is NULL or \301cgd.parent_stack is NULL or agclws is NULL or output is NULL:302sage_free(cgd.object_stack)303sage_free(cgd.degree_stack)304sage_free(cgd.iterator_stack)305sage_free(cgd.aut_gp_stack)306sage_free(cgd.agcl_work_spaces)307sage_free(cgd.dc_work_spaces)308sage_free(cgd.ps_stack)309sage_free(cgd.aug_stack)310sage_free(cgd.parent_stack)311sage_free(cgd)312PS_dealloc(part)313deallocate_agcl_work_space(agclws)314deallocate_agcl_output(output)315return NULL316317cdef int i318cgd.allocd_levels = max_depth319for i from 0 <= i < max_depth:320cgd.agcl_work_spaces[i] = NULL321cgd.dc_work_spaces[i] = NULL322cgd.aut_gp_stack[i] = NULL323cgd.ps_stack[i] = NULL324cgd.aug_stack[i] = NULL325cgd.parent_stack[i] = NULL326cgd.object_stack[i] = NULL327cgd.iterator_stack[i].data = NULL328cgd.agcl_work_spaces[0] = agclws329cgd.aut_gp_stack[0] = output330cgd.ps_stack[0] = part331332cgd.degree_stack[0] = degree333return cgd334335cdef void deallocate_cgd(canonical_generator_data *cgd):336r"""337Deallocate the data part of the canonical generation iterator struct.338"""339if cgd is NULL:340return341cdef int i342cdef void *thingy343cdef void (*clearer)(void*)344for i from 0 <= i < cgd.allocd_levels:345if cgd.agcl_work_spaces[i] is not NULL:346deallocate_agcl_work_space(cgd.agcl_work_spaces[i])347if cgd.ps_stack[i] is not NULL:348PS_dealloc(cgd.ps_stack[i])349if cgd.dc_work_spaces[i] is not NULL:350deallocate_dc_work_space(cgd.dc_work_spaces[i])351if cgd.aut_gp_stack[i] is not NULL:352deallocate_agcl_output(cgd.aut_gp_stack[i])353if cgd.object_stack[i] is not NULL:354cgd.free_object(cgd.object_stack[i])355if cgd.parent_stack[i] is not NULL:356cgd.free_object(cgd.parent_stack[i])357if cgd.aug_stack[i] is not NULL:358cgd.free_aug(cgd.aug_stack[i])359if cgd.iterator_stack[i].data is not NULL:360cgd.free_iter_data(cgd.iterator_stack[i].data)361sage_free(cgd.object_stack)362sage_free(cgd.degree_stack)363sage_free(cgd.iterator_stack)364sage_free(cgd.aut_gp_stack)365sage_free(cgd.agcl_work_spaces)366sage_free(cgd.dc_work_spaces)367sage_free(cgd.ps_stack)368sage_free(cgd.aug_stack)369sage_free(cgd.parent_stack)370sage_free(cgd)371372cdef iterator *setup_canonical_generator(int degree,373bint (*all_children_are_equivalent)(PartitionStack *PS, void *S),374int (*refine_and_return_invariant)\375(PartitionStack *PS, void *S, int *cells_to_refine_by, int ctrb_len),376int (*compare_structures)(int *gamma_1, int *gamma_2, void *S1, void *S2, int degree),377int (*generate_children)(void *, aut_gp_and_can_lab *, iterator *),378void *(*apply_augmentation)(void *, void *, void *, int *, bint *),379void (*free_object)(void *),380void (*free_iter_data)(void *),381void (*free_aug)(void *),382void *(*canonical_parent)(void *child, void *parent, int *permutation, int *degree, bint *mem_err),383int max_depth, bint reduce_children, iterator *cangen_prealloc) except NULL:384"""385Canonical generation of isomorphism classes of objects.386387INPUT:388389- ``S`` - pointer to the seed object390391- ``degree`` - the degree of S392393- ``all_children_are_equivalent`` - pointer to a function394INPUT:395PS -- pointer to a partition stack396S -- pointer to the structure397OUTPUT:398bint -- returns True if it can be determined that all refinements below399the current one will result in an equivalent discrete partition400401- ``refine_and_return_invariant`` - pointer to a function402INPUT:403PS -- pointer to a partition stack404S -- pointer to the structure405alpha -- an array consisting of numbers, which indicate the starting406positions of the cells to refine against (will likely be modified)407OUTPUT:408int -- returns an invariant under application of arbitrary permutations409410- ``compare_structures`` - pointer to a function411INPUT:412gamma_1, gamma_2 -- (list) permutations of the points of S1 and S2413S1, S2 -- pointers to the structures414degree -- degree of gamma_1 and 2415OUTPUT:416int -- 0 if gamma_1(S1) = gamma_2(S2), otherwise -1 or 1 (see docs for cmp),417such that the set of all structures is well-ordered418419- ``generate_children`` - pointer to a function420INPUT:421S -- pointer to the structure422group -- pointer to an automorphism group (canonical relabeling is not guaranteed)423it -- preallocated iterator struct424OUTPUT:425iterator * -- pointer to an iterator over inequivalent augmentations of S426427- ``apply_augmentation`` - pointer to a function428INPUT:429parent -- object to augment430aug -- the augmentation431child -- space to put the augmented object432degree -- pointer to an int, function should store the degree of the augmented object here433mem_err -- pointer where memory error can be reported434OUTPUT:435pointer to child436437- ``free_object`` - pointer to a function438INPUT:439child -- object to be freed440441- ``free_iter_data`` - pointer to a function442INPUT:443data -- data part of an iterator struct444445- ``free_aug`` - pointer to a function446INPUT:447aug -- augmentation to be freed448449- ``canonical_parent`` - pointer to a function450INPUT:451child -- pointer to the structure452parent -- space to store the canonical parent453permutation -- array representing a relabeling of the child454degree -- pointer to store the degree of the parent455mem_err -- pointer for indicating memory errors456OUTPUT:457pointer to the parent458459- ``max_depth`` - maximum depth of augmentations to be made from the seed object S460461OUTPUT:462463pointer to an iterator of objects464465"""466if max_depth <= 1:467raise ValueError("Maximum depth (%d) must be at least two."%max_depth)468if reduce_children:469raise NotImplementedError470471# Allocate memory for the arrays and check for failures:472cdef iterator *canonical_generator473cdef canonical_generator_data *cgd474if cangen_prealloc is NULL:475canonical_generator = <iterator *> sage_malloc(sizeof(iterator))476cgd = allocate_cgd(max_depth, degree)477if canonical_generator is NULL or cgd is NULL:478sage_free(canonical_generator)479deallocate_cgd(cgd)480raise MemoryError481cgd.dealloc = 1482else:483canonical_generator = cangen_prealloc484cgd = <canonical_generator_data *> canonical_generator.data485if cgd.degree_stack[0] != degree or cgd.allocd_levels < max_depth:486deallocate_cgd(cgd)487cgd = allocate_cgd(max_depth, degree)488if cgd is NULL:489raise MemoryError490cgd.dealloc = 0491canonical_generator.data = <void *> cgd492canonical_generator.next = &canonical_generator_next493494cgd.max_level = max_depth495cgd.reduce_children = reduce_children496cgd.mem_err = 0497498cgd.all_children_are_equivalent = all_children_are_equivalent499cgd.refine_and_return_invariant = refine_and_return_invariant500cgd.compare_structures = compare_structures501502cgd.generate_children = generate_children503cgd.apply_augmentation = apply_augmentation504cgd.free_object = free_object505cgd.free_iter_data = free_iter_data506cgd.free_aug = free_aug507cgd.canonical_parent = canonical_parent508509return canonical_generator510511cdef iterator *start_canonical_generator(StabilizerChain *group, void *obj, int degree, iterator *canonical_generator) except NULL:512r"""513Given the containing group ``group`` and the seed object ``obj`` of degree514``degree``, initiate the canonical generator stored (and already allocated)515at ``canonical_generator``.516"""517cdef canonical_generator_data *cgd = <canonical_generator_data *> canonical_generator.data518if obj is NULL:519obj = cgd.object_stack[0]520else:521cgd.object_stack[0] = obj522cgd.level = 1523cgd.group = group524PS_unit_partition(cgd.ps_stack[0])525try:526cgd.aut_gp_stack[0] = get_aut_gp_and_can_lab(obj, cgd.ps_stack[0], degree,527cgd.all_children_are_equivalent,528cgd.refine_and_return_invariant,529cgd.compare_structures,5300, group, cgd.agcl_work_spaces[0], cgd.aut_gp_stack[0])531except MemoryError:532cgd.mem_err = 1533else:534cgd.mem_err |= cgd.generate_children(obj, cgd.aut_gp_stack[0], cgd.iterator_stack)535if cgd.mem_err:536raise MemoryError537538return canonical_generator539540541542543544545546547548