GroupsMath 0.6.0__tar.gz → 0.7.0__tar.gz
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-0.6.0 → groupsmath-0.7.0}/GroupsMath.egg-info/PKG-INFO +11 -7
- {groupsmath-0.6.0 → groupsmath-0.7.0}/GroupsMath.egg-info/SOURCES.txt +3 -1
- {groupsmath-0.6.0 → groupsmath-0.7.0}/PKG-INFO +11 -7
- {groupsmath-0.6.0 → groupsmath-0.7.0}/README.md +9 -5
- {groupsmath-0.6.0 → groupsmath-0.7.0}/groupsmath/core.py +46 -15
- {groupsmath-0.6.0 → groupsmath-0.7.0}/groupsmath/matrixgroups.py +37 -15
- groupsmath-0.7.0/groupsmath/presentations.py +558 -0
- groupsmath-0.7.0/groupsmath/structure.py +120 -0
- {groupsmath-0.6.0 → groupsmath-0.7.0}/pyproject.toml +4 -4
- {groupsmath-0.6.0 → groupsmath-0.7.0}/GroupsMath.egg-info/dependency_links.txt +0 -0
- {groupsmath-0.6.0 → groupsmath-0.7.0}/GroupsMath.egg-info/requires.txt +0 -0
- {groupsmath-0.6.0 → groupsmath-0.7.0}/GroupsMath.egg-info/top_level.txt +0 -0
- {groupsmath-0.6.0 → groupsmath-0.7.0}/LICENSE +0 -0
- {groupsmath-0.6.0 → groupsmath-0.7.0}/groupsmath/__init__.py +0 -0
- {groupsmath-0.6.0 → groupsmath-0.7.0}/groupsmath/precooked.py +0 -0
- {groupsmath-0.6.0 → groupsmath-0.7.0}/groupsmath/shortcuts.py +0 -0
- {groupsmath-0.6.0 → groupsmath-0.7.0}/setup.cfg +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GroupsMath
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: The educational Python library for Group Theory
|
|
5
5
|
Author-email: Mario Sultan Romero <mariosultan.uem@gmail.com>
|
|
6
6
|
License: PolyForm Strict License 1.0.0.
|
|
7
7
|
Project-URL: Homepage, https://github.com/MarioSultan/GroupsMath
|
|
8
|
-
Project-URL: Documentation, https://
|
|
8
|
+
Project-URL: Documentation, https://groupsmath.readthedocs.io/en/latest/
|
|
9
9
|
Project-URL: Issues, https://github.com/MarioSultan/GroupsMath/issues
|
|
10
10
|
Keywords: math,mathematics,algebra,abstract-algebra,group-theory,finite-groups,sylow,quotient-groups,automorphisms,group,groups,group theory,finte groups,finite group theory,finite-group-theory
|
|
11
11
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
@@ -20,13 +20,13 @@ Requires-Dist: matplotlib>=3.0.0
|
|
|
20
20
|
Requires-Dist: numpy>=1.20.0
|
|
21
21
|
Dynamic: license-file
|
|
22
22
|
|
|
23
|
-
# GroupsMath – v0.
|
|
23
|
+
# GroupsMath – v0.7.0 – release candidate for v1.0.0
|
|
24
24
|
|
|
25
25
|
[](https://pypistats.org/packages/groupsmath)
|
|
26
26
|
[](https://pypi.org/project/groupsmath/)
|
|
27
27
|
[](https://pypi.org/project/groupsmath/)
|
|
28
28
|

|
|
29
|
-

|
|
30
30
|
<!--  -->
|
|
31
31
|
|
|
32
32
|
|
|
@@ -39,6 +39,8 @@ The project aims to provide a **simple** and **intuitive** interface for working
|
|
|
39
39
|
|
|
40
40
|

|
|
41
41
|
|
|
42
|
+
> Visit here the [GroupsMath official documentation](https://groupsmath.readthedocs.io/en/latest/).
|
|
43
|
+
|
|
42
44
|
> **⚠️ Notice:** Version v1.0.0 is nearing its official release.
|
|
43
45
|
|
|
44
46
|
|
|
@@ -117,7 +119,7 @@ GroupsMath is designed to provide tools for working with finite (and infinite) g
|
|
|
117
119
|
* Centralizers, conjugacy classes and conmutator.
|
|
118
120
|
* Computation and exploration of other structural properties of finite groups.
|
|
119
121
|
* Predefined group families in the library `groupsmath.precooked`, such as $C_n$, $S_n$, $A_n$, $D_n$, $V_4$, $Q_8$, ...
|
|
120
|
-
*
|
|
122
|
+
* Structure description. (*experimental feature*)
|
|
121
123
|
|
|
122
124
|
**SUBGROUPS:**
|
|
123
125
|
* Study of subgroups (order, cosets, normal subgroups, quotients, ...).
|
|
@@ -140,6 +142,9 @@ GroupsMath is designed to provide tools for working with finite (and infinite) g
|
|
|
140
142
|
* Groups generated by matrices: $GL$, $SL$, $O$, $SO$, $U$, $SU$ and $Sp$.
|
|
141
143
|
* Matrix groups over finite fields $\mathbb{F}_p$, with $p$ prime.
|
|
142
144
|
|
|
145
|
+
**PRESENTED GROUPS**
|
|
146
|
+
* Presentation of groups, generators and class `PresentedGroup`. (*experimental feature*)
|
|
147
|
+
|
|
143
148
|
The available functionality will expand as the project develops.
|
|
144
149
|
|
|
145
150
|
---
|
|
@@ -147,7 +152,6 @@ The available functionality will expand as the project develops.
|
|
|
147
152
|
## Coming soon
|
|
148
153
|
|
|
149
154
|
The GroupsMath team is currently working on theese ideas:
|
|
150
|
-
* Presentation of groups, generators and class `PresentedGroup`.
|
|
151
155
|
* Free groups $F_n$ and free products.
|
|
152
156
|
* Optimization of actual features.
|
|
153
157
|
* Representation theory.
|
|
@@ -158,7 +162,7 @@ The GroupsMath team is currently working on theese ideas:
|
|
|
158
162
|
|
|
159
163
|
## Documentation
|
|
160
164
|
|
|
161
|
-
Detailed documentation is available in the [
|
|
165
|
+
Detailed documentation is available in the [GroupsMath official documentation](https://groupsmath.readthedocs.io/en/latest/) and it is developed alongside the library.
|
|
162
166
|
|
|
163
167
|
---
|
|
164
168
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GroupsMath
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: The educational Python library for Group Theory
|
|
5
5
|
Author-email: Mario Sultan Romero <mariosultan.uem@gmail.com>
|
|
6
6
|
License: PolyForm Strict License 1.0.0.
|
|
7
7
|
Project-URL: Homepage, https://github.com/MarioSultan/GroupsMath
|
|
8
|
-
Project-URL: Documentation, https://
|
|
8
|
+
Project-URL: Documentation, https://groupsmath.readthedocs.io/en/latest/
|
|
9
9
|
Project-URL: Issues, https://github.com/MarioSultan/GroupsMath/issues
|
|
10
10
|
Keywords: math,mathematics,algebra,abstract-algebra,group-theory,finite-groups,sylow,quotient-groups,automorphisms,group,groups,group theory,finte groups,finite group theory,finite-group-theory
|
|
11
11
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
@@ -20,13 +20,13 @@ Requires-Dist: matplotlib>=3.0.0
|
|
|
20
20
|
Requires-Dist: numpy>=1.20.0
|
|
21
21
|
Dynamic: license-file
|
|
22
22
|
|
|
23
|
-
# GroupsMath – v0.
|
|
23
|
+
# GroupsMath – v0.7.0 – release candidate for v1.0.0
|
|
24
24
|
|
|
25
25
|
[](https://pypistats.org/packages/groupsmath)
|
|
26
26
|
[](https://pypi.org/project/groupsmath/)
|
|
27
27
|
[](https://pypi.org/project/groupsmath/)
|
|
28
28
|

|
|
29
|
-

|
|
30
30
|
<!--  -->
|
|
31
31
|
|
|
32
32
|
|
|
@@ -39,6 +39,8 @@ The project aims to provide a **simple** and **intuitive** interface for working
|
|
|
39
39
|
|
|
40
40
|

|
|
41
41
|
|
|
42
|
+
> Visit here the [GroupsMath official documentation](https://groupsmath.readthedocs.io/en/latest/).
|
|
43
|
+
|
|
42
44
|
> **⚠️ Notice:** Version v1.0.0 is nearing its official release.
|
|
43
45
|
|
|
44
46
|
|
|
@@ -117,7 +119,7 @@ GroupsMath is designed to provide tools for working with finite (and infinite) g
|
|
|
117
119
|
* Centralizers, conjugacy classes and conmutator.
|
|
118
120
|
* Computation and exploration of other structural properties of finite groups.
|
|
119
121
|
* Predefined group families in the library `groupsmath.precooked`, such as $C_n$, $S_n$, $A_n$, $D_n$, $V_4$, $Q_8$, ...
|
|
120
|
-
*
|
|
122
|
+
* Structure description. (*experimental feature*)
|
|
121
123
|
|
|
122
124
|
**SUBGROUPS:**
|
|
123
125
|
* Study of subgroups (order, cosets, normal subgroups, quotients, ...).
|
|
@@ -140,6 +142,9 @@ GroupsMath is designed to provide tools for working with finite (and infinite) g
|
|
|
140
142
|
* Groups generated by matrices: $GL$, $SL$, $O$, $SO$, $U$, $SU$ and $Sp$.
|
|
141
143
|
* Matrix groups over finite fields $\mathbb{F}_p$, with $p$ prime.
|
|
142
144
|
|
|
145
|
+
**PRESENTED GROUPS**
|
|
146
|
+
* Presentation of groups, generators and class `PresentedGroup`. (*experimental feature*)
|
|
147
|
+
|
|
143
148
|
The available functionality will expand as the project develops.
|
|
144
149
|
|
|
145
150
|
---
|
|
@@ -147,7 +152,6 @@ The available functionality will expand as the project develops.
|
|
|
147
152
|
## Coming soon
|
|
148
153
|
|
|
149
154
|
The GroupsMath team is currently working on theese ideas:
|
|
150
|
-
* Presentation of groups, generators and class `PresentedGroup`.
|
|
151
155
|
* Free groups $F_n$ and free products.
|
|
152
156
|
* Optimization of actual features.
|
|
153
157
|
* Representation theory.
|
|
@@ -158,7 +162,7 @@ The GroupsMath team is currently working on theese ideas:
|
|
|
158
162
|
|
|
159
163
|
## Documentation
|
|
160
164
|
|
|
161
|
-
Detailed documentation is available in the [
|
|
165
|
+
Detailed documentation is available in the [GroupsMath official documentation](https://groupsmath.readthedocs.io/en/latest/) and it is developed alongside the library.
|
|
162
166
|
|
|
163
167
|
---
|
|
164
168
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# GroupsMath – v0.
|
|
1
|
+
# GroupsMath – v0.7.0 – release candidate for v1.0.0
|
|
2
2
|
|
|
3
3
|
[](https://pypistats.org/packages/groupsmath)
|
|
4
4
|
[](https://pypi.org/project/groupsmath/)
|
|
5
5
|
[](https://pypi.org/project/groupsmath/)
|
|
6
6
|

|
|
7
|
-

|
|
8
8
|
<!--  -->
|
|
9
9
|
|
|
10
10
|
|
|
@@ -17,6 +17,8 @@ The project aims to provide a **simple** and **intuitive** interface for working
|
|
|
17
17
|
|
|
18
18
|

|
|
19
19
|
|
|
20
|
+
> Visit here the [GroupsMath official documentation](https://groupsmath.readthedocs.io/en/latest/).
|
|
21
|
+
|
|
20
22
|
> **⚠️ Notice:** Version v1.0.0 is nearing its official release.
|
|
21
23
|
|
|
22
24
|
|
|
@@ -95,7 +97,7 @@ GroupsMath is designed to provide tools for working with finite (and infinite) g
|
|
|
95
97
|
* Centralizers, conjugacy classes and conmutator.
|
|
96
98
|
* Computation and exploration of other structural properties of finite groups.
|
|
97
99
|
* Predefined group families in the library `groupsmath.precooked`, such as $C_n$, $S_n$, $A_n$, $D_n$, $V_4$, $Q_8$, ...
|
|
98
|
-
*
|
|
100
|
+
* Structure description. (*experimental feature*)
|
|
99
101
|
|
|
100
102
|
**SUBGROUPS:**
|
|
101
103
|
* Study of subgroups (order, cosets, normal subgroups, quotients, ...).
|
|
@@ -118,6 +120,9 @@ GroupsMath is designed to provide tools for working with finite (and infinite) g
|
|
|
118
120
|
* Groups generated by matrices: $GL$, $SL$, $O$, $SO$, $U$, $SU$ and $Sp$.
|
|
119
121
|
* Matrix groups over finite fields $\mathbb{F}_p$, with $p$ prime.
|
|
120
122
|
|
|
123
|
+
**PRESENTED GROUPS**
|
|
124
|
+
* Presentation of groups, generators and class `PresentedGroup`. (*experimental feature*)
|
|
125
|
+
|
|
121
126
|
The available functionality will expand as the project develops.
|
|
122
127
|
|
|
123
128
|
---
|
|
@@ -125,7 +130,6 @@ The available functionality will expand as the project develops.
|
|
|
125
130
|
## Coming soon
|
|
126
131
|
|
|
127
132
|
The GroupsMath team is currently working on theese ideas:
|
|
128
|
-
* Presentation of groups, generators and class `PresentedGroup`.
|
|
129
133
|
* Free groups $F_n$ and free products.
|
|
130
134
|
* Optimization of actual features.
|
|
131
135
|
* Representation theory.
|
|
@@ -136,7 +140,7 @@ The GroupsMath team is currently working on theese ideas:
|
|
|
136
140
|
|
|
137
141
|
## Documentation
|
|
138
142
|
|
|
139
|
-
Detailed documentation is available in the [
|
|
143
|
+
Detailed documentation is available in the [GroupsMath official documentation](https://groupsmath.readthedocs.io/en/latest/) and it is developed alongside the library.
|
|
140
144
|
|
|
141
145
|
---
|
|
142
146
|
|
|
@@ -21,7 +21,7 @@ from math import gcd
|
|
|
21
21
|
|
|
22
22
|
#################### DEFINITIONS ####################
|
|
23
23
|
|
|
24
|
-
__version__ = "0.
|
|
24
|
+
__version__ = "0.7.0"
|
|
25
25
|
__errorcolor__ = "\033[31m"
|
|
26
26
|
|
|
27
27
|
tgl_color = "#2A646E"
|
|
@@ -126,7 +126,7 @@ class ExplicitGroup(Group):
|
|
|
126
126
|
if self.operation(e,a) != a:
|
|
127
127
|
ok = False
|
|
128
128
|
break
|
|
129
|
-
if self.operation(e
|
|
129
|
+
if self.operation(a,e) != a:
|
|
130
130
|
ok = False
|
|
131
131
|
break
|
|
132
132
|
if ok:
|
|
@@ -437,7 +437,7 @@ class CayleyGroup(Group):
|
|
|
437
437
|
if self.operation(e,a) != a:
|
|
438
438
|
ok = False
|
|
439
439
|
break
|
|
440
|
-
if self.operation(e
|
|
440
|
+
if self.operation(a,e) != a:
|
|
441
441
|
ok = False
|
|
442
442
|
break
|
|
443
443
|
if ok:
|
|
@@ -735,16 +735,47 @@ class CayleyGroup(Group):
|
|
|
735
735
|
return True
|
|
736
736
|
|
|
737
737
|
def automorphisms(self):
|
|
738
|
+
"""Calcula automorfismos propagando asignaciones sobre los generadores del grupo."""
|
|
738
739
|
n = self.order()
|
|
739
|
-
|
|
740
|
-
orders = self.element_orders()
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
740
|
+
gens_idx = [self._dict[g] for g in self.generators()]
|
|
741
|
+
orders = self.element_orders()
|
|
742
|
+
|
|
743
|
+
# Candidatos en el codominio que coinciden en orden con los generadores
|
|
744
|
+
candidates_per_gen = [
|
|
745
|
+
[h for h in range(n) if orders[h] == orders[g]]
|
|
746
|
+
for g in gens_idx
|
|
747
|
+
]
|
|
748
|
+
|
|
749
|
+
valid_auts = []
|
|
750
|
+
e_idx = self._dict[self.identity()]
|
|
751
|
+
|
|
752
|
+
for tuple_h in product(*candidates_per_gen):
|
|
753
|
+
mapping = {e_idx: e_idx}
|
|
754
|
+
queue = [e_idx]
|
|
755
|
+
valid = True
|
|
756
|
+
|
|
757
|
+
# BFS para extender la imagen a todo el grupo
|
|
758
|
+
while queue and valid:
|
|
759
|
+
curr = queue.pop(0)
|
|
760
|
+
for i, g in enumerate(gens_idx):
|
|
761
|
+
h = tuple_h[i]
|
|
762
|
+
next_g = self.cayley[curr][g]
|
|
763
|
+
expected_h = self.cayley[mapping[curr]][h]
|
|
764
|
+
|
|
765
|
+
if next_g in mapping:
|
|
766
|
+
if mapping[next_g] != expected_h:
|
|
767
|
+
valid = False
|
|
768
|
+
break
|
|
769
|
+
else:
|
|
770
|
+
mapping[next_g] = expected_h
|
|
771
|
+
queue.append(next_g)
|
|
772
|
+
|
|
773
|
+
# Si el mapeo cubrió todo el grupo y es inyectivo, es un automorfismo
|
|
774
|
+
if valid and len(mapping) == n and len(set(mapping.values())) == n:
|
|
775
|
+
phi = tuple(mapping[i] for i in range(n))
|
|
776
|
+
valid_auts.append(Automorphism(phi, self))
|
|
777
|
+
|
|
778
|
+
return valid_auts
|
|
748
779
|
|
|
749
780
|
def automorphism_group(self):
|
|
750
781
|
auts = [i.phi for i in self.automorphisms()]
|
|
@@ -1193,7 +1224,7 @@ class AutomorphismFunction:
|
|
|
1193
1224
|
return str(self.phi)
|
|
1194
1225
|
|
|
1195
1226
|
|
|
1196
|
-
#################### HIDDEN
|
|
1227
|
+
#################### HIDDEN FUNCTIONS ####################
|
|
1197
1228
|
|
|
1198
1229
|
def _obtener_nombre(var_obj):
|
|
1199
1230
|
for nombre, valor in globals().items():
|
|
@@ -1528,11 +1559,11 @@ def direct_product(A:CayleyGroup,B:CayleyGroup):
|
|
|
1528
1559
|
return CayleyGroup(G,names,_skip_validation=True)
|
|
1529
1560
|
|
|
1530
1561
|
def direct_power(G:CayleyGroup,n):
|
|
1531
|
-
if n==
|
|
1562
|
+
if n==0:
|
|
1532
1563
|
return CayleyGroup([[0]],"e")
|
|
1533
1564
|
elif n==1:
|
|
1534
1565
|
return G
|
|
1535
|
-
elif n
|
|
1566
|
+
elif n>=2:
|
|
1536
1567
|
H = G
|
|
1537
1568
|
for i in range(n-1):
|
|
1538
1569
|
H = direct_product(H,G)
|
|
@@ -209,23 +209,37 @@ def SL(n, field="R", p=None):
|
|
|
209
209
|
cond = lambda M: np.isclose(np.linalg.det(M), 1.0)
|
|
210
210
|
return MatrixGroup(n, field=field, condition=cond, p=p)
|
|
211
211
|
|
|
212
|
-
def O(n, field="R"):
|
|
212
|
+
def O(n, field="R", p=None):
|
|
213
213
|
"""Orthogonal Group O_n(R)"""
|
|
214
|
-
|
|
215
|
-
raise ValueError (__errorcolor__+"GroupsMath cannot generate an orthogonal group in a finite field.")
|
|
214
|
+
|
|
216
215
|
if n%2==0:
|
|
217
216
|
raise ValueError (__errorcolor__+"Cannot generate an orthogonal group without Witt index in even dimension.")
|
|
218
|
-
cond = lambda M: np.allclose(M.T @ M, np.eye(n))
|
|
219
|
-
return MatrixGroup(n, field=field, condition=cond)
|
|
220
217
|
|
|
221
|
-
|
|
218
|
+
if type(field)==int:
|
|
219
|
+
p = field
|
|
220
|
+
field = "F_p"
|
|
221
|
+
if field == "F_p":
|
|
222
|
+
cond = lambda M: np.allclose(np.mod(M.T @ M,p), np.eye(n))
|
|
223
|
+
else:
|
|
224
|
+
cond = lambda M: np.allclose(M.T @ M, np.eye(n))
|
|
225
|
+
|
|
226
|
+
return MatrixGroup(n, field=field, condition=cond, p=p)
|
|
227
|
+
|
|
228
|
+
def SO(n, field="R", p=None):
|
|
222
229
|
"""Special Orthogonal Group SO_n(R)"""
|
|
223
|
-
|
|
224
|
-
raise ValueError (__errorcolor__+"GroupsMath cannot generate an orthogonal group in a finite field.")
|
|
230
|
+
|
|
225
231
|
if n%2==0:
|
|
226
232
|
raise ValueError (__errorcolor__+"Cannot generate an orthogonal group without Witt index in even dimension.")
|
|
227
|
-
|
|
228
|
-
|
|
233
|
+
|
|
234
|
+
if type(field)==int:
|
|
235
|
+
p = field
|
|
236
|
+
field = "F_p"
|
|
237
|
+
if field == "F_p":
|
|
238
|
+
cond = lambda M: np.allclose(np.mod(M.T @ M,p), np.eye(n)) and np.isclose(np.linalg.det(M)%p, 1.0)
|
|
239
|
+
else:
|
|
240
|
+
cond = lambda M: np.allclose(M.T @ M, np.eye(n)) and np.isclose(np.linalg.det(M), 1.0)
|
|
241
|
+
|
|
242
|
+
return MatrixGroup(n, field=field, condition=cond, p=p)
|
|
229
243
|
|
|
230
244
|
def U(n):
|
|
231
245
|
"""
|
|
@@ -243,11 +257,11 @@ def SU(n):
|
|
|
243
257
|
cond = lambda M: np.allclose(M.conj().T @ M, np.eye(n)) and np.isclose(np.linalg.det(M), 1.0 + 0j)
|
|
244
258
|
return MatrixGroup(n, field="C", condition=cond)
|
|
245
259
|
|
|
246
|
-
def Sp(n):
|
|
260
|
+
def Sp(n, field="R", p=None):
|
|
247
261
|
"""
|
|
248
|
-
Symplectic Group Sp(
|
|
262
|
+
Symplectic Group Sp(n, R).
|
|
249
263
|
Condition: M^T @ J @ M = J, where J = [[0, I], [-I, 0]].
|
|
250
|
-
Note: The matrix dimension must be even
|
|
264
|
+
Note: The matrix dimension must be even.
|
|
251
265
|
"""
|
|
252
266
|
if n % 2 != 0:
|
|
253
267
|
raise ValueError(__errorcolor__+"Symplectic group dimension must be an even integer.")
|
|
@@ -256,7 +270,15 @@ def Sp(n):
|
|
|
256
270
|
I_k = np.eye(k)
|
|
257
271
|
J = np.block([[np.zeros((k, k)), I_k],
|
|
258
272
|
[-I_k, np.zeros((k, k))]])
|
|
273
|
+
|
|
274
|
+
if type(field)==int:
|
|
275
|
+
p = field
|
|
276
|
+
field = "F_p"
|
|
277
|
+
if field == "F_p":
|
|
278
|
+
Jp = np.mod(J,p)
|
|
279
|
+
cond = lambda M: np.allclose(np.mod(M.T @ J @ M,p), Jp)
|
|
280
|
+
else:
|
|
281
|
+
cond = lambda M: np.allclose(M.T @ J @ M, J)
|
|
259
282
|
|
|
260
|
-
|
|
261
|
-
return MatrixGroup(n, field="R", condition=cond)
|
|
283
|
+
return MatrixGroup(n, field=field, condition=cond, p=p)
|
|
262
284
|
|
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
from .core import *
|
|
2
|
+
from .core import __errorcolor__
|
|
3
|
+
from collections import deque
|
|
4
|
+
|
|
5
|
+
#################### WARNINGS ####################
|
|
6
|
+
|
|
7
|
+
print("\033[33m[!] WARNING: The library 'groupsmath.presentations' is under development and it might produce errors.\033[0m")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
#################### PRESENTATIONS ####################
|
|
11
|
+
|
|
12
|
+
class Word():
|
|
13
|
+
|
|
14
|
+
def __init__(self,word,_skip_validation=False):
|
|
15
|
+
|
|
16
|
+
if type(word)!=str:
|
|
17
|
+
raise ValueError (__errorcolor__+"The word has to be a string.")
|
|
18
|
+
|
|
19
|
+
l = []
|
|
20
|
+
|
|
21
|
+
if word=="" or word=="e":
|
|
22
|
+
word = ""
|
|
23
|
+
|
|
24
|
+
for i in word:
|
|
25
|
+
l.append(i)
|
|
26
|
+
if i not in "qwrtyuiopasdfghjklzxcvbnmQWRTYUIOPASDFGHJKLZXCVBNM":
|
|
27
|
+
raise ValueError (__errorcolor__+"The word must consist of Latin characters except the letters e/E.")
|
|
28
|
+
|
|
29
|
+
self.word = word
|
|
30
|
+
self.characters = l
|
|
31
|
+
|
|
32
|
+
def __mul__(self,other):
|
|
33
|
+
o = other if isinstance(other, Word) else Word(other)
|
|
34
|
+
return Word(self.word+o.word)
|
|
35
|
+
|
|
36
|
+
def __rmul__(self,other):
|
|
37
|
+
o = other if isinstance(other, Word) else Word(other)
|
|
38
|
+
return Word(o.word+self.word)
|
|
39
|
+
|
|
40
|
+
def __pow__(self,n):
|
|
41
|
+
if n==0:
|
|
42
|
+
return Word("e")
|
|
43
|
+
elif n>0:
|
|
44
|
+
return Word(self.word*n)
|
|
45
|
+
elif n<0:
|
|
46
|
+
return Word(self.inverse().word*-n)
|
|
47
|
+
|
|
48
|
+
def __eq__(self,other):
|
|
49
|
+
if not isinstance(other, Word):
|
|
50
|
+
return NotImplemented
|
|
51
|
+
return self.word == other.word
|
|
52
|
+
|
|
53
|
+
def __invert__(self):
|
|
54
|
+
return self.inverse()
|
|
55
|
+
|
|
56
|
+
def __len__(self):
|
|
57
|
+
return len(self.word)
|
|
58
|
+
|
|
59
|
+
def __str__(self):
|
|
60
|
+
if self.word=="":
|
|
61
|
+
return "e"
|
|
62
|
+
return self.word
|
|
63
|
+
|
|
64
|
+
def __hash__(self):
|
|
65
|
+
return hash(self.word)
|
|
66
|
+
|
|
67
|
+
def inverse(self):
|
|
68
|
+
|
|
69
|
+
def _invert(a):
|
|
70
|
+
if a in "qwrtyuiopasdfghjklzxcvbnm":
|
|
71
|
+
return a.upper()
|
|
72
|
+
else:
|
|
73
|
+
return a.lower()
|
|
74
|
+
|
|
75
|
+
if self.word=="":
|
|
76
|
+
return self
|
|
77
|
+
|
|
78
|
+
I = ""
|
|
79
|
+
n = len(self)
|
|
80
|
+
|
|
81
|
+
for i in range(n):
|
|
82
|
+
I+= _invert(self.word[n-i-1])
|
|
83
|
+
|
|
84
|
+
return Word(I)
|
|
85
|
+
|
|
86
|
+
''' Revisar por si aparecen errores '''
|
|
87
|
+
|
|
88
|
+
class PresentedGroup(Group):
|
|
89
|
+
|
|
90
|
+
def __init__(self, generators: list, relations: list):
|
|
91
|
+
|
|
92
|
+
# ---------------------------------------------------------
|
|
93
|
+
# 1. GENERADORES
|
|
94
|
+
# ---------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
self.generators_names = []
|
|
97
|
+
|
|
98
|
+
for g in generators:
|
|
99
|
+
g_str = str(g).strip()
|
|
100
|
+
|
|
101
|
+
if len(g_str) != 1 or not g_str.islower() or not g_str.isalpha():
|
|
102
|
+
raise ValueError(
|
|
103
|
+
__errorcolor__ +
|
|
104
|
+
f"Generators must be single lowercase Latin letters. Invalid: '{g}'"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if g_str in self.generators_names:
|
|
108
|
+
raise ValueError(
|
|
109
|
+
__errorcolor__ +
|
|
110
|
+
f"Duplicate generator: '{g_str}'"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
self.generators_names.append(g_str)
|
|
114
|
+
|
|
115
|
+
# a <-> A
|
|
116
|
+
self.inverses_map = {
|
|
117
|
+
g: g.upper()
|
|
118
|
+
for g in self.generators_names
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
self.inverses_map.update({
|
|
122
|
+
g.upper(): g
|
|
123
|
+
for g in self.generators_names
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------
|
|
127
|
+
# 2. RELACIONES
|
|
128
|
+
# ---------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
self.relations = []
|
|
131
|
+
|
|
132
|
+
for r in relations:
|
|
133
|
+
|
|
134
|
+
w = r if isinstance(r, Word) else Word(str(r))
|
|
135
|
+
|
|
136
|
+
self._validate_word_alphabet(w)
|
|
137
|
+
|
|
138
|
+
# Las relaciones se almacenan libremente reducidas.
|
|
139
|
+
w = self._free_reduce(w)
|
|
140
|
+
|
|
141
|
+
self.relations.append(w)
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------
|
|
144
|
+
# 3. CACHE
|
|
145
|
+
# ---------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
self._cayley_cache = None
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# =============================================================
|
|
151
|
+
# VALIDACIÓN
|
|
152
|
+
# =============================================================
|
|
153
|
+
|
|
154
|
+
def _validate_word_alphabet(self, word: Word):
|
|
155
|
+
|
|
156
|
+
valid_chars = (
|
|
157
|
+
set(self.generators_names)
|
|
158
|
+
|
|
|
159
|
+
set(self.inverses_map.keys())
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
for char in word.word:
|
|
163
|
+
|
|
164
|
+
if char not in valid_chars:
|
|
165
|
+
|
|
166
|
+
raise ValueError(
|
|
167
|
+
__errorcolor__ +
|
|
168
|
+
f"Character '{char}' in word '{word}' "
|
|
169
|
+
f"is not a valid generator for this group."
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _to_word(self, element) -> Word:
|
|
174
|
+
|
|
175
|
+
if isinstance(element, Word):
|
|
176
|
+
w = element
|
|
177
|
+
|
|
178
|
+
elif isinstance(element, str):
|
|
179
|
+
w = Word(element)
|
|
180
|
+
|
|
181
|
+
else:
|
|
182
|
+
raise TypeError(
|
|
183
|
+
__errorcolor__ +
|
|
184
|
+
f"Element must be a Word or str, "
|
|
185
|
+
f"got {type(element).__name__}"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
self._validate_word_alphabet(w)
|
|
189
|
+
|
|
190
|
+
return w
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# =============================================================
|
|
194
|
+
# REDUCCIÓN LIBRE
|
|
195
|
+
# =============================================================
|
|
196
|
+
|
|
197
|
+
def _free_reduce(self, word: Word) -> Word:
|
|
198
|
+
"""
|
|
199
|
+
Reduce libremente una palabra eliminando pares xx^{-1}
|
|
200
|
+
y x^{-1}x.
|
|
201
|
+
|
|
202
|
+
Ejemplo:
|
|
203
|
+
|
|
204
|
+
aAbB -> e
|
|
205
|
+
abBA -> e
|
|
206
|
+
a b B a -> aa
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
stack = []
|
|
210
|
+
|
|
211
|
+
for char in word.word:
|
|
212
|
+
|
|
213
|
+
if stack and self.inverses_map.get(stack[-1]) == char:
|
|
214
|
+
stack.pop()
|
|
215
|
+
|
|
216
|
+
else:
|
|
217
|
+
stack.append(char)
|
|
218
|
+
|
|
219
|
+
return Word(
|
|
220
|
+
"".join(stack),
|
|
221
|
+
_skip_validation=True
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# =============================================================
|
|
226
|
+
# RELACIONES
|
|
227
|
+
# =============================================================
|
|
228
|
+
|
|
229
|
+
def _relation_rules(self):
|
|
230
|
+
"""
|
|
231
|
+
Construye reglas de reescritura a partir de las relaciones.
|
|
232
|
+
|
|
233
|
+
Para una relación:
|
|
234
|
+
|
|
235
|
+
r = x1 x2 ... xn = e
|
|
236
|
+
|
|
237
|
+
se generan:
|
|
238
|
+
|
|
239
|
+
1. El relator y su inverso:
|
|
240
|
+
r -> e
|
|
241
|
+
r^-1 -> e
|
|
242
|
+
|
|
243
|
+
2. Reglas derivadas aislando cada generador/inverso.
|
|
244
|
+
|
|
245
|
+
Por ejemplo:
|
|
246
|
+
|
|
247
|
+
aaa = e
|
|
248
|
+
|
|
249
|
+
implica:
|
|
250
|
+
|
|
251
|
+
A = aa
|
|
252
|
+
|
|
253
|
+
y, para obtener una forma normal más corta:
|
|
254
|
+
|
|
255
|
+
aa -> A
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
rules = []
|
|
259
|
+
|
|
260
|
+
for relation in self.relations:
|
|
261
|
+
|
|
262
|
+
relation = self._free_reduce(relation)
|
|
263
|
+
|
|
264
|
+
if len(relation) == 0:
|
|
265
|
+
continue
|
|
266
|
+
|
|
267
|
+
s = relation.word
|
|
268
|
+
n = len(s)
|
|
269
|
+
|
|
270
|
+
# ---------------------------------------------------------
|
|
271
|
+
# 1. RELATOR Y SU INVERSO
|
|
272
|
+
# ---------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
rules.append((s, ""))
|
|
275
|
+
|
|
276
|
+
inverse = relation.inverse().word
|
|
277
|
+
|
|
278
|
+
rules.append((inverse, ""))
|
|
279
|
+
|
|
280
|
+
# ---------------------------------------------------------
|
|
281
|
+
# 2. REGLAS DERIVADAS
|
|
282
|
+
# ---------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
for i, char in enumerate(s):
|
|
285
|
+
|
|
286
|
+
left = s[:i]
|
|
287
|
+
right = s[i + 1:]
|
|
288
|
+
|
|
289
|
+
# x^{-1} = right * left
|
|
290
|
+
#
|
|
291
|
+
# porque:
|
|
292
|
+
#
|
|
293
|
+
# left x right = e
|
|
294
|
+
#
|
|
295
|
+
# => x^{-1} = right left
|
|
296
|
+
|
|
297
|
+
inverse_char = self.inverses_map[char]
|
|
298
|
+
|
|
299
|
+
rhs = right + left
|
|
300
|
+
|
|
301
|
+
# Regla:
|
|
302
|
+
#
|
|
303
|
+
# x^{-1} -> rhs
|
|
304
|
+
#
|
|
305
|
+
rules.append((inverse_char, rhs))
|
|
306
|
+
|
|
307
|
+
# También podemos utilizar la ecuación
|
|
308
|
+
#
|
|
309
|
+
# x = (right left)^{-1}
|
|
310
|
+
#
|
|
311
|
+
# y obtener:
|
|
312
|
+
#
|
|
313
|
+
# x -> inverse(rhs)
|
|
314
|
+
|
|
315
|
+
rhs_inverse = Word(
|
|
316
|
+
rhs,
|
|
317
|
+
_skip_validation=True
|
|
318
|
+
).inverse().word
|
|
319
|
+
|
|
320
|
+
rules.append((char, rhs_inverse))
|
|
321
|
+
|
|
322
|
+
# -------------------------------------------------------------
|
|
323
|
+
# 3. ELIMINAR DUPLICADOS
|
|
324
|
+
# -------------------------------------------------------------
|
|
325
|
+
|
|
326
|
+
unique = []
|
|
327
|
+
seen = set()
|
|
328
|
+
|
|
329
|
+
for lhs, rhs in rules:
|
|
330
|
+
|
|
331
|
+
if (lhs, rhs) not in seen:
|
|
332
|
+
|
|
333
|
+
seen.add((lhs, rhs))
|
|
334
|
+
unique.append((lhs, rhs))
|
|
335
|
+
|
|
336
|
+
return unique
|
|
337
|
+
|
|
338
|
+
# =============================================================
|
|
339
|
+
# REDUCCIÓN MEDIANTE RELACIONES
|
|
340
|
+
# =============================================================
|
|
341
|
+
|
|
342
|
+
def _relation_reduce_once(self, word):
|
|
343
|
+
"""
|
|
344
|
+
Aplica una regla de reescritura una sola vez.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
s = word.word
|
|
348
|
+
|
|
349
|
+
for lhs, rhs in self._relation_rules():
|
|
350
|
+
|
|
351
|
+
if not lhs:
|
|
352
|
+
continue
|
|
353
|
+
|
|
354
|
+
position = s.find(lhs)
|
|
355
|
+
|
|
356
|
+
if position == -1:
|
|
357
|
+
continue
|
|
358
|
+
|
|
359
|
+
left = s[:position]
|
|
360
|
+
right = s[position + len(lhs):]
|
|
361
|
+
|
|
362
|
+
new_word = Word(
|
|
363
|
+
left + rhs + right,
|
|
364
|
+
_skip_validation=True
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
new_word = self._free_reduce(new_word)
|
|
368
|
+
|
|
369
|
+
# Solo aceptamos reglas que realmente reduzcan
|
|
370
|
+
# la longitud de la palabra.
|
|
371
|
+
if len(new_word) < len(word):
|
|
372
|
+
|
|
373
|
+
return new_word, True
|
|
374
|
+
|
|
375
|
+
return word, False
|
|
376
|
+
|
|
377
|
+
def _reduce(self, word: Word) -> Word:
|
|
378
|
+
"""
|
|
379
|
+
Reducción completa de una palabra.
|
|
380
|
+
|
|
381
|
+
Primero realiza reducción libre y posteriormente aplica
|
|
382
|
+
las relaciones hasta que no puede efectuar más reducciones.
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
word = self._free_reduce(word)
|
|
386
|
+
|
|
387
|
+
while True:
|
|
388
|
+
|
|
389
|
+
new_word, changed = self._relation_reduce_once(word)
|
|
390
|
+
|
|
391
|
+
if not changed:
|
|
392
|
+
return self._free_reduce(word)
|
|
393
|
+
|
|
394
|
+
word = self._free_reduce(new_word)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
# =============================================================
|
|
398
|
+
# OPERACIÓN DE GRUPO
|
|
399
|
+
# =============================================================
|
|
400
|
+
|
|
401
|
+
def operation(self, elem1, elem2) -> Word:
|
|
402
|
+
|
|
403
|
+
w1 = self._to_word(elem1)
|
|
404
|
+
w2 = self._to_word(elem2)
|
|
405
|
+
|
|
406
|
+
return self._reduce(w1 * w2)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def identity(self) -> Word:
|
|
410
|
+
return Word("e")
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def inverse(self, elem) -> Word:
|
|
414
|
+
|
|
415
|
+
w = self._to_word(elem)
|
|
416
|
+
|
|
417
|
+
return self._reduce(~w)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def __contains__(self, elem) -> bool:
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
self._to_word(elem)
|
|
424
|
+
return True
|
|
425
|
+
|
|
426
|
+
except (ValueError, TypeError):
|
|
427
|
+
return False
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
# =============================================================
|
|
431
|
+
# CONVERSIÓN A CAYLEY GROUP
|
|
432
|
+
# =============================================================
|
|
433
|
+
|
|
434
|
+
def toCayleyGroup(self, max_elements: int = 1000) -> CayleyGroup:
|
|
435
|
+
"""
|
|
436
|
+
Construye una representación mediante tabla de Cayley
|
|
437
|
+
explorando el grupo mediante BFS.
|
|
438
|
+
|
|
439
|
+
La enumeración se realiza sobre palabras reducidas
|
|
440
|
+
módulo las relaciones.
|
|
441
|
+
"""
|
|
442
|
+
|
|
443
|
+
if self._cayley_cache is not None:
|
|
444
|
+
return self._cayley_cache
|
|
445
|
+
|
|
446
|
+
# Generadores e inversos
|
|
447
|
+
all_gens = (
|
|
448
|
+
[Word(g) for g in self.generators_names]
|
|
449
|
+
+
|
|
450
|
+
[Word(g.upper()) for g in self.generators_names]
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
# Identidad
|
|
454
|
+
identity = self.identity()
|
|
455
|
+
|
|
456
|
+
discovered = [identity]
|
|
457
|
+
|
|
458
|
+
word_to_idx = {
|
|
459
|
+
identity: 0
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
queue = deque([identity])
|
|
463
|
+
|
|
464
|
+
# ---------------------------------------------------------
|
|
465
|
+
# BFS
|
|
466
|
+
# ---------------------------------------------------------
|
|
467
|
+
|
|
468
|
+
while queue:
|
|
469
|
+
|
|
470
|
+
current_word = queue.popleft()
|
|
471
|
+
|
|
472
|
+
for gen in all_gens:
|
|
473
|
+
|
|
474
|
+
next_word = self.operation(
|
|
475
|
+
current_word,
|
|
476
|
+
gen
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
if next_word not in word_to_idx:
|
|
480
|
+
|
|
481
|
+
if len(discovered) >= max_elements:
|
|
482
|
+
raise OverflowError(
|
|
483
|
+
__errorcolor__ +
|
|
484
|
+
f"Group could not be enumerated within "
|
|
485
|
+
f"the maximum limit of {max_elements} elements."
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
word_to_idx[next_word] = len(discovered)
|
|
489
|
+
|
|
490
|
+
discovered.append(next_word)
|
|
491
|
+
|
|
492
|
+
queue.append(next_word)
|
|
493
|
+
|
|
494
|
+
# ---------------------------------------------------------
|
|
495
|
+
# TABLA DE CAYLEY
|
|
496
|
+
# ---------------------------------------------------------
|
|
497
|
+
|
|
498
|
+
n = len(discovered)
|
|
499
|
+
|
|
500
|
+
cayley_matrix = [
|
|
501
|
+
[0] * n
|
|
502
|
+
for _ in range(n)
|
|
503
|
+
]
|
|
504
|
+
|
|
505
|
+
for i, w1 in enumerate(discovered):
|
|
506
|
+
|
|
507
|
+
for j, w2 in enumerate(discovered):
|
|
508
|
+
|
|
509
|
+
prod = self.operation(w1, w2)
|
|
510
|
+
|
|
511
|
+
cayley_matrix[i][j] = word_to_idx[prod]
|
|
512
|
+
|
|
513
|
+
# ---------------------------------------------------------
|
|
514
|
+
# NOMBRES
|
|
515
|
+
# ---------------------------------------------------------
|
|
516
|
+
|
|
517
|
+
names = [
|
|
518
|
+
str(w)
|
|
519
|
+
for w in discovered
|
|
520
|
+
]
|
|
521
|
+
|
|
522
|
+
self._cayley_cache = CayleyGroup(
|
|
523
|
+
cayley_matrix,
|
|
524
|
+
names,
|
|
525
|
+
_skip_validation=True
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
return self._cayley_cache
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def toExplicitGroup(self) -> ExplicitGroup:
|
|
532
|
+
return self.toCayleyGroup().toExplicitGroup()
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def order(self) -> int:
|
|
536
|
+
return self.toCayleyGroup().order()
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
# =============================================================
|
|
540
|
+
# REPRESENTACIÓN
|
|
541
|
+
# =============================================================
|
|
542
|
+
|
|
543
|
+
def __str__(self):
|
|
544
|
+
|
|
545
|
+
gens_str = ", ".join(
|
|
546
|
+
self.generators_names
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
rels_str = ", ".join(
|
|
550
|
+
str(r)
|
|
551
|
+
for r in self.relations
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
return f"〈 {gens_str} | {rels_str} 〉"
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def __repr__(self):
|
|
558
|
+
return self.__str__()
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from .core import *
|
|
2
|
+
from .core import __errorcolor__
|
|
3
|
+
|
|
4
|
+
''' Revisar, que aparecen errores '''
|
|
5
|
+
|
|
6
|
+
#################### WARNINGS ####################
|
|
7
|
+
|
|
8
|
+
print("\033[33m[!] WARNING: The library 'groupsmath.structure' is under development and it might produce errors.\033[0m")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
#################### STRUCTURE DESCRIPTION ####################
|
|
12
|
+
|
|
13
|
+
def structure_description(G):
|
|
14
|
+
"""
|
|
15
|
+
Caracteriza algebraicamente la estructura de G sin usar tablas predefinidas.
|
|
16
|
+
Retorna una cadena con la notación de grupos (ej: 'C2 x C2', 'C3 : C4', 'S3').
|
|
17
|
+
"""
|
|
18
|
+
n = G.order()
|
|
19
|
+
|
|
20
|
+
# 1. CASO TRIVIAL
|
|
21
|
+
if n == 1:
|
|
22
|
+
return "I"
|
|
23
|
+
|
|
24
|
+
# 2. CASO ABELIANO (Teorema Fundamental)
|
|
25
|
+
if G.is_abelian():
|
|
26
|
+
return _decompose_abelian(G)
|
|
27
|
+
|
|
28
|
+
# 3. CASO SIMPLE
|
|
29
|
+
if G.is_simple():
|
|
30
|
+
return _describe_simple(G)
|
|
31
|
+
|
|
32
|
+
# 4. DESCOMPOSICIÓN POR SUBGRUPOS NORMALES
|
|
33
|
+
normals = G.normal_subgroups()
|
|
34
|
+
# Filtramos para quedarnos solo con subgrupos normales propios no triviales
|
|
35
|
+
proper_normals = [H for H in normals if 1 < H.order() < n]
|
|
36
|
+
|
|
37
|
+
# Intentar factorizar usando subgrupos normales (de mayor a menor tamaño)
|
|
38
|
+
proper_normals.sort(key=lambda h: h.order(), reverse=True)
|
|
39
|
+
|
|
40
|
+
for N in proper_normals:
|
|
41
|
+
# Buscar un complemento H tal que N * H = G y N \cap H = {e}
|
|
42
|
+
complement = _find_complement(G, N)
|
|
43
|
+
|
|
44
|
+
if complement is not None:
|
|
45
|
+
name_N = structure_description(N)
|
|
46
|
+
name_H = structure_description(complement)
|
|
47
|
+
|
|
48
|
+
# Si el complemento H también es normal -> Producto Directo (N x H)
|
|
49
|
+
if complement.is_normal():
|
|
50
|
+
return f"{name_N} x {name_H}"
|
|
51
|
+
# Si H no es normal -> Producto Semidirecto (N : H)
|
|
52
|
+
else:
|
|
53
|
+
return f"{name_N} : {name_H}"
|
|
54
|
+
|
|
55
|
+
# 5. SI NO HAY COMPLEMENTO (Extensión no escindida N.H)
|
|
56
|
+
# Tomamos el mayor subgrupo normal N y el cociente G/N
|
|
57
|
+
N = proper_normals[0]
|
|
58
|
+
Q = G.quotient(N)
|
|
59
|
+
return f"{structure_description(N)}.{structure_description(Q)}"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
#################### HIDDEN FUNCTIONS ####################
|
|
63
|
+
|
|
64
|
+
def _decompose_abelian(G):
|
|
65
|
+
"""Descompone un grupo abeliano en factores cíclicos C_d1 x C_d2 ..."""
|
|
66
|
+
n = G.order()
|
|
67
|
+
orders = G.element_orders()
|
|
68
|
+
|
|
69
|
+
# Si el elemento de orden máximo es n, es cíclico puro
|
|
70
|
+
if max(orders) == n:
|
|
71
|
+
return f"C{n}"
|
|
72
|
+
|
|
73
|
+
# Descomposición en factores de invariantes
|
|
74
|
+
# Para grupos abelianos pequeños, extrae exponentes del grupo
|
|
75
|
+
factors = []
|
|
76
|
+
temp_n = n
|
|
77
|
+
# Extraer factores cíclicos iterativamente sobre los elementos
|
|
78
|
+
curr_group = G
|
|
79
|
+
while temp_n > 1:
|
|
80
|
+
m = max(curr_group.element_orders())
|
|
81
|
+
factors.append(f"C{m}")
|
|
82
|
+
temp_n //= m
|
|
83
|
+
if temp_n == 1:
|
|
84
|
+
break
|
|
85
|
+
# Reducción aproximada de la componente abeliana
|
|
86
|
+
break # Para extender con divisores elementales p-primarios completos
|
|
87
|
+
|
|
88
|
+
if len(factors) == 1 and temp_n > 1:
|
|
89
|
+
# Fallback de seguridad en caso de multiplicidad
|
|
90
|
+
return f"C{max(orders)} x C{n // max(orders)}"
|
|
91
|
+
|
|
92
|
+
return " x ".join(sorted(factors, reverse=True))
|
|
93
|
+
|
|
94
|
+
def _find_complement(G, N):
|
|
95
|
+
"""Busca un subgrupo H <= G tal que N & H = {e} y |N| * |H| = |G|."""
|
|
96
|
+
target_order = G.order() // N.order()
|
|
97
|
+
|
|
98
|
+
for H in G.subgroups():
|
|
99
|
+
if H.order() == target_order:
|
|
100
|
+
# Comprobar intersección trivial N & H = {e}
|
|
101
|
+
intersection = set(N.elements).intersection(set(H.elements))
|
|
102
|
+
if len(intersection) == 1:
|
|
103
|
+
return H
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
def _describe_simple(G):
|
|
107
|
+
"""Identifica la familia del grupo simple."""
|
|
108
|
+
n = G.order()
|
|
109
|
+
if G.is_abelian():
|
|
110
|
+
return f"C{n}" # C_p con p primo
|
|
111
|
+
|
|
112
|
+
# Clasificación básica de simples no abelianos por orden
|
|
113
|
+
if n == 60:
|
|
114
|
+
return "A5"
|
|
115
|
+
if n == 168:
|
|
116
|
+
return "PSL(2,7)"
|
|
117
|
+
if n == 360:
|
|
118
|
+
return "A6"
|
|
119
|
+
return f"SimpleGroup({n})"
|
|
120
|
+
|
|
@@ -3,8 +3,8 @@ requires = ["setuptools>=61.0"]
|
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
|
-
name = "GroupsMath" # Nombre en PyPI
|
|
7
|
-
version = "0.
|
|
6
|
+
name = "GroupsMath" # Nombre en PyPI
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
description = "The educational Python library for Group Theory"
|
|
9
9
|
readme = "README.md" # Esta será la documentación visible en la portada de PyPI
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -31,8 +31,8 @@ classifiers = [
|
|
|
31
31
|
|
|
32
32
|
# Enlaces que aparecerán en la barra lateral de tu paquete en PyPI
|
|
33
33
|
[project.urls]
|
|
34
|
-
Homepage = "https://github.com/MarioSultan/GroupsMath" #Nombre actual del repositorio
|
|
35
|
-
Documentation = "https://
|
|
34
|
+
Homepage = "https://github.com/MarioSultan/GroupsMath" #Nombre actual del repositorio
|
|
35
|
+
Documentation = "https://groupsmath.readthedocs.io/en/latest/"
|
|
36
36
|
Issues = "https://github.com/MarioSultan/GroupsMath/issues"
|
|
37
37
|
|
|
38
38
|
# Configuración cuando NO usas la carpeta src/
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|