GroupsMath 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- groupsmath/__init__.py +1 -0
- groupsmath/core.py +1176 -0
- groupsmath/precooked.py +75 -0
- groupsmath/shortcuts.py +17 -0
- groupsmath-0.1.0.dist-info/METADATA +121 -0
- groupsmath-0.1.0.dist-info/RECORD +9 -0
- groupsmath-0.1.0.dist-info/WHEEL +5 -0
- groupsmath-0.1.0.dist-info/licenses/LICENSE +14 -0
- groupsmath-0.1.0.dist-info/top_level.txt +1 -0
groupsmath/core.py
ADDED
|
@@ -0,0 +1,1176 @@
|
|
|
1
|
+
# ———————————————————————————————————————————————————————————— #
|
|
2
|
+
# #
|
|
3
|
+
# GroupsMath #
|
|
4
|
+
# A Python library for finite group theory #
|
|
5
|
+
# #
|
|
6
|
+
# by: Mario Sultan Romero #
|
|
7
|
+
# #
|
|
8
|
+
# ———————————————————————————————————————————————————————————— #
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
#################### IMPORTS ####################
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import matplotlib.pyplot as plt
|
|
15
|
+
import matplotlib.colors as mcolors
|
|
16
|
+
from itertools import permutations, combinations, product
|
|
17
|
+
from collections import Counter
|
|
18
|
+
from math import gcd
|
|
19
|
+
from abc import abstractmethod, ABC
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
#################### DEFINITIONS ####################
|
|
23
|
+
|
|
24
|
+
__version__ = "0.3.0"
|
|
25
|
+
|
|
26
|
+
tgl_color = "#2A646E"
|
|
27
|
+
white = mcolors.LinearSegmentedColormap.from_list("white", ["white", "white"])
|
|
28
|
+
tgl = mcolors.LinearSegmentedColormap.from_list("tgl", ["white", tgl_color])
|
|
29
|
+
rainbow = mcolors.LinearSegmentedColormap.from_list("rainbow", ["#FF0000","#FF9100","#F2DA00","#2CDB00","#00DAE9","#1869FF"])#,"#7648FF","#D12BFF"])
|
|
30
|
+
rainbow8 = mcolors.LinearSegmentedColormap.from_list("rainbow8", ["#FF0000","#FF9100","#F2DA00","#2CDB00","#00DAE9","#1869FF","#7648FF","#FF1FF4"])
|
|
31
|
+
|
|
32
|
+
def info():
|
|
33
|
+
print("GroupsMath v"+__version__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
#################### GROUP CLASS ####################
|
|
37
|
+
|
|
38
|
+
class Group(ABC):
|
|
39
|
+
@classmethod
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def operation(self, a, b):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def identity(self):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def inverse(self, a):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
class CayleyGroup(Group):
|
|
55
|
+
|
|
56
|
+
def __init__(self, cayley, names=None, _skip_validation=False):
|
|
57
|
+
|
|
58
|
+
if not _skip_validation:
|
|
59
|
+
valid, message = _is_group(cayley)
|
|
60
|
+
if not valid:
|
|
61
|
+
raise ValueError(message)
|
|
62
|
+
|
|
63
|
+
if names is None:
|
|
64
|
+
names = list(range(len(cayley)))
|
|
65
|
+
|
|
66
|
+
if len(names) != len(cayley):
|
|
67
|
+
raise ValueError("The number of names must match the group order")
|
|
68
|
+
|
|
69
|
+
self.cayley = cayley
|
|
70
|
+
self.names = names
|
|
71
|
+
|
|
72
|
+
#–> DUNDERS
|
|
73
|
+
|
|
74
|
+
def __str__(self):
|
|
75
|
+
G = []
|
|
76
|
+
for i in range(self.order()):
|
|
77
|
+
g = []
|
|
78
|
+
for j in range(self.order()):
|
|
79
|
+
g.append(self.names[self.cayley[i][j]])
|
|
80
|
+
G.append(g)
|
|
81
|
+
s = str(G)
|
|
82
|
+
return f"{G}"
|
|
83
|
+
|
|
84
|
+
def __len__(self):
|
|
85
|
+
return self.order()
|
|
86
|
+
|
|
87
|
+
def __contains__(self, element):
|
|
88
|
+
return element in self.names
|
|
89
|
+
|
|
90
|
+
def __mul__(self, other):
|
|
91
|
+
return direct_product(self,other)
|
|
92
|
+
|
|
93
|
+
def __pow__(self, n):
|
|
94
|
+
return direct_power(self,n)
|
|
95
|
+
|
|
96
|
+
def __truediv__(self, subgroup):
|
|
97
|
+
return self.quotient(subgroup)
|
|
98
|
+
|
|
99
|
+
#–> GROUP METHODS
|
|
100
|
+
|
|
101
|
+
def operation(self, a, b):
|
|
102
|
+
return self.cayley[a][b]
|
|
103
|
+
|
|
104
|
+
def identity(self):
|
|
105
|
+
neutro = None
|
|
106
|
+
for e in range(self.order()):
|
|
107
|
+
ok = True
|
|
108
|
+
for a in range(self.order()):
|
|
109
|
+
if self.cayley[e][a] != a:
|
|
110
|
+
ok = False
|
|
111
|
+
break
|
|
112
|
+
if self.cayley[a][e] != a:
|
|
113
|
+
ok = False
|
|
114
|
+
break
|
|
115
|
+
if ok:
|
|
116
|
+
neutro = e
|
|
117
|
+
break
|
|
118
|
+
return neutro
|
|
119
|
+
|
|
120
|
+
def inverse(self, a):
|
|
121
|
+
e = self.identity()
|
|
122
|
+
inv = None
|
|
123
|
+
for i in range(len(self.cayley)):
|
|
124
|
+
if self.cayley[a][i]==e:
|
|
125
|
+
inv = i
|
|
126
|
+
break
|
|
127
|
+
return inv
|
|
128
|
+
|
|
129
|
+
#–> BASIC METHODS
|
|
130
|
+
|
|
131
|
+
def _print_group(self):
|
|
132
|
+
print(f"CayleyGroup({self.cayley},{self.names})")
|
|
133
|
+
|
|
134
|
+
def order(self):
|
|
135
|
+
return len(self.cayley)
|
|
136
|
+
|
|
137
|
+
def element_orders(self):
|
|
138
|
+
O = []
|
|
139
|
+
for i in range(len(self.cayley)):
|
|
140
|
+
ik = self.cayley[i][i]
|
|
141
|
+
o = 1
|
|
142
|
+
while i!=ik:
|
|
143
|
+
ik = self.cayley[ik][i]
|
|
144
|
+
o+=1
|
|
145
|
+
O.append(o)
|
|
146
|
+
return O
|
|
147
|
+
|
|
148
|
+
def order_distribution(self):
|
|
149
|
+
l = sorted(self.element_orders())
|
|
150
|
+
return dict(Counter(l))
|
|
151
|
+
|
|
152
|
+
def is_cyclic(self):
|
|
153
|
+
return self.order() in self.element_orders()
|
|
154
|
+
|
|
155
|
+
def center(self):
|
|
156
|
+
G = self.cayley
|
|
157
|
+
Z = []
|
|
158
|
+
for i in range(len(G)):
|
|
159
|
+
r = True
|
|
160
|
+
for j in range(len(G)):
|
|
161
|
+
if G[i][j]!=G[j][i]:
|
|
162
|
+
r = False
|
|
163
|
+
break
|
|
164
|
+
if r:
|
|
165
|
+
Z.append(i)
|
|
166
|
+
return(Z)
|
|
167
|
+
|
|
168
|
+
def is_abelian(self):
|
|
169
|
+
return self.order()==len(self.center())
|
|
170
|
+
|
|
171
|
+
def cayley_table(self, title="", colormap=rainbow, names=None):
|
|
172
|
+
|
|
173
|
+
if names==None:
|
|
174
|
+
if len(self.cayley)<=20:
|
|
175
|
+
names=True
|
|
176
|
+
else:
|
|
177
|
+
names=False
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
elements = range(len(self.cayley))
|
|
181
|
+
fig, ax = plt.subplots(figsize=(6, 5))
|
|
182
|
+
im = ax.imshow(self.cayley, cmap=colormap)
|
|
183
|
+
ax.xaxis.tick_top()
|
|
184
|
+
|
|
185
|
+
if names:
|
|
186
|
+
ax.set_xticks(np.arange(len(elements)))
|
|
187
|
+
ax.set_yticks(np.arange(len(elements)))
|
|
188
|
+
ax.set_xticklabels(self.names)
|
|
189
|
+
ax.set_yticklabels(self.names)
|
|
190
|
+
else:
|
|
191
|
+
ax.set_xticks([])
|
|
192
|
+
ax.set_yticks([])
|
|
193
|
+
ax.set_xticklabels([])
|
|
194
|
+
ax.set_yticklabels([])
|
|
195
|
+
|
|
196
|
+
if names:
|
|
197
|
+
for i in range(len(elements)):
|
|
198
|
+
for j in range(len(elements)):
|
|
199
|
+
color_texto = "black"
|
|
200
|
+
ax.text(
|
|
201
|
+
i,
|
|
202
|
+
j,
|
|
203
|
+
f"{self.names[self.cayley[j][i]]}",
|
|
204
|
+
ha="center",
|
|
205
|
+
va="center",
|
|
206
|
+
color=color_texto,
|
|
207
|
+
fontsize=12,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
plt.title(title)
|
|
211
|
+
plt.tight_layout()
|
|
212
|
+
plt.show()
|
|
213
|
+
|
|
214
|
+
def delete_names(self):
|
|
215
|
+
self.names = [i for i in range(len(self.cayley))]
|
|
216
|
+
|
|
217
|
+
#–> SUBGROUPS
|
|
218
|
+
|
|
219
|
+
def proper_subgroups(self):
|
|
220
|
+
G = self.cayley
|
|
221
|
+
E = self.names
|
|
222
|
+
L = []
|
|
223
|
+
|
|
224
|
+
for idxs in _graded_power_set_with_id(G):
|
|
225
|
+
# 1. Comprobación rápida de clausura
|
|
226
|
+
if _is_closed_subset(G, idxs):
|
|
227
|
+
# 2. Construcción rápida omitiendo axiomas heredados
|
|
228
|
+
sub_names = [E[j] for j in idxs]
|
|
229
|
+
sub_matrix = _reset_renaming(_subset(G, idxs))
|
|
230
|
+
|
|
231
|
+
sub_grp = CayleyGroup(sub_matrix, sub_names, _skip_validation=True)
|
|
232
|
+
L.append(CayleySubgroup(sub_grp, self))
|
|
233
|
+
|
|
234
|
+
return L
|
|
235
|
+
|
|
236
|
+
def subgroups(self):
|
|
237
|
+
# 1. Subgrupo trivial {e}
|
|
238
|
+
e_name = self.names[0]
|
|
239
|
+
trivial_grp = CayleyGroup([[0]], [e_name])
|
|
240
|
+
trivial_subgroup = CayleySubgroup(trivial_grp, self)
|
|
241
|
+
|
|
242
|
+
# 2. Subgrupos propios
|
|
243
|
+
subs = self.proper_subgroups()
|
|
244
|
+
|
|
245
|
+
# 3. Subgrupo total (el propio grupo G)
|
|
246
|
+
total_grp = CayleyGroup(self.cayley, self.names)
|
|
247
|
+
total_subgroup = CayleySubgroup(total_grp, self)
|
|
248
|
+
|
|
249
|
+
return [trivial_subgroup] + subs + [total_subgroup]
|
|
250
|
+
|
|
251
|
+
def normal_subgroups(self):
|
|
252
|
+
return [sub for sub in self.subgroups() if sub.is_normal()]
|
|
253
|
+
|
|
254
|
+
def is_simple(self):
|
|
255
|
+
normals = self.normal_subgroups()
|
|
256
|
+
return len(normals) == 2
|
|
257
|
+
|
|
258
|
+
def quotient(self, subgroup):
|
|
259
|
+
if not isinstance(subgroup, CayleySubgroup):
|
|
260
|
+
raise TypeError("Argument must be an instance of CayleySubgroup")
|
|
261
|
+
if subgroup.group is not self:
|
|
262
|
+
raise ValueError("The subgroup does not belong to this group")
|
|
263
|
+
return subgroup.quotient()
|
|
264
|
+
|
|
265
|
+
#–> AUTOMORPHISMS
|
|
266
|
+
|
|
267
|
+
def is_automorphism(self, phi):
|
|
268
|
+
|
|
269
|
+
if not isinstance(phi, (tuple, list)):
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
n = self.order()
|
|
273
|
+
if len(phi) != n:
|
|
274
|
+
return False
|
|
275
|
+
|
|
276
|
+
# 1. Biyectividad (debe ser una permutación completa de los índices 0..n-1)
|
|
277
|
+
if set(phi) != set(range(n)):
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
# 2. Conservación del elemento neutro (el índice 0)
|
|
281
|
+
if phi[0] != 0:
|
|
282
|
+
return False
|
|
283
|
+
|
|
284
|
+
# 3. Preservación de la operación: phi(a * b) == phi(a) * phi(b)
|
|
285
|
+
G = self.cayley
|
|
286
|
+
for i in range(n):
|
|
287
|
+
for j in range(n):
|
|
288
|
+
if phi[G[i][j]] != G[phi[i]][phi[j]]:
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
return True
|
|
292
|
+
|
|
293
|
+
def automorphisms(self):
|
|
294
|
+
n = self.order()
|
|
295
|
+
auts = []
|
|
296
|
+
orders = self.element_orders()[:]
|
|
297
|
+
for p in _order_preserving_permutations(orders):
|
|
298
|
+
try:
|
|
299
|
+
auts.append(Automorphism(p,self))
|
|
300
|
+
except:
|
|
301
|
+
pass
|
|
302
|
+
|
|
303
|
+
return auts
|
|
304
|
+
|
|
305
|
+
def automorphism_group(self):
|
|
306
|
+
auts = [i.phi for i in self.automorphisms()]
|
|
307
|
+
n = len(auts)
|
|
308
|
+
dict_auts = {a: i for i, a in enumerate(auts)}
|
|
309
|
+
|
|
310
|
+
# Tabla de Cayley de Aut(G) mediante composición de permutaciones: (a o b)[x] = a[b[x]]
|
|
311
|
+
cayley_aut = []
|
|
312
|
+
for i in range(n):
|
|
313
|
+
row = []
|
|
314
|
+
a = auts[i]
|
|
315
|
+
for j in range(n):
|
|
316
|
+
b = auts[j]
|
|
317
|
+
comp = tuple(a[b[k]] for k in range(len(b)))
|
|
318
|
+
row.append(dict_auts[comp])
|
|
319
|
+
cayley_aut.append(row)
|
|
320
|
+
|
|
321
|
+
names_aut = [str(auts[i]) for i in range(n)]
|
|
322
|
+
return CayleyGroup(cayley_aut, names_aut, _skip_validation=True)
|
|
323
|
+
|
|
324
|
+
class Subgroup(ABC):
|
|
325
|
+
pass
|
|
326
|
+
|
|
327
|
+
class CayleySubgroup(CayleyGroup, Subgroup):
|
|
328
|
+
|
|
329
|
+
def __init__(self, subgroup:CayleyGroup, group:CayleyGroup):
|
|
330
|
+
|
|
331
|
+
if not isinstance(subgroup, CayleyGroup) or not isinstance(group, CayleyGroup):
|
|
332
|
+
raise ValueError("Both arguments must be instances of CayleyGroup")
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
subgroup_indices = [group.names.index(name) for name in subgroup.names]
|
|
336
|
+
except ValueError:
|
|
337
|
+
raise ValueError("All elements of subgroup must belong to group")
|
|
338
|
+
|
|
339
|
+
if not _form_subgroup(group.cayley, subgroup_indices):
|
|
340
|
+
raise ValueError("The provided group is not a valid subgroup of the main group")
|
|
341
|
+
|
|
342
|
+
# Todo lo que se puede hacer con grupos ahora también con subgrupos.
|
|
343
|
+
super().__init__(subgroup.cayley, subgroup.names)
|
|
344
|
+
|
|
345
|
+
self.subgroup = subgroup
|
|
346
|
+
self.group = group
|
|
347
|
+
self.cayley = subgroup.cayley
|
|
348
|
+
self.names = subgroup.names
|
|
349
|
+
self.gcayley = group.cayley
|
|
350
|
+
self.gnames = group.names
|
|
351
|
+
self._indices = subgroup_indices
|
|
352
|
+
|
|
353
|
+
#–> DUNDERS
|
|
354
|
+
|
|
355
|
+
def __truediv__(self, other):
|
|
356
|
+
"""Permite usar la sintaxis natural G / H o H / H."""
|
|
357
|
+
return self.quotient()
|
|
358
|
+
|
|
359
|
+
def __le__(self, group):
|
|
360
|
+
return group==self.group
|
|
361
|
+
|
|
362
|
+
def __lt__(self, group):
|
|
363
|
+
return group==self.group and self.subgroup.order()<self.group.order()
|
|
364
|
+
|
|
365
|
+
#–> QUOTIENTS
|
|
366
|
+
|
|
367
|
+
def coset(self, element, side="left", return_names=True):
|
|
368
|
+
if side not in ("left", "right"):
|
|
369
|
+
raise ValueError("side must be either 'left' or 'right'")
|
|
370
|
+
|
|
371
|
+
# Buscar directamente la primera aparición del elemento en los nombres del grupo padre
|
|
372
|
+
try:
|
|
373
|
+
elem_idx = self.gnames.index(element)
|
|
374
|
+
except ValueError:
|
|
375
|
+
raise ValueError(f"Element '{element}' is not present in parent group names.")
|
|
376
|
+
|
|
377
|
+
coset_indices = []
|
|
378
|
+
for h in self._indices:
|
|
379
|
+
ah = self.gcayley[elem_idx][h] if side == "left" else self.gcayley[h][elem_idx]
|
|
380
|
+
if ah not in coset_indices:
|
|
381
|
+
coset_indices.append(ah)
|
|
382
|
+
|
|
383
|
+
coset_indices.sort()
|
|
384
|
+
|
|
385
|
+
if return_names:
|
|
386
|
+
return [self.gnames[i] for i in coset_indices]
|
|
387
|
+
return coset_indices
|
|
388
|
+
|
|
389
|
+
def is_normal(self):
|
|
390
|
+
for name in self.gnames:
|
|
391
|
+
left = self.coset(name, side="left", return_names=False)
|
|
392
|
+
right = self.coset(name, side="right", return_names=False)
|
|
393
|
+
if left != right:
|
|
394
|
+
return False
|
|
395
|
+
return True
|
|
396
|
+
|
|
397
|
+
def quotient(self):
|
|
398
|
+
"""Calcula el grupo cociente G/H devolviendo una instancia de CayleyGroup."""
|
|
399
|
+
if not self.is_normal():
|
|
400
|
+
raise ValueError("The subgroup must be normal to construct a quotient group.")
|
|
401
|
+
|
|
402
|
+
# 1. Obtener todas las clases laterales (cosets) únicas expresadas como listas de nombres
|
|
403
|
+
cosets = []
|
|
404
|
+
for name in self.gnames:
|
|
405
|
+
c = self.coset(name, side="left", return_names=True)
|
|
406
|
+
if c not in cosets:
|
|
407
|
+
cosets.append(c)
|
|
408
|
+
|
|
409
|
+
n_cosets = len(cosets)
|
|
410
|
+
|
|
411
|
+
# 2. Mapear cada elemento de G al índice de su clase lateral en 'cosets'
|
|
412
|
+
elem_to_coset = {}
|
|
413
|
+
for idx, c in enumerate(cosets):
|
|
414
|
+
for name in c:
|
|
415
|
+
elem_to_coset[name] = idx
|
|
416
|
+
|
|
417
|
+
# 3. Construir la tabla de Cayley del grupo cociente
|
|
418
|
+
quotient_cayley = []
|
|
419
|
+
for i, c1 in enumerate(cosets):
|
|
420
|
+
row = []
|
|
421
|
+
rep1_idx = self.gnames.index(c1[0])
|
|
422
|
+
for j, c2 in enumerate(cosets):
|
|
423
|
+
rep2_idx = self.gnames.index(c2[0])
|
|
424
|
+
# Producto en el grupo padre: g1 * g2
|
|
425
|
+
prod_idx = self.gcayley[rep1_idx][rep2_idx]
|
|
426
|
+
prod_name = self.gnames[prod_idx]
|
|
427
|
+
row.append(elem_to_coset[prod_name])
|
|
428
|
+
quotient_cayley.append(row)
|
|
429
|
+
|
|
430
|
+
# 4. Asignar nombres representativos a los cosets, p. ej. "{e, r}" o "gH"
|
|
431
|
+
quotient_names = [f"{{{','.join(str(e) for e in c)}}}" for c in cosets]
|
|
432
|
+
|
|
433
|
+
return CayleyGroup(quotient_cayley, quotient_names, _skip_validation=True)
|
|
434
|
+
|
|
435
|
+
class Element:
|
|
436
|
+
|
|
437
|
+
def __init__(self,element,group):
|
|
438
|
+
|
|
439
|
+
if not isinstance(group, CayleyGroup):
|
|
440
|
+
raise TypeError("Argument must be an instance of CayleyGroup")
|
|
441
|
+
|
|
442
|
+
if not element in group:
|
|
443
|
+
raise ValueError("The element must belong to the group.")
|
|
444
|
+
|
|
445
|
+
self.element = element
|
|
446
|
+
self.group = group
|
|
447
|
+
self.index = group.names.index(element)
|
|
448
|
+
|
|
449
|
+
#–> DUNDERS
|
|
450
|
+
|
|
451
|
+
def __str__(self):
|
|
452
|
+
return self.element
|
|
453
|
+
|
|
454
|
+
def __mul__(self, other):
|
|
455
|
+
return Element(self.group.names[self.group.cayley[self.index][other.index]],self.group)
|
|
456
|
+
|
|
457
|
+
def __pow__(self,k):
|
|
458
|
+
if type(k)!=int:
|
|
459
|
+
raise ValueError("n must be an integer")
|
|
460
|
+
if k==1:
|
|
461
|
+
return self
|
|
462
|
+
elif k>1:
|
|
463
|
+
p = self
|
|
464
|
+
for i in range(k-1):
|
|
465
|
+
p = p * self
|
|
466
|
+
return p
|
|
467
|
+
elif k==0:
|
|
468
|
+
return Element(self.group.identity(),self.group)
|
|
469
|
+
elif k==-1:
|
|
470
|
+
return self.inverse()
|
|
471
|
+
elif k<-1:
|
|
472
|
+
p = self.inverse()
|
|
473
|
+
for i in range(k-1):
|
|
474
|
+
p = p * self
|
|
475
|
+
return p
|
|
476
|
+
|
|
477
|
+
def __eq__(self,other):
|
|
478
|
+
return self.element==other.element and self.group==other.group
|
|
479
|
+
|
|
480
|
+
#–> OTHER FUNCTIONS
|
|
481
|
+
|
|
482
|
+
def inverse(self):
|
|
483
|
+
inv = None
|
|
484
|
+
for i in self.group.names:
|
|
485
|
+
if self*Element(i,self.group)==Element(self.group.names[self.group.identity()],self.group):
|
|
486
|
+
inv = i
|
|
487
|
+
break
|
|
488
|
+
return Element(inv,self.group)
|
|
489
|
+
|
|
490
|
+
class Automorphism:
|
|
491
|
+
|
|
492
|
+
def __init__(self,phi:tuple,group:CayleyGroup):
|
|
493
|
+
|
|
494
|
+
if not group.is_automorphism(phi):
|
|
495
|
+
raise ValueError("The tuple phi has to be an automorphism of G.")
|
|
496
|
+
|
|
497
|
+
self.phi = phi
|
|
498
|
+
self.group = group
|
|
499
|
+
|
|
500
|
+
def __len__(self):
|
|
501
|
+
return len(self.phi)
|
|
502
|
+
|
|
503
|
+
def __str__(self):
|
|
504
|
+
return str(self.phi)
|
|
505
|
+
|
|
506
|
+
class AutomorphismFunction:
|
|
507
|
+
|
|
508
|
+
def __init__(self,function:list,group:CayleyGroup):
|
|
509
|
+
f = []
|
|
510
|
+
for i in function:
|
|
511
|
+
if type(i)==Automorphism:
|
|
512
|
+
f.append(i.phi)
|
|
513
|
+
elif type(i)==tuple:
|
|
514
|
+
if not group.is_automorphism(i):
|
|
515
|
+
raise ValueError("The transformation is not a valid automorfism for this group.")
|
|
516
|
+
f.append(i)
|
|
517
|
+
else:
|
|
518
|
+
raise TypeError("The argument must be a list of a Automorphisms or tuples.")
|
|
519
|
+
|
|
520
|
+
self.phi = f
|
|
521
|
+
self.group = group
|
|
522
|
+
|
|
523
|
+
def __str__(self):
|
|
524
|
+
return str(self.phi)
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
#################### HIDDEN COMMANDS ####################
|
|
528
|
+
|
|
529
|
+
def _obtener_nombre(var_obj):
|
|
530
|
+
for nombre, valor in globals().items():
|
|
531
|
+
if valor is var_obj:
|
|
532
|
+
return nombre
|
|
533
|
+
return None
|
|
534
|
+
|
|
535
|
+
def _is_closed(tabla):
|
|
536
|
+
n = len(tabla)
|
|
537
|
+
E = _get_elements(tabla)
|
|
538
|
+
if len(E)!=n:
|
|
539
|
+
return False
|
|
540
|
+
for fila in tabla:
|
|
541
|
+
if len(fila) != n:
|
|
542
|
+
return False
|
|
543
|
+
return True
|
|
544
|
+
|
|
545
|
+
def _is_group(tabla):
|
|
546
|
+
n = len(tabla)
|
|
547
|
+
|
|
548
|
+
# 1. Clausura
|
|
549
|
+
for fila in tabla:
|
|
550
|
+
if len(fila) != n:
|
|
551
|
+
return (False,"ClosingError")
|
|
552
|
+
for x in fila:
|
|
553
|
+
if not (0 <= x < n):
|
|
554
|
+
return (False,"ClosingError")
|
|
555
|
+
|
|
556
|
+
# 2. Cada fila y columna debe ser una permutación
|
|
557
|
+
conjunto = set(range(n))
|
|
558
|
+
|
|
559
|
+
for fila in tabla:
|
|
560
|
+
if set(fila) != conjunto:
|
|
561
|
+
return (False,f"UniquenessError – row {tabla.index(fila)}")
|
|
562
|
+
|
|
563
|
+
for j in range(n):
|
|
564
|
+
columna = {tabla[i][j] for i in range(n)}
|
|
565
|
+
if columna != conjunto:
|
|
566
|
+
return (False,f"UniquenessError – column {j}")
|
|
567
|
+
|
|
568
|
+
# 3. Buscar neutro
|
|
569
|
+
neutro = None
|
|
570
|
+
|
|
571
|
+
for e in range(n):
|
|
572
|
+
ok = True
|
|
573
|
+
|
|
574
|
+
for a in range(n):
|
|
575
|
+
if tabla[e][a] != a:
|
|
576
|
+
ok = False
|
|
577
|
+
break
|
|
578
|
+
if tabla[a][e] != a:
|
|
579
|
+
ok = False
|
|
580
|
+
break
|
|
581
|
+
|
|
582
|
+
if ok:
|
|
583
|
+
neutro = e
|
|
584
|
+
break
|
|
585
|
+
|
|
586
|
+
if neutro is None:
|
|
587
|
+
return (False,"IndentityError – no identity element found")
|
|
588
|
+
|
|
589
|
+
# 4. Inversos
|
|
590
|
+
for a in range(n):
|
|
591
|
+
existe = False
|
|
592
|
+
|
|
593
|
+
for b in range(n):
|
|
594
|
+
if tabla[a][b] == neutro and tabla[b][a] == neutro:
|
|
595
|
+
existe = True
|
|
596
|
+
break
|
|
597
|
+
|
|
598
|
+
if not existe:
|
|
599
|
+
return (False,f"InverseError – ({a},{b})")
|
|
600
|
+
|
|
601
|
+
# 5. Asociatividad
|
|
602
|
+
for a in range(n):
|
|
603
|
+
for b in range(n):
|
|
604
|
+
for c in range(n):
|
|
605
|
+
|
|
606
|
+
izquierda = tabla[tabla[a][b]][c]
|
|
607
|
+
derecha = tabla[a][tabla[b][c]]
|
|
608
|
+
|
|
609
|
+
if izquierda != derecha:
|
|
610
|
+
return (False,f"AssociativityError – ({a},{b},{c})")
|
|
611
|
+
|
|
612
|
+
return (True,"G is a group")
|
|
613
|
+
|
|
614
|
+
def _identity(G):
|
|
615
|
+
neutro = None
|
|
616
|
+
for e in range(len(G)):
|
|
617
|
+
ok = True
|
|
618
|
+
for a in range(len(G)):
|
|
619
|
+
if G[e][a] != a:
|
|
620
|
+
ok = False
|
|
621
|
+
break
|
|
622
|
+
if G[a][e] != a:
|
|
623
|
+
ok = False
|
|
624
|
+
break
|
|
625
|
+
if ok:
|
|
626
|
+
neutro = e
|
|
627
|
+
break
|
|
628
|
+
if neutro is None:
|
|
629
|
+
return (False,"IndentityError – no identity element found")
|
|
630
|
+
return neutro
|
|
631
|
+
|
|
632
|
+
def _subset(G, elements):
|
|
633
|
+
return [[G[r][c] for c in elements] for r in elements]
|
|
634
|
+
|
|
635
|
+
def _form_subgroup(G,elements):
|
|
636
|
+
# Por propiedades de los subgrupos, solo hace falta comprobar la clausura, el resto se heredan del grupo principal.
|
|
637
|
+
return _is_closed(_reset_renaming(_subset(G,elements)))
|
|
638
|
+
|
|
639
|
+
def _sign(p):
|
|
640
|
+
"""
|
|
641
|
+
Devuelve:
|
|
642
|
+
1 -> permutación par
|
|
643
|
+
-1 -> permutación impar
|
|
644
|
+
"""
|
|
645
|
+
inv = 0
|
|
646
|
+
n = len(p)
|
|
647
|
+
|
|
648
|
+
for i in range(n):
|
|
649
|
+
for j in range(i+1, n):
|
|
650
|
+
if p[i] > p[j]:
|
|
651
|
+
inv += 1
|
|
652
|
+
|
|
653
|
+
return 1 if inv % 2 == 0 else -1
|
|
654
|
+
|
|
655
|
+
def _renamed_elements(G,L):
|
|
656
|
+
renamed_G = []
|
|
657
|
+
for I in G:
|
|
658
|
+
H = []
|
|
659
|
+
for i in I:
|
|
660
|
+
H.append(L[i])
|
|
661
|
+
renamed_G.append(H)
|
|
662
|
+
return renamed_G
|
|
663
|
+
|
|
664
|
+
def _get_elements(G):
|
|
665
|
+
"""
|
|
666
|
+
Devuelve una lista con todos los elementos/nombres únicos que aparecen
|
|
667
|
+
dentro de la tabla de Cayley G, conservando el orden de primera aparición.
|
|
668
|
+
|
|
669
|
+
Parámetros:
|
|
670
|
+
G: Lista de listas que representa la tabla de Cayley.
|
|
671
|
+
|
|
672
|
+
Devuelve:
|
|
673
|
+
Lista con los elementos únicos del grupo.
|
|
674
|
+
"""
|
|
675
|
+
# dict.fromkeys() elimina duplicados preservando el orden de aparición
|
|
676
|
+
return list(dict.fromkeys(e for fila in G for e in fila))
|
|
677
|
+
|
|
678
|
+
def _min_div(n):
|
|
679
|
+
for i in range(2, int(n**0.5) + 1):
|
|
680
|
+
if n % i == 0:
|
|
681
|
+
return i
|
|
682
|
+
return n
|
|
683
|
+
|
|
684
|
+
def _graded_power_set_with_id(G):
|
|
685
|
+
|
|
686
|
+
# Principios
|
|
687
|
+
n = len(G)
|
|
688
|
+
m = _min_div(n)
|
|
689
|
+
e = _identity(G)
|
|
690
|
+
|
|
691
|
+
def compute_orders(G):
|
|
692
|
+
|
|
693
|
+
if not _is_group(G)[0]:
|
|
694
|
+
raise TypeError
|
|
695
|
+
|
|
696
|
+
O = []
|
|
697
|
+
for i in range(len(G)):
|
|
698
|
+
ik = G[i][i]
|
|
699
|
+
o = 1
|
|
700
|
+
while i!=ik:
|
|
701
|
+
ik = G[ik][i]
|
|
702
|
+
o+=1
|
|
703
|
+
O.append(o)
|
|
704
|
+
|
|
705
|
+
return O
|
|
706
|
+
|
|
707
|
+
orders = compute_orders(G)
|
|
708
|
+
|
|
709
|
+
# Posibles órdenes de subgrupos (Lagrange)
|
|
710
|
+
R = []
|
|
711
|
+
for r in range(int(n/m)):
|
|
712
|
+
if n%(r+1)==0:
|
|
713
|
+
R.append(r+1)
|
|
714
|
+
#R.append(n)
|
|
715
|
+
|
|
716
|
+
Rm = []
|
|
717
|
+
for r in R[1:]:
|
|
718
|
+
Rm.append(r-1)
|
|
719
|
+
|
|
720
|
+
P = []
|
|
721
|
+
|
|
722
|
+
# Bucle principal
|
|
723
|
+
for rm in Rm:
|
|
724
|
+
E = []
|
|
725
|
+
for el in range(1,n):
|
|
726
|
+
if (rm+1)%orders[el]==0:
|
|
727
|
+
E.append(el)
|
|
728
|
+
|
|
729
|
+
for c in list(combinations(E, rm)):
|
|
730
|
+
P.append(sorted([e]+list(c)))
|
|
731
|
+
|
|
732
|
+
return P
|
|
733
|
+
|
|
734
|
+
def _is_closed_subset(G_cayley, indices):
|
|
735
|
+
"""Comprueba clausura en O(|H|^2) usando un conjunto de índices."""
|
|
736
|
+
indices_set = set(indices)
|
|
737
|
+
for i in indices:
|
|
738
|
+
for j in indices:
|
|
739
|
+
if G_cayley[i][j] not in indices_set:
|
|
740
|
+
return False
|
|
741
|
+
return True
|
|
742
|
+
|
|
743
|
+
def _operate_cosets(G,C,A,B):
|
|
744
|
+
a = A[0]
|
|
745
|
+
b = B[0]
|
|
746
|
+
g = G[a][b]
|
|
747
|
+
for i in C:
|
|
748
|
+
if g in i:
|
|
749
|
+
r = i
|
|
750
|
+
break
|
|
751
|
+
return r
|
|
752
|
+
|
|
753
|
+
def _order_preserving_permutations(orders):
|
|
754
|
+
|
|
755
|
+
classes = {}
|
|
756
|
+
|
|
757
|
+
for i, order in enumerate(orders):
|
|
758
|
+
classes.setdefault(order, []).append(i)
|
|
759
|
+
|
|
760
|
+
classes = list(classes.values())
|
|
761
|
+
|
|
762
|
+
for perms in product(*(permutations(c) for c in classes)):
|
|
763
|
+
result = list(range(len(orders)))
|
|
764
|
+
|
|
765
|
+
for domain, image in zip(classes, perms):
|
|
766
|
+
for x, y in zip(domain, image):
|
|
767
|
+
result[x] = y
|
|
768
|
+
|
|
769
|
+
yield tuple(result)
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
#################### PRODUCTS ####################
|
|
773
|
+
|
|
774
|
+
def direct_product(A:CayleyGroup,B:CayleyGroup):
|
|
775
|
+
G = []
|
|
776
|
+
n, m = A.order(), B.order()
|
|
777
|
+
|
|
778
|
+
for a1 in range(n):
|
|
779
|
+
for b1 in range(m):
|
|
780
|
+
g = []
|
|
781
|
+
for a2 in range(n):
|
|
782
|
+
for b2 in range(m):
|
|
783
|
+
x2 = (a1,b1)
|
|
784
|
+
g.append( (A.cayley[a1][a2])*m+(B.cayley[b1][b2]) )
|
|
785
|
+
G.append(g)
|
|
786
|
+
|
|
787
|
+
names = []
|
|
788
|
+
for a in range(n):
|
|
789
|
+
for b in range(m):
|
|
790
|
+
names.append(str(A.names[a])+","+str(B.names[b]))
|
|
791
|
+
|
|
792
|
+
return CayleyGroup(G,names,_skip_validation=True)
|
|
793
|
+
|
|
794
|
+
def direct_power(G:CayleyGroup,n):
|
|
795
|
+
if n<2:
|
|
796
|
+
raise ValueError("n must be at least 2")
|
|
797
|
+
H = G
|
|
798
|
+
for i in range(n-1):
|
|
799
|
+
H = direct_product(H,G)
|
|
800
|
+
return H
|
|
801
|
+
|
|
802
|
+
def semidirect_product(A: CayleyGroup, B: CayleyGroup, f: AutomorphismFunction):
|
|
803
|
+
G = []
|
|
804
|
+
n, m = A.order(), B.order()
|
|
805
|
+
|
|
806
|
+
phi_list = f.phi if hasattr(f, "phi") else f
|
|
807
|
+
|
|
808
|
+
for a1 in range(n):
|
|
809
|
+
for b1 in range(m):
|
|
810
|
+
g = []
|
|
811
|
+
phi_b1 = phi_list[b1]
|
|
812
|
+
for a2 in range(n):
|
|
813
|
+
for b2 in range(m):
|
|
814
|
+
a2_trans = phi_b1[a2]
|
|
815
|
+
a_prod = A.cayley[a1][a2_trans]
|
|
816
|
+
b_prod = B.cayley[b1][b2]
|
|
817
|
+
g.append(a_prod * m + b_prod)
|
|
818
|
+
G.append(g)
|
|
819
|
+
|
|
820
|
+
names = []
|
|
821
|
+
for a in range(n):
|
|
822
|
+
for b in range(m):
|
|
823
|
+
names.append(str(A.names[a]) + "," + str(B.names[b]))
|
|
824
|
+
|
|
825
|
+
return CayleyGroup(G, names, _skip_validation=True)
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
#################### GENERATORS ####################
|
|
829
|
+
|
|
830
|
+
def cyclic_group(n):
|
|
831
|
+
elements = range(n)
|
|
832
|
+
G = []
|
|
833
|
+
for i in elements:
|
|
834
|
+
g = []
|
|
835
|
+
for j in elements:
|
|
836
|
+
g.append((i+j)%n)
|
|
837
|
+
G.append(g)
|
|
838
|
+
return CayleyGroup(G,_renaming_C(n))
|
|
839
|
+
|
|
840
|
+
def symmetric_group(n):
|
|
841
|
+
|
|
842
|
+
# Lista de todas las permutaciones
|
|
843
|
+
perms = list(permutations(range(n)))
|
|
844
|
+
|
|
845
|
+
# Diccionario permutación -> índice
|
|
846
|
+
index = {p: i for i, p in enumerate(perms)}
|
|
847
|
+
|
|
848
|
+
# Tabla
|
|
849
|
+
G = []
|
|
850
|
+
|
|
851
|
+
for p in perms:
|
|
852
|
+
fila = []
|
|
853
|
+
for q in perms:
|
|
854
|
+
fila.append(index[tuple(p[i] for i in q)])
|
|
855
|
+
G.append(fila)
|
|
856
|
+
|
|
857
|
+
return CayleyGroup(G,_renaming_S(n))
|
|
858
|
+
|
|
859
|
+
def alternating_group(n):
|
|
860
|
+
|
|
861
|
+
perms = [p for p in permutations(range(n)) if _sign(p) == 1]
|
|
862
|
+
|
|
863
|
+
index = {p: i for i, p in enumerate(perms)}
|
|
864
|
+
|
|
865
|
+
G = []
|
|
866
|
+
|
|
867
|
+
for p in perms:
|
|
868
|
+
fila = []
|
|
869
|
+
for q in perms:
|
|
870
|
+
fila.append(index[tuple(p[i] for i in q)])
|
|
871
|
+
G.append(fila)
|
|
872
|
+
|
|
873
|
+
return CayleyGroup(G,_renaming_A(n))
|
|
874
|
+
|
|
875
|
+
def dihedric_group(n):
|
|
876
|
+
|
|
877
|
+
elems = [(k,0) for k in range(n)] + [(k,1) for k in range(n)]
|
|
878
|
+
|
|
879
|
+
index = {g:i for i,g in enumerate(elems)}
|
|
880
|
+
|
|
881
|
+
G = []
|
|
882
|
+
|
|
883
|
+
for (k,a) in elems:
|
|
884
|
+
fila = []
|
|
885
|
+
|
|
886
|
+
for (l,b) in elems:
|
|
887
|
+
|
|
888
|
+
if a == 0:
|
|
889
|
+
m = (k + l) % n
|
|
890
|
+
else:
|
|
891
|
+
m = (k - l) % n
|
|
892
|
+
|
|
893
|
+
fila.append(index[(m, a ^ b)])
|
|
894
|
+
|
|
895
|
+
G.append(fila)
|
|
896
|
+
|
|
897
|
+
return CayleyGroup(G,_renaming_D(n))
|
|
898
|
+
|
|
899
|
+
def quaternion_group():
|
|
900
|
+
|
|
901
|
+
# Multiplicación para la parte positiva
|
|
902
|
+
base = {
|
|
903
|
+
(0,0):( 1,0),
|
|
904
|
+
(0,1):( 1,1),
|
|
905
|
+
(0,2):( 1,2),
|
|
906
|
+
(0,3):( 1,3),
|
|
907
|
+
|
|
908
|
+
(1,0):( 1,1),
|
|
909
|
+
(2,0):( 1,2),
|
|
910
|
+
(3,0):( 1,3),
|
|
911
|
+
|
|
912
|
+
(1,1):(-1,0),
|
|
913
|
+
(2,2):(-1,0),
|
|
914
|
+
(3,3):(-1,0),
|
|
915
|
+
|
|
916
|
+
(1,2):( 1,3),
|
|
917
|
+
(2,3):( 1,1),
|
|
918
|
+
(3,1):( 1,2),
|
|
919
|
+
|
|
920
|
+
(2,1):(-1,3),
|
|
921
|
+
(3,2):(-1,1),
|
|
922
|
+
(1,3):(-1,2),
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
def multiply(a, b):
|
|
926
|
+
sa, xa = a
|
|
927
|
+
sb, xb = b
|
|
928
|
+
|
|
929
|
+
s, x = base[(xa, xb)]
|
|
930
|
+
|
|
931
|
+
return (sa * sb * s, x)
|
|
932
|
+
|
|
933
|
+
elements = [
|
|
934
|
+
( 1,0), (-1,0),
|
|
935
|
+
( 1,1), (-1,1),
|
|
936
|
+
( 1,2), (-1,2),
|
|
937
|
+
( 1,3), (-1,3)
|
|
938
|
+
]
|
|
939
|
+
|
|
940
|
+
index = {g: i for i, g in enumerate(elements)}
|
|
941
|
+
|
|
942
|
+
G = []
|
|
943
|
+
|
|
944
|
+
for a in elements:
|
|
945
|
+
fila = []
|
|
946
|
+
|
|
947
|
+
for b in elements:
|
|
948
|
+
fila.append(index[multiply(a, b)])
|
|
949
|
+
|
|
950
|
+
G.append(fila)
|
|
951
|
+
|
|
952
|
+
return CayleyGroup(G,_renaming_Q8())
|
|
953
|
+
|
|
954
|
+
def units_group(n):
|
|
955
|
+
|
|
956
|
+
if n <= 1:
|
|
957
|
+
raise ValueError("n debe ser mayor que 1")
|
|
958
|
+
|
|
959
|
+
elems = [x for x in range(1, n) if gcd(x, n) == 1]
|
|
960
|
+
index = {g: i for i, g in enumerate(elems)}
|
|
961
|
+
|
|
962
|
+
G = []
|
|
963
|
+
for a in elems:
|
|
964
|
+
fila = []
|
|
965
|
+
for b in elems:
|
|
966
|
+
fila.append(index[(a * b) % n])
|
|
967
|
+
G.append(fila)
|
|
968
|
+
|
|
969
|
+
return CayleyGroup(G,_renaming_U(n))
|
|
970
|
+
|
|
971
|
+
def dicyclic_group(n):
|
|
972
|
+
"""
|
|
973
|
+
Genera la tabla de Cayley del grupo dicíclico Dic_n (de orden 4n).
|
|
974
|
+
Presentación: <a, x | a^(2n) = 1, x^2 = a^n, x^-1 a x = a^-1>
|
|
975
|
+
"""
|
|
976
|
+
if n < 1:
|
|
977
|
+
raise ValueError("n debe ser un entero positivo")
|
|
978
|
+
|
|
979
|
+
# Elementos representados como pares (k, e) donde 0 <= k < 2n y e en {0, 1}
|
|
980
|
+
# (k, 0) -> a^k
|
|
981
|
+
# (k, 1) -> a^k * x
|
|
982
|
+
elems = [(k, 0) for k in range(2 * n)] + [(k, 1) for k in range(2 * n)]
|
|
983
|
+
index = {g: i for i, g in enumerate(elems)}
|
|
984
|
+
|
|
985
|
+
G = []
|
|
986
|
+
for (k, a) in elems:
|
|
987
|
+
fila = []
|
|
988
|
+
for (l, b) in elems:
|
|
989
|
+
if a == 0:
|
|
990
|
+
m = (k + l) % (2 * n)
|
|
991
|
+
c = b
|
|
992
|
+
else:
|
|
993
|
+
if b == 0:
|
|
994
|
+
m = (k - l) % (2 * n)
|
|
995
|
+
c = 1
|
|
996
|
+
else:
|
|
997
|
+
m = (k - l + n) % (2 * n)
|
|
998
|
+
c = 0
|
|
999
|
+
fila.append(index[(m, c)])
|
|
1000
|
+
G.append(fila)
|
|
1001
|
+
|
|
1002
|
+
return CayleyGroup(G,_renaming_Dic(n))
|
|
1003
|
+
|
|
1004
|
+
def tetrahedral_group():
|
|
1005
|
+
"""
|
|
1006
|
+
Full symmetry group of the tetrahedron. Isomorphic to S₄.
|
|
1007
|
+
"""
|
|
1008
|
+
return CayleyGroup(symmetric_group(4).cayley)
|
|
1009
|
+
|
|
1010
|
+
def octahedral_group():
|
|
1011
|
+
"""
|
|
1012
|
+
Full symmetry group of the cube/octahedron. Isomorphic to S₄ × C₂.
|
|
1013
|
+
"""
|
|
1014
|
+
return CayleyGroup((symmetric_group(4) * cyclic_group(2)).cayley)
|
|
1015
|
+
|
|
1016
|
+
def icosahedral_group():
|
|
1017
|
+
"""
|
|
1018
|
+
Full symmetry group of the icosahedron/dodecahedron. Isomorphic to A₅ × C₂.
|
|
1019
|
+
"""
|
|
1020
|
+
return CayleyGroup((alternating_group(5) * cyclic_group(2)).cayley)
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
#################### VISUALIZATION AND RENAMING HELPERS ####################
|
|
1024
|
+
|
|
1025
|
+
def cayley_table(G, title="", colormap=rainbow, names="", renaming=[]):
|
|
1026
|
+
if title=="":
|
|
1027
|
+
if _obtener_nombre(G)==None:
|
|
1028
|
+
title = f"Cayley table"
|
|
1029
|
+
else:
|
|
1030
|
+
title = f"Cayley table of {_obtener_nombre(G)}"
|
|
1031
|
+
if names=="":
|
|
1032
|
+
if len(G)<=20:
|
|
1033
|
+
names=True
|
|
1034
|
+
else:
|
|
1035
|
+
names=False
|
|
1036
|
+
if renaming==[]:
|
|
1037
|
+
renaming = [rf"${e}$" for e in range(len(G))]
|
|
1038
|
+
|
|
1039
|
+
elements = range(len(G))
|
|
1040
|
+
fig, ax = plt.subplots(figsize=(6, 5))
|
|
1041
|
+
im = ax.imshow(G, cmap=colormap)
|
|
1042
|
+
ax.set_xticks(np.arange(len(elements)))
|
|
1043
|
+
ax.set_yticks(np.arange(len(elements)))
|
|
1044
|
+
ax.set_xticklabels(renaming)
|
|
1045
|
+
ax.set_yticklabels(renaming)
|
|
1046
|
+
|
|
1047
|
+
ax.xaxis.tick_top()
|
|
1048
|
+
|
|
1049
|
+
if names:
|
|
1050
|
+
for i in range(len(elements)):
|
|
1051
|
+
for j in range(len(elements)):
|
|
1052
|
+
color_texto = "black"
|
|
1053
|
+
ax.text(
|
|
1054
|
+
i,
|
|
1055
|
+
j,
|
|
1056
|
+
f"{renaming[G[j][i]]}",
|
|
1057
|
+
ha="center",
|
|
1058
|
+
va="center",
|
|
1059
|
+
color=color_texto,
|
|
1060
|
+
fontsize=12,
|
|
1061
|
+
)
|
|
1062
|
+
|
|
1063
|
+
plt.title(title)
|
|
1064
|
+
plt.tight_layout()
|
|
1065
|
+
plt.show()
|
|
1066
|
+
|
|
1067
|
+
def _renaming_C(n):
|
|
1068
|
+
r = []
|
|
1069
|
+
if n >= 1:
|
|
1070
|
+
r.append(r"$e$")
|
|
1071
|
+
if n > 1:
|
|
1072
|
+
r.append(r"$r$")
|
|
1073
|
+
if n > 2:
|
|
1074
|
+
for i in range(2, n):
|
|
1075
|
+
r.append(rf"$r^{{{i}}}$")
|
|
1076
|
+
return r
|
|
1077
|
+
|
|
1078
|
+
def _renaming_S(n):
|
|
1079
|
+
"""
|
|
1080
|
+
Renombrado para S_n en notación de ciclos.
|
|
1081
|
+
Coincide con el orden de elementos de generate_group_S(n).
|
|
1082
|
+
"""
|
|
1083
|
+
def tuple_to_cycle(p):
|
|
1084
|
+
visited = [False] * len(p)
|
|
1085
|
+
cycles = []
|
|
1086
|
+
for i in range(len(p)):
|
|
1087
|
+
if not visited[i]:
|
|
1088
|
+
curr = i
|
|
1089
|
+
cycle = []
|
|
1090
|
+
while not visited[curr]:
|
|
1091
|
+
visited[curr] = True
|
|
1092
|
+
cycle.append(curr + 1) # Usamos representación 1-based (1..n)
|
|
1093
|
+
curr = p[curr]
|
|
1094
|
+
if len(cycle) > 1:
|
|
1095
|
+
cycles.append("(" + "".join(map(str, cycle)) + ")")
|
|
1096
|
+
return "".join(cycles) if cycles else "e"
|
|
1097
|
+
|
|
1098
|
+
perms = list(permutations(range(n)))
|
|
1099
|
+
return [tuple_to_cycle(p) for p in perms]
|
|
1100
|
+
|
|
1101
|
+
def _renaming_A(n):
|
|
1102
|
+
"""
|
|
1103
|
+
Renombrado para A_n en notación de ciclos.
|
|
1104
|
+
Coincide con el orden de elementos de generate_group_A(n).
|
|
1105
|
+
"""
|
|
1106
|
+
def tuple_to_cycle(p):
|
|
1107
|
+
visited = [False] * len(p)
|
|
1108
|
+
cycles = []
|
|
1109
|
+
for i in range(len(p)):
|
|
1110
|
+
if not visited[i]:
|
|
1111
|
+
curr = i
|
|
1112
|
+
cycle = []
|
|
1113
|
+
while not visited[curr]:
|
|
1114
|
+
visited[curr] = True
|
|
1115
|
+
cycle.append(curr + 1)
|
|
1116
|
+
curr = p[curr]
|
|
1117
|
+
if len(cycle) > 1:
|
|
1118
|
+
cycles.append("(" + "".join(map(str, cycle)) + ")")
|
|
1119
|
+
return "".join(cycles) if cycles else "e"
|
|
1120
|
+
|
|
1121
|
+
perms = [p for p in permutations(range(n)) if _sign(p) == 1]
|
|
1122
|
+
return [tuple_to_cycle(p) for p in perms]
|
|
1123
|
+
|
|
1124
|
+
def _renaming_D(n):
|
|
1125
|
+
r = []
|
|
1126
|
+
# Rotaciones
|
|
1127
|
+
for k in range(n):
|
|
1128
|
+
if k == 0:
|
|
1129
|
+
r.append(r"$e$")
|
|
1130
|
+
elif k == 1:
|
|
1131
|
+
r.append(r"$r$")
|
|
1132
|
+
else:
|
|
1133
|
+
r.append(rf"$r^{{{k}}}$")
|
|
1134
|
+
|
|
1135
|
+
# Reflexiones
|
|
1136
|
+
for k in range(n):
|
|
1137
|
+
if k == 0:
|
|
1138
|
+
r.append(r"$s$")
|
|
1139
|
+
elif k == 1:
|
|
1140
|
+
r.append(r"$rs$")
|
|
1141
|
+
else:
|
|
1142
|
+
r.append(rf"$r^{{{k}}}s$")
|
|
1143
|
+
|
|
1144
|
+
return r
|
|
1145
|
+
|
|
1146
|
+
def _renaming_Q8():
|
|
1147
|
+
return ["1","-1","i","-i","j","-j","k","-k"]
|
|
1148
|
+
|
|
1149
|
+
def _renaming_U(n):
|
|
1150
|
+
return [str(x) for x in range(1, n) if gcd(x, n) == 1]
|
|
1151
|
+
|
|
1152
|
+
def _renaming_Dic(n):
|
|
1153
|
+
names = []
|
|
1154
|
+
for k in range(2 * n):
|
|
1155
|
+
if k == 0:
|
|
1156
|
+
names.append(r"$e$")
|
|
1157
|
+
elif k == 1:
|
|
1158
|
+
names.append(r"$a$")
|
|
1159
|
+
else:
|
|
1160
|
+
names.append(rf"$a^{{{k}}}$")
|
|
1161
|
+
for k in range(2 * n):
|
|
1162
|
+
if k == 0:
|
|
1163
|
+
names.append(r"$x$")
|
|
1164
|
+
elif k == 1:
|
|
1165
|
+
names.append(r"$ax$")
|
|
1166
|
+
else:
|
|
1167
|
+
names.append(rf"$a^{{{k}}}x$")
|
|
1168
|
+
return names
|
|
1169
|
+
|
|
1170
|
+
def _reset_renaming(G):
|
|
1171
|
+
l = _get_elements(G)
|
|
1172
|
+
D = {}
|
|
1173
|
+
for i in range(len(l)):
|
|
1174
|
+
D[l[i]]=i
|
|
1175
|
+
return _renamed_elements(G,D)
|
|
1176
|
+
|