Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/coding/codecan/codecan.pyx
8815 views
1
r"""
2
Canonical forms and automorphism group computation for linear codes over finite fields.
3
4
We implemented the algorithm described in [Feu2009] which computes the unique
5
semilinearly isometric code (canonical form) in the equivalence class of a given
6
linear code ``C``. Furthermore, this algorithm will return the automorphism
7
group of ``C``, too.
8
9
The algorithm should be started via a further class
10
:class:`~sage.coding.codecan.autgroup_can_label.LinearCodeAutGroupCanLabel`.
11
This class removes duplicated columns (up to multiplications
12
by units) and zero columns. Hence, we can suppose that the input for the algorithm
13
developed here is a set of points in `PG(k-1, q)`.
14
15
The implementation is based on the class
16
:class:`sage.groups.perm_gps.partn_ref2.refinement_generic.PartitionRefinement_generic`.
17
See the description of this algorithm in
18
:mod:`sage.groups.perm_gps.partn_ref2.refinement_generic`.
19
In the language given there, we have to implement the group action of
20
`G = (GL(k,q) \times {\GF{q}^*}^n ) \rtimes Aut(\GF{q})` on the set `X =
21
(\GF{q}^k)^n` of `k \times n` matrices over `\GF{q}` (with the above
22
restrictions).
23
24
The derived class here implements the stabilizers
25
`G_{\Pi^{(I)}(x)}` of the projections `\Pi^{(I)}(x)` of `x` to
26
the coordinates specified in the sequence `I`. Furthermore, we implement
27
the inner minimization, i.e. the computation of a canonical form of
28
the projection `\Pi^{(I)}(x)` under the action of `G_{\Pi^{(I^{(i-1)})}(x)}` .
29
Finally, we provide suitable homomorphisms of group actions for the refinements
30
and methods to compute the applied group elements in `G \rtimes S_n`.
31
32
The algorithm also uses Jeffrey Leon's idea of maintaining an
33
invariant set of codewords which is computed in the beginning, see
34
:meth:`~sage.coding.codecan.codecan.PartitionRefinementLinearCode._init_point_hyperplane_incidence`.
35
An example for such a set is the set of all codewords of weight `\leq w` for
36
some uniquely defined `w`. In our case, we interpret the codewords as a set of
37
hyperplanes (via the corresponding information word) and compute invariants of
38
the bipartite, colored derived subgraph of the point-hyperplane incidence graph,
39
see :meth:`PartitionRefinementLinearCode._point_refine` and
40
:meth:`PartitionRefinementLinearCode._hyp_refine`.
41
42
Since we are interested in subspaces (linear codes) instead of matrices, our
43
group elements returned in
44
:meth:`PartitionRefinementLinearCode.get_transporter` and
45
:meth:`PartitionRefinementLinearCode.get_autom_gens`
46
will be elements in the group
47
`({\GF{q}^*}^n \rtimes Aut(\GF{q})) \rtimes S_n =
48
({\GF{q}^*}^n \rtimes (Aut(\GF{q}) \times S_n)`.
49
50
AUTHORS:
51
52
- Thomas Feulner (2012-11-15): initial version
53
54
REFERENCES:
55
56
.. [Feu2009] T. Feulner. The Automorphism Groups of Linear Codes and
57
Canonical Representatives of Their Semilinear Isometry Classes.
58
Advances in Mathematics of Communications 3 (4), pp. 363-383, Nov 2009
59
60
EXAMPLES:
61
62
Get the canonical form of the Simplex code::
63
64
sage: from sage.coding.codecan.codecan import PartitionRefinementLinearCode
65
sage: mat = codes.HammingCode(3, GF(3)).dual_code().gen_mat()
66
sage: P = PartitionRefinementLinearCode(mat.ncols(), mat)
67
sage: cf = P.get_canonical_form(); cf
68
[1 0 0 0 0 1 1 1 1 1 1 1 1]
69
[0 1 0 1 1 0 0 1 1 2 2 1 2]
70
[0 0 1 1 2 1 2 1 2 1 2 0 0]
71
72
The transporter element is a group element which maps the input
73
to its canonical form::
74
75
sage: cf.echelon_form() == (P.get_transporter() * mat).echelon_form()
76
True
77
78
The automorphism group of the input, i.e. the stabilizer under this group action,
79
is returned by generators::
80
81
sage: P.get_autom_order_permutation() == GL(3, GF(3)).order()/(len(GF(3))-1)
82
True
83
sage: A = P.get_autom_gens()
84
sage: all( [(a*mat).echelon_form() == mat.echelon_form() for a in A])
85
True
86
"""
87
88
#*******************************************************************************
89
# Copyright (C) 2012 Thomas Feulner <[email protected]>
90
#
91
# Distributed under the terms of the GNU General Public License (GPL)
92
# as published by the Free Software Foundation; either version 2 of
93
# the License, or (at your option) any later version.
94
# http://www.gnu.org/licenses/
95
#*******************************************************************************
96
97
include '../../groups/perm_gps/partn_ref/data_structures_pyx.pxi'
98
99
from copy import copy
100
from sage.matrix.matrix cimport Matrix
101
from sage.groups.perm_gps.permgroup import PermutationGroup
102
cimport sage.groups.perm_gps.partn_ref2.refinement_generic
103
from sage.modules.finite_submodule_iter cimport FiniteFieldsubspace_projPoint_iterator as FFSS_projPoint
104
105
106
cdef class InnerGroup:
107
r"""
108
This class implements the stabilizers `G_{\Pi^{(I)}(x)}` described in
109
:mod:`sage.groups.perm_gps.partn_ref2.refinement_generic` with
110
`G = (GL(k,q) \times \GF{q}^n ) \rtimes Aut(\GF{q})`.
111
112
Those stabilizers can be stored as triples:
113
- ``rank`` - an integer in `\{0, \ldots, k\}`
114
- ``row_partition`` - a partition of `\{0, \ldots, k-1\}` with
115
discrete cells for all integers `i \geq rank`.
116
- ``frob_pow`` an integer in `\{0, \ldots, r-1\}` if `q = p^r`
117
118
The group `G_{\Pi^{(I)}(x)}` contains all elements `(A, \varphi, \alpha) \in G`,
119
where
120
- `A` is a `2 \times 2` blockmatrix, whose upper left matrix
121
is a `k \times k` diagonal matrix whose entries `A_{i,i}` are constant
122
on the cells of the partition ``row_partition``.
123
The lower left matrix is zero.
124
And the right part is arbitrary.
125
- The support of the columns given by `i \in I` intersect exactly one
126
cell of the partition. The entry `\varphi_i` is equal to the entries
127
of the corresponding diagonal entry of `A`.
128
- `alpha` is a power of `\tau^{frob_pow}`, where `tau` denotes the
129
Frobenius automorphism of the finite field `\GF{q}`.
130
131
See [Feu2009]_ for more details.
132
133
REFERENCES:
134
135
.. [Feu2009] T. Feulner. The Automorphism Groups of Linear Codes and
136
Canonical Representatives of Their Semilinear Isometry Classes.
137
Advances in Mathematics of Communications 3 (4), pp. 363-383, Nov 2009
138
"""
139
def __cinit__(self, k=0, algorithm="semilinear", **kwds):
140
r"""
141
See :class:`sage.coding.codecan.codecan.InnerGroup`
142
143
INPUT:
144
145
- ``k`` -- an integer, gives the dimension of the matrix component
146
- ``algorithm`` -- either
147
* "semilinear" -- full group
148
* "linear" -- no field automorphisms, i.e. `G = (GL(k,q) \times \GF{q}^n )`
149
* "permutational -- no field automorphisms and no column multiplications
150
i.e. `G = GL(k,q)`
151
- ``transporter`` (optional) -- set to an element of the group
152
:class:`sage.groups.semimonomial_transformations.semimonomial_transformation_group.SemimonomialTransformationGroup`
153
if you would like to modify this element simultaneously
154
155
EXAMPLES::
156
157
sage: from sage.coding.codecan.codecan import InnerGroup
158
sage: IG = InnerGroup(10)
159
sage: IG = InnerGroup(10, "linear")
160
sage: IG = InnerGroup(10, "permutational")
161
162
::
163
164
sage: S = SemimonomialTransformationGroup(GF(4, 'a'), 8)
165
sage: IG = InnerGroup(3, transporter=S.an_element())
166
"""
167
168
self.rank = 0
169
if k > 0:
170
self.row_partition = OP_new(k)
171
172
if algorithm == "permutational":
173
self.frob_pow = 0
174
self.permutational_only = 1
175
for i in range(1, k):
176
OP_join(self.row_partition, 0, i)
177
else:
178
self.permutational_only = 0
179
if algorithm == "semilinear":
180
self.frob_pow = 1
181
elif algorithm == "linear":
182
self.frob_pow = 0
183
184
185
self.compute_transporter = False
186
if "transporter" in kwds:
187
self.transporter = kwds["transporter"]
188
self.compute_transporter = True
189
190
def __dealloc__(self):
191
r"""
192
Deallocates ``self``.
193
"""
194
OP_dealloc(self.row_partition)
195
196
cdef int get_rep(self, int pos):
197
"""
198
Get the index of the cell of ``self.row_partition`` containing ``pos``.
199
"""
200
return OP_find(self.row_partition, pos)
201
202
cdef bint has_semilinear_action(self):
203
"""
204
Returns ``True`` iff the field automorphism group component of ``self``
205
is non-trivial.
206
"""
207
return (self.frob_pow > 0)
208
209
cdef int join_rows(self, int rep1, int rep2):
210
"""
211
Join the cells with unique representatives
212
``rep1`` and ``rep2`` of ``self.row_partition``.
213
Return the index of the join.
214
"""
215
OP_join(self.row_partition, rep1, rep2)
216
return self.get_rep(rep1)
217
218
cdef void copy_from(self, InnerGroup other):
219
"""
220
Copy the group ``other`` to ``self``.
221
"""
222
self.rank = other.rank
223
self.frob_pow = other.frob_pow
224
self.permutational_only = other.permutational_only
225
OP_copy_from_to(other.row_partition, self.row_partition)
226
227
cdef minimize_by_row_mult(self, FreeModuleElement w):
228
r"""
229
We suppose `v \in \GF{q}^k` and the entries `v_i = 0` for all
230
``i >= self.rank``.
231
We compute the smallest element ``w`` in the orbit of ``v`` under the
232
group action of the matrix components of all elements in ``self``.
233
234
We return ``d, w``, where ``d`` is a dictionary mapping
235
``self.row_partition`` (accessed via their unique representatives)
236
to its necessary multiplication. Non-occurring cells are multiplicated
237
by 1.
238
"""
239
cdef FreeModuleElement v = w.__copy__()
240
cdef dict d = dict()
241
if self.permutational_only:
242
return d, v
243
244
cdef list nz_pos = v.nonzero_positions()
245
for r in nz_pos:
246
r_rep = self.get_rep(r)
247
if r_rep not in d:
248
d[r_rep] = v[r] ** (-1)
249
v[r] = 1
250
else:
251
v[r] *= d[r_rep]
252
return d, v
253
254
cdef minimize_matrix_col(self, object m, int pos, list fixed_minimized_cols,
255
bint *group_changed):
256
r"""
257
Minimize the column at position ``pos`` of the matrix ``m`` by the
258
action of ``self``. ``m`` should have no zero column. ``self`` is set to
259
the stabilizer of this column.
260
261
We return ``group_changed, mm`` where ``group_changed`` is a boolean
262
indicating if ``self`` got changed and ``mm`` is the modification of
263
``m``.
264
265
In the array ``fixed_minimized_cols`` we store, those
266
columns of ``m`` which are known to be invariant under ``self``.
267
"""
268
group_changed[0] = False
269
cdef SemimonomialTransformation my_trans
270
cdef FreeModuleElement act_col = m.column(pos)
271
cdef int pivot = -1
272
cdef list nz_pos = act_col.nonzero_positions()
273
cdef int applied_frob, i, col, row, first_nz_rep
274
275
F = m.base_ring()
276
277
for i in nz_pos:
278
if i >= self.rank:
279
pivot = i
280
break
281
282
if pivot == -1:
283
if self.permutational_only:
284
return m
285
# this column is linearly dependent on those already fixed
286
first_nz = nz_pos.pop(0)
287
first_nz_rep = self.get_rep(first_nz)
288
factor = m[first_nz, pos] ** (-1)
289
m.rescale_col(pos, factor)
290
291
if self.compute_transporter:
292
n = self.transporter.parent().degree()
293
v = (F.one(),)*(pos) + (factor**(-1), ) + (F.one(),)*(n-pos-1)
294
my_trans = self.transporter.parent()(v=v)
295
296
d, _ = self.minimize_by_row_mult(factor * act_col)
297
d.pop(first_nz_rep)
298
if len(d): # there is at least one more multiplication
299
group_changed[0] = True
300
for i in range(self.rank):
301
factor = d.get(self.get_rep(i))
302
if factor and not factor.is_zero():
303
m.rescale_row(i, factor)
304
for i in d.iterkeys():
305
first_nz_rep = self.join_rows(first_nz_rep, i)
306
# rescale the already fixed part by column multiplications
307
for col in fixed_minimized_cols:
308
col_nz = m.column(col).nonzero_positions()
309
if len(col_nz) > 0:
310
row = col_nz[0]
311
if self.compute_transporter:
312
my_trans.v = (my_trans.v[:col] + (m[row, col],) +
313
my_trans.v[col+1:])
314
m.rescale_col(col, m[row, col] ** (-1))
315
if self.has_semilinear_action():
316
applied_frob = 0
317
self.minimize_by_frobenius(m[nz_pos].column(pos), &applied_frob, &self.frob_pow)
318
f = F.hom([F.gen() ** (F.characteristic() ** applied_frob)])
319
m = m.apply_map(f) # this would change the reference!
320
321
if self.compute_transporter:
322
my_trans.v = tuple([my_trans.v[i].frobenius(applied_frob)
323
for i in range(len(my_trans.v))])
324
my_trans.alpha = f
325
326
if self.compute_transporter:
327
self.transporter = my_trans * self.transporter
328
else:
329
# this column is linearly independent on those already fixed,
330
# map it to the self._rank-th unit vector
331
group_changed[0] = True
332
self.gaussian_elimination(m, pos, pivot, nz_pos)
333
self.rank += 1
334
return m
335
336
cdef void gaussian_elimination(self, object m, int pos, int pivot, list nz_pos):
337
r"""
338
Minimize the column at position ``pos`` of the matrix ``m`` by the
339
action of ``self``. We know that the there is some nonzero entry of this
340
column at ``pivot >= self.rank``. All nonzero entries are stored in
341
the list ``nz_pos``.
342
343
``self`` is not modified by this function, but ``m`` is.
344
"""
345
nz_pos.remove(pivot)
346
m.rescale_row(pivot, m[pivot, pos] ** (-1))
347
348
for r in nz_pos:
349
m.add_multiple_of_row(r, pivot, -m[r, pos]) # Gaussian elimination
350
if pivot != self.rank:
351
m.swap_rows(self.rank, pivot)
352
353
cdef InnerGroup _new_c(self):
354
r"""
355
Make a new copy of ``self``.
356
"""
357
cdef InnerGroup res = InnerGroup()
358
res.frob_pow = self.frob_pow
359
res.rank = self.rank
360
res.row_partition = OP_copy(self.row_partition)
361
res.permutational_only = self.permutational_only
362
return res
363
364
cdef SemimonomialTransformation get_transporter(self):
365
r"""
366
Return the group element we have applied. Should only be called if
367
we passed an element in
368
:meth:`sage.coding.codecan.codecan.InnerGroup.__cinit__`.
369
"""
370
return self.transporter
371
372
def __repr__(self):
373
"""
374
EXAMPLES::
375
376
sage: from sage.coding.codecan.codecan import InnerGroup
377
sage: InnerGroup(10)
378
Subgroup of (GL(k,q) times \GF{q}^n ) rtimes Aut(\GF{q}) with rank = 0,
379
frobenius power = 1 and partition = 0 -> 0 1 -> 1 2 -> 2 3 -> 3 4 -> 4 5 -> 5
380
6 -> 6 7 -> 7 8 -> 8 9 -> 9
381
"""
382
return "Subgroup of (GL(k,q) times \GF{q}^n ) rtimes Aut(\GF{q}) " + \
383
"with rank = %s, frobenius power = %s and partition =%s" % (self.rank,
384
self.frob_pow, OP_string(self.row_partition))
385
386
cdef void minimize_by_frobenius(self, object v, int *applied_frob, int *stab_pow):
387
r"""
388
Minimize the vector ``v \in \GF{q}^k`` by the
389
action of the field automorphism component of ``self``.
390
``self`` and ``v`` are not modified by this function.
391
392
Let `\tau` denote the Frobenius automorphism of ``\GF{q}``. Then
393
``applied_frob``-th power of `\tau` will give us the minimal element.
394
The ``stab_pow``-th power of `\tau` will generate the stabilizer of `v`.
395
"""
396
stab_pow[0] = self.frob_pow
397
applied_frob[0] = 0
398
cdef int loc_frob, min_pow = 0
399
for el in v:
400
x = el.frobenius(applied_frob[0])
401
y = x # the elements in the cyclic(!) orbit
402
m = x # a candidate for the minimal element
403
404
loc_frob = 0
405
min_pow = 0
406
407
while 1:
408
loc_frob += 1
409
y = y.frobenius(stab_pow[0])
410
if y == x:
411
break
412
if y < m:
413
m = y
414
min_pow = loc_frob
415
416
# now x.frobenius(stab_pow*loc_frob) == x
417
applied_frob[0] += min_pow * stab_pow[0]
418
stab_pow[0] *= loc_frob
419
if stab_pow[0] == el.parent().degree():
420
stab_pow[0] = 0
421
break # for
422
423
cpdef int get_frob_pow(self):
424
r"""
425
Return the power of the Frobenius automorphism which generates
426
the corresponding component of ``self``.
427
428
EXAMPLES::
429
430
sage: from sage.coding.codecan.codecan import InnerGroup
431
sage: I = InnerGroup(10)
432
sage: I.get_frob_pow()
433
1
434
"""
435
return self.frob_pow
436
437
cpdef column_blocks(self, mat):
438
r"""
439
Let ``mat`` be a matrix which is stabilized by ``self`` having no zero
440
columns. We know that for each column of ``mat`` there is a uniquely
441
defined cell in ``self.row_partition`` having a nontrivial intersection
442
with the support of this particular column.
443
444
This function returns a partition (as list of lists) of the columns
445
indices according to the partition of the rows given by ``self``.
446
447
EXAMPLES::
448
449
sage: from sage.coding.codecan.codecan import InnerGroup
450
sage: I = InnerGroup(3)
451
sage: mat = Matrix(GF(3), [[0,1,0],[1,0,0], [0,0,1]])
452
sage: I.column_blocks(mat)
453
[[1], [0], [2]]
454
"""
455
if self.row_partition.num_cells == 1:
456
return [range(mat.ncols())]
457
458
r = [[] for i in range(mat.ncols()) ]
459
cols = iter(mat.columns())
460
for i in range(mat.ncols()):
461
# there should be no zero columns by assumption!
462
m = OP_find(self.row_partition, cols.next().nonzero_positions()[0])
463
r[m].append(i)
464
return [ x for x in r if len(x) > 0 ]
465
466
cdef class PartitionRefinementLinearCode(PartitionRefinement_generic):
467
"""
468
See :mod:`sage.coding.codecan.codecan`.
469
470
EXAMPLES::
471
472
sage: from sage.coding.codecan.codecan import PartitionRefinementLinearCode
473
sage: mat = codes.HammingCode(3, GF(3)).dual_code().gen_mat()
474
sage: P = PartitionRefinementLinearCode(mat.ncols(), mat)
475
sage: cf = P.get_canonical_form(); cf
476
[1 0 0 0 0 1 1 1 1 1 1 1 1]
477
[0 1 0 1 1 0 0 1 1 2 2 1 2]
478
[0 0 1 1 2 1 2 1 2 1 2 0 0]
479
480
::
481
482
sage: cf.echelon_form() == (P.get_transporter() * mat).echelon_form()
483
True
484
485
::
486
487
sage: P.get_autom_order_permutation() == GL(3, GF(3)).order()/(len(GF(3))-1)
488
True
489
sage: A = P.get_autom_gens()
490
sage: all( [(a*mat).echelon_form() == mat.echelon_form() for a in A])
491
True
492
"""
493
def __cinit__(self, n, gen_mat, **kwds):
494
r"""
495
Initialization. See :meth:`__init__`.
496
497
EXAMPLES::
498
499
sage: from sage.coding.codecan.codecan import PartitionRefinementLinearCode
500
sage: mat = codes.HammingCode(3, GF(3)).dual_code().gen_mat()
501
sage: P = PartitionRefinementLinearCode(mat.ncols(), mat)
502
"""
503
self._k = gen_mat.nrows()
504
self._q = len(gen_mat.base_ring())
505
self._nr_of_supp_refine_calls = 0
506
self._nr_of_point_refine_calls = 0
507
self._matrix = copy(gen_mat)
508
self._root_matrix = gen_mat
509
self._stored_states = dict()
510
self._supp_refine_vals = _BestValStore(n)
511
self._point_refine_vals = _BestValStore(n)
512
# self._hyp_refine_vals will initialized after
513
# we computed the set of codewords
514
515
def __init__(self, n, gen_mat, P=None, algorithm_type="semilinear"):
516
r"""
517
Initialization, we immediately start the algorithm
518
(see :mod:``sage.coding.codecan.codecan``)
519
to compute the canonical form and automorphism group of the linear code
520
generated by ``gen_mat``.
521
522
INPUT:
523
524
- ``n`` -- an integer
525
- ``gen_mat`` -- a `k \times n` matrix over `\GF{q}` of full row rank,
526
i.e. `k<n` and without zero columns.
527
- partition (optional) -- a partition (as list of lists) of the set
528
`\{0, \ldots, n-1\}` which restricts the action of the permutational
529
part of the group to the stabilizer of this partition
530
- algorithm_type (optional) -- use one of the following options
531
* "semilinear" - full group
532
* "linear" - no field automorphisms, i.e. `G = (GL(k,q) \times \GF{q}^n )`
533
* "permutational - no field automorphisms and no column multiplications
534
i.e. `G = GL(k,q)`
535
536
EXAMPLES::
537
538
sage: from sage.coding.codecan.codecan import PartitionRefinementLinearCode
539
sage: mat = codes.HammingCode(3, GF(3)).dual_code().gen_mat()
540
sage: P = PartitionRefinementLinearCode(mat.ncols(), mat)
541
"""
542
self._run(P, algorithm_type)
543
544
def __dealloc__(self):
545
r"""
546
Deallocates ``self``.
547
"""
548
cdef int i
549
for i in range(self._n):
550
bitset_free(self._points2hyp[i])
551
552
for i in range(self._hyp_part.degree):
553
bitset_free(self._hyp2points[i])
554
555
sage_free(self._hyp2points)
556
sage_free(self._points2hyp)
557
PS_dealloc(self._hyp_part)
558
sage_free(self._hyp_refine_vals_scratch)
559
560
def __repr__(self):
561
"""
562
EXAMPLES::
563
564
sage: from sage.coding.codecan.codecan import PartitionRefinementLinearCode
565
sage: mat = codes.HammingCode(3, GF(3)).dual_code().gen_mat()
566
sage: PartitionRefinementLinearCode(mat.ncols(), mat)
567
Canonical form algorithm for linear code generated by
568
[1 0 1 1 0 1 0 1 1 1 0 1 1]
569
[0 1 1 2 0 0 1 1 2 0 1 1 2]
570
[0 0 0 0 1 1 1 1 1 2 2 2 2]
571
"""
572
return "Canonical form algorithm for linear code generated" + \
573
" by\n%s" % (self._root_matrix)
574
575
def _run(self, P, algorithm_type):
576
"""
577
Start the main algorithm, this method is called in :meth:`init`.
578
See this method for the description of the input.
579
580
EXAMPLES::
581
582
sage: from sage.coding.codecan.codecan import PartitionRefinementLinearCode
583
sage: mat = codes.HammingCode(3, GF(3)).dual_code().gen_mat()
584
sage: P = PartitionRefinementLinearCode(mat.ncols(), mat) #indirect doctest
585
sage: P.get_canonical_form()
586
[1 0 0 0 0 1 1 1 1 1 1 1 1]
587
[0 1 0 1 1 0 0 1 1 2 2 1 2]
588
[0 0 1 1 2 1 2 1 2 1 2 0 0]
589
"""
590
self._init_point_hyperplane_incidence()
591
F = self._matrix.base_ring()
592
if F.order() == 2:
593
algorithm_type = "permutational"
594
elif self._matrix.base_ring().is_prime_field() and algorithm_type != "permutational":
595
algorithm_type = "linear"
596
self._inner_group = InnerGroup(self._k, algorithm_type)
597
598
self._init_partition_stack(P)
599
self._init_point_hyperplane_incidence()
600
self._start_Sn_backtrack() #start the main computation
601
602
# up to now, we just computed the permutational part of the group action
603
# compute the other components of the transporter
604
from sage.combinat.permutation import Permutation
605
from sage.groups.semimonomial_transformations.semimonomial_transformation_group import SemimonomialTransformationGroup
606
from sage.groups.perm_gps.permgroup_named import SymmetricGroup
607
608
S = SemimonomialTransformationGroup(self._matrix.base_ring(), self._n)
609
S_n = SymmetricGroup(self._n)
610
611
self._transporter = S(perm= S_n(self._to_best.sage()))
612
self._transporter, self._best_candidate, remaining_inner_group = self._compute_group_element(self._transporter, algorithm_type)
613
614
# compute the other components of the automorphism group generators
615
self._autom_group_generators = []
616
transp_inv = self._transporter ** (-1)
617
618
for a in self._known_automorphisms.small_generating_set():
619
x = S(perm=self._transporter.get_perm() * Permutation(S_n(a)))
620
x, _, _ = self._compute_group_element(x, algorithm_type)
621
self._autom_group_generators.append(transp_inv * x)
622
623
if algorithm_type == "permutational":
624
self._inner_group_stabilizer_order = 1
625
else:
626
P = remaining_inner_group.column_blocks(self._best_candidate)
627
for p in P:
628
x = S(v=[ F.primitive_element() if i in p else F.one() for i in range(self._n) ])
629
self._autom_group_generators.append(transp_inv * x * self._transporter)
630
self._inner_group_stabilizer_order = (len(F) - 1) ** len(P)
631
632
633
if remaining_inner_group.get_frob_pow() > 0:
634
x = S(autom=F.hom([F.primitive_element() ** (remaining_inner_group.get_frob_pow() * F.characteristic())]))
635
self._autom_group_generators.append(transp_inv * x * self._transporter)
636
self._inner_group_stabilizer_order *= Integer(F.degree() / remaining_inner_group.get_frob_pow())
637
638
cdef _compute_group_element(self, SemimonomialTransformation trans, str algorithm_type):
639
"""
640
Apply ``trans`` to ``self._root_matrix`` and minimize the this matrix
641
column by column under the inner minimization. The action is
642
simoultaneously applied to ``trans``.
643
644
The output of this function is a triple containing, the modified
645
group element ``trans``, the minimized matrix and the stabilizer of this
646
matrix under the inner group.
647
"""
648
cdef InnerGroup inner_group = InnerGroup(self._k, algorithm_type, transporter=trans)
649
cdef bint group_changed = False
650
cdef int i
651
cdef list fixed_pos = []
652
mat = trans * self._root_matrix
653
654
for i in range(self._n):
655
mat = inner_group.minimize_matrix_col(mat, i, fixed_pos,
656
&group_changed)
657
fixed_pos.append(i)
658
659
trans = inner_group.get_transporter()
660
return trans, mat, inner_group
661
662
def get_canonical_form(self):
663
r"""
664
Return the canonical form for this matrix.
665
666
EXAMPLES::
667
668
sage: from sage.coding.codecan.codecan import PartitionRefinementLinearCode
669
sage: mat = codes.HammingCode(3, GF(3)).dual_code().gen_mat()
670
sage: P1 = PartitionRefinementLinearCode(mat.ncols(), mat)
671
sage: CF1 = P1.get_canonical_form()
672
sage: s = SemimonomialTransformationGroup(GF(3), mat.ncols()).an_element()
673
sage: P2 = PartitionRefinementLinearCode(mat.ncols(), s*mat)
674
sage: CF1 == P2.get_canonical_form()
675
True
676
"""
677
return self._best_candidate
678
679
def get_transporter(self):
680
"""
681
Return the transporter element, mapping the initial matrix to its
682
canonical form.
683
684
EXAMPLES::
685
686
sage: from sage.coding.codecan.codecan import PartitionRefinementLinearCode
687
sage: mat = codes.HammingCode(3, GF(3)).dual_code().gen_mat()
688
sage: P = PartitionRefinementLinearCode(mat.ncols(), mat)
689
sage: CF = P.get_canonical_form()
690
sage: t = P.get_transporter()
691
sage: (t*mat).echelon_form() == CF.echelon_form()
692
True
693
"""
694
return self._transporter
695
696
def get_autom_gens(self):
697
"""
698
Return generators of the automorphism group of the initial matrix.
699
700
EXAMPLES::
701
702
sage: from sage.coding.codecan.codecan import PartitionRefinementLinearCode
703
sage: mat = codes.HammingCode(3, GF(3)).dual_code().gen_mat()
704
sage: P = PartitionRefinementLinearCode(mat.ncols(), mat)
705
sage: A = P.get_autom_gens()
706
sage: all( [(a*mat).echelon_form() == mat.echelon_form() for a in A])
707
True
708
"""
709
return self._autom_group_generators
710
711
def get_autom_order_inner_stabilizer(self):
712
"""
713
Return the order of the stabilizer of the initial matrix under
714
the action of the inner group `G`.
715
716
EXAMPLES::
717
718
sage: from sage.coding.codecan.codecan import PartitionRefinementLinearCode
719
sage: mat = codes.HammingCode(3, GF(3)).dual_code().gen_mat()
720
sage: P = PartitionRefinementLinearCode(mat.ncols(), mat)
721
sage: P.get_autom_order_inner_stabilizer()
722
2
723
sage: mat2 = Matrix(GF(4, 'a'), [[1,0,1], [0,1,1]])
724
sage: P2 = PartitionRefinementLinearCode(mat2.ncols(), mat2)
725
sage: P2.get_autom_order_inner_stabilizer()
726
6
727
"""
728
return self._inner_group_stabilizer_order
729
730
cdef _init_point_hyperplane_incidence(self):
731
"""
732
Compute a set of codewords `W` of `C` (generated by self) which is compatible
733
with the group action, i.e. if we start with some other code `(g,\pi)C`
734
the result should be `(g,\pi)W`.
735
736
The set `W` will consist of all normalized codewords up to some weight
737
`w`, where `w` is the smallest integer such that `W` spans the linear code `C`.
738
739
This set is then transformed to an incidence matrix ``self._points2hyp``
740
of the point-hyperplane graph (points correspond to rows, hyperplanes to
741
columns). The hyperplanes correspond to the information
742
words. For performance reasons, we also store the transpose
743
``self._hyp2points`` of ``self._points2hyp``.
744
745
This graph will be later used in the refinement procedures.
746
"""
747
from sage.matrix.constructor import matrix
748
cdef FFSS_projPoint iter = FFSS_projPoint(self._matrix)
749
750
ambient_space = (self._matrix.base_ring()) ** (self._n)
751
weights2size = [0] * (self.len() + 1)
752
W = [[] for xx in range(self.len() + 1)]
753
span = [ambient_space.zero_subspace()] * (self.len() + 1)
754
min_weight = self.len()
755
max_weight = self.len()
756
757
while 1: # compute an invariant set of (normalized) codewords which span the subspace
758
try:
759
cw = iter.next()
760
except StopIteration:
761
break
762
w = cw.hamming_weight()
763
if min_weight > w:
764
min_weight = w
765
if w <= max_weight:
766
X = ambient_space.subspace([cw])
767
for i in range(w, max_weight):
768
old_dim = span[i].dimension()
769
span[i] += X
770
if span[i].dimension() == old_dim:
771
break # this will also be the case for all others
772
if old_dim + 1 == self._k:
773
# the codewords of weight <= max_weight span the code
774
max_weight = i
775
break
776
W[w].append(cw)
777
778
flat_W = sum(W[min_weight: max_weight + 1], [])
779
cdef int __hyp2points_size = len(flat_W)
780
self._hyp_part = PS_new(__hyp2points_size, 1)
781
s = -1
782
for x in W[min_weight: max_weight]:
783
s += len(x)
784
if s >= 0:
785
self._hyp_part.levels[s] = 0
786
787
self._hyp2points = < bitset_t *> sage_malloc(self._hyp_part.degree * sizeof(bitset_t))
788
if self._hyp2points is NULL:
789
raise MemoryError('allocating PartitionRefinementLinearCode')
790
self._points2hyp = < bitset_t *> sage_malloc(self._n * sizeof(bitset_t))
791
if self._hyp2points is NULL:
792
sage_free(self._hyp2points)
793
raise MemoryError('allocating PartitionRefinementLinearCode')
794
795
for i in range(self._n):
796
bitset_init(self._points2hyp[i], self._hyp_part.degree)
797
bitset_zero(self._points2hyp[i])
798
799
for i in range(self._hyp_part.degree):
800
bitset_init(self._hyp2points[i], self._n)
801
bitset_zero(self._hyp2points[i])
802
for j in flat_W[i].support():
803
bitset_add(self._hyp2points[i], j)
804
bitset_add(self._points2hyp[j], i)
805
806
self._hyp_refine_vals_scratch = <long *> sage_malloc(
807
self._hyp_part.degree * sizeof(long))
808
if self._hyp_refine_vals_scratch is NULL:
809
self.__dealloc__()
810
raise MemoryError('allocating PartitionRefinementLinearCode')
811
812
self._hyp_refine_vals = _BestValStore(self._hyp_part.degree)
813
814
cdef bint _minimization_allowed_on_col(self, int pos):
815
r"""
816
Decide if we are allowed to perform the inner minimization on position
817
``pos`` which is supposed to be a singleton. For linear codes over finite
818
fields, we can always return ``True``.
819
"""
820
return True
821
822
cdef bint _inner_min_(self, int pos, bint *inner_group_changed):
823
r"""
824
Minimize the node by the action of the inner group on the ``pos``-th position.
825
Sets ``inner_group_changed`` to ``True`` if and only if the inner group
826
has changed.
827
828
INPUT:
829
830
- ``pos`` -- A position in ``range(self.n)``
831
832
OUTPUT:
833
834
- ``True`` if and only if the actual node compares less or equal
835
to the candidate for the canonical form.
836
"""
837
self._matrix = self._inner_group.minimize_matrix_col(self._matrix, pos,
838
self._fixed_minimized, inner_group_changed)
839
840
# finally compare the new column with the best candidate
841
if self._is_candidate_initialized:
842
cmp_res = cmp(self._matrix.column(pos), self._best_candidate.column(
843
self._inner_min_order_best[ len(self._fixed_minimized) ]))
844
if cmp_res > 0:
845
return False
846
if cmp_res < 0:
847
# the next leaf will become the next candidate
848
self._is_candidate_initialized = False
849
return True
850
851
852
853
cdef bint _refine(self, bint *part_changed,
854
bint inner_group_changed, bint first_step):
855
"""
856
Refine the partition ``self.part``. Set ``part_changed`` to ``True``
857
if and only if ``self.part`` was refined.
858
859
OUTPUT:
860
861
- ``False`` -- only if the actual node compares larger than the candidate
862
for the canonical form.
863
"""
864
part_changed[0] = False
865
cdef bint res, hyp_part_changed = not first_step
866
cdef bint n_partition_changed = first_step
867
cdef bint n_partition_changed_copy = n_partition_changed
868
869
while hyp_part_changed or n_partition_changed:
870
inner_group_changed = False
871
res = self._inner_min_refine(&inner_group_changed, &n_partition_changed)
872
if not res:
873
return False
874
875
part_changed[0] |= n_partition_changed
876
n_partition_changed = n_partition_changed_copy
877
n_partition_changed_copy = True
878
879
if n_partition_changed:
880
if PS_is_discrete(self._part):
881
return True
882
if inner_group_changed:
883
continue
884
885
while hyp_part_changed or n_partition_changed:
886
if n_partition_changed:
887
res = self._hyp_refine(&hyp_part_changed)
888
if not res:
889
return False
890
n_partition_changed = False
891
else:
892
res = self._point_refine(&inner_group_changed, &n_partition_changed)
893
if not res:
894
return False
895
part_changed[0] |= n_partition_changed
896
hyp_part_changed = False
897
if inner_group_changed:
898
break # perform the inner_min_refine first!
899
if n_partition_changed and PS_is_discrete(self._part):
900
return True
901
return True
902
903
904
cdef bint _inner_min_refine(self, bint *inner_stab_changed, bint *changed_partition):
905
"""
906
Refine the partition ``self.part`` by computing the orbit (respectively
907
the hash of a canonical form) of each column vector under the inner group.
908
909
New fixed points of ``self.part`` get refined by the inner group. If this
910
leads to a smaller group then we set ``inner_stab_changed`` to ``True``.
911
``changed_partition`` is set to ``True`` if and only if ``self.part``
912
was refined.
913
914
OUTPUT:
915
916
- ``False`` only if the image under this homomorphism of group actions
917
compares larger than the image of the candidate for the canonical form.
918
"""
919
cdef int i, j, res, stab_pow, apply_pow
920
921
if self._inner_group.rank < 2:
922
return True
923
924
lower = iter(self._matrix[ : self._inner_group.rank ].columns())
925
upper = iter(self._matrix[ self._inner_group.rank : ].columns())
926
927
for i in range(self._n):
928
l = lower.next()
929
u = upper.next()
930
931
if u.is_zero() and not i in self._fixed_minimized:
932
# minimize by self._inner_group as in _inner_min:
933
_, l = self._inner_group.minimize_by_row_mult(l)
934
935
if self._inner_group.has_semilinear_action():
936
stab_pow = self._inner_group.frob_pow
937
apply_pow = 0
938
self._inner_group.minimize_by_frobenius(l, &apply_pow, &stab_pow)
939
940
F = self._matrix.base_ring()
941
f = F.hom([F.gen() ** (F.characteristic() ** apply_pow)])
942
l = l.apply_map(f)
943
res = 0
944
for r in iter(l):
945
res *= self._q
946
res += hash(r)
947
self._refine_vals_scratch[i] = res
948
else:
949
self._refine_vals_scratch[i] = -1
950
951
# provide some space to store the result (if not already exists)
952
cdef long * best_vals = self._supp_refine_vals.get_row(self._nr_of_supp_refine_calls)
953
self._nr_of_supp_refine_calls += 1
954
return self._one_refinement(best_vals, 0, self._n, inner_stab_changed,
955
changed_partition, "supp_refine")
956
957
cdef bint _point_refine(self, bint *inner_stab_changed, bint *changed_partition):
958
"""
959
Refine the partition ``self.part`` by counting
960
(colored) neighbours in the point-hyperplane graph.
961
962
New fixed points of ``self.part`` get refined by the inner group. If this
963
leads to a smaller group then we set ``inner_stab_changed`` to ``True``.
964
``changed_partition`` is set to ``True`` if and only if ``self.part``
965
was refined.
966
967
OUTPUT:
968
969
- ``False`` only if the image under this homomorphism of group actions
970
compares larger than the image of the candidate for the canonical form.
971
"""
972
973
self._part.depth += 1
974
PS_clear(self._part)
975
976
cdef bitset_t * nonsingletons, scratch
977
bitset_init(scratch, self._hyp_part.degree)
978
nonsingletons = < bitset_t *> sage_malloc(0)
979
cdef int nr_cells = PS_all_new_cells(self._hyp_part, & nonsingletons)
980
981
for i in range(self._n):
982
res = [0] * nr_cells
983
for j in range(nr_cells):
984
bitset_and(scratch, self._points2hyp[i], nonsingletons[j])
985
res[j] = bitset_hamming_weight(scratch)
986
self._refine_vals_scratch[i] = hash(tuple(res))
987
988
for j in range(nr_cells):
989
bitset_free(nonsingletons[j])
990
sage_free(nonsingletons)
991
bitset_free(scratch)
992
993
# provide some space to store the result (if not already exists)
994
cdef long * best_vals = self._point_refine_vals.get_row(self._nr_of_point_refine_calls)
995
self._nr_of_point_refine_calls += 1
996
cdef bint ret_val = self._one_refinement(best_vals, 0, self._n,
997
inner_stab_changed, changed_partition, "point_refine")
998
999
if not changed_partition[0]:
1000
self._part.depth -= 1
1001
return ret_val
1002
1003
cdef bint _hyp_refine(self, bint *changed_partition):
1004
"""
1005
Refine the partition of the hyperplanes by counting
1006
(colored) neighbours in the point-hyperplane graph.
1007
1008
``changed_partition`` is set to ``True`` if and only if the partition
1009
was refined.
1010
1011
OUTPUT:
1012
1013
- ``False`` only if the image under this homomorphism of group actions
1014
compares larger than the image of the candidate for the canonical form.
1015
"""
1016
1017
1018
self._hyp_part.depth += 1
1019
PS_clear(self._hyp_part)
1020
cdef bitset_t * nonsingletons, scratch
1021
bitset_init(scratch, self._part.degree)
1022
nonsingletons = < bitset_t *> sage_malloc(0)
1023
cdef int nr_cells = PS_all_new_cells(self._part, & nonsingletons)
1024
1025
for i in range(self._hyp_part.degree):
1026
res = [0] * nr_cells
1027
for j in range(nr_cells):
1028
bitset_and(scratch, self._hyp2points[i], nonsingletons[j])
1029
res[j] = bitset_hamming_weight(scratch)
1030
self._hyp_refine_vals_scratch[i] = hash(tuple(res))
1031
1032
for j in range(nr_cells):
1033
bitset_free(nonsingletons[j])
1034
sage_free(nonsingletons)
1035
bitset_free(scratch)
1036
1037
# provide some space to store the result (if not already exists)
1038
cdef long * best_vals = self._hyp_refine_vals.get_row(self._nr_of_hyp_refine_calls)
1039
self._nr_of_hyp_refine_calls += 1
1040
1041
cdef tuple ret_val = PS_refinement(self._hyp_part,
1042
self._hyp_refine_vals_scratch, best_vals, 0, self._hyp_part.degree,
1043
&self._is_candidate_initialized, changed_partition)
1044
1045
if not changed_partition[0]:
1046
self._hyp_part.depth -= 1
1047
return ret_val[0]
1048
1049
cdef tuple _store_state_(self):
1050
r"""
1051
Store the current state of the node to a tuple, such that it can be
1052
restored by :meth:`_restore_state_`.
1053
"""
1054
return (self._inner_group._new_c(), self._nr_of_supp_refine_calls,
1055
self._nr_of_point_refine_calls, self._nr_of_hyp_refine_calls,
1056
self._hyp_part.depth)
1057
1058
cdef void _restore_state_(self, tuple act_state):
1059
r"""
1060
The inverse of :meth:`_store_state_`.
1061
"""
1062
self._inner_group.copy_from(act_state[0])
1063
self._nr_of_supp_refine_calls = act_state[1]
1064
self._nr_of_point_refine_calls = act_state[2]
1065
self._nr_of_hyp_refine_calls = act_state[3]
1066
self._hyp_part.depth = act_state[4]
1067
1068
cdef void _store_best_(self):
1069
"""
1070
Store this node as the actual best candidate for the canonical form.
1071
"""
1072
self._best_candidate = copy(self._matrix)
1073
1074
cdef void _latex_act_node(self, str comment="", int printlvl=0):
1075
"""
1076
Print the actual status as latex (tikz) commands to
1077
``self._latex_debug_string``. Only needed if one wants to visualize
1078
the algorithm using latex. This is very helpful for debugging purposes.
1079
"""
1080
if not BACKTRACK_WITHLATEX_DEBUG:
1081
return
1082
1083
self._latex_debug_string += "\\node{\\begin{tabular}{"
1084
cdef int i, j, last = -1
1085
divide_sign = ""
1086
for i from 0 <= i < self._part.degree:
1087
if self._part.levels[i] <= self._part.depth:
1088
self._latex_debug_string += divide_sign + "*{" + str(i - last) + "}{c}"
1089
last = i
1090
divide_sign = "|"
1091
self._latex_debug_string += "}"
1092
1093
# Print the applied permutation. We do highlight the fixed positions.
1094
for i from 0 <= i < (self._n):
1095
if self._part.entries[i] in self._fixed_minimized:
1096
self._latex_debug_string += "\\color{red}"
1097
1098
self._latex_debug_string += str(self._part.entries[i])
1099
if i == self._n - 1:
1100
self._latex_debug_string += " \\\\\\hline\n"
1101
else:
1102
self._latex_debug_string += " & "
1103
1104
permuted_matrix = self._matrix.matrix_from_columns([self._part.entries[i] for i in range(self._n) ])
1105
1106
# Now we will finally print the matrix.
1107
for i from 0 <= i < self._k:
1108
for j from 0 <= j < (self._n - 1):
1109
self._latex_debug_string += "$" + permuted_matrix[i, j]._latex_() + "$ & "
1110
self._latex_debug_string += "$" + permuted_matrix[i, self._n - 1]._latex_() + "$ \\\\\n"
1111
1112
if comment != "":
1113
self._latex_debug_string += "\\multicolumn{" + str(self._n) + "}{c}{" + comment + "}\n"
1114
self._latex_debug_string += "\\end{tabular}};\n"
1115
1116