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.
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GroupsMath
3
- Version: 0.6.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://github.com/MarioSultan/GroupsMath/tree/main/docs
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.6.0
23
+ # GroupsMath – v0.7.0 – release candidate for v1.0.0
24
24
 
25
25
  [![PyPI Downloads](https://img.shields.io/pypi/dm/groupsmath)](https://pypistats.org/packages/groupsmath)
26
26
  [![PyPI Version](https://img.shields.io/pypi/v/groupsmath?color=2A646E)](https://pypi.org/project/groupsmath/)
27
27
  [![Python Versions](https://img.shields.io/pypi/pyversions/groupsmath?color=2A646E)](https://pypi.org/project/groupsmath/)
28
28
  ![Last Commit](https://img.shields.io/github/last-commit/MarioSultan/groupsmath?color=2A646E)
29
- ![coverage](https://img.shields.io/badge/road%20to%20v1.0.0-85%25-yellowgreen?color=2A646E)
29
+ ![coverage](https://img.shields.io/badge/road%20to%20v1.0.0-95%25-yellowgreen?color=2A646E)
30
30
  <!-- ![GitHub Stars](https://img.shields.io/github/stars/MarioSultan/groupsmath?style=social) -->
31
31
 
32
32
 
@@ -39,6 +39,8 @@ The project aims to provide a **simple** and **intuitive** interface for working
39
39
 
40
40
  ![Examples](https://raw.githubusercontent.com/MarioSultan/GroupsMath/main/imgs/examples.png)
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
- * Isomorphisms between groups.
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 [`docs`](https://github.com/MarioSultan/GroupsMath/tree/main/docs) directory of GitHub and it is developed alongside the library.
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
 
@@ -10,4 +10,6 @@ groupsmath/__init__.py
10
10
  groupsmath/core.py
11
11
  groupsmath/matrixgroups.py
12
12
  groupsmath/precooked.py
13
- groupsmath/shortcuts.py
13
+ groupsmath/presentations.py
14
+ groupsmath/shortcuts.py
15
+ groupsmath/structure.py
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GroupsMath
3
- Version: 0.6.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://github.com/MarioSultan/GroupsMath/tree/main/docs
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.6.0
23
+ # GroupsMath – v0.7.0 – release candidate for v1.0.0
24
24
 
25
25
  [![PyPI Downloads](https://img.shields.io/pypi/dm/groupsmath)](https://pypistats.org/packages/groupsmath)
26
26
  [![PyPI Version](https://img.shields.io/pypi/v/groupsmath?color=2A646E)](https://pypi.org/project/groupsmath/)
27
27
  [![Python Versions](https://img.shields.io/pypi/pyversions/groupsmath?color=2A646E)](https://pypi.org/project/groupsmath/)
28
28
  ![Last Commit](https://img.shields.io/github/last-commit/MarioSultan/groupsmath?color=2A646E)
29
- ![coverage](https://img.shields.io/badge/road%20to%20v1.0.0-85%25-yellowgreen?color=2A646E)
29
+ ![coverage](https://img.shields.io/badge/road%20to%20v1.0.0-95%25-yellowgreen?color=2A646E)
30
30
  <!-- ![GitHub Stars](https://img.shields.io/github/stars/MarioSultan/groupsmath?style=social) -->
31
31
 
32
32
 
@@ -39,6 +39,8 @@ The project aims to provide a **simple** and **intuitive** interface for working
39
39
 
40
40
  ![Examples](https://raw.githubusercontent.com/MarioSultan/GroupsMath/main/imgs/examples.png)
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
- * Isomorphisms between groups.
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 [`docs`](https://github.com/MarioSultan/GroupsMath/tree/main/docs) directory of GitHub and it is developed alongside the library.
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.6.0
1
+ # GroupsMath – v0.7.0 – release candidate for v1.0.0
2
2
 
3
3
  [![PyPI Downloads](https://img.shields.io/pypi/dm/groupsmath)](https://pypistats.org/packages/groupsmath)
4
4
  [![PyPI Version](https://img.shields.io/pypi/v/groupsmath?color=2A646E)](https://pypi.org/project/groupsmath/)
5
5
  [![Python Versions](https://img.shields.io/pypi/pyversions/groupsmath?color=2A646E)](https://pypi.org/project/groupsmath/)
6
6
  ![Last Commit](https://img.shields.io/github/last-commit/MarioSultan/groupsmath?color=2A646E)
7
- ![coverage](https://img.shields.io/badge/road%20to%20v1.0.0-85%25-yellowgreen?color=2A646E)
7
+ ![coverage](https://img.shields.io/badge/road%20to%20v1.0.0-95%25-yellowgreen?color=2A646E)
8
8
  <!-- ![GitHub Stars](https://img.shields.io/github/stars/MarioSultan/groupsmath?style=social) -->
9
9
 
10
10
 
@@ -17,6 +17,8 @@ The project aims to provide a **simple** and **intuitive** interface for working
17
17
 
18
18
  ![Examples](https://raw.githubusercontent.com/MarioSultan/GroupsMath/main/imgs/examples.png)
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
- * Isomorphisms between groups.
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 [`docs`](https://github.com/MarioSultan/GroupsMath/tree/main/docs) directory of GitHub and it is developed alongside the library.
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.6.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,a) != a:
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,a) != a:
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
- auts = []
740
- orders = self.element_orders()[:]
741
- for p in _order_preserving_permutations(orders):
742
- try:
743
- auts.append(Automorphism(p,self))
744
- except:
745
- pass
746
-
747
- return auts
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 COMMANDS ####################
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==2:
1562
+ if n==0:
1532
1563
  return CayleyGroup([[0]],"e")
1533
1564
  elif n==1:
1534
1565
  return G
1535
- elif n>2:
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
- if field=="F_p" or type(field)==int:
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
- def SO(n, field="R"):
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
- if field=="F_p" or type(field)==int:
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
- cond = lambda M: np.allclose(M.T @ M, np.eye(n)) and np.isclose(np.linalg.det(M), 1.0)
228
- return MatrixGroup(n, field=field, condition=cond)
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(2n, R) over real matrices.
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 (2n).
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
- cond = lambda M: np.allclose(M.T @ J @ M, J)
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 (ej: mi-paquete)
7
- version = "0.6.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 (voy a cambiarlo en breve)
35
- Documentation = "https://github.com/MarioSultan/GroupsMath/tree/main/docs" # <-- Enlace a tu carpeta docs
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