Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pyx
8817 views
1
r"""
2
Automorphism groups and canonical labels.
3
4
For details see section 3 of [Feu13]_.
5
6
Definitions
7
###########
8
9
Let `G` be a group which acts on a finite set `X` and let `\mathcal{L}(G)`
10
denote the set of subgroups of `G`. We say that
11
a mapping `Can_G: X \rightarrow X \times G \times \mathcal{L}(G),
12
x \mapsto \left( CF_G(x), T_G(x), G_x \right)` with
13
14
- `CF_G(gx) = CF_G(x)` for all `x \in X` and all `g\in G` (canonical form)
15
- `CF_G(x) = T_G(x) x` for all `x \in X` (transporter element)
16
- `G_x = \{g \in G \mid gx=x\}` (stabilizer)
17
18
is a canonization algorithm.
19
20
Let `H` be another group acting on a set `Y`. A pair `(\theta, \omega)` with
21
a homomorphism `\theta: G \rightarrow H` and `\omega: X \rightarrow Y`
22
is called a homomorphism of group actions if `\omega(gx) =
23
\theta(g)\omega(x)` for all `g \in G`, for all `x \in X`. In the case that
24
`G=H` and `\theta = id_G`, we call `\omega` a `G`-homomorphism.
25
26
A standard
27
partition is a sequence `C = (C_1, ..., C_k)` of subsets of `[n] = \{0, \ldots,
28
n-1\}` where the `C_i` are
29
disjoint, consecutive intervals and their union is equal to `[n]`. Each element
30
is
31
called a cell and the elements lying in a cell of cardinality 1 are called
32
fixed points. Let `I_C` be a sequence of all fixed points of `C` (the exact
33
ordering is important, but will be determined by the algorithm).
34
The stabilizer of `(S_n)_C` is a standard Young subgroup of `S_n`.
35
36
37
Requirements
38
############
39
40
In the following we want to present an efficient algorithm for the canonization
41
problem in the case that the group action is of type `G \rtimes S_n` on
42
`X^n`. For this goal, we suppose that the group action of `G = G \rtimes \{id\}`
43
on `X^n` has the following properties:
44
45
-- The action of `G \rtimes \{id\}` on `X^n` is given by a direct product of
46
(maybe different) group actions of `G` on `X`.
47
48
-- For every `i \in \{0, \ldots, n-1\}` let `\Pi^{(i)}: X^n \rightarrow X`
49
be the projection to the `i`-th coordinate. The function `\Pi^{(i)}`
50
is an invariant under the action of the subgroup
51
`(S_n)_i = \{\pi \in S_n \mid \pi(i) = i\}`.
52
53
Let `(i_0, \ldots, i_{k-1}) = I \subseteq \{0, \ldots, n-1\}` be an
54
injective sequence. We define
55
`\Pi^{(I)}(x) := (\Pi^{(i_0)}(x), \ldots, \Pi^{(i_{k-1})}(x))`.
56
We call an element `x \in X` `I`-semicanonical if and only if
57
`\Pi^{(I)}(x) \leq \Pi^{(I)}(gx)` for all `g \in G`.
58
59
For each `x \in X^n` there is obviously an `I`-semicanonical element in the orbit `Gx`.
60
Suppose that `x` is already `I`-semicanonical
61
and `J` is an injective sequence which extends
62
`I` by one further coordinate `i`, then we call the process of computing
63
a `J`-semicanonical representative the inner minimization at position `i`.
64
65
This could be described by a group action of the stabilizer `G_{\Pi^{(I)}(x)}`.
66
on `x_i`.
67
68
69
Partitions and refinements
70
##########################
71
72
Now the idea for the canonization algorithm for the action of `G \rtimes
73
S_n` on `X^n` can be formulated as follows.
74
Let us suppose that we want
75
to canonize `x\in X^n`. We build a
76
backtrack tree in the following manner:
77
78
- each node gets represented by a quadruple `(C, I_C, y, G_{\Pi^{(I_C)}(y))})`
79
where `C` is an ordered partition, `I_C` a subsequence of its fixed
80
points, `y \in X^n` is `I_C`-semicanonical
81
and `G_{\Pi^{(I_C)}(y))}` is the remaining group of the inner action
82
- the root node is on level `0` and gets represented by `(([n]), (), x, G)`
83
- nodes with `| I_C | = n`, i.e. all coordinates are fixed, will be leafs
84
and we have `G_{\Pi^{(I_C)}(y))} = G_y`
85
- the children of some other node `(C, I_C, y, G_{\Pi^{(I_C)}(y))})` are
86
constructed by
87
* picking one cell `C_j` with `| C_j |` (the choice has to be invariant in
88
some sense)
89
* separating the point `\min(C_j)` from its cell `C_j` which leads to the partition
90
`D` and the sequence `I_D`
91
* building the `| C_j |` successors `(D, I_D, z_k, G_{\Pi^{(I_D)}(z_k))})`
92
by applying the permutation
93
`\sigma_k := (\min(C_j), k)` for all `k \in C_j` to `y` and computing an
94
`I_D`-semicanonical representative `z_k` of `\sigma_k y`
95
- if the projection `\Pi^{(I_D)}(z_k)` is not optimal then we stop the
96
backtracking in this node (i.e. prune the subtree below this node)
97
- the tree is traversed in a depth-first manner
98
- the smallest reached leaf node is defined to be the canonical form
99
(the defined ordering takes all comparisons in predecessors also into account)
100
- equal leaf nodes correspond to automorphism of `x`, they could be used to
101
define further pruning methods.
102
103
We are able to speed up the computation by making use of refinements
104
(i.e. `(S_n)_C`-homomorphisms). Suppose we constructed a node
105
`(C, I_C, y, G_{\Pi^{(I_C)}(y))})`. We define
106
`Y := \{z \in X^n \mid \Pi^{(I_C)}(z) = \Pi^{(I_C)}(y)\}`
107
and we search for functions `\omega: Y \rightarrow \mathbb{Z}^n` which are
108
constant on the orbits of `G_{\Pi^{(I_C)}(y)}` and compatible with the action
109
of `(S_n)_C` on both sides. Then we compute a permutation `\pi \in (S_n)_C`
110
which cell-wisely sorts the entries of `\omega(y)`. Afterwards we are allowed
111
to reduce ourselves to the stabilizer `(S_n)_D` of `\omega(\pi y)`. If this
112
stabilizer contains further fixed points, they are appended (in some fixed
113
order) to the sequence `I_C` in order to define `I_D`. Furthermore the element
114
`y` gets updated by an `I_D`-semicanonical representative of this orbit. These
115
refinements could also be applied iteratively.
116
117
As said before, the backtrack search tree is traversed in a depth-first search
118
manner, hence we maintain a candidate for the result. Each newly constructed
119
node is compared to this candidate. If it
120
- compares larger, then we can prune the
121
subtree rooted in this node
122
- compares smaller, then we replace the candidate by the actual node
123
(more precisely by the next leaf node we will construct, for this we
124
use the boolean flag ``_is_candidate_initialized``)
125
- compares equal we just continue.
126
127
128
Implementation Details
129
######################
130
131
The class :class:`~sage.groups.perm_gps.partn_ref2.PartitionRefinement_generic`
132
provides a framework for such a backtracking.
133
It maintains the partition `C` and the sequence `I_C`.
134
Instead of permuting the elements `x \in X`
135
during the construction of nodes and in the refinements,
136
we use a ``PartitionStack``,
137
see :mod:`sage.groups.perm_gps.partn_ref.automorphism_group_canonical_label`.
138
Hence, `C = (C_1, ..., C_k)` and `I_C` will be maintained implicitly. If `\pi \in S_n` is the permutation
139
applied to this node, then we will store `\pi(I_C)` and in the partition stack
140
we will store `(\pi(C_1), ..., \pi(C_k))`.
141
142
The class further decides when we have to
143
call the inner minimization and which subtrees could be pruned by the use
144
of automorphisms, see :class:`sage.groups.perm_gps.partn_ref2.LabelledBranching` for more details.
145
146
Derived classes
147
###############
148
149
Derived classes have to implement the following methods, see the
150
method descriptions for more information:
151
- Cython Functions:
152
- bint _inner_min_(self, int pos, bint * inner_group_changed)
153
- bint _refine(self, bint * part_changed)
154
- tuple _store_state_(self)
155
- void _restore_state_(self, tuple act_state)
156
- void _store_best_(self)
157
- void _latex_act_node(self, str comment=None) (to use the implemented debugging method
158
via printing the backtrack tree using latex)
159
- bint _minimization_allowed_on_col(self, int pos)
160
161
- Python functions:
162
- get_canonical_form(self)
163
- get_transporter(self)
164
- get_autom_gens(self)
165
166
AUTHORS:
167
168
- Thomas Feulner (2012-11-15): initial version
169
170
REFERENCES:
171
172
.. [Feu13] Feulner, Thomas, "Eine kanonische Form
173
zur Darstellung aequivalenter Codes --
174
Computergestuetzte Berechnung und ihre Anwendung
175
in der Codierungstheorie, Kryptographie und Geometrie --",
176
Dissertation, University of Bayreuth, 2013.
177
"""
178
179
#*****************************************************************************
180
# Copyright (C) 2012 Thomas Feulner <[email protected]>
181
#
182
# Distributed under the terms of the GNU General Public License (GPL)
183
# as published by the Free Software Foundation; either version 2 of
184
# the License, or (at your option) any later version.
185
# http://www.gnu.org/licenses/
186
#*****************************************************************************
187
188
include 'sage/groups/perm_gps/partn_ref/data_structures_pyx.pxi'
189
190
from copy import copy
191
192
cdef tuple PS_refinement(PartitionStack * part, long *refine_vals, long *best,
193
int begin, int end,
194
bint * cand_initialized, bint *changed_partition):
195
"""
196
Refine the partition stack by the values given by ``refine_vals``.
197
We also compare our actual refinement result with the vector ``best`` in the
198
case that ``cand_initialized`` is set to ``True``.
199
200
If the current (canonical) vector is smaller than ``best``, then we will
201
replace the values stored in ``best`` and set ``cand_initialized``
202
to ``False``.
203
204
The function will set ``changed_partition`` to ``True`` if and only if
205
some cell was refined.
206
207
The function will return the boolean value ``False`` if and only if
208
the (canonical) vector is larger than ``best``. The second entry of the
209
tuple will contain all new singleton cells, which appeared in the
210
refinement of ``refine_vals``.
211
"""
212
global global_refine_vals_array
213
214
changed_partition[0] = False
215
cdef int i = begin, loc_begin = begin, j, last_val, act_val
216
cdef list newly_fixed = []
217
218
while i < end:
219
if part.levels[i] <= part.depth:
220
# [loc_begin, ..., i] is a block of the old partition
221
if i > loc_begin:
222
global_refine_vals_array = refine_vals;
223
qsort(part.entries + loc_begin, (i+1)-loc_begin, sizeof(int), my_comp_func);
224
last_val = refine_vals[ part.entries[loc_begin] ]
225
j = loc_begin
226
while 1:
227
act_val = refine_vals[ part.entries[j] ]
228
if act_val != last_val:
229
if j == 1 or part.levels[j - 2] <= part.depth:
230
# a block of size 1
231
newly_fixed.append(part.entries[j - 1])
232
233
part.levels[j - 1] = part.depth # a new block
234
last_val = act_val
235
changed_partition[0] = True
236
237
# compare with the candidate
238
if cand_initialized[0]:
239
if act_val < best[j]:
240
cand_initialized[0] = False
241
best[j] = act_val
242
if act_val > best[j]:
243
return (False, [])
244
else:
245
best[j] = act_val
246
247
if j == i: # check for end of while
248
if loc_begin != i and part.levels[i - 1] <= part.depth:
249
# this is a singleton
250
newly_fixed.append(part.entries[i])
251
break
252
253
j += 1
254
255
256
loc_begin = i + 1
257
i += 1
258
return (True, newly_fixed)
259
260
cdef class _BestValStore:
261
r"""
262
This class implements a dynamic array of integer vectors of length `n`.
263
"""
264
def __cinit__(self, int n):
265
r"""
266
Initialization.
267
268
EXAMPLES::
269
270
sage: from sage.groups.perm_gps.partn_ref2.refinement_generic import _BestValStore
271
sage: B = _BestValStore(5)
272
"""
273
self.default_data_length = n
274
self.storage_length = 0
275
self.values = <long *> sage_malloc(0)
276
if self.values is NULL:
277
raise MemoryError('allocating _BestValStore')
278
279
def __dealloc__(self):
280
"""
281
Dealloc.
282
"""
283
sage_free(self.values)
284
285
cdef long * get_row(self, int i):
286
r"""
287
Return the i-th row.
288
289
If this row is not yet initialized (i >= self.storage_length), then
290
we extend the array and fill the new positions
291
with the all zero vector.
292
"""
293
if i >= self.storage_length:
294
self.values = < long *> sage_realloc(self.values, (i + 1)* self.default_data_length * sizeof(long))
295
if self.values is NULL:
296
raise MemoryError('resizing _BestValStore')
297
self.storage_length = i + 1
298
return self.values+(i*self.default_data_length)
299
300
cdef class LabelledBranching:
301
r"""
302
This class implements complete labelled branchings.
303
304
To each subgroup of `S_n` we can uniquely assign a directed forest
305
on `n` vertices, where the
306
edges `(i,j)` fulfill `i<j` and some further conditions on the edge labels
307
(which we do not want to state).
308
This graph is called a complete labelled branching.
309
310
The edges `(i,j)` will be stored in a vector ``father`` with
311
``father_j = -1`` if `j` is a root of a tree, and
312
``father_j = i`` if `i` is the predecessor of `j`, i.e. is an `(i,j)` is an edge.
313
314
EXAMPLES::
315
316
sage: from sage.groups.perm_gps.partn_ref2.refinement_generic import LabelledBranching
317
sage: L = LabelledBranching(3)
318
sage: L.add_gen(libgap.eval('(1,2,3)'))
319
sage: L.get_order()
320
3
321
sage: L.small_generating_set()
322
[(1,2,3)]
323
"""
324
325
def __cinit__(self, n):
326
r"""
327
Initialization by the identity subgroup.
328
329
EXAMPLES::
330
331
sage: from sage.groups.perm_gps.partn_ref2.refinement_generic import LabelledBranching
332
sage: L = LabelledBranching(3)
333
"""
334
from sage.libs.gap.libgap import libgap
335
336
self.n = n
337
self.group = libgap.eval("Group(())")
338
self.ClosureGroup = libgap.eval("ClosureGroup")
339
self.father = < int *> sage_malloc(n * sizeof(int))
340
if self.father is NULL:
341
raise MemoryError('allocating LabelledBranching')
342
self.act_perm = < int *> sage_malloc(n * sizeof(int))
343
if self.act_perm is NULL:
344
sage_free(self.father)
345
raise MemoryError('allocating LabelledBranching')
346
cdef int i
347
for i from 0 <= i < self.n:
348
self.father[i] = -1
349
350
def __dealloc__(self):
351
"""
352
Dealloc.
353
"""
354
sage_free(self.father)
355
sage_free(self.act_perm)
356
357
cpdef add_gen(self, GapElement_Permutation gen):
358
r"""
359
Add a further generator to the group and
360
update the complete labeled branching.
361
362
EXAMPLES::
363
364
sage: from sage.groups.perm_gps.partn_ref2.refinement_generic import LabelledBranching
365
sage: L = LabelledBranching(3)
366
sage: L.add_gen(libgap.eval('(1,2,3)'))
367
"""
368
from sage.libs.gap.libgap import libgap
369
370
self.group = self.ClosureGroup(self.group, gen)
371
libgap.StabChain(self.group)
372
cdef GapElement h = self.group
373
cdef int i, i_1, j, f
374
375
if libgap.IsTrivial(h).sage():
376
return
377
for i in range(self.n):
378
self.father[i] = -1
379
380
while 1:
381
i = libgap.SmallestMovedPoint(h).sage()
382
i_1 = i - 1
383
f = self.father[i_1]
384
for j in libgap.Orbit(h, i).sage():
385
self.father[j - 1] = i_1
386
self.father[i_1] = f
387
h = libgap.Stabilizer(h, i)
388
if libgap.IsTrivial(h).sage():
389
break
390
391
cdef bint has_empty_intersection(self, PartitionStack * part):
392
r"""
393
Pruning by automorphisms.
394
395
The partition stack ``part`` encodes a right coset `G\pi` where
396
`G <= S_n` is a standard Young subgroup. On the other hand
397
this complete labeled branching of the group `A` naturally defines
398
a transversal of left coset representatives `T` of `S_n/A`.
399
400
This function returns true if and only if `T \cap G\pi` is empty, see
401
[Feu13]_.
402
"""
403
cdef int i, j, k
404
for i from 0 <= i < self.n:
405
self.act_perm[part.entries[i]] = i
406
407
for i from 0 <= i < self.n:
408
j = self.father[i]
409
if j != -1:
410
j = self.act_perm[j]
411
k = self.act_perm[i]
412
413
while k < j:
414
if part.levels[k] <= part.depth:
415
return 1 # True
416
k += 1
417
return 0 # False
418
419
def small_generating_set(self):
420
r"""
421
Return a small set of generators of the group stored by ``self``.
422
423
EXAMPLES::
424
425
sage: from sage.groups.perm_gps.partn_ref2.refinement_generic import LabelledBranching
426
sage: L = LabelledBranching(3)
427
sage: L.small_generating_set()
428
[]
429
sage: L.add_gen(libgap.eval('(1,2,3)'))
430
sage: L.small_generating_set()
431
[(1,2,3)]
432
"""
433
from sage.libs.gap.libgap import libgap
434
435
return libgap.SmallGeneratingSet(self.group).sage()
436
437
def get_order(self):
438
r"""
439
Return the order of the group stored by ``self``.
440
441
EXAMPLES::
442
443
sage: from sage.groups.perm_gps.partn_ref2.refinement_generic import LabelledBranching
444
sage: L = LabelledBranching(3)
445
sage: L.get_order()
446
1
447
sage: L.add_gen(libgap.eval('(1,2,3)'))
448
sage: L.get_order()
449
3
450
"""
451
from sage.libs.gap.libgap import libgap
452
453
return libgap.Order(self.group).sage()
454
455
cdef class PartitionRefinement_generic:
456
r"""
457
Implements the partition and refinement framework for
458
group actions `G \rtimes S_n` on `X^n` as described in
459
:mod:`sage.groups.perm_gps.partn_ref2.refinement_generic`.
460
"""
461
462
def __cinit__(self, n, *args, **kwds):
463
"""
464
Init.
465
466
EXAMPLES::
467
468
sage: from sage.groups.perm_gps.partn_ref2.refinement_generic import PartitionRefinement_generic
469
sage: P = PartitionRefinement_generic(5)
470
"""
471
self._n = n # int
472
self._is_candidate_initialized = False
473
474
self._known_automorphisms = LabelledBranching(n)
475
self._refine_vals_scratch = <long *> sage_malloc(n * sizeof(long))
476
if self._refine_vals_scratch is NULL:
477
raise MemoryError('allocating PartitionRefinement_generic')
478
479
self._latex_debug_string = ""
480
self._fixed_not_minimized = []
481
self._fixed_minimized = []
482
self._allowance_best = []
483
self._nr_of_inner_min_unmin_calls=0
484
485
def __dealloc__(self):
486
"""
487
Dealloc.
488
"""
489
PS_dealloc(self._part)
490
sage_free(self._refine_vals_scratch)
491
492
#####################################################################
493
# The following functions have to be implemented by derived classes
494
#####################################################################
495
cdef bint _inner_min_(self, int pos, bint * inner_group_changed):
496
"""
497
Minimize the node by the action of the inner group on the i-th position.
498
499
INPUT:
500
501
- `pos` - A position in `range(self.n)`
502
- `inner_group_changed` - will be set to true if `G_y` got smaller
503
504
OUTPUT:
505
506
- `True` if and only if the actual node compares less or equal to
507
the candidate for the canonical form.
508
"""
509
raise NotImplementedError
510
511
cdef bint _refine(self, bint *part_changed, bint inner_group_changed, bint first_step):
512
r"""
513
Refine the partition ``self._part``.
514
515
Set ``part_changed`` to ``True`` if and only if the refinement let
516
to a smaller subgroup of `S_n`. This function also has to take
517
care on ``self._is_candidate_initialized``.
518
519
OUTPUT:
520
521
- `False` only if the actual node compares larger than the candidate
522
for the canonical form.
523
"""
524
raise NotImplementedError
525
526
cdef tuple _store_state_(self):
527
r"""
528
Store the current state of the node to a tuple, such that it can be
529
restored by :meth:`_restore_state_`.
530
"""
531
raise NotImplementedError
532
533
cdef void _restore_state_(self, tuple act_state):
534
r"""
535
The inverse of :meth:`_store_state_`.
536
"""
537
raise NotImplementedError
538
539
cdef void _store_best_(self):
540
r"""
541
Store this leaf node as the actual best candidate for the canonical form.
542
"""
543
raise NotImplementedError
544
545
cdef bint _minimization_allowed_on_col(self, int pos):
546
r"""
547
Decide if we are allowed to perform the inner minimization on position
548
``pos`` which is supposed to be a singleton.
549
"""
550
raise NotImplementedError
551
552
def get_canonical_form(self):
553
r"""
554
Return the canonical form we have computed.
555
556
EXAMPLES::
557
558
sage: from sage.groups.perm_gps.partn_ref2.refinement_generic import PartitionRefinement_generic
559
sage: P = PartitionRefinement_generic(5)
560
sage: P.get_canonical_form()
561
Traceback (most recent call last):
562
...
563
NotImplementedError
564
"""
565
raise NotImplementedError
566
567
def get_transporter(self):
568
r"""
569
Return the transporter element we have computed.
570
571
EXAMPLES::
572
573
sage: from sage.groups.perm_gps.partn_ref2.refinement_generic import PartitionRefinement_generic
574
sage: P = PartitionRefinement_generic(5)
575
sage: P.get_transporter()
576
Traceback (most recent call last):
577
...
578
NotImplementedError
579
"""
580
raise NotImplementedError
581
582
def get_autom_gens(self):
583
r"""
584
Return a list of generators we have computed.
585
586
EXAMPLES::
587
588
sage: from sage.groups.perm_gps.partn_ref2.refinement_generic import PartitionRefinement_generic
589
sage: P = PartitionRefinement_generic(5)
590
sage: P.get_autom_gens()
591
Traceback (most recent call last):
592
...
593
NotImplementedError
594
"""
595
raise NotImplementedError
596
597
def get_autom_order_permutation(self):
598
r"""
599
Return the order of the automorphism group we have computes
600
601
EXAMPLES::
602
603
sage: from sage.groups.perm_gps.partn_ref2.refinement_generic import PartitionRefinement_generic
604
sage: P = PartitionRefinement_generic(5)
605
sage: P.get_autom_order_permutation()
606
1
607
"""
608
return self._known_automorphisms.get_order()
609
610
###############################################
611
# THE BACKTRACK ALGORITHM:
612
###############################################
613
cdef void _init_partition_stack(self, list partition):
614
r"""
615
Initialize the partition stack.
616
617
If the list ``partition`` is not ``None``, it must
618
represent a coloring of the coordinates `\{0, \ldots, n-1\}`, i.e.
619
a list of disjoint lists.
620
"""
621
cdef bint inner_group_changed = False
622
if partition is None:
623
self._part = PS_new(self._n, 1)
624
else:
625
self._part = PS_from_list(partition)
626
# minimize singletons which already exist in this partition
627
self._fixed_not_minimized = PS_singletons(self._part)
628
self._inner_min_unminimized(&inner_group_changed)
629
if self._part is NULL:
630
sage_free(self._refine_vals_scratch)
631
raise MemoryError('initializing the partition stack')
632
633
cdef void _start_Sn_backtrack(self):
634
r"""
635
Start the partition refinement algorithm.
636
"""
637
self._init_latex()
638
self._backtrack(True)
639
self._finish_latex()
640
641
cdef void _backtrack(self, bint first_step = False):
642
r"""
643
Backtracking with pruning.
644
645
- We first check if the subtree below this node contains any
646
valuable information otherwise we return. This test is based on
647
the subgroup of already known automorphisms.
648
- Perform a refinement. If this node is a leaf, we do all necessary updates.
649
- Else, we split the points of a fixed cell and backtrack recursively.
650
This leads to a depth-first-search construction of the search tree.
651
"""
652
self._latex_new_lvl()
653
cdef bint n_partition_changed = False, inner_group_changed=False
654
655
if self._cut_by_known_automs():
656
self._latex_act_node("autcut")
657
self._latex_finish_lvl()
658
return
659
660
if not self._inner_min_unminimized(&inner_group_changed):
661
self._latex_act_node("inner-min")
662
self._latex_finish_lvl()
663
return
664
665
if not self._refine(&n_partition_changed, inner_group_changed, first_step):
666
self._latex_finish_lvl()
667
return
668
669
if PS_is_discrete(self._part):
670
self._leaf_computations()
671
self._latex_finish_lvl()
672
return
673
674
# store some important states of the current node
675
cdef int old_partition_depth = self._part.depth
676
cdef int old_fixed_minimized_len = len(self._fixed_minimized)
677
cdef int old_nr_of_inner_min_unmin_calls = self._nr_of_inner_min_unmin_calls
678
cdef list old_fixed_not_minimized = copy(self._fixed_not_minimized)
679
cdef tuple act_state = self._store_state_()
680
681
# determine a cell C of the partition stack, which we will individualize
682
cdef bitset_t b
683
bitset_init(b, self._n)
684
PS_move_all_mins_to_front(self._part)
685
cdef int second_pos
686
cdef int smallest = PS_first_smallest(self._part, b, &second_pos,
687
self)
688
if second_pos != -1:
689
self._fixed_not_minimized.append(second_pos)
690
cdef int pos = smallest
691
self._latex_act_node()
692
693
# recursively individualize the elements of the cell
694
while 1: # run over all elements in this cell
695
self._part.depth = old_partition_depth + 1
696
PS_clear(self._part)
697
PS_split_point(self._part, pos) # <- individualization!!!
698
self._fixed_not_minimized.append(pos)
699
self._backtrack() # backtracking
700
701
#restore the old state and continue backtracking
702
self._part.depth = old_partition_depth
703
self._fixed_minimized = self._fixed_minimized[:old_fixed_minimized_len]
704
self._fixed_not_minimized = copy(old_fixed_not_minimized)
705
self._nr_of_inner_min_unmin_calls = old_nr_of_inner_min_unmin_calls
706
self._restore_state_(act_state) # we fixed at least one position, undo this change
707
708
if second_pos != -1:
709
# special case when block has size 2
710
# update for second run on while, i.e. pos == second_pos
711
self._fixed_not_minimized.append(smallest)
712
713
pos = bitset_next(b, pos + 1) # find the next candidate to individualize
714
if pos == -1:
715
# all candidates have been individualized!
716
break
717
718
bitset_free(b)
719
self._latex_finish_lvl()
720
721
cdef bint _inner_min_unminimized(self, bint *inner_group_changed):
722
r"""
723
Compute a subsequence `J = (j_0, \ldots, j_{l-1})`
724
of fixed coordinates, which were not yet used in
725
the inner minimization process (we call this sequence of used coordinates
726
`I`) and compute the `(I+J)`-semicanonical representative.
727
"""
728
cdef bint loc_inner_group_changed, reset_allowance_best = False
729
cdef int i, j, best_end, best_ind
730
731
if self._is_candidate_initialized:
732
allowance_best = self._allowance_best[self._nr_of_inner_min_unmin_calls]
733
best_end = len(allowance_best)
734
best_ind = 0
735
else:
736
allowance_best = []
737
reset_allowance_best = True
738
inner_group_changed[0] = False
739
i = len(self._fixed_not_minimized)-1
740
while i>=0:
741
pos = self._fixed_not_minimized[i]
742
if self._minimization_allowed_on_col(pos):
743
for j from 0 <= j < self._n:
744
if self._part.entries[j] == pos:
745
my_final_pos = j
746
break
747
748
if self._is_candidate_initialized:
749
if best_ind == best_end:
750
self._is_candidate_initialized = False
751
reset_allowance_best = True
752
else:
753
if allowance_best[best_ind] < my_final_pos:
754
return False
755
if allowance_best[best_ind] > my_final_pos:
756
self._is_candidate_initialized = False
757
allowance_best = allowance_best[:best_ind]
758
reset_allowance_best = True
759
best_ind += 1 # to avoid to come to this point again
760
761
if not self._is_candidate_initialized:
762
allowance_best.append(my_final_pos)
763
764
if not self._inner_min_(pos, &loc_inner_group_changed):
765
return False
766
767
if not reset_allowance_best and not self._is_candidate_initialized:
768
allowance_best = allowance_best[:best_ind]
769
reset_allowance_best = True
770
771
self._fixed_not_minimized.remove(pos)
772
self._fixed_minimized.append(pos)
773
inner_group_changed[0] |= loc_inner_group_changed
774
if loc_inner_group_changed:
775
i = len(self._fixed_not_minimized)
776
i -= 1
777
778
if self._is_candidate_initialized:
779
if best_ind < best_end:
780
return False
781
else:
782
self._allowance_best = self._allowance_best[:self._nr_of_inner_min_unmin_calls]
783
self._allowance_best.append(allowance_best)
784
self._nr_of_inner_min_unmin_calls += 1
785
return True
786
787
cdef bint _one_refinement(self, long * best, int begin, int end, bint *inner_group_changed,
788
bint *changed_partition, str refine_name):
789
r"""
790
Let ``self._refine_vals_scratch`` contain the result of the `(S_n)_C`-homomorphism
791
`\omega` and ``best`` be the result of `\omega` applied to the candidate.
792
793
This method computes performs the refinements, i.e. updates the partition stack
794
and furthermore compares the result with ``best`` and decides
795
- if we could prune the subtree -> return value is set to False
796
- if the inner group has changed -> sets ``inner_group_changed`` to True
797
- if the partition has changed -> sets ``changed_partition`` to True
798
799
The string ``refine_name`` is only neccessary for printing within the
800
latex output (if activated).
801
"""
802
cdef tuple res = PS_refinement(self._part, self._refine_vals_scratch, best, begin, end,
803
&self._is_candidate_initialized, changed_partition)
804
inner_group_changed[0] = False
805
if res[0]:
806
if changed_partition and self._cut_by_known_automs():
807
self._latex_act_node("autcut in " + refine_name)
808
return False
809
810
if len( res[1] ) > 0:
811
self._fixed_not_minimized += res[1]
812
if self._inner_min_unminimized(inner_group_changed):
813
return True
814
else:
815
self._latex_act_node("inner-min in " + refine_name)
816
return False
817
return True
818
self._latex_act_node(refine_name)
819
return False
820
821
cdef int len(self):
822
r"""
823
Return the degree of the acting symmetric group.
824
"""
825
return self._n
826
827
cdef void _leaf_computations(self):
828
r"""
829
All neccessary computations which have to be performed in a leaf.
830
831
There are to possibilities depending on the flag
832
``self._is_candidate_initialized``:
833
- ``True``: There is another leaf equal to this node. This defines an automorphism
834
which we add to the group of known automorphisms stored in
835
``self._known_automorphisms``.
836
- ``False``: We have shown, that the current leaf is smaller than all
837
other leaf nodes already visited. We set it to be the
838
candidate for the canonical form.
839
"""
840
from sage.libs.gap.libgap import libgap
841
cdef int i
842
843
if self._is_candidate_initialized:
844
transp = libgap.PermList([self._part.entries[i] + 1 for i in range(self._n)])
845
# transp is the inverse of the actual transporter element (seen as action from left!)
846
self._known_automorphisms.add_gen(self._to_best_inverse * transp) # first apply _to_best_inverse then transp
847
self._latex_act_node("AUTOM")
848
else:
849
self._is_candidate_initialized = True
850
self._inner_min_order_best = self._fixed_minimized
851
self._to_best = libgap.PermList([self._part.entries[i] + 1 for i in range(self._n)])
852
self._to_best_inverse = self._to_best.Inverse()
853
854
# call the method from the derived class
855
self._store_best_()
856
self._latex_act_node("NEW")
857
858
cdef bint _cut_by_known_automs(self):
859
r"""
860
Return ``True`` if the subtree below this node does not contain
861
any *interesting* information.
862
"""
863
return self._is_candidate_initialized and self._known_automorphisms.has_empty_intersection(self._part)
864
865
866
###########################################################################
867
# These functions are used to produce some latex output:
868
# it writes the actual node
869
# to a 'tikz node' in latex. You just have to provide '_latex_act_node'
870
# in derived classes and to set
871
# BACKTRACK_WITHLATEX_DEBUG = 1 in the setup.py script when
872
# building this module!
873
###########################################################################
874
cdef void _latex_act_node(self, str comment="", int printlvl=0):
875
r"""
876
Append the actual node as a string of latex-commands to
877
``self._latex_debug_string``
878
"""
879
raise NotImplementedError # must be implemented by derived classes
880
881
def _latex_view(self, title=None):
882
r"""
883
Evaluate the latex commands written to ``self._latex_debug_string``
884
and view the result.
885
886
EXAMPLES::
887
888
sage: from sage.groups.perm_gps.partn_ref2.refinement_generic import PartitionRefinement_generic
889
sage: P = PartitionRefinement_generic(5)
890
sage: P._latex_view()
891
sorry, no debug output was written. Set BACKTRACK_WITHLATEX_DEBUG to True if interested in this information
892
"""
893
if BACKTRACK_WITHLATEX_DEBUG:
894
from sage.misc.latex import latex, view
895
latex.extra_preamble('')
896
latex.add_to_preamble("\\usepackage{tikz}")
897
latex.add_to_preamble("\\usepackage{tikz-qtree}")
898
view(self._latex_debug_string, engine="pdflatex", title=title)
899
else:
900
print "sorry, no debug output was written. " + \
901
"Set BACKTRACK_WITHLATEX_DEBUG to True if interested in this information"
902
903
cdef void _init_latex(self):
904
r"""
905
Add some initial commands to the string
906
``self._latex_debug_string``.
907
"""
908
if BACKTRACK_WITHLATEX_DEBUG:
909
from sage.misc.latex import LatexExpr
910
self._latex_debug_string = LatexExpr(
911
"\\resizebox{\\textwidth}{!}{\n" +
912
"\\begin{tikzpicture}\n" +
913
"\\tikzset{level distance=3cm, edge from parent/.style=" +
914
"{draw, edge from parent path={(\\tikzparentnode.south) -- (\\tikzchildnode.north)}}}\n" +
915
"\Tree")
916
self._latex_debug_string += "[."
917
self._latex_act_node()
918
919
cdef void _finish_latex(self):
920
r"""
921
Add some final commands to the string
922
``self._latex_debug_string``.
923
"""
924
if BACKTRACK_WITHLATEX_DEBUG:
925
self._latex_debug_string += "]\n"
926
self._latex_debug_string += "\\end{tikzpicture} }\n"
927
928
cdef void _latex_new_lvl(self):
929
r"""
930
Add some commands to the string ``self._latex_debug_string``,
931
in the case that the depth was increased during the backtracking.
932
"""
933
if BACKTRACK_WITHLATEX_DEBUG:
934
self._latex_debug_string += "[."
935
936
cdef void _latex_finish_lvl(self):
937
r"""
938
Add some commands to the string ``self._latex_debug_string``,
939
in the case that we return to a node in the backtracking.
940
"""
941
if BACKTRACK_WITHLATEX_DEBUG:
942
self._latex_debug_string += "]\n"
943